rea-agents 2.4.0 → 2.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.
- package/README.md +80 -18
- package/bridge/ghidra/ReaGhidraBridge.java +48 -1
- package/bridge/hopper_bridge.py +259 -24
- package/dist/application/AnalysisClientCleanup.js +14 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
- package/dist/application/BinarySession.js +15 -4
- package/dist/application/BinarySessionStatus.js +31 -1
- package/dist/application/BrowserEvidence.js +14 -0
- package/dist/application/BrowserScenarioCapturePort.js +1 -0
- package/dist/application/BrowserScenarioCaptureService.js +53 -0
- package/dist/application/BrowserScenarioEvidence.js +40 -0
- package/dist/application/CallPathTracing.js +160 -0
- package/dist/application/CapabilityInventory.js +68 -12
- package/dist/application/CompositeProvider.js +10 -1
- package/dist/application/DirectAnalysis.js +7 -2
- package/dist/application/EnhancedLiteralTracing.js +118 -0
- package/dist/application/EnhancedTools.js +35 -121
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
- package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
- package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
- package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
- package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
- package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
- package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
- package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
- package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
- package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
- package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
- package/dist/application/ProcessReactiveEffects.js +6 -0
- package/dist/application/ProcessReactiveHarness.js +16 -1
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReconstructionObligationAuthority.js +72 -0
- package/dist/application/ReconstructionObligationCandidates.js +265 -0
- package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
- package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
- package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
- package/dist/application/ReconstructionObligationLedgerService.js +72 -0
- package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
- package/dist/application/ReconstructionObligationPolicies.js +132 -0
- package/dist/application/ReconstructionReadinessService.js +48 -0
- package/dist/artifacts/ArtifactProvider.js +87 -22
- package/dist/browser/BrowserScenarioSecrets.js +53 -0
- package/dist/browser/BrowserScenarioSessionPort.js +1 -0
- package/dist/browser/CdpBrowserProvider.js +2 -2
- package/dist/browser/CdpCaptureDocuments.js +57 -0
- package/dist/browser/CdpCaptureStorage.js +71 -29
- package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
- package/dist/browser/CdpEndpoint.js +8 -6
- package/dist/browser/CdpPageCapture.js +2 -0
- package/dist/browser/JavaScriptRuntimeScope.js +38 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
- package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
- package/dist/browser/PlaywrightScenarioActions.js +62 -0
- package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
- package/dist/browser/PlaywrightScenarioEvents.js +301 -0
- package/dist/browser/PlaywrightScenarioSession.js +300 -0
- package/dist/browser/V8InspectorCaptureProjection.js +110 -0
- package/dist/browser/V8InspectorEndpoint.js +72 -0
- package/dist/browser/V8InspectorProvider.js +267 -0
- package/dist/catalogIdentity.js +8 -0
- package/dist/cli/artifactCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +37 -0
- package/dist/cli.js +4 -0
- package/dist/cliApplicationCommands.js +30 -2
- package/dist/cliBrowserAdvancedCommands.js +17 -5
- package/dist/cliBrowserCommands.js +1 -0
- package/dist/cliBrowserScenarioCommands.js +43 -0
- package/dist/cliCommandNames.js +8 -0
- package/dist/cliJavaScriptRuntimeCommands.js +164 -0
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/environment.js +12 -0
- package/dist/config/parseConfig.js +88 -17
- package/dist/contracts/analysisSearchContract.js +14 -0
- package/dist/contracts/applicationToolContracts.js +86 -2
- package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
- package/dist/contracts/artifactToolContracts.js +22 -0
- package/dist/contracts/browserProviderToolContracts.js +7 -0
- package/dist/contracts/browserScenarioToolContracts.js +81 -0
- package/dist/contracts/browserToolContracts.js +88 -98
- package/dist/contracts/enhancedInputs.js +25 -6
- package/dist/contracts/functionInstructionContract.js +8 -0
- package/dist/contracts/hopperMemoryContracts.js +22 -0
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
- package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
- package/dist/contracts/reconstructionReadinessExample.js +436 -0
- package/dist/contracts/toolContractExamples.js +5 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +13 -21
- package/dist/contracts/toolEffects.js +21 -0
- package/dist/contracts/toolOutputSchemaGroups.js +70 -20
- package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
- package/dist/domain/analysisErrorProjection.js +8 -0
- package/dist/domain/artifactInspection.js +347 -0
- package/dist/domain/browserCaptureComparison.js +82 -0
- package/dist/domain/browserObservation.js +8 -1
- package/dist/domain/browserObservationErrors.js +1 -0
- package/dist/domain/browserObservationSchemas.js +26 -0
- package/dist/domain/browserScenario.js +165 -0
- package/dist/domain/browserScenarioCapture.js +57 -0
- package/dist/domain/browserScenarioCaptureValues.js +204 -0
- package/dist/domain/browserScenarioDiff.js +324 -0
- package/dist/domain/browserScenarioDiffValues.js +157 -0
- package/dist/domain/browserScenarioNormalization.js +45 -0
- package/dist/domain/browserScenarioValues.js +372 -0
- package/dist/domain/hopperValues.js +10 -0
- package/dist/domain/javascriptRuntimeObservation.js +170 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
- package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +5 -4
- package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
- package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
- package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
- package/dist/domain/javascriptSemanticDataEffects.js +284 -0
- package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
- package/dist/domain/javascriptSemanticFingerprints.js +174 -0
- package/dist/domain/javascriptSemanticIr.js +21 -1
- package/dist/domain/javascriptSemanticObjects.js +115 -0
- package/dist/domain/javascriptSemanticPromises.js +290 -0
- package/dist/domain/javascriptSemanticResources.js +136 -0
- package/dist/domain/javascriptStaticAnalysis.js +4 -3
- package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
- package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
- package/dist/domain/processCapture.js +8 -2
- package/dist/domain/processReactiveScenario.js +1 -0
- package/dist/domain/processReactiveTransition.js +9 -4
- package/dist/domain/providerCleanupError.js +11 -0
- package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
- package/dist/domain/reconstructionReadiness.js +121 -0
- package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
- package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
- package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
- package/dist/domain/reconstructionReadinessSchemas.js +240 -0
- package/dist/domain/sourceToBundleComparison.js +335 -0
- package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
- package/dist/domain/sourceToBundleSignals.js +202 -0
- package/dist/domain/webCaptureDiff.js +141 -27
- package/dist/domain/webScreenshot.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +13 -1
- package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
- package/dist/hopper/HopperCleanup.js +158 -0
- package/dist/hopper/HopperClient.js +69 -99
- package/dist/hopper/HopperProvider.js +20 -1
- package/dist/hopper/HopperRequestQueue.js +187 -0
- package/dist/hopper/HopperResponseStream.js +46 -0
- package/dist/hopper/protocol.js +1 -0
- package/dist/main/transport.js +24 -24
- package/dist/main.js +6 -0
- package/dist/mcpDoctor.js +25 -2
- package/dist/process/ProcessCommandIdentity.js +19 -0
- package/dist/process/ProcessOwnership.js +11 -8
- package/dist/server/DynamicToolAvailability.js +70 -0
- package/dist/server/createServer.js +46 -1
- package/dist/server/lifecycleProgress.js +10 -0
- package/dist/server/permissionFailure.js +12 -0
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
- package/dist/server/registerApplicationTools/helpers.js +11 -7
- package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
- package/dist/server/registerApplicationTools/readiness.js +55 -0
- package/dist/server/registerApplicationTools.js +6 -0
- package/dist/server/registerArtifactTools.js +9 -1
- package/dist/server/registerBrowserScenarioTool.js +25 -0
- package/dist/server/registerBrowserTools.js +2 -2
- package/dist/server/registerCloseLifecycleTool.js +57 -0
- package/dist/server/registerEnhancedTools.js +16 -5
- package/dist/server/registerEvidenceResources.js +4 -0
- package/dist/server/registerEvidenceTools.js +9 -2
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
- package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
- package/dist/server/registerReconstructionReadinessResource.js +39 -0
- package/dist/server/registerSessionStatusTool.js +11 -8
- package/dist/server/registerSessionTools.js +11 -57
- package/dist/server/sessionAvailabilityPolicy.js +10 -8
- package/package.json +12 -6
- package/skills/reverse-engineer-anything/SKILL.md +2 -2
|
@@ -201,9 +201,16 @@ export const inspectWebPageInputSchema = z
|
|
|
201
201
|
websocket_shape_approved: z.boolean().default(false),
|
|
202
202
|
include_script_sources: z.boolean().default(false),
|
|
203
203
|
include_storage_keys: z.boolean().default(false),
|
|
204
|
+
include_storage_fingerprints: z.boolean().default(false),
|
|
204
205
|
limits: browserInspectionLimitsSchema.default(DEFAULT_BROWSER_INSPECTION_LIMITS),
|
|
205
206
|
})
|
|
206
207
|
.superRefine((input, context) => {
|
|
208
|
+
if (input.include_storage_fingerprints && !input.include_storage_keys)
|
|
209
|
+
context.addIssue({
|
|
210
|
+
code: "custom",
|
|
211
|
+
path: ["include_storage_fingerprints"],
|
|
212
|
+
message: "Storage fingerprints require storage key capture",
|
|
213
|
+
});
|
|
207
214
|
if (input.limits.max_script_source_bytes >
|
|
208
215
|
input.limits.max_total_script_source_bytes)
|
|
209
216
|
context.addIssue({
|
|
@@ -261,7 +268,7 @@ export const inspectWebPageInputSchema = z
|
|
|
261
268
|
message: "Per-field accessibility text limit cannot exceed the total text limit",
|
|
262
269
|
});
|
|
263
270
|
});
|
|
264
|
-
const sanitizedBrowserUrlSchema = z.object({
|
|
271
|
+
export const sanitizedBrowserUrlSchema = z.object({
|
|
265
272
|
url: z.string().max(MAX_SANITIZED_BROWSER_URL_CHARS),
|
|
266
273
|
origin: z.string().max(2_048).nullable(),
|
|
267
274
|
query_parameter_names: z
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -57,6 +57,12 @@ const browserAxNodeSchema = z.object({
|
|
|
57
57
|
name: z.string().nullable(),
|
|
58
58
|
description: z.string().nullable(),
|
|
59
59
|
ignored: z.boolean(),
|
|
60
|
+
states: z
|
|
61
|
+
.array(z.object({
|
|
62
|
+
name: z.string(),
|
|
63
|
+
value: z.union([z.boolean(), z.number(), z.string()]),
|
|
64
|
+
}))
|
|
65
|
+
.default([]),
|
|
60
66
|
});
|
|
61
67
|
const browserScriptSourceSchema = z.discriminatedUnion("included", [
|
|
62
68
|
z.object({ included: z.literal(false), reason: z.string() }),
|
|
@@ -280,6 +286,26 @@ export const webPageInspectionSchema = z.object({
|
|
|
280
286
|
session_storage_keys: z.array(z.string()),
|
|
281
287
|
indexed_db_names: z.array(z.string()),
|
|
282
288
|
cache_names: z.array(z.string()),
|
|
289
|
+
content_fingerprints: z
|
|
290
|
+
.array(z.object({
|
|
291
|
+
scope: z.enum([
|
|
292
|
+
"cookie",
|
|
293
|
+
"local_storage",
|
|
294
|
+
"session_storage",
|
|
295
|
+
"indexed_db_schema",
|
|
296
|
+
"indexed_db_record",
|
|
297
|
+
"cache_entry",
|
|
298
|
+
]),
|
|
299
|
+
identity_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
300
|
+
value_sha256: z
|
|
301
|
+
.string()
|
|
302
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
303
|
+
.nullable(),
|
|
304
|
+
complete: z.boolean(),
|
|
305
|
+
}))
|
|
306
|
+
.default([]),
|
|
307
|
+
fingerprint_algorithm: z.literal("sha256").default("sha256"),
|
|
308
|
+
fingerprints_complete: z.boolean().default(false),
|
|
283
309
|
values_redacted: z.literal(true),
|
|
284
310
|
}),
|
|
285
311
|
limitations: z.array(z.string()),
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { BROWSER_SCENARIO_LIMITS, browserScenarioActionSchema, browserScenarioAllowedOriginsSchema, browserScenarioBrowserSchema, browserScenarioCaptureLimitsSchema, browserScenarioCaptureSchema, browserScenarioEnvironmentSchema, browserScenarioRedactionSchema, browserScenarioRequestReplaySchema, browserScenarioSecretSchema, browserScenarioStorageSchema, browserScenarioUrlSchema, } from "./browserScenarioValues.js";
|
|
3
|
+
export { BROWSER_SCENARIO_LIMITS, browserScenarioActionSchema, browserScenarioUrlSchema, browserScenarioValueSchema, } from "./browserScenarioValues.js";
|
|
4
|
+
const scenarioShapeSchema = z.strictObject({
|
|
5
|
+
schema_version: z.literal(1),
|
|
6
|
+
browser: browserScenarioBrowserSchema,
|
|
7
|
+
start_url: browserScenarioUrlSchema,
|
|
8
|
+
allowed_origins: browserScenarioAllowedOriginsSchema,
|
|
9
|
+
environment: browserScenarioEnvironmentSchema,
|
|
10
|
+
actions: z
|
|
11
|
+
.array(browserScenarioActionSchema)
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(BROWSER_SCENARIO_LIMITS.actions),
|
|
14
|
+
storage: browserScenarioStorageSchema,
|
|
15
|
+
request_replay: browserScenarioRequestReplaySchema,
|
|
16
|
+
secrets: z
|
|
17
|
+
.array(browserScenarioSecretSchema)
|
|
18
|
+
.max(BROWSER_SCENARIO_LIMITS.secrets)
|
|
19
|
+
.default([]),
|
|
20
|
+
redaction: browserScenarioRedactionSchema,
|
|
21
|
+
capture: browserScenarioCaptureSchema,
|
|
22
|
+
limits: browserScenarioCaptureLimitsSchema,
|
|
23
|
+
approved: z.literal(true),
|
|
24
|
+
});
|
|
25
|
+
const addIssue = (context, path, message) => context.addIssue({ code: "custom", path, message });
|
|
26
|
+
const assertUnique = (values, path, label, context) => {
|
|
27
|
+
if (new Set(values).size !== values.length)
|
|
28
|
+
addIssue(context, path, `${label} must be unique`);
|
|
29
|
+
};
|
|
30
|
+
const originFor = (destination) => new URL(destination.url).origin;
|
|
31
|
+
const assertAllowedDestination = (destination, allowedOrigins, path, context) => {
|
|
32
|
+
if (!allowedOrigins.has(originFor(destination)))
|
|
33
|
+
addIssue(context, path, `URL origin ${originFor(destination)} is not declared in allowed_origins`);
|
|
34
|
+
};
|
|
35
|
+
const secretReferencesInValue = (value) => (value?.source === "secret" ? [value.secret_id] : []);
|
|
36
|
+
const destinationSecretReferences = (destination) => destination.query.flatMap(({ value }) => secretReferencesInValue(value));
|
|
37
|
+
const actionSecretReferences = (action) => {
|
|
38
|
+
if (action.action === "fill" || action.action === "select_option")
|
|
39
|
+
return secretReferencesInValue(action.value);
|
|
40
|
+
if (action.action === "goto")
|
|
41
|
+
return destinationSecretReferences(action.destination);
|
|
42
|
+
return [];
|
|
43
|
+
};
|
|
44
|
+
const validateStorage = (scenario, allowedOrigins, context) => {
|
|
45
|
+
const references = [];
|
|
46
|
+
scenario.storage.cookies.forEach((cookie, index) => {
|
|
47
|
+
assertAllowedDestination(cookie.destination, allowedOrigins, ["storage", "cookies", index, "destination"], context);
|
|
48
|
+
references.push(...secretReferencesInValue(cookie.value));
|
|
49
|
+
references.push(...destinationSecretReferences(cookie.destination));
|
|
50
|
+
});
|
|
51
|
+
for (const collection of ["local_storage", "session_storage"])
|
|
52
|
+
scenario.storage[collection].forEach((storage, index) => {
|
|
53
|
+
if (!allowedOrigins.has(storage.origin))
|
|
54
|
+
addIssue(context, ["storage", collection, index, "origin"], `Storage origin ${storage.origin} is not declared in allowed_origins`);
|
|
55
|
+
for (const entry of storage.entries)
|
|
56
|
+
references.push(...secretReferencesInValue(entry.value));
|
|
57
|
+
});
|
|
58
|
+
return references;
|
|
59
|
+
};
|
|
60
|
+
const SENSITIVE_HEADERS = new Set([
|
|
61
|
+
"authorization",
|
|
62
|
+
"cookie",
|
|
63
|
+
"proxy-authorization",
|
|
64
|
+
"set-cookie",
|
|
65
|
+
]);
|
|
66
|
+
const FORBIDDEN_REPLAY_HEADERS = new Set([
|
|
67
|
+
"content-length",
|
|
68
|
+
"location",
|
|
69
|
+
"transfer-encoding",
|
|
70
|
+
]);
|
|
71
|
+
const validateRequestReplay = (scenario, allowedOrigins, context) => {
|
|
72
|
+
if (scenario.request_replay.mode === "disabled")
|
|
73
|
+
return [];
|
|
74
|
+
assertUnique(scenario.request_replay.routes.map(({ route_id: id }) => id), ["request_replay", "routes"], "Replay route IDs", context);
|
|
75
|
+
assertUnique(scenario.request_replay.routes.map(({ method, request }) => `${method} ${request.url}?${request.query
|
|
76
|
+
.map(({ name, value }) => value.source === "secret"
|
|
77
|
+
? `${name}=\${secret:${value.secret_id}}`
|
|
78
|
+
: `${name}=${value.value}`)
|
|
79
|
+
.join("&")}`), ["request_replay", "routes"], "Replay request matchers", context);
|
|
80
|
+
const references = [];
|
|
81
|
+
scenario.request_replay.routes.forEach((route, routeIndex) => {
|
|
82
|
+
assertAllowedDestination(route.request, allowedOrigins, ["request_replay", "routes", routeIndex, "request"], context);
|
|
83
|
+
references.push(...destinationSecretReferences(route.request));
|
|
84
|
+
if (route.response.kind === "redirect") {
|
|
85
|
+
assertAllowedDestination(route.response.destination, allowedOrigins, ["request_replay", "routes", routeIndex, "response", "destination"], context);
|
|
86
|
+
references.push(...destinationSecretReferences(route.response.destination));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
assertUnique(route.response.headers.map(({ name }) => name.toLowerCase()), ["request_replay", "routes", routeIndex, "response", "headers"], "Replay response header names", context);
|
|
90
|
+
route.response.headers.forEach((header, headerIndex) => {
|
|
91
|
+
if (FORBIDDEN_REPLAY_HEADERS.has(header.name))
|
|
92
|
+
addIssue(context, [
|
|
93
|
+
"request_replay",
|
|
94
|
+
"routes",
|
|
95
|
+
routeIndex,
|
|
96
|
+
"response",
|
|
97
|
+
"headers",
|
|
98
|
+
headerIndex,
|
|
99
|
+
], `Replay header ${header.name} must be modeled by the provider`);
|
|
100
|
+
if (SENSITIVE_HEADERS.has(header.name) &&
|
|
101
|
+
header.value.source === "literal")
|
|
102
|
+
addIssue(context, [
|
|
103
|
+
"request_replay",
|
|
104
|
+
"routes",
|
|
105
|
+
routeIndex,
|
|
106
|
+
"response",
|
|
107
|
+
"headers",
|
|
108
|
+
headerIndex,
|
|
109
|
+
"value",
|
|
110
|
+
], `Credential header ${header.name} requires a declared secret`);
|
|
111
|
+
references.push(...secretReferencesInValue(header.value));
|
|
112
|
+
});
|
|
113
|
+
references.push(...secretReferencesInValue(route.response.body));
|
|
114
|
+
});
|
|
115
|
+
return references;
|
|
116
|
+
};
|
|
117
|
+
const validateSecretReferences = (scenario, references, context) => {
|
|
118
|
+
const declared = new Set(scenario.secrets.map(({ secret_id: id }) => id));
|
|
119
|
+
for (const reference of references)
|
|
120
|
+
if (!declared.has(reference))
|
|
121
|
+
addIssue(context, ["secrets"], `Secret reference ${reference} is not declared`);
|
|
122
|
+
const used = new Set(references);
|
|
123
|
+
scenario.secrets.forEach(({ secret_id: id }, index) => {
|
|
124
|
+
if (!used.has(id))
|
|
125
|
+
addIssue(context, ["secrets", index, "secret_id"], `Secret declaration ${id} is unused`);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const validateRedactionCoverage = (scenario, context) => {
|
|
129
|
+
const redactedQueries = new Set(scenario.redaction.query_parameter_names);
|
|
130
|
+
const destinations = [scenario.start_url];
|
|
131
|
+
for (const action of scenario.actions)
|
|
132
|
+
if (action.action === "goto")
|
|
133
|
+
destinations.push(action.destination);
|
|
134
|
+
for (const cookie of scenario.storage.cookies)
|
|
135
|
+
destinations.push(cookie.destination);
|
|
136
|
+
if (scenario.request_replay.mode === "exact")
|
|
137
|
+
for (const route of scenario.request_replay.routes) {
|
|
138
|
+
destinations.push(route.request);
|
|
139
|
+
if (route.response.kind === "redirect")
|
|
140
|
+
destinations.push(route.response.destination);
|
|
141
|
+
}
|
|
142
|
+
for (const destination of destinations)
|
|
143
|
+
for (const query of destination.query)
|
|
144
|
+
if (query.value.source === "secret" &&
|
|
145
|
+
!redactedQueries.has(query.name.toLowerCase()))
|
|
146
|
+
addIssue(context, ["redaction", "query_parameter_names"], `Secret query parameter ${query.name} must be declared for redaction`);
|
|
147
|
+
};
|
|
148
|
+
const validateBrowserScenario = (scenario, context) => {
|
|
149
|
+
const allowedOrigins = new Set(scenario.allowed_origins);
|
|
150
|
+
assertAllowedDestination(scenario.start_url, allowedOrigins, ["start_url"], context);
|
|
151
|
+
assertUnique(scenario.actions.map(({ step_id: id }) => id), ["actions"], "Action step IDs", context);
|
|
152
|
+
assertUnique(scenario.secrets.map(({ secret_id: id }) => id), ["secrets"], "Secret IDs", context);
|
|
153
|
+
assertUnique(scenario.secrets.map(({ environment_variable: name }) => name), ["secrets"], "Secret environment variables", context);
|
|
154
|
+
const references = scenario.actions.flatMap(actionSecretReferences);
|
|
155
|
+
for (const [index, action] of scenario.actions.entries())
|
|
156
|
+
if (action.action === "goto")
|
|
157
|
+
assertAllowedDestination(action.destination, allowedOrigins, ["actions", index, "destination"], context);
|
|
158
|
+
references.push(...destinationSecretReferences(scenario.start_url));
|
|
159
|
+
references.push(...validateStorage(scenario, allowedOrigins, context));
|
|
160
|
+
references.push(...validateRequestReplay(scenario, allowedOrigins, context));
|
|
161
|
+
validateSecretReferences(scenario, references, context);
|
|
162
|
+
validateRedactionCoverage(scenario, context);
|
|
163
|
+
};
|
|
164
|
+
/** Strict provider-neutral contract for one bounded browser capture scenario. */
|
|
165
|
+
export const browserScenarioSchema = scenarioShapeSchema.superRefine(validateBrowserScenario);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { browserScenarioCompletenessSchema, browserScenarioEventSchema, browserScenarioStepSchema, } from "./browserScenarioCaptureValues.js";
|
|
3
|
+
export { browserScenarioCompletenessSchema, browserScenarioEventSchema, browserScenarioStepSchema, browserStepArtifactsSchema, captureStateSchema, } from "./browserScenarioCaptureValues.js";
|
|
4
|
+
/** Step-indexed, explicitly bounded browser scenario observation. */
|
|
5
|
+
export const browserScenarioCaptureSchema = z
|
|
6
|
+
.strictObject({
|
|
7
|
+
schema_version: z.literal(1),
|
|
8
|
+
browser: z.strictObject({
|
|
9
|
+
mode: z.enum(["launch", "connect"]),
|
|
10
|
+
process_ownership: z.enum(["provider-owned", "external"]),
|
|
11
|
+
cleanup: z.enum(["terminated-owned-process", "disconnected-external"]),
|
|
12
|
+
product: z.string().min(1).max(1_024),
|
|
13
|
+
version: z.string().min(1).max(256),
|
|
14
|
+
}),
|
|
15
|
+
scenario: z.strictObject({
|
|
16
|
+
start_origin: z.string().min(1).max(2_048),
|
|
17
|
+
allowed_origins: z.array(z.string().min(1).max(2_048)).min(1).max(32),
|
|
18
|
+
action_count: z.number().int().min(1).max(128),
|
|
19
|
+
secret_references: z.array(z.string().min(1).max(64)).max(64),
|
|
20
|
+
}),
|
|
21
|
+
duration_ms: z.number().int().min(0),
|
|
22
|
+
steps: z.array(browserScenarioStepSchema).min(1).max(129),
|
|
23
|
+
events: z.strictObject({
|
|
24
|
+
retained: z.number().int().min(0),
|
|
25
|
+
dropped: z.number().int().min(0),
|
|
26
|
+
items: z.array(browserScenarioEventSchema).max(100_000),
|
|
27
|
+
}),
|
|
28
|
+
completeness: browserScenarioCompletenessSchema,
|
|
29
|
+
limitations: z.array(z.string().min(1).max(2_048)).max(64),
|
|
30
|
+
})
|
|
31
|
+
.superRefine((capture, context) => {
|
|
32
|
+
if (capture.events.retained !== capture.events.items.length)
|
|
33
|
+
context.addIssue({
|
|
34
|
+
code: "custom",
|
|
35
|
+
path: ["events", "retained"],
|
|
36
|
+
message: "Retained event count must equal items length",
|
|
37
|
+
});
|
|
38
|
+
if (capture.steps.length !== capture.scenario.action_count + 1)
|
|
39
|
+
context.addIssue({
|
|
40
|
+
code: "custom",
|
|
41
|
+
path: ["steps"],
|
|
42
|
+
message: "Capture must include initial state and one entry per action",
|
|
43
|
+
});
|
|
44
|
+
const expectedIndices = capture.steps.map((_, index) => index);
|
|
45
|
+
if (capture.steps.some(({ step_index: index }, offset) => index !== expectedIndices[offset]))
|
|
46
|
+
context.addIssue({
|
|
47
|
+
code: "custom",
|
|
48
|
+
path: ["steps"],
|
|
49
|
+
message: "Step indices must be contiguous from zero",
|
|
50
|
+
});
|
|
51
|
+
if (capture.events.items.some(({ step_index: index }) => index >= capture.steps.length))
|
|
52
|
+
context.addIssue({
|
|
53
|
+
code: "custom",
|
|
54
|
+
path: ["events", "items"],
|
|
55
|
+
message: "Event references an unknown step index",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { sanitizedBrowserUrlSchema } from "./browserObservation.js";
|
|
4
|
+
import { webScreenshotArtifactSchema } from "./webScreenshot.js";
|
|
5
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
6
|
+
const boundedTextSchema = z.string().max(1_048_576);
|
|
7
|
+
const textArtifactSchema = z
|
|
8
|
+
.strictObject({
|
|
9
|
+
sha256: digestSchema,
|
|
10
|
+
bytes: z
|
|
11
|
+
.number()
|
|
12
|
+
.int()
|
|
13
|
+
.min(0)
|
|
14
|
+
.max(16 * 1_024 * 1_024),
|
|
15
|
+
text: boundedTextSchema,
|
|
16
|
+
})
|
|
17
|
+
.superRefine((artifact, context) => {
|
|
18
|
+
const bytes = Buffer.from(artifact.text);
|
|
19
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
20
|
+
if (bytes.byteLength !== artifact.bytes || sha256 !== artifact.sha256)
|
|
21
|
+
context.addIssue({
|
|
22
|
+
code: "custom",
|
|
23
|
+
message: "Text artifact digest or size mismatch",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
export const captureStateSchema = (value) => z.discriminatedUnion("state", [
|
|
27
|
+
z.strictObject({ state: z.literal("captured"), value }),
|
|
28
|
+
z.strictObject({
|
|
29
|
+
state: z.literal("not_requested"),
|
|
30
|
+
}),
|
|
31
|
+
z.strictObject({
|
|
32
|
+
state: z.literal("missing"),
|
|
33
|
+
reason: z.string().min(1).max(1_024),
|
|
34
|
+
}),
|
|
35
|
+
z.strictObject({
|
|
36
|
+
state: z.literal("truncated"),
|
|
37
|
+
observed: z.number().int().min(0),
|
|
38
|
+
retained: z.number().int().min(0),
|
|
39
|
+
reason: z.string().min(1).max(1_024),
|
|
40
|
+
}),
|
|
41
|
+
]);
|
|
42
|
+
const historySchema = z.strictObject({
|
|
43
|
+
length: z.number().int().min(0),
|
|
44
|
+
current_url: sanitizedBrowserUrlSchema,
|
|
45
|
+
navigation_entries: z
|
|
46
|
+
.array(z.strictObject({
|
|
47
|
+
type: z.enum([
|
|
48
|
+
"navigate",
|
|
49
|
+
"reload",
|
|
50
|
+
"back_forward",
|
|
51
|
+
"prerender",
|
|
52
|
+
"unknown",
|
|
53
|
+
]),
|
|
54
|
+
name: sanitizedBrowserUrlSchema,
|
|
55
|
+
}))
|
|
56
|
+
.max(256),
|
|
57
|
+
});
|
|
58
|
+
const storageValueSchema = z.strictObject({
|
|
59
|
+
name: z.string().max(1_024),
|
|
60
|
+
value_state: z.enum(["hashed", "redacted-secret"]),
|
|
61
|
+
value_sha256: digestSchema.nullable(),
|
|
62
|
+
});
|
|
63
|
+
const storageSnapshotSchema = z.strictObject({
|
|
64
|
+
cookies: z
|
|
65
|
+
.array(z.strictObject({
|
|
66
|
+
name: z.string().max(1_024),
|
|
67
|
+
domain: z.string().max(2_048),
|
|
68
|
+
path: z.string().max(4_096),
|
|
69
|
+
secure: z.boolean(),
|
|
70
|
+
http_only: z.boolean(),
|
|
71
|
+
same_site: z.enum(["Strict", "Lax", "None"]),
|
|
72
|
+
value_state: z.enum(["hashed", "redacted-secret"]),
|
|
73
|
+
value_sha256: digestSchema.nullable(),
|
|
74
|
+
}))
|
|
75
|
+
.max(512),
|
|
76
|
+
local_storage: z.array(storageValueSchema).max(512),
|
|
77
|
+
session_storage: z.array(storageValueSchema).max(512),
|
|
78
|
+
});
|
|
79
|
+
export const browserStepArtifactsSchema = z.strictObject({
|
|
80
|
+
screenshot: captureStateSchema(webScreenshotArtifactSchema),
|
|
81
|
+
dom: captureStateSchema(textArtifactSchema),
|
|
82
|
+
accessibility: captureStateSchema(textArtifactSchema),
|
|
83
|
+
url: captureStateSchema(sanitizedBrowserUrlSchema),
|
|
84
|
+
history: captureStateSchema(historySchema),
|
|
85
|
+
storage: captureStateSchema(storageSnapshotSchema),
|
|
86
|
+
});
|
|
87
|
+
const eventBase = {
|
|
88
|
+
sequence: z.number().int().min(1),
|
|
89
|
+
step_index: z.number().int().min(0),
|
|
90
|
+
};
|
|
91
|
+
const eventUrl = sanitizedBrowserUrlSchema.nullable();
|
|
92
|
+
export const browserScenarioEventSchema = z.discriminatedUnion("kind", [
|
|
93
|
+
z.strictObject({
|
|
94
|
+
...eventBase,
|
|
95
|
+
kind: z.literal("console"),
|
|
96
|
+
level: z.string().min(1).max(64),
|
|
97
|
+
text: z.string().max(65_536),
|
|
98
|
+
url: eventUrl,
|
|
99
|
+
}),
|
|
100
|
+
z.strictObject({
|
|
101
|
+
...eventBase,
|
|
102
|
+
kind: z.literal("page-error"),
|
|
103
|
+
message: z.string().max(65_536),
|
|
104
|
+
stack: z.string().max(262_144).nullable(),
|
|
105
|
+
}),
|
|
106
|
+
z.strictObject({
|
|
107
|
+
...eventBase,
|
|
108
|
+
kind: z.enum(["request", "response", "request-failed"]),
|
|
109
|
+
method: z.string().min(1).max(32),
|
|
110
|
+
url: sanitizedBrowserUrlSchema,
|
|
111
|
+
resource_type: z.string().min(1).max(64),
|
|
112
|
+
status: z.number().int().min(100).max(599).nullable(),
|
|
113
|
+
header_names: z.array(z.string().max(256)).max(256),
|
|
114
|
+
failure: z.string().max(1_024).nullable(),
|
|
115
|
+
}),
|
|
116
|
+
z.strictObject({
|
|
117
|
+
...eventBase,
|
|
118
|
+
kind: z.enum(["websocket-opened", "websocket-closed"]),
|
|
119
|
+
url: sanitizedBrowserUrlSchema,
|
|
120
|
+
}),
|
|
121
|
+
z.strictObject({
|
|
122
|
+
...eventBase,
|
|
123
|
+
kind: z.enum(["websocket-frame-sent", "websocket-frame-received"]),
|
|
124
|
+
url: sanitizedBrowserUrlSchema,
|
|
125
|
+
payload_type: z.enum(["text", "binary"]),
|
|
126
|
+
payload_bytes: z.number().int().min(0),
|
|
127
|
+
payload_text: z.string().max(65_536).nullable(),
|
|
128
|
+
truncated: z.boolean(),
|
|
129
|
+
}),
|
|
130
|
+
z.strictObject({
|
|
131
|
+
...eventBase,
|
|
132
|
+
kind: z.enum([
|
|
133
|
+
"frame-attached",
|
|
134
|
+
"frame-detached",
|
|
135
|
+
"frame-navigated",
|
|
136
|
+
"worker-created",
|
|
137
|
+
"worker-closed",
|
|
138
|
+
"popup-opened",
|
|
139
|
+
"popup-closed",
|
|
140
|
+
]),
|
|
141
|
+
url: eventUrl,
|
|
142
|
+
name: z.string().max(1_024).nullable(),
|
|
143
|
+
}),
|
|
144
|
+
z.strictObject({
|
|
145
|
+
...eventBase,
|
|
146
|
+
kind: z.literal("download-cancelled"),
|
|
147
|
+
suggested_filename: z.string().max(1_024),
|
|
148
|
+
url: sanitizedBrowserUrlSchema,
|
|
149
|
+
}),
|
|
150
|
+
]);
|
|
151
|
+
const completenessSectionSchema = z.enum([
|
|
152
|
+
"action",
|
|
153
|
+
"screenshot",
|
|
154
|
+
"dom",
|
|
155
|
+
"accessibility",
|
|
156
|
+
"url",
|
|
157
|
+
"history",
|
|
158
|
+
"storage",
|
|
159
|
+
"events",
|
|
160
|
+
"frames",
|
|
161
|
+
"workers",
|
|
162
|
+
"popups",
|
|
163
|
+
"websockets",
|
|
164
|
+
]);
|
|
165
|
+
export const browserScenarioCompletenessSchema = z
|
|
166
|
+
.strictObject({
|
|
167
|
+
status: z.enum(["complete", "incomplete", "truncated"]),
|
|
168
|
+
equality_eligible: z.boolean(),
|
|
169
|
+
missing_sections: z.array(completenessSectionSchema),
|
|
170
|
+
truncated_sections: z.array(completenessSectionSchema),
|
|
171
|
+
})
|
|
172
|
+
.superRefine((value, context) => {
|
|
173
|
+
const expectedStatus = value.truncated_sections.length > 0
|
|
174
|
+
? "truncated"
|
|
175
|
+
: value.missing_sections.length > 0
|
|
176
|
+
? "incomplete"
|
|
177
|
+
: "complete";
|
|
178
|
+
if (value.status !== expectedStatus)
|
|
179
|
+
context.addIssue({
|
|
180
|
+
code: "custom",
|
|
181
|
+
path: ["status"],
|
|
182
|
+
message: "Completeness status does not match section accounting",
|
|
183
|
+
});
|
|
184
|
+
if (value.equality_eligible !== (expectedStatus === "complete"))
|
|
185
|
+
context.addIssue({
|
|
186
|
+
code: "custom",
|
|
187
|
+
path: ["equality_eligible"],
|
|
188
|
+
message: "Missing or truncated evidence is never equality-eligible",
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
export const browserScenarioStepSchema = z.strictObject({
|
|
192
|
+
step_index: z.number().int().min(0),
|
|
193
|
+
step_id: z.string().min(1).max(64),
|
|
194
|
+
action: z.string().min(1).max(64),
|
|
195
|
+
status: z.enum(["completed", "failed", "cancelled"]),
|
|
196
|
+
elapsed_ms: z.number().int().min(0),
|
|
197
|
+
before_url: sanitizedBrowserUrlSchema,
|
|
198
|
+
after_url: sanitizedBrowserUrlSchema,
|
|
199
|
+
error: z.string().max(4_096).nullable(),
|
|
200
|
+
event_sequence_start: z.number().int().min(1),
|
|
201
|
+
event_sequence_end: z.number().int().min(0),
|
|
202
|
+
artifacts: browserStepArtifactsSchema,
|
|
203
|
+
completeness: browserScenarioCompletenessSchema,
|
|
204
|
+
});
|