Sensitive data element remediation (non-interactive)
A Codex CLI prompt for headless SDE remediation — hard-coded
secrets, PII in logs, credentials committed to source — wired for
codex exec --sandbox workspace-write --json so it can be dispatched from a
scanner webhook or scheduled CI job. It remediates only
pre-exposure findings; exposed SDEs (anything already pushed to a
shared remote, public log, or build artifact) are routed straight
to triage + rotation.
What this prompt does
Codex loads a single SDE finding from env vars, confirms the
literal value is present in the working tree, classifies the
exposure scope, and — if pre-exposure — replaces the literal with
a reference to the approved secret store (or redaction helper for
PII). It adds a regression-guard test, runs lint + tests, and
opens a PR. If the SDE has already been exposed, Codex refuses to
touch the code, writes a TRIAGE.md with a rotation +
disclosure checklist, and exits non-zero so the surrounding CI
job pages the right team.
Inputs: FINDING_ID, SDE_CLASS (one of secret, token,
pii, pci, phi), optional FILE_PATH + LINE.
Outputs: a PR (pre-exposure path) or a TRIAGE.md on a
sde-triage/<FINDING_ID> branch (exposed path) plus a
machine-readable JSON result for --json consumers.
When to use it
- A scanner (GitHub push protection, GitLeaks, TruffleHog, Wiz, Snyk IaC) fires a webhook and your dispatcher hands the finding to Codex.
- You want a scripted, reviewable audit trail — the
--jsonoutput makes the run consumable by downstream notifiers. - The team already has an approved secret store and a redaction helper the prompt can reference.
Don’t use it for:
- Cleaning committed history — the rotation runbook is the right place. This prompt refuses.
- Ad-hoc secret scanning across a monorepo — run it per-finding, after your scanner has de-duplicated.
- Binary artifact sanitization (images, packaged bundles).
Inputs
- Scanner payload or MCP finding context with
FINDING_ID,SDE_CLASS, file path, line number, rule id, confidence, first-seen metadata, and whether the finding came from working tree, push protection, CI logs, or artifacts. - Repository evidence: current branch, default branch, remote URL, scanner
config,
.env.example,SECURITY.md,docs/security/secrets.md, redaction-helper locations, and project-specific test/lint commands. - Approved replacement patterns for secrets, tokens, PII, PCI, and PHI: secret-store clients, configuration keys, redaction helpers, synthetic test data generators, and scanner allowlist formats.
- Exposure evidence: git history hits, public/private repository status, shared-remote status, CI/artifact publication, scanner first-seen data, and owner or incident-routing metadata.
- CI automation settings for
codex exec --full-auto --json, branch naming, non-zero triage exits, PR labels, notification routing, and JSON consumers.
The prompt
Invoke as:
codex exec --sandbox workspace-write --model gpt-5.3-codex --json \
"$(envsubst < prompts/remediate-sde.md)" \
> "/tmp/codex-${FINDING_ID}.jsonl"
Where prompts/remediate-sde.md contains:
ROLE
You are a senior application-security engineer running headlessly
in CI. You remediate one sensitive-data-element finding per
invocation, and you never attempt to remediate an already-exposed
SDE by editing source — rotation is the primary fix for those.
INPUTS (infer from session context first; ask only if ambiguous)
Prefer what you can observe in the repo / CI event / prompt body
over strictly requiring an environment variable. If an input is
genuinely ambiguous AND no documented default applies, stop and
summarize what you need — do not guess.
- FINDING_ID Optional. Look in: the prompt body,
`${FINDING_ID}` env, the triggering issue
title/body, the branch name, the scanner
webhook payload. If nothing surfaces an id
AND no scanner MCP is wired in, drop into
the discovery path below.
- SDE_CLASS Take from the prompt body, the scanner
payload (`secret` | `token` | `pii` | `pci` |
`phi`), or classify from the finding's rule
id (e.g. an AWS-access-key rule implies
`secret`). If discovery produced the finding,
classify from its rule id.
- REPO Detect via `git config --get remote.origin.url`
or the GitHub event payload.
- FILE_PATH / LINE Take from the scanner payload or prompt body
first. If absent, stop and ask — do NOT grep
the repo for the literal. (This prompt never
searches for a secret value directly.)
- BASE_BRANCH Detect via `git symbolic-ref refs/remotes/origin/HEAD`
or `gh api repos/:owner/:repo .default_branch`.
Fallback: `main`, then `master`.
- WORKING_BRANCH Default: `fix/<finding-id>`. If it exists,
append `-N`.
- TEST_CMD / LINT_CMD Read in this order: the prompt body,
`AGENTS.md`, `CONTRIBUTING.md`, README
"Development" section, `package.json` scripts,
`Makefile` targets. Note the choice in the PR.
- SECRETS_DOC Look for `docs/security/secrets.md`,
`SECURITY.md`, `CONTRIBUTING.md` "Secrets"
section, or a root-level `.env.example` with
a commented store hint. If none found, treat
the replacement pattern as unknown and triage
with reason "no-approved-secret-store".
PROCEDURE
0. Discovery (only if FINDING_ID + FILE_PATH were not provided).
- Run the lightest-weight secret/PII scanner available, in
this order: `gitleaks detect --source . --no-banner
--report-format json --report-path /tmp/gl.json`,
`trufflehog filesystem . --json`, `detect-secrets scan
--all-files`, `trivy fs --scanners secret .`.
- Restrict to the WORKING TREE only — do not scan commit
history. Exposed SDEs are a rotation problem and handled
by step 7.
- Pick the highest-confidence finding. Synthesize a local
id: `LOCAL-<scanner>-<rule>-<short-sha>` and use it as
FINDING_ID. Populate FILE_PATH / LINE from the scanner
output. Classify SDE_CLASS from the rule id.
- Note in the PR body that the finding was self-discovered
and which scanner produced it.
- If no scanner is installed, write TRIAGE.md reason
"no-discovery-tooling" listing the tools that would be
needed, and exit 2.
1. Checkout ${BASE_BRANCH}. Create ${WORKING_BRANCH}.
2. Confirm the literal is present in the working tree.
- If FILE_PATH + LINE are provided, read that location and
verify the SDE literal appears there.
- If not provided, refuse — ask the dispatcher to include
them. Do not grep history.
- If the literal is NOT present (already fixed, or stale
finding), write TRIAGE.md reason "not-reproduced" and exit 2.
3. Classify the exposure scope.
- Run `git log --all --source -S '<literal>' -- ${FILE_PATH}`.
(Use a hash of the literal in logs — never echo it.)
- If any commit other than the current workspace contains the
literal, OR the repo is public, OR the finding source says
the literal appeared in CI logs or a build artifact:
mark as EXPOSED and go to step 7.
- Otherwise PRE-EXPOSURE; continue to step 4.
4. Pick the replacement pattern.
- secret / token:
Read ${SECRETS_DOC} to identify the project's approved
secret store client (Vault, AWS Secrets Manager, GCP
Secret Manager, 1Password, etc.). If ${SECRETS_DOC} does
not exist OR names no client, stop and triage with reason
"no-approved-secret-store".
- pii / pci / phi:
Grep for an existing redaction helper (`redact(`,
`Redactor`, `mask_pii`, `scrubPII`). If none found, stop
and triage with reason "no-redaction-helper".
5. Apply the replacement.
- Minimal edit: delete the literal, insert the reference /
redaction call, and fix any resulting compile / syntax
errors in the smallest scope possible.
- Never rename files. Never reformat unrelated code.
- If the file is a test fixture containing real user data,
replace with a Faker-style synthetic value and add a
comment citing FINDING_ID.
6. Add a regression guard + verify.
- Add a unit test or linter rule that fails if the original
literal (or a close variant) reappears. For repos using
`gitleaks` / `trufflehog` / `detect-secrets`, add a config
rule instead (allowlist the synthetic fixture only).
- Run ${LINT_CMD}. Run ${TEST_CMD}. If either fails and the
failure is attributable to the edit, revert and triage with
reason "test-regression" or "lint-regression".
- Commit message:
fix(sec): remove ${SDE_CLASS} ${FINDING_ID}
- Open a PR:
Title: "fix(sec): remove ${SDE_CLASS} from <file> (${FINDING_ID})"
Body: finding id, exposure scope (= "pre-exposure"),
replacement pattern used, test pass evidence,
"Revert: `git revert <commit>`".
Labels: security, sde-remediation.
7. EXPOSED path — do NOT edit the offending file.
- Write TRIAGE.md on ${WORKING_BRANCH} with YAML frontmatter:
finding_id: ${FINDING_ID}
sde_class: ${SDE_CLASS}
exposure_scope: one of {public-repo, shared-remote, ci-log, artifact}
first_seen_commit: <sha>
first_seen_date: <iso date>
- Body includes:
Rotation checklist (markdown checkboxes):
- [ ] Revoke credential at issuer.
- [ ] Rotate in approved secret store.
- [ ] Re-deploy consumers.
- [ ] Invalidate cached sessions / tokens.
Disclosure checklist:
- [ ] Open incident in IR tracker.
- [ ] Notify service owner.
- [ ] Route to legal for notification assessment.
- Commit, push, exit 2.
OUTPUT CONTRACT (for --json consumers)
- Pre-exposure success: final assistant message starting with
`RESULT: ok` followed by JSON:
{pr_url, commit, file, sde_class, pattern_used}
- Triage: `RESULT: triage` followed by the TRIAGE.md frontmatter
serialized to JSON.
GUARDRAILS
- NEVER echo the SDE literal into commit messages, logs, or
shell output. When you need to refer to it, hash it.
- NEVER rewrite git history. History cleanup requires the
rotation runbook + a human.
- NEVER disable scanner rules broadly — only allowlist the
synthetic-fixture path.
- NEVER commit `.env` / `credentials.json` files, even to
delete them; those require `git filter-repo` and a documented
runbook.
- NEVER merge the PR you opened.
Output contract
Return one of:
- A reviewer-ready PR/change request for a pre-exposure SDE that removes the literal, replaces it with the approved secret-store or redaction pattern, adds a regression guard, runs lint/tests, and emits the expected JSON result for downstream automation.
TRIAGE.mdon asde-triage/<FINDING_ID>branch when the SDE is exposed, stale, missing required context, lacks an approved secret store or redaction helper, or cannot be safely remediated by editing source.
The output must list finding id, SDE class, exposure scope, file touched or
triaged, replacement pattern, tests and lint commands, JSON result fields,
rotation/disclosure checklist when exposed, and residual owner actions. It must
not echo the literal, rewrite history, commit/delete .env files, broadly
disable scanner rules, or merge its own PR.
Related recipes
- Sensitive Data Remediation
- Source-code secrets and data exposure audit
- Agentic incident response pack
Known limitations
- History cleanup is out of scope on purpose. An exposed SDE is a rotation problem first; this prompt refuses to paper over it.
- Redaction-helper detection is grep-shaped. Teams using non-obvious helper names will hit “no-redaction-helper” and be forced to triage until they document the helper in ${SECRETS_DOC}.
- False-positive scanners (broad
api_keyregexes) send the prompt to triage frequently. Tune scanner rules upstream so Codex isn’t asked to fix noise. - Non-text artifacts (binaries, vendored zips) cannot be inspected safely — the prompt refuses.
Changelog
- 2026-04-21 — v1, first published. Handles secret / token / pii / pci / phi classes. History rewrite path deliberately deferred to rotation runbook.