[GH-ISSUE #6834] [Bug] Duplicate protobuf field numbers silently select the last schema declaration #4179

Closed
opened 2026-10-31 00:47:28 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6834

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

REA accepts a FileDescriptorSet that declares first = 1 and second = 1 in one message. For the same raw bytes 08 01, it labels the field second; reversing only the declaration order labels it first. Both report schema_status: "decoded" and complete body coverage without an ambiguity diagnostic.

A source-boundary HAR reproduction through prepareCaptureProtobuf and decodeCaptureProtobuf also reports protobuf_coverage: {status: "complete", selected_records: 1, inspected_records: 1} for both conflicting descriptors. The final report passes webNetworkCaptureSchema validation. This is observed source execution, not a public CLI/MCP reproduction.

Reproduction

From the repository root with locked dependencies installed, save this executed reduced script outside the checkout as /tmp/rea-protobuf-repro.mjs, then run node --import tsx /tmp/rea-protobuf-repro.mjs. It creates only in-memory descriptors and inert protobuf bytes; it does not execute an application or contact any destination.

import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
const repo = process.cwd(), require = createRequire(repo + '/package.json');
const { create, toBinary } = require('@bufbuild/protobuf');
const { FileDescriptorSetSchema } = require('@bufbuild/protobuf/wkt');
const source = p => import(pathToFileURL(repo + '/src/browser/history/protobuf/' + p + '.ts'));
const { ProtobufSchema } = await source('ProtobufSchema');
const { ProtobufBudget } = await source('ProtobufWire');
const { decodeProtobufBody } = await source('ProtobufBody');
for (const [label, names, numbers] of [
  ['unique-control', ['first', 'second'], [1, 2]],
  ['duplicate-first-second', ['first', 'second'], [1, 1]],
  ['duplicate-second-first', ['second', 'first'], [1, 1]],
]) {
  const descriptor = toBinary(FileDescriptorSetSchema, create(FileDescriptorSetSchema, {
    file: [{ name: 's.proto', package: 's', syntax: 'proto3', messageType: [{
      name: 'Message', field: names.map((name, i) => ({ name, number: numbers[i], type: 5, label: 1 })),
    }] }],
  }));
  const budget = new ProtobufBudget();
  const schema = await ProtobufSchema.read(descriptor, 's.Message', budget, x => x);
  const decoded = await decodeProtobufBody(Buffer.from([8, 1]), 'raw', {
    pointer: '/body', content_type: 'application/x-protobuf', body_sha256: 'a'.repeat(64),
  }, x => x, budget, schema);
  const { schema_name, schema_status, schema_value } = decoded.fields[0];
  console.log(JSON.stringify({ label, status: decoded.status, schema_name, schema_status, schema_value }));
}

One execution of this three-case reduced probe and one execution of the corresponding three-case HAR decoding probe produced the same order-dependent labels. The unique-number control selects first.

Expected behavior

Reject a descriptor containing two declarations for one field number, or retain the schema interpretation as explicitly ambiguous. Do not silently choose a name and report successful schema decoding.

The Protobuf field-number rules require unique numbers within a message. REA documents explicit schema disagreements alongside unchanged wire facts. Repeated occurrences of a field in the wire message remain valid and are a separate concept.

Impact and scope

An invalid or incorrectly assembled descriptor can silently attach the wrong field name to retained protocol evidence. The declaration order decides the name, while both field-level status and aggregate protobuf coverage indicate successful decoding. Raw bytes and their numeric value remain intact.

The verified scope is synthetic proto3 descriptors and one two-byte raw message in a synthetic HAR. This does not affect descriptors whose field numbers are unique. Public CLI/MCP, published-package execution, other platforms, proto2, and editions were not tested for this case. No incident with a real capture is claimed.

REA version

rea-agents 6.7.0; source main 9066444f6b8e9ffe1607a9b02cde3895a37db431. The coordinating investigation checked npm latest as 6.7.0; this worker executed source modules.

Runtime and platform

Node.js v26.12.1; Linux 7.2.8-9-omarchy x64; locked @bufbuild/protobuf 2.16.0 and tsx 4.23.15. The repository's Node pin is 24.22.1; that pinned runtime was not separately exercised.

Target and artifact context

Synthetic FileDescriptorSets defining s.Message / test.Message; two optional int32 fields with duplicate number 1. Harmless raw protobuf body 08 01; integration fixture uses HAR 1.2 and application/x-protobuf base64 response content. No real devices or application targets.

Interface

Other

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Evidence provenance: source review and direct execution of production protobuf interpretation functions. Local verification: reproduced. Reproduction completeness: complete for the source-boundary fixtures; public CLI/MCP untested. Both saved probe stderr streams are empty.

Actual reduced-probe stdout:

{"label":"unique-control","status":"complete","schema_name":"first","schema_status":"decoded","schema_value":"1"}
{"label":"duplicate-first-second","status":"complete","schema_name":"second","schema_status":"decoded","schema_value":"1"}
{"label":"duplicate-second-first","status":"complete","schema_name":"first","schema_status":"decoded","schema_value":"1"}

The separate HAR-boundary probe reports coverage.status: "complete", body_status: "complete", and schema_status: "decoded" in all three cases, with the same first / second / first labels. It reads each actual descriptor file through the production artifact reader, completes decoding, validates the final capture report, and closes the resource scope.

Typed outcome or recovery expected

Return an invalid_request identifying the conflicting descriptor/message/field number before schema annotation, while retaining the existing safe source-evidence behavior. An explicit unsupported or ambiguous schema status would also preserve the uncertainty; a successfully decoded single name does not.

Analysis or suspected cause

Source-derived: ProtobufSchema.read checks wire syntax and constructs a registry without checking field-number uniqueness. annotate constructs a Map keyed by field number; the later declaration overwrites the earlier one. Both int32 declarations agree with the wire type, so aggregate coverage sees no schema failure.

Additional context

The supplied open issue index and all-state GitHub searches for protobuf, descriptor duplicate, and protobuf field number found no matching report. Related #6118 concerns generic diagnostics for descriptors that are rejected; this case accepts an ambiguous descriptor and assigns a successful interpretation.

Prepared with Codex from synthetic source probes. Verification finished before drafting.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Executed source-boundary controls show order-dependent field names from duplicate schema declarations, with successful field status and complete aggregate coverage. The defect is bounded to invalid descriptors and does not change original capture bytes.
  • Workaround status: available
  • Workaround basis: The executed unique-number control removes the conflicting declaration for field 1 and consistently decodes that field as first. Correct the supplied descriptor to contain one authoritative declaration per number before interpreting the capture.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6834 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Observed behavior REA accepts a FileDescriptorSet that declares `first = 1` and `second = 1` in one message. For the same raw bytes `08 01`, it labels the field `second`; reversing only the declaration order labels it `first`. Both report `schema_status: "decoded"` and complete body coverage without an ambiguity diagnostic. A source-boundary HAR reproduction through `prepareCaptureProtobuf` and `decodeCaptureProtobuf` also reports `protobuf_coverage: {status: "complete", selected_records: 1, inspected_records: 1}` for both conflicting descriptors. The final report passes `webNetworkCaptureSchema` validation. This is observed source execution, not a public CLI/MCP reproduction. ## Reproduction From the repository root with locked dependencies installed, save this executed reduced script outside the checkout as `/tmp/rea-protobuf-repro.mjs`, then run `node --import tsx /tmp/rea-protobuf-repro.mjs`. It creates only in-memory descriptors and inert protobuf bytes; it does not execute an application or contact any destination. ```javascript import { createRequire } from 'node:module'; import { pathToFileURL } from 'node:url'; const repo = process.cwd(), require = createRequire(repo + '/package.json'); const { create, toBinary } = require('@bufbuild/protobuf'); const { FileDescriptorSetSchema } = require('@bufbuild/protobuf/wkt'); const source = p => import(pathToFileURL(repo + '/src/browser/history/protobuf/' + p + '.ts')); const { ProtobufSchema } = await source('ProtobufSchema'); const { ProtobufBudget } = await source('ProtobufWire'); const { decodeProtobufBody } = await source('ProtobufBody'); for (const [label, names, numbers] of [ ['unique-control', ['first', 'second'], [1, 2]], ['duplicate-first-second', ['first', 'second'], [1, 1]], ['duplicate-second-first', ['second', 'first'], [1, 1]], ]) { const descriptor = toBinary(FileDescriptorSetSchema, create(FileDescriptorSetSchema, { file: [{ name: 's.proto', package: 's', syntax: 'proto3', messageType: [{ name: 'Message', field: names.map((name, i) => ({ name, number: numbers[i], type: 5, label: 1 })), }] }], })); const budget = new ProtobufBudget(); const schema = await ProtobufSchema.read(descriptor, 's.Message', budget, x => x); const decoded = await decodeProtobufBody(Buffer.from([8, 1]), 'raw', { pointer: '/body', content_type: 'application/x-protobuf', body_sha256: 'a'.repeat(64), }, x => x, budget, schema); const { schema_name, schema_status, schema_value } = decoded.fields[0]; console.log(JSON.stringify({ label, status: decoded.status, schema_name, schema_status, schema_value })); } ``` One execution of this three-case reduced probe and one execution of the corresponding three-case HAR decoding probe produced the same order-dependent labels. The unique-number control selects `first`. ## Expected behavior Reject a descriptor containing two declarations for one field number, or retain the schema interpretation as explicitly ambiguous. Do not silently choose a name and report successful schema decoding. The [Protobuf field-number rules](https://protobuf.dev/programming-guides/proto3/#assigning-field-numbers) require unique numbers within a message. REA documents [explicit schema disagreements alongside unchanged wire facts](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/docs/web-network-captures.md#L153-L162). Repeated occurrences of a field in the wire message remain valid and are a separate concept. ## Impact and scope An invalid or incorrectly assembled descriptor can silently attach the wrong field name to retained protocol evidence. The declaration order decides the name, while both field-level status and aggregate protobuf coverage indicate successful decoding. Raw bytes and their numeric value remain intact. The verified scope is synthetic proto3 descriptors and one two-byte raw message in a synthetic HAR. This does not affect descriptors whose field numbers are unique. Public CLI/MCP, published-package execution, other platforms, proto2, and editions were not tested for this case. No incident with a real capture is claimed. ## REA version `rea-agents` 6.7.0; source main `9066444f6b8e9ffe1607a9b02cde3895a37db431`. The coordinating investigation checked npm latest as 6.7.0; this worker executed source modules. ## Runtime and platform Node.js v26.12.1; Linux 7.2.8-9-omarchy x64; locked `@bufbuild/protobuf` 2.16.0 and `tsx` 4.23.15. The repository's Node pin is 24.22.1; that pinned runtime was not separately exercised. ## Target and artifact context Synthetic FileDescriptorSets defining `s.Message` / `test.Message`; two optional int32 fields with duplicate number 1. Harmless raw protobuf body `08 01`; integration fixture uses HAR 1.2 and `application/x-protobuf` base64 response content. No real devices or application targets. ## Interface Other ## Earliest failure stage Request parsing or validation ## Relevant output or evidence Evidence provenance: source review and direct execution of production protobuf interpretation functions. Local verification: reproduced. Reproduction completeness: complete for the source-boundary fixtures; public CLI/MCP untested. Both saved probe stderr streams are empty. Actual reduced-probe stdout: ```jsonl {"label":"unique-control","status":"complete","schema_name":"first","schema_status":"decoded","schema_value":"1"} {"label":"duplicate-first-second","status":"complete","schema_name":"second","schema_status":"decoded","schema_value":"1"} {"label":"duplicate-second-first","status":"complete","schema_name":"first","schema_status":"decoded","schema_value":"1"} ``` The separate HAR-boundary probe reports `coverage.status: "complete"`, `body_status: "complete"`, and `schema_status: "decoded"` in all three cases, with the same `first` / `second` / `first` labels. It reads each actual descriptor file through the production artifact reader, completes decoding, validates the final capture report, and closes the resource scope. ## Typed outcome or recovery expected Return an `invalid_request` identifying the conflicting descriptor/message/field number before schema annotation, while retaining the existing safe source-evidence behavior. An explicit unsupported or ambiguous schema status would also preserve the uncertainty; a successfully decoded single name does not. ## Analysis or suspected cause Source-derived: [`ProtobufSchema.read`](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/browser/history/protobuf/ProtobufSchema.ts#L135-L162) checks wire syntax and constructs a registry without checking field-number uniqueness. [`annotate`](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/browser/history/protobuf/ProtobufSchema.ts#L178-L198) constructs a Map keyed by field number; the later declaration overwrites the earlier one. Both int32 declarations agree with the wire type, so [aggregate coverage](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/browser/history/protobuf/ProtobufCapture.ts#L511-L530) sees no schema failure. ## Additional context The supplied open issue index and all-state GitHub searches for `protobuf`, `descriptor duplicate`, and `protobuf field number` found no matching report. Related #6118 concerns generic diagnostics for descriptors that are rejected; this case accepts an ambiguous descriptor and assigns a successful interpretation. Prepared with Codex from synthetic source probes. Verification finished before drafting. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Executed source-boundary controls show order-dependent field names from duplicate schema declarations, with successful field status and complete aggregate coverage. The defect is bounded to invalid descriptors and does not change original capture bytes. - Workaround status: available - Workaround basis: The executed unique-number control removes the conflicting declaration for field 1 and consistently decodes that field as first. Correct the supplied descriptor to contain one authoritative declaration per number before interpreting the capture.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4179
No description provided.