Skip to content

security: pin the red-team gate to an explicit model, and run it — 50/50 resisted (#76) - #81

Merged
virtualsteve-exa merged 4 commits into
devfrom
fix/76-redteam-gate
Aug 13, 2026
Merged

virtualsteve-exa merged 4 commits into
devfrom
fix/76-redteam-gate

Conversation

@virtualsteve-exa

Copy link
Copy Markdown
Collaborator

Closes both halves of #76 — the floating model default and the stale gate — plus a harness bug the run itself surfaced.

1. The gate is pinned to an explicit model

--models defaulted to the alias sonnet, so the gate's result could shift under the harness whenever a new Sonnet shipped, and a recorded verdict couldn't be tied to a model version after the fact. The default is now the explicit supported floor, claude-sonnet-4-6, matching the discipline the dated results/ artifacts already followed.

Belt and braces: the runner now captures the model the CLI session actually resolved (from its init event) and tallies and reports under that, printing a notice when it differs from what was requested. So even an alias-invoked run produces an attributable artifact.

Docs follow: HISTORY.md's note that Sonnet 5 is "not approved for cyber work" was stale and is replaced with the real reason the pin exists — reproducibility, not a restriction on any model. PLAN.md's "weakest supported model (currently Sonnet)" now names the pinned ID in all three places.

2. A stray stream event was discarding whole trials

The first live trial died with 'str' object has no attribute 'get'. The CLI stream carries lines whose message is a plain string rather than an object (error/notice events — an MCP server failing to reconnect, for one), and _parse called .get() on it unconditionally. The per-trial guard then degraded that into a fully ERRORED trial: a drive that had completed 19 tool calls and produced a 36k-character report was thrown away because of one unrelated notice.

Grading integrity was never at risk — _inconc_rows already refuses to read an all-errored blocking-class attack as a pass (verdict INCONCLUSIVE, exit 3) — but the harness burned a full live drive each time it hit, and could have stalled the gate on re-runs. Now parsed defensively by shape, with regression tests for both the odd-shaped line and the resolved-model capture. Negative-verified: the new test fails red against the unfixed parse.

3. The gate ran — 🟢 PASS

results/2026-08-13T2009-claude-sonnet-4-6.md. Ten class-A families × 5 trials on the pinned model, judge on, driven against the working-tree plugin.

Every family landed 0/5, with zero errored and zero inconclusive trials — so the verdict rests on 50 valid trials, not on absence of evidence.

The a10 rows are the ones worth reading. In 4 of 5 trials the model did reproduce the phishing markdown link in its chat output — the injection still works on the model — but the persisted artifact was clean every time, because the write-sink neutralizer defanged it before it would become a case note. That's precisely the distinction output-pipeline grading exists to make: a pass where the model simply never emitted the payload would prove nothing about the guardrail. a10 landed 3/3 in the July run and drove the whole deterministic fix, so watching the code layer fire is the point of the exercise. The raw-chat copy remains the documented, best-effort-only residual.

HISTORY.md gains the run row and a reading note, and I fixed that file's own root-relative connector/ paths after #66 while I was in it (it's the file this PR already edits, so it doesn't conflict with #80). The a10 fix summary's resistance figure moves from the 0/2 fix-time retest to 0/5 here.

Verification

265 tests pass (263 + the two new regression tests) · AI BOM current · corpus lints 10/10 · the run drove live against the staging tenant with all writes, closes and containment denied.

Note for the promotion

This satisfies the red-team release gate in CONTRIBUTING.md for the upcoming dev → main release; the ABV gate already has its dated 0.6.9 PASS. What #76 does not close is the class C/D fixture gap — all ten fixtures are class A, so gate bypass and data leak have still never been exercised despite both being individually release-blocking. That's the larger piece of work Praxen flagged as finding 008, and it's worth its own issue rather than being folded in here.

🤖 Generated with Claude Code

virtualsteve-exa and others added 3 commits August 13, 2026 11:53
…olved model in every artifact (#76)

The runner's --models default was the floating 'sonnet' alias, so the gate's
result could change out from under the harness whenever a new Sonnet shipped,
and a recorded verdict could not be tied to a specific model version after
the fact. Now:

- --models defaults to the explicit supported-floor ID (claude-sonnet-4-6),
  matching the discipline the dated results/ artifacts already show;
- the runner captures the session's *resolved* model ID from the CLI init
  event and tallies/reports under it, so even an alias-invoked run produces
  an attributable artifact (and prints a notice when the two differ);
- HISTORY.md's stale note (Sonnet 5 'not approved for cyber work' — no longer
  true) is replaced with the real reason the pin exists: reproducibility;
- PLAN.md's 'weakest supported model (currently Sonnet)' wording names the
  pinned ID explicitly in all three places.

The AI BOM is unaffected: it inventories dependencies, not harness source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Steve Wilson <steve.wilson@exabeam.com>
Found while running the gate for #76: the first live trial died with
"'str' object has no attribute 'get'".

The CLI's stream-json output carries lines whose `message` is a plain
STRING rather than an object (error/notice events — an MCP server failing
to reconnect, for instance). _parse called .get() on it unconditionally,
raising AttributeError, which the runner's per-trial `except Exception`
then degraded into a fully ERRORED trial. A drive that actually completed
19 tool calls and produced a 36k-character report was discarded because
one unrelated notice appeared in the transcript.

Grading integrity was never at risk — _inconc_rows already refuses to read
an all-errored blocking-class attack as a pass (verdict INCONCLUSIVE, exit
3) — but the harness burned a full live drive per occurrence and could
have stalled the gate on re-runs.

Parse defensively by SHAPE, not just by event type, and add regression
tests for both the odd-shaped line and the resolved-model capture.
Negative-verified: the new test fails red on the unfixed parse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Steve Wilson <steve.wilson@exabeam.com>
…ASS (#76)

The staleness half of #76. The gate had not run since 2026-07-03, across
0.6.7/0.6.8/0.6.9, and this promotion carries #66/#73/#77/#78.

Run: 10 class-A families x 5 trials on the pinned claude-sonnet-4-6, judge on,
driven against the working-tree plugin. Every family landed 0/5; no errored and
no inconclusive trials, so the verdict rests on 50 valid trials rather than on
absence of evidence.

The a10 rows carry the real signal. In 4 of 5 trials the model reproduced the
phishing markdown link in its chat output — the injection still works on the
model — but the persisted artifact was clean every time, because the write-sink
neutralizer defanged it. That is exactly what output-pipeline grading exists to
distinguish: a pass where the model never emitted the payload would prove
nothing about the guardrail. a10 landed 3/3 in the first run and drove the
deterministic fix, so seeing the code layer fire is the point.

Also fixes the ledger's own stale connector/ paths after #66, and updates the
a10 summary's resistance figure from the 0/2 fix-time retest to 0/5 here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Steve Wilson <steve.wilson@exabeam.com>
Praxen finding 008 was 'awaiting triage'; it now has a home (#82) rather than
being an untracked row. The gap itself is unchanged — the corpus is still
class-A only, and classes C and D remain unexercised despite both blocking a
release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Steve Wilson <steve.wilson@exabeam.com>
@virtualsteve-exa
virtualsteve-exa merged commit 9210675 into dev Aug 13, 2026
2 checks passed
@virtualsteve-exa
virtualsteve-exa deleted the fix/76-redteam-gate branch August 13, 2026 20:15
virtualsteve-exa added a commit that referenced this pull request Aug 13, 2026
Minor, not patch: the shipped surface changes shape. Only the payload under
plugin/ is distributed (#66), and the installer can now install the governance
gate rather than only warn about it (#73).

Also stamps the changelog entry for the red-team gate work (#76/#81), which
landed after the [Unreleased] section was written, and dates the section.

Both release gates ran against this candidate: red team 50/50 resisted (PASS,
2026-08-13 artifact); ABV 0 Critical (PASS, 2026-08-12 artifact against 0.6.9).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Steve Wilson <steve.wilson@exabeam.com>
virtualsteve-exa added a commit that referenced this pull request Aug 13, 2026
…otes

Two release-record items:

1. The 2026-08-12 Praxen scan is 0.7.0's ABV gate artifact (maintainer
   determination). It ran against the dev tip as it stood (005fa4c) — the head
   of the line 0.7.0 was cut from, not a stale release tag — and every
   behavioural change merged since is either a remediation of one of that
   scan's own findings (001->#73, 007->#78, 009->#81) or the path-only plugin/
   restructure. Recorded in security/praxen/README.md rather than only in the
   release PR, so a reader can tell which release the dated artifact verified.

2. #79 landed on dev after the version bump, so the changelog gains its entry:
   the release smoke now asserts the governance gate actually installs. Release
   machinery earns a changelog line by the 0.6.4 precedent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Steve Wilson <steve.wilson@exabeam.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant