rea-agents 0.2.1 → 0.4.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.
Files changed (122) hide show
  1. package/README.md +124 -20
  2. package/bridge/hopper_bridge.py +141 -13
  3. package/dist/application/AnalysisProvider.js +12 -1
  4. package/dist/application/ArtifactExtraction.js +166 -0
  5. package/dist/application/ArtifactGraphConstruction.js +257 -0
  6. package/dist/application/ArtifactInventory.js +253 -0
  7. package/dist/application/BinarySession.js +164 -14
  8. package/dist/application/CompositeProvider.js +73 -0
  9. package/dist/application/DirectAnalysis.js +27 -3
  10. package/dist/application/Doctor.js +35 -6
  11. package/dist/application/EnhancedTools.js +8 -7
  12. package/dist/application/EvidenceBundleCommands.js +29 -0
  13. package/dist/application/EvidenceBundleFiles.js +127 -0
  14. package/dist/application/EvidenceLedger.js +225 -18
  15. package/dist/application/FilesystemSnapshot.js +124 -0
  16. package/dist/application/LinuxHopper.js +186 -0
  17. package/dist/application/LoopbackReplay.js +195 -37
  18. package/dist/application/ProcessHarness.js +200 -191
  19. package/dist/application/ProcessNormalization.js +44 -0
  20. package/dist/application/ProcessOwnership.js +84 -0
  21. package/dist/application/ProcessSampling.js +284 -0
  22. package/dist/application/RealHopperAssertions.js +105 -0
  23. package/dist/application/ReferenceSourceImport.js +182 -0
  24. package/dist/application/ReferenceSourceImportEntries.js +122 -0
  25. package/dist/application/ReferenceSourceImportPolicy.js +73 -0
  26. package/dist/application/ReferenceSourceImportTypes.js +18 -0
  27. package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
  28. package/dist/application/Setup.js +192 -41
  29. package/dist/application/Uninstall.js +130 -0
  30. package/dist/application/runtime.js +8 -1
  31. package/dist/artifacts/ArtifactPaths.js +51 -0
  32. package/dist/artifacts/ArtifactProvider.js +130 -0
  33. package/dist/artifacts/ArtifactReader.js +9 -0
  34. package/dist/artifacts/AsarArtifactReader.js +62 -0
  35. package/dist/artifacts/DirectoryArtifactReader.js +107 -0
  36. package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
  37. package/dist/artifacts/SafeOutputTree.js +199 -0
  38. package/dist/artifacts/StreamBytes.js +10 -0
  39. package/dist/artifacts/ZipArtifactReader.js +109 -0
  40. package/dist/cli.js +153 -6
  41. package/dist/config.js +35 -1
  42. package/dist/contracts/artifactComparisonExample.js +95 -0
  43. package/dist/contracts/artifactToolContracts.js +84 -0
  44. package/dist/contracts/enhancedInputs.js +4 -0
  45. package/dist/contracts/functionComparisonExample.js +57 -0
  46. package/dist/contracts/investigationExamples.js +118 -0
  47. package/dist/contracts/nativeToolContracts.js +52 -0
  48. package/dist/contracts/processCaptureExample.js +25 -0
  49. package/dist/contracts/toolContractExamples.js +69 -0
  50. package/dist/contracts/toolContracts.js +99 -36
  51. package/dist/contracts/toolOutputSchemas.js +154 -82
  52. package/dist/contracts/unknownContractExamples.js +33 -0
  53. package/dist/domain/artifactComparison.js +273 -0
  54. package/dist/domain/artifactGraph.js +194 -0
  55. package/dist/domain/artifactInventoryEvidence.js +150 -0
  56. package/dist/domain/binaryTarget.js +55 -0
  57. package/dist/domain/bundleComparison.js +266 -0
  58. package/dist/domain/callPath.js +346 -0
  59. package/dist/domain/changedBehavior.js +294 -0
  60. package/dist/domain/errors.js +192 -4
  61. package/dist/domain/evidence.js +41 -10
  62. package/dist/domain/evidenceBundle.js +187 -6
  63. package/dist/domain/functionComparison.js +201 -0
  64. package/dist/domain/functionComparisonNormalization.js +112 -0
  65. package/dist/domain/functionComparisonResults.js +54 -0
  66. package/dist/domain/functionComparisonSchemas.js +82 -0
  67. package/dist/domain/functionDossierEvidence.js +171 -0
  68. package/dist/domain/hopperValues.js +14 -7
  69. package/dist/domain/nativeInspection.js +142 -0
  70. package/dist/domain/processCapture.js +152 -54
  71. package/dist/domain/processComparison.js +106 -0
  72. package/dist/domain/reconstructionUnknowns.js +90 -0
  73. package/dist/domain/reconstructionVerification.js +285 -0
  74. package/dist/domain/reconstructionVerificationSchemas.js +126 -0
  75. package/dist/domain/referenceSourceClassification.js +496 -0
  76. package/dist/domain/referenceSourceGraph.js +376 -0
  77. package/dist/domain/referenceSourceImportParsing.js +235 -0
  78. package/dist/domain/residualUnknown.js +239 -0
  79. package/dist/domain/staticRuntimeCorrelation.js +375 -0
  80. package/dist/hopper/BridgeLauncher.js +40 -3
  81. package/dist/hopper/HopperClient.js +14 -5
  82. package/dist/hopper/HopperProvider.js +57 -22
  83. package/dist/identity.js +1 -0
  84. package/dist/main.js +5 -1
  85. package/dist/native/CommandRunner.js +156 -0
  86. package/dist/native/NativeMacOSProvider.js +306 -0
  87. package/dist/native/NativeMachoInspection.js +135 -0
  88. package/dist/native/parsers/codesign.js +55 -0
  89. package/dist/native/parsers/demangle.js +26 -0
  90. package/dist/native/parsers/dyldInfo.js +25 -0
  91. package/dist/native/parsers/lipo.js +67 -0
  92. package/dist/native/parsers/otool.js +193 -0
  93. package/dist/native/parsers/plist.js +23 -0
  94. package/dist/reference/ReferenceSourceReader.js +73 -0
  95. package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
  96. package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
  97. package/dist/reference/ReferenceSourceReaderFile.js +119 -0
  98. package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
  99. package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
  100. package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
  101. package/dist/server/createServer.js +31 -5
  102. package/dist/server/recordDerivedEvidence.js +10 -0
  103. package/dist/server/registerArtifactComparisonTool.js +62 -0
  104. package/dist/server/registerArtifactTools.js +6 -0
  105. package/dist/server/registerBundleComparisonTool.js +47 -0
  106. package/dist/server/registerEnhancedTools.js +64 -12
  107. package/dist/server/registerEvidenceTools.js +36 -0
  108. package/dist/server/registerFunctionComparisonTool.js +68 -0
  109. package/dist/server/registerInvestigationTools.js +224 -0
  110. package/dist/server/registerNativeTools.js +6 -0
  111. package/dist/server/registerOfficialTools.js +47 -14
  112. package/dist/server/registerProcessComparisonTool.js +106 -0
  113. package/dist/server/registerSessionTools.js +179 -70
  114. package/dist/server/sessionEvidence.js +28 -0
  115. package/dist/server/sessionToolPolicies.js +64 -0
  116. package/dist/server/toolRegistrationOptions.js +7 -0
  117. package/dist/server/toolResult.js +8 -5
  118. package/install.sh +198 -0
  119. package/package.json +19 -2
  120. package/scripts/rea.mjs +5 -1
  121. package/skills/rea-analysis/SKILL.md +77 -2
  122. /package/dist/{application/HopperToolPort.js → domain/referenceSourcePolicy.js} +0 -0
