Found by adversarial review of #30 and reproduced locally — this is verified, not a report.
normalize() in vinci/containment-broker/src/canonical.mjs:11 maps a Buffer to
{"$bytes_base64": "..."} but never escapes that key when it appears in an ordinary object. A raw
buffer and a plain object carrying the same key therefore canonicalize to identical bytes.
$ node -e '...canonicalBytes({payload: Buffer.from([0xde,0xad,0xbe,0xef])}) vs {payload:{"$bytes_base64":"3q2+7w=="}}'
buffer form canonical: {"payload":{"$bytes_base64":"3q2+7w=="}}
object form canonical: {"payload":{"$bytes_base64":"3q2+7w=="}}
sha buffer: 1806a1cd17601d0d0b1b17e95a861dba2481d6f307e3d617932651eb6b073260
sha object: 1806a1cd17601d0d0b1b17e95a861dba2481d6f307e3d617932651eb6b073260
COLLISION CONFIRMED
Why it matters. canonicalBytes feeds sha256, which feeds body_sha256 and the HMAC in
authenticateReceipt/verifyReceipt. Two structurally different payloads produce the same
authenticated bytes, so a receipt authenticated over one substitutes for the other and verifies
cleanly. That is a signature-substitution property in the receipt layer, and receipts are the
evidence the authority decisions are built on.
Suggested fix. Make the byte encoding unforgeable from ordinary data — e.g. reserve and reject
the $bytes_base64 key on plain objects, or tag values by type rather than by a magic key.
Not a regression from #30; this is in the v3 boundary. Filed separately rather than folded into that
PR because it is a distinct correctness property with its own test surface.
Found by adversarial review of #30 and reproduced locally — this is verified, not a report.
normalize()invinci/containment-broker/src/canonical.mjs:11maps aBufferto{"$bytes_base64": "..."}but never escapes that key when it appears in an ordinary object. A rawbuffer and a plain object carrying the same key therefore canonicalize to identical bytes.
Why it matters.
canonicalBytesfeedssha256, which feedsbody_sha256and the HMAC inauthenticateReceipt/verifyReceipt. Two structurally different payloads produce the sameauthenticated bytes, so a receipt authenticated over one substitutes for the other and verifies
cleanly. That is a signature-substitution property in the receipt layer, and receipts are the
evidence the authority decisions are built on.
Suggested fix. Make the byte encoding unforgeable from ordinary data — e.g. reserve and reject
the
$bytes_base64key on plain objects, or tag values by type rather than by a magic key.Not a regression from #30; this is in the v3 boundary. Filed separately rather than folded into that
PR because it is a distinct correctness property with its own test surface.