Loading repository data…
Loading repository data…
b4dc0de / repository
Tiny process runner for Linux / Unix-like systems that securely hands off a secret to a child process over a dedicated file descriptor.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
__
___ ___ __ ______/ /
/ _ \/ _ \/ // / __/ _ \
/ .__/\___/\_,_/\__/_//_/
/_/
Pouch is a process runner for Linux and Unix-like systems that delivers secrets to your app over a dedicated file descriptor—gloriously over-engineered to implement every form of secure hardening imaginable, regardless of utility.
It’s for people who wouldn’t trust corporate cloud vaults like AWS Secrets Manager or Google Secret Manager, and who roll their eyes at hardware like TPMs and HSMs. For a detailed walkthrough of how Pouch delivers secrets and the reasoning behind each safeguard see Execution Flow.
Install via Cargo:
cargo install pouch-run --locked
The binary will be installed at $CARGO_HOME/bin/pouch.
--from <SOURCE> selects the secret source; see Sources.--fd <N> chooses the secret FD number (≥3) to avoid stdio and reserved descriptors; otherwise a safe descriptor is auto-selected.--max-bytes <N> bounds the accepted size (default 262144) to limit memory usage and denial-of-service risk.--consume-timeout <seconds> waits for a validated ACK after delivery (default 3.0) to prove receipt; 0 disables the wait and requires --i-accept-no-ack.--i-accept-no-ack explicitly opts out of ACK enforcement and prints a warning, reducing guarantees to fire-and-forget.--keep-fd <N> keeps additional FDs (≥3) open in the child besides {0,1,2,secret,ack}; use sparingly since extra FDs widen the attack surface.--no-containment disables Linux cgroup v2 containment used for teardown isolation, trading compatibility for weaker cleanup guarantees.-v / --verbose emits structural diagnostics to stderr only and never logs secret content, avoiding accidental leaks.--keep-env preserves the child's environment instead of a clean allowlist; Pouch still scrubs LD_*/DYLD_* and requires an absolute command path to reduce hijacking risk.The child command and its arguments follow -- and are passed verbatim. The command must be an absolute path (no PATH lookup) to avoid PATH hijacking and ensure deterministic execution.
- (stdin) reads from stdin (must not be a TTY) with size capped by --max-bytes; use prompt for interactive input to avoid echoing secrets.
file:/ABSOLUTE/PATH reads from an absolute path only, rejects .., allows only regular files, and rejects symlinks with O_NOFOLLOW plus fstat verification; group/world‑writable files are refused and size is checked before reading to enforce --max-bytes, all to prevent path/symlink attacks and tampering.
fd:N reads from an already‑open FD N, rejects 0, 1, and 2, duplicates the FD with CLOEXEC, and reads up to --max-bytes, avoiding stdio confusion, handle leaks, and unbounded reads.
prompt reads interactively from /dev/tty and requires a controlling TTY so a human can enter the secret securely.
cred:NAME or cred:UNIT/NAME is shorthand for systemd credentials: it reads from ${CREDENTIALS_DIRECTORY}/NAME or /run/credentials/UNIT.cred/NAME as applicable, and applies the same file: safety checks to preserve file‑source hardening.
0 indicates success (validated ACK observed and child exit 0; or fire‑and‑forget mirrors child 0).1 indicates a usage or validation error (invalid source, TTY on stdin, invalid path, or --consume-timeout 0 without the guard flag).2 indicates delivery failure when validated ACK fails (EOF before 48 bytes, timeout, oversized/truncated payload, nonce/digest mismatch, or premature child exit). It also occurs on EPIPE while writing the secret; with ACK enforcement, pouch tears down the subtree (process group or cgroup) before exiting.3 indicates an I/O or system error (open/dup/pipe failures, etc.).128 + signal.When enforcement is enabled, the parent generates a 16‑byte random nonce and exposes it to the child as a 32‑character lowercase hex string via POUCH_ACK_NONCE to bind the ACK to this delivery and prevent replay.
The child must then write exactly 48 bytes to POUCH_ACK_FD: the raw 16‑byte nonce followed by a 32‑byte BLAKE2s digest keyed with that nonce over the exact secret bytes, which proves receipt of the exact data and prevents tampering.
The parent verifies both values in constant time and rejects any deviation (size errors, extra bytes, mismatch, or premature EOF) to avoid timing and truncation/extension attacks.
+-----------------------+ write secret +-------------+
| Parent (pouch runner) | ------------------------> | Secret Pipe |
+-----------------------+ +-------------+
| ^ |
| | 48-byte ACK (nonce + digest) v POUCH_SECRET_FD
| +----------------------+ +---------------+
| | | Child |
| read from | | (exec target) |
+------------------------- ACK Pipe <---------+---------------+
(POUCH_ACK_FD)
A validated ACK proves only that the child has read the complete secret. It does not guarantee how the secret is used afterward. The child should close the ACK descriptor immediately after sending the 48‑byte payload. Extra data beyond this is ignored.
import os, hashlib, binascii
fd = int(os.environ["POUCH_SECRET_FD"]) # secret FD number
buf = []
while True:
chunk = os.read(fd, 65536)
if not chunk:
break
buf.append(chunk)
os.close(fd)
data = b"".join(buf)
nonce = binascii.unhexlify(os.environ["POUCH_ACK_NONCE"]) # 16-byte raw nonce
digest = hashlib.blake2s(data, digest_size=32, key=nonce).digest()
os.write(int(os.environ["POUCH_ACK_FD"]), nonce + digest) # 48-byte ACK
os.close(int(os.environ["POUCH_ACK_FD"]))
Logging in to a container registry in GitHub Actions without exposing the token in environment variables or logs. This example uses the drop‑in Python helper from examples/example.py to emit the secret on stdout and send a validated ACK over POUCH_ACK_FD, then pipes the secret into docker login:
name: ci
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install pouch (Cargo)
run: cargo install pouch-run --locked
- name: Registry login via FD (no env)
env:
REGISTRY: ghcr.io
USERNAME: ${{ github.actor }}
run: |
printf '%s' '${{ secrets.REGISTRY_TOKEN }}' \
| pouch --from - -- /usr/bin/python3 examples/example.py \
| docker login -u "$USERNAME" --password-stdin "$REGISTRY"
Notes:
ps, crash dumps, or inherited subprocesses.docker login with any tool that accepts credentials on stdin to avoid writing secrets to files or environment variables.--consume-timeout 0 --i-accept-no-ack disables enforcement, trading delivery assurance for compatibility.For other languages, see the examples directory.
The parent ignores SIGPIPE so writes to a closed pipe return EPIPE instead of killing the runner, preventing a child from terminating the parent by closing the pipe.
Core dumps are disabled and the secret buffer is page‑locked and marked no‑core‑dump, keeping plaintext out of crash dumps and swap to reduce accidental leakage while the buffer is live.
The delivery pipe is created with FD_CLOEXEC on both ends so unintended exec’d processes cannot inherit the handles, preventing secret leaks to unrelated children.
A safe target file descriptor ≥3 is chosen (or honored from --fd) while avoiding stdio, systemd sockets and runtime IPC FDs, preventing accidental collisions or hijacking.
The pipe’s read end is duplicated onto the chosen descriptor with CLOEXEC so the secret exists only on the intended FD, confining access to a predictable, non‑leaking handle.
All redundant read ends in the parent are closed so EOF is unambiguous and cannot be masked, ensuring the child can reliably detect completion.
Memory is pre‑allocated up to --max-bytes to bound usage and prevent unbounded allocation or fragmentation attacks.
The secret is read only after validating the source (rejecting TTY stdin, symlinks, and relative paths) to block TOCTTOU tricks and path hijacks.
On any read error the buffer is immediately wiped so partial plaintext cannot be recovered later.
Diagnostics in verbose mode include only structural metadata on stderr, never secret bytes, to prevent exfiltration via logs.
The child starts from a cleared environment to neutralize dangerous variables (e.g., LD_*, DYLD_*, and PATH) that could hijack execution.
Only an allowlisted set of env vars is restored and PATH is sanitized to a root‑owned, non‑writable set of directories, reducing the risk of spoofed binaries.
The child receives only POUCH_SECRET_FD, POUCH_ACK_FD, and POUCH_ACK_NONCE (as 32‑char hex) so it has the capability to read and acknowledge without seeing the secret in env.
Just before exec, all descriptors except `{0,1,2,POUCH_SECRET_FD,POUCH_ACK_FD