rea-agents 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +55 -12
  2. package/bridge/hopper_bridge.py +12 -3
  3. package/dist/application/AnalysisSnapshotCache.js +8 -6
  4. package/dist/application/ArtifactGraphConstruction.js +12 -3
  5. package/dist/application/ArtifactInventory.js +100 -27
  6. package/dist/application/AuthorizedArtifactInventory.js +4 -3
  7. package/dist/application/BinarySession.js +92 -4
  8. package/dist/application/BoundedJsonFiles.js +6 -29
  9. package/dist/application/BrowserEvidence.js +35 -0
  10. package/dist/application/BrowserObservationPort.js +1 -0
  11. package/dist/application/BrowserObservationService.js +52 -0
  12. package/dist/application/CapabilityInventory.js +134 -0
  13. package/dist/application/ClientRegistrationStatus.js +69 -0
  14. package/dist/application/ConfiguredRoots.js +19 -0
  15. package/dist/application/CrossVersionInventory.js +19 -8
  16. package/dist/application/CrossVersionInvestigation.js +74 -5
  17. package/dist/application/DeferredFileAuthorization.js +61 -0
  18. package/dist/application/DirectAnalysis.js +117 -9
  19. package/dist/application/Doctor.js +74 -2
  20. package/dist/application/EvidenceLedger.js +6 -5
  21. package/dist/application/InvestigationWorkspaceStore.js +2 -26
  22. package/dist/application/PermissionAuthority.js +183 -0
  23. package/dist/application/PermissionConfiguration.js +26 -0
  24. package/dist/application/ProcessCaptureAuthority.js +3 -2
  25. package/dist/application/ProcessCaptureError.js +13 -0
  26. package/dist/application/ProcessCaptureLifecycle.js +21 -9
  27. package/dist/application/ProcessCheckpoints.js +1 -1
  28. package/dist/application/ProcessCli.js +40 -15
  29. package/dist/application/ProcessHarness.js +10 -20
  30. package/dist/application/ProcessNormalization.js +17 -2
  31. package/dist/application/ProcessOwnership.js +4 -3
  32. package/dist/application/ProcessSampling.js +52 -17
  33. package/dist/application/ProgressReporter.js +30 -0
  34. package/dist/application/ProjectPermissionStore.js +213 -0
  35. package/dist/application/ReferenceSourceImportPolicy.js +3 -2
  36. package/dist/application/Setup.js +2 -41
  37. package/dist/application/SupportedClients.js +41 -0
  38. package/dist/application/TerminalRenderer.js +1 -1
  39. package/dist/application/Uninstall.js +1 -1
  40. package/dist/application/Upgrade.js +2 -0
  41. package/dist/application/runtime.js +1 -1
  42. package/dist/artifacts/ArtifactProvider.js +13 -3
  43. package/dist/artifacts/MachOSliceArtifactReader.js +1 -1
  44. package/dist/artifacts/NativeDmgArtifactReader.js +1 -1
  45. package/dist/artifacts/SafeOutputTree.js +3 -3
  46. package/dist/browser/CdpBrowserProvider.js +158 -0
  47. package/dist/browser/CdpCaptureDocuments.js +140 -0
  48. package/dist/browser/CdpCaptureEvents.js +248 -0
  49. package/dist/browser/CdpCaptureStorage.js +53 -0
  50. package/dist/browser/CdpCaptureValues.js +70 -0
  51. package/dist/browser/CdpConnection.js +218 -0
  52. package/dist/browser/CdpEndpoint.js +114 -0
  53. package/dist/browser/CdpOptionalCommand.js +15 -0
  54. package/dist/browser/CdpPageCapture.js +273 -0
  55. package/dist/catalogIdentity.js +123 -0
  56. package/dist/cli.js +48 -3
  57. package/dist/cliBrowserCommands.js +136 -0
  58. package/dist/cliEvidenceCommands.js +42 -1
  59. package/dist/cliInvestigationCommands.js +42 -6
  60. package/dist/cliLogging.js +20 -3
  61. package/dist/cliPolicyCommands.js +118 -0
  62. package/dist/cliProcessCommands.js +4 -14
  63. package/dist/config.js +107 -3
  64. package/dist/contracts/artifactToolContracts.js +14 -1
  65. package/dist/contracts/browserToolContracts.js +80 -0
  66. package/dist/contracts/errorSchemas.js +100 -0
  67. package/dist/contracts/toolContracts.js +13 -6
  68. package/dist/contracts/toolOutputSchemas.js +62 -30
  69. package/dist/domain/artifactComparison.js +38 -11
  70. package/dist/domain/artifactGraph.js +25 -1
  71. package/dist/domain/artifactInventoryEvidence.js +18 -0
  72. package/dist/domain/browserObservation.js +361 -0
  73. package/dist/domain/changedBehavior.js +9 -18
  74. package/dist/domain/errors.js +257 -3
  75. package/dist/domain/investigationWorkspace.js +131 -4
  76. package/dist/domain/jsonLimits.js +26 -0
  77. package/dist/domain/permissionPolicy.js +161 -0
  78. package/dist/domain/processCapture.js +3 -1
  79. package/dist/domain/processComparison.js +23 -34
  80. package/dist/domain/reconstructionVerification.js +3 -16
  81. package/dist/domain/reconstructionVerificationSchemas.js +3 -0
  82. package/dist/domain/staticRuntimeCorrelation.js +7 -1
  83. package/dist/domain/symbolAnalysis.js +1 -1
  84. package/dist/generatedPackageMetadata.js +9 -0
  85. package/dist/hopper/BridgeLauncher.js +7 -3
  86. package/dist/hopper/HopperClient.js +32 -5
  87. package/dist/identity.js +10 -3
  88. package/dist/logger.js +2 -2
  89. package/dist/main.js +101 -7
  90. package/dist/native/CommandRunner.js +11 -4
  91. package/dist/server/createServer.js +59 -5
  92. package/dist/server/mcpProgress.js +23 -0
  93. package/dist/server/registerArtifactComparisonTool.js +6 -2
  94. package/dist/server/registerBrowserTools.js +36 -0
  95. package/dist/server/registerBundleComparisonTool.js +6 -2
  96. package/dist/server/registerEnhancedTools.js +18 -1
  97. package/dist/server/registerEvidenceResources.js +302 -0
  98. package/dist/server/registerEvidenceTools.js +58 -1
  99. package/dist/server/registerFunctionComparisonTool.js +6 -2
  100. package/dist/server/registerInvestigationTools.js +78 -10
  101. package/dist/server/registerOfficialTools.js +19 -1
  102. package/dist/server/registerProcessComparisonTool.js +9 -3
  103. package/dist/server/registerSessionStatusTool.js +44 -3
  104. package/dist/server/registerSessionTools.js +144 -9
  105. package/dist/server/runDerivedOperation.js +43 -0
  106. package/dist/server/toolResult.js +27 -3
  107. package/dist/serverIdentity.js +70 -0
  108. package/package.json +7 -4
  109. package/scripts/rea.mjs +1 -1
  110. package/skills/rea-analysis/SKILL.md +18 -3
