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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createBrowserScenarioEvidence } from "./BrowserScenarioEvidence.js";
|
|
2
|
+
import { digestJson } from "./JavaScriptReplayPlanning.js";
|
|
3
|
+
import { AnalysisCapabilityUnavailableError, AnalysisProtocolError, PermissionRequiredError, } from "../domain/errors.js";
|
|
4
|
+
import { isLiteralLoopbackHostname } from "../domain/browserObservation.js";
|
|
5
|
+
import { err } from "../domain/result.js";
|
|
6
|
+
const OPERATION = "capture_browser_scenario";
|
|
7
|
+
const PROVIDER_ID = "rea-playwright-browser-scenario";
|
|
8
|
+
const networkScope = (origins) => origins.every((origin) => isLiteralLoopbackHostname(new URL(origin).hostname))
|
|
9
|
+
? "loopback"
|
|
10
|
+
: "external";
|
|
11
|
+
const authorizeScenario = async (authority, scenario) => {
|
|
12
|
+
if (authority === undefined)
|
|
13
|
+
return err(new AnalysisCapabilityUnavailableError(PROVIDER_ID, OPERATION, "browser scenario permission policy is not configured"));
|
|
14
|
+
const authorized = await authority.authorize({
|
|
15
|
+
capability: "browser_automate",
|
|
16
|
+
roots: [],
|
|
17
|
+
executables: scenario.browser.mode === "launch"
|
|
18
|
+
? [scenario.browser.executable_path]
|
|
19
|
+
: [],
|
|
20
|
+
environment_names: scenario.secrets.map(({ environment_variable: name }) => name),
|
|
21
|
+
origins: [
|
|
22
|
+
...(scenario.browser.mode === "connect"
|
|
23
|
+
? [scenario.browser.cdp_endpoint]
|
|
24
|
+
: []),
|
|
25
|
+
...scenario.allowed_origins,
|
|
26
|
+
],
|
|
27
|
+
network: networkScope(scenario.allowed_origins),
|
|
28
|
+
mount: false,
|
|
29
|
+
operation_identity: `${OPERATION}:${digestJson(scenario)}`,
|
|
30
|
+
}, "write");
|
|
31
|
+
if (!authorized.ok)
|
|
32
|
+
return err(authorized.error instanceof PermissionRequiredError
|
|
33
|
+
? authorized.error
|
|
34
|
+
: new AnalysisProtocolError(authorized.error.message, {
|
|
35
|
+
cause: authorized.error,
|
|
36
|
+
}));
|
|
37
|
+
return { ok: true, value: true };
|
|
38
|
+
};
|
|
39
|
+
/** Authorize and execute one controlled browser scenario. */
|
|
40
|
+
export const captureBrowserScenario = async (provider, authority, scenario, options = {}) => {
|
|
41
|
+
const authorized = await authorizeScenario(authority, scenario);
|
|
42
|
+
if (!authorized.ok)
|
|
43
|
+
return authorized;
|
|
44
|
+
if (provider === undefined)
|
|
45
|
+
return err(new AnalysisCapabilityUnavailableError(PROVIDER_ID, OPERATION, "browser scenario provider is not configured"));
|
|
46
|
+
const captured = await provider.captureScenario(scenario, options);
|
|
47
|
+
return captured.ok
|
|
48
|
+
? {
|
|
49
|
+
ok: true,
|
|
50
|
+
value: createBrowserScenarioEvidence(scenario, captured.value, provider.identity()),
|
|
51
|
+
}
|
|
52
|
+
: captured;
|
|
53
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
|
|
2
|
+
import { createEvidence, } from "../domain/evidence.js";
|
|
3
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
4
|
+
import { digestJson } from "./JavaScriptReplayPlanning.js";
|
|
5
|
+
const browserScenarioParameters = (scenario) => ({
|
|
6
|
+
scenario_sha256: digestJson(scenario),
|
|
7
|
+
browser_mode: scenario.browser.mode,
|
|
8
|
+
start_url: sanitizeBrowserUrl(scenario.start_url.url),
|
|
9
|
+
allowed_origins: scenario.allowed_origins,
|
|
10
|
+
actions: scenario.actions.map(({ step_id, action }) => ({
|
|
11
|
+
step_id,
|
|
12
|
+
action,
|
|
13
|
+
})),
|
|
14
|
+
secret_declarations: scenario.secrets.map(({ secret_id, environment_variable, purpose, redaction }) => ({
|
|
15
|
+
secret_id,
|
|
16
|
+
environment_variable,
|
|
17
|
+
purpose,
|
|
18
|
+
redaction,
|
|
19
|
+
})),
|
|
20
|
+
capture: scenario.capture,
|
|
21
|
+
limits: scenario.limits,
|
|
22
|
+
});
|
|
23
|
+
/** Create Evidence v2 without retaining resolved scenario secret values. */
|
|
24
|
+
export const createBrowserScenarioEvidence = (scenario, capture, provider) => createEvidence(undefined, provider, {
|
|
25
|
+
predicateType: "rea.browser-scenario-capture/v1",
|
|
26
|
+
operation: "capture_browser_scenario",
|
|
27
|
+
parameters: browserScenarioParameters(scenario),
|
|
28
|
+
result: jsonValueSchema.parse(capture),
|
|
29
|
+
confidence: "observed",
|
|
30
|
+
authority: "controlled-replay",
|
|
31
|
+
environment: {
|
|
32
|
+
id: `${capture.browser.product}@${capture.browser.version}`,
|
|
33
|
+
platform: process.platform,
|
|
34
|
+
architecture: process.arch,
|
|
35
|
+
isolation: capture.browser.process_ownership === "provider-owned"
|
|
36
|
+
? "process"
|
|
37
|
+
: "none",
|
|
38
|
+
},
|
|
39
|
+
limitations: capture.limitations,
|
|
40
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { projectAnalysisError } from "../domain/errors.js";
|
|
2
|
+
import { parseRelatedAddresses } from "../domain/hopperValues.js";
|
|
3
|
+
import { ok } from "../domain/result.js";
|
|
4
|
+
/** Trace one deterministic bounded direct caller/callee path. */
|
|
5
|
+
export const traceCallPath = async (call, input, signal) => {
|
|
6
|
+
const state = createTraceState(input);
|
|
7
|
+
while (state.queueIndex < state.queue.length &&
|
|
8
|
+
state.traversalPath.length === 0) {
|
|
9
|
+
const address = state.queue[state.queueIndex++];
|
|
10
|
+
if (address === undefined)
|
|
11
|
+
break;
|
|
12
|
+
const current = state.visited.get(address);
|
|
13
|
+
if (current === undefined)
|
|
14
|
+
continue;
|
|
15
|
+
if (current.depth >= input.max_depth) {
|
|
16
|
+
state.frontier.add(address);
|
|
17
|
+
state.residual.add("Call-path traversal reached the depth limit.");
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (state.operations >= input.max_operations) {
|
|
21
|
+
recordPendingFrontier(state, input);
|
|
22
|
+
state.residual.add("Call-path traversal reached the operation budget.");
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
state.operations += 1;
|
|
26
|
+
const found = await expandAddress(call, {
|
|
27
|
+
input,
|
|
28
|
+
state,
|
|
29
|
+
address,
|
|
30
|
+
current,
|
|
31
|
+
...(signal === undefined ? {} : { signal }),
|
|
32
|
+
});
|
|
33
|
+
if (found)
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
return ok(projectTrace(input, state));
|
|
37
|
+
};
|
|
38
|
+
const createTraceState = (input) => ({
|
|
39
|
+
visited: new Map([[input.start, { depth: 0, path: [input.start] }]]),
|
|
40
|
+
queue: [input.start],
|
|
41
|
+
edges: new Map(),
|
|
42
|
+
failures: [],
|
|
43
|
+
frontier: new Set(),
|
|
44
|
+
residual: new Set(),
|
|
45
|
+
operations: 0,
|
|
46
|
+
queueIndex: 0,
|
|
47
|
+
traversalPath: input.goal === input.start ? [input.start] : [],
|
|
48
|
+
});
|
|
49
|
+
const expandAddress = async (call, expansion) => {
|
|
50
|
+
const { input, state, address, current, signal } = expansion;
|
|
51
|
+
const relation = input.direction === "forward" ? "callees" : "callers";
|
|
52
|
+
const tool = input.direction === "forward" ? "procedure_callees" : "procedure_callers";
|
|
53
|
+
const result = await call(tool, { procedure: address }, signal);
|
|
54
|
+
if (!result.ok) {
|
|
55
|
+
state.failures.push({
|
|
56
|
+
address,
|
|
57
|
+
error: projectAnalysisError(result.error),
|
|
58
|
+
});
|
|
59
|
+
state.residual.add(`Call relationships were unavailable for ${address}.`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const related = parseRelatedAddresses(result.value, relation);
|
|
63
|
+
if (!related.ok) {
|
|
64
|
+
state.failures.push({
|
|
65
|
+
address,
|
|
66
|
+
error: projectAnalysisError(related.error),
|
|
67
|
+
});
|
|
68
|
+
state.residual.add(`Call relationships were unreadable for ${address}.`);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
for (const relatedAddress of sortedUniqueAddresses(related.value)) {
|
|
72
|
+
recordEdge(input, state, {
|
|
73
|
+
address,
|
|
74
|
+
relatedAddress,
|
|
75
|
+
discoveryDepth: current.depth + 1,
|
|
76
|
+
});
|
|
77
|
+
const path = state.visited.get(relatedAddress)?.path ?? [
|
|
78
|
+
...current.path,
|
|
79
|
+
relatedAddress,
|
|
80
|
+
];
|
|
81
|
+
if (state.visited.has(relatedAddress)) {
|
|
82
|
+
if (relatedAddress === input.goal) {
|
|
83
|
+
state.traversalPath = path;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (state.visited.size >= input.max_nodes) {
|
|
89
|
+
if (state.frontier.size < input.max_nodes)
|
|
90
|
+
state.frontier.add(relatedAddress);
|
|
91
|
+
state.residual.add("Call-path traversal reached the node limit.");
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
state.visited.set(relatedAddress, {
|
|
95
|
+
depth: current.depth + 1,
|
|
96
|
+
path,
|
|
97
|
+
});
|
|
98
|
+
if (relatedAddress === input.goal) {
|
|
99
|
+
state.traversalPath = path;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
state.queue.push(relatedAddress);
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
};
|
|
106
|
+
const recordEdge = (input, state, edge) => {
|
|
107
|
+
const sourceAddress = input.direction === "forward" ? edge.address : edge.relatedAddress;
|
|
108
|
+
const targetAddress = input.direction === "forward" ? edge.relatedAddress : edge.address;
|
|
109
|
+
const edgeKey = `${sourceAddress}\u0000${targetAddress}`;
|
|
110
|
+
if (state.edges.has(edgeKey))
|
|
111
|
+
return;
|
|
112
|
+
if (state.edges.size >= input.max_nodes) {
|
|
113
|
+
state.residual.add("Call-path traversal reached the edge limit.");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
state.edges.set(edgeKey, {
|
|
117
|
+
source_address: sourceAddress,
|
|
118
|
+
target_address: targetAddress,
|
|
119
|
+
discovery_depth: edge.discoveryDepth,
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
const recordPendingFrontier = (state, input) => {
|
|
123
|
+
for (const pending of state.queue.slice(state.queueIndex - 1))
|
|
124
|
+
if (state.frontier.size < input.max_nodes)
|
|
125
|
+
state.frontier.add(pending);
|
|
126
|
+
};
|
|
127
|
+
const projectTrace = (input, state) => ({
|
|
128
|
+
start: input.start,
|
|
129
|
+
goal: input.goal ?? null,
|
|
130
|
+
direction: input.direction,
|
|
131
|
+
goal_status: input.goal === undefined
|
|
132
|
+
? "not_requested"
|
|
133
|
+
: state.traversalPath.length > 0
|
|
134
|
+
? "reached"
|
|
135
|
+
: "not_reached",
|
|
136
|
+
nodes: [...state.visited.entries()]
|
|
137
|
+
.map(([address, { depth }]) => ({ address, depth }))
|
|
138
|
+
.sort((left, right) => left.depth - right.depth || compareAddress(left.address, right.address)),
|
|
139
|
+
edges: [...state.edges.values()].sort((left, right) => left.discovery_depth - right.discovery_depth ||
|
|
140
|
+
compareAddress(left.source_address, right.source_address) ||
|
|
141
|
+
compareAddress(left.target_address, right.target_address)),
|
|
142
|
+
traversal_path: state.traversalPath,
|
|
143
|
+
failures: state.failures,
|
|
144
|
+
frontier: [...state.frontier].sort(compareAddress),
|
|
145
|
+
limits: {
|
|
146
|
+
max_depth: input.max_depth,
|
|
147
|
+
max_nodes: input.max_nodes,
|
|
148
|
+
max_operations: input.max_operations,
|
|
149
|
+
nodes_visited: state.visited.size,
|
|
150
|
+
operations_used: state.operations,
|
|
151
|
+
},
|
|
152
|
+
truncated: state.residual.size > 0,
|
|
153
|
+
residual_unknowns: [...state.residual],
|
|
154
|
+
limitations: [
|
|
155
|
+
"Direct provider call relationships may omit unresolved indirect calls.",
|
|
156
|
+
"A not_reached goal is only absent from this bounded traversal, not from the complete program.",
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
const compareAddress = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
160
|
+
const sortedUniqueAddresses = (addresses) => [...new Set(addresses)].sort(compareAddress);
|
|
@@ -11,6 +11,18 @@ const statusSchema = z.object({
|
|
|
11
11
|
availability_code: z.string().nullable().optional(),
|
|
12
12
|
})),
|
|
13
13
|
});
|
|
14
|
+
const NO_CLIENT_FEATURES = {
|
|
15
|
+
elicitation_form: false,
|
|
16
|
+
elicitation_url: false,
|
|
17
|
+
roots: false,
|
|
18
|
+
sampling: false,
|
|
19
|
+
};
|
|
20
|
+
const CLIENT_FEATURE_REQUIREMENTS = {
|
|
21
|
+
capture_process_scenario: {
|
|
22
|
+
required: [],
|
|
23
|
+
optional: ["elicitation_form"],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
14
26
|
const ENHANCED_REQUIREMENTS = {
|
|
15
27
|
swift_classes: ["list_procedures"],
|
|
16
28
|
get_objc_classes: ["list_names"],
|
|
@@ -33,8 +45,8 @@ const ENHANCED_REQUIREMENTS = {
|
|
|
33
45
|
"resolve_containing_procedure",
|
|
34
46
|
],
|
|
35
47
|
};
|
|
36
|
-
/** Build stable per-operation availability
|
|
37
|
-
export const buildCapabilityInventory = (sessionStatus, policy) => {
|
|
48
|
+
/** Build stable per-operation availability for discovery and tool visibility. */
|
|
49
|
+
export const buildCapabilityInventory = (sessionStatus, policy, clientFeatures = NO_CLIENT_FEATURES) => {
|
|
38
50
|
const status = statusSchema.parse(sessionStatus);
|
|
39
51
|
const descriptors = new Map(status.capabilities.map((descriptor) => [
|
|
40
52
|
descriptor.operation,
|
|
@@ -53,12 +65,19 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
|
|
|
53
65
|
descriptors,
|
|
54
66
|
policy,
|
|
55
67
|
});
|
|
68
|
+
const clientRequirements = clientRequirementsFor(contract.name, clientFeatures);
|
|
69
|
+
const clientBlocked = clientRequirements.missing_required.length > 0;
|
|
56
70
|
return {
|
|
57
71
|
name: contract.name,
|
|
58
72
|
surface: contract.kind,
|
|
59
|
-
available: availability.reason === "available",
|
|
60
|
-
reason:
|
|
61
|
-
|
|
73
|
+
available: availability.reason === "available" && !clientBlocked,
|
|
74
|
+
reason: clientBlocked
|
|
75
|
+
? "client_capability_missing"
|
|
76
|
+
: availability.reason,
|
|
77
|
+
remediation: clientBlocked
|
|
78
|
+
? `Use an MCP client that supports: ${clientRequirements.missing_required.join(", ")}.`
|
|
79
|
+
: availability.remediation,
|
|
80
|
+
client_requirements: clientRequirements,
|
|
62
81
|
effects: { ...contract.effects },
|
|
63
82
|
annotations: {
|
|
64
83
|
read_only: contract.annotations.readOnlyHint ?? false,
|
|
@@ -69,6 +88,18 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
|
|
|
69
88
|
};
|
|
70
89
|
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
71
90
|
};
|
|
91
|
+
const clientRequirementsFor = (name, clientFeatures) => {
|
|
92
|
+
const requirements = CLIENT_FEATURE_REQUIREMENTS[name] ?? {
|
|
93
|
+
required: [],
|
|
94
|
+
optional: [],
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
required: [...requirements.required],
|
|
98
|
+
optional: [...requirements.optional],
|
|
99
|
+
missing_required: requirements.required.filter((feature) => !clientFeatures[feature]),
|
|
100
|
+
missing_optional: requirements.optional.filter((feature) => !clientFeatures[feature]),
|
|
101
|
+
};
|
|
102
|
+
};
|
|
72
103
|
const availabilityFor = (context) => {
|
|
73
104
|
const javascriptApplication = javascriptApplicationAvailability(context);
|
|
74
105
|
if (javascriptApplication !== null)
|
|
@@ -94,6 +125,9 @@ const javascriptApplicationAvailability = ({ name, policy, }) => {
|
|
|
94
125
|
: null;
|
|
95
126
|
};
|
|
96
127
|
const policyAvailability = ({ name, kind, policy, }) => {
|
|
128
|
+
const browser = browserPolicyAvailability(name, kind, policy);
|
|
129
|
+
if (browser !== null)
|
|
130
|
+
return browser;
|
|
97
131
|
if (name === "capture_process_scenario" && !policy.processCaptureEnabled)
|
|
98
132
|
return {
|
|
99
133
|
reason: "policy_disabled",
|
|
@@ -121,26 +155,43 @@ const policyAvailability = ({ name, kind, policy, }) => {
|
|
|
121
155
|
reason: "policy_disabled",
|
|
122
156
|
remediation: "Configure or grant an evidence_read root.",
|
|
123
157
|
};
|
|
124
|
-
if (kind === "
|
|
125
|
-
return policy.
|
|
158
|
+
if (kind === "electron-provider")
|
|
159
|
+
return policy.electronObservationEnabled === true
|
|
126
160
|
? { reason: "available", remediation: null }
|
|
127
161
|
: {
|
|
128
162
|
reason: "policy_disabled",
|
|
129
|
-
remediation: "Enable
|
|
163
|
+
remediation: "Enable Electron observation and configure a loopback CDP endpoint and canonical file roots.",
|
|
130
164
|
};
|
|
131
|
-
if (kind === "
|
|
132
|
-
return policy.
|
|
165
|
+
if (kind === "runtime-provider")
|
|
166
|
+
return policy.v8InspectorObservationEnabled === true
|
|
133
167
|
? { reason: "available", remediation: null }
|
|
134
168
|
: {
|
|
135
169
|
reason: "policy_disabled",
|
|
136
|
-
remediation: "Enable
|
|
170
|
+
remediation: "Enable V8 Inspector observation and configure exact loopback endpoints plus canonical file roots or exact origins.",
|
|
137
171
|
};
|
|
138
172
|
if (kind === "session")
|
|
139
173
|
return { reason: "available", remediation: null };
|
|
140
174
|
return null;
|
|
141
175
|
};
|
|
176
|
+
const browserPolicyAvailability = (name, kind, policy) => {
|
|
177
|
+
if (name === "capture_browser_scenario")
|
|
178
|
+
return policy.browserScenarioEnabled === true
|
|
179
|
+
? { reason: "available", remediation: null }
|
|
180
|
+
: {
|
|
181
|
+
reason: "policy_disabled",
|
|
182
|
+
remediation: "Enable browser scenarios and configure exact executable roots or loopback CDP endpoints, page origins, and environment secret names.",
|
|
183
|
+
};
|
|
184
|
+
if (kind !== "browser-provider")
|
|
185
|
+
return null;
|
|
186
|
+
return policy.browserObservationEnabled === true
|
|
187
|
+
? { reason: "available", remediation: null }
|
|
188
|
+
: {
|
|
189
|
+
reason: "policy_disabled",
|
|
190
|
+
remediation: "Enable browser observation and configure exact CDP endpoint and page origins.",
|
|
191
|
+
};
|
|
192
|
+
};
|
|
142
193
|
const targetAvailability = ({ kind, targetKind, targetOpen, }) => {
|
|
143
|
-
if (!targetOpen)
|
|
194
|
+
if (!targetOpen && (kind === "official-proxy" || kind === "enhanced"))
|
|
144
195
|
return {
|
|
145
196
|
reason: "target_required",
|
|
146
197
|
remediation: "Call open_binary with a supported local target.",
|
|
@@ -159,6 +210,11 @@ const providerAvailability = ({ descriptors, kind, name, }) => {
|
|
|
159
210
|
if (kind === "enhanced")
|
|
160
211
|
return composedAvailability(name, descriptors);
|
|
161
212
|
const descriptor = descriptors.get(name);
|
|
213
|
+
if (descriptor === undefined &&
|
|
214
|
+
(kind === "artifact-provider" ||
|
|
215
|
+
kind === "managed-provider" ||
|
|
216
|
+
kind === "native-provider"))
|
|
217
|
+
return { reason: "available", remediation: null };
|
|
162
218
|
if (descriptor === undefined)
|
|
163
219
|
return {
|
|
164
220
|
reason: "provider_missing",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AnalysisCapabilityUnavailableError } from "../domain/errors.js";
|
|
2
2
|
import { err, ok } from "../domain/result.js";
|
|
3
|
+
import { closeAnalysisClient } from "./AnalysisClientCleanup.js";
|
|
3
4
|
/** Synthetic compatibility identity for a deterministic provider set. */
|
|
4
5
|
export const compositeProviderIdentity = (identities) => ({
|
|
5
6
|
id: `composite:${identities
|
|
@@ -61,6 +62,10 @@ export class CompositeProvider {
|
|
|
61
62
|
clients.set(provider, created);
|
|
62
63
|
return created;
|
|
63
64
|
};
|
|
65
|
+
const closeWithOutcome = async (options) => {
|
|
66
|
+
const outcomes = await Promise.all([...clients.entries()].map(([provider, client]) => closeAnalysisClient(client, provider.identity().id, options)));
|
|
67
|
+
return outcomes.find((outcome) => !outcome.ok) ?? ok(null);
|
|
68
|
+
};
|
|
64
69
|
return {
|
|
65
70
|
execute: (operation, parameters, options) => {
|
|
66
71
|
if (operation === "health")
|
|
@@ -80,8 +85,12 @@ export class CompositeProvider {
|
|
|
80
85
|
runtimeLineageSnapshots: () => [...clients.values()]
|
|
81
86
|
.flatMap((client) => client.runtimeLineageSnapshots?.() ?? [])
|
|
82
87
|
.sort((left, right) => left.provider.id.localeCompare(right.provider.id)),
|
|
88
|
+
requestActivitySnapshots: () => [...clients.values()]
|
|
89
|
+
.flatMap((client) => client.requestActivitySnapshots?.() ?? [])
|
|
90
|
+
.sort((left, right) => left.provider.id.localeCompare(right.provider.id)),
|
|
91
|
+
closeWithOutcome,
|
|
83
92
|
close: async () => {
|
|
84
|
-
await
|
|
93
|
+
await closeWithOutcome();
|
|
85
94
|
},
|
|
86
95
|
};
|
|
87
96
|
}
|
|
@@ -12,6 +12,7 @@ import { err, ok } from "../domain/result.js";
|
|
|
12
12
|
import { loadConfiguredPermissionAuthority } from "./PermissionConfiguration.js";
|
|
13
13
|
import { authorizeFileReadWithDeferredWrite, } from "./DeferredFileAuthorization.js";
|
|
14
14
|
import { REA_WORKFLOW_PROVIDER, workflowAnalysisProfile, } from "./InvestigationProviders.js";
|
|
15
|
+
import { artifactInspectionResultSchema } from "../domain/artifactInspection.js";
|
|
15
16
|
export { runCapabilityStatus, runProviderStatus, } from "./DirectAnalysisStatus.js";
|
|
16
17
|
/**
|
|
17
18
|
* Open one binary, execute one tool, and always release provider resources.
|
|
@@ -60,7 +61,7 @@ const authorizeAnalysis = async (authority, tool, arguments_) => {
|
|
|
60
61
|
cause: result.error,
|
|
61
62
|
}));
|
|
62
63
|
}
|
|
63
|
-
if (
|
|
64
|
+
if (["inventory_artifact", "inspect_artifact"].includes(tool) &&
|
|
64
65
|
arguments_.native_mount_approved === true) {
|
|
65
66
|
const result = await authority.authorize({
|
|
66
67
|
capability: "native_mount",
|
|
@@ -69,7 +70,7 @@ const authorizeAnalysis = async (authority, tool, arguments_) => {
|
|
|
69
70
|
environment_names: [],
|
|
70
71
|
network: "none",
|
|
71
72
|
mount: true,
|
|
72
|
-
operation_identity:
|
|
73
|
+
operation_identity: `${tool}:native_mount`,
|
|
73
74
|
}, "read");
|
|
74
75
|
if (!result.ok)
|
|
75
76
|
return err(result.error instanceof PermissionRequiredError
|
|
@@ -212,6 +213,10 @@ const executeAnalysisTool = async (input) => {
|
|
|
212
213
|
rawResult: result.value.rawResult,
|
|
213
214
|
limitations: result.value.limitations,
|
|
214
215
|
locations: result.value.locations,
|
|
216
|
+
evidenceLinks: tool === "inspect_artifact"
|
|
217
|
+
? artifactInspectionResultSchema.parse(result.value.result)
|
|
218
|
+
.evidence_links
|
|
219
|
+
: [],
|
|
215
220
|
});
|
|
216
221
|
return { output: evidence, evidence };
|
|
217
222
|
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { AnalysisOutputError } from "../domain/errors.js";
|
|
2
|
+
import { parseAddressedPage } from "../domain/hopperValues.js";
|
|
3
|
+
import { err, ok } from "../domain/result.js";
|
|
4
|
+
const FEATURE_SEARCH_SOURCES = [
|
|
5
|
+
["list_strings", "string"],
|
|
6
|
+
["list_procedures", "procedure"],
|
|
7
|
+
];
|
|
8
|
+
const STRING_SEARCH_SOURCES = [
|
|
9
|
+
["list_strings", "string"],
|
|
10
|
+
];
|
|
11
|
+
/** Trace a bounded feature or string literal through provider xrefs. */
|
|
12
|
+
export const traceLiteralFeature = async (call, input, scope, signal) => {
|
|
13
|
+
const sources = scope === "feature" ? FEATURE_SEARCH_SOURCES : STRING_SEARCH_SOURCES;
|
|
14
|
+
const searched = await literalMatches(call, input, sources, signal);
|
|
15
|
+
if (!searched.ok)
|
|
16
|
+
return searched;
|
|
17
|
+
const residual = new Set();
|
|
18
|
+
const traced = await traceReferences(call, {
|
|
19
|
+
matches: searched.value.matches,
|
|
20
|
+
limit: input.limit,
|
|
21
|
+
operationBudget: input.max_operations - searched.value.operations,
|
|
22
|
+
...(signal === undefined ? {} : { signal }),
|
|
23
|
+
});
|
|
24
|
+
if (!traced.ok)
|
|
25
|
+
return traced;
|
|
26
|
+
for (const reason of traced.value.residual)
|
|
27
|
+
residual.add(reason);
|
|
28
|
+
const operations = searched.value.operations + traced.value.operations;
|
|
29
|
+
if (operations >= input.max_operations)
|
|
30
|
+
residual.add("Investigation reached the operation budget.");
|
|
31
|
+
if (searched.value.matches.length >= input.limit)
|
|
32
|
+
residual.add("Literal matches reached the configured limit.");
|
|
33
|
+
return ok({
|
|
34
|
+
query: input.query,
|
|
35
|
+
search_mode: "literal",
|
|
36
|
+
operations_used: operations,
|
|
37
|
+
operation_budget: input.max_operations,
|
|
38
|
+
matches: searched.value.matches,
|
|
39
|
+
references: traced.value.references,
|
|
40
|
+
truncated: residual.size > 0,
|
|
41
|
+
residual_unknowns: [...residual],
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
const literalMatches = async (call, input, sources, signal) => {
|
|
45
|
+
let operations = 0;
|
|
46
|
+
const matches = [];
|
|
47
|
+
const needle = input.case_sensitive ? input.query : input.query.toLowerCase();
|
|
48
|
+
for (const [tool, type] of sources) {
|
|
49
|
+
let offset = 0;
|
|
50
|
+
while (operations < input.max_operations && matches.length < input.limit) {
|
|
51
|
+
operations += 1;
|
|
52
|
+
const result = await call(tool, { offset, limit: 500 }, signal);
|
|
53
|
+
if (!result.ok)
|
|
54
|
+
return result;
|
|
55
|
+
const page = parseAddressedPage(result.value);
|
|
56
|
+
if (!page.ok)
|
|
57
|
+
return page;
|
|
58
|
+
for (const item of page.value.items) {
|
|
59
|
+
const haystack = input.case_sensitive
|
|
60
|
+
? item.name
|
|
61
|
+
: item.name.toLowerCase();
|
|
62
|
+
if (haystack.includes(needle))
|
|
63
|
+
matches.push({ type, address: item.address, value: item.name });
|
|
64
|
+
if (matches.length >= input.limit)
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (!page.value.hasMore || page.value.nextOffset === null)
|
|
68
|
+
break;
|
|
69
|
+
offset = page.value.nextOffset;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return ok({ matches, operations });
|
|
73
|
+
};
|
|
74
|
+
const traceReferences = async (call, request) => {
|
|
75
|
+
let operations = 0;
|
|
76
|
+
const references = [];
|
|
77
|
+
const residual = new Set();
|
|
78
|
+
for (const match of request.matches) {
|
|
79
|
+
if (operations >= request.operationBudget) {
|
|
80
|
+
residual.add("Reference traversal stopped at the operation budget.");
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
operations += 1;
|
|
84
|
+
const xrefs = await call("xrefs", { address: match.address }, request.signal);
|
|
85
|
+
if (!xrefs.ok)
|
|
86
|
+
return xrefs;
|
|
87
|
+
if (!Array.isArray(xrefs.value))
|
|
88
|
+
return err(new AnalysisOutputError("xrefs", "provider returned a non-array result"));
|
|
89
|
+
for (const source of xrefs.value) {
|
|
90
|
+
if (typeof source !== "string")
|
|
91
|
+
return err(new AnalysisOutputError("xrefs", "provider returned a non-address value"));
|
|
92
|
+
if (operations >= request.operationBudget) {
|
|
93
|
+
residual.add("Containing-procedure resolution stopped at the operation budget.");
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
operations += 1;
|
|
97
|
+
const resolved = await call("resolve_containing_procedure", { address: source }, request.signal);
|
|
98
|
+
if (!resolved.ok)
|
|
99
|
+
return resolved;
|
|
100
|
+
references.push({
|
|
101
|
+
target_address: match.address,
|
|
102
|
+
source_address: source,
|
|
103
|
+
containing_procedure: resolved.value,
|
|
104
|
+
});
|
|
105
|
+
if (references.length >= request.limit) {
|
|
106
|
+
residual.add("Reference results reached the configured limit.");
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (references.length >= request.limit)
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
return ok({
|
|
114
|
+
references,
|
|
115
|
+
operations,
|
|
116
|
+
residual: [...residual],
|
|
117
|
+
});
|
|
118
|
+
};
|