rea-agents 2.4.0 → 3.0.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 +91 -19
- package/bridge/ghidra/ReaGhidraBridge.java +524 -4
- package/bridge/hopper_bridge.py +313 -28
- package/dist/application/AnalysisClientCleanup.js +14 -0
- package/dist/application/AnalysisContextQueries.js +130 -0
- package/dist/application/AnalysisSnapshotCache.js +4 -2
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
- package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
- package/dist/application/BinarySession.js +20 -8
- package/dist/application/BinarySessionComposition.js +21 -0
- package/dist/application/BinarySessionRecords.js +79 -9
- 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 +84 -14
- package/dist/application/ClientRegistrationStatus.js +4 -1
- package/dist/application/CompositeProvider.js +10 -1
- package/dist/application/DirectAnalysis.js +13 -4
- package/dist/application/EnhancedLiteralTracing.js +118 -0
- package/dist/application/EnhancedTools.js +71 -127
- package/dist/application/EvidenceLedger.js +45 -16
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
- package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
- 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/LazyAnalysisProvider.js +77 -0
- package/dist/application/LoopbackReplay.js +1 -1
- package/dist/application/NativeApiInspection.js +80 -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/application/SessionProviderRouter.js +6 -6
- package/dist/application/SetupClientConfiguration.js +4 -2
- package/dist/application/runtime.js +26 -9
- 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 +259 -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 +11 -3
- package/dist/cli/artifactCommands.js +44 -3
- package/dist/cli/coreAnalysisCommands.js +78 -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 +9 -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 +29 -2
- 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 +35 -6
- package/dist/contracts/functionInstructionContract.js +8 -0
- package/dist/contracts/functionWorkflowToolContracts.js +30 -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/promptContracts.js +14 -15
- package/dist/contracts/reconstructionReadinessExample.js +436 -0
- package/dist/contracts/toolContractExamples.js +8 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +34 -25
- package/dist/contracts/toolEffects.js +25 -0
- package/dist/contracts/toolOutputSchemaGroups.js +120 -30
- package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
- package/dist/domain/analysisErrorPresentation.js +8 -2
- package/dist/domain/analysisErrorProjection.js +9 -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/bytecodeProvider.js +179 -0
- package/dist/domain/conformancePackage.js +169 -0
- package/dist/domain/conformanceReplay.js +104 -0
- package/dist/domain/conformanceTrustGate.js +193 -0
- package/dist/domain/customProtocolCapture.js +137 -0
- package/dist/domain/eventProcessTree.js +214 -0
- package/dist/domain/evidenceBundle.js +47 -0
- package/dist/domain/hopperStartupFailure.js +1 -1
- package/dist/domain/hopperValues.js +14 -1
- package/dist/domain/inputIssueProjection.js +7 -1
- 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 +13 -16
- 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/javascriptSourceParser.js +15 -0
- package/dist/domain/javascriptStaticAnalysis.js +12 -15
- package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
- package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
- package/dist/domain/mobileApplicationInvestigation.js +159 -0
- package/dist/domain/nativeApiBoundary.js +130 -0
- package/dist/domain/objcSwiftMetadata.js +150 -0
- package/dist/domain/packageAnalysis.js +123 -0
- package/dist/domain/peInspection.js +196 -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/protocolCapture.js +243 -0
- 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/dotnet/ManagedStaticProvider.js +34 -5
- package/dist/generatedMcpToolCatalog.js +37 -0
- package/dist/generatedPackageMetadata.js +4 -4
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +40 -2
- package/dist/ghidra/GhidraProviderCapabilities.js +47 -38
- package/dist/hopper/HopperCleanup.js +158 -0
- package/dist/hopper/HopperClient.js +93 -99
- package/dist/hopper/HopperProvider.js +61 -30
- package/dist/hopper/HopperRequestQueue.js +215 -0
- package/dist/hopper/HopperResponseStream.js +46 -0
- package/dist/hopper/protocol.js +63 -20
- package/dist/main/transport.js +24 -27
- package/dist/main.js +14 -6
- package/dist/mcpDoctor.js +27 -4
- package/dist/mcpStartupPolicy.js +14 -0
- package/dist/process/ProcessCommandIdentity.js +19 -0
- package/dist/process/ProcessOwnership.js +11 -8
- package/dist/server/DynamicToolAvailability.js +70 -0
- package/dist/server/LazyToolCatalog.js +134 -0
- package/dist/server/createServer.js +92 -154
- package/dist/server/hydrateServerTools.js +191 -0
- 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 +28 -7
- package/dist/server/registerEvidenceResources.js +54 -101
- package/dist/server/registerEvidenceTools.js +9 -2
- package/dist/server/registerInvestigationTools.js +1 -2
- package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
- package/dist/server/registerReconstructionObligationLedgerResource.js +32 -0
- package/dist/server/registerReconstructionReadinessResource.js +32 -0
- package/dist/server/registerSessionRecordTools.js +82 -12
- package/dist/server/registerSessionStatusTool.js +11 -8
- package/dist/server/registerSessionTools.js +32 -58
- package/dist/server/sessionAvailabilityPolicy.js +10 -8
- package/dist/server/toolRegistrationOptions.js +21 -6
- package/dist/server/toolResult.js +12 -0
- package/package.json +28 -11
- package/scripts/package-runner-bootstrap.mjs +72 -0
- package/scripts/rea.mjs +11 -1
- package/skills/reverse-engineer-anything/SKILL.md +10 -3
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { artifactInventoryResultSchema, } from "./artifactGraph.js";
|
|
5
|
+
import { evidenceSchema, parseEvidence } from "./evidence.js";
|
|
6
|
+
import { jsonValueSchema } from "./jsonValue.js";
|
|
7
|
+
const boundedTextSchema = z.string().min(1).max(4_096);
|
|
8
|
+
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
9
|
+
const artifactIdSchema = z.string().regex(/^art_[a-f0-9]{64}$/u);
|
|
10
|
+
const inspectionLimitsSchema = z.strictObject({
|
|
11
|
+
max_observations: z.number().int().min(1).max(500).default(100),
|
|
12
|
+
max_relationships: z.number().int().min(1).max(500).default(100),
|
|
13
|
+
max_hypotheses: z.number().int().min(1).max(100).default(20),
|
|
14
|
+
max_unexplored_branches: z.number().int().min(1).max(100).default(20),
|
|
15
|
+
max_next_probes: z.number().int().min(1).max(50).default(20),
|
|
16
|
+
max_substeps: z.literal(1).default(1),
|
|
17
|
+
});
|
|
18
|
+
export const artifactInspectionLimitsSchema = inspectionLimitsSchema;
|
|
19
|
+
const inspectionObservationSchema = z.strictObject({
|
|
20
|
+
observation_id: z.string().regex(/^aio_[a-f0-9]{64}$/u),
|
|
21
|
+
kind: z.enum(["root-manifest", "artifact", "occurrence", "integrity"]),
|
|
22
|
+
subject: boundedTextSchema,
|
|
23
|
+
value: jsonValueSchema,
|
|
24
|
+
evidence_id: evidenceIdSchema,
|
|
25
|
+
});
|
|
26
|
+
const inspectionRelationshipSchema = z.strictObject({
|
|
27
|
+
relationship_id: z.string().regex(/^air_[a-f0-9]{64}$/u),
|
|
28
|
+
relation: z.enum([
|
|
29
|
+
"contains",
|
|
30
|
+
"extracts",
|
|
31
|
+
"slice-of",
|
|
32
|
+
"embeds",
|
|
33
|
+
"loads",
|
|
34
|
+
"maps-source",
|
|
35
|
+
"derived-from",
|
|
36
|
+
]),
|
|
37
|
+
source_artifact_id: artifactIdSchema,
|
|
38
|
+
target_artifact_id: artifactIdSchema,
|
|
39
|
+
occurrence_id: z.string().regex(/^occ_[a-f0-9]{64}$/u),
|
|
40
|
+
logical_path: boundedTextSchema.nullable(),
|
|
41
|
+
evidence_id: evidenceIdSchema,
|
|
42
|
+
});
|
|
43
|
+
const inspectionHypothesisSchema = z.strictObject({
|
|
44
|
+
hypothesis_id: z.string().regex(/^aih_[a-f0-9]{64}$/u),
|
|
45
|
+
statement: boundedTextSchema,
|
|
46
|
+
confidence: z.enum(["medium", "low"]),
|
|
47
|
+
basis_evidence_ids: z.array(evidenceIdSchema).min(1).max(8),
|
|
48
|
+
limitation: boundedTextSchema,
|
|
49
|
+
});
|
|
50
|
+
const inspectionContradictionSchema = z.strictObject({
|
|
51
|
+
contradiction_id: z.string().regex(/^aic_[a-f0-9]{64}$/u),
|
|
52
|
+
statement: boundedTextSchema,
|
|
53
|
+
occurrence_id: z.string().regex(/^occ_[a-f0-9]{64}$/u),
|
|
54
|
+
declared_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
55
|
+
observed_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
56
|
+
evidence_id: evidenceIdSchema,
|
|
57
|
+
});
|
|
58
|
+
const nextProbeSchema = z.strictObject({
|
|
59
|
+
operation: z.string().min(1).max(128),
|
|
60
|
+
rationale: boundedTextSchema,
|
|
61
|
+
authority: z.enum([
|
|
62
|
+
"artifact-bytes",
|
|
63
|
+
"managed-static-analysis",
|
|
64
|
+
"native-analysis-provider",
|
|
65
|
+
"ast-static-analysis",
|
|
66
|
+
]),
|
|
67
|
+
});
|
|
68
|
+
const unexploredBranchSchema = z.strictObject({
|
|
69
|
+
branch_id: z.string().regex(/^aib_[a-f0-9]{64}$/u),
|
|
70
|
+
reason: z.enum([
|
|
71
|
+
"page-limit",
|
|
72
|
+
"format-specific-analysis-required",
|
|
73
|
+
"unknown-format",
|
|
74
|
+
]),
|
|
75
|
+
detail: boundedTextSchema,
|
|
76
|
+
next_probe: nextProbeSchema.nullable(),
|
|
77
|
+
evidence_id: evidenceIdSchema,
|
|
78
|
+
});
|
|
79
|
+
/** Provider-neutral inspection result with its atomic source Evidence. */
|
|
80
|
+
export const artifactInspectionResultSchema = z.strictObject({
|
|
81
|
+
schema_version: z.literal(1),
|
|
82
|
+
inspection_id: z.string().regex(/^ai_[a-f0-9]{64}$/u),
|
|
83
|
+
subject: z.strictObject({
|
|
84
|
+
manifest_id: z.string().regex(/^agm_[a-f0-9]{64}$/u),
|
|
85
|
+
root_artifact_id: artifactIdSchema,
|
|
86
|
+
root_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
87
|
+
root_format: z.string().min(1).max(128),
|
|
88
|
+
}),
|
|
89
|
+
substeps: z
|
|
90
|
+
.array(z.strictObject({
|
|
91
|
+
substep_id: z.string().regex(/^ais_[a-f0-9]{64}$/u),
|
|
92
|
+
operation: z.literal("inventory_artifact"),
|
|
93
|
+
status: z.literal("completed"),
|
|
94
|
+
evidence_id: evidenceIdSchema,
|
|
95
|
+
evidence: evidenceSchema,
|
|
96
|
+
}))
|
|
97
|
+
.min(1)
|
|
98
|
+
.max(1),
|
|
99
|
+
observations: z.array(inspectionObservationSchema).max(500),
|
|
100
|
+
derived_relationships: z.array(inspectionRelationshipSchema).max(500),
|
|
101
|
+
hypotheses: z.array(inspectionHypothesisSchema).max(100),
|
|
102
|
+
contradictions: z.array(inspectionContradictionSchema).max(100),
|
|
103
|
+
unexplored_branches: z.array(unexploredBranchSchema).max(100),
|
|
104
|
+
next_probes: z.array(nextProbeSchema).max(50),
|
|
105
|
+
coverage: z.strictObject({
|
|
106
|
+
status: z.enum(["complete-within-substeps", "partial", "truncated"]),
|
|
107
|
+
substeps_completed: z.literal(1),
|
|
108
|
+
observations_retained: z.number().int().min(0),
|
|
109
|
+
observations_omitted: z.number().int().min(0),
|
|
110
|
+
relationships_retained: z.number().int().min(0),
|
|
111
|
+
relationships_omitted: z.number().int().min(0),
|
|
112
|
+
hypotheses_retained: z.number().int().min(0),
|
|
113
|
+
hypotheses_omitted: z.number().int().min(0),
|
|
114
|
+
unexplored_branches_retained: z.number().int().min(0),
|
|
115
|
+
unexplored_branches_omitted: z.number().int().min(0),
|
|
116
|
+
next_probes_retained: z.number().int().min(0),
|
|
117
|
+
next_probes_omitted: z.number().int().min(0),
|
|
118
|
+
}),
|
|
119
|
+
evidence_links: z.array(evidenceIdSchema).min(1).max(1),
|
|
120
|
+
limitations: z.array(boundedTextSchema).max(1_000),
|
|
121
|
+
});
|
|
122
|
+
/** Project one successful inventory Evidence record into an inspection report. */
|
|
123
|
+
export const createArtifactInspection = (inventoryInput, limits) => {
|
|
124
|
+
const inventoryEvidence = parseInventoryEvidence(inventoryInput);
|
|
125
|
+
const inventory = artifactInventoryResultSchema.parse(inventoryEvidence.normalized_result);
|
|
126
|
+
const observations = allObservations(inventory, inventoryEvidence.evidence_id);
|
|
127
|
+
const relationships = allRelationships(inventory, inventoryEvidence.evidence_id);
|
|
128
|
+
const hypotheses = allHypotheses(inventory, inventoryEvidence.evidence_id);
|
|
129
|
+
const nextProbes = probesFor(inventory);
|
|
130
|
+
const branches = allUnexploredBranches(inventory, inventoryEvidence.evidence_id, nextProbes);
|
|
131
|
+
const retained = {
|
|
132
|
+
observations: observations.slice(0, limits.max_observations),
|
|
133
|
+
relationships: relationships.slice(0, limits.max_relationships),
|
|
134
|
+
hypotheses: hypotheses.slice(0, limits.max_hypotheses),
|
|
135
|
+
branches: branches.slice(0, limits.max_unexplored_branches),
|
|
136
|
+
probes: nextProbes.slice(0, limits.max_next_probes),
|
|
137
|
+
};
|
|
138
|
+
const omissions = {
|
|
139
|
+
observations: observations.length - retained.observations.length,
|
|
140
|
+
relationships: relationships.length - retained.relationships.length,
|
|
141
|
+
hypotheses: hypotheses.length - retained.hypotheses.length,
|
|
142
|
+
branches: branches.length - retained.branches.length,
|
|
143
|
+
probes: nextProbes.length - retained.probes.length,
|
|
144
|
+
};
|
|
145
|
+
const semantic = {
|
|
146
|
+
schema_version: 1,
|
|
147
|
+
subject: {
|
|
148
|
+
manifest_id: inventory.manifest.manifest_id,
|
|
149
|
+
root_artifact_id: inventory.manifest.root_artifact_id,
|
|
150
|
+
root_sha256: inventory.manifest.root_sha256,
|
|
151
|
+
root_format: inventory.manifest.root_format,
|
|
152
|
+
},
|
|
153
|
+
substeps: [
|
|
154
|
+
{
|
|
155
|
+
substep_id: `ais_${digestCanonical({
|
|
156
|
+
operation: "inventory_artifact",
|
|
157
|
+
evidence_id: inventoryEvidence.evidence_id,
|
|
158
|
+
})}`,
|
|
159
|
+
operation: "inventory_artifact",
|
|
160
|
+
status: "completed",
|
|
161
|
+
evidence_id: inventoryEvidence.evidence_id,
|
|
162
|
+
evidence: inventoryEvidence,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
observations: retained.observations,
|
|
166
|
+
derived_relationships: retained.relationships,
|
|
167
|
+
hypotheses: retained.hypotheses,
|
|
168
|
+
contradictions: contradictions(inventory, inventoryEvidence.evidence_id),
|
|
169
|
+
unexplored_branches: retained.branches,
|
|
170
|
+
next_probes: retained.probes,
|
|
171
|
+
coverage: {
|
|
172
|
+
status: inspectionCoverage(inventory, omissions),
|
|
173
|
+
substeps_completed: 1,
|
|
174
|
+
observations_retained: retained.observations.length,
|
|
175
|
+
observations_omitted: omissions.observations,
|
|
176
|
+
relationships_retained: retained.relationships.length,
|
|
177
|
+
relationships_omitted: omissions.relationships,
|
|
178
|
+
hypotheses_retained: retained.hypotheses.length,
|
|
179
|
+
hypotheses_omitted: omissions.hypotheses,
|
|
180
|
+
unexplored_branches_retained: retained.branches.length,
|
|
181
|
+
unexplored_branches_omitted: omissions.branches,
|
|
182
|
+
next_probes_retained: retained.probes.length,
|
|
183
|
+
next_probes_omitted: omissions.probes,
|
|
184
|
+
},
|
|
185
|
+
evidence_links: [inventoryEvidence.evidence_id],
|
|
186
|
+
limitations: uniqueSorted([
|
|
187
|
+
...inventory.limitations,
|
|
188
|
+
"Inspection derives bounded static hypotheses and next probes; it does not execute the artifact or claim format-specific semantics.",
|
|
189
|
+
]),
|
|
190
|
+
};
|
|
191
|
+
return artifactInspectionResultSchema.parse({
|
|
192
|
+
...semantic,
|
|
193
|
+
inspection_id: `ai_${digestCanonical(semantic)}`,
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
const parseInventoryEvidence = (input) => {
|
|
197
|
+
const evidence = parseEvidence(input);
|
|
198
|
+
if (evidence.operation !== "inventory_artifact")
|
|
199
|
+
throw new TypeError("Artifact inspection substep requires inventory_artifact Evidence");
|
|
200
|
+
return evidence;
|
|
201
|
+
};
|
|
202
|
+
const allObservations = (inventory, evidenceId) => [
|
|
203
|
+
observation("root-manifest", inventory.manifest.root_artifact_id, inventory.manifest, evidenceId),
|
|
204
|
+
...inventory.nodes.items.map((node) => observation("artifact", node.artifact_id, node, evidenceId)),
|
|
205
|
+
...inventory.occurrences.items.map((occurrence) => observation("occurrence", occurrence.occurrence_id, occurrence, evidenceId)),
|
|
206
|
+
...inventory.integrity_contradictions.map((value) => observation("integrity", value.contradiction_id, value, evidenceId)),
|
|
207
|
+
];
|
|
208
|
+
const observation = (kind, subject, value, evidenceId) => {
|
|
209
|
+
const semantic = {
|
|
210
|
+
kind,
|
|
211
|
+
subject,
|
|
212
|
+
value: jsonValueSchema.parse(value),
|
|
213
|
+
evidence_id: evidenceId,
|
|
214
|
+
};
|
|
215
|
+
return { observation_id: `aio_${digestCanonical(semantic)}`, ...semantic };
|
|
216
|
+
};
|
|
217
|
+
const allRelationships = (inventory, evidenceId) => inventory.edges.items.map((edge) => {
|
|
218
|
+
const semantic = {
|
|
219
|
+
relation: edge.relation,
|
|
220
|
+
source_artifact_id: edge.parent_artifact_id,
|
|
221
|
+
target_artifact_id: edge.child_artifact_id,
|
|
222
|
+
occurrence_id: edge.occurrence_id,
|
|
223
|
+
logical_path: edge.logical_path,
|
|
224
|
+
evidence_id: evidenceId,
|
|
225
|
+
};
|
|
226
|
+
return {
|
|
227
|
+
relationship_id: `air_${digestCanonical(semantic)}`,
|
|
228
|
+
...semantic,
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
const allHypotheses = (inventory, evidenceId) => {
|
|
232
|
+
const statements = inventory.manifest.root_format === "pe"
|
|
233
|
+
? [
|
|
234
|
+
"The PE artifact may contain managed CLI metadata or native code; inventory bytes alone do not distinguish them.",
|
|
235
|
+
]
|
|
236
|
+
: ["asar", "javascript-bundle"].includes(inventory.manifest.root_format)
|
|
237
|
+
? [
|
|
238
|
+
"The artifact may contain a recoverable JavaScript application graph.",
|
|
239
|
+
]
|
|
240
|
+
: [];
|
|
241
|
+
return statements.map((statement) => {
|
|
242
|
+
const semantic = {
|
|
243
|
+
statement,
|
|
244
|
+
confidence: "low",
|
|
245
|
+
basis_evidence_ids: [evidenceId],
|
|
246
|
+
limitation: "Format classification proposes a bounded follow-up; it is not semantic proof.",
|
|
247
|
+
};
|
|
248
|
+
return { hypothesis_id: `aih_${digestCanonical(semantic)}`, ...semantic };
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
const contradictions = (inventory, evidenceId) => inventory.integrity_contradictions.map((value) => {
|
|
252
|
+
const semantic = {
|
|
253
|
+
statement: `Declared integrity for ${value.logical_path} contradicts observed bytes.`,
|
|
254
|
+
occurrence_id: value.occurrence_id,
|
|
255
|
+
declared_sha256: value.declared_sha256,
|
|
256
|
+
observed_sha256: value.observed_sha256,
|
|
257
|
+
evidence_id: evidenceId,
|
|
258
|
+
};
|
|
259
|
+
return {
|
|
260
|
+
contradiction_id: `aic_${digestCanonical(semantic)}`,
|
|
261
|
+
...semantic,
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
const probesFor = (inventory) => {
|
|
265
|
+
const format = inventory.manifest.root_format;
|
|
266
|
+
if (["asar", "javascript-bundle", "directory"].includes(format))
|
|
267
|
+
return [
|
|
268
|
+
{
|
|
269
|
+
operation: "analyze_javascript_application",
|
|
270
|
+
rationale: "Recover bounded JavaScript modules, source maps, Electron boundaries, and application relationships.",
|
|
271
|
+
authority: "ast-static-analysis",
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
if (format === "pe")
|
|
275
|
+
return [
|
|
276
|
+
{
|
|
277
|
+
operation: "inspect_managed_artifact",
|
|
278
|
+
rationale: "Check execution-free CLI metadata before choosing a deep native provider.",
|
|
279
|
+
authority: "managed-static-analysis",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
operation: "open_binary",
|
|
283
|
+
rationale: "Open as native code only when managed inspection reports a native or mixed boundary.",
|
|
284
|
+
authority: "native-analysis-provider",
|
|
285
|
+
},
|
|
286
|
+
];
|
|
287
|
+
if (["mach-o", "mach-o-universal", "elf"].includes(format))
|
|
288
|
+
return [
|
|
289
|
+
{
|
|
290
|
+
operation: "open_binary",
|
|
291
|
+
rationale: "Bind one deep native provider before function-level analysis.",
|
|
292
|
+
authority: "native-analysis-provider",
|
|
293
|
+
},
|
|
294
|
+
];
|
|
295
|
+
if (format === "plist")
|
|
296
|
+
return [
|
|
297
|
+
{
|
|
298
|
+
operation: "inspect_plist",
|
|
299
|
+
rationale: "Parse property-list values without executing the artifact.",
|
|
300
|
+
authority: "artifact-bytes",
|
|
301
|
+
},
|
|
302
|
+
];
|
|
303
|
+
return [];
|
|
304
|
+
};
|
|
305
|
+
const allUnexploredBranches = (inventory, evidenceId, nextProbes) => {
|
|
306
|
+
const pages = [
|
|
307
|
+
["nodes", inventory.nodes.next_offset],
|
|
308
|
+
["occurrences", inventory.occurrences.next_offset],
|
|
309
|
+
["relationships", inventory.edges.next_offset],
|
|
310
|
+
];
|
|
311
|
+
const branches = pages.flatMap(([collection, nextOffset]) => nextOffset === null
|
|
312
|
+
? []
|
|
313
|
+
: [
|
|
314
|
+
branch("page-limit", `${collection} continue at offset ${String(nextOffset)}.`, null, evidenceId),
|
|
315
|
+
]);
|
|
316
|
+
branches.push(...nextProbes.map((probe) => branch("format-specific-analysis-required", `${probe.operation} was not run by provider-neutral artifact inspection.`, probe, evidenceId)));
|
|
317
|
+
if (inventory.manifest.root_format === "unknown")
|
|
318
|
+
branches.push(branch("unknown-format", "Artifact bytes did not match an admitted format classifier.", null, evidenceId));
|
|
319
|
+
return branches;
|
|
320
|
+
};
|
|
321
|
+
const branch = (reason, detail, nextProbe, evidenceId) => {
|
|
322
|
+
const semantic = {
|
|
323
|
+
reason,
|
|
324
|
+
detail,
|
|
325
|
+
next_probe: nextProbe,
|
|
326
|
+
evidence_id: evidenceId,
|
|
327
|
+
};
|
|
328
|
+
return { branch_id: `aib_${digestCanonical(semantic)}`, ...semantic };
|
|
329
|
+
};
|
|
330
|
+
const inspectionCoverage = (inventory, omissions) => {
|
|
331
|
+
if (Object.values(omissions).some((count) => count > 0))
|
|
332
|
+
return "truncated";
|
|
333
|
+
return [
|
|
334
|
+
inventory.nodes.next_offset,
|
|
335
|
+
inventory.occurrences.next_offset,
|
|
336
|
+
inventory.edges.next_offset,
|
|
337
|
+
].some((offset) => offset !== null)
|
|
338
|
+
? "partial"
|
|
339
|
+
: "complete-within-substeps";
|
|
340
|
+
};
|
|
341
|
+
const digestCanonical = (value) => {
|
|
342
|
+
const encoded = canonicalize(value);
|
|
343
|
+
if (encoded === undefined)
|
|
344
|
+
throw new TypeError("Artifact inspection could not canonicalize");
|
|
345
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
346
|
+
};
|
|
347
|
+
const uniqueSorted = (values) => [...new Set(values)].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { browserScenarioDiffSchema, compareBrowserScenarios, compareBrowserScenariosInputSchema, } from "./browserScenarioDiff.js";
|
|
3
|
+
import { compareWebCaptures, captureSnapshotSchema, webCaptureDiffSchema, } from "./webCaptureDiff.js";
|
|
4
|
+
/** Top-level MCP shape for legacy page or step-indexed scenario comparison. */
|
|
5
|
+
export const browserCaptureComparisonWireSchema = z
|
|
6
|
+
.strictObject({
|
|
7
|
+
before: captureSnapshotSchema
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Earlier passive web-page capture; pair with after."),
|
|
10
|
+
after: captureSnapshotSchema
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Later passive web-page capture; pair with before."),
|
|
13
|
+
before_scenario: compareBrowserScenariosInputSchema.shape.before_scenario
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Earlier browser scenario capture; pair with after_scenario."),
|
|
16
|
+
after_scenario: compareBrowserScenariosInputSchema.shape.after_scenario
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Later browser scenario capture; pair with before_scenario."),
|
|
19
|
+
normalization: compareBrowserScenariosInputSchema.shape.normalization.describe("Scenario-only exact-literal normalization policy."),
|
|
20
|
+
max_changes: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.min(1)
|
|
24
|
+
.max(20_000)
|
|
25
|
+
.default(2_000)
|
|
26
|
+
.describe("Maximum normalized change records to retain."),
|
|
27
|
+
})
|
|
28
|
+
.superRefine((input, context) => {
|
|
29
|
+
const hasPassive = input.before !== undefined || input.after !== undefined;
|
|
30
|
+
const hasScenario = input.before_scenario !== undefined || input.after_scenario !== undefined;
|
|
31
|
+
if (hasPassive === hasScenario)
|
|
32
|
+
context.addIssue({
|
|
33
|
+
code: "custom",
|
|
34
|
+
message: "Provide exactly one complete pair: before/after or before_scenario/after_scenario",
|
|
35
|
+
});
|
|
36
|
+
if (hasPassive && (input.before === undefined || input.after === undefined))
|
|
37
|
+
context.addIssue({
|
|
38
|
+
code: "custom",
|
|
39
|
+
message: "Passive comparison requires both before and after",
|
|
40
|
+
});
|
|
41
|
+
if (hasScenario &&
|
|
42
|
+
(input.before_scenario === undefined ||
|
|
43
|
+
input.after_scenario === undefined))
|
|
44
|
+
context.addIssue({
|
|
45
|
+
code: "custom",
|
|
46
|
+
message: "Browser scenario comparison requires both before_scenario and after_scenario",
|
|
47
|
+
});
|
|
48
|
+
if (hasPassive && input.normalization.rules.length > 0)
|
|
49
|
+
context.addIssue({
|
|
50
|
+
code: "custom",
|
|
51
|
+
path: ["normalization"],
|
|
52
|
+
message: "Literal normalization rules apply only to browser scenario captures",
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
/** Parsed legacy page snapshots or step-indexed browser scenarios. */
|
|
56
|
+
export const browserCaptureComparisonInputSchema = browserCaptureComparisonWireSchema.transform((input) => {
|
|
57
|
+
if (input.before_scenario !== undefined &&
|
|
58
|
+
input.after_scenario !== undefined)
|
|
59
|
+
return {
|
|
60
|
+
before_scenario: input.before_scenario,
|
|
61
|
+
after_scenario: input.after_scenario,
|
|
62
|
+
normalization: input.normalization,
|
|
63
|
+
max_changes: input.max_changes,
|
|
64
|
+
};
|
|
65
|
+
if (input.before !== undefined && input.after !== undefined)
|
|
66
|
+
return {
|
|
67
|
+
before: input.before,
|
|
68
|
+
after: input.after,
|
|
69
|
+
max_changes: input.max_changes,
|
|
70
|
+
};
|
|
71
|
+
throw new TypeError("Capture comparison pair refinement failed");
|
|
72
|
+
});
|
|
73
|
+
/** Result from passive page or browser scenario comparison. */
|
|
74
|
+
export const browserCaptureComparisonSchema = z.union([
|
|
75
|
+
browserScenarioDiffSchema,
|
|
76
|
+
webCaptureDiffSchema,
|
|
77
|
+
]);
|
|
78
|
+
/** Dispatch a parsed capture comparison to its pure domain comparator. */
|
|
79
|
+
export const compareBrowserCaptures = (input) => isScenarioComparison(input)
|
|
80
|
+
? compareBrowserScenarios(input)
|
|
81
|
+
: compareWebCaptures(input);
|
|
82
|
+
const isScenarioComparison = (input) => "before_scenario" in input;
|
|
@@ -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);
|