@@ -0,0 +1,109 @@
1
+ import { open } from "node:fs/promises";
2
+ import { PassThrough } from "node:stream";
3
+ import { once } from "node:events";
4
+ import { Reader, ZipReader } from "@zip.js/zip.js";
5
+ import { ArtifactReaderFailure, } from "./ArtifactReader.js";
6
+ class NodeFileReader extends Reader {
7
+ path;
8
+ #handle;
9
+ constructor(path) {
10
+ super(path);
11
+ this.path = path;
12
+ }
13
+ async init() {
14
+ this.#handle = await open(this.path, "r");
15
+ this.size = (await this.#handle.stat()).size;
16
+ }
17
+ async readUint8Array(index, length) {
18
+ const handle = this.#handle;
19
+ if (handle === undefined)
20
+ throw new Error("ZIP reader is not initialized");
21
+ const bytes = Buffer.alloc(Math.min(length, this.size - index));
22
+ const read = await handle.read(bytes, 0, bytes.length, index);
23
+ return bytes.subarray(0, read.bytesRead);
24
+ }
25
+ async closeHandle() {
26
+ await this.#handle?.close();
27
+ this.#handle = undefined;
28
+ }
29
+ }
30
+ /** Lazy Zip64-capable reader with CRC and overlap verification on every read. */
31
+ export class ZipArtifactReader {
32
+ format;
33
+ #source;
34
+ #reader;
35
+ #entries = new Map();
36
+ constructor(path, format) {
37
+ this.format = format;
38
+ this.#source = new NodeFileReader(path);
39
+ this.#reader = new ZipReader(this.#source, {
40
+ checkSignature: true,
41
+ checkOverlappingEntry: true,
42
+ });
43
+ }
44
+ async *entries(signal) {
45
+ for await (const entry of this.#reader.getEntriesGenerator()) {
46
+ abortIfNeeded(signal);
47
+ this.#entries.set(entry.filename, entry);
48
+ const symlink = isSymlink(entry);
49
+ yield {
50
+ path: entry.filename,
51
+ kind: entry.directory ? "directory" : symlink ? "symlink" : "file",
52
+ declaredSize: entry.directory ? null : entry.uncompressedSize,
53
+ compressedSize: entry.directory ? null : entry.compressedSize,
54
+ executable: entry.executable,
55
+ encrypted: entry.encrypted,
56
+ byteOffset: null,
57
+ declaredSha256: null,
58
+ limitations: symlink ? ["Archive symlink target was not read."] : [],
59
+ adapterKey: entry.filename,
60
+ };
61
+ }
62
+ }
63
+ open(entry, signal) {
64
+ abortIfNeeded(signal);
65
+ const stored = this.#entries.get(entry.adapterKey);
66
+ if (stored === undefined || stored.directory || isSymlink(stored))
67
+ return Promise.reject(new ArtifactReaderFailure("format", "ZIP entry is not a regular file"));
68
+ if (stored.encrypted)
69
+ return Promise.reject(new ArtifactReaderFailure("unavailable", "Encrypted ZIP entry is unsupported"));
70
+ return Promise.resolve(extractStream(stored, signal));
71
+ }
72
+ async close() {
73
+ await this.#reader.close().catch(() => undefined);
74
+ await this.#source.closeHandle();
75
+ }
76
+ provenance() {
77
+ return [];
78
+ }
79
+ }
80
+ const extractStream = (entry, signal) => {
81
+ const output = new PassThrough();
82
+ const writable = new WritableStream({
83
+ write: async (chunk) => {
84
+ abortIfNeeded(signal);
85
+ if (!output.write(Buffer.from(chunk)))
86
+ await once(output, "drain");
87
+ },
88
+ close: () => {
89
+ output.end();
90
+ },
91
+ abort: (reason) => {
92
+ output.destroy(toError(reason));
93
+ },
94
+ });
95
+ void entry
96
+ .getData(writable, {
97
+ checkSignature: true,
98
+ checkOverlappingEntry: true,
99
+ onprogress: () => abortIfNeeded(signal),
100
+ })
101
+ .catch((cause) => output.destroy(toError(cause)));
102
+ return output;
103
+ };
104
+ const isSymlink = (entry) => entry.unixMode !== undefined && (entry.unixMode & 0o170000) === 0o120000;
105
+ const abortIfNeeded = (signal) => {
106
+ if (signal?.aborted === true)
107
+ throw new ArtifactReaderFailure("cancelled", "ZIP operation cancelled");
108
+ };
109
+ const toError = (cause) => cause instanceof Error ? cause : new Error("ZIP extraction failed");
package/dist/cli.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import { Cli, z } from "incur";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { runDoctor } from "./application/Doctor.js";
4
- import { runDirectAnalysis } from "./application/DirectAnalysis.js";
4
+ import { runDirectAnalysis, runProviderAnalysis, } from "./application/DirectAnalysis.js";
5
5
  import { runSetup } from "./application/Setup.js";
