rea-agents 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +14 -4
  2. package/bridge/ghidra/ReaGhidraBridge.java +477 -4
  3. package/bridge/hopper_bridge.py +54 -4
  4. package/dist/application/AnalysisContextQueries.js +130 -0
  5. package/dist/application/AnalysisSnapshotCache.js +4 -2
  6. package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
  7. package/dist/application/BinarySession.js +5 -4
  8. package/dist/application/BinarySessionComposition.js +21 -0
  9. package/dist/application/BinarySessionRecords.js +79 -9
  10. package/dist/application/CapabilityInventory.js +16 -2
  11. package/dist/application/ClientRegistrationStatus.js +4 -1
  12. package/dist/application/DirectAnalysis.js +6 -2
  13. package/dist/application/EnhancedTools.js +37 -7
  14. package/dist/application/EvidenceLedger.js +45 -16
  15. package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
  16. package/dist/application/LazyAnalysisProvider.js +77 -0
  17. package/dist/application/LoopbackReplay.js +1 -1
  18. package/dist/application/NativeApiInspection.js +80 -0
  19. package/dist/application/SessionProviderRouter.js +6 -6
  20. package/dist/application/SetupClientConfiguration.js +4 -2
  21. package/dist/application/runtime.js +26 -9
  22. package/dist/browser/PlaywrightBrowserScenarioProvider.js +8 -2
  23. package/dist/catalogIdentity.js +3 -3
  24. package/dist/cli/artifactCommands.js +5 -3
  25. package/dist/cli/coreAnalysisCommands.js +41 -0
  26. package/dist/cliCommandNames.js +1 -0
  27. package/dist/contracts/artifactToolContracts.js +8 -3
  28. package/dist/contracts/enhancedInputs.js +10 -0
  29. package/dist/contracts/functionWorkflowToolContracts.js +30 -0
  30. package/dist/contracts/promptContracts.js +14 -15
  31. package/dist/contracts/toolContractExamples.js +3 -0
  32. package/dist/contracts/toolContracts.js +21 -4
  33. package/dist/contracts/toolEffects.js +4 -0
  34. package/dist/contracts/toolOutputSchemaGroups.js +50 -10
  35. package/dist/domain/analysisErrorPresentation.js +8 -2
  36. package/dist/domain/analysisErrorProjection.js +1 -0
  37. package/dist/domain/bytecodeProvider.js +179 -0
  38. package/dist/domain/conformancePackage.js +169 -0
  39. package/dist/domain/conformanceReplay.js +104 -0
  40. package/dist/domain/conformanceTrustGate.js +193 -0
  41. package/dist/domain/customProtocolCapture.js +137 -0
  42. package/dist/domain/eventProcessTree.js +214 -0
  43. package/dist/domain/evidenceBundle.js +47 -0
  44. package/dist/domain/hopperStartupFailure.js +1 -1
  45. package/dist/domain/hopperValues.js +4 -1
  46. package/dist/domain/inputIssueProjection.js +7 -1
  47. package/dist/domain/javascriptSemanticAnalysis.js +8 -12
  48. package/dist/domain/javascriptSourceParser.js +15 -0
  49. package/dist/domain/javascriptStaticAnalysis.js +8 -12
  50. package/dist/domain/mobileApplicationInvestigation.js +159 -0
  51. package/dist/domain/nativeApiBoundary.js +130 -0
  52. package/dist/domain/objcSwiftMetadata.js +150 -0
  53. package/dist/domain/packageAnalysis.js +123 -0
  54. package/dist/domain/peInspection.js +196 -0
  55. package/dist/domain/protocolCapture.js +243 -0
  56. package/dist/dotnet/ManagedStaticProvider.js +34 -5
  57. package/dist/generatedMcpToolCatalog.js +37 -0
  58. package/dist/generatedPackageMetadata.js +4 -4
  59. package/dist/ghidra/GhidraDefaults.js +1 -1
  60. package/dist/ghidra/GhidraFunctionValues.js +27 -1
  61. package/dist/ghidra/GhidraProviderCapabilities.js +40 -38
  62. package/dist/hopper/HopperClient.js +26 -2
  63. package/dist/hopper/HopperProvider.js +42 -30
  64. package/dist/hopper/HopperRequestQueue.js +32 -4
  65. package/dist/hopper/HopperResponseStream.js +2 -2
  66. package/dist/hopper/protocol.js +63 -21
  67. package/dist/main/transport.js +2 -5
  68. package/dist/main.js +14 -12
  69. package/dist/mcpDoctor.js +2 -2
  70. package/dist/mcpStartupPolicy.js +14 -0
  71. package/dist/server/LazyToolCatalog.js +134 -0
  72. package/dist/server/createServer.js +64 -171
  73. package/dist/server/hydrateServerTools.js +191 -0
  74. package/dist/server/registerEnhancedTools.js +13 -3
  75. package/dist/server/registerEvidenceResources.js +50 -101
  76. package/dist/server/registerInvestigationTools.js +1 -2
  77. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
  78. package/dist/server/registerReconstructionObligationLedgerResource.js +0 -7
  79. package/dist/server/registerReconstructionReadinessResource.js +0 -7
  80. package/dist/server/registerSessionRecordTools.js +82 -12
  81. package/dist/server/registerSessionStatusTool.js +1 -1
  82. package/dist/server/registerSessionTools.js +21 -1
  83. package/dist/server/toolRegistrationOptions.js +21 -6
  84. package/dist/server/toolResult.js +12 -0
  85. package/package.json +21 -10
  86. package/scripts/package-runner-bootstrap.mjs +72 -0
  87. package/scripts/rea.mjs +11 -1
  88. package/skills/reverse-engineer-anything/SKILL.md +10 -3
