diff --git a/extensions/ai-providers/cursor/protobuf.ts b/extensions/ai-providers/cursor/protobuf.ts index 7c04ae79..956907b9 100644 --- a/extensions/ai-providers/cursor/protobuf.ts +++ b/extensions/ai-providers/cursor/protobuf.ts @@ -447,13 +447,12 @@ function compileMapField(desc: MapFieldDesc): CompiledField { decode(message, reader, wireType, _fieldNumber) { assertWireType(wireType, 2); const target = mapField(message, name); - const limit = reader.uint32(); - const end = reader.pos + limit; + const entryReader = new Reader(reader.bytes()); let entryKey = ""; let entryValue: unknown = valCodec.defaultValue; - while (reader.pos < end) { - const tag = reader.uint32(); + while (entryReader.pos < entryReader.len) { + const tag = entryReader.uint32(); const entryNumber = tag >>> 3; const entryWireType = tag & 7; if (!isWireType(entryWireType)) { @@ -463,12 +462,12 @@ function compileMapField(desc: MapFieldDesc): CompiledField { } if (entryNumber === 1) { assertWireType(entryWireType, key.wireType); - entryKey = requireString(key.decode(reader)); + entryKey = requireString(key.decode(entryReader)); } else if (entryNumber === 2) { assertWireType(entryWireType, valCodec.wireType); - entryValue = valCodec.decode(reader); + entryValue = valCodec.decode(entryReader); } else { - reader.skip(entryWireType); + entryReader.skip(entryWireType); } } diff --git a/tests/extensions/ai-providers/cursor-protobuf.test.ts b/tests/extensions/ai-providers/cursor-protobuf.test.ts new file mode 100644 index 00000000..87f8a178 --- /dev/null +++ b/tests/extensions/ai-providers/cursor-protobuf.test.ts @@ -0,0 +1,28 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + pb, + type ProtoMessage, +} from "../../../extensions/ai-providers/cursor/protobuf.ts"; + +interface StringMapMessage extends ProtoMessage { + values: Record; +} + +const StringMap = pb("test.StringMap", [ + { kind: "map", no: 1, name: "values", K: "string", V: "string" }, +]); + +test("protobuf maps do not decode beyond a declared entry boundary", () => { + const valid = Uint8Array.from([ + 0x0a, 0x0a, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x03, 0x66, 0x6f, 0x6f, + ]); + const malformed = Uint8Array.from(valid); + malformed[1] = 9; + + assert.equal(StringMap.decode(valid).values.url, "foo"); + assert.throws( + () => StringMap.decode(malformed), + /Unexpected EOF reading bytes/, + ); +});