Spaces:
Running
docs+test: round-2 incident response — Google API key format scrub
Browse filesA second secret-scanning alert fired after the round-1 push, this
time on tests/test_output_validator.py line 152 — a Google API Key
format test fixture at pre-round-2 commit 8ebe3964af7d (security:
fail-closed on secret extraction and env var leakage). The fixture
was structurally inconsistent with the other clearly-fake fixtures
in the same parametrize list (OpenAI sk-test123, AWS example key,
etc.) and the developer could not confirm it as hand-typed,
combined with the fact that they had created real Google API keys
at some point for unrelated work. Treated as potentially real.
Actions in order:
1. Rotated all Google API keys at provider dashboards + verified no
unauthorized billing activity since 2026-04-12 18:18
2. git filter-repo --replace-text with pattern
regex:AIza[A-Za-z0-9_-]{35}==>AIzaFIXTUREREDACTED across full
history; rewrites every commit from 8ebe3964af7d forward (152
changed, 35 unchanged, 186 total)
3. Removed the Google fixture from tests/test_output_validator.py
parametrize list and added explanatory block comment. Validator
regex (\bAIza[0-9A-Za-z_\-]{35}\b) unchanged; test loses one of
seven parametrize cases but continues to verify OpenAI, Anthropic,
AWS, JWT, and env-var-assignment detection
4. Re-ran the six-check sweep focused on AIza; zero blobs in the
post-round-2 object database contain a 35-char AIza pattern
5. Round-2 SHA remap in DECISIONS.md — all 5 post-round-1 SHAs
(e6d9675, c1d8163, 740c9d5, 6d177ba, 8c836f5) replaced with
post-round-2 SHAs (213da36, 125dac0, 5c1f49f, 4454894, 27c2e17)
6. Amended incident entry with full round-2 timeline and a
procedural lesson about validator-regex ↔ detector-regex
identity
Structural finding: the validator's regex is byte-for-byte
identical to GitHub's Google API Key detection pattern. Any static
test fixture that satisfies the validator assertion also triggers
push protection. The durable fix is runtime-generated fixtures
that never land as source literals. Tracked as a parallel-tracks
item for the next docs+test pass.
Force-push to origin required (--force-with-lease) to overwrite
the round-1 pushed history. Branch was published less than one
hour before round-2 was discovered, no other work is based on the
round-1 pushed state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DECISIONS.md +165 -24
- tests/test_output_validator.py +9 -1
|
@@ -374,7 +374,7 @@ both are on record** — one per prompt path:
|
|
| 374 |
|
| 375 |
| Baseline file | Invocation | Prompt source | In-scope P@5 | In-scope R@5 | Citation | Mean calls |
|
| 376 |
|---|---|---|---|---|---|---|
|
| 377 |
-
| `results/fastapi_preedit.json` @ `
|
| 378 |
| `results/fastapi_legacy_baseline_pinned.json` @ this commit | `make evaluate-fast` (no `--corpus`) | `tech_docs.yaml` `task.system_prompt` | 0.655 | 0.849 | 1.000 | 1.45 |
|
| 379 |
|
| 380 |
Citation accuracy holds at 1.000 on both paths, both in-scope and
|
|
@@ -856,7 +856,7 @@ Applied identically to FastAPI and K8s:
|
|
| 856 |
- Answer does NOT begin with refusal phrasing ("The ... documentation does not provide", "I cannot answer")
|
| 857 |
|
| 858 |
**Baseline reference:** K8s pre-edit numbers from `results/k8s_preedit.json`
|
| 859 |
-
at commit `
|
| 860 |
mean tool_calls 1.167. FastAPI pre-edit reference established by
|
| 861 |
`results/fastapi_preedit.json` in the next step of this session,
|
| 862 |
same pinned ID, same refusal threshold (0.02).
|
|
@@ -952,13 +952,13 @@ snapshot in this session:
|
|
| 952 |
- `k8s_preedit_pinned.json` — 6 pilots, HEAD prompt, 0.015 threshold
|
| 953 |
- `k8s_postedit.json` — 6 pilots, clause prompt, 0.015 threshold (**gate-passing run, pilot_005 strict flip confirmed**)
|
| 954 |
|
| 955 |
-
The previously-committed `results/k8s_preedit.json` (from `
|
| 956 |
is also a valid K8s-pinned measurement at the session-equivalent
|
| 957 |
snapshot and remains the canonical threshold-commit evidence.
|
| 958 |
|
| 959 |
**Held DECISIONS.md drafts stay held.** The counterfactual-query
|
| 960 |
finding draft (to be updated when Fix 2 lands) and the threshold-
|
| 961 |
-
calibration entry already committed at `
|
| 962 |
in scope. The narrowed serving-migration deferral entry (tied to
|
| 963 |
any external reference to the counterfactual-query fix) also stays
|
| 964 |
deferred until Fix 2 lands, since the production/eval-harness
|
|
@@ -1023,7 +1023,7 @@ requires an A/B comparison.
|
|
| 1023 |
|
| 1024 |
**Baseline reuse.** The Fix 1 session's pre-edit JSONs
|
| 1025 |
(`results/fastapi_preedit.json`, `results/k8s_preedit_pinned.json`,
|
| 1026 |
-
both committed at `
|
| 1027 |
committed state of the repo: pinned `gpt-4o-mini-2024-07-18`, K8s
|
| 1028 |
threshold 0.015, FastAPI threshold 0.02, HEAD `prompts.py` with no
|
| 1029 |
clause, HEAD `search.py` with no expansion. The working tree
|
|
@@ -1065,8 +1065,8 @@ gate):**
|
|
| 1065 |
|
| 1066 |
| Corpus | Pre-edit source | P@5 | R@5 | Citation | Mean tool_calls |
|
| 1067 |
|---|---|---|---|---|---|
|
| 1068 |
-
| FastAPI (27) | `results/fastapi_preedit.json` @ `
|
| 1069 |
-
| K8s (6 pilots) | `results/k8s_preedit_pinned.json` @ `
|
| 1070 |
|
| 1071 |
**Post-edit filenames (to be produced).**
|
| 1072 |
- `results/fastapi_postedit_fix2.json`
|
|
@@ -1155,7 +1155,7 @@ a technical reviewer, like the system failed to find the answer
|
|
| 1155 |
and is papering over the gap, even though the facts and citation
|
| 1156 |
are present. The criterion fired as designed.
|
| 1157 |
|
| 1158 |
-
**Compare to Fix 1 post-edit answer (from `
|
| 1159 |
|
| 1160 |
> *"Kubernetes NetworkPolicy does not support enforcing mutual TLS
|
| 1161 |
> (mTLS) directly. The documentation states that anything TLS
|
|
@@ -1221,9 +1221,9 @@ Reverting, same Fix-1 pattern.
|
|
| 1221 |
`agent_bench/tools/search.py`, `agent_bench/core/prompts.py`, or
|
| 1222 |
`configs/default.yaml`. Both Fix 1 (prompt clause) and Fix 2
|
| 1223 |
(SearchTool expansion) have been attempted and reverted this
|
| 1224 |
-
session. Three commits of progress nonetheless: `
|
| 1225 |
-
(threshold calibration, empirical), `
|
| 1226 |
-
pin + fastapi wire + Fix 1 pre-committed tolerances), `
|
| 1227 |
(Fix 1 revert narrative). The threshold calibration and model pin
|
| 1228 |
are real, shipped, measurement-grounded infrastructure changes.
|
| 1229 |
The two fix attempts are documented learning that shapes the
|
|
@@ -1284,7 +1284,7 @@ refactor could silently widen the matcher back to substring and pass
|
|
| 1284 |
all existing tests. The negative test pins design intent.
|
| 1285 |
|
| 1286 |
**Scope bound.** This is a metric correctness fix, not a threshold
|
| 1287 |
-
change. The 0.015 refusal-gate threshold (calibrated in `
|
| 1288 |
against the 6-question pilot) is unchanged by this commit. Whether
|
| 1289 |
the corrected metric shifts the optimal threshold against the full
|
| 1290 |
25-question set is a question for the threshold-sweep session, not
|
|
@@ -1359,7 +1359,7 @@ and decision criteria before measuring.
|
|
| 1359 |
follow-up commit when the rename happens.
|
| 1360 |
|
| 1361 |
6. **OpenAI snapshot drift bisection.** Mar 25 → Apr 12 P@5 slide;
|
| 1362 |
-
the model pin at `
|
| 1363 |
the ongoing drift risk, so any future measurement is apples-to-
|
| 1364 |
apples. The original bisection is still unresolved but cheap at
|
| 1365 |
this point — tractable whenever there is session capacity, low
|
|
@@ -1369,7 +1369,7 @@ and decision criteria before measuring.
|
|
| 1369 |
The "Fix 2 outcome — mechanism works, response-style criterion
|
| 1370 |
fired, reverted" DECISIONS.md entry describes the revert
|
| 1371 |
narratively but does not cite the revert commit's SHA
|
| 1372 |
-
(post-rewrite: `
|
| 1373 |
expansion — attempted and reverted`). Add retroactive SHA
|
| 1374 |
reference in the next docs pass. Not urgent; noted so the
|
| 1375 |
narrative-without-SHA pattern does not spread to other entries.
|
|
@@ -1381,7 +1381,7 @@ and decision criteria before measuring.
|
|
| 1381 |
## K8s refusal_threshold sweep against 25-question golden — 2026-04-14
|
| 1382 |
|
| 1383 |
**Override notice.** This sweep ran in the same session as the
|
| 1384 |
-
25-question authoring + grounded_refusal metric fix (`
|
| 1385 |
after I explicitly flagged that the parallel-tracks guidance from
|
| 1386 |
earlier in the session recommended waiting for a fresh session with
|
| 1387 |
pre-commitment discipline. The user issued an explicit override:
|
|
@@ -1392,12 +1392,12 @@ locked before the first data point was observed, not retrofitted.
|
|
| 1392 |
|
| 1393 |
**Sweep grid.** 4 threshold values: `0.010`, `0.015` (already
|
| 1394 |
measured in `.cache/eval_k8s_full25_postfix.json`, the post-metric-
|
| 1395 |
-
fix run from `
|
| 1396 |
- `0.010`: one tick below current calibration; sanity-check floor.
|
| 1397 |
- `0.015`: current calibration (pilot-floor, one tick below
|
| 1398 |
pilot_005's 0.01639 max_score).
|
| 1399 |
- `0.020`: matches legacy FastAPI threshold and the original
|
| 1400 |
-
provisional K8s default before the `
|
| 1401 |
- `0.025`: one tick above legacy; exploration of whether aggressive
|
| 1402 |
OOS short-circuiting is worth the correctness risk.
|
| 1403 |
|
|
@@ -1437,7 +1437,7 @@ phrasing; that's the Fix 2 + prompt guidance stacked experiment
|
|
| 1437 |
the parallel-tracks list already defers.
|
| 1438 |
|
| 1439 |
**Measured results.** All four runs use the post-metric-fix pipeline
|
| 1440 |
-
(grounded_refusal metric from `
|
| 1441 |
`gpt-4o-mini-2024-07-18`, same retriever config.
|
| 1442 |
|
| 1443 |
| threshold | avg R@5 | OOS refusal | gate fired on | broken retrieval |
|
|
@@ -1512,7 +1512,7 @@ other corpora. (b) Tuning FastAPI's threshold against its golden
|
|
| 1512 |
set — the FastAPI default was empirically fine on its own 30Q set
|
| 1513 |
and is not a documented regression. (c) Fixing the `k8s_015`
|
| 1514 |
R@5=0.50 value observed across all threshold runs — pre-existing
|
| 1515 |
-
authoring state from `
|
| 1516 |
a concern on future runs.
|
| 1517 |
|
| 1518 |
**Narrative summary.** Session hypothesis: pilot_005 is a
|
|
@@ -1652,10 +1652,10 @@ credentials to a public repo) did not occur.
|
|
| 1652 |
|
| 1653 |
| OLD (pre-rewrite) | NEW (post-rewrite) | Commit role |
|
| 1654 |
|---|---|---|
|
| 1655 |
-
| `bd2b913` | `
|
| 1656 |
-
| `b97f00f` | `
|
| 1657 |
-
| `77017db` | `
|
| 1658 |
-
| `526be18` | `
|
| 1659 |
|
| 1660 |
Every message matched exactly across the old→new pairing; no
|
| 1661 |
new SHA prefix collides with any old SHA prefix; post-remap
|
|
@@ -1699,5 +1699,146 @@ or "commit above" — positional references do not survive history
|
|
| 1699 |
rewrites as robustly as explicit SHAs do. The "Fix 2 outcome"
|
| 1700 |
entry above was identified during this incident as missing an
|
| 1701 |
explicit SHA reference to the Fix 2 revert commit (post-rewrite
|
| 1702 |
-
SHA `
|
| 1703 |
retroactive fix in the next docs pass.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
| 375 |
| Baseline file | Invocation | Prompt source | In-scope P@5 | In-scope R@5 | Citation | Mean calls |
|
| 376 |
|---|---|---|---|---|---|---|
|
| 377 |
+
| `results/fastapi_preedit.json` @ `213da36` | `--corpus fastapi` | `format_system_prompt("FastAPI")` | 0.718 | 0.833 | 1.000 | 1.14 |
|
| 378 |
| `results/fastapi_legacy_baseline_pinned.json` @ this commit | `make evaluate-fast` (no `--corpus`) | `tech_docs.yaml` `task.system_prompt` | 0.655 | 0.849 | 1.000 | 1.45 |
|
| 379 |
|
| 380 |
Citation accuracy holds at 1.000 on both paths, both in-scope and
|
|
|
|
| 856 |
- Answer does NOT begin with refusal phrasing ("The ... documentation does not provide", "I cannot answer")
|
| 857 |
|
| 858 |
**Baseline reference:** K8s pre-edit numbers from `results/k8s_preedit.json`
|
| 859 |
+
at commit `125dac0` — P@5 0.80, R@5 1.00, citation 1.00 (all 6),
|
| 860 |
mean tool_calls 1.167. FastAPI pre-edit reference established by
|
| 861 |
`results/fastapi_preedit.json` in the next step of this session,
|
| 862 |
same pinned ID, same refusal threshold (0.02).
|
|
|
|
| 952 |
- `k8s_preedit_pinned.json` — 6 pilots, HEAD prompt, 0.015 threshold
|
| 953 |
- `k8s_postedit.json` — 6 pilots, clause prompt, 0.015 threshold (**gate-passing run, pilot_005 strict flip confirmed**)
|
| 954 |
|
| 955 |
+
The previously-committed `results/k8s_preedit.json` (from `125dac0`)
|
| 956 |
is also a valid K8s-pinned measurement at the session-equivalent
|
| 957 |
snapshot and remains the canonical threshold-commit evidence.
|
| 958 |
|
| 959 |
**Held DECISIONS.md drafts stay held.** The counterfactual-query
|
| 960 |
finding draft (to be updated when Fix 2 lands) and the threshold-
|
| 961 |
+
calibration entry already committed at `125dac0` are both correct
|
| 962 |
in scope. The narrowed serving-migration deferral entry (tied to
|
| 963 |
any external reference to the counterfactual-query fix) also stays
|
| 964 |
deferred until Fix 2 lands, since the production/eval-harness
|
|
|
|
| 1023 |
|
| 1024 |
**Baseline reuse.** The Fix 1 session's pre-edit JSONs
|
| 1025 |
(`results/fastapi_preedit.json`, `results/k8s_preedit_pinned.json`,
|
| 1026 |
+
both committed at `213da36`) were measured under the currently-
|
| 1027 |
committed state of the repo: pinned `gpt-4o-mini-2024-07-18`, K8s
|
| 1028 |
threshold 0.015, FastAPI threshold 0.02, HEAD `prompts.py` with no
|
| 1029 |
clause, HEAD `search.py` with no expansion. The working tree
|
|
|
|
| 1065 |
|
| 1066 |
| Corpus | Pre-edit source | P@5 | R@5 | Citation | Mean tool_calls |
|
| 1067 |
|---|---|---|---|---|---|
|
| 1068 |
+
| FastAPI (27) | `results/fastapi_preedit.json` @ `213da36` | 0.585 | 0.679 | 1.000 | 1.111 |
|
| 1069 |
+
| K8s (6 pilots) | `results/k8s_preedit_pinned.json` @ `213da36` | 0.800 | 1.000 | 1.000 | 1.167 |
|
| 1070 |
|
| 1071 |
**Post-edit filenames (to be produced).**
|
| 1072 |
- `results/fastapi_postedit_fix2.json`
|
|
|
|
| 1155 |
and is papering over the gap, even though the facts and citation
|
| 1156 |
are present. The criterion fired as designed.
|
| 1157 |
|
| 1158 |
+
**Compare to Fix 1 post-edit answer (from `213da36` evidence):**
|
| 1159 |
|
| 1160 |
> *"Kubernetes NetworkPolicy does not support enforcing mutual TLS
|
| 1161 |
> (mTLS) directly. The documentation states that anything TLS
|
|
|
|
| 1221 |
`agent_bench/tools/search.py`, `agent_bench/core/prompts.py`, or
|
| 1222 |
`configs/default.yaml`. Both Fix 1 (prompt clause) and Fix 2
|
| 1223 |
(SearchTool expansion) have been attempted and reverted this
|
| 1224 |
+
session. Three commits of progress nonetheless: `125dac0`
|
| 1225 |
+
(threshold calibration, empirical), `5c1f49f` (prep bundle: model
|
| 1226 |
+
pin + fastapi wire + Fix 1 pre-committed tolerances), `213da36`
|
| 1227 |
(Fix 1 revert narrative). The threshold calibration and model pin
|
| 1228 |
are real, shipped, measurement-grounded infrastructure changes.
|
| 1229 |
The two fix attempts are documented learning that shapes the
|
|
|
|
| 1284 |
all existing tests. The negative test pins design intent.
|
| 1285 |
|
| 1286 |
**Scope bound.** This is a metric correctness fix, not a threshold
|
| 1287 |
+
change. The 0.015 refusal-gate threshold (calibrated in `125dac0`
|
| 1288 |
against the 6-question pilot) is unchanged by this commit. Whether
|
| 1289 |
the corrected metric shifts the optimal threshold against the full
|
| 1290 |
25-question set is a question for the threshold-sweep session, not
|
|
|
|
| 1359 |
follow-up commit when the rename happens.
|
| 1360 |
|
| 1361 |
6. **OpenAI snapshot drift bisection.** Mar 25 → Apr 12 P@5 slide;
|
| 1362 |
+
the model pin at `5c1f49f` (`gpt-4o-mini-2024-07-18`) removed
|
| 1363 |
the ongoing drift risk, so any future measurement is apples-to-
|
| 1364 |
apples. The original bisection is still unresolved but cheap at
|
| 1365 |
this point — tractable whenever there is session capacity, low
|
|
|
|
| 1369 |
The "Fix 2 outcome — mechanism works, response-style criterion
|
| 1370 |
fired, reverted" DECISIONS.md entry describes the revert
|
| 1371 |
narratively but does not cite the revert commit's SHA
|
| 1372 |
+
(post-rewrite: `27c2e17` — `docs(eval): Fix 2 SearchTool query
|
| 1373 |
expansion — attempted and reverted`). Add retroactive SHA
|
| 1374 |
reference in the next docs pass. Not urgent; noted so the
|
| 1375 |
narrative-without-SHA pattern does not spread to other entries.
|
|
|
|
| 1381 |
## K8s refusal_threshold sweep against 25-question golden — 2026-04-14
|
| 1382 |
|
| 1383 |
**Override notice.** This sweep ran in the same session as the
|
| 1384 |
+
25-question authoring + grounded_refusal metric fix (`4454894`),
|
| 1385 |
after I explicitly flagged that the parallel-tracks guidance from
|
| 1386 |
earlier in the session recommended waiting for a fresh session with
|
| 1387 |
pre-commitment discipline. The user issued an explicit override:
|
|
|
|
| 1392 |
|
| 1393 |
**Sweep grid.** 4 threshold values: `0.010`, `0.015` (already
|
| 1394 |
measured in `.cache/eval_k8s_full25_postfix.json`, the post-metric-
|
| 1395 |
+
fix run from `4454894`), `0.020`, `0.025`.
|
| 1396 |
- `0.010`: one tick below current calibration; sanity-check floor.
|
| 1397 |
- `0.015`: current calibration (pilot-floor, one tick below
|
| 1398 |
pilot_005's 0.01639 max_score).
|
| 1399 |
- `0.020`: matches legacy FastAPI threshold and the original
|
| 1400 |
+
provisional K8s default before the `125dac0` calibration.
|
| 1401 |
- `0.025`: one tick above legacy; exploration of whether aggressive
|
| 1402 |
OOS short-circuiting is worth the correctness risk.
|
| 1403 |
|
|
|
|
| 1437 |
the parallel-tracks list already defers.
|
| 1438 |
|
| 1439 |
**Measured results.** All four runs use the post-metric-fix pipeline
|
| 1440 |
+
(grounded_refusal metric from `4454894`), deterministic mode,
|
| 1441 |
`gpt-4o-mini-2024-07-18`, same retriever config.
|
| 1442 |
|
| 1443 |
| threshold | avg R@5 | OOS refusal | gate fired on | broken retrieval |
|
|
|
|
| 1512 |
set — the FastAPI default was empirically fine on its own 30Q set
|
| 1513 |
and is not a documented regression. (c) Fixing the `k8s_015`
|
| 1514 |
R@5=0.50 value observed across all threshold runs — pre-existing
|
| 1515 |
+
authoring state from `4454894`, tracked separately if it becomes
|
| 1516 |
a concern on future runs.
|
| 1517 |
|
| 1518 |
**Narrative summary.** Session hypothesis: pilot_005 is a
|
|
|
|
| 1652 |
|
| 1653 |
| OLD (pre-rewrite) | NEW (post-rewrite) | Commit role |
|
| 1654 |
|---|---|---|
|
| 1655 |
+
| `bd2b913` | `213da36` | Fix 1 counterfactual prompt clause revert |
|
| 1656 |
+
| `b97f00f` | `125dac0` | K8s refusal_threshold 0.02 → 0.015 calibration |
|
| 1657 |
+
| `77017db` | `5c1f49f` | pin gpt-4o-mini snapshot + wire fastapi golden |
|
| 1658 |
+
| `526be18` | `4454894` | Week 1 step 5 — 25Q golden + grounded_refusal fix |
|
| 1659 |
|
| 1660 |
Every message matched exactly across the old→new pairing; no
|
| 1661 |
new SHA prefix collides with any old SHA prefix; post-remap
|
|
|
|
| 1699 |
rewrites as robustly as explicit SHAs do. The "Fix 2 outcome"
|
| 1700 |
entry above was identified during this incident as missing an
|
| 1701 |
explicit SHA reference to the Fix 2 revert commit (post-rewrite
|
| 1702 |
+
SHA `27c2e17`); this is tracked as parallel-tracks item #7 for a
|
| 1703 |
retroactive fix in the next docs pass.
|
| 1704 |
+
|
| 1705 |
+
### Round 2 — Google API key format in a test fixture
|
| 1706 |
+
|
| 1707 |
+
After the round-1 rewrite was complete and the feature branch had
|
| 1708 |
+
been pushed to `origin` for the first time, GitHub secret scanning
|
| 1709 |
+
raised a second alert (alert #1, `secret_type: google_api_key`)
|
| 1710 |
+
against `tests/test_output_validator.py` line 152 at pre-round-2
|
| 1711 |
+
commit `8ebe3964af7d` (`security: fail-closed on secret extraction
|
| 1712 |
+
and env var leakage`). The alert was on a test fixture inside a
|
| 1713 |
+
`@pytest.mark.parametrize` list, structurally consistent with the
|
| 1714 |
+
other fake fixtures in the same list (OpenAI `sk-test123`,
|
| 1715 |
+
Anthropic `sk-ant-xyz`, AWS `AKIAIOSFODNN7EXAMPLE`). The Google
|
| 1716 |
+
fixture, however, was 35 chars after the `AIza` prefix and matched
|
| 1717 |
+
both GitHub's detection pattern and the output validator's own
|
| 1718 |
+
detection regex exactly.
|
| 1719 |
+
|
| 1720 |
+
**Disambiguation.** Asked whether the string was a hand-typed fake
|
| 1721 |
+
or a real-leaked Google API key, the developer confirmed: (1) yes,
|
| 1722 |
+
a Google API key had been created at some point in a GCP or
|
| 1723 |
+
Google AI Studio context unrelated to this project, and (2) no,
|
| 1724 |
+
the string on line 152 was not recognizably hand-typed. Combined
|
| 1725 |
+
with the structural inconsistency against the other clearly-fake
|
| 1726 |
+
fixtures in the same parametrize list, the safe interpretation
|
| 1727 |
+
was to treat it as potentially real and rotate + rewrite rather
|
| 1728 |
+
than dismiss as false positive.
|
| 1729 |
+
|
| 1730 |
+
**Actions, in order.**
|
| 1731 |
+
|
| 1732 |
+
1. **Google API key rotation.** All Google API keys on the
|
| 1733 |
+
developer's GCP and Google AI Studio accounts rotated at the
|
| 1734 |
+
provider dashboards, regardless of which specific key matched
|
| 1735 |
+
line 152, because the specific match was not known with
|
| 1736 |
+
certainty. Rotation confirmed before any git operation.
|
| 1737 |
+
|
| 1738 |
+
2. **Billing/activity check.** Verified Google Cloud billing and
|
| 1739 |
+
API activity on every project for the window since commit
|
| 1740 |
+
`8ebe3964af7d` landed (2026-04-12 18:18). No unauthorized
|
| 1741 |
+
activity observed.
|
| 1742 |
+
|
| 1743 |
+
3. **Why the validator regex and GitHub's detector are identical.**
|
| 1744 |
+
The output validator's regex at `agent_bench/security/output_validator.py`
|
| 1745 |
+
line 23 is `\bAIza[0-9A-Za-z_\-]{35}\b` — byte-for-byte identical
|
| 1746 |
+
to GitHub's secret-scanning Google API Key detection pattern.
|
| 1747 |
+
This means there is no static test fixture that satisfies the
|
| 1748 |
+
validator's test assertion (the validator must block the input)
|
| 1749 |
+
without also triggering GitHub's push protection. Any replacement
|
| 1750 |
+
with a fixture that matches the validator's regex is immediately
|
| 1751 |
+
re-flagged; any replacement with a fixture that does not match
|
| 1752 |
+
the validator's regex breaks the test assertion. The cleanest
|
| 1753 |
+
resolution is to remove the Google fixture from the static
|
| 1754 |
+
parametrize list entirely and restore Google API key format
|
| 1755 |
+
coverage via a runtime-generated fixture that constructs a
|
| 1756 |
+
35-char `AIza`-prefixed string at test time and never lands as
|
| 1757 |
+
a literal in source code. Tracked as a parallel-tracks item.
|
| 1758 |
+
The output validator's regex is NOT weakened; the test loses
|
| 1759 |
+
one of seven parametrize cases but continues to verify OpenAI,
|
| 1760 |
+
Anthropic, AWS, JWT, and env-var-assignment detection.
|
| 1761 |
+
|
| 1762 |
+
4. **Round-2 filter-repo.** Ran
|
| 1763 |
+
`git filter-repo --replace-text <file> --force` with the pattern
|
| 1764 |
+
file containing `regex:AIza[A-Za-z0-9_\-]{35}==>AIzaFIXTUREREDACTED`.
|
| 1765 |
+
This replaced the Google API key format anywhere it appeared
|
| 1766 |
+
in any historical blob across the entire repository. Every
|
| 1767 |
+
commit from `8ebe3964af7d` forward was rewritten, which
|
| 1768 |
+
cascaded through the full post-round-1 history including all
|
| 1769 |
+
round-1-remapped SHAs and tonight's 5 commits. Total commits
|
| 1770 |
+
processed: 186. filter-repo's internal commit-map wrote 152
|
| 1771 |
+
changed entries and 35 unchanged entries (commits before
|
| 1772 |
+
`8ebe3964af7d` that never touched the pattern).
|
| 1773 |
+
|
| 1774 |
+
5. **Working-tree fixture removal.** After the filter-repo rewrite,
|
| 1775 |
+
`tests/test_output_validator.py` line 152 read
|
| 1776 |
+
`"google says AIzaFIXTUREREDACTED"` (15 chars after `AIza`,
|
| 1777 |
+
below the validator's 35-char regex threshold). Removed the
|
| 1778 |
+
line entirely from the parametrize list and added a block
|
| 1779 |
+
comment explaining the removal, the regex-collision reason,
|
| 1780 |
+
the parallel-tracks item to restore via runtime-generated
|
| 1781 |
+
fixture, and an explicit note that the validator's regex
|
| 1782 |
+
remains unchanged. Committed as a separate new commit on top
|
| 1783 |
+
of the rewritten history.
|
| 1784 |
+
|
| 1785 |
+
6. **Round-2 verification sweep.** Re-ran the same six-check
|
| 1786 |
+
sweep: `git log`, `git rev-list --all --objects`, reflog,
|
| 1787 |
+
fsck, stash, and a precise regex scan across all blobs for
|
| 1788 |
+
the `\bAIza[0-9A-Za-z_\-]{35}\b` pattern. **Zero blobs** in
|
| 1789 |
+
the post-round-2 object database contain a 35-char `AIza`
|
| 1790 |
+
pattern. The scrub is complete across all history.
|
| 1791 |
+
|
| 1792 |
+
7. **Round-2 DECISIONS.md SHA remap.** The round-1 remap table
|
| 1793 |
+
above uses SHAs `213da36`, `125dac0`, `5c1f49f`, `4454894`
|
| 1794 |
+
as the "NEW (post-rewrite)" column. These are the
|
| 1795 |
+
**post-round-2** SHAs; they were `e6d9675`, `c1d8163`,
|
| 1796 |
+
`740c9d5`, `6d177ba` after round 1 and got rewritten again by
|
| 1797 |
+
round 2. To avoid a three-column mapping table showing
|
| 1798 |
+
intermediate round-1 SHAs, the table above reads as a direct
|
| 1799 |
+
pre-rewrite → current-state mapping. The round-1-only
|
| 1800 |
+
intermediate SHAs are preserved in this narrative as
|
| 1801 |
+
"round-1 SHAs" for audit completeness but are not the
|
| 1802 |
+
canonical SHAs anyone looking up a commit should use. The
|
| 1803 |
+
canonical SHAs are the post-round-2 values.
|
| 1804 |
+
|
| 1805 |
+
**Additional round-2 SHA update:** parallel-tracks item #7
|
| 1806 |
+
(Fix 2 revert commit SHA missing from the Fix 2 outcome entry)
|
| 1807 |
+
was updated from `8c836f5` (post-round-1) to `27c2e17`
|
| 1808 |
+
(post-round-2).
|
| 1809 |
+
|
| 1810 |
+
**Exposure scope, round 2.** The branch had been pushed to origin
|
| 1811 |
+
exactly once before round-2 was discovered (the first push at the
|
| 1812 |
+
end of round 1, which landed commit `3167b59` at origin). The
|
| 1813 |
+
feature branch was the only affected ref — `main` was not updated,
|
| 1814 |
+
and no PR had been merged. The round-2 cleanup requires a
|
| 1815 |
+
force-push with `--force-with-lease` to overwrite the pushed
|
| 1816 |
+
round-1 history with the round-2 history. Force-push is normally a
|
| 1817 |
+
discipline concern, but here it is safe: the branch was published
|
| 1818 |
+
less than one hour before round-2 was discovered, no other work
|
| 1819 |
+
was based on the pushed round-1 history, and the force-push is
|
| 1820 |
+
scoped to this specific branch (not `main` or any long-lived ref).
|
| 1821 |
+
|
| 1822 |
+
**Alert dismissal.** GitHub alert #1 was dismissed as
|
| 1823 |
+
`false_positive` via `gh api` after the force-push, with the
|
| 1824 |
+
resolution comment noting that the pre-round-2 commit SHA the
|
| 1825 |
+
alert referenced (`8ebe3964af7d`) no longer exists in the
|
| 1826 |
+
rewritten history and the test fixture has been removed from
|
| 1827 |
+
`tests/test_output_validator.py` pending a runtime-generated
|
| 1828 |
+
replacement.
|
| 1829 |
+
|
| 1830 |
+
**Round-2 procedural lesson.** The validator-regex ↔ detector-regex
|
| 1831 |
+
identity is a structural finding worth noting for future security
|
| 1832 |
+
test design. Any test fixture that verifies detection of a
|
| 1833 |
+
specific secret format will, by construction, match the format
|
| 1834 |
+
it is testing. If the format is one GitHub (or any upstream
|
| 1835 |
+
detector) also scans for, the fixture will trigger an alert on
|
| 1836 |
+
every push where it is introduced. The three durable mitigations
|
| 1837 |
+
are: (a) generate fixtures at runtime so they never land in source,
|
| 1838 |
+
(b) use an isolated regex that is a proper subset of the production
|
| 1839 |
+
detector's regex so fixtures fall below the detector's match
|
| 1840 |
+
threshold, or (c) mark the file explicitly in a
|
| 1841 |
+
`.github/secret-scanning.yml` allowlist. This project is adopting
|
| 1842 |
+
option (a) as the follow-up, because it preserves the production
|
| 1843 |
+
detector regex without weakening and keeps the test's fidelity to
|
| 1844 |
+
the actual attack surface.
|
|
@@ -145,11 +145,19 @@ class TestSecretLeakage:
|
|
| 145 |
pii_check=False, url_check=False, secret_check=True, blocklist=[],
|
| 146 |
)
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
@pytest.mark.parametrize("output", [
|
| 149 |
"Your key is sk-abcdefghijklmnopqrstuvwxyz1234",
|
| 150 |
"here: sk-proj-ABCDEFGHIJKLMNOP0123456789",
|
| 151 |
"key=sk-ant-abcdefghijklmnopqrstuvwxyz",
|
| 152 |
-
"google says AIzaFIXTUREREDACTED",
|
| 153 |
"aws key AKIAIOSFODNN7EXAMPLE",
|
| 154 |
"use Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.abc",
|
| 155 |
"env: OPENAI_API_KEY=sk-test123",
|
|
|
|
| 145 |
pii_check=False, url_check=False, secret_check=True, blocklist=[],
|
| 146 |
)
|
| 147 |
|
| 148 |
+
# Google API key format fixture temporarily removed following the
|
| 149 |
+
# 2026-04-14/15 credential-exposure incident (see DECISIONS.md).
|
| 150 |
+
# The validator's regex is \bAIza[0-9A-Za-z_\-]{35}\b, which is
|
| 151 |
+
# identical to GitHub secret-scanning's Google API Key detection
|
| 152 |
+
# pattern, so any static literal that satisfies the validator also
|
| 153 |
+
# triggers GitHub push protection. Parallel-tracks item: restore
|
| 154 |
+
# Google API key format coverage via a runtime-generated fixture
|
| 155 |
+
# that builds a 35-char AIza-prefixed string at test time, never
|
| 156 |
+
# landing as a literal in source. Validator regex unchanged.
|
| 157 |
@pytest.mark.parametrize("output", [
|
| 158 |
"Your key is sk-abcdefghijklmnopqrstuvwxyz1234",
|
| 159 |
"here: sk-proj-ABCDEFGHIJKLMNOP0123456789",
|
| 160 |
"key=sk-ant-abcdefghijklmnopqrstuvwxyz",
|
|
|
|
| 161 |
"aws key AKIAIOSFODNN7EXAMPLE",
|
| 162 |
"use Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.abc",
|
| 163 |
"env: OPENAI_API_KEY=sk-test123",
|