Skip to content

containment-broker: Buffer and plain-object payloads canonicalize identically, so a receipt substitutes for a different payload #34

Description

@thegeorgepu

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions