v0.1.0 · AES-256-GCM · X25519 sealed responses · machin/MFL

veil

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.

The fix Phoenix needed. Phoenix Secrets had AES-256-GCM, X25519 sealed responses, ACLs, attestation — but its 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.

How it works

Two paths for agents. Neither exposes the raw secret.

veil exec (env injection + scrubbing)

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.

veil resolve (sealed token)

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

The 3 fixes vs Phoenix

Phoenix flawVeil 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

Usage

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

Trust model

Both humans and agents can use veil, but only humans can see raw secrets.

CommandAgentHumanWhy
setyesyesAgent already has the value -- no new leak. Human can rotate later.
execyesyesScrubbed output -- agent never sees values.
resolveyesyesSealed token -- agent can't decrypt.
listyesyesPaths only -- no values.
getnoyesRaw value retrieval -- requires admin token.
deletenoyesDestructive -- requires admin token.
agentnoyesAdmin 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.

Cryptography

AES-256-GCM envelope encryption

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.

X25519 sealed responses

Sealed tokens are encrypted with X25519 ECDH + HKDF-SHA256 derived keys. Path and ref are bound into the envelope to prevent relabeling attacks.

Output scrubbing

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.

Deny-by-default ACL

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.

Known limitations

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.

LimitationWhyMitigation
master.key/seal.key readable by same-user processesFiles are 0600 but same user can read themDedicated user account; container isolation
acl.json token hashes readableSHA-256 hashes, not raw tokensUse long random tokens; rotate if compromised
Child /proc/PID/environ readableInherent to env injectionUse veil resolve (sealed tokens) instead
Arbitrary transforms (rot13, XOR) bypass scrubberInfinite possible encodingsRestrict child commands to trusted code
set -v puts secret in argvVisible in ps for process lifetimeUse 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.

Install

# 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.

Agent-first CLI specs

Veil implements four of the six agent-first CLI specs:

SpecStatusImplementation
cli-output-specyesJSON stdout, typed errors, semantic exit codes, help-json
cli-guide-specyesveil guide — embedded mental model
cli-update-specyesveil update, install, uninstall
cli-feedback-specyesveil feedback — relay-only dual-write
cli-telemetry-specnoDeliberately skipped — a secret manager making network calls is a bad look
cli-daemon-specn/aVeil is CLI-only, no daemon