Env-key extraction safety — the prefix-collision rule

Origin: incident #3494 (2026-07-09). A Tier-1 MongoDB rs0 admin credential was exposed in visible session output while extracting a value from a dotenv-style file during the #3171 SOPS-drift fix. The credential was rotated the same day (PR #3513) — this playbook is the durable prevention (root-cause follow-up item 3: “a corrected, anchored extraction pattern that cannot suffer this substring-collision bug again”).


What went wrong

The extraction used an unanchored grep:

# ❌ WRONG — unanchored: matches every line that CONTAINS the substring
grep 'MONGODB_URL=' backend-environment.env

PMONGODB_URL= contains MONGODB_URL= as a substring, so this grep matched both lines:

PMONGODB_URL=mongodb://admin:<password>@…   ← matched (unwanted)
MONGODB_URL=mongodb://admin:<password>@…    ← matched (wanted)

The captured shell variable ended up holding two concatenated values with an embedded newline. When it was later diffed for key-name verification (cut -d= -f1), the bare unprefixed fragment surfaced as a visible line — printing a full mongodb://admin:<password>@… URI into the transcript.

This is a prefix-collision: whenever one env key is a suffix of another (MONGODB_URLPMONGODB_URL, DB_URLAPP_DB_URL, KEYAPI_KEY), an unanchored match leaks the sibling key’s value.


The rule

Always anchor at line-start with ^KEY= and require exactly one match. The ^ anchor alone already defeats the prefix-collision (^MONGODB_URL= cannot match PMONGODB_URL=); requiring a single match additionally catches accidental duplicate keys.

# ✅ CORRECT — anchored, exact key, value captured silently, never echoed
VALUE=$(grep "^MONGODB_URL=" backend-environment.env | cut -d= -f2-)
some-command --uri "$VALUE"; unset VALUE

Never cat / Read a credential file, never print the captured variable, and reference the KEY NAME only in any diagnostic output — per the global secret-safety rules in CLAUDE.md.


Preferred: use the shared helper

scripts/lib/extract_env_key.sh encapsulates all four guarantees (anchored ^KEY=, exactly-one-match enforcement, value to stdout only, regex-injection-proof key validation). Prefer it over hand-written greps in scripts and rotation tooling:

source /opt/p24-infra/scripts/lib/extract_env_key.sh
 
# From a file:
VALUE=$(extract_env_key MONGODB_URL backend-environment.env) || { echo "extract failed" >&2; exit 1; }
some-command --uri "$VALUE"; unset VALUE
 
# From a SOPS-decrypted stream (keeps plaintext off disk):
VALUE=$(sops -d --input-type dotenv --output-type dotenv secrets/pinbox24-w3.env.sops \
          | extract_env_key_stdin V32_MONGODB_URL) || exit 1

Exit codes: 0 one match (value on stdout) · 1 bad usage / invalid key / unreadable file · 2 key absent · 3 duplicate/collision (value withheld). Under set -e any non-zero aborts the caller, so a collision can never silently pass through.

Self-test (synthetic non-secret fixtures, reproduces the exact #3494 collision):

bash scripts/lib/extract_env_key.sh --self-test

Checklist before extracting any value from a dotenv/SOPS file

  • Grep is anchored: ^KEY= (or use extract_env_key) — never a bare KEY=.
  • Exactly one match is required; the command fails on zero or many.
  • The value is captured into a variable and used as a command argument — never printed.
  • No later diff / cut / verification step echoes lines that contain the value.
  • Diagnostics reference the KEY NAME only.
  • Temp plaintext files are deleted and value variables unset immediately after use.

Related: docs/secrets-rotation-log.md (#3494 entry), docs/playbooks/sops-edit-operations.md, docs/playbooks/static-api-key-incident-rotation.md, CLAUDE.md §Secrets.