Skip to content

verify_result_bundle raises for a wrong-typed subject, the last field that does not return a verdict #4077

Description

@chernistry

Reserved for @timsurrealedu. GitHub drops an assignment to an account that is not yet a repo collaborator, silently, so this line is the assignment.

verify_result_bundle reads the DSSE statement's subject without settling its type. subject is a sibling of predicate, one layer above the fields #4055, #4070 and #4076 now guard — the layer those three deliberately did not widen into.

Reproduction

Measured with #4076 applied, envelope validly signed, only subject's shape varied:

subject = 7             -> TypeError
subject = true          -> TypeError
subject = 1.5           -> TypeError
subject = {"k": "v"}    -> KeyError
subject = {"a": {...}}  -> KeyError

Two distinct failure modes, which is the tell that more than one read is unguarded: the scalars fail where the value is indexed or iterated, the mappings fail on a key that is assumed present.

The same 117-probe sweep returns a verdict for every other shape — predicate, bundle, worker, patch, gates, gates[i], gates[i].log, chain, against 13 wrong types each, 0 raises. This is the only field left.

Scope

Settle subject's type before reading it, in the shape the four guards above it already use, and keep the two failure modes distinct in the verdict: a subject that is not a list is one error; a list holding an element that is not a mapping, or a mapping missing digest, is another and should name the index the way gates[i] does.

The existing subject.digest.sha256 error must keep its current meaning for a well-shaped subject. Check it before changing anything — a guard that swallows the real digest mismatch would trade a traceback for a false pass, which is worse than what is there now.

Proof

  • A fuzz rather than N hand-written cases: mutate subject, re-sign with the same key, assert no raise. test_absent_bundle_keeps_three_error_behavior has the re-signing recipe, and fix(security): refuse wrong-typed predicate in verify_result_bundle (#4072) #4076's tests have the pattern for pinning an error list. Seven hand-written cases prove seven cases; the claim is "never raises".
  • The absent-subject case keeps whatever shape it has today, pinned in a test. Absent and present-but-wrong-typed are different answers.
  • The 50 tests in tests/unit/security/test_result_receipt_bundle.py stay green.

After this, verify_result_bundle returns a verdict for every input shape rather than for most of them, and the sentence in the module docstring can say so without a qualifier. Worth putting that line in the PR — it is what the four changes were for.

Out of scope

  • The envelope layer around the statement (payload_type, signatures). If the same shape is there, name it in the PR and open one follow-up.
  • Anything below the predicate, which is done.

The decision left to whoever takes this

Whether a wrong-typed subject still lets the digest comparison run against an empty subject list — producing both subject and subject.digest.sha256 — or replaces it. #4076 chose to accumulate one layer down and gave the reason; the same reasoning may or may not apply here, since the second error would be about a subject that was never really examined. Say which you picked and why, and pin the resulting list.

Agent brief

Repo: sipyourdrink-ltd/bernstein. Read
src/bernstein/core/security/result_receipt_bundle.py -- verify_result_bundle,
specifically the predicate/bundle/worker/patch/gates guards. Match that shape.

Defect: the DSSE statement's `subject` is read without its type being settled.
Scalars raise TypeError; a mapping raises KeyError. Two failure modes, so more
than one read is unguarded -- find both.

Write a fuzz test first (mutate, re-sign, assert no raise), not N hand-written
cases. Keep the existing subject.digest.sha256 error meaningful for a
well-shaped subject: a guard that swallows a real digest mismatch is worse than
the traceback it replaces.

Verify: uv run pytest tests/unit/security/test_result_receipt_bundle.py -q
(all green); uv run ruff check . && uv run ruff format --check .; uv run mypy src

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai-welcomeSuitable for AI agent contributorsbugSomething isn't workingsecuritySecurity hardeningsize/sup-for-grabsListed on up-for-grabs.net — no commitment, low friction

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions