You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1ef01ca
Browse filesBrowse the repository at this point in the historyBrowse files
fix: never let a detected credential leave the process
Addresses the secret-handling half of the review on #22.
A secret finding is the one type where the alert hands us the thing we
must not repeat, and where the pull request is not the remediation: the
credential was committed, so it is in git history and live whether or
not the PR merges. Only rotation fixes it.
The PR is still opened — that is what makes the exposure visible and
gets it actioned — but it is no longer allowed to imply it is the fix.
The body now opens with a warning saying so in terms, and rotation is
injected as the first manual step deterministically rather than left to
the impact model to think of, so it reaches the webhook too.
The credential itself is scrubbed at every point where it would leave
the repository's access boundary (redact.py, one chokepoint per path):
both model prompts, the impact model's prose on the way back, the PR
title, body and commit message, and NotificationPayload — which covers
the run log and the Slack webhook, and means a backend added later
inherits the scrubbing instead of having to remember it.
The commit diff is the deliberate exception: a commit that removes a
line renders that line, and no wording changes what `git show` prints.
That residue is exactly what the PR warning exists to declare.
Candidates come from the alert's code_snippet, longest first — the
assignment's quoted value, other quoted literals, and the whole line as
a diff contains it. Keys are never candidates: `API_KEY = "sk-live-…"`
yields the value, never `API_KEY`, because the variable name is what
the PR has to tell an operator to set. Only secret findings build a
live redactor; blanking a SAST or CVE snippet would blind gate 2 to the
lines it has to judge.
Also from the same comment: the sanity gate's secret-pattern check ran
only for `secret` findings, so a CVE bump that pasted a token into a
lockfile was never checked — the module docstring's "no new secrets"
was true for one type in four. It now runs for every type.
And secret.md promised "never commit the actual secret value, even to
remove it", which is not achievable by construction. It now says what
actually happens and what the agent must never author.
32 new tests; 403 total.
Copy file name to clipboardExpand all lines: plugins/security-engineer/HARNESS.md
+41-1Lines changed: 41 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -118,7 +118,7 @@ the commit will contain.
118
118
119
119
| # | Gate | Checks | On failure |
120
120
|---|---|---|---|
121
-
| 1 |**sanity** (`validator.sanity_check`) | Diff is non-empty; within the type's line budget (cve 200, sast 100, iac/secret 50); for `secret` findings, no added line matches a secret pattern; the agent's own `diff_summary` does not name a version its diff never added |`FAILED`, no PR |
121
+
| 1 |**sanity** (`validator.sanity_check`) | Diff is non-empty; within the type's line budget (cve 200, sast 100, iac/secret 50); no added line matches a secret pattern, **whatever the finding type** — a CVE bump that pastes a token into a lockfile fails here too; the agent's own `diff_summary` does not name a version its diff never added |`FAILED`, no PR |
122
122
| 2 |**llm** (`validator.llm_validate`) | A single-shot model call: does this diff address this alert? `pass` / `fail` / `uncertain`|`fail` → `FAILED`. `uncertain` → passes, PR labelled `needs-review`|
123
123
| 3 |**verify** (`pipeline.verify`) |**CVE:** the manifest still declares the package; it is no longer on the version the alert was raised against; if the agent chose a different version, that version carries no known advisory; a lockfile beside the manifest agrees; then `go build ./...` (Go) or `cargo metadata --locked` (Cargo). **Other types:** language build check (below) |`FAILED`, no PR |
124
124
| 4 |**orca** (`validator.orca_check_gate`) | Polls the Orca GitHub App check on the PR head SHA. On failure, pulls the check **annotations** — file, line, message | Reverts the worktree to the branch head, re-invokes the fix agent *with the annotations as feedback*, re-runs gates 1 and 3, and pushes a follow-up commit to the same PR branch. After `max_retries`, follows `on_failure`|
@@ -205,6 +205,46 @@ fix agents.
205
205
206
206
---
207
207
208
+
## Secret findings
209
+
210
+
A secret finding is the one type where the alert hands the harness the thing it
211
+
must not repeat, and where the pull request is **not** the remediation. The
212
+
credential was committed: it is in git history and live regardless of whether the
213
+
PR merges. Only rotation at the provider fixes it.
214
+
215
+
The PR is still opened, because it is what makes the exposure visible and gets it
216
+
actioned — but it is not allowed to imply it is the fix:
217
+
218
+
- The body opens with a warning saying in terms that this does not remediate,
219
+
that the credential remains in history and live, and that rotation comes first.
220
+
- Rotation is injected as the **first** manual step (`_with_rotation_step`),
221
+
deterministically rather than left to the impact model to think of — so it
222
+
reaches the webhook payload as well as the PR body.
223
+
224
+
**What is redacted, and what cannot be.** The credential is already in the repo,
225
+
so the PR diff adds no audience the repository did not already have. What a run
226
+
adds is the paths that leave the repository's access boundary, and those are
227
+
closed at the point of egress (`redact.py`):
228
+
229
+
| Path | Redacted |
230
+
|---|---|
231
+
|`llm_validate` prompt → model | Yes — whole prompt, including `alert_json`|
232
+
|`analyze_impact` prompt → model | Yes — whole prompt |
233
+
| Impact `description` / `concerns` / `manual_steps` → PR body | Yes, on parse |
234
+
| PR title and body → GitHub, watch notifications | Yes, immediately before `open-pr`|
235
+
|`NotificationPayload` → console, run log, webhook | Yes, in `_notify_payload` — one chokepoint, so a backend added later inherits it |
236
+
| The commit diff itself |**No — not possible.** A commit that removes a line renders that line. This is what the PR warning exists for |
237
+
238
+
Candidates come from the alert's `code_snippet`, longest first: the quoted value
239
+
on the right of an assignment, any other quoted literal, and the whole stripped
240
+
line (which is the form a unified diff contains). Keys are never candidates —
241
+
`API_KEY = "sk-live-…"` yields the value, never `API_KEY`, because the variable
242
+
name is exactly what the PR has to tell an operator to set.
243
+
244
+
Only secret findings build a live redactor. For a SAST or CVE alert the
245
+
`code_snippet` is ordinary vulnerable code, and scrubbing it from the diff would
246
+
blind gate 2 to the lines it has to judge.
247
+
208
248
## Degradation policy
209
249
210
250
A harness that fails closed on every unknown never finishes; one that fails open
Copy file name to clipboardExpand all lines: plugins/security-engineer/skills/run/fix-agents/secret.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -87,7 +87,8 @@ ENV API_KEY=""
87
87
88
88
## Important Notes
89
89
90
-
-**Never** commit the actual secret value, even to remove it — the PR body should only describe the type (e.g. "API key", "database password"), not the value itself.
90
+
-**Removing the secret is not remediation.** The credential was committed, so it stays in git history and stays live until somebody rotates it at the provider. The pull request is cleanup and a visible prompt to rotate — never describe it as the fix.
91
+
-**Never write the value into anything you author.** Not the PR body, not the commit message, not the branch name — describe the type only (e.g. "API key", "database password"). The removal diff is the one place the value unavoidably still appears: a deleted line is rendered by `git`, and no wording changes that. The orchestrator redacts known values from the PR body, the run log, the webhook payload and both model prompts, but treat that as a backstop, not a licence.
91
92
- If the secret appears in multiple files, fix all occurrences in the same branch/PR.
92
93
- The PR body must list every env var name that needs to be set by operators after merge.
0 commit comments