Skip to content

Verifier does not reject malformed structured-field input at step 1 (wrong error code; covered-component smuggling) #976

Description

@KonstantinMirin

Found while vendoring the complete AdCP 3.1.1 request-signing vector set (#975). Reproductions are the newly vendored vectors, currently strict-xfail in tests/conformance/signing/test_verifier_vectors.py.

Problem

verify_request_signature has no step-1 strict-parse stage. Structured-field input that is malformed or ambiguous is not rejected at parse time; it flows on to a later checklist step and is rejected under a different error code — or, worse, is silently resolved to one interpretation.

Conformance requires byte-for-byte match on expected_outcome.error_code, so rejecting with the wrong code is a graded failure, not a near-miss. More importantly, each of these is a parser-differential surface: signer and verifier can disagree on what was actually signed.

Failing vectors

vector expected actual step reached
negative/021-duplicate-signature-input-label.json request_signature_header_malformed request_signature_components_incomplete 6
negative/022-multi-valued-content-type.json request_signature_header_malformed request_signature_invalid 10
negative/023-multi-valued-content-digest.json request_signature_header_malformed request_signature_invalid 10

021 — duplicate Signature-Input dictionary key

Signature-Input declares label sig1 twice. RFC 8941 §3.2 permits an implementation to reject or retain the last value; the AdCP profile requires rejection at step 1 as downgrade protection.

This is the one with teeth. The SDK silently resolves the duplicate and then rejects for an unrelated reason — request_signature_components_incomplete: required covered component missing: @authority — which means it took the second sig1, the one covering fewer components. That is precisely the smuggling scenario the spec's rejection rule exists to prevent: a proxy appends a weaker sig1 and a verifier that reads the last value grades a different component set than the signer committed to. The vector's own $comment calls this out.

022 — multi-valued Content-Type

Content-Type: application/json, text/plain on a field covered by the signature. Content-Type is not a List-Structured-Field, so concatenation leaves the covered value undefined relative to the signature base. The SDK derives a base from the raw value and fails at crypto verify instead of rejecting at parse.

023 — multi-valued Content-Digest

Two sha-256 members in one Content-Digest. Per RFC 9530 §2 this is a Dictionary field and duplicate algorithms are ambiguous for signature coverage. Same failure mode as 022.

Suggested direction

A strict-parse stage ahead of the checklist that rejects with request_signature_header_malformed on: duplicate keys in the Signature-Input / Signature dictionaries, and multiple field values (or duplicate structured-field members) for any covered non-list field.

Note this changes the error code returned for inputs that are currently rejected as request_signature_invalid / request_signature_components_incomplete, which is a visible behavior change for consumers keying on the code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions