diff --git a/.agents/prompts/build.txt b/.agents/prompts/build.txt index 68f6fbe14..77d4462ed 100644 --- a/.agents/prompts/build.txt +++ b/.agents/prompts/build.txt @@ -246,6 +246,33 @@ When referencing specific functions or code include the pattern `file_path:line_ - Immediately warn: "That looks like a credential. Conversation transcripts are stored on disk — treat this value as compromised. Rotate it and store the new value via `aidevops secret set NAME` in your terminal." - Do NOT repeat, echo, or reference the pasted credential value in your response - Continue helping with the task using a placeholder like `` instead +# +# 8.2 Secret as command argument exposure (t4939) +# Threat: a secret passed as a command argument (not an env var) can be echoed +# back in error messages, appear in `ps` output, and leak into logs — even when +# the command's *intent* is safe (e.g., a DB insert). The agent assesses the +# command as safe because it's not a `cat` or `echo`, but any program can print +# its argv on failure. Error paths are invisible at invocation time. +# Incident: qs-agency migration — WEBHOOK_SECRET interpolated into `wp db query` +# SQL argument, WP-CLI printed the full argument on parse failure, secret entered +# conversation transcript. Required immediate rotation. +- When a subprocess needs a secret value, ALWAYS pass it as an environment variable, NEVER as a command argument. Command arguments appear in error messages, `ps` output, and logs. Environment variables do not. + - UNSAFE: `SECRET=$(gopass show -o name) cmd "INSERT INTO t VALUES ('$SECRET')"` — if `cmd` fails, it may print the argument including the secret + - UNSAFE: `curl -H "Authorization: Bearer $TOKEN" ...` — `ps` shows the full command line; error output may echo headers + - UNSAFE: `mysql -p"$PASSWORD" dbname` — password visible in process list + - SAFE: `SECRET=$(gopass show -o name) MY_SECRET="$SECRET" cmd` — subprocess reads via `getenv("MY_SECRET")`; error handlers never print env vars + - SAFE: `aidevops secret NAME -- cmd` — injects as env var with automatic output redaction + - SAFE: `SSH_AUTH_SOCK=... ssh ...` — env-based auth, no secret in argv + - The subprocess must read the value from its environment (`getenv()` in C/PHP, `process.env` in Node, `os.environ` in Python, `ENV[]` in Ruby), not from `$1`/`argv`. + - When the target program only accepts secrets as arguments (no env var support), write the secret to a temporary file (mode 0600), pass the file path as the argument, and delete the file immediately after. This is a last resort — prefer programs that support env var or stdin input. + - For SSH/remote commands: `ssh host "ENV_VAR='value' command"` passes the secret in the remote shell's environment, not as an argument to `ssh` itself. Alternatively, use `ssh -o SendEnv=VAR` with server-side `AcceptEnv` configuration. +# +# 8.3 Post-execution secret detection (t4939, layer 2) +# After any Bash command whose input references a credential variable (gopass, +# $*_SECRET, $*_TOKEN, $*_KEY, $*_PASSWORD), verify the output doesn't contain +# the secret value before presenting it to the user. +- After running any command that references a credential variable, assess whether the output could contain the secret value. If the command failed (non-zero exit) and the secret was passed as an argument (violating 8.2), assume the output is contaminated — do not present it to the user. Flag for immediate credential rotation. +- This is a judgment call, not a regex check. The agent knows which variables contain secrets and can assess whether output looks like it contains credential material (long base64 strings, API key patterns, JSON with auth fields). - Confirm destructive operations before execution - NEVER create files in `~/` root - use `~/.aidevops/.agent-workspace/work/[project]/` - Do not commit files containing secrets (.env, credentials.json, etc.) diff --git a/.agents/services/hosting/cloudron.md b/.agents/services/hosting/cloudron.md index 6f930773e..6702cf837 100644 --- a/.agents/services/hosting/cloudron.md +++ b/.agents/services/hosting/cloudron.md @@ -290,7 +290,7 @@ docker exec -it mysql mysql -u -p docker exec -it mysql mysql -uroot -p"$(cat /home/yellowtent/platformdata/mysql/root_password)" ``` -> **Security note**: The `docker inspect` command above reveals database credentials. Redact passwords before pasting output into forum posts, tickets, or chat. The `-p$(cat ...)` pattern briefly exposes the password in the process list while the command runs. +> **Security note**: The `docker inspect` command above reveals database credentials. Redact passwords before pasting output into forum posts, tickets, or chat. The `-p$(cat ...)` pattern briefly exposes the password in the process list while the command runs. Prefer passing credentials via environment variables instead of command arguments where possible (see `prompts/build.txt` section 8.2). #### **Common Database Fixes** diff --git a/.agents/tools/credentials/gopass.md b/.agents/tools/credentials/gopass.md index 350c18769..9b7ef2025 100644 --- a/.agents/tools/credentials/gopass.md +++ b/.agents/tools/credentials/gopass.md @@ -154,12 +154,15 @@ Agent should start with this warning in chat: 3. Agent uses secret via: `aidevops secret SECRET_NAME -- command` 4. Output is automatically redacted +**Env var, not argument**: When passing secrets to subprocesses, ALWAYS use environment variables, never command arguments. Arguments appear in `ps`, error messages, and logs -- even when the command's intent is safe (e.g., a DB insert). Use `aidevops secret NAME -- cmd` which injects as env var automatically, or `MY_SECRET="$value" cmd` where the subprocess reads via `getenv()`. See `prompts/build.txt` section 8.2 for the full rule. + **Prohibited commands** (NEVER run in agent context): - `gopass show` / `gopass cat` -- prints secret values - `cat ~/.config/aidevops/credentials.sh` -- exposes plaintext - `echo $SECRET_NAME` -- leaks to agent context - `env | grep` -- exposes environment variables +- `cmd "$SECRET"` -- secret as command argument, visible in `ps` and error output ## psst Alternative diff --git a/.agents/tools/security/opsec.md b/.agents/tools/security/opsec.md index 4e8723485..9bd14e76a 100644 --- a/.agents/tools/security/opsec.md +++ b/.agents/tools/security/opsec.md @@ -80,6 +80,7 @@ Session safety model for AI-assisted terminals: - Prefer key-name checks, masked previews, or fingerprints over raw value display. - Avoid writing raw secrets to temporary files (`/tmp/*`) where possible; prefer in-memory handling and immediate cleanup. - If a command cannot be made secret-safe, do not run it via AI tools. Instruct the user to run it locally and never ask them to paste the output. +- **Env var, not argument (t4939)**: When a subprocess needs a secret, pass it as an environment variable, never as a command argument. Arguments appear in `ps`, error messages, and logs. Use `aidevops secret NAME -- cmd` (auto-injects as env var with redaction) or `MY_SECRET="$value" cmd` where the subprocess reads via `getenv()`. See `prompts/build.txt` section 8.2 for the full rule and safe/unsafe patterns. ## Platform Trust Matrix