A secret manager where agents never see raw secrets —
not in stdout, not in stderr, not in logs, not in MCP responses.
Output scrubbing is mandatory, not optional.
exec command used
syscall.Exec with no output interception. A child process
printing key=$SECRET leaked the raw value straight to the model.
Veil is a clean-room reimplementation in machin/MFL
that fixes all three critical flaws: mandatory scrubbing, no raw output by
default, and sealed mode as the only mode.
Two paths for agents. Neither exposes the raw secret.
Resolve secrets internally, inject as env vars, run the child,
capture stdout + stderr, scrub every known secret to ***,
emit only the scrubbed output. The agent runs the command but never
sees the values.
Returns an opaque VEIL_SEALED:... blob encrypted with
X25519 + AES-256-GCM, bound to the secret path and ref. The agent
gets a token it can pass around but cannot decrypt. Only the holder
of the seal private key can open it.
# Agent wants to run a command that needs a secret: veil exec --env API_KEY=veil://myapp/api-key -- python app.py # Agent needs a reference to a secret (not the value): veil resolve veil://myapp/api-key → {"sealed":"VEIL_SEALED:eyJ...","ok":"true"} # Human retrieves a secret to a file (never stdout by default): veil get myapp/api-key -o /tmp/secret
| Phoenix flaw | Veil fix |
|---|---|
exec uses syscall.Exec — child stdout/stderr leaks secrets directly to the model |
→ exec captures output via machin's exec(), scrubs both streams, emits only ***-redacted text |
| resolve and get print raw secrets to stdout | → get refuses stdout by default (requires -o <file> or --raw); resolve returns opaque sealed tokens |
| MCP plaintext is the default unless sealed mode is explicitly configured | → Sealed mode is the only mode — resolve always returns VEIL_SEALED:... tokens |
veil init # Initialize ~/.veil (generates admin token) veil set myapp/api-key -v "sk-xxx" # Store a secret (warns: use --stdin) veil set myapp/api-key --stdin # Store from stdin (no argv leak) veil get myapp/api-key -o /tmp/secret --token <admin> # Retrieve to file (human only) veil list # List secret paths veil delete myapp/api-key --token <admin> # Delete a secret (human only) veil exec --env KEY=veil://myapp/api-key -- cmd # Run cmd with env + scrubbing (agent) veil resolve veil://myapp/api-key # Get sealed token (agent) veil agent create ci-bot <token> --token <admin> # Create an agent (human only) veil backup push --remote http://lume:8788 # Backup to lume veil guide # Embedded mental model veil help-json # Machine-readable catalog
Both humans and agents can use veil, but only humans can see raw secrets.
| Command | Agent | Human | Why |
|---|---|---|---|
set | yes | yes | Agent already has the value -- no new leak. Human can rotate later. |
exec | yes | yes | Scrubbed output -- agent never sees values. |
resolve | yes | yes | Sealed token -- agent can't decrypt. |
list | yes | yes | Paths only -- no values. |
get | no | yes | Raw value retrieval -- requires admin token. |
delete | no | yes | Destructive -- requires admin token. |
agent | no | yes | Admin operations -- requires admin token. |
The admin token is generated during veil init and shown once. Humans store it in a password manager or export VEIL_ADMIN_TOKEN. Agents don't have it, so admin-gated commands are human-only in practice.
Master KEK wraps per-secret DEKs. Compromising one DEK exposes only one secret. Rotating the KEK only requires re-wrapping DEKs, not re-encrypting all secrets.
Sealed tokens are encrypted with X25519 ECDH + HKDF-SHA256 derived keys. Path and ref are bound into the envelope to prevent relabeling attacks.
Every secret value is replaced with *** in child stdout
and stderr before emitting. Catches exact, base64, hex, URL-encoded,
reversed, space-separated, and partial substring (>=8 chars) variants.
The agent sees the command output but not the secrets.
Path-based permissions with glob matching. Token hashing (SHA-256).
Per-agent access control. admin implicitly grants all
actions.
No custom cryptography. All primitives are machin builtins: aes_gcm_encrypt/decrypt, x25519_pub/shared, hkdf_sha256, rand_bytes, sha256_bytes.
Veil prevents secrets from entering the LLM context window via command output. It does not protect against a malicious same-user process that reads files directly.
| Limitation | Why | Mitigation |
|---|---|---|
master.key/seal.key readable by same-user processes | Files are 0600 but same user can read them | Dedicated user account; container isolation |
acl.json token hashes readable | SHA-256 hashes, not raw tokens | Use long random tokens; rotate if compromised |
Child /proc/PID/environ readable | Inherent to env injection | Use veil resolve (sealed tokens) instead |
| Arbitrary transforms (rot13, XOR) bypass scrubber | Infinite possible encodings | Restrict child commands to trusted code |
set -v puts secret in argv | Visible in ps for process lifetime | Use set --stdin instead |
The threat model is well-behaved agents using the CLI as intended, not malicious same-user processes. A malicious agent could always cat ~/.veil/master.key and write a decryption script. Veil's job is to ensure that normal agent workflows -- exec, resolve, list, set -- never expose raw secrets to the model.
# from a release (no build needed) curl -sL https://github.com/javimosch/veil/releases/latest/download/veil-linux-amd64 -o veil chmod +x veil ./veil install # copies to ~/.local/bin # or build from source git clone https://github.com/javimosch/veil.git cd veil ./build.sh # machin encode + machin build → ./veil ./veil guide # read the mental model ./veil help-json # machine-readable command catalog
Requires machin installed to build from source. Single ~90 KB binary, no runtime, no dependencies. Self-update: veil update.
Veil implements four of the six agent-first CLI specs:
| Spec | Status | Implementation |
|---|---|---|
| cli-output-spec | yes | JSON stdout, typed errors, semantic exit codes, help-json |
| cli-guide-spec | yes | veil guide — embedded mental model |
| cli-update-spec | yes | veil update, install, uninstall |
| cli-feedback-spec | yes | veil feedback — relay-only dual-write |
| cli-telemetry-spec | no | Deliberately skipped — a secret manager making network calls is a bad look |
| cli-daemon-spec | n/a | Veil is CLI-only, no daemon |