Using sops Repositories that use this layout commit their secrets to git as sops-encrypted dotenv files, one per deployment environment: , . The files decrypt with age identities. There is no , no secrets service, and no session to log in to. Every checkout, worktree, and cloud sandbox has the encrypted files at clone; the only input anywhere is an age private key. ( ) is the only interface. Do not call directly in a repo that has the wrapper. Identities | Identity | Scope | Where the private key lives | Decrypts | | ---------- | ----------- | -------------------------------------------------…

` round-trips exactly.\n- `sops decrypt --output-type json` gives a flat object; the wrapper uses it for `exec`.\n- Diffs show one changed `ENC[...]` line per changed value plus `sops_lastmodified` and\n `sops_mac`, so review can see which keys changed without seeing values.\n\n## Why the wrapper spawns the child itself\n\n`sops exec-env file 'cmd'` runs the command through `sh -c` and does not forward\n`SIGTERM` to it; under `docker stop` the child is killed only when the container's grace\nperiod expires. `tools/secrets.ts exec` decrypts to JSON, spawns the command directly with\n`stdio: inherit`, forwards `SIGINT`, `SIGTERM`, `SIGHUP`, strips `SOPS_AGE_KEY` and\n`SOPS_AGE_KEY_FILE` from the child environment, and exits with the child's code.\nDecrypted values override same-named shell variables.\n\n## Cloud and container facts\n\n- Claude Code on the web has no secrets store; its environment variables field is the\n place, and the doc says not to put credentials there. An `agent`-scoped age key that\n decrypts dev only is the accepted trade.\n- Codex cloud \"secrets\" are stripped before the agent phase; \"environment variables\"\n persist.\n- Cursor `Runtime Secret` redaction is substring matching; `od -c` bypasses it.\n- The runtime image needs the `sops` binary (~15 MB), `secrets/`, `.sops.yaml`, and\n `tools/secrets.ts`. Docker's legacy builder rejects `ADD --chmod`; use `ADD` then\n `RUN chmod 755`. Railway builds with BuildKit, where either works.\n- Next `NEXT_PUBLIC_*` values are inlined at build, so the web image builds through\n `node tools/secrets.ts exec prod -- pnpm run build:web`; off Railway, pass\n `SOPS_AGE_KEY` as a BuildKit secret mount, not a build arg.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3583,"content_sha256":"71ccdbe8383298c38b8c59914a4892092f3da4ee7ad4597d9a6fce9827531c01"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Using sops","type":"text"}]},{"type":"paragraph","content":[{"text":"Repositories that use this layout commit their secrets to git as sops-encrypted dotenv files, one per deployment environment: ","type":"text"},{"text":"secrets/dev.env","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"secrets/prod.env","type":"text","marks":[{"type":"code_inline"}]},{"text":". The files decrypt with age identities. There is no ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":", no secrets service, and no session to log in to. Every checkout, worktree, and cloud sandbox has the encrypted files at clone; the only input anywhere is an age private key.","type":"text"}]},{"type":"paragraph","content":[{"text":"pnpm secrets","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"tools/secrets.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":") is the only interface. Do not call ","type":"text"},{"text":"sops","type":"text","marks":[{"type":"code_inline"}]},{"text":" directly in a repo that has the wrapper.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Identities","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Identity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scope","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Where the private key lives","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Decrypts","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"agent","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"user-wide","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sops's user key file on every machine agents run on (","type":"text"},{"text":"references/setup.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" step 3 has its path); ","type":"text"},{"text":"SOPS_AGE_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" in cloud sandboxes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"dev.env","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"personal","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"user-wide","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"the user's password manager","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"files listing its public key","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"prod","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"per project","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"that project's production platform and documented password-manager item","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"prod.env","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":".sops.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" lists recipients by public key. Encrypting needs no private key; decrypting or editing needs one recipient's private key. ","type":"text"},{"text":"agent","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"personal","type":"text","marks":[{"type":"code_inline"}]},{"text":" are local-development keys shared by every project; ","type":"text"},{"text":"prod","type":"text","marks":[{"type":"code_inline"}]},{"text":" is minted per project so one leaked deploy variable exposes one project. These are conventions, not guaranteed recipients. Existing repos may use only shared ","type":"text"},{"text":"dev","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"prod","type":"text","marks":[{"type":"code_inline"}]},{"text":" keys. Read the repository's secrets guide and ","type":"text"},{"text":".sops.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" before choosing a key; the encrypted file's recipient metadata determines which identities can decrypt it.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Agent workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"Dev secrets are yours to manage without asking:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"pnpm secrets show dev # everything, decrypted\npnpm secrets get dev STRIPE_KEY\npnpm secrets set dev STRIPE_KEY sk_test_1\npnpm secrets unset dev STRIPE_KEY\npnpm secrets exec dev -- node apps/worker/src/main.ts","type":"text"}]},{"type":"paragraph","content":[{"text":"exec","type":"text","marks":[{"type":"code_inline"}]},{"text":" puts the decrypted values in the child's environment (over the shell's), removes ","type":"text"},{"text":"SOPS_AGE_KEY*","type":"text","marks":[{"type":"code_inline"}]},{"text":" from it, forwards signals, and exits with the child's status.","type":"text"}]},{"type":"paragraph","content":[{"text":"When an authorized task needs production secrets, perform elevation yourself. Do not ask the user to run the command or reconfirm an already authorized task.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check access with ","type":"text"},{"text":"pnpm secrets exec prod -- true","type":"text","marks":[{"type":"code_inline"}]},{"text":", which prints no secret values. An existing ","type":"text"},{"text":".age/elevated","type":"text","marks":[{"type":"code_inline"}]},{"text":" file alone does not prove that its key can decrypt prod.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If access fails, read the repo's secrets guide and recipient configuration. Use the documented account, vault, item, and field for a matching key. Do not assume a ","type":"text"},{"text":"Personal","type":"text","marks":[{"type":"code_inline"}]},{"text":" vault, an ","type":"text"},{"text":"age-personal","type":"text","marks":[{"type":"code_inline"}]},{"text":" recipient, or the CLI's default account.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run the documented read yourself, piping the key directly into the wrapper: ","type":"text"},{"text":"op read 'op://VAULT/ITEM/FIELD' --account ACCOUNT | pnpm secrets elevate","type":"text","marks":[{"type":"code_inline"}]},{"text":". Replace the placeholders with the discovered identifiers; never print the key.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm decryption with ","type":"text"},{"text":"pnpm secrets exec prod -- true","type":"text","marks":[{"type":"code_inline"}]},{"text":", then continue the task.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"When the documented 1Password location is missing, use ","type":"text"},{"text":"op account list --format json","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"op vault list --account ACCOUNT --format json","type":"text","marks":[{"type":"code_inline"}]},{"text":" to identify the correct account and vault. Search item metadata in that vault for the documented key name with ","type":"text"},{"text":"op item list --account ACCOUNT --vault VAULT --format json","type":"text","marks":[{"type":"code_inline"}]},{"text":"; inspect only matching items. Pass ","type":"text"},{"text":"--account","type":"text","marks":[{"type":"code_inline"}]},{"text":" on subsequent reads. ","type":"text"},{"text":"Private","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Personal","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"Shared","type":"text","marks":[{"type":"code_inline"}]},{"text":" are distinct names, not interchangeable aliases. If a key is found but cannot decrypt, compare its public key with the file's recipients instead of repeatedly trying vault names. Keep private keys out of tool output.","type":"text"}]},{"type":"paragraph","content":[{"text":"Ask the user only when progress requires their interaction, such as unlocking 1Password or granting unavailable access. State the actual blocker. Elevation is per checkout and lasts until ","type":"text"},{"text":".age/elevated","type":"text","marks":[{"type":"code_inline"}]},{"text":" is deleted; do not copy it into another worktree.","type":"text"}]},{"type":"paragraph","content":[{"text":"When you add a variable, add it to the env schema and to every ","type":"text"},{"text":"secrets/\u003cenv>.env","type":"text","marks":[{"type":"code_inline"}]},{"text":" you can decrypt. If you cannot decrypt prod, say so in the PR: the typed env check fails the prod boot until the value is set, which is the intended signal.","type":"text"}]},{"type":"paragraph","content":[{"text":"Never write an ","type":"text"},{"text":"AGE-SECRET-KEY-...","type":"text","marks":[{"type":"code_inline"}]},{"text":" into a tracked file, a log, or a commit. Never put ","type":"text"},{"text":"personal","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"prod","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a cloud environment.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Human setup","type":"text"}]},{"type":"paragraph","content":[{"text":"For the one-time steps (generating keys, installing ","type":"text"},{"text":"sops","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"age","type":"text","marks":[{"type":"code_inline"}]},{"text":", wiring the ","type":"text"},{"text":"agent","type":"text","marks":[{"type":"code_inline"}]},{"text":" key into agent tools and cloud sandboxes, configuring the prod platform, and rotating keys), read ","type":"text"},{"text":"references/setup.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". When the user asks to be reminded of the steps, walk them through that file in order.","type":"text"}]},{"type":"paragraph","content":[{"text":"For the sops and age behavior the design relies on (identity union, ","type":"text"},{"text":"updatekeys","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"exec-env","type":"text","marks":[{"type":"code_inline"}]},{"text":" limitations, dotenv quirks), read ","type":"text"},{"text":"references/sops-notes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},"metadata":{"date":"2026-11-02","name":"using-sops","author":"@skillopedia","source":{"stars":0,"repo_name":"agents","origin_url":"https://github.com/jssblck/agents/blob/HEAD/skills/using-sops/SKILL.md","repo_owner":"jssblck","body_sha256":"76f2203463943c598aa8d1d30cc8fcf4e5079b367bc5f5c00eacef03727514ab","cluster_key":"8cc822dd20a85445fc60b698ac6b46e5533399540fc83ac102325c740f8435da","clean_bundle":{"format":"clean-skill-bundle-v1","source":"jssblck/agents/skills/using-sops/SKILL.md","text_bytes":10758,"source_commit":"e1294cd9041643344c849e88d7fa5a2342f11a3b","skills_sh_rank":9470,"attachment_count":3,"text_attachments":3,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/using-sops/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"security","import_tag":"skills-sh-top20k-2026-11","description":"Use for secrets or key setup in a repo with .sops.yaml, encrypted environment files, or a pnpm secrets script."}},"renderedAt":1793671352024}

Using sops Repositories that use this layout commit their secrets to git as sops-encrypted dotenv files, one per deployment environment: , . The files decrypt with age identities. There is no , no secrets service, and no session to log in to. Every checkout, worktree, and cloud sandbox has the encrypted files at clone; the only input anywhere is an age private key. ( ) is the only interface. Do not call directly in a repo that has the wrapper. Identities | Identity | Scope | Where the private key lives | Decrypts | | ---------- | ----------- | -------------------------------------------------…