Repository navigation
fix(bug-campaign): accumulated proven fixes, each red on development (XWING-2203) - #153
Conversation
…WING-2203) verify_merkle_integrity coerced metadata.merkle_epoch with a bare int(), so a tampered layer with a non-integer epoch raised TypeError/ValueError out of the tamper-evidence reader instead of returning an IntegrityFinding. leaf_format 30 lines below already did the defensive coercion; epoch now mirrors it: one ERROR finding, early return. Red: test_non_numeric_epoch_is_error[None|x] — ValueError/TypeError at ledger.py:329 on unmodified development.
…ed verification (XWING-2203) verify_merkle_signature dispatches merkle_signing_method='identity' layers to a bare SigstoreOIDCBackend() when no explicit backend is passed — the only wiring traust-core uses — which structurally cannot verify (expected_identity/oidc_issuer are None). The collapsed result was an ERROR 'merkle_root_signature verification failed' for an intact layer: tamper-evidence language for a configuration gap, while the keypair analogue (no pubkey configured) is a WARNING. The backend layer already classifies this as outcome=refused reason=identity_policy_unavailable. Adds SigningBackend.can_verify (True on the base; policy presence for identity) and checks it after dispatch: an unconfigured policy now returns the same WARNING shape as an unconfigured key. Red: test_verify_merkle_signature_identity_without_policy_reports_unconfigured_not_failed — ERROR 'verification failed — OIDC verification requires expected_identity and oidc_issuer' on unmodified development.
…, not a crash (XWING-2203) verify_merkle_signature defaults sig_format to 1 when merkle_signature_format is absent, then feeds untrusted metadata.merkle_root straight into bytes.fromhex via merkle_root_payload — a tampered layer with a non-hex root raised ValueError out of the verifier (uncaught through traust-core's LedgerService._issues) instead of returning an IntegrityFinding like every sibling malformed field (bad leaf_format, bad sig_format, unknown method). The format-1 branch now catches ValueError and returns one ERROR finding. Red: test_verify_merkle_signature_non_hex_root_is_finding_not_crash — ValueError 'non-hexadecimal number found' at ledger.py:478 on unmodified development.
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: smith-xyz The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
…r wired (XWING-2203) traust-core — the library's only consumer — imports 11 names: the merkle append/fronter primitives, stamp/sign/verify, SigningConfig (keypair only). Everything else was surface the deleted REST service / CLI loaded from LAAS_SIGNING_* env vars, kept alive by the 7f34ff6 library-only refactor: - SigstoreOIDCBackend and all of it: _resolve_identity_token, _interactive_token (interactive OAuth2 — a CLI concern a batch writer must never fall into), custom-infrastructure ca_url/tlog_url with _build_signing_context's f-string JSON trust config, oidc_* config, allow_interactive. The keypair analogue of an unconfigured verifier was already a WARNING; identity verification was never wired in core, so no layer carries an identity signature. - stamp_and_sign: exported as 'the single entry point every write path should use', but core hand-rolls the sequence in _sign with the CORRECT staleness check (layer.merkle_root_signature is not None) — stamp_and_sign's root-moved check fabricated a drop alarm on every never-signed re-write, presumably why core bypassed it. - rekor opt-in on sign: contradicted the policy of record (config/external-tools.yaml: transparency_log forbidden); the never-publish flags and the post-condition discard are now unconditional. - can_verify (added this branch) and the identity-dispatch WARNING: superseded — there is no identity dispatch left to misclassify. - Dead references: the format-1 warning pointed at sign_merkle_root.py, deleted in 7f34ff6; SignAttempt.warning() docstring's 'six call sites' (one caller remains). signing.py 719 → 418 lines. Suite: 278 passed (34 sigstore/rekor tests removed with the surface). Core's import list is untouched by this diff.
PR Summary by QodoHarden Merkle verification and simplify offline keypair signing
AI Description
Diagram
High-Level Assessment
Files changed (12)
|
Code Review by Qodo 1. Historical identity signatures cannot verify
|
Code Review by Qodo
1. Historical identity signatures stop verifying
|
…ots (XWING-2203) Review follow-ups on PR traust-security#153 (Qodo + manual review, session at 98d1b97): - the merkle_epoch guard caught TypeError/ValueError but not the OverflowError int(float('inf')) raises — a layer with an infinite epoch crashed verify_merkle_integrity instead of returning a finding - the leaf_format coercion had the same OverflowError gap (same defect class, unflagged) — now covered - the format-1 root guard caught ValueError but not the TypeError bytes.fromhex raises on a non-string root (merkle_root=5) — now a finding - AGENTS.md and a SignResult comment still advertised the removed Sigstore identity backend Red on 98d1b97: test_non_numeric_epoch_is_error[inf], test_unknown_leaf_format_is_error[inf], test_verify_merkle_signature_non_hex_root_is_finding_not_crash[5].
Review round 1 — dispositions (commit
|
| Qodo finding | verdict | what was done |
|---|---|---|
3d70d784 non-string root crash |
accepted, fixed | bytes.fromhex(5) raises TypeError, which the ValueError-only guard missed → guard catches (TypeError, ValueError); test parametrized with 5 (red: TypeError at ledger.py:444) |
749a687c infinite epoch crash |
accepted, fixed | int(float("inf")) raises OverflowError → epoch guard catches it; the leaf_format coercion had the same unflagged gap and got the same fix (both red first) |
96801ed3 AGENTS.md lists removed backend |
accepted, fixed | AGENTS.md + a stale (OIDC) comment no longer advertise identity signing |
2bcc5d78 historical identity signatures error |
open by design, parked on data | No code can verify an identity signature post-removal, so a caller-side policy is moot; the question is only whether such rows exist. Core has only ever wired keypair. Needed: SELECT merkle_signing_method, count(*) FROM traust_ledger.layers GROUP BY 1 on production. Only-keypair → dismiss + drop identity from traust-contracts (XWING-2363). Rows exist → migration, not a softer verdict: a WARNING under required=True would open a method-flip laundering path. |
History: 32f9146 (identity-dispatch WARNING) is superseded by the dead-code removal and kept for its red-test proof rather than squashed.
|
I'm okay with us removing the oidc for now it was never used but I wish it was :) |
…d signing surface removed (XWING-2440) Pins the ledger branch carrying traust-security/traust-ledger#153: three proven fixes (non-integer merkle_epoch crash, non-hex root crash on the format-1 path, identity dispatch misclassification) and the removal of the service-era signing surface core never wires (SigstoreOIDCBackend, stamp_and_sign, rekor opt-in, custom-URL config). Cross-check: full suite green against the new pin (8,442 passed) — core's 11-name import surface is untouched by the removal.
Proven bugs (red → green)
ledger.pyverify_merkle_integrityint(meta.get("merkle_epoch", 0))— a tampered layer with a non-integer epoch raised TypeError/ValueError out of the tamper-evidence reader instead of returning an IntegrityFinding (leaf_format30 lines below already did the defensive coercion)test_non_numeric_epoch_is_error[None|x]— ValueError at ledger.py:329ledger.pyverify_merkle_signaturetest_..._identity_without_policy_reports_unconfigured_not_failedledger.pymerkle_root_payloadmerkle_signature_formatis absent) fed untrustedmerkle_rootintobytes.fromhex→ ValueError escaped the verifier uncaught instead of an IntegrityFindingtest_..._non_hex_root_is_finding_not_crash— ValueError at ledger.py:478Review round 1 (Qodo @ 98d1b97 + end-to-end review) — commit
6a5a3eeEvery guard widened below was red on
98d1b97before the fix (bug-sniff discipline: proof before code); Qodo's re-review of6a5a3eeauto-attributed all three asimplemented.3d70d784: non-string root still crashesbytes.fromhex(5)raises TypeError, not ValueError — the format-1 guard caught only ValueError, somerkle_root=5escaped the verifiertest_..._non_hex_root_is_finding_not_crash[5]— TypeError at ledger.py:444(TypeError, ValueError); test parametrized with5749a687c: infinite epoch still crashesint(float("inf"))raises OverflowError — neither the epoch guard nor theleaf_formatcoercion caught it (the leaf-format gap was the same class, unflagged by both reviews)test_non_numeric_epoch_is_error[inf]andtest_unknown_leaf_format_is_error[inf]— OverflowErrorOverflowError96801ed3: AGENTS.md listed the removed backend(OIDC)SignResult comment updatedOpen — Qodo
2bcc5d78(action_required), parked on a data check, deliberately not softened:Removing the identity backend makes a layer marked
merkle_signing_method='identity'get ERROR "not a recognized signing method", which blocks append/seal on such rows in core. Justification for keeping the ERROR: post-removal, no code exists that can verify an identity signature — a caller "with the identity policy" has nothing to consume it — so the only question is whether historical identity rows exist. Core has only ever builtSigningConfig(method="keypair")(itsLedgerService._signing), and the 2026-08-17 incident's signed layers were keypair, so the expectation is zero rows — but that is data, not code, so it stays open. Action: runSELECT merkle_signing_method, count(*) FROM traust_ledger.layers GROUP BY 1on production. If onlykeypair→ dismiss this finding and dropidentityfrom traust-contracts' signing-method enum (follow-up, XWING-2363). If identity rows exist → restore verification for them via a migration; a WARNING verdict is rejected because underrequired=Trueit would open a method-flip laundering path (flip the method, skip signature verification, get re-signed).History note: commit
32f9146(the identity-dispatch WARNING fix) is superseded by the dead-code removal — kept rather than squashed so each red-test proof stays reviewable; Qodo re-reviews each new head regardless.Dead code removed (service-era surface core never wires)
traust-core — the only consumer — imports 11 names; everything else was loaded from the deleted service's
LAAS_SIGNING_*env config and kept alive by the library-only refactor (7f34ff6):SigstoreOIDCBackend+oidc_*/allow_interactive/ca_url/tlog_url/ interactive OAuth2SigningConfig(method="keypair")only); custom-URL trust config was env-service config, built by f-string JSON interpolationstamp_and_sign_signwith the correct staleness check (merkle_root_signature is not None) —stamp_and_sign's root-moved check fabricated a drop alarm on never-signed re-writesrekoropt-in onsignsign_merkle_root.py(deleted in7f34ff6);SignAttempt.warning()docstring's "six call sites" (one caller remains)signing.py719 → 418 lines. Kept: the RFC 6962 proof surface and restatements module — audit-facing primitives, flagged in status.md for a product call.Needs your verification (data behaviour)
2bcc5d78)SELECT merkle_signing_method, count(*) FROM traust_ledger.layers GROUP BY 1on production; see the justification aboveCoverage
63/63 units (100%) judged across
src/traust_ledger/integrityin one wave: 60 clean, 3 proven → fixed here. Ledger: progress-trackertracking/bug-campaign/traust-ledger.