6
+ import { runUninstall } from "./application/Uninstall.js";
6
7
  import { PRODUCT_IDENTITY } from "./identity.js";
7
8
  import { createLogger, parseLogLevel } from "./logger.js";
9
+ import { parseConfig } from "./config.js";
10
+ import { exportEvidenceBundleCommand, importEvidenceBundleCommand, } from "./application/EvidenceBundleCommands.js";
11
+ import { importReferenceSource } from "./application/ReferenceSourceImport.js";
8
12
  /**
9
13
  * Build the one-shot Incur CLI without starting Hopper at import time.
10
14
  * Analysis commands acquire and close their own sessions; bare `mcp` and
@@ -15,18 +19,18 @@ export const createCli = () => {
15
19
  ? "silent"
16
20
  : parseLogLevel(process.env.REA_LOG_LEVEL));
17
21
  const cli = Cli.create(PRODUCT_IDENTITY.cliBinary, {
18
- version: "0.1.0",
19
- description: "Reverse engineer apps from your terminal or coding agent.",
22
+ version: process.env.REA_PACKAGE_VERSION ?? "0.0.0-development",
23
+ description: "Reverse engineer anything from your terminal or coding agent.",
20
24
  mcp: {
21
25
  command: PRODUCT_IDENTITY.mcpCommand,
22
- instructions: "Ask which app and feature the user wants to understand, then open the app and investigate it.",
26
+ instructions: "Ask what software, artifact, protocol, or behavior the user wants to understand, then choose the available investigation capabilities that can produce evidence.",
23
27
  },
24
28
  sync: {
25
29
  cwd: fileURLToPath(new URL("..", import.meta.url)),
26
30
  include: ["skills/*"],
27
31
  suggestions: [
28
- "understand how an app feature works",
29
- "decompile part of an app",
32
+ "understand how a software feature works",
33
+ "investigate an artifact or observed behavior",
30
34
  "check my REA setup",
31
35
  ],
32
36
  },
@@ -49,6 +53,17 @@ export const createCli = () => {
49
53
  }),
50
54
  run: ({ options }) => logCliCommand(logger, "doctor", () => runDoctor(options.target)),
51
55
  });
56
+ cli.command("uninstall", {
57
+ description: "Remove REA-owned agent configuration and skill files",
58
+ options: z.object({
59
+ purgeData: z
60
+ .boolean()
61
+ .default(false)
62
+ .describe("Also remove REA caches and state"),
63
+ }),
64
+ alias: { purgeData: "purge-data" },
65
+ run: ({ options }) => logCliCommand(logger, "uninstall", () => runUninstall(options.purgeData)),
66
+ });
52
67
  cli.command("analyze", {
53
68
  description: "Get an overview of an app",
54
69
  args: z.object({
@@ -64,8 +79,140 @@ export const createCli = () => {
64
79
  }),
65
80
  run: ({ args }) => logCliCommand(logger, "decompile", () => runDirectAnalysis(args.path, "procedure_pseudo_code", { procedure: args.address }, logger)),
66
81
  });
82
+ registerNativeCommands(cli, logger);
83
+ registerArtifactCommands(cli, logger);
84
+ registerEvidenceCommands(cli, logger);
85
+ registerReferenceSourceCommand(cli, logger);
67
86
  return cli;
68
87
  };
88
+ const registerReferenceSourceCommand = (cli, logger) => {
89
+ cli.command("import-reference-source", {
90
+ description: "Import a bounded source tree as historical reference only",
91
+ args: z.object({
92
+ root: z
93
+ .string()
94
+ .describe("Source root allowed by REA_REFERENCE_ROOTS_JSON"),
95
+ }),
96
+ run: ({ args }) => logCliCommand(logger, "import-reference-source", async () => {
97
+ const config = parseConfig(process.env);
98
+ if (!config.ok)
99
+ return { error: config.error._tag, message: config.error.message };
100
+ const imported = await importReferenceSource({
101
+ root: args.root,
102
+ caller: "rea-cli",
103
+ policy: config.value.referenceSourcePolicy,
104
+ importer: PRODUCT_IDENTITY.packageName,
105
+ importerVersion: null,
106
+ });
107
+ return imported.ok
108
+ ? imported.value
109
+ : { error: imported.error.code, message: imported.error.message };
110
+ }),
111
+ });
112
+ };
113
+ const registerArtifactCommands = (cli, logger) => {
114
+ cli.command("inventory-artifact", {
115
+ description: "Build a bounded deterministic artifact graph",
116
+ args: z.object({
117
+ path: z.string().describe("Application or package path"),
118
+ }),
119
+ options: z.object({
120
+ offset: z.number().int().min(0).default(0),
121
+ limit: z.number().int().min(1).max(500).default(100),
122
+ }),
123
+ run: ({ args, options }) => logCliCommand(logger, "inventory-artifact", () => runProviderAnalysis(args.path, "inventory_artifact", {
124
+ node_offset: options.offset,
125
+ node_limit: options.limit,
126
+ occurrence_offset: options.offset,
127
+ occurrence_limit: options.limit,
128
+ edge_offset: options.offset,
129
+ edge_limit: options.limit,
130
+ }, logger)),
131
+ });
132
+ cli.command("extract-artifact", {
133
+ description: "Extract explicitly selected artifact occurrences safely",
134
+ args: z.object({
135
+ path: z.string().describe("Application or package path"),
136
+ outputRoot: z.string().describe("Absent absolute output root"),
137
+ occurrenceIds: z
138
+ .array(z.string().regex(/^occ_[a-f0-9]{64}$/u))
139
+ .min(1)
140
+ .max(500),
141
+ }),
142
+ alias: { outputRoot: "output-root", occurrenceIds: "occurrence-ids" },
143
+ run: ({ args }) => logCliCommand(logger, "extract-artifact", () => runProviderAnalysis(args.path, "extract_artifact", {
144
+ approved: true,
145
+ output_root: args.outputRoot,
146
+ occurrence_ids: args.occurrenceIds,
147
+ }, logger)),
148
+ });
149
+ };
150
+ const registerNativeCommands = (cli, logger) => {
151
+ for (const [command, tool] of [
152
+ ["inspect-macho", "inspect_macho"],
153
+ ["inspect-signature", "inspect_signature"],
154
+ ["list-architectures", "list_architectures"],
155
+ ]) {
156
+ cli.command(command, {
157
+ description: `Run ${tool} without launching Hopper`,
158
+ args: z.object({ path: z.string().describe("Mach-O or app path") }),
159
+ run: ({ args }) => logCliCommand(logger, command, () => runProviderAnalysis(args.path, tool, {}, logger)),
160
+ });
161
+ }
162
+ cli.command("inspect-plist", {
163
+ description: "Parse app plist metadata without launching Hopper",
164
+ args: z.object({ path: z.string().describe("App or Mach-O path") }),
165
+ options: z.object({
166
+ relativePath: z.string().default("Contents/Info.plist"),
167
+ }),
168
+ alias: { relativePath: "relative-path" },
169
+ run: ({ args, options }) => logCliCommand(logger, "inspect-plist", () => runProviderAnalysis(args.path, "inspect_plist", { relative_path: options.relativePath }, logger)),
170
+ });
171
+ cli.command("demangle-swift", {
172
+ description: "Demangle a bounded Swift symbol batch without Hopper",
173
+ args: z.object({
174
+ path: z.string().describe("Artifact path used for evidence identity"),
175
+ symbols: z.array(z.string().min(1)).min(1).max(500),
176
+ }),
177
+ run: ({ args }) => logCliCommand(logger, "demangle-swift", () => runProviderAnalysis(args.path, "demangle_swift", { symbols: args.symbols }, logger)),
178
+ });
179
+ };
180
+ const registerEvidenceCommands = (cli, logger) => {
181
+ cli.command("evidence-import", {
182
+ description: "Validate and import a bounded local Evidence v2 bundle",
183
+ args: z.object({
184
+ path: z.string().describe("Evidence bundle JSON path"),
185
+ }),
186
+ run: ({ args }) => logCliCommand(logger, "evidence-import", async () => {
187
+ const config = parseConfig(process.env);
188
+ if (!config.ok)
189
+ return { error: config.error._tag, message: config.error.message };
190
+ const imported = await importEvidenceBundleCommand(args.path, config.value.evidenceFilePolicy);
191
+ return imported.ok
192
+ ? imported.value
193
+ : { error: imported.error._tag, message: imported.error.message };
194
+ }),
195
+ });
196
+ cli.command("evidence-export", {
197
+ description: "Validate and atomically export canonical Evidence v2 JSON",
198
+ args: z.object({
199
+ source: z.string().describe("Existing evidence bundle JSON path"),
200
+ output: z.string().describe("Canonical output JSON path"),
201
+ }),
202
+ options: z.object({
203
+ overwrite: z.boolean().default(false).describe("Replace output file"),
204
+ }),
205
+ run: ({ args, options }) => logCliCommand(logger, "evidence-export", async () => {
206
+ const config = parseConfig(process.env);
207
+ if (!config.ok)
208
+ return { error: config.error._tag, message: config.error.message };
209
+ const exported = await exportEvidenceBundleCommand(args.source, args.output, options.overwrite, config.value.evidenceFilePolicy);
210
+ return exported.ok
211
+ ? exported.value
212
+ : { error: exported.error._tag, message: exported.error.message };
213
+ }),
214
+ });
215
+ };
69
216
  const logCliCommand = async (logger, command, execute) => {
70
217
  const startedAt = performance.now();
71
218
  try {
package/dist/config.js CHANGED
@@ -2,6 +2,9 @@ import { z } from "zod";
2
2
  import { ConfigurationError } from "./domain/errors.js";
3
3
  import { err, ok } from "./domain/result.js";
4
4
  const DEFAULT_HOPPER_LAUNCHER_PATH = "/Applications/Hopper Disassembler.app/Contents/MacOS/hopper";
5
+ const defaultHopperLauncherPath = () => process.platform === "linux"
6
+ ? "/opt/hopper/bin/Hopper"
7
+ : DEFAULT_HOPPER_LAUNCHER_PATH;
5
8
  const environmentSchema = z.object({
6
9
  HOPPER_LAUNCHER_PATH: z.string().min(1).optional(),
7
10
  HOPPER_TARGET_PATH: z.string().min(1).optional(),
@@ -11,9 +14,15 @@ const environmentSchema = z.object({
11
14
  .enum(["trace", "debug", "info", "warn", "error", "fatal", "silent"])
12
15
  .default("info"),
13
16
  REA_PROCESS_CAPTURE_ENABLED: z.enum(["true", "false"]).default("false"),
17
+ REA_PROCESS_ALLOW_EXTERNAL_NETWORK: z
18
+ .enum(["true", "false"])
19
+ .default("false"),
14
20
  REA_PROCESS_EXECUTABLE_ROOTS_JSON: z.string().default("[]"),
15
21
  REA_PROCESS_WORKING_ROOTS_JSON: z.string().default("[]"),
16
22
  REA_PROCESS_ALLOWED_ENV_JSON: z.string().default("[]"),
23
+ REA_EVIDENCE_ROOTS_JSON: z.string().default("[]"),
24
+ REA_REFERENCE_ROOTS_JSON: z.string().default("[]"),
25
+ REA_REFERENCE_SECRET_PATTERNS_JSON: z.string().default("[]"),
17
26
  });
18
27
  const parseStringArray = (encoded, name) => {
19
28
  try {
@@ -69,9 +78,18 @@ export const parseConfig = (environment) => {
69
78
  const allowedEnvironment = parseStringArray(parsedEnvironment.data.REA_PROCESS_ALLOWED_ENV_JSON, "REA_PROCESS_ALLOWED_ENV_JSON");
70
79
  if (!allowedEnvironment.ok)
71
80
  return allowedEnvironment;
81
+ const evidenceRoots = parseStringArray(parsedEnvironment.data.REA_EVIDENCE_ROOTS_JSON, "REA_EVIDENCE_ROOTS_JSON");
82
+ if (!evidenceRoots.ok)
83
+ return evidenceRoots;
84
+ const referenceRoots = parseStringArray(parsedEnvironment.data.REA_REFERENCE_ROOTS_JSON, "REA_REFERENCE_ROOTS_JSON");
85
+ if (!referenceRoots.ok)
86
+ return referenceRoots;
87
+ const secretPatterns = parseStringArray(parsedEnvironment.data.REA_REFERENCE_SECRET_PATTERNS_JSON, "REA_REFERENCE_SECRET_PATTERNS_JSON");
88
+ if (!secretPatterns.ok)
89
+ return secretPatterns;
72
90
  return ok({
73
91
  hopperLauncherPath: parsedEnvironment.data.HOPPER_LAUNCHER_PATH ??
74
- DEFAULT_HOPPER_LAUNCHER_PATH,
92
+ defaultHopperLauncherPath(),
75
93
  hopperTargetPath: parsedEnvironment.data.HOPPER_TARGET_PATH,
76
94
  hopperTargetKind: parsedEnvironment.data.HOPPER_TARGET_KIND,
77
95
  hopperLoaderArgs: parsedArgs.data,
@@ -81,6 +99,22 @@ export const parseConfig = (environment) => {
81
99
  executableRoots: executableRoots.value,
82
100
  workingRoots: workingRoots.value,
83
101
  allowedEnvironment: allowedEnvironment.value,
102
+ allowExternalNetwork: parsedEnvironment.data.REA_PROCESS_ALLOW_EXTERNAL_NETWORK === "true",
103
+ },
104
+ evidenceFilePolicy: {
105
+ roots: evidenceRoots.value,
106
+ maxBytes: 64 * 1024 * 1024,
107
+ maxDepth: 64,
108
+ maxStringLength: 1024 * 1024,
109
+ maxNodes: 1_000_000,
110
+ },
111
+ referenceSourcePolicy: {
112
+ roots: referenceRoots.value,
113
+ secretPatterns: secretPatterns.value,
114
+ maxBytes: 16 * 1024 * 1024,
115
+ maxEntries: 10_000,
116
+ maxDepth: 32,
117
+ maxPathBytes: 4_096,
84
118
  },
85
119
  });
86
120
  };
@@ -0,0 +1,95 @@
1
+ import { createEvidence } from "../domain/evidence.js";
2
+ import { jsonValueSchema } from "../domain/jsonValue.js";
3
+ const inventory = (digit) => {
4
+ const sha = digit.repeat(64);
5
+ const artifactId = `art_${sha}`;
6
+ const occurrenceId = `occ_${sha}`;
7
+ return jsonValueSchema.parse({
8
+ manifest: {
9
+ schema_version: 1,
10
+ manifest_id: `agm_${sha}`,
11
+ root_artifact_id: artifactId,
12
+ root_sha256: sha,
13
+ root_format: "file",
14
+ graph_sha256: sha,
15
+ node_count: 1,
16
+ occurrence_count: 1,
17
+ edge_count: 0,
18
+ },
19
+ nodes: {
20
+ items: [
21
+ {
22
+ artifact_id: artifactId,
23
+ kind: "resource",
24
+ format: "file",
25
+ sha256: sha,
26
+ size: 1,
27
+ media_type: null,
28
+ architecture: null,
29
+ executable: false,
30
+ content_state: "materialized",
31
+ limitations: [],
32
+ },
33
+ ],
34
+ offset: 0,
35
+ limit: 100,
36
+ total: 1,
37
+ next_offset: null,
38
+ },
39
+ occurrences: {
40
+ items: [
41
+ {
42
+ occurrence_id: occurrenceId,
43
+ artifact_id: artifactId,
44
+ parent_occurrence_id: null,
45
+ logical_path: ".",
46
+ entry_kind: "file",
47
+ declared_size: 1,
48
+ compressed_size: null,
49
+ executable: false,
50
+ encrypted: false,
51
+ hash_status: "verified",
52
+ source_location: null,
53
+ limitations: [],
54
+ },
55
+ ],
56
+ offset: 0,
57
+ limit: 100,
58
+ total: 1,
59
+ next_offset: null,
60
+ },
61
+ edges: { items: [], offset: 0, limit: 100, total: 0, next_offset: null },
62
+ limits: {
63
+ max_entries: 100,
64
+ max_total_bytes: 1024,
65
+ max_entry_bytes: 1024,
66
+ max_compression_ratio: 10,
67
+ max_depth: 10,
68
+ max_path_bytes: 256,
69
+ },
70
+ provenance: [],
71
+ limitations: [],
72
+ });
73
+ };
74
+ const provider = {
75
+ id: "rea-artifact",
76
+ name: "REA artifact graph",
77
+ version: "1",
78
+ };
79
+ /** Canonical complete inputs used to advertise artifact comparison. */
80
+ export const ARTIFACT_COMPARISON_EXAMPLE = {
81
+ left: createEvidence(undefined, provider, {
82
+ operation: "inventory_artifact",
83
+ parameters: {},
84
+ result: inventory("0"),
85
+ confidence: "observed",
86
+ authority: "shipped-artifact",
87
+ }),
88
+ right: createEvidence(undefined, provider, {
89
+ operation: "inventory_artifact",
90
+ parameters: {},
91
+ result: inventory("1"),
92
+ confidence: "observed",
93
+ authority: "shipped-artifact",
94
+ }),
95
+ };
@@ -0,0 +1,84 @@
1
+ import { z } from "zod";
2
+ import { artifactOutputSchemas } from "./toolOutputSchemas.js";
3
+ import { jsonValueSchema } from "../domain/jsonValue.js";
4
+ const pageInput = {
5
+ node_offset: z.number().int().min(0).default(0),
6
+ node_limit: z.number().int().min(1).max(500).default(100),
7
+ occurrence_offset: z.number().int().min(0).default(0),
8
+ occurrence_limit: z.number().int().min(1).max(500).default(100),
9
+ edge_offset: z.number().int().min(0).default(0),
10
+ edge_limit: z.number().int().min(1).max(500).default(100),
11
+ };
12
+ const traversalLimitsInput = {
13
+ max_entries: z.number().int().min(1).max(1_000_000).default(10_000),
14
+ max_total_bytes: z
15
+ .number()
16
+ .int()
17
+ .min(1)
18
+ .max(Number.MAX_SAFE_INTEGER)
19
+ .default(1_073_741_824),
20
+ max_entry_bytes: z
21
+ .number()
22
+ .int()
23
+ .min(1)
24
+ .max(Number.MAX_SAFE_INTEGER)
25
+ .default(268_435_456),
26
+ max_compression_ratio: z.number().min(1).max(100_000).default(1_000),
27
+ max_depth: z.number().int().min(0).max(100).default(20),
28
+ max_path_bytes: z.number().int().min(1).max(65_535).default(4_096),
29
+ };
30
+ /** Exact caller boundary for deterministic artifact inventory. */
31
+ export const artifactInventoryInputSchema = z.object({
32
+ ...pageInput,
33
+ ...traversalLimitsInput,
34
+ });
35
+ /** Exact caller boundary for approved artifact extraction. */
36
+ export const artifactExtractionInputSchema = z.object({
37
+ approved: z.literal(true),
38
+ output_root: z.string().min(1).max(4_096),
39
+ occurrence_ids: z
40
+ .array(z.string().regex(/^occ_[a-f0-9]{64}$/u))
41
+ .min(1)
42
+ .max(500),
43
+ offset: z.number().int().min(0).default(0),
44
+ limit: z.number().int().min(1).max(500).default(100),
45
+ ...traversalLimitsInput,
46
+ });
47
+ const exampleInputSchema = z.record(z.string(), jsonValueSchema);
48
+ const examples = {
49
+ inventory_artifact: {},
50
+ extract_artifact: {
51
+ approved: true,
52
+ output_root: "/tmp/rea-extracted",
53
+ occurrence_ids: [`occ_${"0".repeat(64)}`],
54
+ },
55
+ };
56
+ const artifact = (name, description, inputSchema, mutatesFilesystem) => {
57
+ const outputSchema = artifactOutputSchemas[name];
58
+ if (outputSchema === undefined)
59
+ throw new Error(`Missing artifact output schema for ${name}`);
60
+ return {
61
+ name,
62
+ description,
63
+ kind: "artifact-provider",
64
+ inputSchema,
65
+ outputSchema,
66
+ annotations: {
67
+ readOnlyHint: !mutatesFilesystem,
68
+ destructiveHint: false,
69
+ idempotentHint: true,
70
+ openWorldHint: false,
71
+ },
72
+ examples: [
73
+ {
74
+ title: `Example ${name.replaceAll("_", " ")} request`,
75
+ input: exampleInputSchema.parse(examples[name] ?? {}),
76
+ },
77
+ ],
78
+ };
79
+ };
80
+ /** Artifact-graph inventory and explicitly approved safe extraction contracts. */
81
+ export const ARTIFACT_TOOL_CONTRACTS = [
82
+ artifact("inventory_artifact", "Inventory the active application or package as a deterministic, content-addressed artifact graph. Returns bounded node and edge pages without extracting or mounting by default.", artifactInventoryInputSchema, false),
83
+ artifact("extract_artifact", "Extract selected graph artifacts beneath an explicit output root. Requires approval, rejects traversal and symlink escapes, never overwrites, enforces bomb limits, and verifies cleanup.", artifactExtractionInputSchema, true),
84
+ ];
@@ -56,6 +56,10 @@ export const enhancedInputSchemas = {
56
56
  case_sensitive: z.boolean().default(false),
57
57
  limit: z.number().int().min(1).max(100).default(20),
58
58
  max_operations: z.number().int().min(1).max(100).default(20),
59
+ unknown_registry_approved: z
60
+ .literal(true)
61
+ .optional()
62
+ .describe("Explicit approval to record bounded residuals durably"),
59
63
  }),
