Skip to content
Back to skills

Op Secrets Injection

DSecurity

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.

  • 4 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 12, 2026
devopspythonshellbashsqldockerkubernetesdebugginggitapidatabase

Works with

  • cli
  • api

Security analysis

D59/100
  • mediumUses curl or wget to download content
  • criticalModifies startup scripts or system services for persistence
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study

Pro scans all 2 files and shows the line behind each finding

Scanned September 12, 2026

npx -y skills add aibot88/sec_skill_store --skill op-secrets-injection --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Op Secrets Injection?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Op Secrets Injection
[![Security: D — Skills Directory](https://www.skillsdirectory.com/api/skills/aibot88-op-secrets-injection/badge)](https://www.skillsdirectory.com/skills/aibot88-op-secrets-injection)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: op-secrets-injection
description: 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.
version: 1.0.0
---

# 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:

```bash
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:

```bash
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:

```yaml
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

```bash
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:

```bash
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:

```bash
# .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
```

```bash
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:

```bash
# .env
DATABASE_URL=op://${APP_ENV}/db/connection_string
API_KEY=op://${APP_ENV}/api/key
```

```bash
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):

```bash
DB_PASS=op://prod/db/password op run -- sh -c 'echo "$DB_PASS" | psql ...'
```

The wrong way (broken):
```bash
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

```bash
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

```yaml
- 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:
```yaml
- 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:

```bash
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](https://developer.1password.com/docs/k8s/) or render `Secret` manifests with `op inject` at deploy time:

```bash
op inject -i secret.yaml.tpl | kubectl apply -f -
```

## Anti-patterns to flag

- A `.env` file with plaintext values committed to a repo. Replace values with `op://...` references and run via `op run --env-file`.
- `op read $REF >> ~/.bashrc`. Bakes the value into the dotfile. Use `op run` or read on demand.
- A script that does `export PASS=$(op read ...)` then runs many subcommands. Acceptable for ad-hoc use; for anything checked in, prefer `op run --env-file=.env -- script.sh`.
- Calling `op read` in a hot loop. Each call hits the local agent / 1Password.com. Read once into a variable, or run the inner work under `op run`.
- Using `--no-masking` in production. It's a debug flag.

Files in this skill

  • SKILL.md7.7 KB
  • source.json1016 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.