Op secrets injection
Skill RadOrigin-LLC/RAD-Claude-Skills/plugins/rad-1password/skills/op-secrets-injection
Use this skill when the user is loading 1Password secrets into a running process or config file — anything involving `op run`, `op inject`, `op read`, `.env` files containing `op://...` references, secret-reference template syntax, the `{{ op://... }}` placeholder format, populating YAML/JSON/TOML config from a template, or wiring 1Password into Docker/Kubernetes/CI/CD. Also use when the user asks how to keep secrets out of `.env`, source control, or shell history.From its SKILL.md
npx -y skills add RadOrigin-LLC/RAD-Claude-Skills --skill op-secrets-injectionAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.7 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it
Loading secrets at runtime: op read, op inject, op run
Three commands solve three different problems. Pick by destination, not by habit.
| Destination | Use |
|---|---|
| Single secret value, transient (one shell command) | op read |
| Config file on disk (or stdin/stdout pipeline) with placeholders | op inject |
| Process that expects env vars (most apps, dev servers, scripts) | op run |
op read — single secret to stdout
op read [flags] <secret-reference>
| Flag | Purpose |
|---|---|
-o, --out-file <path> | Write to file instead of stdout. |
--file-mode <octal> | Output file mode (default 0600). |
-n, --no-newline | Suppress trailing newline. |
-f, --force | Overwrite existing file without prompt. |
Reference query parameters
Append ?attribute=... or ?ssh-format=... to extract derived values:
op read "op://prod/2fa/otp-field?attribute=otp" # current 6-digit code
op read "op://prod/login/password?attribute=type" # field type metadata
op read "op://prod/ssh-key/private key?ssh-format=openssh" # OpenSSH-formatted key
Inline in commands
This is fine when the secret is needed once and won't end up in ps output for long:
docker login -u "$(op read op://prod/docker/user)" -p "$(op read op://prod/docker/pass)"
psql "$(op read op://prod/db/connection_string)"
For long-running commands, prefer op run — exposing secrets via $(...) puts them in the parent shell's argv briefly.
op inject — render a templated file
op inject [flags]
| Flag | Purpose |
|---|---|
-i, --in-file <path> | Template input. If omitted, reads stdin. |
-o, --out-file <path> | Output destination. If omitted, writes stdout. |
--file-mode <octal> | Output file mode (default 0600). |
-f, --force | Overwrite existing output. |
Template syntax
Two reference forms are accepted in templates:
unenclosed: op://vault/item/field
enclosed: {{ op://vault/item/field }}
Use enclosed in nearly every case — they're robust around adjacent text:
database_url: postgres://{{ op://prod/db/user }}:{{ op://prod/db/pass }}@{{ op://prod/db/host }}/{{ op://prod/db/name }}
Unenclosed references end at the first character outside [A-Za-z0-9_.?\-], which makes adjacent characters dangerous.
Environment-variable interpolation in templates
Templates can also expand env vars, with default-value support:
${VAR} ${VAR:-default}
$VAR op://${ENV:-dev}/db/password
Combined: a single template handles dev/staging/prod by exporting ENV differently before each op inject.
Streaming when you don't need the file on disk
op inject -i nginx.conf.tpl | docker run -i --rm nginx nginx -c /dev/stdin -t
op inject -i app.yaml.tpl | kubectl apply -f -
This avoids the "delete the resolved file when done" rule entirely.
When you must write the file
Always treat output as plaintext-secret material:
op inject -i config.yml.tpl -o config.yml
trap 'shred -u config.yml 2>/dev/null || rm -f config.yml' EXIT
# ... start app that reads config.yml ...
Add the resolved filename to .gitignore.
op run — exec a command with secrets in env
op run [flags] -- <command> [args...]
| Flag | Purpose |
|---|---|
--env-file <path> | Load env vars from a dotenv file. Repeatable; later files win on conflict. |
--no-masking | Don't redact secrets in the subprocess's stdout/stderr. |
The dotenv pattern (recommended for app dev)
.env contains references, not values. Commit it. The plaintext secrets never touch disk:
# .env — safe to commit
DATABASE_URL=op://prod/db/connection_string
STRIPE_SECRET_KEY=op://prod/stripe/api-key
JWT_SIGNING_KEY=op://prod/auth/jwt-key
SENTRY_DSN=op://prod/sentry/dsn
op run --env-file=.env -- npm run dev
op run --env-file=.env -- python manage.py runserver
op run --env-file=.env -- ./bin/server
Multi-environment with one template
.env references a variable substituted at run time:
# .env
DATABASE_URL=op://${APP_ENV}/db/connection_string
API_KEY=op://${APP_ENV}/api/key
APP_ENV=staging op run --env-file=.env -- ./bin/server
APP_ENV=prod op run --env-file=.env -- ./bin/server
Chained references (env var → secret reference)
Set the env var to a reference, then use a subshell so $VAR expands inside the subprocess (after op run has substituted):
DB_PASS=op://prod/db/password op run -- sh -c 'echo "$DB_PASS" | psql ...'
The wrong way (broken):
DB_PASS=op://prod/db/password op run -- echo "$DB_PASS"
# parent shell expands $DB_PASS to the literal "op://..." string before op run sees it
Masking
By default, secrets passed via env are redacted from the child process's stdout/stderr. Helpful for screenshare/CI logs. Disable with --no-masking (or OP_RUN_NO_MASKING=true) only for debugging.
Multiple env files
op run --env-file=.env.shared --env-file=.env.local -- ./bin/server
Later files override earlier files for the same key.
CI / production patterns
GitHub Actions
- uses: 1password/load-secrets-action@v2
with:
export-env: true
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
DATABASE_URL: op://prod/db/connection_string
STRIPE_KEY: op://prod/stripe/api-key
- run: ./bin/migrate && ./bin/start
If you don't want the Action, raw op works:
- run: |
curl -sSf https://downloads.1password.com/linux/keys/1password.asc | sudo gpg --dearmor -o /usr/share/keyrings/1password-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | sudo tee /etc/apt/sources.list.d/1password.sources
sudo apt update && sudo apt install -y 1password-cli
op run --env-file=.env -- ./bin/migrate
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
Docker (build vs run)
Don't bake secrets into images. Inject at run time:
docker run --rm \
-e OP_SERVICE_ACCOUNT_TOKEN \
-v "$PWD/.env:/app/.env:ro" \
-v "$(which op):/usr/local/bin/op:ro" \
myapp:latest \
op run --env-file=/app/.env -- /app/bin/start
Better: use the official 1password/op image multi-stage to bake op into your runtime image, then run as above.
Kubernetes
Use the 1Password Operator or render Secret manifests with op inject at deploy time:
op inject -i secret.yaml.tpl | kubectl apply -f -
Anti-patterns to flag
- A
.envfile with plaintext values committed to a repo. Replace values withop://...references and run viaop run --env-file. op read $REF >> ~/.bashrc. Bakes the value into the dotfile. Useop runor read on demand.- A script that does
export PASS=$(op read ...)then runs many subcommands. Acceptable for ad-hoc use; for anything checked in, preferop run --env-file=.env -- script.sh. - Calling
op readin a hot loop. Each call hits the local agent / 1Password.com. Read once into a variable, or run the inner work underop run. - Using
--no-maskingin production. It's a debug flag.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most containers cloud skills give in ~2.0k tokens
Counted across 607 of the 657 authors here whose files we hold, read 2026-08-07
- Run containers as a non-root userin 66 of 607, across 46 files
- Use multi-stage buildsin 53 of 607, across 44 files
- Use Promise.all for independent operationsin 47 of 607, across 13 files
- Import directly instead of barrel filesin 46 of 607, across 12 files
- Use ternary instead of AND for conditionalsin 45 of 607, across 12 files
- Use Set or Map for O(1) lookupsin 42 of 607, across 10 files
- Create a .dockerignore filein 41 of 607, across 31 files
- Read individual rule files for detailsin 39 of 607, across 9 files
- Copy dependency files before source codein 36 of 607, across 23 files
- Authenticate server actions like API routesin 35 of 607, across 7 files
- Use next/dynamic for heavy componentsin 34 of 607, across 9 files
- Use React.cache for per-request deduplicationin 34 of 607, across 10 files
Said here and by no other author read
- pick the command by destination type
- use op read for single transient values
- use op inject for templated files
- use op run for processes needing env vars
- use enclosed template references in op inject
- stream templated output to avoid disk writes
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.