agents/openai.yaml
interface: display_name: "Using sops" short_description: "Read, write, and run with sops-encrypted secrets" default_prompt: "Use $using-sops to work with this repository's secrets."
jssblck/agents · GitHub
Use when a repo has .sops.yaml and secrets/<env>.env, or a pnpm secrets script. Covers reading and writing dev secrets, prod elevation, running with secrets, and the human key setup.
프로젝트 폴더에서 아래 명령어를 실행하고, 설치할 에이전트를 선택하세요.
npx skills add jssblck/agents --skill using-sops설치 명령을 직접 실행해야 적용됩니다. 지원 에이전트와 필요한 권한·라이선스는 제작자의 안내를 확인하세요.
agents/openai.yamlinterface: display_name: "Using sops" short_description: "Read, write, and run with sops-encrypted secrets" default_prompt: "Use $using-sops to work with this repository's secrets."
references/setup.md# Setup: keys, machines, sandboxes, production
Steps 1 through 4 are once per person, machine, or sandbox. Steps 5 and 6 are per project.
## 1. Install the tools
Both are single static Go binaries.
```sh
# Linux (WSL2 included). Adjust arch as needed.
curl -fsSL -o ~/.local/bin/sops https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.linux.amd64
chmod +x ~/.local/bin/sops
curl -fsSL https://github.com/FiloSottile/age/releases/download/v1.3.1/age-v1.3.1-linux-amd64.tar.gz | tar xz
mv age/age age/age-keygen ~/.local/bin/ && rm -rf age
```
macOS: `brew install sops age`. Confirm with `sops --version --disable-version-check`
and `age-keygen --version`. `pnpm run doctor` in a starter repo reports both.
## 2. Generate the shared identities (once, ever)
```sh
age-keygen -o personal.txt
age-keygen -o agent.txt
```
Each file holds a comment with the public key (`age1...`) and the private key
(`AGE-SECRET-KEY-1...`). Then:
- `personal.txt`: store the whole file in the password manager as `age-personal`, with the
private key in a field you can read from the CLI (for 1Password: `op read
'op://Personal/age-personal/private key'`). Delete the local file.
- `agent.txt`: keep for step 3, then store a copy in the password manager as
`age-agent` for future machines. Delete the local file after step 3.
Record both public keys somewhere handy (a note in the password manager works). Every new
project's `.sops.yaml` needs them. The `prod` key is per project and is generated in step 6.
## 3. Install the agent key on a machine
```sh
mkdir -p ~/.config/sops/age
# paste the AGE-SECRET-KEY-1... line from age-agent
install -m 600 /dev/stdin ~/.config/sops/age/keys.txt <<'EOF'
AGE-SECRET-KEY-1...
EOF
```
sops reads that path by default. Any agent session on this machine, in any project,
worktree, or shell (including editors like t3code that spawn their own shells), can now
`pnpm secrets show dev`. Nothing prompts.
## 4. Cloud sandboxes
Give each platform one environment variable, `SOPS_AGE_KEY`, whose value is the `agent`
private key line. sops reads it directly. Nothing needs network access.
| Platform | Where | Notes |
| -------- | ----- | ----- |
| Claude Code on the web | environment settings, environment variables field | works with network access set to none; the environment's setup script must install `sops` (two lines from step 1, into `/usr/local/bin`) |
| Codex cloud | environment, **environment variables** (not secrets) | Codex removes "secrets" before the agent phase; a variable persists. The starter's `.codex/environments/environment.toml` setup script installs `sops` when missing |
| Cursor cloud agents | environment secrets, type `Environment Variable` | `Runtime Secret` also works but redaction is by substring and adds nothing here |
| Daytona | sandbox `envVars` (SDK) or snapshot env | install `sops` in the snapshot image |
Never put `personal` or `prod` in a sandbox.
## 5. Production
Only that project's `prod` identity goes to production. The service starts through
`pnpm secrets exec prod -- <command>` (containers: `node tools/secrets.ts exec prod -- ...`
as `CMD`), so decryption happens at boot and the app never sees the key.
**Railway** (or any container platform): set one service variable, `SOPS_AGE_KEY`, to the
`prod` private key. The image already carries `sops`, `secrets/`, `.sops.yaml`, and
`tools/secrets.ts` (the starter's release recipe copies them). Nothing else to sync: a
secret change is a commit, and the next deploy has it.
**Bare server with systemd**: keep the key out of the unit file and out of `/proc`.
```sh
sudo mkdir -p /etc/credstore.encrypted
printf '%s' 'AGE-SECRET-KEY-1...' | sudo systemd-creds encrypt --name=age-prod - /etc/credstore.encrypted/age-prod.cred
```
```ini
[Service]
User=app
WorkingDirectory=/srv/app
LoadCredentialEncrypted=age-prod:/etc/credstore.encrypted/age-prod.cred
Environment=SOPS_AGE_KEY_FILE=%d/age-prod
ExecStart=/usr/bin/node tools/secrets.ts exec prod -- node apps/server/src/main.ts
```
`systemd-creds` binds the ciphertext to the host TPM or `/var/lib/systemd/credential.secret`;
`%d` is a per-service tmpfs only that service can read.
## 6. Per project
Once per new repository (the `bootstrap` skill asks for this):
1. Generate the project's prod key: `age-keygen -o prod.txt`. Store the file in the
password manager as `age-prod-<project>`, note the public key, delete the local file.
Set the private key on the deploy target (step 5) when the first deploy happens.
2. Put the three public keys in `.sops.yaml`, replacing the placeholders:
```yaml
creation_rules:
- path_regex: secrets/dev\.env$
age: 'age1PERSONAL,age1AGENT'
- path_regex: secrets/prod\.env$
age: 'age1PERSONAL,age1PROD'
```
3. `pnpm secrets init dev && pnpm secrets init prod`.
4. Elevate the checkout so you can write prod values:
`op read 'op://Personal/age-personal/private key' | pnpm secrets elevate`.
5. `pnpm secrets set dev KEY value` and `pnpm secrets set prod KEY value` as needed. Commit
`.sops.yaml` and `secrets/`.
Sibling worktrees are unelevated by default; repeat step 4 in a checkout that needs prod.
Delete `.age/elevated` to drop elevation.
## 7. Rotation
Add or replace a recipient (a new machine key, a lost personal key):
```sh
# edit .sops.yaml, then, holding a key that can already decrypt each file:
sops updatekeys -y secrets/dev.env
sops updatekeys -y secrets/prod.env
```
Commit both. A leaked private key means: rotate the recipient as above, then change every
value that key could read. Encrypted history remains readable to whoever holds the old
key; git cannot unpublish it.
references/sops-notes.md# sops and age behavior the flow relies on Verified against sops 3.13.3 and age 1.3.1 in August 2026. ## Identity sources are unioned sops tries every age identity it can find: `~/.config/sops/age/keys.txt` (or `$XDG_CONFIG_HOME/sops/age/keys.txt`), the `SOPS_AGE_KEY` variable (one or more identities, newline separated), `SOPS_AGE_KEY_FILE`, and `SOPS_AGE_KEY_CMD` output. None replaces the others. That is why `.age/elevated` passed as `SOPS_AGE_KEY_FILE` adds prod without removing dev. ## Encrypt needs no private key; every write re-encrypts the whole file `sops set`, `unset`, and `edit` decrypt the document, change it, and re-encrypt under all recipients in the file's metadata. So an editor needs at least one recipient's private key even to append a new value. A bare `age -r` append is not a valid edit: sops keeps a MAC over the document. This is why the agent cannot add a prod value on its own and why elevation exists. `sops encrypt` (what `pnpm secrets init` runs) needs only `.sops.yaml` recipients. It reads stdin when no filename is given, but then `--filename-override` is required so the creation rule matches. An empty stdin fails; a single comment line is enough. ## `.sops.yaml` matters only at create and updatekeys time Decrypt, set, unset, and edit read recipients from the file's own metadata. Changing `.sops.yaml` alone changes nothing; run `sops updatekeys -y <file>` with a key that can decrypt it, then commit. ## dotenv format specifics - Comments are preserved and encrypted (`#ENC[...,type:comment]`). - Values are strings. `sops set file '["KEY"]' '"value"'` takes JSON: the wrapper quotes for you, so `pnpm secrets set dev PW 'p@ss w"ord$'` round-trips exactly. - `sops decrypt --output-type json` gives a flat object; the wrapper uses it for `exec`. - Diffs show one changed `ENC[...]` line per changed value plus `sops_lastmodified` and `sops_mac`, so review can see which keys changed without seeing values. ## Why the wrapper spawns the child itself `sops exec-env file 'cmd'` runs the command through `sh -c` and does not forward `SIGTERM` to it; under `docker stop` the child is killed only when the container's grace period expires. `tools/secrets.ts exec` decrypts to JSON, spawns the command directly with `stdio: inherit`, forwards `SIGINT`, `SIGTERM`, `SIGHUP`, strips `SOPS_AGE_KEY` and `SOPS_AGE_KEY_FILE` from the child environment, and exits with the child's code. Decrypted values override same-named shell variables. ## Cloud and container facts - Claude Code on the web has no secrets store; its environment variables field is the place, and the doc says not to put credentials there. An `agent`-scoped age key that decrypts dev only is the accepted trade. - Codex cloud "secrets" are stripped before the agent phase; "environment variables" persist. - Cursor `Runtime Secret` redaction is substring matching; `od -c` bypasses it. - The runtime image needs the `sops` binary (~15 MB), `secrets/`, `.sops.yaml`, and `tools/secrets.ts`. Docker's legacy builder rejects `ADD --chmod`; use `ADD` then `RUN chmod 755`. Railway builds with BuildKit, where either works. - Next `NEXT_PUBLIC_*` values are inlined at build, so the web image builds through `node tools/secrets.ts exec prod -- pnpm run build:web`; off Railway, pass `SOPS_AGE_KEY` as a BuildKit secret mount, not a build arg.
SKILL.md--- name: using-sops description: Use when a repo has .sops.yaml and secrets/<env>.env, or a pnpm secrets script. Covers reading and writing dev secrets, prod elevation, running with secrets, and the human key setup. --- # Using sops Repositories that use this layout commit their secrets to git as sops-encrypted dotenv files, one per deployment environment: `secrets/dev.env`, `secrets/prod.env`. The files decrypt with age identities. There is no `.env`, 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. `pnpm secrets` (`tools/secrets.ts`) is the only interface. Do not call `sops` directly in a repo that has the wrapper. ## Identities | Identity | Scope | Where the private key lives | Decrypts | | ---------- | ----------- | ---------------------------------------------------------------- | ----------- | | `agent` | user-wide | `~/.config/sops/age/keys.txt` on every machine agents run on; `SOPS_AGE_KEY` in cloud sandboxes | `dev.env` | | `personal` | user-wide | the user's password manager | every file | | `prod` | per project | that project's production platform only | `prod.env` | `.sops.yaml` lists recipients by public key. Encrypting needs no private key; decrypting or editing needs one recipient's private key. `agent` and `personal` are local-development keys shared by every project; `prod` is minted per project so one leaked deploy variable exposes one project. ## Agent workflow Dev secrets are yours to manage without asking: ```sh pnpm secrets show dev # everything, decrypted pnpm secrets get dev STRIPE_KEY pnpm secrets set dev STRIPE_KEY sk_test_1 pnpm secrets unset dev STRIPE_KEY pnpm secrets exec dev -- node apps/worker/src/main.ts ``` `exec` puts the decrypted values in the child's environment (over the shell's), removes `SOPS_AGE_KEY*` from it, forwards signals, and exits with the child's status. Prod secrets need elevation. When a task requires reading or writing `prod.env`: 1. Check for `.age/elevated` in this checkout. If present, prod commands work; carry on. 2. If absent, ask the user to run, in a terminal of their own: `op read 'op://Personal/age-personal/private key' | pnpm secrets elevate` (or however their password manager prints the key). Say why you need it. 3. Elevation is per checkout and lasts until `.age/elevated` is deleted. Do not copy it into another worktree. When you add a variable, add it to the env schema and to every `secrets/<env>.env` 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. Never write an `AGE-SECRET-KEY-...` into a tracked file, a log, or a commit. Never put `personal` or `prod` in a cloud environment. ## Human setup For the one-time steps (generating keys, installing `sops` and `age`, wiring the `agent` key into agent tools and cloud sandboxes, configuring the prod platform, and rotating keys), read `references/setup.md`. When the user asks to be reminded of the steps, walk them through that file in order. For the sops and age behavior the design relies on (identity union, `updatekeys`, `exec-env` limitations, dotenv quirks), read `references/sops-notes.md`.