Files
command-center/omp-docs/research/secrets-for-agents.md
T
Frank 02883d3ed2 docs: rebuild KB in OKF v0.1 with live-verified facts
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.
2026-08-28 07:57:13 -07:00

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.
secrets
agent-security
git-crypt
sops
age
pass
gopass
1password
vault
systemd
threat-model
research
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 cat any 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 exec to 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 exec is 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 decrypt stdout leak is a real risk — agents that capture stdout will see plaintext.
  • The .sops.yaml creation 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

  1. Store encrypted secrets (e.g., .env.enc) in the repo.
  2. On each machine, a setup script decrypts them to a quarantined location (e.g., ~/.secrets/) that is gitignored.
  3. 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 exec injects 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.yaml creation 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.

  • 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)