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
|
@@ -45,9 +45,9 @@ const captureSummaries = (captures, runtime) => captures.map((capture) => {
|
|
|
45
45
|
kind: capture.kind,
|
|
46
46
|
target_node_id: targetNode.node_id,
|
|
47
47
|
target_key: capture.inspection.target.target_id,
|
|
48
|
-
target_location: capture.
|
|
49
|
-
? capture.inspection.target.url
|
|
50
|
-
: capture.inspection.target.file_path.slice(0, 4_096),
|
|
48
|
+
target_location: ("url" in capture.inspection.target
|
|
49
|
+
? capture.inspection.target.url
|
|
50
|
+
: capture.inspection.target.file_path).slice(0, 4_096),
|
|
51
51
|
frames: capture.inspection.frames.length,
|
|
52
52
|
scripts: capture.inspection.scripts.items.length,
|
|
53
53
|
workers: capture.inspection.workers.length,
|
|
@@ -197,7 +197,7 @@ const runtimeEvidence = (capture, input) => ({
|
|
|
197
197
|
name: capture.evidence.provider.id,
|
|
198
198
|
version: capture.evidence.provider.version ??
|
|
199
199
|
(capture.kind === "browser" ? "2" : "1"),
|
|
200
|
-
operation: capture.
|
|
200
|
+
operation: capture.evidence.operation,
|
|
201
201
|
executable_sha256: null,
|
|
202
202
|
},
|
|
203
203
|
coverage: runtimeSectionCoverage(capture, input.section),
|
|
@@ -260,7 +260,7 @@ const runtimeRelationshipEvidence = (capture, entity) => ({
|
|
|
260
260
|
name: capture.evidence.provider.id,
|
|
261
261
|
version: capture.evidence.provider.version ??
|
|
262
262
|
(capture.kind === "browser" ? "2" : "1"),
|
|
263
|
-
operation: capture.
|
|
263
|
+
operation: capture.evidence.operation,
|
|
264
264
|
executable_sha256: null,
|
|
265
265
|
},
|
|
266
266
|
coverage: runtimeSectionCoverage(capture, entity.section),
|
|
@@ -133,7 +133,7 @@ const layerSummarySchema = z.strictObject({
|
|
|
133
133
|
const captureSummarySchema = z.strictObject({
|
|
134
134
|
evidence_id: evidenceIdSchema,
|
|
135
135
|
capture_sha256: digestSchema,
|
|
136
|
-
kind: z.enum(["browser", "electron"]),
|
|
136
|
+
kind: z.enum(["browser", "electron", "v8-inspector"]),
|
|
137
137
|
target_node_id: nodeIdSchema,
|
|
138
138
|
target_key: boundedTextSchema,
|
|
139
139
|
target_location: boundedTextSchema,
|
|
@@ -5,7 +5,7 @@ import { collectSemanticModuleLink, collectSemanticCallable, collectSemanticRefe
|
|
|
5
5
|
import { currentSemanticScope, resolveSemanticBindingFromScope, semanticScopeId, semanticVariableScope, } from "./javascriptSemanticState.js";
|
|
6
6
|
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
|
7
7
|
import { collectSemanticReturns, resolveSemanticModuleCallables, } from "./javascriptSemanticReturns.js";
|
|
8
|
-
import {
|
|
8
|
+
import { collectJavaScriptDerivedSemantics } from "./javascriptSemanticDerivedAnalysis.js";
|
|
9
9
|
import { compareCodePoints, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
|
|
10
10
|
/** Recover bounded lexical bindings, aliases, constants, and module links. */
|
|
11
11
|
export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
|
|
@@ -28,16 +28,16 @@ export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
|
|
|
28
28
|
const bindings = immutableSemanticBindings(state);
|
|
29
29
|
const callables = collectSemanticReturns(file.program, state, parserPartial);
|
|
30
30
|
const moduleLinks = resolveSemanticModuleCallables(state, callables);
|
|
31
|
-
const
|
|
31
|
+
const derived = collectJavaScriptDerivedSemantics(file.program, state, callables, parserPartial);
|
|
32
32
|
return {
|
|
33
33
|
schema: "JavaScriptSemanticIR",
|
|
34
|
-
schemaVersion:
|
|
34
|
+
schemaVersion: 4,
|
|
35
35
|
scopes: immutableSemanticScopes(state),
|
|
36
36
|
bindings,
|
|
37
37
|
callables,
|
|
38
38
|
references,
|
|
39
39
|
moduleLinks,
|
|
40
|
-
...
|
|
40
|
+
...derived,
|
|
41
41
|
coverage: {
|
|
42
42
|
status: state.limitsReached.size > 0
|
|
43
43
|
? "truncated"
|
|
@@ -59,6 +59,7 @@ export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
|
|
|
59
59
|
"Values and aliases were recovered from inert syntax only; no JavaScript was executed.",
|
|
60
60
|
"Return sites include only direct callable returns; nested callable returns remain separate.",
|
|
61
61
|
"Local call, argument, return, and closure relations are static candidates and do not prove runtime invocation.",
|
|
62
|
+
"Promise ownership covers explicit unshadowed Promise construction, static factories, aggregation, chaining, and await syntax only.",
|
|
62
63
|
"Cross-function mutation and dynamic property resolution remain unknown.",
|
|
63
64
|
],
|
|
64
65
|
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import { reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
|
|
3
|
+
import { resolveSemanticBindingState, semanticResolutionBlocked, } from "./javascriptSemanticState.js";
|
|
4
|
+
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
|
5
|
+
import { range } from "./javascriptStaticAnalysisHelpers.js";
|
|
6
|
+
const EVENT_REGISTER_METHODS = [
|
|
7
|
+
"on",
|
|
8
|
+
"once",
|
|
9
|
+
"addListener",
|
|
10
|
+
"prependListener",
|
|
11
|
+
"prependOnceListener",
|
|
12
|
+
];
|
|
13
|
+
const EVENT_REMOVE_METHODS = [
|
|
14
|
+
"off",
|
|
15
|
+
"removeListener",
|
|
16
|
+
"removeAllListeners",
|
|
17
|
+
];
|
|
18
|
+
const EVENT_METHODS = [
|
|
19
|
+
...EVENT_REGISTER_METHODS,
|
|
20
|
+
...EVENT_REMOVE_METHODS,
|
|
21
|
+
"emit",
|
|
22
|
+
];
|
|
23
|
+
const TIMER_SCHEDULE_METHODS = [
|
|
24
|
+
"setTimeout",
|
|
25
|
+
"setInterval",
|
|
26
|
+
"setImmediate",
|
|
27
|
+
];
|
|
28
|
+
const TIMER_CANCEL_METHODS = [
|
|
29
|
+
"clearTimeout",
|
|
30
|
+
"clearInterval",
|
|
31
|
+
"clearImmediate",
|
|
32
|
+
];
|
|
33
|
+
/** Recover explicit EventEmitter and Node timer candidates from inert syntax. */
|
|
34
|
+
export const collectJavaScriptSemanticAsyncEffects = (program, state, callables) => {
|
|
35
|
+
const events = [];
|
|
36
|
+
const timers = [];
|
|
37
|
+
const callableIds = new Set(callables.map(({ callableId }) => callableId));
|
|
38
|
+
const callableStack = [];
|
|
39
|
+
traverseJavaScriptAst(program, {
|
|
40
|
+
enter: (node, parent) => {
|
|
41
|
+
const callableId = semanticCallableIdForNode(node);
|
|
42
|
+
if (callableId !== null && callableIds.has(callableId))
|
|
43
|
+
callableStack.push(callableId);
|
|
44
|
+
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
|
45
|
+
return;
|
|
46
|
+
const ownerCallableId = callableStack.at(-1) ?? null;
|
|
47
|
+
collectEvent(node, ownerCallableId, state, events);
|
|
48
|
+
collectTimer({ node, parent, ownerCallableId, state, output: timers });
|
|
49
|
+
},
|
|
50
|
+
exit: (node) => {
|
|
51
|
+
const callableId = semanticCallableIdForNode(node);
|
|
52
|
+
if (callableId !== null && callableStack.at(-1) === callableId)
|
|
53
|
+
callableStack.pop();
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
eventOperations: events,
|
|
58
|
+
timerOperations: resolveTimers(timers),
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const collectEvent = (node, ownerCallableId, state, output) => {
|
|
62
|
+
const member = memberCallee(node);
|
|
63
|
+
if (member === null)
|
|
64
|
+
return;
|
|
65
|
+
const method = EVENT_METHODS.find((candidate) => candidate ===
|
|
66
|
+
semanticStaticPropertyName(member.property, member.computed));
|
|
67
|
+
if (method === undefined)
|
|
68
|
+
return;
|
|
69
|
+
const kind = eventKind(method);
|
|
70
|
+
if (output.length >= state.limits.maxEventOperations) {
|
|
71
|
+
reachSemanticLimit(state, "maxEventOperations");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const eventArgument = node.arguments[0];
|
|
75
|
+
const listenerArgument = node.arguments[1];
|
|
76
|
+
const emitter = emitterIdentity(member.object, state);
|
|
77
|
+
const eventName = literalString(eventArgument);
|
|
78
|
+
output.push({
|
|
79
|
+
eventId: `event:${kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
|
|
80
|
+
kind,
|
|
81
|
+
method,
|
|
82
|
+
location: range(node),
|
|
83
|
+
ownerCallableId,
|
|
84
|
+
emitterKey: emitter.key,
|
|
85
|
+
emitterBindingId: emitter.bindingId,
|
|
86
|
+
eventName,
|
|
87
|
+
listenerBindingId: bindingId(listenerArgument, state),
|
|
88
|
+
listenerLocation: t.isNode(listenerArgument)
|
|
89
|
+
? range(listenerArgument)
|
|
90
|
+
: null,
|
|
91
|
+
resolution: eventName === null
|
|
92
|
+
? "unresolved"
|
|
93
|
+
: emitter.complete
|
|
94
|
+
? "complete"
|
|
95
|
+
: "partial",
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const eventKind = (method) => {
|
|
99
|
+
if (EVENT_REGISTER_METHODS.some((candidate) => candidate === method))
|
|
100
|
+
return "register";
|
|
101
|
+
if (EVENT_REMOVE_METHODS.some((candidate) => candidate === method))
|
|
102
|
+
return "remove";
|
|
103
|
+
return "dispatch";
|
|
104
|
+
};
|
|
105
|
+
const emitterIdentity = (node, state) => {
|
|
106
|
+
if (t.isIdentifier(node)) {
|
|
107
|
+
const binding = resolveSemanticBindingState(state, node, node.name);
|
|
108
|
+
if (binding !== undefined)
|
|
109
|
+
return {
|
|
110
|
+
key: `binding:${binding.bindingId}`,
|
|
111
|
+
bindingId: binding.bindingId,
|
|
112
|
+
complete: true,
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
key: `global:${node.name}`,
|
|
116
|
+
bindingId: null,
|
|
117
|
+
complete: !semanticResolutionBlocked(state, node, node.name),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (t.isThisExpression(node))
|
|
121
|
+
return { key: "this", bindingId: null, complete: true };
|
|
122
|
+
return {
|
|
123
|
+
key: `expression:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
|
|
124
|
+
bindingId: null,
|
|
125
|
+
complete: false,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const collectTimer = (input) => {
|
|
129
|
+
const { node, parent, ownerCallableId, state, output } = input;
|
|
130
|
+
const method = timerMethod(node.callee, state);
|
|
131
|
+
if (method === null)
|
|
132
|
+
return;
|
|
133
|
+
if (output.length >= state.limits.maxTimerOperations) {
|
|
134
|
+
reachSemanticLimit(state, "maxTimerOperations");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const kind = TIMER_SCHEDULE_METHODS.some((candidate) => candidate === method)
|
|
138
|
+
? "schedule"
|
|
139
|
+
: "cancel";
|
|
140
|
+
output.push({
|
|
141
|
+
node,
|
|
142
|
+
method,
|
|
143
|
+
kind,
|
|
144
|
+
ownerCallableId,
|
|
145
|
+
handleBindingId: kind === "schedule"
|
|
146
|
+
? assignedBindingId(parent, node, state)
|
|
147
|
+
: bindingId(node.arguments[0], state),
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
const resolveTimers = (candidates) => {
|
|
151
|
+
const schedulesByBinding = new Map();
|
|
152
|
+
for (const candidate of candidates) {
|
|
153
|
+
if (candidate.kind !== "schedule" || candidate.handleBindingId === null)
|
|
154
|
+
continue;
|
|
155
|
+
const existing = schedulesByBinding.get(candidate.handleBindingId) ?? [];
|
|
156
|
+
existing.push(candidate);
|
|
157
|
+
schedulesByBinding.set(candidate.handleBindingId, existing);
|
|
158
|
+
}
|
|
159
|
+
return candidates.map((candidate) => {
|
|
160
|
+
const linked = candidate.kind === "cancel" && candidate.handleBindingId !== null
|
|
161
|
+
? (schedulesByBinding.get(candidate.handleBindingId) ?? [])
|
|
162
|
+
: [];
|
|
163
|
+
return {
|
|
164
|
+
timerId: timerId(candidate),
|
|
165
|
+
kind: candidate.kind,
|
|
166
|
+
method: candidate.method,
|
|
167
|
+
location: range(candidate.node),
|
|
168
|
+
ownerCallableId: candidate.ownerCallableId,
|
|
169
|
+
handleBindingId: candidate.handleBindingId,
|
|
170
|
+
linkedTimerId: linked.length === 1 && linked[0] !== undefined
|
|
171
|
+
? timerId(linked[0])
|
|
172
|
+
: null,
|
|
173
|
+
delayMilliseconds: timerDelay(candidate),
|
|
174
|
+
resolution: candidate.kind === "schedule"
|
|
175
|
+
? "complete"
|
|
176
|
+
: linked.length === 1
|
|
177
|
+
? "complete"
|
|
178
|
+
: linked.length > 1
|
|
179
|
+
? "partial"
|
|
180
|
+
: "unresolved",
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
const timerMethod = (callee, state) => {
|
|
185
|
+
if (t.isIdentifier(callee)) {
|
|
186
|
+
const binding = resolveSemanticBindingState(state, callee, callee.name);
|
|
187
|
+
if (binding === undefined) {
|
|
188
|
+
const method = admittedTimerMethod(callee.name);
|
|
189
|
+
if (method === null)
|
|
190
|
+
return null;
|
|
191
|
+
return semanticResolutionBlocked(state, callee, callee.name)
|
|
192
|
+
? null
|
|
193
|
+
: method;
|
|
194
|
+
}
|
|
195
|
+
return timerMethodFromBinding(binding);
|
|
196
|
+
}
|
|
197
|
+
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
|
|
198
|
+
return null;
|
|
199
|
+
const method = admittedTimerMethod(semanticStaticPropertyName(callee.property, callee.computed));
|
|
200
|
+
return method !== null &&
|
|
201
|
+
t.isIdentifier(callee.object) &&
|
|
202
|
+
timerNamespaceBinding(resolveSemanticBindingState(state, callee.object, callee.object.name))
|
|
203
|
+
? method
|
|
204
|
+
: null;
|
|
205
|
+
};
|
|
206
|
+
const admittedTimerMethod = (value) => [...TIMER_SCHEDULE_METHODS, ...TIMER_CANCEL_METHODS].find((candidate) => candidate === value) ?? null;
|
|
207
|
+
const timerMethodFromBinding = (binding) => {
|
|
208
|
+
for (const { specifier, importedPath } of binding.directOrigins) {
|
|
209
|
+
if (!timerModule(specifier))
|
|
210
|
+
continue;
|
|
211
|
+
const method = admittedTimerMethod(importedPath.at(-1) ?? "");
|
|
212
|
+
if (method !== null)
|
|
213
|
+
return method;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
};
|
|
217
|
+
const timerNamespaceBinding = (binding) => binding?.directOrigins.some(({ specifier, importedPath }) => timerModule(specifier) && importedPath.length === 0) ?? false;
|
|
218
|
+
const timerModule = (specifier) => specifier === "timers" || specifier === "node:timers";
|
|
219
|
+
const assignedBindingId = (parent, node, state) => {
|
|
220
|
+
const identifier = t.isVariableDeclarator(parent) &&
|
|
221
|
+
parent.init === node &&
|
|
222
|
+
t.isIdentifier(parent.id)
|
|
223
|
+
? parent.id
|
|
224
|
+
: t.isAssignmentExpression(parent) &&
|
|
225
|
+
parent.right === node &&
|
|
226
|
+
t.isIdentifier(parent.left)
|
|
227
|
+
? parent.left
|
|
228
|
+
: null;
|
|
229
|
+
return identifier === null
|
|
230
|
+
? null
|
|
231
|
+
: (resolveSemanticBindingState(state, identifier, identifier.name)
|
|
232
|
+
?.bindingId ?? null);
|
|
233
|
+
};
|
|
234
|
+
const bindingId = (node, state) => t.isIdentifier(node)
|
|
235
|
+
? (resolveSemanticBindingState(state, node, node.name)?.bindingId ?? null)
|
|
236
|
+
: null;
|
|
237
|
+
const literalString = (node) => {
|
|
238
|
+
if (t.isStringLiteral(node))
|
|
239
|
+
return node.value;
|
|
240
|
+
if (t.isTemplateLiteral(node) && node.expressions.length === 0)
|
|
241
|
+
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? null;
|
|
242
|
+
return null;
|
|
243
|
+
};
|
|
244
|
+
const timerDelay = (candidate) => {
|
|
245
|
+
if (candidate.kind !== "schedule" || candidate.method === "setImmediate")
|
|
246
|
+
return null;
|
|
247
|
+
const delay = candidate.node.arguments[1];
|
|
248
|
+
return t.isNumericLiteral(delay) ? delay.value : null;
|
|
249
|
+
};
|
|
250
|
+
const timerId = (candidate) => `timer:${candidate.kind}:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`;
|
|
251
|
+
const memberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
|
|
252
|
+
? node.callee
|
|
253
|
+
: null;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import { reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
|
|
3
|
+
import { resolveSemanticBindingState, } from "./javascriptSemanticState.js";
|
|
4
|
+
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
|
5
|
+
import { compareCodePoints, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
|
|
6
|
+
const CHILD_PROCESS_METHODS = ["spawn", "exec", "execFile", "fork"];
|
|
7
|
+
const CHILD_LISTENER_METHODS = ["on", "once", "addListener"];
|
|
8
|
+
/** Recover explicit asynchronous node:child_process ownership candidates. */
|
|
9
|
+
export const collectJavaScriptSemanticChildProcesses = (program, state, callables) => {
|
|
10
|
+
const candidates = collectSpawns(program, state, callables);
|
|
11
|
+
const childProcessSpawns = candidates.map((candidate) => immutableSpawn(candidate));
|
|
12
|
+
return {
|
|
13
|
+
childProcessSpawns,
|
|
14
|
+
childProcessInteractions: collectInteractions(program, state, callables, candidates),
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
const collectSpawns = (program, state, callables) => {
|
|
18
|
+
const output = [];
|
|
19
|
+
const admittedCallables = new Set(callables.map(({ callableId }) => callableId));
|
|
20
|
+
const callableStack = [];
|
|
21
|
+
traverseJavaScriptAst(program, {
|
|
22
|
+
enter: (node, parent) => {
|
|
23
|
+
const callableId = semanticCallableIdForNode(node);
|
|
24
|
+
if (callableId !== null && admittedCallables.has(callableId))
|
|
25
|
+
callableStack.push(callableId);
|
|
26
|
+
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
|
27
|
+
return;
|
|
28
|
+
const method = childProcessMethod(node.callee, state);
|
|
29
|
+
if (method === null)
|
|
30
|
+
return;
|
|
31
|
+
if (operationLimitReached(output.length, state))
|
|
32
|
+
return;
|
|
33
|
+
output.push({
|
|
34
|
+
node,
|
|
35
|
+
method,
|
|
36
|
+
ownerCallableId: callableStack.at(-1) ?? null,
|
|
37
|
+
resultBindingId: assignedBindingId(parent, node, state),
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
exit: (node) => popCallable(node, callableStack),
|
|
41
|
+
});
|
|
42
|
+
return output;
|
|
43
|
+
};
|
|
44
|
+
const immutableSpawn = (candidate) => {
|
|
45
|
+
const options = spawnOptions(candidate);
|
|
46
|
+
const command = literalString(candidate.node.arguments[0]);
|
|
47
|
+
return {
|
|
48
|
+
processId: spawnId(candidate),
|
|
49
|
+
method: candidate.method,
|
|
50
|
+
location: range(candidate.node),
|
|
51
|
+
ownerCallableId: candidate.ownerCallableId,
|
|
52
|
+
resultBindingId: candidate.resultBindingId,
|
|
53
|
+
command,
|
|
54
|
+
argvCount: spawnArgvCount(candidate),
|
|
55
|
+
environmentSupplied: objectHasProperty(options, "env"),
|
|
56
|
+
stdioMode: stdioMode(options),
|
|
57
|
+
resolution: command === null ? "partial" : "complete",
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const collectInteractions = (program, state, callables, spawns) => {
|
|
61
|
+
const output = [];
|
|
62
|
+
const callableIds = new Set(callables.map(({ callableId }) => callableId));
|
|
63
|
+
const callableStack = [];
|
|
64
|
+
const spawnByNode = new WeakMap();
|
|
65
|
+
const spawnsByBinding = new Map();
|
|
66
|
+
for (const spawn of spawns) {
|
|
67
|
+
spawnByNode.set(spawn.node, spawn);
|
|
68
|
+
if (spawn.resultBindingId === null)
|
|
69
|
+
continue;
|
|
70
|
+
const existing = spawnsByBinding.get(spawn.resultBindingId) ?? [];
|
|
71
|
+
existing.push(spawn);
|
|
72
|
+
spawnsByBinding.set(spawn.resultBindingId, existing);
|
|
73
|
+
}
|
|
74
|
+
const context = {
|
|
75
|
+
state,
|
|
76
|
+
spawnByNode,
|
|
77
|
+
spawnsByBinding,
|
|
78
|
+
};
|
|
79
|
+
traverseJavaScriptAst(program, {
|
|
80
|
+
enter: (node) => {
|
|
81
|
+
const callableId = semanticCallableIdForNode(node);
|
|
82
|
+
if (callableId !== null && callableIds.has(callableId))
|
|
83
|
+
callableStack.push(callableId);
|
|
84
|
+
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
|
85
|
+
return;
|
|
86
|
+
const interaction = childInteraction(node, callableStack.at(-1) ?? null, context);
|
|
87
|
+
if (interaction === null)
|
|
88
|
+
return;
|
|
89
|
+
if (operationLimitReached(spawns.length + output.length, state))
|
|
90
|
+
return;
|
|
91
|
+
output.push(interaction);
|
|
92
|
+
},
|
|
93
|
+
exit: (node) => popCallable(node, callableStack),
|
|
94
|
+
});
|
|
95
|
+
return output;
|
|
96
|
+
};
|
|
97
|
+
const childInteraction = (node, ownerCallableId, context) => {
|
|
98
|
+
const { state, spawnByNode, spawnsByBinding } = context;
|
|
99
|
+
const member = memberCallee(node);
|
|
100
|
+
if (member === null)
|
|
101
|
+
return null;
|
|
102
|
+
const method = semanticStaticPropertyName(member.property, member.computed);
|
|
103
|
+
const binding = objectBinding(member.object, state);
|
|
104
|
+
const linked = resolveChildCandidates(member.object, binding, spawnByNode, spawnsByBinding);
|
|
105
|
+
if (linked.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
const classification = classifyInteraction(method, node.arguments[0]);
|
|
108
|
+
if (classification === null)
|
|
109
|
+
return null;
|
|
110
|
+
const { kind, interactionMethod, eventName } = classification;
|
|
111
|
+
const listener = node.arguments[1];
|
|
112
|
+
return {
|
|
113
|
+
interactionId: `child:${kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
|
|
114
|
+
kind,
|
|
115
|
+
method: interactionMethod,
|
|
116
|
+
location: range(node),
|
|
117
|
+
ownerCallableId,
|
|
118
|
+
processBindingId: binding?.bindingId ?? null,
|
|
119
|
+
linkedProcessIds: linked
|
|
120
|
+
.map((candidate) => spawnId(candidate))
|
|
121
|
+
.sort(compareCodePoints),
|
|
122
|
+
eventName: kind === "listener" ? eventName : null,
|
|
123
|
+
signalName: kind === "signal"
|
|
124
|
+
? (literalString(node.arguments[0]) ?? "SIGTERM")
|
|
125
|
+
: null,
|
|
126
|
+
listenerLocation: kind === "listener" && t.isNode(listener) ? range(listener) : null,
|
|
127
|
+
resolution: linked.length === 1 ? "complete" : "partial",
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
const classifyInteraction = (method, argument) => {
|
|
131
|
+
if (method === "kill")
|
|
132
|
+
return { kind: "signal", interactionMethod: "kill", eventName: null };
|
|
133
|
+
const listenerMethod = CHILD_LISTENER_METHODS.find((candidate) => candidate === method);
|
|
134
|
+
const eventName = literalString(argument);
|
|
135
|
+
return listenerMethod !== undefined &&
|
|
136
|
+
(eventName === "exit" || eventName === "error")
|
|
137
|
+
? { kind: "listener", interactionMethod: listenerMethod, eventName }
|
|
138
|
+
: null;
|
|
139
|
+
};
|
|
140
|
+
const childProcessMethod = (callee, state) => {
|
|
141
|
+
if (t.isIdentifier(callee)) {
|
|
142
|
+
const binding = resolveSemanticBindingState(state, callee, callee.name);
|
|
143
|
+
return binding === undefined ? null : methodFromBinding(binding);
|
|
144
|
+
}
|
|
145
|
+
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
|
|
146
|
+
return null;
|
|
147
|
+
const method = admittedChildMethod(semanticStaticPropertyName(callee.property, callee.computed));
|
|
148
|
+
if (method === null || !t.isIdentifier(callee.object))
|
|
149
|
+
return null;
|
|
150
|
+
const binding = resolveSemanticBindingState(state, callee.object, callee.object.name);
|
|
151
|
+
return childProcessNamespace(binding) ? method : null;
|
|
152
|
+
};
|
|
153
|
+
const methodFromBinding = (binding) => {
|
|
154
|
+
for (const { specifier, importedPath } of binding.directOrigins) {
|
|
155
|
+
if (!childProcessModule(specifier))
|
|
156
|
+
continue;
|
|
157
|
+
const method = admittedChildMethod(importedPath.at(-1) ?? "");
|
|
158
|
+
if (method !== null)
|
|
159
|
+
return method;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
};
|
|
163
|
+
const admittedChildMethod = (value) => CHILD_PROCESS_METHODS.find((candidate) => candidate === value) ?? null;
|
|
164
|
+
const childProcessNamespace = (binding) => binding?.directOrigins.some(({ specifier, importedPath }) => childProcessModule(specifier) && importedPath.length === 0) ?? false;
|
|
165
|
+
const childProcessModule = (specifier) => specifier === "child_process" || specifier === "node:child_process";
|
|
166
|
+
const resolveChildCandidates = (node, binding, spawnByNode, spawnsByBinding) => {
|
|
167
|
+
const direct = spawnByNode.get(node);
|
|
168
|
+
if (direct !== undefined)
|
|
169
|
+
return [direct];
|
|
170
|
+
return binding === undefined
|
|
171
|
+
? []
|
|
172
|
+
: (spawnsByBinding.get(binding.bindingId) ?? []);
|
|
173
|
+
};
|
|
174
|
+
const spawnOptions = (candidate) => {
|
|
175
|
+
const index = candidate.method === "exec" ? 1 : 2;
|
|
176
|
+
const value = candidate.node.arguments[index];
|
|
177
|
+
return t.isObjectExpression(value) ? value : null;
|
|
178
|
+
};
|
|
179
|
+
const spawnArgvCount = (candidate) => {
|
|
180
|
+
if (candidate.method === "exec")
|
|
181
|
+
return null;
|
|
182
|
+
const args = candidate.node.arguments[1];
|
|
183
|
+
return t.isArrayExpression(args) ? args.elements.length : null;
|
|
184
|
+
};
|
|
185
|
+
const objectHasProperty = (object, name) => objectProperty(object, name) !== null;
|
|
186
|
+
const stdioMode = (options) => {
|
|
187
|
+
const property = objectProperty(options, "stdio");
|
|
188
|
+
if (property === null || !t.isObjectProperty(property))
|
|
189
|
+
return "default";
|
|
190
|
+
if (t.isStringLiteral(property.value))
|
|
191
|
+
return property.value.value;
|
|
192
|
+
if (t.isArrayExpression(property.value))
|
|
193
|
+
return "array";
|
|
194
|
+
return "dynamic";
|
|
195
|
+
};
|
|
196
|
+
const objectProperty = (object, name) => object?.properties.find((property) => (t.isObjectMethod(property) || t.isObjectProperty(property)) &&
|
|
197
|
+
propertyName(property.key) === name) ?? null;
|
|
198
|
+
const objectBinding = (node, state) => t.isIdentifier(node)
|
|
199
|
+
? resolveSemanticBindingState(state, node, node.name)
|
|
200
|
+
: undefined;
|
|
201
|
+
const assignedBindingId = (parent, node, state) => {
|
|
202
|
+
const identifier = t.isVariableDeclarator(parent) &&
|
|
203
|
+
parent.init === node &&
|
|
204
|
+
t.isIdentifier(parent.id)
|
|
205
|
+
? parent.id
|
|
206
|
+
: t.isAssignmentExpression(parent) &&
|
|
207
|
+
parent.right === node &&
|
|
208
|
+
t.isIdentifier(parent.left)
|
|
209
|
+
? parent.left
|
|
210
|
+
: null;
|
|
211
|
+
return identifier === null
|
|
212
|
+
? null
|
|
213
|
+
: (resolveSemanticBindingState(state, identifier, identifier.name)
|
|
214
|
+
?.bindingId ?? null);
|
|
215
|
+
};
|
|
216
|
+
const literalString = (node) => {
|
|
217
|
+
if (t.isStringLiteral(node))
|
|
218
|
+
return node.value;
|
|
219
|
+
if (t.isTemplateLiteral(node) && node.expressions.length === 0)
|
|
220
|
+
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? null;
|
|
221
|
+
return null;
|
|
222
|
+
};
|
|
223
|
+
const operationLimitReached = (retained, state) => {
|
|
224
|
+
if (retained < state.limits.maxChildProcessOperations)
|
|
225
|
+
return false;
|
|
226
|
+
reachSemanticLimit(state, "maxChildProcessOperations");
|
|
227
|
+
return true;
|
|
228
|
+
};
|
|
229
|
+
const popCallable = (node, stack) => {
|
|
230
|
+
const callableId = semanticCallableIdForNode(node);
|
|
231
|
+
if (callableId !== null && stack.at(-1) === callableId)
|
|
232
|
+
stack.pop();
|
|
233
|
+
};
|
|
234
|
+
const spawnId = (candidate) => `child:spawn:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`;
|
|
235
|
+
const memberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
|
|
236
|
+
? node.callee
|
|
237
|
+
: null;
|