fix(browser): require storage fingerprint approval

This commit is contained in:
morluto
2026-07-24 14:13:57 +00:00
parent 7b1e3bda00
commit f0069cbee4
11 changed files with 26 additions and 5 deletions
+5 -4
View File
@@ -92,11 +92,11 @@ rea inspect-web-page http://127.0.0.1:9222 TARGET_ID \
--include-json-body-shapes --json-body-schema-approved \
--include-websocket-shapes --websocket-shape-approved \
--include-script-sources \
--include-storage-keys \
--include-storage-keys --include-storage-fingerprints \
--json
```
Accessibility text and script content may contain application secrets and become part of the returned Evidence. Accessibility and console text have independent per-field and aggregate UTF-8 byte limits; credential-shaped console substrings are redacted. JSON and WebSocket captures retain paths, types, counts, and truncation only, never values or examples. Storage values remain redacted even when key-name capture is approved; SHA-256 identity/value fingerprints support comparison only when cookies, DOM storage, IndexedDB, and Cache Storage were all captured completely.
Accessibility text and script content may contain application secrets and become part of the returned Evidence. Accessibility and console text have independent per-field and aggregate UTF-8 byte limits; credential-shaped console substrings are redacted. JSON and WebSocket captures retain paths, types, counts, and truncation only, never values or examples. Storage values remain redacted even when key-name capture is approved. `--include-storage-fingerprints` separately approves SHA-256 identity/value fingerprints, which support comparison only when cookies, DOM storage, IndexedDB, and Cache Storage were all captured completely.
Screenshot pixels require both `--approved` and `--screenshot-approved`:
@@ -122,7 +122,8 @@ rea capture-web-screenshot http://127.0.0.1:9222 TARGET_ID \
"include_websocket_shapes": false,
"websocket_shape_approved": false,
"include_script_sources": false,
"include_storage_keys": false
"include_storage_keys": false,
"include_storage_fingerprints": false
}
```
@@ -141,7 +142,7 @@ REA removes sensitive values before normalized event data is retained:
- Request/response bodies are not requested or parsed by default. With independent approval, only allowed-origin JSON media types are read within per-body and aggregate budgets, converted immediately to value-free property paths/types, and discarded. `Network.getResponseBody` is never sent without that approval.
- Console observations with an approved stack source retain call type, argument types, timestamp, and redacted source location. With independent approval, only already-delivered primitive values are retained after credential redaction and byte bounds; objects, getters, and remote properties are never expanded.
- WebSocket observations retain direction, opcode, and payload byte length. With independent approval, bounded text frames are classified as text or value-free JSON shape; binary bytes, hashes, prefixes, and raw frames are never retained.
- Storage observations always redact values. Key names, IndexedDB names, cache names, and stable content fingerprints are omitted unless explicitly requested. Cache bodies above 64 KiB and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` approval. Cache bodies above 64 KiB and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
- Script metadata is included only when CDP supplies a URL on an allowed origin. Stable keys exclude transient CDP script IDs, and exact transient raw URLs are used only during script/resource reconciliation. URL-less scripts are excluded because their origin cannot be established. When CDP supplies an execution-context association, the accepted script retains its authorized frame ID for later attribution. Source content is omitted unless explicitly requested and becomes a self-verifying `rea://web-content/sha256/...` artifact subject to per-script and aggregate byte limits.
Cross-origin frames, resources, scripts, events, and workers are excluded unless their exact origins are also approved. Excluded target details are counted without being exposed.
+1
View File
@@ -91,6 +91,7 @@ try {
target_id: target,
observation_ms: 1_000,
include_storage_keys: true,
include_storage_fingerprints: true,
}),
);
if (!observed.ok) throw observed.error;
+1
View File
@@ -164,6 +164,7 @@ const browserParameters = (
include_websocket_shapes: input.include_websocket_shapes,
include_script_sources: input.include_script_sources,
include_storage_keys: input.include_storage_keys,
include_storage_fingerprints: input.include_storage_fingerprints,
limits: input.limits,
...(input.include_script_sources && "source_capture_approved" in input
? {
+1 -1
View File
@@ -71,7 +71,7 @@ export const captureStorage = async (
: [{ name: name.slice(0, 1_024), id } satisfies CapturedCache];
});
const maximum = context.input.limits.max_storage_keys;
const fingerprints = context.input.include_storage_keys
const fingerprints = context.input.include_storage_fingerprints
? await captureStorageFingerprints({
context,
origin,
+1
View File
@@ -134,6 +134,7 @@ const registerPageInspection = (
websocket_shape_approved: options.websocketShapeApproved,
include_script_sources: options.includeScriptSources,
include_storage_keys: options.includeStorageKeys,
include_storage_fingerprints: options.includeStorageFingerprints,
limits: {
max_frames: options.maxFrames,
max_dom_nodes: options.maxDomNodes,
+4
View File
@@ -74,6 +74,10 @@ export const browserPageInspectionOptions = z.object({
.boolean()
.default(false)
.describe("Include storage key names without values"),
includeStorageFingerprints: z
.boolean()
.default(false)
.describe("Include content-derived storage fingerprints"),
maxFrames: boundedCount("page frames", 1_000, 200),
maxDomNodes: boundedCount("DOM nodes", 10_000, 2_000),
maxAxNodes: boundedCount("accessibility nodes", 10_000, 2_000),
+1
View File
@@ -164,6 +164,7 @@ export const BROWSER_TOOL_CONTRACTS = [
websocket_shape_approved: false,
include_script_sources: false,
include_storage_keys: false,
include_storage_fingerprints: false,
limits: {
max_frames: 200,
max_dom_nodes: 2_000,
+7
View File
@@ -222,11 +222,18 @@ export const inspectWebPageInputSchema = z
websocket_shape_approved: z.boolean().default(false),
include_script_sources: z.boolean().default(false),
include_storage_keys: z.boolean().default(false),
include_storage_fingerprints: z.boolean().default(false),
limits: browserInspectionLimitsSchema.default(
DEFAULT_BROWSER_INSPECTION_LIMITS,
),
})
.superRefine((input, context) => {
if (input.include_storage_fingerprints && !input.include_storage_keys)
context.addIssue({
code: "custom",
path: ["include_storage_fingerprints"],
message: "Storage fingerprints require storage key capture",
});
if (
input.limits.max_script_source_bytes >
input.limits.max_total_script_source_bytes
+1
View File
@@ -167,6 +167,7 @@ describe("browser observation contracts", () => {
{ include_console_text: true },
{ include_json_body_shapes: true },
{ include_websocket_shapes: true },
{ include_storage_fingerprints: true },
])
expect(
inspectWebPageInputSchema.safeParse({ ...base, ...input }).success,
+2
View File
@@ -257,6 +257,7 @@ describe("CdpBrowserProvider", () => {
approved: true,
observation_ms: 0,
include_storage_keys: true,
include_storage_fingerprints: true,
}),
);
@@ -1262,6 +1263,7 @@ describe("CdpBrowserProvider", () => {
expect(result.value.storage.session_storage_keys).toHaveLength(1);
expect(result.value.storage.indexed_db_names).toHaveLength(1);
expect(result.value.storage.cache_names).toHaveLength(1);
expect(result.value.storage.content_fingerprints).toEqual([]);
});
it("degrades optional domains but propagates protocol and payload failures", async () => {
+2
View File
@@ -138,6 +138,7 @@ describe("web capture diff", () => {
observation_ms: 0,
include_accessibility_text: true,
include_storage_keys: true,
include_storage_fingerprints: true,
}),
);
if (!captured.ok) throw captured.error;
@@ -211,6 +212,7 @@ describe("web capture diff", () => {
observation_ms: 0,
include_accessibility_text: true,
include_storage_keys: true,
include_storage_fingerprints: true,
}),
);
if (!captured.ok) throw captured.error;