@@ -0,0 +1,243 @@
1
+ import { z } from "zod";
2
+ import { jsonValueSchema } from "./jsonValue.js";
3
+ /** Supported protocol families for structured protocol capture. */
4
+ export const protocolFamilySchema = z.enum([
5
+ "grpc",
6
+ "protobuf",
7
+ "json-rpc",
8
+ "messagepack",
9
+ ]);
10
+ /** A decoded field from a protocol message. */
11
+ export const decodedFieldSchema = z.strictObject({
12
+ /** Field name or path within the message structure. */
13
+ path: z.string().min(1),
14
+ /** Wire type or encoding hint. */
15
+ wire_type: z.string().nullable(),
16
+ /** Decoded field value. */
17
+ value: jsonValueSchema.nullable(),
18
+ /** Whether this field value was inferred rather than decoded. */
19
+ inferred: z.boolean().default(false),
20
+ });
21
+ /** Schema inference hypothesis for a repeated message pattern. */
22
+ export const schemaHypothesisSchema = z.strictObject({
23
+ /** Hypothesized field name. */
24
+ field_name: z.string().min(1),
25
+ /** Inferred type. */
26
+ inferred_type: z.string().min(1),
27
+ /** Confidence: 0-1 */
28
+ confidence: z.number().min(0).max(1),
29
+ /** Supporting evidence count. */
30
+ evidence_count: z.number().int().nonnegative(),
31
+ });
32
+ /** Result of decoding a single protocol message. */
33
+ export const protocolMessageSchema = z.strictObject({
34
+ /** Monotonic sequence number. */
35
+ sequence: z.number().int().nonnegative(),
36
+ /** Timestamp in milliseconds. */
37
+ at_ms: z.number().int().nonnegative(),
38
+ /** Protocol family. */
39
+ family: protocolFamilySchema,
40
+ /** Direction of the message. */
41
+ direction: z.enum(["request", "response"]),
42
+ /** Transport endpoint or path (e.g. gRPC method path). */
43
+ endpoint: z.string().nullable(),
44
+ /** Content type of the raw payload. */
45
+ content_type: z.string().nullable(),
46
+ /** Raw payload bytes as base64. */
47
+ raw_payload: z.string().nullable(),
48
+ /** Decoded fields from the payload. */
49
+ decoded_fields: z.array(decodedFieldSchema).default([]),
50
+ /** Inferred schema hypotheses. */
51
+ schema_hypotheses: z.array(schemaHypothesisSchema).default([]),
52
+ /** Whether decoding was truncated. */
53
+ truncated: z.boolean().default(false),
54
+ /** Whether credentials were detected and redacted. */
55
+ credentials_redacted: z.boolean().default(false),
56
+ });
57
+ /** A captured protocol session with messages and inferred schemas. */
58
+ export const protocolCaptureSchema = z.strictObject({
59
+ /** Protocol family for this capture. */
60
+ family: protocolFamilySchema,
61
+ /** Captured messages in order. */
62
+ messages: z.array(protocolMessageSchema).min(0).max(10_000),
63
+ /** Aggregated schema hypotheses across all messages. */
64
+ inferred_schema: z.array(schemaHypothesisSchema).default([]),
65
+ /** Whether any message was truncated. */
66
+ has_truncated: z.boolean().default(false),
67
+ /** Whether credentials were detected and redacted. */
68
+ credentials_detected: z.boolean().default(false),
69
+ });
70
+ /** Classification of a byte sequence as a known protocol family. */
71
+ export function classifyProtocolFamily(contentType, path) {
72
+ if (contentType) {
73
+ if (contentType.includes("application/grpc"))
74
+ return "grpc";
75
+ if (contentType.includes("application/x-protobuf"))
76
+ return "protobuf";
77
+ if (contentType.includes("application/json-rpc"))
78
+ return "json-rpc";
79
+ if (contentType.includes("application/msgpack") ||
80
+ contentType.includes("application/x-msgpack"))
81
+ return "messagepack";
82
+ }
83
+ if (path) {
84
+ if (path.startsWith("/grpc.") || path.includes("/grpc/"))
85
+ return "grpc";
86
+ if (path.includes("/json-rpc") || path.includes("/jsonrpc"))
87
+ return "json-rpc";
88
+ }
89
+ return null;
90
+ }
91
+ /** Credential-like field names that should be redacted. */
92
+ const CREDENTIAL_PATTERNS = [
93
+ "password",
94
+ "passwd",
95
+ "secret",
96
+ "token",
97
+ "api_key",
98
+ "apikey",
99
+ "authorization",
100
+ "auth",
101
+ "credential",
102
+ "private_key",
103
+ ];
104
+ /** Check if a field path looks like it may contain credentials. */
105
+ export function looksLikeCredential(path) {
106
+ const lower = path.toLowerCase();
107
+ return CREDENTIAL_PATTERNS.some((p) => lower.includes(p));
108
+ }
109
+ /** Decode a JSON-RPC message from raw payload bytes. */
110
+ export function decodeJsonRpc(rawPayload) {
111
+ const text = new TextDecoder().decode(rawPayload);
112
+ const parsed = JSON.parse(text);
113
+ const fields = [];
114
+ if (typeof parsed === "object" && parsed !== null) {
115
+ for (const [key, value] of Object.entries(parsed)) {
116
+ if (looksLikeCredential(key)) {
117
+ fields.push({
118
+ path: key,
119
+ wire_type: "json",
120
+ value: "[REDACTED]",
121
+ inferred: false,
122
+ });
123
+ }
124
+ else {
125
+ fields.push({
126
+ path: key,
127
+ wire_type: "json",
128
+ value: value,
129
+ inferred: false,
130
+ });
131
+ }
132
+ }
133
+ }
134
+ return { decoded_fields: fields, truncated: false };
135
+ }
136
+ /** Decode a MessagePack message (simplified: only checks basic structure). */
137
+ export function decodeMessagePack(rawPayload) {
138
+ const fields = [];
139
+ // MessagePack uses a single-byte type tag
140
+ if (rawPayload.length === 0) {
141
+ return { decoded_fields: [], truncated: false };
142
+ }
143
+ const tag = rawPayload[0];
144
+ // Map type: 0x80-0x8f (fixmap), 0xde (map16), 0xdf (map32)
145
+ if ((tag >= 0x80 && tag <= 0x8f) || tag === 0xde || tag === 0xdf) {
146
+ fields.push({
147
+ path: "type",
148
+ wire_type: "msgpack",
149
+ value: "map",
150
+ inferred: false,
151
+ });
152
+ }
153
+ // Array type: 0x90-0x9f (fixarray), 0xdc (array16), 0xdd (array32)
154
+ else if ((tag >= 0x90 && tag <= 0x9f) || tag === 0xdc || tag === 0xdd) {
155
+ fields.push({
156
+ path: "type",
157
+ wire_type: "msgpack",
158
+ value: "array",
159
+ inferred: false,
160
+ });
161
+ }
162
+ else {
163
+ fields.push({
164
+ path: "type",
165
+ wire_type: "msgpack",
166
+ value: "unknown",
167
+ inferred: true,
168
+ });
169
+ }
170
+ return {
171
+ decoded_fields: fields,
172
+ truncated: rawPayload.length > 10_000,
173
+ };
174
+ }
175
+ /** Decode a gRPC frame (5-byte header + protobuf payload). */
176
+ export function decodeGrpcFrame(rawPayload) {
177
+ // gRPC uses a 5-byte prefix: 1 byte compressed flag + 4 byte length
178
+ if (rawPayload.length < 5) {
179
+ return {
180
+ decoded_fields: [
181
+ {
182
+ path: "error",
183
+ wire_type: "grpc",
184
+ value: "frame too short",
185
+ inferred: false,
186
+ },
187
+ ],
188
+ truncated: false,
189
+ };
190
+ }
191
+ const compressed = rawPayload[0] !== 0;
192
+ const length = (rawPayload[1] << 24) |
193
+ (rawPayload[2] << 16) |
194
+ (rawPayload[3] << 8) |
195
+ rawPayload[4];
196
+ return {
197
+ decoded_fields: [
198
+ {
199
+ path: "compressed",
200
+ wire_type: "grpc",
201
+ value: compressed,
202
+ inferred: false,
203
+ },
204
+ {
205
+ path: "message_length",
206
+ wire_type: "grpc",
207
+ value: length,
208
+ inferred: false,
209
+ },
210
+ ],
211
+ truncated: length > 10_000,
212
+ };
213
+ }
214
+ /** Infer schema hypotheses from a set of decoded messages. */
215
+ export function inferMessageSchema(messages) {
216
+ const fieldCounts = new Map();
217
+ for (const msg of messages) {
218
+ for (const field of msg.decoded_fields) {
219
+ const existing = fieldCounts.get(field.path);
220
+ if (existing) {
221
+ existing.count++;
222
+ if (field.wire_type)
223
+ existing.types.add(field.wire_type);
224
+ }
225
+ else {
226
+ fieldCounts.set(field.path, {
227
+ count: 1,
228
+ types: new Set(field.wire_type ?? "unknown"),
229
+ });
230
+ }
231
+ }
232
+ }
233
+ const hypotheses = [];
234
+ for (const [field_name, data] of fieldCounts) {
235
+ hypotheses.push({
236
+ field_name,
237
+ inferred_type: [...data.types].join("|"),
238
+ confidence: Math.min(1, data.count / messages.length || 0),
239
+ evidence_count: data.count,
240
+ });
241
+ }
242
+ return hypotheses;
243
+ }
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
2
3
  import { readFile, stat } from "node:fs/promises";
