[GH-ISSUE #2554] fix(browser): report omitted IndexedDB values as incomplete fingerprints #872

Closed
opened 2026-10-15 18:29:11 +00:00 by bsakaguchi · 1 comment
Owner

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

Summary

When storage fingerprint capture receives an IndexedDB entry whose value remote object is absent, it emits a synthetic value digest and reports complete coverage. The failure was observed at the storage capture boundary with storage keys and fingerprints enabled. These flags permit downstream content comparison to treat unobserved data as comparable. The trigger is a partial or malformed protocol reply; no real-browser occurrence was established.

Steps to reproduce

On the source revision under Runtime or environment with dependencies installed, add a Vitest test under src/browser using the following fixture. It invokes the production storage capture boundary with both required selections enabled and substitutes only protocol replies.

import { expect, it } from "vitest";
import type { CdpConnection } from "./CdpConnection.js";
import { captureStorage } from "./CdpCaptureStorage.js";
import { inspectWebPageInputSchema } from "../domain/browserObservation.js";

it("keeps an omitted IndexedDB value unknown", async () => {
  const replies: Record<string, unknown> = {
    "Storage.getUsageAndQuota": { usage: 0, quota: 1000 },
    "DOMStorage.getDOMStorageItems": { entries: [] },
    "IndexedDB.requestDatabaseNames": { databaseNames: ["db"] },
    "CacheStorage.requestCacheNames": { caches: [] },
    "Network.getCookies": { cookies: [] },
    "IndexedDB.requestDatabase": {
      databaseWithObjectStores: {
        name: "db",
        objectStores: [{
          name: "records",
          keyPath: { type: "string", string: "id" },
          autoIncrement: false,
          indexes: [],
        }],
      },
    },
    "IndexedDB.requestData": {
      objectStoreDataEntries: [{
        key: { type: "number", value: 1 },
        primaryKey: { type: "number", value: 1 },
      }],
      hasMore: false,
    },
  };
  const connection = {
    async send(method: string): Promise<unknown> {
      if (!(method in replies)) throw new Error(method);
      return replies[method];
    },
  } as unknown as CdpConnection;
  const input = inspectWebPageInputSchema.parse({
    cdp_endpoint: "http://127.0.0.1:9222",
    allowed_origins: ["https://example.test"],
    target_id: "page-1",
    include_storage_keys: true,
    include_storage_fingerprints: true,
  });
  const { value: storage } = await captureStorage(
    { connection, sessionId: undefined, input },
    "https://example.test/app", [],
  );
  const record = storage.content_fingerprints.find(
    item => item.scope === "indexed_db_record",
  );
  expect.soft(record?.value_sha256).toBeNull();
  expect.soft(record?.complete).toBe(false);
  expect.soft(storage.fingerprints_complete).toBe(false);
});

Run the exact test with npm run test:focused -- <repo-relative-test-path>. In the verifier's equivalent fixture, all three assertions failed. The supplied-value controls used the same entry with value: { type: "number", value: 1 } and with value: { type: "undefined" }; both remained complete and produced distinct observed digests.

Expected behavior

A reply that identifies a record but omits its value must preserve that missing observation. The record must not claim complete value coverage, and the aggregate storage fingerprint coverage must be incomplete. If the record fingerprint remains in the output, its unavailable value digest should be null. An explicitly supplied remote object representing a JavaScript undefined value is distinct from an omitted remote object.

Actual behavior

The omitted-value entry returned value_sha256: "cf939b398b75f0a65d78fbc06aaf35308fa67b9dd3dd0de3a961c4796f1be0e6", complete: true, and aggregate fingerprints_complete: true. That digest equals SHA-256 of the canonical JSON string "undefined", including the quotation marks. The focused run exited 1 with one failed test and two passing controls. This was a simulated protocol replay against the production storage boundary, not a live Chrome or full CLI/MCP reproduction.

Evidence

  • Expected source: docs/browser-observation.md:140 requires complete storage capture before content comparison; docs/browser-observation.md:179 makes partial IndexedDB remote objects incomplete. AGENTS.md:19 and AGENTS.md:29 require preservation of omissions and unknown boundary facts. src/browser/CdpCaptureStorageFingerprints.ts:416 represents an unavailable digest with null.
  • Failure source: The boundary replay returned the values above. src/browser/CdpCaptureStorageFingerprints.ts:282 normalizes absent entry fields before fingerprinting; src/browser/CdpCaptureStorageFingerprints.ts:445 converts an absent value into a string; src/browser/CdpCaptureStorageFingerprints.ts:475 accepts the absent remote object as complete. src/browser/CdpCaptureStorage.ts:65 invokes fingerprint capture when selected and exposes its aggregate completeness.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Public inspection reaches storage capture after target/origin admission. The request selected both required storage flags. The transport validates the response envelope but accepts its result record without parsing IndexedDB entry fields. The storage capture checks database/cache inventory shape and entry array shape; no guard requires the entry's value. Final page identity guards can still pass for a stable authorized page. This reachability was checked from source; the executed probe started at storage capture. Downstream comparison trusts the recorded completeness flags in src/domain/webCaptureDiff.ts:654 and selects fingerprints marked complete at line 722. A false comparison result was not separately executed.

The historical storage comparison issue requires that missing or truncated evidence never establish equality. It is a closed feature report by another author, with no comments, delivered by merged PR 433. It is reference context, not a duplicate report of this omitted-value failure. Read-only open and closed issue searches found no matching existing report.

Restoration check

Repeat the omitted-value fixture. The capture must expose unknown value coverage through incomplete record and aggregate flags, with a null value digest if it retains the record. Keep the numeric remote-object control complete with a non-null digest. Keep the explicit { type: "undefined" } control distinguishable from omission, so restoration preserves legitimate stored undefined values. Verify comparison remains unknown for otherwise identical captures whose required record value was never observed.

Runtime or environment

Failure verified at a2d8b94dd5 and still present at 8b181716ac, package version 6.4.0, Node v26.10.0, npm 11.19.1, Vitest 5.0.3. The replay used simulated CDP replies on macOS; no browser process was required.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A partial or malformed IndexedDB reply produces a definite content digest and complete coverage for a value never delivered. This compromises the bounded storage evidence/comparison result. A real-browser occurrence, severe harm, or failure frequency was not established.
  • Workaround status: unverified
  • Workaround basis: No practical workaround preserving content comparison for the affected reply was demonstrated. Supplying a complete numeric or explicit-undefined remote object passes the controls, but this does not establish that affected users can change their protocol peer's replies.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2554 ## Summary When storage fingerprint capture receives an IndexedDB entry whose value remote object is absent, it emits a synthetic value digest and reports complete coverage. The failure was observed at the storage capture boundary with storage keys and fingerprints enabled. These flags permit downstream content comparison to treat unobserved data as comparable. The trigger is a partial or malformed protocol reply; no real-browser occurrence was established. ## Steps to reproduce On the source revision under Runtime or environment with dependencies installed, add a Vitest test under src/browser using the following fixture. It invokes the production storage capture boundary with both required selections enabled and substitutes only protocol replies. ```ts import { expect, it } from "vitest"; import type { CdpConnection } from "./CdpConnection.js"; import { captureStorage } from "./CdpCaptureStorage.js"; import { inspectWebPageInputSchema } from "../domain/browserObservation.js"; it("keeps an omitted IndexedDB value unknown", async () => { const replies: Record<string, unknown> = { "Storage.getUsageAndQuota": { usage: 0, quota: 1000 }, "DOMStorage.getDOMStorageItems": { entries: [] }, "IndexedDB.requestDatabaseNames": { databaseNames: ["db"] }, "CacheStorage.requestCacheNames": { caches: [] }, "Network.getCookies": { cookies: [] }, "IndexedDB.requestDatabase": { databaseWithObjectStores: { name: "db", objectStores: [{ name: "records", keyPath: { type: "string", string: "id" }, autoIncrement: false, indexes: [], }], }, }, "IndexedDB.requestData": { objectStoreDataEntries: [{ key: { type: "number", value: 1 }, primaryKey: { type: "number", value: 1 }, }], hasMore: false, }, }; const connection = { async send(method: string): Promise<unknown> { if (!(method in replies)) throw new Error(method); return replies[method]; }, } as unknown as CdpConnection; const input = inspectWebPageInputSchema.parse({ cdp_endpoint: "http://127.0.0.1:9222", allowed_origins: ["https://example.test"], target_id: "page-1", include_storage_keys: true, include_storage_fingerprints: true, }); const { value: storage } = await captureStorage( { connection, sessionId: undefined, input }, "https://example.test/app", [], ); const record = storage.content_fingerprints.find( item => item.scope === "indexed_db_record", ); expect.soft(record?.value_sha256).toBeNull(); expect.soft(record?.complete).toBe(false); expect.soft(storage.fingerprints_complete).toBe(false); }); ``` Run the exact test with `npm run test:focused -- <repo-relative-test-path>`. In the verifier's equivalent fixture, all three assertions failed. The supplied-value controls used the same entry with `value: { type: "number", value: 1 }` and with `value: { type: "undefined" }`; both remained complete and produced distinct observed digests. ## Expected behavior A reply that identifies a record but omits its value must preserve that missing observation. The record must not claim complete value coverage, and the aggregate storage fingerprint coverage must be incomplete. If the record fingerprint remains in the output, its unavailable value digest should be null. An explicitly supplied remote object representing a JavaScript undefined value is distinct from an omitted remote object. ## Actual behavior The omitted-value entry returned `value_sha256: "cf939b398b75f0a65d78fbc06aaf35308fa67b9dd3dd0de3a961c4796f1be0e6"`, `complete: true`, and aggregate `fingerprints_complete: true`. That digest equals SHA-256 of the canonical JSON string `"undefined"`, including the quotation marks. The focused run exited 1 with one failed test and two passing controls. This was a simulated protocol replay against the production storage boundary, not a live Chrome or full CLI/MCP reproduction. ## Evidence - Expected source: `docs/browser-observation.md:140` requires complete storage capture before content comparison; `docs/browser-observation.md:179` makes partial IndexedDB remote objects incomplete. `AGENTS.md:19` and `AGENTS.md:29` require preservation of omissions and unknown boundary facts. `src/browser/CdpCaptureStorageFingerprints.ts:416` represents an unavailable digest with null. - Failure source: The boundary replay returned the values above. `src/browser/CdpCaptureStorageFingerprints.ts:282` normalizes absent entry fields before fingerprinting; `src/browser/CdpCaptureStorageFingerprints.ts:445` converts an absent value into a string; `src/browser/CdpCaptureStorageFingerprints.ts:475` accepts the absent remote object as complete. `src/browser/CdpCaptureStorage.ts:65` invokes fingerprint capture when selected and exposes its aggregate completeness. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Public inspection reaches storage capture after target/origin admission. The request selected both required storage flags. The transport validates the response envelope but accepts its result record without parsing IndexedDB entry fields. The storage capture checks database/cache inventory shape and entry array shape; no guard requires the entry's value. Final page identity guards can still pass for a stable authorized page. This reachability was checked from source; the executed probe started at storage capture. Downstream comparison trusts the recorded completeness flags in `src/domain/webCaptureDiff.ts:654` and selects fingerprints marked complete at line 722. A false comparison result was not separately executed. The historical [storage comparison issue](https://github.com/morluto/rea/issues/377) requires that missing or truncated evidence never establish equality. It is a closed feature report by another author, with no comments, delivered by merged PR 433. It is reference context, not a duplicate report of this omitted-value failure. Read-only open and closed issue searches found no matching existing report. ## Restoration check Repeat the omitted-value fixture. The capture must expose unknown value coverage through incomplete record and aggregate flags, with a null value digest if it retains the record. Keep the numeric remote-object control complete with a non-null digest. Keep the explicit `{ type: "undefined" }` control distinguishable from omission, so restoration preserves legitimate stored undefined values. Verify comparison remains unknown for otherwise identical captures whose required record value was never observed. ## Runtime or environment Failure verified at a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 and still present at 8b181716aca2cb467be499cba226abfb5ae19141, package version 6.4.0, Node v26.10.0, npm 11.19.1, Vitest 5.0.3. The replay used simulated CDP replies on macOS; no browser process was required. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A partial or malformed IndexedDB reply produces a definite content digest and complete coverage for a value never delivered. This compromises the bounded storage evidence/comparison result. A real-browser occurrence, severe harm, or failure frequency was not established. - Workaround status: unverified - Workaround basis: No practical workaround preserving content comparison for the affected reply was demonstrated. Supplying a complete numeric or explicit-undefined remote object passes the controls, but this does not establish that affected users can change their protocol peer's replies.
bsakaguchi 2026-10-15 18:29:11 +00:00
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Addressed on main in bffdd91053fb5d076b762cf1b325e8dcb5ab2d8c.

An omitted IndexedDB RemoteObject value yields an unknown digest and incomplete fingerprint; explicit undefined remains distinguishable.

Validation: Affected suites passed in the merged 65-file run (1169 passed, 2 explicit skips), with full static/architecture checks and documentation checks passing. Final installed-package verification is recorded separately in the audit.

Verification scope: Controlled source and production-boundary verification on macOS with pinned Node 24.18.0; real external engines and Windows hosts were not independently rerun. Closure covers the described source defect, not broader provider conformance or performance.

<!-- gh-comment-id:6159207556 --> @morluto commented on GitHub (Oct 14, 2026): Addressed on main in [bffdd91053fb5d076b762cf1b325e8dcb5ab2d8c](https://github.com/morluto/rea/commit/bffdd91053fb5d076b762cf1b325e8dcb5ab2d8c). An omitted IndexedDB RemoteObject value yields an unknown digest and incomplete fingerprint; explicit undefined remains distinguishable. Validation: Affected suites passed in the merged 65-file run (1169 passed, 2 explicit skips), with full static/architecture checks and documentation checks passing. Final installed-package verification is recorded separately in the audit. Verification scope: Controlled source and production-boundary verification on macOS with pinned Node 24.18.0; real external engines and Windows hosts were not independently rerun. Closure covers the described source defect, not broader provider conformance or performance.
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#872
No description provided.