diff --git a/packages/protocol/src/crypto/receive-envelope.ts b/packages/protocol/src/crypto/receive-envelope.ts index c40dad8..f875db8 100644 --- a/packages/protocol/src/crypto/receive-envelope.ts +++ b/packages/protocol/src/crypto/receive-envelope.ts @@ -75,17 +75,17 @@ export async function receiveEnvelope( selfId: string, deps: ReceiveEnvelopeDeps, ): Promise { - // Step 0: known wire version + // Step 1 (§4.3): pre-decode wire size cap — before any JSON.parse + if (utf8ToBytes(wire).length > MAX_ENVELOPE_WIRE_BYTES) { + return { ok: false, error: "envelope_too_large" }; + } + + // §4.1 wire version gate (between §4.3 steps 1 and 2) const outerVersion = parseOuterVersion(wire); if (outerVersion !== null && outerVersion !== 1) { return { ok: false, error: "unsupported_version" }; } - // Step 1: pre-decode wire size cap - if (utf8ToBytes(wire).length > MAX_ENVELOPE_WIRE_BYTES) { - return { ok: false, error: "envelope_too_large" }; - } - // Step 2: strict-decode outer + parse body JSON let outer: OuterEnvelope; try { diff --git a/packages/protocol/src/receive-envelope.test.ts b/packages/protocol/src/receive-envelope.test.ts index 38bf5f4..d368fc3 100644 --- a/packages/protocol/src/receive-envelope.test.ts +++ b/packages/protocol/src/receive-envelope.test.ts @@ -110,8 +110,8 @@ function tamperBody(wire: string, patch: (body: EnvelopeBody) => void): string { return serializeOuterEnvelope(tampered); } -describe("receiveEnvelope steps 0–6 (§4.3)", () => { - it("step 0: unknown wire version → unsupported_version, dispatch not called", async () => { +describe("receiveEnvelope steps 1–6 (§4.3)", () => { + it("§4.1 wire version: unknown version at valid size → unsupported_version, dispatch not called", async () => { const { wire, bob, bobId } = makeValidWire(); const deps = makeDeps(bob); const badWire = tamperOuter(wire, (outer) => { @@ -137,6 +137,24 @@ describe("receiveEnvelope steps 0–6 (§4.3)", () => { expect(deps.seqStore.commitAccepted).not.toHaveBeenCalled(); }); + it("step 1: oversized wire with v:2 → envelope_too_large (not unsupported_version)", async () => { + const { wire, bob, bobId } = makeValidWire(); + const deps = makeDeps(bob); + const badWire = padWireToSize( + tamperOuter(wire, (outer) => { + outer.v = 2; + }), + MAX_ENVELOPE_WIRE_BYTES + 1, + ); + expect(wireUtf8Length(badWire)).toBeGreaterThan(MAX_ENVELOPE_WIRE_BYTES); + + const result = await receiveEnvelope(badWire, bobId, deps); + + expect(result).toEqual({ ok: false, error: "envelope_too_large" }); + expect(deps.dispatch).not.toHaveBeenCalled(); + expect(deps.seqStore.commitAccepted).not.toHaveBeenCalled(); + }); + it("step 1: wire === 65536 bytes passes size check", async () => { const { wire, bob, bobId } = makeValidWire(); const deps = makeDeps(bob); @@ -286,7 +304,7 @@ describe("receiveEnvelope steps 0–6 (§4.3)", () => { expect(deps.seqStore.commitAccepted).not.toHaveBeenCalled(); }); - it("valid wire passing steps 0–6 continues to step 7+ (not rejected early)", async () => { + it("valid wire passing steps 1–6 continues to step 7+ (not rejected early)", async () => { const { wire, bob, bobId } = makeValidWire(); const deps = makeDeps(bob);