60
64
  };
61
65
  /** Runtime parser for dispatching only the eight closed enhanced names. */
@@ -0,0 +1,57 @@
1
+ import { enhancedInputSchemas } from "./enhancedInputs.js";
2
+ import { createEvidence } from "../domain/evidence.js";
3
+ import { jsonValueSchema } from "../domain/jsonValue.js";
4
+ const bounded = (items) => ({
5
+ items,
6
+ total: items.length,
7
+ returned: items.length,
8
+ truncated: false,
9
+ next_offset: null,
10
+ });
11
+ const dossier = (text) => jsonValueSchema.parse({
12
+ procedure: {
13
+ address: "0x1000",
14
+ name: "main",
15
+ signature: "int main(void)",
16
+ locals: [],
17
+ },
18
+ pseudocode: {
19
+ text,
20
+ total_chars: [...text].length,
21
+ returned_chars: [...text].length,
22
+ truncated: false,
23
+ next_offset: null,
24
+ },
25
+ assembly: bounded([]),
26
+ comments: bounded([]),
27
+ callers: bounded([]),
28
+ callees: bounded([]),
29
+ incoming_references: bounded([]),
30
+ outgoing_references: bounded([]),
31
+ referenced_strings: bounded([]),
32
+ referenced_names: bounded([]),
33
+ basic_blocks: bounded([{ start: "0x1000", end: "0x1001", successors: [] }]),
34
+ instruction_scan: { scanned: 1, truncated: false },
35
+ });
36
+ const observe = (digit, text) => createEvidence({
37
+ path: `/tmp/function-${digit}`,
38
+ sha256: digit.repeat(64),
39
+ format: "mach-o",
40
+ }, {
41
+ id: "rea-workflow",
42
+ name: "REA composed investigation workflow",
43
+ version: "1",
44
+ }, {
45
+ operation: "analyze_function",
46
+ parameters: enhancedInputSchemas.analyze_function.parse({
47
+ procedure: "main",
48
+ }),
49
+ result: dossier(text),
50
+ confidence: "derived",
51
+ authority: "shipped-artifact",
52
+ });
53
+ /** Canonical explicit function pair used in public contract examples. */
54
+ export const FUNCTION_COMPARISON_EXAMPLE = {
55
+ left: observe("0", "return 0;"),
56
+ right: observe("1", "return 1;"),
57
+ };