3
4
  import { createAnalysisExecution, } from "../application/AnalysisProvider.js";
4
5
  import { MANAGED_STATIC_PROVIDER } from "../application/InvestigationProviders.js";
@@ -48,6 +49,7 @@ export class ManagedStaticProvider {
48
49
  }
49
50
  class ManagedStaticClient {
50
51
  target;
52
+ #snapshotBytes;
51
53
  constructor(target) {
52
54
  this.target = target;
53
55
  }
@@ -65,12 +67,18 @@ class ManagedStaticClient {
65
67
  const metadata = await stat(this.target.path);
66
68
  if (metadata.size > maxFileBytes)
67
69
  return err(new AnalysisCapabilityUnavailableError(IDENTITY.id, operation, `Artifact size ${String(metadata.size)} exceeds max_file_bytes ${String(maxFileBytes)}.`));
68
- const bytes = await readFile(this.target.path, options?.signal === undefined ? undefined : { signal: options.signal });
69
- if (bytes.length > maxFileBytes)
70
+ const snapshot = this.#snapshotBytes;
71
+ const observed = snapshot === undefined
72
+ ? await readManagedSnapshot(this.target.path, options?.signal)
73
+ : await hashManagedSource(this.target.path, options?.signal);
74
+ if (observed.byteLength > maxFileBytes)
70
75
  return err(new AnalysisCapabilityUnavailableError(IDENTITY.id, operation, `Artifact grew beyond max_file_bytes ${String(maxFileBytes)} while it was read.`));
71
- const observedDigest = createHash("sha256").update(bytes).digest("hex");
72
- if (observedDigest !== this.target.sha256)
73
- return err(new EvidenceIntegrityError(`Managed artifact digest changed after open: expected ${this.target.sha256}, observed ${observedDigest} at ${this.target.path}`));
76
+ if (observed.sha256 !== this.target.sha256)
77
+ return err(new EvidenceIntegrityError(`Managed artifact digest changed after open: expected ${this.target.sha256}, observed ${observed.sha256} at ${this.target.path}`));
78
+ const bytes = snapshot ?? observed.bytes;
79
+ if (bytes === undefined)
80
+ throw new TypeError("Managed snapshot bytes are unavailable");
81
+ this.#snapshotBytes = bytes;
74
82
  const result = inspectManagedOperation(operation, parameters, bytes, this.target);
75
83
  return ok(createAnalysisExecution(result, IDENTITY, {
76
84
  rawResult: null,
@@ -86,9 +94,30 @@ class ManagedStaticClient {
86
94
  }
87
95
  }
88
96
  close() {
97
+ this.#snapshotBytes = undefined;
89
98
  return Promise.resolve();
90
99
  }
91
100
  }
101
+ const readManagedSnapshot = async (path, signal) => {
102
+ const bytes = await readFile(path, signal === undefined ? undefined : { signal });
103
+ return {
104
+ byteLength: bytes.length,
105
+ sha256: createHash("sha256").update(bytes).digest("hex"),
106
+ bytes,
107
+ };
108
+ };
109
+ const hashManagedSource = async (path, signal) => {
110
+ const digest = createHash("sha256");
111
+ let byteLength = 0;
112
+ const stream = createReadStream(path, signal === undefined ? undefined : { signal });
113
+ for await (const chunk of stream) {
114
+ if (!Buffer.isBuffer(chunk))
115
+ throw new TypeError("Managed source stream returned non-buffer data");
116
+ byteLength += chunk.length;
117
+ digest.update(chunk);
118
+ }
119
+ return { byteLength, sha256: digest.digest("hex") };
120
+ };
92
121
  const isManagedOperation = (operation) => MANAGED_TOOL_CONTRACTS.some(({ name }) => name === operation);
93
122
  const limitationsFor = (operation) => Object.freeze(operation === "inspect_managed_artifact"
94
123
  ? [