Skip to content

feat(signature): surface an audit-only signing-regression finding on drift from the repo baseline (JEF-264)#139

Merged
thejefflarson merged 1 commit into
mainfrom
thejefflarson/jef-264-surface-a-signing-regression-finding-on-drift-from-the-repo
Jul 1, 2026
Merged

feat(signature): surface an audit-only signing-regression finding on drift from the repo baseline (JEF-264)#139
thejefflarson merged 1 commit into
mainfrom
thejefflarson/jef-264-surface-a-signing-regression-finding-on-drift-from-the-repo

Conversation

@thejefflarson

Copy link
Copy Markdown
Owner

What

A repo with an established signed history that suddenly serves an unsigned/invalid image — or one signed by a new identity — is the push-access-compromise signal, and was invisible until now even with the JEF-263 baseline. This surfaces a loud, audit-only signing-regression finding on drift from the baseline. Still admits (shadow invariant, ADR-0016); enforcement (JEF-265) and Rekor history (JEF-266) stay out of scope.

Classifier design (engine/src/engine/signing_drift.rs)

Pure, deterministic classify(Option<&SigningBaseline>, &SigningPosture) -> SigningDrift:

  • Continuous — signed by a known identity (normal redeploy, even a new digest), or a transient/unknown state with no history to regress against.
  • NewRepo — first signed sight of a never-seen repo (TOFU cold start; baseline recorded, no finding).
  • Regression { kind, established }Unsigned / Invalid / IdentityChange{new_identity,new_issuer}. established is read straight from the baseline (JEF-263's 24h flag), so the classifier stays clock-free and exhaustively unit-testable.

Rules honored: signed→unsigned/invalid on an established repo ⇒ regression; new signer ⇒ a distinct regression; known-identity new digest ⇒ no finding; cold-start ⇒ no regression; regression against a cold baseline ⇒ reduced intensity (established:false).

Status-strip honesty model

Regressions feed the strip without the reachability Decision/Finding pipeline. The sweep records each regression onto the admission-decision log (SigningRegression/<repo>, decision allow); DashboardState derives (established, cold) counts from that log and folds them into StatusStripProps on every tab via with_signing_regressions. Established regressions count toward breach (block green and the calm "watching" reading); cold regressions map to uncertain (block green, read as watching). A standing regression can never show green.

Render

A loud signing regression banner on the repo group (breach rail + ● glyph + word, lexically distinct from calm "not signed"), stating before→after with both identities in full. Every untrusted Fulcio SAN is emitted via maud interpolation only — no PreEscaped, no markup concatenation, and data-regression uses the fixed kind token, never identity text.

Tests

  • Classifier: 12 pure/deterministic cases (whole rule table).
  • Sweep detection: →unsigned, →invalid, identity-change, normal-redeploy (none), cold-start (none), regression-against-cold (reduced).
  • Inventory mapping + per-repo counts: 8.
  • Regression render + crafted-identity escaping: 3.
  • Strip honesty (established→breach, cold→uncertain): 2.

Gates (all green)

cargo fmt · cargo build · cargo build --example dashboard_preview · cargo clippy --all-targets (0 warnings) · cargo test (448 unit + guards, incl. the 1,000-line file-size guard).

Judgment calls / scope notes

  • Routing via the PolicyDecisionLog (honored literally): regressions are encoded as self-describing log rows (signature = regression-<kind>-<strength>, reason = <after> | before: <ids>) and parsed back in the view_model — the same string idiom the existing signing-inventory mapper already uses — so no new state handle or baseline access at render time.
  • props.rs split: the JEF-264 additions pushed props.rs past the 1,000-line hard cap, so the signing-inventory presentation types were moved into props/signing.rs (re-exported flat). Required by the change, not opportunistic.
  • Identity-change on a cold baseline is treated as a reduced-intensity regression too (consistent with the general cold-baseline rule); folding into the baseline set bounds it to fire once per new signer.
  • Staleness: like the existing observation rows, a regression row lingers in the bounded ring until re-swept/evicted — consistent with JEF-261/262 semantics.

Closes JEF-264

🤖 Generated with Claude Code

…drift from the repo baseline (JEF-264)

A repo with an established signed history suddenly serving unsigned/invalid,
or signed by a NEW identity, is the push-access-compromise signal — invisible
until now even with the JEF-263 baseline. This adds a pure, deterministic drift
classifier over (baseline, fresh posture) and surfaces a loud, audit-only
signing-regression finding on drift, still admitting (shadow invariant).

- Classifier (engine/signing_drift.rs): pure Continuous / Regression{Unsigned |
  Invalid | IdentityChange} / NewRepo over (SigningBaseline, SigningPosture),
  reading the baseline's `established` flag for reduced-intensity on cold
  baselines. Never a clock/IO — exhaustively unit-tested.
- Sweep wiring: classify each observed posture against the CURRENT baseline
  BEFORE learning, and record a regression finding onto the same admission log
  (SigningRegression/<repo>, decision `allow` — never denies). No egress; not
  journaled, rebuilt per pass.
- Render: a loud "signing regression" banner on the repo group (breach rail +
  ● glyph + word, lexically distinct from calm "not signed"), stating
  before→after with BOTH identities in FULL. Every untrusted Fulcio SAN escaped
  via maud interpolation only (no PreEscaped, no class= from untrusted text).
- Status-strip honesty: established regressions count toward breach, cold ones
  toward uncertain — both forbid green — wired WITHOUT the reachability
  Decision/Finding pipeline, via SigningRegressionCounts folded into the strip
  on every tab.
- Split props.rs into props/{mod,signing}.rs to stay under the 1,000-line cap.

Tests: classifier (12, pure), sweep detection (6: →unsigned/→invalid/identity/
normal-redeploy-none/cold-start-none/cold-reduced), inventory mapping + counts
(8), regression render + crafted-identity escaping (3), strip honesty (2).

Closes JEF-264

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP
@thejefflarson thejefflarson merged commit a01fb93 into main Jul 1, 2026
3 checks passed
@thejefflarson thejefflarson deleted the thejefflarson/jef-264-surface-a-signing-regression-finding-on-drift-from-the-repo branch July 1, 2026 07:18
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