Skip to content

Fix proof-flow evidence logical_id collision on degenerate corpora (CVE-2026-4600) - #132

Draft
jorge-garcia-le wants to merge 1 commit into
mainfrom
fix/proof-hunt-eval-patches
Draft

Fix proof-flow evidence logical_id collision on degenerate corpora (CVE-2026-4600)#132
jorge-garcia-le wants to merge 1 commit into
mainfrom
fix/proof-hunt-eval-patches

Conversation

@jorge-garcia-le

Copy link
Copy Markdown
Contributor

Problem

Running the proof flow against CVE-2026-4600 (jsrsasign) aborts with:

ERROR: Evidence evidencel-2c2c1cb37effa428 already exists; append a successor revision instead

Root cause

Every VersionedRecord derives its logical_id by hashing an identity payload. For Evidence (models.py) that payload was only:

{ "kind", "artifact_digest", "observations", "provenance" }

The mechanical obligation resolver (resolvers.py::_resolution) builds evidence from the matching facts, with a constant provenance (producer="mechanical-obligation-resolver", no command / context packet) and observations keyed on fact.id. So when two different obligations (from two different candidates) resolve off the same fact via the same deterministic rule, all four identity fields are byte-identical → identical logical_id.

The append-only ProofGraph._add treats a repeat logical_id as "you must be revising" and raises. That raise is swallowed on the falsifier path (engine.py:580) but not on the resolver path — apply_resolution at engine.py:856 is unguarded, so the ValueError propagates out of arun() and kills the whole run.

Why it only surfaced now

Prior proof-flow targets were C (jq, etc.): multi-file trees where facts live at distinct ids/locations, so distinct obligations produce distinct evidence. jsrsasign's entire source is effectively one minified line (jsrsasign-all-min.js), so ~200 candidates converge on the same handful of sentinel_use facts — the first corpus degenerate enough for two mechanical resolutions to be literally identical evidence.

Confirmed from the run dir (results/cve-2026-4600/sh-cbad7381/): the colliding evidence's observations all cite fact-d09dfc9df70fa9ae at jsrsasign-all-min.js:225, kind static_sentinel_definition, constant mechanical provenance. Two obligations (obligationl-f69a6adac865f0c2 and a sibling) claim it.

Fix

  1. Widen Evidence.identity_payload to include supports / contradicts. Evidence supporting different claims/obligations is genuinely different logical evidence, so it earns a distinct logical_id. (Claim identity already carries the candidate subject + obligation scope, so the supported claim ids differ per obligation.)
  2. Make apply_resolution idempotent as a defensive net: if an evidence/claim logical_id is already in the graph, reuse it instead of re-adding — a genuine duplicate can never again abort a whole run.

Tests

Two synchronous regression tests in test_sourcehunt_proof_investigation.py reproducing the jsrsasign scenario (two candidates, same shared fact):

  • test_evidence_identity_distinguishes_supported_claims — same kind/observations, distinct supported claims ⇒ distinct evidence logical_id.
  • test_apply_resolution_tolerates_duplicate_evidence — re-applying an identical resolution no longer raises.

Verification

  • New tests pass; ruff clean on all changed files.
  • The 4 affected proof test files show an identical failure set with and without this change (13 pre-existing failures + my 2 new passes). The pre-existing failures are @pytest.mark.asyncio tests skipped because pytest-asyncio isn't active in this venv — environmental, unrelated to this change.

Branch naming

Named fix/proof-hunt-eval-patches (not CVE-specific) intentionally — a landing spot for further proof-flow eval fixes surfaced by other CVEs.

The mechanical obligation resolver builds Evidence whose identity payload
was only {kind, artifact_digest, observations, provenance}. When many
candidates converge on the same handful of facts — e.g. a single minified
JS bundle like jsrsasign (CVE-2026-4600) — two distinct obligations resolve
off the identical sentinel_use fact via the same deterministic rule, so
their evidence hashes to the same logical_id. The append-only ProofGraph
rejects the second add with 'Evidence ... already exists; append a
successor revision instead', and because apply_resolution's add is
unguarded (engine.py:856) the ValueError aborts the entire run.

C targets never tripped this: distinct facts across a multi-file tree yield
distinct observation sets. A degenerate single-file corpus is the trigger.

Fix:
- Widen Evidence.identity_payload to include supports/contradicts. Evidence
  for different claims/obligations is genuinely different logical evidence,
  so it gets distinct logical_ids. (Claim identity already carries the
  candidate subject + obligation scope, so supports/contradicts differ.)
- Make apply_resolution idempotent as a defensive net: skip re-adding an
  evidence/claim whose logical_id is already in the graph, so a genuine
  duplicate reuses the stored record instead of aborting the run.

Adds two regression tests reproducing the jsrsasign scenario.
@jorge-garcia-le
jorge-garcia-le force-pushed the fix/proof-hunt-eval-patches branch from bc27621 to 3b1b6c5 Compare August 6, 2026 22:36
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