Copy of the legacy ~/.pi/agent/docs tree rebuilt as the command-center knowledge base: concept files promoted to path identity, index listings stripped of frontmatter, all wikilinks/relative links resolved, every checkable claim verified against the live fleet (Dankserver = Ryzen 7 7700X + RTX 3090; Strix Halo is Dankclaw), unverifiable claims tagged. kb-lint exits 0.
23 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| research | Secrets Management for AI Agents in the Loop | Threat-modelled survey of secrets tools where agents have shell access and can read plaintext files and paste secrets into logs. |
|
2026-08-28 |
Secrets Management for AI Agents in the Loop
Threat Model
This document evaluates secrets management tools under the following constraints:
- An AI agent has shell access on the machine (via tool calls like
bash,edit,write). - The agent can
catany plaintext file on disk. - The agent can paste secrets into tool output, logs, or remote API calls.
- Any secret exposed to stdout of a tool call ends up in the model's context window.
- A secret in a tracked git file = secret in every agent's context window on every clone.
- The git repo is shared across 3 machines (local laptop, dankclaw Strix Halo, dankserver RTX 3090) and pushed to a self-hosted Gitea instance.
The question: for each tool, does a secret ever appear in plaintext where an agent can read or copy it, and how does the tool handle git-history safety?
1. git-crypt
Canonical: https://www.agwa.name/projects/git-crypt (URL verified: 200) Repo: https://github.com/AGWA/git-crypt (URL verified: 200)
How it works
git-crypt uses git clean/merge filters to encrypt specific files at commit time and decrypt them at checkout. The .gitattributes file declares which paths to encrypt. Encryption uses AES-256-CTR with a synthetic IV derived from SHA-1 HMAC.
Agent behavior
| Aspect | Detail |
|---|---|
| Decryption trigger | Transparent on git checkout — the agent never calls git-crypt unlock explicitly on a freshly cloned repo. |
| Secret on disk | Plaintext on disk after checkout. The agent can cat encrypted files and see GITCRYPT header; it can cat decrypted files and see plaintext. |
| Secret in stdout | No — decryption happens inside git's filter pipeline, not via agent-invoked command. Agent never sees git-crypt output containing secrets. |
| TTY / unlock needed | GPG mode requires gpg-agent to be running (passphrase cached or key unlocked). If the agent invokes git-crypt unlock and needs a passphrase, the unlock UI may require TTY/interactivity. |
| Git history | Encrypted blobs only — git log --all shows ciphertext. However, the .git-crypt/ key file is GPG-encrypted and stored in the repo, so any collaborator with the GPG key can unlock. |
Limitations for agent use
- File names not encrypted — an agent (or anyone reading the repo) can see which files exist even without keys.
- No access revocation — once a GPG key has cloned the repo, it has access to all historical plaintext. No key rotation.
- File count limits — the
.git-crypt/locked/directory stores encrypted versions of every tracked file. Large repos get slow. - Not compressible — encrypted files defeat git's delta compression.
- Not deprecated — per the README, version 0.8.0 released 2025-09-23; the project is "bug-free and reliable" but notes it has "not yet reached maturity" (pre-1.0). Not unmaintained.
Agent safety verdict
Moderate. Plaintext appears on disk post-checkout but the agent doesn't explicitly decrypt — git does it silently. However, any agent that opens the file sees plaintext. In a shared repo scenario, any collaborator who gets the GPG private key gets full access to all history.
2. SOPS + age
Canonical: https://getsops.io/ (URL verified: 200) Repo: https://github.com/getsops/sops (URL verified: 200, CNCF Sandbox project) age: https://filippo.io/age (URL verified: 200) Spec: https://age-encryption.org/v1 (URL verified: 200)
How it works
SOPS is a secrets editor that supports YAML, JSON, ENV, INI, and BINARY formats. It encrypts only values (not keys or structure) using either age (preferred, symmetric/public-key) or PGP. With age, each file is encrypted with a session key, and the session key is encrypted to each authorized age public key. The .sops.yaml config file declares creation rules with age recipients.
From the SOPS docs:
"SOPS encrypts configuration files while keeping the structure visible. Keys are not encrypted, while values and comments are encrypted."
— SOPS homepage, https://getsops.io/
The sops exec subcommand runs a given command with decrypted secrets injected as environment variables, then cleans up:
"SOPS supports
sops execto run commands with decrypted secrets available as environment variables. This avoids writing decrypted data to disk."— SOPS advanced usage docs, https://getsops.io/docs/usage/advanced/
Agent behavior
| Aspect | Detail |
|---|---|
| Decryption command | Agent must run sops decrypt file.sops.yaml or sops exec -- command. Plaintext appears in stdout (decrypt) or env vars (exec). |
| Secret in stdout | sops decrypt outputs full decrypted file to stdout — agent sees the plaintext. sops exec does not output secrets to stdout. |
| Secret on disk | sops decrypt -o writes plaintext to a file the agent can read. sops exec never writes decrypted data to disk. |
| TTY / unlock needed | None. age identity files are just text files (AGE-SECRET-KEY-1...). No passphrase prompt, no gpg-agent, no TTY. Fully non-interactive. |
| Git history | Encrypted blobs only. The .sops.yaml config file (with age recipients) is stored in plaintext in the repo — this is intentional and safe, as public keys are not secrets. |
| Key sharing | Age recipients (public keys) can be shared openly. Identity keys (secret keys) must be kept separate from the repo — stored on each machine's filesystem. |
| Key service | SOPS supports a local key service (sops --keyserv) but it's rarely needed with age since identity files are just files on disk. |
Advantages for agent fleets
- Fully non-interactive — no TTY, no passphrase, no agent interaction needed. Age identity is a file.
- Structural visibility — encrypted files retain YAML/JSON structure so agents can inspect config shape without decrypting.
- Multi-recipient — encrypt to multiple age keys; all fleet machines can decrypt.
sops execis agent-safe — secrets are injected as env vars, never written to stdout or disk.- Age keys are tiny and textual — easy to distribute, no GPG keyring management.
Caveats
sops decryptstdout leak is a real risk — agents that capture stdout will see plaintext.- The
.sops.yamlcreation rules file in the repo exposes which age public keys are authorized — harmless but reveals the key set.
Agent safety verdict
High. With sops exec, secrets never appear in stdout or on disk during agent operation. With sops decrypt, plaintext goes to stdout where the agent can read it. Age is fully non-interactive and requires no TTY.
3. pass (password-store) + age
Canonical: https://www.passwordstore.org/ (URL verified: 200) Repo: https://git.zx2c4.com/password-store/ (URL verified: 200)
How it works
pass is a minimal Unix password manager by Jason A. Donenfeld. Each password is a GPG-encrypted file in ~/.password-store/, versioned in git. The standard tool uses GPG by default.
Agent behavior (GPG backend — default)
| Aspect | Detail |
|---|---|
| Decryption command | pass show Path/To/Secret — outputs password to stdout. |
| Secret in stdout | Yes. pass show outputs plaintext to stdout. Agent sees the secret. |
| Secret on disk | Plaintext only transiently in stdout; the encrypted file is on disk. |
| TTY / unlock needed | Yes — GPG agent. pass relies on gpg-agent which may require a passphrase prompt (TTY). Without a cached passphrase, the agent will block waiting for interactive input. |
| Git history | Encrypted blobs (.gpg files). |
gopass with age backend
Canonical: https://github.com/gopasspw/gopass (URL verified: 200)
gopass is a Go reimplementation of pass designed for teams. It supports an age backend (gopass config backend set age) as a drop-in replacement for GPG.
| Aspect | Detail |
|---|---|
| Decryption | gopass show Path/To/Secret — outputs to stdout. |
| Secret in stdout | Yes. Same as pass. |
| TTY / unlock needed | No with age backend. Age identity is a file — no agent/TUI needed. Fully non-interactive. |
| Git history | Encrypted blobs (.age files). |
Agent safety verdict
Low (GPG) / Moderate (gopass + age). GPG requires agent interaction for unlock. gopass + age removes the TTY requirement but plaintext still goes to stdout where the agent can read/copy it.
4. 1Password CLI + Connect
CLI docs: https://developer.1password.com/docs/cli/ (URL verified: 200) Connect docs: https://developer.1password.com/docs/connect/ (URL verified: 200)
How it works
1Password CLI v2 authenticates to 1Password via biometric unlock (op signin) or service account tokens. op connect runs a local REST API server that CLI tools talk to for vault access. For agent/CI use, 1Password recommends Service Accounts and 1Password Environments.
Agent behavior
| Aspect | Detail |
|---|---|
| Authentication | op signin with username/password requires biometric unlock (TTY). Service accounts use tokens and are non-interactive. |
| Secret retrieval | op item get --fields name=password outputs to stdout. op eval or op run injects into subprocess env vars. |
| Secret in stdout | Yes for direct retrieval commands. op run --env / op run -- injects as env vars without stdout exposure. |
| TTY / unlock needed | Personal accounts: YES (biometric prompt). Service accounts: NO (token-based, fully non-interactive). |
| Requires internet | YES. Requires connection to 1Password Cloud (or self-hosted Connect server for enterprise). |
| Git history | N/A — secrets live in 1Password vaults, not in the git repo. No encrypted files in repo. |
| Cost | $36/user/year (Personal) or $10/user/month (Team). Self-hosted Connect requires Business plan. |
Agent safety verdict
Moderate to High (Service Accounts) but with significant downsides: requires cloud connectivity, costs money, and personal-account auth requires biometric TTY interaction. Service accounts are designed for CI/CD automation and work headlessly, but they introduce an external dependency and cost that a solo operator on 3 machines may not want.
5. HashiCorp Vault (AppRole)
Docs: https://developer.hashicorp.com/vault/docs/auth/approle (URL verified: 200)
How it works
Vault is an enterprise secrets management platform. The AppRole auth method allows machines to authenticate with Vault using a role_id and secret_id pair. The recommended token type for AppRole is batch — these tokens cannot create child tokens and have a short TTL.
From the Vault docs:
"The approle auth method allows machines or apps to authenticate with Vault-defined roles. ... This auth method is oriented to automated workflows (machines and services), and is less useful for human operators. We recommend using batch tokens with the AppRole auth method."
— Vault AppRole docs, https://developer.hashicorp.com/vault/docs/auth/approle
Agent behavior
| Aspect | Detail |
|---|---|
| Authentication | Agent calls vault write auth/approle/login role_id=... secret_id=... — fully non-interactive. Returns a token. |
| Secret retrieval | vault kv get -field=mysecret secret/myapp — outputs to stdout. |
| Secret in stdout | Yes. Direct kv get commands output plaintext to stdout. |
| Secret on disk | Vault token can be stored in $VAULT_TOKEN env var or file. The token itself is a secret credential. |
| TTY / unlock needed | No. AppRole login is a simple HTTP POST with role_id + secret_id. |
| Requires running Vault server | Yes. A Vault server must be running (local or remote) and unsealed. |
| Git history | Secrets are stored in Vault's storage backend, not in git. Only the role config lives in version control. |
| Cost/overhead | High. Requires running a Vault server, configuring auth backends, managing lease renewal, and maintaining the Vault infrastructure. |
Agent safety verdict
Moderate. Fully non-interactive and secrets don't live in git. However, it's overkill for 3 machines — requires a running Vault server, configuration management, and token lease management. For a solo operator, this is massive operational burden.
6. systemd credentials
Docs: https://www.freedesktop.org/software/systemd/man/latest/systemd-creds.html (URL verified: 200) Spec: https://systemd.io/CREDENTIALS (URL verified: 200)
How it works
systemd provides a native credential passing mechanism. The systemd-creds tool encrypts credential files with either a TPM2-derived key or a host key stored in /var/lib/systemd/credential.secret. Units declare credentials via LoadCredential=, SetCredential=, or LoadCredentialEncrypted=. Decrypted credentials appear as files in $CREDENTIALS_DIRECTORY with restricted permissions (0400).
From the systemd credentials spec:
"Service credentials are acquired at the moment of service activation, and released on service deactivation. They are immutable during the service lifetime. Service credentials are accessible to service code as regular files... Unlike environment variables the credential data is not propagated down the process tree."
— systemd.io/CREDENTIALS
Agent behavior
| Aspect | Detail |
|---|---|
| Decryption trigger | Automatic at service startup. The decrypted credential files appear in $CREDENTIALS_DIRECTORY with mode 0400 (owner root only). |
| Secret on disk | Plaintext in $CREDENTIALS_DIRECTORY. If the agent runs as the service user or root, it can read these files. |
| Secret in stdout | No — decryption is done by systemd, not by agent-invoked commands. |
| TTY / unlock needed | No. Decryption is automatic via TPM2 key or host key (/var/lib/systemd/credential.secret). |
| Requires systemd service | Yes. Only works when the agent is running inside a systemd service unit that declares the credentials. |
| Git history | Encrypted credential blobs can be stored in git (LoadCredentialEncrypted=). No plaintext secrets in history. |
| Machine-bound | TPM2-encrypted credentials are bound to the specific machine's TPM. Host-key encrypted credentials are portable but less secure. |
| Fleet sync | Encrypted credentials can be pushed to Gitea and decrypted on each machine using each machine's host key or TPM. |
Limitations for agent use
- Only works for systemd service processes — the agent must be running as a systemd unit. If you're running agents from a shell session (e.g., Claude Code in ghostty), credentials are not available.
- Size limited — credentials are limited-size binary objects (kernel mempage limit).
- Not for interactive use — designed for services, not interactive terminal sessions.
- Encrypted credentials (
LoadCredentialEncrypted=) require each target machine to have the decrypting key (TPM2 or host key).
Agent safety verdict
High (for services) / N/A (for interactive shells). If your agents run as systemd services, credentials are perfect — decrypted files appear with restricted permissions, no stdout leaks, no TTY. For interactive shell sessions (your current setup), this doesn't apply.
7. env-file / quarantine pattern
This is a pattern, not a tool.
How it works
- Store encrypted secrets (e.g.,
.env.enc) in the repo. - On each machine, a setup script decrypts them to a quarantined location (e.g.,
~/.secrets/) that is gitignored. - Agents read secrets from the quarantined location.
Agent behavior
| Aspect | Detail |
|---|---|
| Decryption command | Any tool (sops decrypt, age --decrypt, gpg -d, etc.) run by the user in a script. |
| Secret in stdout | Depends on the decryption tool used. With sops exec or age --decrypt -o, no stdout leak. |
| Secret on disk | Plaintext in quarantine dir (~/.secrets/) which is gitignored. |
| TTY / unlock needed | Depends on decryption backend. Age = no TTY. GPG = TTY needed. |
| Git history | Encrypted only. |
| Fleet sync | The quarantine directory must be manually synced (e.g., via SSH/rsync) across machines, or each machine must have the decryption key. |
Agent safety verdict
Flexible but manual. The pattern itself is sound — quarantined plaintext out of git. But you need a setup script per machine and must coordinate key distribution.
Decision Matrix
| Tool | Agent-Safe (no stdout leak) | Non-Interactive Unlock | Git-History Safety | Fleet Sync Across 3 Machines | Tooling Burden |
|---|---|---|---|---|---|
| git-crypt | Moderate | GPG: may need TTY | Excellent (encrypted) | Manual (GPG key sharing) | Low-Medium |
| SOPS + age | High (with exec) |
✅ None | Excellent (encrypted) | ✅ Age key files on each machine | Low |
| pass (GPG) | Low (stdout leak) | ❌ GPG agent/TUI | Excellent | Manual (GPG keyring) | Low |
| gopass + age | Low (stdout leak) | ✅ None | Excellent (encrypted) | ✅ Age key files on each machine | Low |
| 1Password CLI | Moderate (stdout leak) | Service acct: ✅ / Personal: ❌ | N/A (cloud) | ✅ Cloud-native | Medium (cost + cloud) |
| Vault AppRole | Moderate (stdout leak) | ✅ None | N/A (cloud) | ✅ Cloud-native | High (server) |
| systemd creds | ✅ High (for services) | ✅ None | Excellent | Each machine needs key | Medium |
| env-file quarantine | Depends on tool | Depends on backend | Encrypted only | Manual sync | Low-Medium |
Recommendation Ranking for This Fleet
Fleet profile: Solo operator, 3 machines (laptop + dankclaw + dankserver), shared git repo pushed to self-hosted Gitea, local LLM agents with shell access.
1. SOPS + age — Top recommendation
Why:
sops execinjects secrets as environment variables — never touches stdout or disk during agent operation.- Age identity files are plaintext text files — zero TTY, zero interactivity.
- Fully offline — no cloud dependency, no cost.
.sops.yamlcreation rules with age recipients are safe in git (public keys only).- Easy to distribute age key files to all 3 machines.
- Supports YAML, JSON, ENV, INI — covers all config formats.
- CNCF project, actively maintained.
Setup:
# On each machine: generate age key
age-keygen -o ~/.secrets/agent-age.key
# Distribute public keys to all machines
age -r <pubkey-machine1> -r <pubkey-machine2> -r <pubkey-machine3> \
-i ~/.secrets/agent-age.key secrets.yaml > secrets.yaml.sops
# In .sops.yaml:
creation_rules:
- path_regex: \.sops\.ya?ml$
key_groups:
- age:
- age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
- age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg
- age1xk2...
Agent workflow:
# Agent-safe: secrets in env vars, zero stdout exposure
sops exec --secret-env DB_PASS=secrets.yaml:database.password \
-- sh -c 'echo "Using DB password from env"'
# Non-agent-safe (for human use only): stdout leak
sops decrypt secrets.yaml.sops
2. Systemd credentials — Best for long-running agent services
Why:
- If agents run as systemd user services, credentials are automatically mounted as restricted files.
- TPM2-bound encryption makes credentials truly machine-specific.
- No decryption commands needed — systemd handles everything at service start.
Limitation: Doesn't work for interactive shell sessions (e.g., Claude Code running in ghostty).
3. env-file quarantine — Supplementary pattern
Why:
- Simple, explicit, and works with any encryption backend.
- Gitignored quarantine dir keeps plaintext out of git.
- Good for secrets that need to be written to files (e.g., TLS certs, API key files).
Downside: Requires a setup script per machine and manual key distribution.
Not recommended for this fleet
- git-crypt: Works but GPG mode may need TTY. Overkill for a few secret files. No access revocation.
- pass (GPG): GPG agent requires TTY — agents will block on passphrase prompts.
- 1Password: Requires cloud connectivity and paid subscription — unnecessary cost.
- Vault: Massive operational overhead for 3 machines. Enterprise solution for a solo operator.
Citations
- SOPS homepage: "Simple And Flexible Tool For Managing Secrets. SOPS encrypts configuration files while keeping the structure visible." — https://getsops.io/ (HTTP 200)
- SOPS advanced usage: "Passing Secrets to Other Processes" /
sops exec— https://getsops.io/docs/usage/advanced/ (HTTP 200) - SOPS age key management: "For most use cases, use the Encrypt and Decrypt functions with HybridRecipient and HybridIdentity." — https://pkg.go.dev/filippo.io/age (HTTP 200)
- age format spec: "age is a simple, modern and secure file encryption tool, format, and Go library." — https://age-encryption.org/v1 (HTTP 200)
- age official docs: "age-keygen -o key.txt" / hybrid post-quantum keys — https://filippo.io/age (HTTP 200)
- pass homepage: "pass — the standard unix password manager. Each password lives inside of a gpg encrypted file." — https://www.passwordstore.org/ (HTTP 200)
- gopass GitHub: "The slightly more awesome standard UNIX password manager for teams. Other backends for encryption (e.g. age) and storage (e.g. fossil) are also available." — https://github.com/gopasspw/gopass (HTTP 200)
- 1Password CLI: "1Password CLI brings 1Password to your terminal. Sign in to 1Password CLI with your fingerprint, and securely access everything you need during development." — https://developer.1password.com/docs/cli/ (HTTP 200)
- 1Password Connect: "Use 1Password Connect Server with 1Password CLI to securely access your items in your company's apps and cloud infrastructure using a private REST API." — https://developer.1password.com/docs/connect/ (HTTP 200)
- Vault AppRole: "The approle auth method allows machines or apps to authenticate with Vault-defined roles. This auth method is oriented to automated workflows (machines and services)." — https://developer.hashicorp.com/vault/docs/auth/approle (HTTP 200)
- systemd-creds man page: "Credentials are limited-size binary or textual objects that may be passed to unit processes." — https://www.freedesktop.org/software/systemd/man/latest/systemd-creds.html (HTTP 200)
- systemd credentials spec: "Service credentials are acquired at the moment of service activation, and released on service deactivation. ... Unlike environment variables the credential data is not propagated down the process tree." — https://systemd.io/CREDENTIALS (HTTP 200)
- git-crypt: "git-crypt enables transparent encryption and decryption of files in a git repository." (v0.8.0, released 2025-09-23) — https://www.agwa.name/projects/git-crypt (HTTP 200)