@@ -0,0 +1,140 @@
1
+ import { allowedSanitizedUrl, boundedText, numberValue, recordValue, recordsValue, requiredRecord, stringValue, } from "./CdpCaptureValues.js";
2
+ /** Normalize allowed frames while retaining a bounded prefix and total count. */
3
+ export const captureFrames = (result, allowedOrigins, maximum) => {
4
+ const root = recordValue(requiredRecord(result).frameTree);
5
+ if (root === undefined)
6
+ return { total: 0, items: [] };
7
+ const items = [];
8
+ let total = 0;
9
+ for (const tree of walkFrameTrees(root)) {
10
+ const frame = recordValue(tree.frame);
11
+ const frameId = stringValue(frame?.id);
12
+ const sanitized = allowedSanitizedUrl(frame?.url, allowedOrigins);
13
+ if (frame === undefined ||
14
+ frameId === undefined ||
15
+ frameId.length > 256 ||
16
+ sanitized === undefined ||
17
+ sanitized.origin === null)
18
+ continue;
19
+ total += 1;
20
+ if (items.length >= maximum)
21
+ continue;
22
+ items.push({
23
+ frame_id: frameId,
24
+ parent_frame_id: (stringValue(frame.parentId) ?? "").slice(0, 256) || null,
25
+ url: sanitized.url,
26
+ origin: sanitized.origin,
27
+ });
28
+ }
29
+ return { total, items };
30
+ };
31
+ /** Read the current main-frame URL from an untrusted Page.getFrameTree result. */
32
+ export const mainFrameUrl = (result) => stringValue(recordValue(recordValue(requiredRecord(result).frameTree)?.frame)?.url);
33
+ /** Normalize allowed resources while retaining a bounded prefix and total count. */
34
+ export const captureResources = (result, allowedOrigins, maximum) => {
35
+ const root = recordValue(requiredRecord(result).frameTree);
36
+ if (root === undefined)
37
+ return { total: 0, items: [] };
38
+ const items = [];
39
+ let total = 0;
40
+ for (const tree of walkFrameTrees(root)) {
41
+ for (const resource of recordsValue(tree.resources)) {
42
+ const url = allowedSanitizedUrl(resource.url, allowedOrigins);
43
+ if (url === undefined || url.origin === null)
44
+ continue;
45
+ total += 1;
46
+ if (items.length >= maximum)
47
+ continue;
48
+ const contentSize = numberValue(resource.contentSize);
49
+ items.push({
50
+ url: url.url,
51
+ origin: url.origin,
52
+ type: (stringValue(resource.type) ?? "Other").slice(0, 100),
53
+ mime_type: (stringValue(resource.mimeType) ?? "").slice(0, 256),
54
+ content_size: contentSize === undefined ? null : Math.max(0, contentSize),
55
+ });
56
+ }
57
+ }
58
+ return { total, items };
59
+ };
60
+ const walkFrameTrees = function* (root) {
61
+ const pending = [root];
62
+ while (pending.length > 0) {
63
+ const tree = pending.pop();
64
+ if (tree === undefined)
65
+ return;
66
+ const children = recordsValue(tree.childFrames);
67
+ for (let index = children.length - 1; index >= 0; index -= 1) {
68
+ const child = children[index];
69
+ if (child !== undefined)
70
+ pending.push(child);
71
+ }
72
+ yield tree;
73
+ }
74
+ };
75
+ export const captureDom = (result, allowedOrigins, input) => {
76
+ const root = requiredRecord(result);
77
+ const strings = Array.isArray(root.strings)
78
+ ? root.strings.map((value) => stringValue(value) ?? "")
79
+ : [];
80
+ const nodes = [];
81
+ let total = 0;
82
+ for (const document of recordsValue(root.documents)) {
83
+ const documentUrl = indexedString(strings, document.documentURL);
84
+ if (allowedSanitizedUrl(documentUrl, allowedOrigins) === undefined)
85
+ continue;
86
+ const documentNodes = recordValue(document.nodes);
87
+ if (documentNodes === undefined)
88
+ continue;
89
+ const nodeTypes = numberArray(documentNodes.nodeType);
90
+ const nodeNames = numberArray(documentNodes.nodeName);
91
+ const nodeValues = numberArray(documentNodes.nodeValue);
92
+ const parents = numberArray(documentNodes.parentIndex);
93
+ const attributes = Array.isArray(documentNodes.attributes)
94
+ ? documentNodes.attributes
95
+ : [];
96
+ const baseIndex = nodes.length;
97
+ total += nodeTypes.length;
98
+ for (let index = 0; index < nodeTypes.length && nodes.length < input.limits.max_dom_nodes; index += 1) {
99
+ const attributeIndexes = numberArray(attributes[index]);
100
+ const parent = Math.trunc(parents[index] ?? -1);
101
+ nodes.push({
102
+ index: nodes.length,
103
+ parent_index: parent < 0 ? -1 : baseIndex + parent,
104
+ node_type: Math.max(0, Math.trunc(nodeTypes[index] ?? 0)),
105
+ node_name: indexedString(strings, nodeNames[index]).slice(0, 256),
106
+ node_value_length: indexedString(strings, nodeValues[index]).length,
107
+ attribute_names: attributeIndexes
108
+ .filter((_value, attributeIndex) => attributeIndex % 2 === 0)
109
+ .map((value) => indexedString(strings, value).slice(0, 256))
110
+ .slice(0, 200),
111
+ });
112
+ }
113
+ }
114
+ return { total, nodes };
115
+ };
116
+ export const captureAccessibility = (results, maximum) => {
117
+ const all = results.flatMap((result) => recordsValue(requiredRecord(result).nodes));
118
+ return {
119
+ total: all.length,
120
+ nodes: all.slice(0, maximum).map((node) => ({
121
+ node_id: (stringValue(node.nodeId) ?? "").slice(0, 256),
122
+ parent_id: (stringValue(node.parentId) ?? "").slice(0, 256) || null,
123
+ role: axText(node.role),
124
+ name: axText(node.name),
125
+ description: axText(node.description),
126
+ ignored: node.ignored === true,
127
+ })),
128
+ };
129
+ };
130
+ const axText = (value) => boundedText(recordValue(value)?.value, 1_024);
131
+ const indexedString = (strings, index) => {
132
+ const integer = numberValue(index);
133
+ return integer === undefined ? "" : (strings[Math.trunc(integer)] ?? "");
134
+ };
135
+ const numberArray = (value) => Array.isArray(value)
136
+ ? value.flatMap((item) => {
137
+ const number = numberValue(item);
138
+ return number === undefined ? [] : [number];
139
+ })
140
+ : [];
@@ -0,0 +1,248 @@
1
+ import { allowedSanitizedUrl, boundedText, isHttpUrl, numberValue, recordValue, recordsValue, stringValue, } from "./CdpCaptureValues.js";
2
+ /** Bounded event accumulator that drops payload values at ingestion time. */
3
+ export class CdpCaptureEvents {
4
+ input;
5
+ allowedOrigins;
6
+ scripts = new Map();
7
+ network = new Map();
8
+ allowedWebSockets = new Set();
9
+ console = [];
10
+ websockets = [];
11
+ dropped = 0;
12
+ originViolation = false;
13
+ navigationDuringCapture = false;
14
+ #mainFrameId;
15
+ constructor(input, allowedOrigins) {
16
+ this.input = input;
17
+ this.allowedOrigins = allowedOrigins;
18
+ }
19
+ beginAuthorizedFrame(frameId) {
20
+ this.#mainFrameId = frameId;
21
+ this.originViolation = false;
22
+ this.navigationDuringCapture = false;
23
+ this.dropped = 0;
24
+ this.scripts.clear();
25
+ this.network.clear();
26
+ this.allowedWebSockets.clear();
27
+ this.console.length = 0;
28
+ this.websockets.length = 0;
29
+ }
30
+ beginFinalCapture(frameId) {
31
+ this.#mainFrameId = frameId;
32
+ this.navigationDuringCapture = false;
33
+ }
34
+ ingest(event) {
35
+ const params = recordValue(event.params);
36
+ if (params === undefined)
37
+ return;
38
+ switch (event.method) {
39
+ case "Debugger.scriptParsed":
40
+ this.#script(params);
41
+ break;
42
+ case "Network.requestWillBeSent":
43
+ this.#request(params);
44
+ break;
45
+ case "Network.responseReceived":
46
+ this.#response(params);
47
+ break;
48
+ case "Network.loadingFinished":
49
+ this.#finished(params);
50
+ break;
51
+ case "Runtime.consoleAPICalled":
52
+ this.#console(params);
53
+ break;
54
+ case "Page.frameNavigated":
55
+ this.#frameNavigated(params);
56
+ break;
57
+ case "Network.webSocketCreated":
58
+ this.#websocketCreated(params);
59
+ break;
60
+ case "Network.webSocketFrameSent":
61
+ this.#websocket(params, "sent");
62
+ break;
63
+ case "Network.webSocketFrameReceived":
64
+ this.#websocket(params, "received");
65
+ break;
66
+ }
67
+ }
68
+ #script(params) {
69
+ const scriptId = stringValue(params.scriptId);
70
+ const rawUrl = stringValue(params.url) ?? "";
71
+ const sanitized = allowedSanitizedUrl(rawUrl, this.allowedOrigins);
72
+ if (scriptId === undefined ||
73
+ scriptId.length > 256 ||
74
+ sanitized === undefined)
75
+ return;
76
+ if (this.scripts.size >= this.input.limits.max_scripts &&
77
+ !this.scripts.has(scriptId)) {
78
+ this.dropped += 1;
79
+ return;
80
+ }
81
+ this.scripts.set(scriptId, {
82
+ scriptId,
83
+ url: sanitized.url,
84
+ origin: sanitized.origin,
85
+ hash: (stringValue(params.hash) ?? "").slice(0, 512),
86
+ length: Math.max(0, Math.min(Number.MAX_SAFE_INTEGER, Math.trunc(numberValue(params.length) ?? 0))),
87
+ isModule: params.isModule === true,
88
+ language: boundedText(params.scriptLanguage, 100),
89
+ sourceMapUrl: this.#sourceMap(params.sourceMapURL),
90
+ });
91
+ }
92
+ #sourceMap(value) {
93
+ const sanitized = allowedSanitizedUrl(value, this.allowedOrigins);
94
+ return sanitized?.url ?? null;
95
+ }
96
+ #request(params) {
97
+ const requestId = stringValue(params.requestId);
98
+ const request = recordValue(params.request);
99
+ const sanitized = allowedSanitizedUrl(request?.url, this.allowedOrigins);
100
+ if (requestId === undefined || requestId.length > 256)
101
+ return;
102
+ if (request === undefined || sanitized === undefined) {
103
+ this.network.delete(requestId);
104
+ return;
105
+ }
106
+ if (this.network.size >= this.input.limits.max_network_events &&
107
+ !this.network.has(requestId)) {
108
+ this.dropped += 1;
109
+ return;
110
+ }
111
+ const initiator = recordValue(params.initiator);
112
+ const initiatorUrl = allowedSanitizedUrl(firstCallFrame(initiator)?.url, this.allowedOrigins);
113
+ this.network.set(requestId, {
114
+ request_id: requestId,
115
+ url: sanitized.url,
116
+ origin: sanitized.origin ?? "",
117
+ method: (stringValue(request.method) ?? "GET").slice(0, 32),
118
+ resource_type: boundedText(params.type, 100),
119
+ status: null,
120
+ mime_type: null,
121
+ encoded_data_length: null,
122
+ initiator: {
123
+ type: (stringValue(initiator?.type) ?? "other").slice(0, 100),
124
+ url: initiatorUrl?.url ?? null,
125
+ line: integerOrNull(firstCallFrame(initiator)?.lineNumber),
126
+ column: integerOrNull(firstCallFrame(initiator)?.columnNumber),
127
+ },
128
+ });
129
+ }
130
+ #response(params) {
131
+ const requestId = stringValue(params.requestId);
132
+ if (requestId === undefined || requestId.length > 256)
133
+ return;
134
+ const current = this.network.get(requestId);
135
+ const response = recordValue(params.response);
136
+ const sanitized = allowedSanitizedUrl(response?.url, this.allowedOrigins);
137
+ if (current === undefined)
138
+ return;
139
+ if (response === undefined || sanitized === undefined) {
140
+ this.network.delete(requestId);
141
+ return;
142
+ }
143
+ this.network.set(requestId, {
144
+ ...current,
145
+ status: numberValue(response.status) ?? null,
146
+ mime_type: boundedText(response.mimeType, 256),
147
+ });
148
+ }
149
+ #finished(params) {
150
+ const requestId = stringValue(params.requestId);
151
+ if (requestId === undefined || requestId.length > 256)
152
+ return;
153
+ const current = this.network.get(requestId);
154
+ if (current === undefined)
155
+ return;
156
+ this.network.set(requestId, {
157
+ ...current,
158
+ encoded_data_length: Math.max(0, numberValue(params.encodedDataLength) ?? 0),
159
+ });
160
+ }
161
+ #console(params) {
162
+ const frame = firstCallFrame(recordValue(params.stackTrace));
163
+ const source = allowedSanitizedUrl(frame?.url, this.allowedOrigins);
164
+ if (frame === undefined || source === undefined)
165
+ return;
166
+ if (this.console.length >= this.input.limits.max_console_events) {
167
+ this.dropped += 1;
168
+ return;
169
+ }
170
+ this.console.push({
171
+ type: (stringValue(params.type) ?? "unknown").slice(0, 100),
172
+ timestamp: numberValue(params.timestamp) ?? 0,
173
+ argument_types: recordsValue(params.args)
174
+ .map((argument) => (stringValue(argument.type) ?? "unknown").slice(0, 100))
175
+ .slice(0, 100),
176
+ url: source?.url ?? null,
177
+ line: integerOrNull(frame?.lineNumber),
178
+ column: integerOrNull(frame?.columnNumber),
179
+ });
180
+ }
181
+ #websocket(params, direction) {
182
+ const requestId = stringValue(params.requestId);
183
+ if (requestId === undefined ||
184
+ requestId.length > 256 ||
185
+ (!this.network.has(requestId) && !this.allowedWebSockets.has(requestId)))
186
+ return;
187
+ if (this.websockets.length >= this.input.limits.max_websocket_events) {
188
+ this.dropped += 1;
189
+ return;
190
+ }
191
+ const response = recordValue(params.response);
192
+ const payload = stringValue(response?.payloadData) ?? "";
193
+ const opcode = Math.max(0, Math.trunc(numberValue(response?.opcode) ?? 0));
194
+ this.websockets.push({
195
+ request_id: requestId,
196
+ direction,
197
+ opcode,
198
+ payload_bytes: opcode === 1
199
+ ? Buffer.byteLength(payload)
200
+ : Buffer.from(payload, "base64").byteLength,
201
+ });
202
+ }
203
+ #websocketCreated(params) {
204
+ const requestId = stringValue(params.requestId);
205
+ const rawUrl = stringValue(params.url);
206
+ if (requestId === undefined ||
207
+ requestId.length > 256 ||
208
+ rawUrl === undefined)
209
+ return;
210
+ let parsed;
211
+ try {
212
+ parsed = new URL(rawUrl);
213
+ }
214
+ catch {
215
+ return;
216
+ }
217
+ if (parsed.protocol === "ws:")
218
+ parsed.protocol = "http:";
219
+ else if (parsed.protocol === "wss:")
220
+ parsed.protocol = "https:";
221
+ else
222
+ return;
223
+ if (this.allowedOrigins.has(parsed.origin) &&
224
+ (this.allowedWebSockets.has(requestId) ||
225
+ this.allowedWebSockets.size < this.input.limits.max_websocket_events))
226
+ this.allowedWebSockets.add(requestId);
227
+ else if (this.allowedOrigins.has(parsed.origin))
228
+ this.dropped += 1;
229
+ }
230
+ #frameNavigated(params) {
231
+ const frame = recordValue(params.frame);
232
+ if (stringValue(frame?.id) !== this.#mainFrameId)
233
+ return;
234
+ this.navigationDuringCapture = true;
235
+ const rawUrl = stringValue(frame?.url);
236
+ if (isHttpUrl(rawUrl) &&
237
+ allowedSanitizedUrl(rawUrl, this.allowedOrigins) === undefined)
238
+ this.originViolation = true;
239
+ }
240
+ }
241
+ const firstCallFrame = (value) => {
242
+ const callFrames = recordsValue(value?.callFrames);
243
+ return callFrames[0];
244
+ };
245
+ const integerOrNull = (value) => {
246
+ const number = numberValue(value);
247
+ return number === undefined ? null : Math.max(0, Math.trunc(number));
248
+ };
@@ -0,0 +1,53 @@
1
+ import { CdpConnection } from "./CdpConnection.js";
2
+ import { optionalCdpCommand } from "./CdpOptionalCommand.js";
3
+ import { numberValue, recordValue, recordsValue, stringValue, } from "./CdpCaptureValues.js";
4
+ /** Capture redacted, bounded storage metadata for one authorized origin. */
5
+ export const captureStorage = async (context, origin, limitations) => {
6
+ const quota = recordValue(await optionalCdpCommand(context, "Storage.getUsageAndQuota", { origin }, limitations));
7
+ const local = context.input.include_storage_keys
8
+ ? await storageKeys(context, origin, true, limitations)
9
+ : [];
10
+ const session = context.input.include_storage_keys
11
+ ? await storageKeys(context, origin, false, limitations)
12
+ : [];
13
+ const indexed = context.input.include_storage_keys
14
+ ? stringArray(recordValue(await optionalCdpCommand(context, "IndexedDB.requestDatabaseNames", { securityOrigin: origin }, limitations))?.databaseNames)
15
+ : [];
16
+ const caches = context.input.include_storage_keys
17
+ ? recordsValue(recordValue(await optionalCdpCommand(context, "CacheStorage.requestCacheNames", { securityOrigin: origin }, limitations))?.caches).flatMap((cache) => {
18
+ const name = stringValue(cache.cacheName);
19
+ return name === undefined ? [] : [name.slice(0, 1_024)];
20
+ })
21
+ : [];
22
+ const maximum = context.input.limits.max_storage_keys;
23
+ return {
24
+ value: {
25
+ origin,
26
+ usage_bytes: numberValue(quota?.usage) ?? null,
27
+ quota_bytes: numberValue(quota?.quota) ?? null,
28
+ local_storage_keys: local.slice(0, maximum),
29
+ session_storage_keys: session.slice(0, maximum),
30
+ indexed_db_names: indexed.slice(0, maximum),
31
+ cache_names: caches.slice(0, maximum),
32
+ values_redacted: true,
33
+ },
34
+ truncated: [local, session, indexed, caches].some((items) => items.length > maximum),
35
+ };
36
+ };
37
+ const storageKeys = async (context, origin, isLocalStorage, limitations) => {
38
+ const result = recordValue(await optionalCdpCommand(context, "DOMStorage.getDOMStorageItems", { storageId: { securityOrigin: origin, isLocalStorage } }, limitations));
39
+ return Array.isArray(result?.entries)
40
+ ? result.entries.flatMap((entry) => {
41
+ if (!Array.isArray(entry))
42
+ return [];
43
+ const key = stringValue(entry[0]);
44
+ return key === undefined ? [] : [key.slice(0, 1_024)];
45
+ })
46
+ : [];
47
+ };
48
+ const stringArray = (value) => Array.isArray(value)
49
+ ? value.flatMap((item) => {
50
+ const text = stringValue(item);
51
+ return text === undefined ? [] : [text.slice(0, 1_024)];
52
+ })
53
+ : [];
@@ -0,0 +1,70 @@
1
+ import { createHash } from "node:crypto";
2
+ import { AnalysisCancelledError, BrowserObservationError, } from "../domain/errors.js";
3
+ import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
4
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
+ export const recordValue = (value) => isRecord(value) ? value : undefined;
6
+ export const recordsValue = (value) => Array.isArray(value)
7
+ ? value.flatMap((item) => {
8
+ const record = recordValue(item);
9
+ return record === undefined ? [] : [record];
10
+ })
11
+ : [];
12
+ export const stringValue = (value) => typeof value === "string" ? value : undefined;
13
+ export const numberValue = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
14
+ export const requiredRecord = (value) => {
15
+ const record = recordValue(value);
16
+ if (record === undefined)
17
+ throw new BrowserObservationError("inspect_web_page", "protocol_error");
18
+ return record;
19
+ };
20
+ export const boundedText = (value, maximum = 1_024) => {
21
+ const text = stringValue(value);
22
+ return text === undefined ? null : text.slice(0, maximum);
23
+ };
24
+ export const isHttpUrl = (value) => {
25
+ if (value === undefined)
26
+ return false;
27
+ try {
28
+ const protocol = new URL(value).protocol;
29
+ return protocol === "http:" || protocol === "https:";
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ };
35
+ export const allowedSanitizedUrl = (value, allowedOrigins) => {
36
+ const text = stringValue(value);
37
+ if (text === undefined)
38
+ return undefined;
39
+ const sanitized = sanitizeBrowserUrl(text);
40
+ return sanitized.origin !== null && allowedOrigins.has(sanitized.origin)
41
+ ? sanitized
42
+ : undefined;
43
+ };
44
+ export const sourceResult = (source) => ({
45
+ included: true,
46
+ sha256: createHash("sha256").update(source).digest("hex"),
47
+ bytes: Buffer.byteLength(source),
48
+ content: source,
49
+ });
50
+ export const sourceExcluded = (reason) => ({ included: false, reason });
51
+ export const delayWithCancellation = async (durationMs, signal) => {
52
+ if (durationMs === 0)
53
+ return;
54
+ if (signal?.aborted === true)
55
+ throw new AnalysisCancelledError("inspect_web_page");
56
+ await new Promise((resolve, reject) => {
57
+ const timer = setTimeout(() => {
58
+ signal?.removeEventListener("abort", onAbort);
59
+ resolve();
60
+ }, durationMs);
61
+ const onAbort = () => {
62
+ clearTimeout(timer);
63
+ signal?.removeEventListener("abort", onAbort);
64
+ reject(new AnalysisCancelledError("inspect_web_page"));
65
+ };
66
+ signal?.addEventListener("abort", onAbort, { once: true });
67
+ if (signal?.aborted === true)
68
+ onAbort();
69
+ });
70
+ };