rea-agents 1.6.0 → 2.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 +157 -66
- package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
- package/bridge/hopper_bridge.py +13 -3
- package/dist/application/AbortablePromise.js +25 -0
- package/dist/application/AnalysisProvider.js +3 -0
- package/dist/application/AnalysisProviderEvaluation.js +60 -0
- package/dist/application/AnalysisProviderRegistry.js +228 -0
- package/dist/application/AnalysisSnapshotCache.js +41 -21
- package/dist/application/AnalysisSnapshotFiles.js +5 -6
- package/dist/application/BinarySession.js +218 -101
- package/dist/application/CapabilityInventory.js +18 -0
- package/dist/application/CompositeProvider.js +20 -8
- package/dist/application/CrossVersionInvestigation.js +10 -6
- package/dist/application/DirectAnalysis.js +49 -35
- package/dist/application/Doctor.js +110 -8
- package/dist/application/ElectronBoundaryAnalysis.js +96 -0
- package/dist/application/ElectronBoundaryGraph.js +9 -0
- package/dist/application/ElectronBoundaryGraphContext.js +62 -0
- package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
- package/dist/application/ElectronBoundaryGraphNative.js +86 -0
- package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
- package/dist/application/EnhancedTools.js +39 -14
- package/dist/application/InvestigationProviders.js +41 -0
- package/dist/application/JavaScriptApplicationEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
- package/dist/application/JavaScriptApplicationService.js +74 -0
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
- package/dist/application/JavaScriptArtifactAnalysis.js +305 -0
- package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
- package/dist/application/JavaScriptArtifactFiles.js +231 -0
- package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
- package/dist/application/JavaScriptArtifactGraphBuilder.js +144 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
- package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
- package/dist/application/JavaScriptArtifactGraphEvidence.js +99 -0
- package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
- package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +77 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +18 -0
- package/dist/application/JavaScriptReplayPlanning.js +201 -0
- package/dist/application/JavaScriptReplayService.js +266 -0
- package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
- package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
- package/dist/application/MacHopper.js +20 -4
- package/dist/application/ManagedApplicationGraphService.js +55 -0
- package/dist/application/ManagedMemberComparisonService.js +122 -0
- package/dist/application/ManagedNativeVerificationService.js +39 -0
- package/dist/application/ManagedReconstructionService.js +58 -0
- package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
- package/dist/application/PermissionAuthority.js +1 -1
- package/dist/application/ProcessCaptureLifecycle.js +1 -1
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProcessSampling.js +16 -12
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/SessionProviderRouter.js +219 -0
- package/dist/application/Setup.js +84 -60
- package/dist/application/SetupClients.js +1 -1
- package/dist/application/SetupPlan.js +9 -2
- package/dist/application/SupportedClients.js +40 -16
- package/dist/application/Upgrade.js +1 -1
- package/dist/application/runtime.js +11 -6
- package/dist/artifacts/AsarArtifactReader.js +23 -3
- package/dist/browser/CdpBrowserProvider.js +4 -2
- package/dist/browser/CdpCaptureEvents.js +49 -0
- package/dist/browser/CdpElectronInspection.js +106 -162
- package/dist/browser/CdpElectronProvider.js +9 -3
- package/dist/browser/CdpElectronScriptEvents.js +69 -0
- package/dist/browser/CdpElectronScripts.js +94 -0
- package/dist/browser/CdpElectronWorkers.js +56 -0
- package/dist/browser/CdpEndpoint.js +14 -1
- package/dist/browser/CdpPageCapture.js +8 -4
- package/dist/catalogIdentity.js +6 -42
- package/dist/cli.js +446 -34
- package/dist/cliApplicationCommands.js +62 -0
- package/dist/cliBrowserAdvancedCommands.js +5 -4
- package/dist/cliBrowserCommands.js +5 -4
- package/dist/cliCommandNames.js +56 -0
- package/dist/cliElectronCommands.js +106 -3
- package/dist/cliEvidenceCommands.js +4 -3
- package/dist/cliInvestigationCommands.js +2 -1
- package/dist/cliJsonInput.js +54 -0
- package/dist/cliPolicyCommands.js +2 -1
- package/dist/cliProcessCommands.js +3 -2
- package/dist/config.js +101 -2
- package/dist/contracts/applicationToolContracts.js +91 -0
- package/dist/contracts/artifactToolContracts.js +5 -9
- package/dist/contracts/browserToolContracts.js +14 -62
- package/dist/contracts/electronToolContracts.js +45 -19
- package/dist/contracts/enhancedInputs.js +2 -2
- package/dist/contracts/errorSchemas.js +1 -0
- package/dist/contracts/investigationExamples.js +0 -6
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
- package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
- package/dist/contracts/managedToolContracts.js +153 -0
- package/dist/contracts/managedWorkflowExamples.js +346 -0
- package/dist/contracts/managedWorkflowToolContracts.js +203 -0
- package/dist/contracts/nativeToolContracts.js +2 -6
- package/dist/contracts/promptContracts.js +18 -2
- package/dist/contracts/providerSelection.js +28 -0
- package/dist/contracts/sessionLifecycleInputs.js +2 -0
- package/dist/contracts/toolContractExamples.js +10 -25
- package/dist/contracts/toolContracts.js +69 -65
- package/dist/contracts/toolEffects.js +160 -0
- package/dist/contracts/toolOutputSchemas.js +152 -42
- package/dist/doctorRuntime.js +47 -0
- package/dist/domain/analysisProfile.js +78 -0
- package/dist/domain/analysisSnapshot.js +92 -46
- package/dist/domain/artifactComparison.js +3 -4
- package/dist/domain/artifactGraph.js +1 -0
- package/dist/domain/binaryTarget.js +4 -29
- package/dist/domain/browserObservation.js +3 -0
- package/dist/domain/bundleComparison.js +4 -4
- package/dist/domain/electronObservation.js +13 -0
- package/dist/domain/electronStaticAnalysis.js +9 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
- package/dist/domain/electronStaticAnalysisIpc.js +171 -0
- package/dist/domain/electronStaticAnalysisNative.js +164 -0
- package/dist/domain/electronStaticAnalysisTypes.js +1 -0
- package/dist/domain/electronStaticAnalysisValues.js +103 -0
- package/dist/domain/errors.js +148 -7
- package/dist/domain/evidence.js +44 -5
- package/dist/domain/evidenceBundle.js +9 -7
- package/dist/domain/functionComparison.js +9 -3
- package/dist/domain/functionComparisonNormalization.js +37 -0
- package/dist/domain/functionComparisonSchemas.js +3 -8
- package/dist/domain/functionDossierEvidence.js +4 -1
- package/dist/domain/hopperValues.js +66 -9
- package/dist/domain/inputIssueProjection.js +52 -0
- package/dist/domain/investigationWorkspace.js +2 -1
- package/dist/domain/javascriptApplicationAnalysis.js +155 -0
- package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +379 -0
- package/dist/domain/javascriptApplicationGraph.js +282 -0
- package/dist/domain/javascriptApplicationGraphSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
- package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionItems.js +221 -0
- package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
- package/dist/domain/javascriptAstFingerprint.js +143 -0
- package/dist/domain/javascriptFeatureSeed.js +140 -0
- package/dist/domain/javascriptFeatureTrace.js +232 -0
- package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
- package/dist/domain/javascriptFeatureTraversal.js +88 -0
- package/dist/domain/javascriptNativeHandoff.js +85 -0
- package/dist/domain/javascriptReplay.js +361 -0
- package/dist/domain/javascriptRuntimeLoadState.js +106 -0
- package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
- package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
- package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
- package/dist/domain/javascriptStaticAnalysis.js +346 -0
- package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +274 -0
- package/dist/domain/javascriptStaticAnalysisState.js +23 -0
- package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
- package/dist/domain/managedApplicationGraph.js +556 -0
- package/dist/domain/managedArtifact.js +444 -0
- package/dist/domain/managedMemberComparison.js +565 -0
- package/dist/domain/managedNativeVerification.js +408 -0
- package/dist/domain/managedReconstruction.js +183 -0
- package/dist/domain/managedRuntimeCorrelation.js +175 -0
- package/dist/domain/reconstructionVerificationSchemas.js +1 -3
- package/dist/dotnet/ManagedArtifactInspector.js +318 -0
- package/dist/dotnet/ManagedMemberInspector.js +887 -0
- package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
- package/dist/dotnet/ManagedMetadataInventory.js +368 -0
- package/dist/dotnet/ManagedMetadataLayout.js +322 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
- package/dist/dotnet/ManagedPeReader.js +157 -0
- package/dist/dotnet/ManagedReaderFailure.js +10 -0
- package/dist/dotnet/ManagedStaticProvider.js +203 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
- package/dist/ghidra/GhidraClient.js +400 -0
- package/dist/ghidra/GhidraClientTypes.js +1 -0
- package/dist/ghidra/GhidraDefaults.js +20 -0
- package/dist/ghidra/GhidraDiagnostics.js +43 -0
- package/dist/ghidra/GhidraDoctor.js +59 -0
- package/dist/ghidra/GhidraFunctionValues.js +264 -0
- package/dist/ghidra/GhidraInstallation.js +239 -0
- package/dist/ghidra/GhidraInventoryValues.js +217 -0
- package/dist/ghidra/GhidraLauncher.js +139 -0
- package/dist/ghidra/GhidraProvider.js +366 -0
- package/dist/ghidra/GhidraRequestQueue.js +98 -0
- package/dist/ghidra/GhidraResponseBuffer.js +31 -0
- package/dist/ghidra/GhidraResponseRouter.js +24 -0
- package/dist/ghidra/GhidraSessionError.js +39 -0
- package/dist/ghidra/GhidraSessionValues.js +62 -0
- package/dist/ghidra/GhidraSocketConnection.js +46 -0
- package/dist/ghidra/protocol.js +69 -0
- package/dist/hopper/BridgeLauncher.js +19 -54
- package/dist/hopper/HopperAnalysisProfile.js +106 -0
- package/dist/hopper/HopperClient.js +154 -162
- package/dist/hopper/HopperProvider.js +99 -7
- package/dist/hopper/HopperSessionValues.js +27 -0
- package/dist/hopper/HopperSocketConnection.js +31 -0
- package/dist/main.js +14 -0
- package/dist/native/NativeMacOSProvider.js +3 -1
- package/dist/process/PendingOperations.js +59 -0
- package/dist/process/PrivateRuntimeRoot.js +33 -0
- package/dist/process/ProviderDeadline.js +95 -0
- package/dist/process/ProviderProcess.js +275 -0
- package/dist/replay/JavaScriptReplayWorker.js +286 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
- package/dist/replay/LinuxRuntimeClosure.js +46 -0
- package/dist/replay/LinuxSeccompPolicy.js +40 -0
- package/dist/replay/ReplayWorkerProtocol.js +55 -0
- package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
- package/dist/server/createServer.js +53 -0
- package/dist/server/promptCompletion.js +9 -3
- package/dist/server/registerApplicationTools.js +115 -0
- package/dist/server/registerArtifactComparisonTool.js +16 -9
- package/dist/server/registerBrowserTools.js +33 -8
- package/dist/server/registerBundleComparisonTool.js +17 -20
- package/dist/server/registerElectronTools.js +45 -3
- package/dist/server/registerEnhancedTools.js +67 -19
- package/dist/server/registerEvidenceTools.js +5 -1
- package/dist/server/registerFunctionComparisonTool.js +14 -7
- package/dist/server/registerInvestigationTools.js +21 -17
- package/dist/server/registerManagedTools.js +6 -0
- package/dist/server/registerManagedWorkflowTools.js +277 -0
- package/dist/server/registerOfficialTools.js +11 -8
- package/dist/server/registerProcessComparisonTool.js +25 -47
- package/dist/server/registerSessionStatusTool.js +7 -7
- package/dist/server/registerSessionTools.js +57 -87
- package/dist/server/sessionEvidence.js +14 -19
- package/dist/server/toolInputValidation.js +77 -0
- package/dist/server/toolRegistrationOptions.js +79 -2
- package/dist/server/toolResult.js +25 -17
- package/package.json +17 -7
- package/scripts/rea.mjs +36 -2
- package/skills/rea-analysis/SKILL.md +59 -3
- /package/dist/{application → process}/ProcessOwnership.js +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import canonicalize from "canonicalize";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
const REPLAY_POLICY_VERSION = "linux-bwrap-systemd-v1";
|
|
4
|
+
const REPLAY_POLICY_DOCUMENT = Object.freeze({
|
|
5
|
+
version: REPLAY_POLICY_VERSION,
|
|
6
|
+
namespaces: ["user", "pid", "network", "ipc", "uts", "cgroup"],
|
|
7
|
+
capabilities: "drop-all",
|
|
8
|
+
network: "none",
|
|
9
|
+
filesystem: "minimal-read-only-runtime-plus-private-tmpfs",
|
|
10
|
+
seccomp: "rea-linux-x86_64-v1",
|
|
11
|
+
resource_manager: "systemd-user-cgroup-v2",
|
|
12
|
+
nested_user_namespaces: false,
|
|
13
|
+
});
|
|
14
|
+
/** Read, normalize, and content-address every input to one replay experiment. */
|
|
15
|
+
export const prepareReplayPlan = async (input, policy, host) => {
|
|
16
|
+
const cases = replayCases(input).map((item) => ({
|
|
17
|
+
...item,
|
|
18
|
+
sha256: digestJson(item.arguments),
|
|
19
|
+
}));
|
|
20
|
+
if (new TextEncoder().encode(JSON.stringify(cases.map(({ arguments: args }) => args))).byteLength > input.limits.input_bytes)
|
|
21
|
+
throw new RangeError("Replay case inputs exceed the aggregate byte limit");
|
|
22
|
+
await host.probe(policy);
|
|
23
|
+
const [runtime, bubblewrap, systemdRun, systemctl, shell, worker, runtimeFiles, left, right,] = await Promise.all([
|
|
24
|
+
host.identifyExecutable(policy.nodePath, ["--version"]),
|
|
25
|
+
host.identifyExecutable(policy.bubblewrapPath, ["--version"]),
|
|
26
|
+
host.identifyExecutable(policy.systemdRunPath, ["--version"]),
|
|
27
|
+
host.identifyExecutable(policy.systemctlPath, ["--version"]),
|
|
28
|
+
host.identifyExecutable(policy.shellPath, ["--version"]),
|
|
29
|
+
host.identifyWorker(),
|
|
30
|
+
host.identifyRuntimeClosure(policy.nodePath),
|
|
31
|
+
prepareSide(input.left, input.limits.module_bytes, host),
|
|
32
|
+
input.right === undefined
|
|
33
|
+
? Promise.resolve(undefined)
|
|
34
|
+
: prepareSide(input.right, input.limits.module_bytes, host),
|
|
35
|
+
]);
|
|
36
|
+
const withoutDigest = {
|
|
37
|
+
schema_version: 1,
|
|
38
|
+
policy_version: REPLAY_POLICY_VERSION,
|
|
39
|
+
policy_sha256: digestJson(REPLAY_POLICY_DOCUMENT),
|
|
40
|
+
network: "none",
|
|
41
|
+
filesystem: {
|
|
42
|
+
host_writes: false,
|
|
43
|
+
private_tmpfs_bytes: input.limits.tmpfs_bytes,
|
|
44
|
+
},
|
|
45
|
+
runtime: {
|
|
46
|
+
executable: runtime,
|
|
47
|
+
worker,
|
|
48
|
+
read_only_files: runtimeFiles.map((item) => ({
|
|
49
|
+
source_path: item.sourcePath,
|
|
50
|
+
destination_path: item.destinationPath,
|
|
51
|
+
sha256: item.sha256,
|
|
52
|
+
})),
|
|
53
|
+
},
|
|
54
|
+
sandbox: {
|
|
55
|
+
bubblewrap,
|
|
56
|
+
systemd_run: systemdRun,
|
|
57
|
+
systemctl,
|
|
58
|
+
shell,
|
|
59
|
+
seccomp_sha256: host.seccompDigest(),
|
|
60
|
+
},
|
|
61
|
+
left: left.commitment,
|
|
62
|
+
...(right === undefined ? {} : { right: right.commitment }),
|
|
63
|
+
cases,
|
|
64
|
+
determinism: input.determinism,
|
|
65
|
+
limits: input.limits,
|
|
66
|
+
effects: [
|
|
67
|
+
"Execute selected extracted modules in a disposable Linux OS sandbox",
|
|
68
|
+
"Create and remove one private cgroup and mount namespace",
|
|
69
|
+
"Retain bounded return, exception, termination, and diagnostic evidence",
|
|
70
|
+
"Perform no host filesystem writes and expose no host or external network",
|
|
71
|
+
...(input.reproducer_export === undefined
|
|
72
|
+
? []
|
|
73
|
+
: ["Write one separately approved reproducer after sandbox cleanup"]),
|
|
74
|
+
],
|
|
75
|
+
reproducer_export: input.reproducer_export === undefined
|
|
76
|
+
? null
|
|
77
|
+
: {
|
|
78
|
+
path: input.reproducer_export.path,
|
|
79
|
+
include_sources: input.reproducer_export.include_sources,
|
|
80
|
+
authority: "evidence_write",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const publicPlan = {
|
|
84
|
+
...withoutDigest,
|
|
85
|
+
plan_digest: digestJson(withoutDigest),
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
publicPlan,
|
|
89
|
+
leftSources: left.sources,
|
|
90
|
+
...(right === undefined ? {} : { rightSources: right.sources }),
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
const prepareSide = async (side, maximumBytes, host) => {
|
|
94
|
+
const aliases = new Set(side.modules.map(({ alias }) => alias));
|
|
95
|
+
if (aliases.size !== side.modules.length || !aliases.has(side.entry_alias))
|
|
96
|
+
throw new TypeError("Replay manifest aliases or entry alias are invalid");
|
|
97
|
+
for (const module of side.modules)
|
|
98
|
+
for (const dependency of Object.values(module.dependencies))
|
|
99
|
+
if (!aliases.has(dependency))
|
|
100
|
+
throw new TypeError(`Undeclared replay dependency alias: ${dependency}`);
|
|
101
|
+
const loaded = [];
|
|
102
|
+
let totalBytes = 0;
|
|
103
|
+
for (const module of side.modules) {
|
|
104
|
+
const remaining = maximumBytes - totalBytes;
|
|
105
|
+
if (remaining <= 0)
|
|
106
|
+
throw new RangeError("Replay module bytes exceed the aggregate limit");
|
|
107
|
+
const source = await host.readSource(module.path, remaining);
|
|
108
|
+
totalBytes += source.bytes.byteLength;
|
|
109
|
+
loaded.push({ module, source });
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
commitment: {
|
|
113
|
+
modules: loaded.map(({ module, source }) => ({
|
|
114
|
+
alias: module.alias,
|
|
115
|
+
canonical_path: source.canonicalPath,
|
|
116
|
+
format: module.format,
|
|
117
|
+
role: module.role,
|
|
118
|
+
byte_count: source.bytes.byteLength,
|
|
119
|
+
sha256: digestBytes(source.bytes),
|
|
120
|
+
dependencies: module.dependencies,
|
|
121
|
+
})),
|
|
122
|
+
entry_alias: side.entry_alias,
|
|
123
|
+
entry_export: side.entry_export,
|
|
124
|
+
},
|
|
125
|
+
sources: Object.fromEntries(loaded.map(({ module, source }) => [
|
|
126
|
+
module.alias,
|
|
127
|
+
new TextDecoder("utf-8", { fatal: true }).decode(source.bytes),
|
|
128
|
+
])),
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
const replayCases = (input) => {
|
|
132
|
+
const explicit = input.cases.map((item) => ({
|
|
133
|
+
case_id: item.case_id,
|
|
134
|
+
arguments: item.arguments,
|
|
135
|
+
}));
|
|
136
|
+
if (input.generator === undefined)
|
|
137
|
+
return uniqueCases(explicit);
|
|
138
|
+
let state = input.generator.seed || 0x9e37_79b9;
|
|
139
|
+
const next = () => {
|
|
140
|
+
state ^= state << 13;
|
|
141
|
+
state ^= state >>> 17;
|
|
142
|
+
state ^= state << 5;
|
|
143
|
+
return state >>> 0;
|
|
144
|
+
};
|
|
145
|
+
const corpus = boundaryCorpus(input.generator.preset);
|
|
146
|
+
const generated = Array.from({ length: input.generator.count }, (_, index) => ({
|
|
147
|
+
case_id: `generated-${input.generator?.preset ?? "boundary"}-${String(index)}`,
|
|
148
|
+
arguments: [corpus[next() % corpus.length] ?? ""],
|
|
149
|
+
}));
|
|
150
|
+
return uniqueCases([...explicit, ...generated]);
|
|
151
|
+
};
|
|
152
|
+
const uniqueCases = (cases) => {
|
|
153
|
+
const ids = new Set();
|
|
154
|
+
for (const item of cases) {
|
|
155
|
+
if (ids.has(item.case_id))
|
|
156
|
+
throw new TypeError(`Duplicate replay case ID: ${item.case_id}`);
|
|
157
|
+
ids.add(item.case_id);
|
|
158
|
+
}
|
|
159
|
+
return cases;
|
|
160
|
+
};
|
|
161
|
+
const boundaryCorpus = (preset) => {
|
|
162
|
+
if (preset === "sanitizer-boundaries")
|
|
163
|
+
return [
|
|
164
|
+
"",
|
|
165
|
+
"<script>alert(1)</script>",
|
|
166
|
+
"<img src=x onerror=alert(1)>",
|
|
167
|
+
"javascript:alert(1)",
|
|
168
|
+
"<b>ok</b>",
|
|
169
|
+
"\u0000",
|
|
170
|
+
];
|
|
171
|
+
if (preset === "clipboard-boundaries")
|
|
172
|
+
return [
|
|
173
|
+
"",
|
|
174
|
+
"plain",
|
|
175
|
+
"a\r\nb",
|
|
176
|
+
"a\nb",
|
|
177
|
+
"\t",
|
|
178
|
+
"😀",
|
|
179
|
+
"text/html",
|
|
180
|
+
"text/plain",
|
|
181
|
+
];
|
|
182
|
+
return [
|
|
183
|
+
"",
|
|
184
|
+
" ",
|
|
185
|
+
"\n",
|
|
186
|
+
"# heading",
|
|
187
|
+
"**bold**",
|
|
188
|
+
"[x](javascript:y)",
|
|
189
|
+
"`code`",
|
|
190
|
+
"😀",
|
|
191
|
+
"\u0000",
|
|
192
|
+
"a".repeat(1024),
|
|
193
|
+
];
|
|
194
|
+
};
|
|
195
|
+
export const digestBytes = (value) => createHash("sha256").update(value).digest("hex");
|
|
196
|
+
export const digestJson = (value) => {
|
|
197
|
+
const encoded = canonicalize(value);
|
|
198
|
+
if (encoded === undefined)
|
|
199
|
+
throw new TypeError("Replay commitment is not canonical JSON");
|
|
200
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
201
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import writeFileAtomic from "write-file-atomic";
|
|
3
|
+
import { controlledReplayInputSchema, replayExecutionResultSchema, controlledReplayOutputSchema, replayEvidenceSchema, } from "../domain/javascriptReplay.js";
|
|
4
|
+
import { AnalysisCapabilityUnavailableError, AnalysisCancelledError, AnalysisInputError, AnalysisProtocolError, PermissionRequiredError, ReplayPlanStaleError, } from "../domain/errors.js";
|
|
5
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
6
|
+
import { projectInputIssues } from "../domain/inputIssueProjection.js";
|
|
7
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
8
|
+
import { err, ok } from "../domain/result.js";
|
|
9
|
+
import { digestBytes, prepareReplayPlan, } from "./JavaScriptReplayPlanning.js";
|
|
10
|
+
const OPERATION = "run_controlled_replay";
|
|
11
|
+
const isAborted = (signal) => signal?.aborted === true;
|
|
12
|
+
/** Plan or execute one content-bound extracted-module replay experiment. */
|
|
13
|
+
export const runControlledReplay = async (dependencies, rawInput, options = {}) => {
|
|
14
|
+
const parsed = controlledReplayInputSchema.safeParse(rawInput);
|
|
15
|
+
if (!parsed.success)
|
|
16
|
+
return err(new AnalysisInputError(OPERATION, { cause: parsed.error }, projectInputIssues(parsed.error.issues, rawInput)));
|
|
17
|
+
return runControlledReplayValidated(dependencies, parsed.data, options);
|
|
18
|
+
};
|
|
19
|
+
/** Plan or execute replay input already parsed by a trusted adapter. */
|
|
20
|
+
export const runControlledReplayValidated = async (dependencies, input, options = {}) => {
|
|
21
|
+
if (!dependencies.policy.enabled)
|
|
22
|
+
return err(new AnalysisCapabilityUnavailableError("rea-javascript-replay", OPERATION, "controlled replay is disabled; configure exact roots and executables before enabling it"));
|
|
23
|
+
if (dependencies.authority === undefined)
|
|
24
|
+
return err(new AnalysisCapabilityUnavailableError("rea-javascript-replay", OPERATION, "JavaScript replay permission policy is not configured"));
|
|
25
|
+
if (isAborted(options.signal))
|
|
26
|
+
return err(new AnalysisCancelledError(OPERATION));
|
|
27
|
+
const request = permissionRequest(input, dependencies.policy);
|
|
28
|
+
const authorized = input.mode === "plan"
|
|
29
|
+
? await dependencies.authority.explain(request, "read")
|
|
30
|
+
: await dependencies.authority.authorize(request, "read");
|
|
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
|
+
let prepared;
|
|
38
|
+
try {
|
|
39
|
+
prepared = await prepareReplayPlan(input, dependencies.policy, dependencies.host);
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
return err(new AnalysisCapabilityUnavailableError("rea-javascript-replay", OPERATION, cause instanceof Error ? cause.message : "replay planning failed"));
|
|
43
|
+
}
|
|
44
|
+
if (isAborted(options.signal))
|
|
45
|
+
return err(new AnalysisCancelledError(OPERATION));
|
|
46
|
+
if (input.mode === "plan")
|
|
47
|
+
return ok(asJson({
|
|
48
|
+
phase: "plan",
|
|
49
|
+
plan: prepared.publicPlan,
|
|
50
|
+
source_evidence: [],
|
|
51
|
+
evidence: null,
|
|
52
|
+
}));
|
|
53
|
+
if (input.plan_digest !== prepared.publicPlan.plan_digest)
|
|
54
|
+
return err(new ReplayPlanStaleError(input.plan_digest ?? "", prepared.publicPlan.plan_digest));
|
|
55
|
+
let canonicalExportPath;
|
|
56
|
+
if (input.reproducer_export !== undefined) {
|
|
57
|
+
if (input.reproducer_export.approved !== true)
|
|
58
|
+
return err(new AnalysisInputError(`${OPERATION}:reproducer_export`));
|
|
59
|
+
const exportAuthorized = await dependencies.authority.authorize({
|
|
60
|
+
capability: "evidence_write",
|
|
61
|
+
roots: [input.reproducer_export.path],
|
|
62
|
+
executables: [],
|
|
63
|
+
environment_names: [],
|
|
64
|
+
network: "none",
|
|
65
|
+
mount: false,
|
|
66
|
+
operation_identity: `${OPERATION}:reproducer:${prepared.publicPlan.plan_digest}`,
|
|
67
|
+
}, "write");
|
|
68
|
+
if (!exportAuthorized.ok)
|
|
69
|
+
return err(exportAuthorized.error instanceof PermissionRequiredError
|
|
70
|
+
? exportAuthorized.error
|
|
71
|
+
: new AnalysisProtocolError(exportAuthorized.error.message, {
|
|
72
|
+
cause: exportAuthorized.error,
|
|
73
|
+
}));
|
|
74
|
+
canonicalExportPath = exportAuthorized.value.request.roots[0];
|
|
75
|
+
if (canonicalExportPath !== input.reproducer_export.path)
|
|
76
|
+
return err(new AnalysisInputError(`${OPERATION}:reproducer_export:path`, {
|
|
77
|
+
cause: new TypeError(`Use the canonical reproducer path: ${canonicalExportPath ?? "unavailable"}`),
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
if (isAborted(options.signal))
|
|
81
|
+
return err(new AnalysisCancelledError(OPERATION));
|
|
82
|
+
await options.progress?.report({
|
|
83
|
+
phase: OPERATION,
|
|
84
|
+
completed: 0,
|
|
85
|
+
total: 1,
|
|
86
|
+
message: "Admitting approved modules into the isolated replay worker",
|
|
87
|
+
});
|
|
88
|
+
if (isAborted(options.signal))
|
|
89
|
+
return err(new AnalysisCancelledError(OPERATION));
|
|
90
|
+
try {
|
|
91
|
+
let executed = replayExecutionResultSchema.parse(await dependencies.runner.execute(prepared, dependencies.policy, options.signal));
|
|
92
|
+
await options.progress?.report({
|
|
93
|
+
phase: OPERATION,
|
|
94
|
+
completed: 1,
|
|
95
|
+
total: 1,
|
|
96
|
+
message: "Controlled replay stopped and sandbox cleanup was observed",
|
|
97
|
+
terminal: true,
|
|
98
|
+
});
|
|
99
|
+
if (executed.termination === "cancelled")
|
|
100
|
+
return err(new AnalysisCancelledError(OPERATION));
|
|
101
|
+
if (input.reproducer_export !== undefined &&
|
|
102
|
+
canonicalExportPath !== undefined &&
|
|
103
|
+
executed.cleanup.state === "complete") {
|
|
104
|
+
const manifest = {
|
|
105
|
+
schema_version: 1,
|
|
106
|
+
plan: prepared.publicPlan,
|
|
107
|
+
result: executed,
|
|
108
|
+
sources: input.reproducer_export.include_sources
|
|
109
|
+
? {
|
|
110
|
+
left: prepared.leftSources,
|
|
111
|
+
...(prepared.rightSources === undefined
|
|
112
|
+
? {}
|
|
113
|
+
: { right: prepared.rightSources }),
|
|
114
|
+
}
|
|
115
|
+
: null,
|
|
116
|
+
};
|
|
117
|
+
const encoded = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`);
|
|
118
|
+
try {
|
|
119
|
+
await writeFileAtomic(canonicalExportPath, encoded, {
|
|
120
|
+
mode: 0o600,
|
|
121
|
+
});
|
|
122
|
+
executed = replayExecutionResultSchema.parse({
|
|
123
|
+
...executed,
|
|
124
|
+
reproducer: {
|
|
125
|
+
state: "written",
|
|
126
|
+
path: canonicalExportPath,
|
|
127
|
+
sha256: digestBytes(encoded),
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (cause) {
|
|
132
|
+
executed = replayExecutionResultSchema.parse({
|
|
133
|
+
...executed,
|
|
134
|
+
limitations: [
|
|
135
|
+
...executed.limitations,
|
|
136
|
+
"Replay completed, but the separately approved reproducer export failed.",
|
|
137
|
+
],
|
|
138
|
+
reproducer: {
|
|
139
|
+
state: "failed",
|
|
140
|
+
path: canonicalExportPath,
|
|
141
|
+
error: cause instanceof Error
|
|
142
|
+
? cause.message
|
|
143
|
+
: "unknown reproducer export failure",
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const leftCount = prepared.publicPlan.cases.length;
|
|
149
|
+
const sourceEvidence = [
|
|
150
|
+
createReplayEvidence(prepared, executed, "left", executed.outcomes.slice(0, leftCount)),
|
|
151
|
+
...(prepared.publicPlan.right === undefined
|
|
152
|
+
? []
|
|
153
|
+
: [
|
|
154
|
+
createReplayEvidence(prepared, executed, "right", executed.outcomes.slice(leftCount)),
|
|
155
|
+
]),
|
|
156
|
+
];
|
|
157
|
+
const subject = prepared.publicPlan.left.modules[0];
|
|
158
|
+
const evidence = createEvidence(subject === undefined
|
|
159
|
+
? undefined
|
|
160
|
+
: {
|
|
161
|
+
path: subject.canonical_path,
|
|
162
|
+
sha256: subject.sha256,
|
|
163
|
+
format: "javascript",
|
|
164
|
+
}, {
|
|
165
|
+
id: "rea-javascript-replay",
|
|
166
|
+
name: "REA isolated JavaScript replay",
|
|
167
|
+
version: "1",
|
|
168
|
+
}, {
|
|
169
|
+
predicateType: "javascript-controlled-replay",
|
|
170
|
+
operation: OPERATION,
|
|
171
|
+
parameters: {
|
|
172
|
+
replay_plan: jsonValueSchema.parse(JSON.parse(JSON.stringify(prepared.publicPlan))),
|
|
173
|
+
},
|
|
174
|
+
result: jsonValueSchema.parse(JSON.parse(JSON.stringify(executed))),
|
|
175
|
+
rawResult: jsonValueSchema.parse(JSON.parse(JSON.stringify({
|
|
176
|
+
cases: prepared.publicPlan.cases,
|
|
177
|
+
outcomes: executed.outcomes,
|
|
178
|
+
stderr: executed.stderr,
|
|
179
|
+
}))),
|
|
180
|
+
confidence: executed.comparison === undefined ? "observed" : "derived",
|
|
181
|
+
authority: "controlled-replay",
|
|
182
|
+
environment: {
|
|
183
|
+
id: prepared.publicPlan.plan_digest,
|
|
184
|
+
platform: process.platform,
|
|
185
|
+
architecture: process.arch,
|
|
186
|
+
isolation: "container",
|
|
187
|
+
},
|
|
188
|
+
limitations: executed.limitations,
|
|
189
|
+
locations: prepared.publicPlan.left.modules.map((module) => ({
|
|
190
|
+
kind: "artifact-path",
|
|
191
|
+
path: module.canonical_path,
|
|
192
|
+
})),
|
|
193
|
+
evidenceLinks: sourceEvidence.map(({ evidence_id: id }) => id),
|
|
194
|
+
});
|
|
195
|
+
return ok(asJson({
|
|
196
|
+
phase: "execute",
|
|
197
|
+
plan: null,
|
|
198
|
+
source_evidence: sourceEvidence.map((item) => replayEvidenceSchema.parse(item)),
|
|
199
|
+
evidence: replayEvidenceSchema.parse(evidence),
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
catch (cause) {
|
|
203
|
+
return err(new AnalysisProtocolError(cause instanceof z.ZodError
|
|
204
|
+
? "Controlled replay produced an invalid bounded result"
|
|
205
|
+
: "Controlled replay could not establish or clean up its sandbox", { cause }));
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const createReplayEvidence = (prepared, executed, side, outcomes) => {
|
|
209
|
+
const manifest = side === "left" ? prepared.publicPlan.left : prepared.publicPlan.right;
|
|
210
|
+
const subject = manifest?.modules[0];
|
|
211
|
+
const result = replayExecutionResultSchema.parse({
|
|
212
|
+
...executed,
|
|
213
|
+
outcomes,
|
|
214
|
+
comparison: undefined,
|
|
215
|
+
reproducer: null,
|
|
216
|
+
});
|
|
217
|
+
return createEvidence(subject === undefined
|
|
218
|
+
? undefined
|
|
219
|
+
: {
|
|
220
|
+
path: subject.canonical_path,
|
|
221
|
+
sha256: subject.sha256,
|
|
222
|
+
format: "javascript",
|
|
223
|
+
}, {
|
|
224
|
+
id: "rea-javascript-replay",
|
|
225
|
+
name: "REA isolated JavaScript replay",
|
|
226
|
+
version: "1",
|
|
227
|
+
}, {
|
|
228
|
+
predicateType: "javascript-controlled-replay-observation",
|
|
229
|
+
operation: OPERATION,
|
|
230
|
+
parameters: {
|
|
231
|
+
replay_plan: jsonValueSchema.parse(JSON.parse(JSON.stringify(prepared.publicPlan))),
|
|
232
|
+
side,
|
|
233
|
+
},
|
|
234
|
+
result: jsonValueSchema.parse(JSON.parse(JSON.stringify(result))),
|
|
235
|
+
rawResult: jsonValueSchema.parse(JSON.parse(JSON.stringify({ cases: prepared.publicPlan.cases, outcomes }))),
|
|
236
|
+
confidence: "observed",
|
|
237
|
+
authority: "controlled-replay",
|
|
238
|
+
environment: {
|
|
239
|
+
id: prepared.publicPlan.plan_digest,
|
|
240
|
+
platform: process.platform,
|
|
241
|
+
architecture: process.arch,
|
|
242
|
+
isolation: "container",
|
|
243
|
+
},
|
|
244
|
+
limitations: executed.limitations,
|
|
245
|
+
locations: (manifest?.modules ?? []).map((module) => ({
|
|
246
|
+
kind: "artifact-path",
|
|
247
|
+
path: module.canonical_path,
|
|
248
|
+
})),
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
const asJson = (value) => jsonValueSchema.parse(JSON.parse(JSON.stringify(controlledReplayOutputSchema.parse(value))));
|
|
252
|
+
const permissionRequest = (input, policy) => ({
|
|
253
|
+
capability: "javascript_replay",
|
|
254
|
+
roots: [...input.left.modules, ...(input.right?.modules ?? [])].map(({ path }) => path),
|
|
255
|
+
executables: [
|
|
256
|
+
policy.nodePath,
|
|
257
|
+
policy.bubblewrapPath,
|
|
258
|
+
policy.systemdRunPath,
|
|
259
|
+
policy.systemctlPath,
|
|
260
|
+
policy.shellPath,
|
|
261
|
+
],
|
|
262
|
+
environment_names: [],
|
|
263
|
+
network: "none",
|
|
264
|
+
mount: true,
|
|
265
|
+
operation_identity: `${OPERATION}:${input.plan_digest ?? "plan"}`,
|
|
266
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createEvidence, } from "../domain/evidence.js";
|
|
2
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
3
|
+
import { JAVASCRIPT_RUNTIME_RECONCILIATION_PROVIDER } from "./InvestigationProviders.js";
|
|
4
|
+
/** Create derived Evidence v2 for one exact set of static/runtime inputs. */
|
|
5
|
+
export const createJavaScriptRuntimeReconciliationEvidence = (input, result) => createEvidence(undefined, JAVASCRIPT_RUNTIME_RECONCILIATION_PROVIDER, {
|
|
6
|
+
predicateType: "rea.javascript-runtime-reconciliation/v1",
|
|
7
|
+
operation: "reconcile_javascript_runtime",
|
|
8
|
+
parameters: parameters(input),
|
|
9
|
+
result: jsonValueSchema.parse(result),
|
|
10
|
+
rawResult: null,
|
|
11
|
+
confidence: "inferred",
|
|
12
|
+
authority: "analyst-inference",
|
|
13
|
+
environment: null,
|
|
14
|
+
limitations: result.limitations,
|
|
15
|
+
evidenceLinks: result.evidence_links,
|
|
16
|
+
});
|
|
17
|
+
const parameters = (input) => ({
|
|
18
|
+
static_layers: input.static_layers.map((layer) => ({
|
|
19
|
+
role: layer.role,
|
|
20
|
+
evidence_id: layer.analysis.evidence_id,
|
|
21
|
+
runtime_mappings: layer.runtime_mappings,
|
|
22
|
+
})),
|
|
23
|
+
runtime_evidence_ids: input.runtime_observations.map(({ evidence_id: id }) => id),
|
|
24
|
+
limits: input.limits,
|
|
25
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AnalysisInputError, AnalysisProtocolError, } from "../domain/errors.js";
|
|
3
|
+
import { projectInputIssues } from "../domain/inputIssueProjection.js";
|
|
4
|
+
import { reconcileJavaScriptRuntime } from "../domain/javascriptRuntimeReconciliation.js";
|
|
5
|
+
import { reconcileJavaScriptRuntimeInputSchema } from "../domain/javascriptRuntimeReconciliationSchemas.js";
|
|
6
|
+
import { err, ok } from "../domain/result.js";
|
|
7
|
+
import { createJavaScriptRuntimeReconciliationEvidence } from "./JavaScriptRuntimeReconciliationEvidence.js";
|
|
8
|
+
const OPERATION = "reconcile_javascript_runtime";
|
|
9
|
+
/** Derive a combined JAG from verified static and passive-runtime Evidence. */
|
|
10
|
+
export const reconcileJavaScriptRuntimeEvidence = (rawInput) => {
|
|
11
|
+
const parsed = reconcileJavaScriptRuntimeInputSchema.safeParse(rawInput);
|
|
12
|
+
if (!parsed.success)
|
|
13
|
+
return err(new AnalysisInputError(OPERATION, undefined, projectInputIssues(parsed.error.issues, rawInput)));
|
|
14
|
+
return reconcileJavaScriptRuntimeEvidenceValidated(parsed.data);
|
|
15
|
+
};
|
|
16
|
+
/** Reconcile input already parsed by a trusted adapter boundary. */
|
|
17
|
+
export const reconcileJavaScriptRuntimeEvidenceValidated = (input) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = reconcileJavaScriptRuntime(input);
|
|
20
|
+
return ok(createJavaScriptRuntimeReconciliationEvidence(input, result));
|
|
21
|
+
}
|
|
22
|
+
catch (cause) {
|
|
23
|
+
return err(cause instanceof z.ZodError || cause instanceof TypeError
|
|
24
|
+
? new AnalysisInputError(OPERATION)
|
|
25
|
+
: new AnalysisProtocolError("JavaScript runtime reconciliation produced an invalid result", { cause }));
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -11,7 +11,7 @@ const DOWNLOAD_PREFIX = "https://www.hopperapp.com:443/downloader/public/";
|
|
|
11
11
|
const MAX_PACKAGE_BYTES = 100_000_000;
|
|
12
12
|
const BUNDLE_IDENTIFIER = "com.cryptic-apps.hopper-web-4";
|
|
13
13
|
/** Download, verify, and install Hopper into the current user's Applications directory. */
|
|
14
|
-
export const installMacHopper = async (host = systemMacHopperInstallHost()) => {
|
|
14
|
+
export const installMacHopper = async (options = {}, host = systemMacHopperInstallHost()) => {
|
|
15
15
|
let temporary;
|
|
16
16
|
let mounted = false;
|
|
17
17
|
try {
|
|
@@ -30,7 +30,8 @@ export const installMacHopper = async (host = systemMacHopperInstallHost()) => {
|
|
|
30
30
|
if (!integrityMatches(archive.bytes, release))
|
|
31
31
|
return { status: "failed", reason: "integrity" };
|
|
32
32
|
const destination = host.destination();
|
|
33
|
-
|
|
33
|
+
const destinationExists = await host.destinationExists(destination);
|
|
34
|
+
if (destinationExists && options.replaceExisting !== true)
|
|
34
35
|
return { status: "failed", reason: "destination_exists" };
|
|
35
36
|
temporary = await host.createTemporaryDirectory();
|
|
36
37
|
const packagePath = join(temporary, "hopper.dmg");
|
|
@@ -44,7 +45,7 @@ export const installMacHopper = async (host = systemMacHopperInstallHost()) => {
|
|
|
44
45
|
if (source === undefined || !(await host.validBundle(source)))
|
|
45
46
|
return { status: "failed", reason: "bundle" };
|
|
46
47
|
const stage = `${destination}.rea-stage`;
|
|
47
|
-
if (!(await host.installBundle(source, destination, stage)))
|
|
48
|
+
if (!(await host.installBundle(source, destination, stage, destinationExists)))
|
|
48
49
|
return { status: "failed", reason: "copy" };
|
|
49
50
|
const launcherPath = join(destination, "Contents/MacOS/hopper");
|
|
50
51
|
if (!(await host.launcherReady(launcherPath)))
|
|
@@ -126,16 +127,31 @@ const systemMacHopperInstallHost = () => ({
|
|
|
126
127
|
return false;
|
|
127
128
|
}
|
|
128
129
|
},
|
|
129
|
-
async installBundle(source, destination, stage) {
|
|
130
|
+
async installBundle(source, destination, stage, replaceExisting) {
|
|
131
|
+
const previous = `${stage}-previous`;
|
|
130
132
|
try {
|
|
131
133
|
await mkdir(join(homedir(), "Applications"), { recursive: true });
|
|
132
134
|
await rm(stage, { recursive: true, force: true });
|
|
135
|
+
await rm(previous, { recursive: true, force: true });
|
|
133
136
|
await execFileAsync("ditto", [source, stage]);
|
|
137
|
+
await access(join(stage, "Contents/MacOS/hopper"));
|
|
138
|
+
if (replaceExisting)
|
|
139
|
+
await rename(destination, previous);
|
|
134
140
|
await rename(stage, destination);
|
|
141
|
+
if (replaceExisting)
|
|
142
|
+
await rm(previous, { recursive: true, force: true });
|
|
135
143
|
return true;
|
|
136
144
|
}
|
|
137
145
|
catch {
|
|
138
146
|
await rm(stage, { recursive: true, force: true });
|
|
147
|
+
if (replaceExisting) {
|
|
148
|
+
try {
|
|
149
|
+
await rename(previous, destination);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// The original destination was not moved, or rollback is impossible.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
139
155
|
return false;
|
|
140
156
|
}
|
|
141
157
|
},
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AnalysisInputError, AnalysisProtocolError, } from "../domain/errors.js";
|
|
3
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
4
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
5
|
+
import { managedApplicationGraphResultSchema, projectManagedApplicationGraph, projectManagedApplicationGraphInputSchema, } from "../domain/managedApplicationGraph.js";
|
|
6
|
+
import { err, ok } from "../domain/result.js";
|
|
7
|
+
import { MANAGED_WORKFLOW_PROVIDER } from "./InvestigationProviders.js";
|
|
8
|
+
const OPERATION = "project_managed_application_graph";
|
|
9
|
+
/** Project authenticated managed Evidence into a provider-neutral graph. */
|
|
10
|
+
export const projectManagedApplicationGraphEvidence = (rawInput) => {
|
|
11
|
+
const parsed = projectManagedApplicationGraphInputSchema.safeParse(rawInput);
|
|
12
|
+
if (!parsed.success)
|
|
13
|
+
return err(new AnalysisInputError(OPERATION, { cause: parsed.error }));
|
|
14
|
+
try {
|
|
15
|
+
const result = projectManagedApplicationGraph(parsed.data);
|
|
16
|
+
return ok(createManagedApplicationGraphEvidence(parsed.data, result));
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
return err(cause instanceof TypeError || cause instanceof z.ZodError
|
|
20
|
+
? new AnalysisInputError(OPERATION, { cause })
|
|
21
|
+
: new AnalysisProtocolError("Managed application graph projection produced an invalid result", { cause }));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const createManagedApplicationGraphEvidence = (input, result) => createEvidence(subjectTarget(input), MANAGED_WORKFLOW_PROVIDER, {
|
|
25
|
+
predicateType: "rea.managed-application-graph/v1",
|
|
26
|
+
operation: OPERATION,
|
|
27
|
+
parameters: {
|
|
28
|
+
managed_artifact_evidence_id: input.managed_artifact?.evidence_id ?? null,
|
|
29
|
+
managed_members_evidence_id: input.managed_members?.evidence_id ?? null,
|
|
30
|
+
managed_native_boundaries_evidence_id: input.managed_native_boundaries?.evidence_id ?? null,
|
|
31
|
+
limits: jsonValueSchema.parse(input.limits),
|
|
32
|
+
},
|
|
33
|
+
result: jsonValueSchema.parse(managedApplicationGraphResultSchema.parse(result)),
|
|
34
|
+
rawResult: null,
|
|
35
|
+
confidence: "inferred",
|
|
36
|
+
authority: "analyst-inference",
|
|
37
|
+
environment: null,
|
|
38
|
+
limitations: result.limitations,
|
|
39
|
+
evidenceLinks: result.evidence_links,
|
|
40
|
+
});
|
|
41
|
+
const subjectTarget = (input) => {
|
|
42
|
+
const subject = input.managed_artifact?.subject ??
|
|
43
|
+
input.managed_members?.subject ??
|
|
44
|
+
input.managed_native_boundaries?.subject;
|
|
45
|
+
if (subject === undefined || subject === null || subject.format !== "pe")
|
|
46
|
+
return undefined;
|
|
47
|
+
return {
|
|
48
|
+
path: subject.local_path,
|
|
49
|
+
sha256: subject.digest.sha256,
|
|
50
|
+
format: "pe",
|
|
51
|
+
...(subject.architecture === null
|
|
52
|
+
? {}
|
|
53
|
+
: { architecture: subject.architecture }),
|
|
54
|
+
};
|
|
55
|
+
};
|