claude-flow 3.59.17 → 3.60.1
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/.claude-plugin/marketplace.json +3 -3
- package/README.md +2 -1
- package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/session-manager.js +4 -1
- package/node_modules/@claude-flow/mcp/dist/session-manager.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/package.json +3 -2
- package/v3/@claude-flow/cli/README.md +2 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/agntcy/publish.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/agntcy/runtime.d.ts +4 -1
- package/v3/@claude-flow/cli/dist/src/commands/agntcy/runtime.js +15 -2
- package/v3/@claude-flow/cli/dist/src/commands/agntcy/swarm-join.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/agntcy/transport.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/cleanup.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/update.js +5 -3
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +4 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.js +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +17 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/daa-tools.js +20 -12
- package/v3/@claude-flow/cli/dist/src/mcp-tools/github-tools.js +78 -75
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +11 -17
- package/v3/@claude-flow/cli/dist/src/mcp-tools/rure-tools.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/rure-tools.js +359 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +17 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +18 -9
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.d.ts +12 -0
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +21 -1
- package/v3/@claude-flow/cli/dist/src/rure/aidefence-gate.d.ts +25 -0
- package/v3/@claude-flow/cli/dist/src/rure/aidefence-gate.js +63 -0
- package/v3/@claude-flow/cli/dist/src/rure/analyze-js-app.d.ts +17 -0
- package/v3/@claude-flow/cli/dist/src/rure/analyze-js-app.js +26 -0
- package/v3/@claude-flow/cli/dist/src/rure/binary-parse.d.ts +38 -0
- package/v3/@claude-flow/cli/dist/src/rure/binary-parse.js +347 -0
- package/v3/@claude-flow/cli/dist/src/rure/dotnet-parse.d.ts +30 -0
- package/v3/@claude-flow/cli/dist/src/rure/dotnet-parse.js +299 -0
- package/v3/@claude-flow/cli/dist/src/rure/evm-parse.d.ts +52 -0
- package/v3/@claude-flow/cli/dist/src/rure/evm-parse.js +233 -0
- package/v3/@claude-flow/cli/dist/src/rure/extract-asar.d.ts +33 -0
- package/v3/@claude-flow/cli/dist/src/rure/extract-asar.js +70 -0
- package/v3/@claude-flow/cli/dist/src/rure/find-feature.d.ts +26 -0
- package/v3/@claude-flow/cli/dist/src/rure/find-feature.js +69 -0
- package/v3/@claude-flow/cli/dist/src/rure/ts-walk.d.ts +47 -0
- package/v3/@claude-flow/cli/dist/src/rure/ts-walk.js +158 -0
- package/v3/@claude-flow/cli/dist/src/rure/walk-files.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/rure/walk-files.js +71 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +17 -3
- package/v3/@claude-flow/cli/dist/src/update/checker.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/update/checker.js +4 -2
- package/v3/@claude-flow/cli/package.json +4 -3
- package/.claude/.proven-config-version +0 -1
- package/.claude/proven-config.json +0 -42
|
@@ -899,7 +899,7 @@ export const neuralTools = [
|
|
|
899
899
|
properties: {
|
|
900
900
|
modelId: { type: 'string', description: 'Model ID to compress' },
|
|
901
901
|
method: { type: 'string', enum: ['quantize', 'prune', 'distill'], description: 'Compression method' },
|
|
902
|
-
targetSize: { type: 'number', description: '
|
|
902
|
+
targetSize: { type: 'number', description: 'Not used: quantize (#4195) and prune (#4423) are unavailable, and distill merges by similarity' },
|
|
903
903
|
},
|
|
904
904
|
},
|
|
905
905
|
handler: async (input) => {
|
|
@@ -910,7 +910,6 @@ export const neuralTools = [
|
|
|
910
910
|
}
|
|
911
911
|
const store = loadNeuralStore();
|
|
912
912
|
const method = input.method || 'quantize';
|
|
913
|
-
const targetReduction = input.targetSize || 0.5;
|
|
914
913
|
const patterns = Object.values(store.patterns);
|
|
915
914
|
if (patterns.length === 0) {
|
|
916
915
|
return { success: false, error: 'No patterns to compress. Train patterns first with neural_train.' };
|
|
@@ -936,23 +935,18 @@ export const neuralTools = [
|
|
|
936
935
|
// Resolve provider only for operations that actually use embeddings.
|
|
937
936
|
await ensureEmbeddings();
|
|
938
937
|
if (method === 'prune') {
|
|
939
|
-
//
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
toRemove.push(id);
|
|
945
|
-
}
|
|
946
|
-
for (const id of toRemove)
|
|
947
|
-
delete store.patterns[id];
|
|
948
|
-
saveNeuralStore(store);
|
|
938
|
+
// #4423: prune deleted patterns with usageCount below targetSize, but
|
|
939
|
+
// nothing in the neural store records usage (patterns are written with
|
|
940
|
+
// 0 and only max-merged), so any targetSize > 0, including the old 0.5
|
|
941
|
+
// default, deleted and persisted every pattern. Refuse until usage is
|
|
942
|
+
// tracked, the same way quantize refuses (#4195). The store is untouched.
|
|
949
943
|
return {
|
|
950
|
-
success:
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
patternsRemoved:
|
|
944
|
+
success: false,
|
|
945
|
+
error: 'Pruning is unavailable: neural patterns do not record usage, so there is no signal to choose which to remove.',
|
|
946
|
+
method,
|
|
947
|
+
patternsRemoved: 0,
|
|
954
948
|
patternsBefore: beforeCount,
|
|
955
|
-
patternsAfter:
|
|
949
|
+
patternsAfter: beforeCount,
|
|
956
950
|
};
|
|
957
951
|
}
|
|
958
952
|
if (method === 'distill') {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ruRE Phase 1 MCP Tools — JS/Electron static analysis.
|
|
3
|
+
*
|
|
4
|
+
* See plugins/ruflo-rure/docs/adrs/0001-scope-and-dual-use-policy.md and
|
|
5
|
+
* 0002-phase1-architecture.md. These tools parse local files; they never
|
|
6
|
+
* execute analyzed code, and all extracted text is routed through the
|
|
7
|
+
* AIDefence gate before being returned to the caller.
|
|
8
|
+
*/
|
|
9
|
+
import type { MCPTool } from './types.js';
|
|
10
|
+
export declare const rureTools: MCPTool[];
|
|
11
|
+
//# sourceMappingURL=rure-tools.d.ts.map
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ruRE Phase 1 MCP Tools — JS/Electron static analysis.
|
|
3
|
+
*
|
|
4
|
+
* See plugins/ruflo-rure/docs/adrs/0001-scope-and-dual-use-policy.md and
|
|
5
|
+
* 0002-phase1-architecture.md. These tools parse local files; they never
|
|
6
|
+
* execute analyzed code, and all extracted text is routed through the
|
|
7
|
+
* AIDefence gate before being returned to the caller.
|
|
8
|
+
*/
|
|
9
|
+
import { openSync, fstatSync, readFileSync, closeSync } from 'node:fs';
|
|
10
|
+
import { validatePath, validateText } from './validate-input.js';
|
|
11
|
+
import { extractAsarWithCaps, DEFAULT_ASAR_LIMITS } from '../rure/extract-asar.js';
|
|
12
|
+
import { analyzeJsApp } from '../rure/analyze-js-app.js';
|
|
13
|
+
import { findFeature } from '../rure/find-feature.js';
|
|
14
|
+
import { gateWithAIDefence } from '../rure/aidefence-gate.js';
|
|
15
|
+
import { parseBinary, extractStrings, MAX_BINARY_BYTES } from '../rure/binary-parse.js';
|
|
16
|
+
import { parseDotNet } from '../rure/dotnet-parse.js';
|
|
17
|
+
import { analyzeEvm, parseHex, MAX_EVM_BYTES } from '../rure/evm-parse.js';
|
|
18
|
+
function fail(error) {
|
|
19
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error }) }], isError: true };
|
|
20
|
+
}
|
|
21
|
+
function ok(data) {
|
|
22
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }) }] };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Screens untrusted extracted text through AIDefence. Every attacker-derived string in a
|
|
26
|
+
* tool result goes through here (paths, names, literals), results are cached per call, and
|
|
27
|
+
* `screened` reports honestly whether the scanner actually ran on everything.
|
|
28
|
+
*/
|
|
29
|
+
class Screener {
|
|
30
|
+
redacted = 0;
|
|
31
|
+
unscreened = 0;
|
|
32
|
+
cache = new Map();
|
|
33
|
+
async one(item) {
|
|
34
|
+
let g = this.cache.get(item);
|
|
35
|
+
if (!g) {
|
|
36
|
+
g = await gateWithAIDefence(item);
|
|
37
|
+
this.cache.set(item, g);
|
|
38
|
+
if (g.flagged)
|
|
39
|
+
this.redacted += 1;
|
|
40
|
+
if (!g.gated && item)
|
|
41
|
+
this.unscreened += 1;
|
|
42
|
+
}
|
|
43
|
+
return g.text;
|
|
44
|
+
}
|
|
45
|
+
async screen(items) {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const item of items)
|
|
48
|
+
out.push(await this.one(item));
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
get screened() {
|
|
52
|
+
return this.unscreened === 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function screenEvidence(sc, e) {
|
|
56
|
+
return { ...e, file: await sc.one(e.file) };
|
|
57
|
+
}
|
|
58
|
+
/** Returns a module analysis with every attacker-derived string screened and clipped; raw snippets are dropped. */
|
|
59
|
+
async function screenModule(sc, mod) {
|
|
60
|
+
const imports = [];
|
|
61
|
+
for (const i of mod.imports) {
|
|
62
|
+
imports.push({ source: await sc.one(i.source), names: await sc.screen(i.names), evidence: await screenEvidence(sc, i.evidence) });
|
|
63
|
+
}
|
|
64
|
+
const exports = [];
|
|
65
|
+
for (const e of mod.exports)
|
|
66
|
+
exports.push({ name: await sc.one(e.name), evidence: await screenEvidence(sc, e.evidence) });
|
|
67
|
+
const electronBoundaries = [];
|
|
68
|
+
for (const b of mod.electronBoundaries) {
|
|
69
|
+
electronBoundaries.push({ kind: b.kind, detail: await sc.one(b.detail), evidence: await screenEvidence(sc, b.evidence) });
|
|
70
|
+
}
|
|
71
|
+
return { file: await sc.one(mod.file), imports, exports, electronBoundaries };
|
|
72
|
+
}
|
|
73
|
+
/** Opens once and checks the size on the same descriptor, so a file swapped after a stat cannot bypass the cap. */
|
|
74
|
+
function readCapped(path, cap) {
|
|
75
|
+
const fd = openSync(path, 'r');
|
|
76
|
+
try {
|
|
77
|
+
const st = fstatSync(fd);
|
|
78
|
+
if (!st.isFile())
|
|
79
|
+
return { ok: false, error: 'path is not a regular file' };
|
|
80
|
+
if (st.size > cap)
|
|
81
|
+
return { ok: false, error: `file exceeds the ${cap}-byte analysis cap` };
|
|
82
|
+
return { ok: true, bytes: readFileSync(fd) };
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
closeSync(fd);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export const rureTools = [
|
|
89
|
+
{
|
|
90
|
+
name: 'rure_extract_asar',
|
|
91
|
+
description: 'Unpack an Electron .asar archive to a scratch directory, under resource caps (max total declared size, max file count) checked from the archive header before any bytes are written. Refuses to partially extract: returns truncated:true with nothing written if the archive exceeds the caps. Use when you need to analyze the JS inside a packaged Electron app and rure_analyze_js_app rejects the .asar path directly.',
|
|
92
|
+
category: 'rure',
|
|
93
|
+
tags: ['reverse-engineering', 'electron', 'asar', 'static-analysis'],
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
path: { type: 'string', description: 'Absolute path to the .asar file' },
|
|
98
|
+
},
|
|
99
|
+
required: ['path'],
|
|
100
|
+
},
|
|
101
|
+
handler: async (input) => {
|
|
102
|
+
const v = validatePath(input.path, 'path');
|
|
103
|
+
if (!v.valid)
|
|
104
|
+
return fail(v.error);
|
|
105
|
+
try {
|
|
106
|
+
const result = await extractAsarWithCaps(v.sanitized, DEFAULT_ASAR_LIMITS);
|
|
107
|
+
if (result.truncated) {
|
|
108
|
+
return ok({ truncated: true, reason: result.reason, fileCount: result.fileCount, totalBytes: result.totalBytes });
|
|
109
|
+
}
|
|
110
|
+
return ok({
|
|
111
|
+
truncated: false,
|
|
112
|
+
unpackedPath: result.unpackedPath,
|
|
113
|
+
fileCount: result.fileCount,
|
|
114
|
+
totalBytes: result.totalBytes,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return fail(`asar extraction failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'rure_analyze_js_app',
|
|
124
|
+
description: 'Statically analyze a directory of JavaScript/TypeScript/Electron source: module list, import/export graph, and Electron process-boundary detection (ipcMain/ipcRenderer/contextBridge, require("electron"), nodeIntegration flags). Every finding carries evidence (file + line range), AIDefence-screened before return. Use when you need a structural map of unfamiliar JS/Electron source rather than reading files one at a time — native Read is fine for a single already-identified file, but wrong for discovering the import graph or Electron boundaries across a whole app. Does not accept a .asar path directly — run rure_extract_asar first and pass its unpackedPath.',
|
|
125
|
+
category: 'rure',
|
|
126
|
+
tags: ['reverse-engineering', 'electron', 'javascript', 'static-analysis'],
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
path: { type: 'string', description: 'Absolute path to a directory of extracted JS/Electron source' },
|
|
131
|
+
},
|
|
132
|
+
required: ['path'],
|
|
133
|
+
},
|
|
134
|
+
handler: async (input) => {
|
|
135
|
+
const v = validatePath(input.path, 'path');
|
|
136
|
+
if (!v.valid)
|
|
137
|
+
return fail(v.error);
|
|
138
|
+
try {
|
|
139
|
+
const analysis = await analyzeJsApp(v.sanitized);
|
|
140
|
+
const sc = new Screener();
|
|
141
|
+
const modules = [];
|
|
142
|
+
for (const m of analysis.modules)
|
|
143
|
+
modules.push(await screenModule(sc, m));
|
|
144
|
+
return ok({
|
|
145
|
+
filesScanned: analysis.filesScanned,
|
|
146
|
+
truncated: analysis.truncated,
|
|
147
|
+
skippedFiles: analysis.skippedFiles,
|
|
148
|
+
isElectron: analysis.isElectron,
|
|
149
|
+
modules,
|
|
150
|
+
redactedSnippetCount: sc.redacted,
|
|
151
|
+
screened: sc.screened,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
return fail(`analysis failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'rure_find_feature',
|
|
161
|
+
description: 'Locate where a described feature likely lives in an already-extracted JS/Electron app, using deterministic lexical and structural scoring (not semantic/LLM matching, so results stay explainable). Returns ranked matches with file, line range, a short snippet, and a score, every snippet AIDefence-screened before return. Use when the target app is too large to read file-by-file and you need evidence of where a feature lives before explaining or reimplementing it — grep is fine for an exact known string, but wrong when you only have a plain-language description of the behavior.',
|
|
162
|
+
category: 'rure',
|
|
163
|
+
tags: ['reverse-engineering', 'search', 'static-analysis'],
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {
|
|
167
|
+
path: { type: 'string', description: 'Absolute path to a directory of extracted JS/Electron source' },
|
|
168
|
+
query: { type: 'string', description: 'Plain-language description of the feature to locate' },
|
|
169
|
+
},
|
|
170
|
+
required: ['path', 'query'],
|
|
171
|
+
},
|
|
172
|
+
handler: async (input) => {
|
|
173
|
+
const v = validatePath(input.path, 'path');
|
|
174
|
+
if (!v.valid)
|
|
175
|
+
return fail(v.error);
|
|
176
|
+
const q = validateText(input.query, 'query', 2000);
|
|
177
|
+
if (!q.valid)
|
|
178
|
+
return fail(q.error);
|
|
179
|
+
try {
|
|
180
|
+
const result = await findFeature(v.sanitized, q.sanitized);
|
|
181
|
+
const sc = new Screener();
|
|
182
|
+
const gated = [];
|
|
183
|
+
for (const m of result.matches) {
|
|
184
|
+
const before = sc.redacted;
|
|
185
|
+
const snippet = await sc.one(m.snippet);
|
|
186
|
+
gated.push({ ...m, file: await sc.one(m.file), snippet, redacted: sc.redacted > before });
|
|
187
|
+
}
|
|
188
|
+
return ok({
|
|
189
|
+
matches: gated,
|
|
190
|
+
filesScanned: result.filesScanned,
|
|
191
|
+
truncated: result.truncated,
|
|
192
|
+
skippedFiles: result.skippedFiles,
|
|
193
|
+
screened: sc.screened,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
return fail(`feature search failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: 'rure_analyze_binary',
|
|
203
|
+
description: 'Statically inspect a native ELF, PE, or Mach-O file (ADR-0003): format, architecture, entry point, section layout, linked libraries, and imported/exported symbols, plus optional printable strings. Pure byte parsing under hard caps (128 MiB file, bounded tables); the file is never loaded or executed, and every name and string is AIDefence-screened before return. Use when you need an inventory of what a native binary links against and exposes before deciding where to look next — not for disassembly or decompilation, which this does not do. Malformed headers yield warnings, not errors.',
|
|
204
|
+
category: 'rure',
|
|
205
|
+
tags: ['reverse-engineering', 'binary', 'elf', 'pe', 'macho', 'static-analysis'],
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: 'object',
|
|
208
|
+
properties: {
|
|
209
|
+
path: { type: 'string', description: 'Absolute path to a native binary (ELF, PE, or Mach-O)' },
|
|
210
|
+
includeStrings: { type: 'boolean', description: 'Also return up to 200 printable strings (default false)' },
|
|
211
|
+
},
|
|
212
|
+
required: ['path'],
|
|
213
|
+
},
|
|
214
|
+
handler: async (input) => {
|
|
215
|
+
const v = validatePath(input.path, 'path');
|
|
216
|
+
if (!v.valid)
|
|
217
|
+
return fail(v.error);
|
|
218
|
+
try {
|
|
219
|
+
const r = readCapped(v.sanitized, MAX_BINARY_BYTES);
|
|
220
|
+
if (!r.ok)
|
|
221
|
+
return fail(r.error);
|
|
222
|
+
const buf = r.bytes;
|
|
223
|
+
const info = parseBinary(buf);
|
|
224
|
+
const sc = new Screener();
|
|
225
|
+
const libraries = await sc.screen(info.libraries);
|
|
226
|
+
const imports = await sc.screen(info.imports);
|
|
227
|
+
const exports = await sc.screen(info.exports);
|
|
228
|
+
const sectionNames = await sc.screen(info.sections.map((x) => x.name));
|
|
229
|
+
const sections = info.sections.map((x, i) => ({ ...x, name: sectionNames[i] }));
|
|
230
|
+
let strings;
|
|
231
|
+
let stringsTruncated = false;
|
|
232
|
+
const warnings = [...info.warnings];
|
|
233
|
+
if (input.includeStrings === true) {
|
|
234
|
+
if (info.format === 'unknown') {
|
|
235
|
+
warnings.push('strings not extracted: file is not a recognized binary format');
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
const ex = extractStrings(buf);
|
|
239
|
+
stringsTruncated = ex.truncated;
|
|
240
|
+
const gated = await sc.screen(ex.strings.map((x) => x.value));
|
|
241
|
+
strings = ex.strings.map((x, i) => ({ value: gated[i], offset: x.offset }));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return ok({ ...info, warnings, sections, libraries, imports, exports, ...(strings ? { strings, stringsTruncated } : {}), redactedCount: sc.redacted, screened: sc.screened });
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
return fail(`binary analysis failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'rure_analyze_dotnet',
|
|
253
|
+
description: 'Inspect a managed .NET assembly (.dll/.exe) from its ECMA-335 metadata (ADR-0004): runtime version, assembly identity, assembly references, type names, method count, P/Invoke native imports (module + function), user-string literals, and entry-point token. Reads metadata only under hard caps; the assembly is never loaded or run, and every name and string is AIDefence-screened before return. Use when you need the dependency and native-call surface of a .NET binary before deciding where to dig deeper — not for IL disassembly or decompilation, which this does not do. A native (non-managed) PE returns isManaged:false.',
|
|
254
|
+
category: 'rure',
|
|
255
|
+
tags: ['reverse-engineering', 'dotnet', 'assembly', 'static-analysis'],
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: 'object',
|
|
258
|
+
properties: {
|
|
259
|
+
path: { type: 'string', description: 'Absolute path to a .NET assembly (.dll or .exe)' },
|
|
260
|
+
},
|
|
261
|
+
required: ['path'],
|
|
262
|
+
},
|
|
263
|
+
handler: async (input) => {
|
|
264
|
+
const v = validatePath(input.path, 'path');
|
|
265
|
+
if (!v.valid)
|
|
266
|
+
return fail(v.error);
|
|
267
|
+
try {
|
|
268
|
+
const r = readCapped(v.sanitized, MAX_BINARY_BYTES);
|
|
269
|
+
if (!r.ok)
|
|
270
|
+
return fail(r.error);
|
|
271
|
+
const info = parseDotNet(r.bytes);
|
|
272
|
+
const sc = new Screener();
|
|
273
|
+
const assembly = info.assembly ? { ...info.assembly, name: await sc.one(info.assembly.name) } : null;
|
|
274
|
+
const refNames = await sc.screen(info.assemblyRefs.map((x) => x.name));
|
|
275
|
+
const types = await sc.screen(info.types);
|
|
276
|
+
const pmods = await sc.screen(info.pinvokes.map((x) => x.module));
|
|
277
|
+
const pfuncs = await sc.screen(info.pinvokes.map((x) => x.function));
|
|
278
|
+
const userStrings = await sc.screen(info.userStrings);
|
|
279
|
+
const runtimeVersion = info.runtimeVersion === null ? null : await sc.one(info.runtimeVersion);
|
|
280
|
+
return ok({
|
|
281
|
+
...info,
|
|
282
|
+
runtimeVersion,
|
|
283
|
+
assembly,
|
|
284
|
+
assemblyRefs: info.assemblyRefs.map((x, i) => ({ ...x, name: refNames[i] })),
|
|
285
|
+
types,
|
|
286
|
+
pinvokes: info.pinvokes.map((_, i) => ({ module: pmods[i], function: pfuncs[i] })),
|
|
287
|
+
userStrings,
|
|
288
|
+
redactedCount: sc.redacted,
|
|
289
|
+
screened: sc.screened,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
return fail(`dotnet analysis failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'rure_analyze_evm',
|
|
299
|
+
description: 'Statically inventory EVM contract bytecode (ADR-0005): instruction and JUMPDEST counts, function selectors recognized from the dispatcher (with names for a small well-known table only), counts and offsets of hazardous opcodes (SELFDESTRUCT, DELEGATECALL, CALLCODE, CREATE/CREATE2, ORIGIN, CALL, STATICCALL), embedded addresses, event topics, EIP-1167 minimal-proxy detection, compiler metadata, and embedded strings such as revert reasons (AIDefence-screened). Pass bytecode inline as hex, or a path to a file of hex text or raw bytes. Aligned disassembly only: there is no EVM interpreter, no RPC, and no network. Use when you need the exposed surface and risky capabilities of a contract before deeper review; a hazardous opcode is an inventory fact, not a vulnerability finding, and dispatcher detection can miss unusual layouts.',
|
|
300
|
+
category: 'rure',
|
|
301
|
+
tags: ['reverse-engineering', 'evm', 'smart-contract', 'static-analysis'],
|
|
302
|
+
inputSchema: {
|
|
303
|
+
type: 'object',
|
|
304
|
+
properties: {
|
|
305
|
+
bytecode: { type: 'string', description: 'Contract bytecode as hex (0x prefix optional). Provide this or path, not both.' },
|
|
306
|
+
path: { type: 'string', description: 'Absolute path to a file containing hex text or raw bytecode' },
|
|
307
|
+
includeDisassembly: { type: 'boolean', description: 'Also return the first 500 instructions (default false)' },
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
handler: async (input) => {
|
|
311
|
+
const hasInline = typeof input.bytecode === 'string' && input.bytecode.length > 0;
|
|
312
|
+
const hasPath = typeof input.path === 'string' && input.path.length > 0;
|
|
313
|
+
if (hasInline === hasPath)
|
|
314
|
+
return fail('provide exactly one of bytecode or path');
|
|
315
|
+
try {
|
|
316
|
+
let bytes;
|
|
317
|
+
if (hasInline) {
|
|
318
|
+
const text = input.bytecode;
|
|
319
|
+
if (text.length > MAX_EVM_BYTES * 2 + 2)
|
|
320
|
+
return fail(`bytecode exceeds the ${MAX_EVM_BYTES}-byte cap`);
|
|
321
|
+
const parsed = parseHex(text);
|
|
322
|
+
if (!parsed.ok)
|
|
323
|
+
return fail(parsed.error);
|
|
324
|
+
bytes = parsed.bytes;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
const v = validatePath(input.path, 'path');
|
|
328
|
+
if (!v.valid)
|
|
329
|
+
return fail(v.error);
|
|
330
|
+
const r = readCapped(v.sanitized, MAX_EVM_BYTES * 2 + 64);
|
|
331
|
+
if (!r.ok)
|
|
332
|
+
return fail(r.error);
|
|
333
|
+
const raw = r.bytes;
|
|
334
|
+
const asText = raw.toString('latin1').trim();
|
|
335
|
+
if (/^(0x)?[0-9a-fA-F\s]+$/.test(asText)) {
|
|
336
|
+
const parsed = parseHex(asText.replace(/\s/g, ''));
|
|
337
|
+
if (!parsed.ok)
|
|
338
|
+
return fail(parsed.error);
|
|
339
|
+
bytes = parsed.bytes;
|
|
340
|
+
}
|
|
341
|
+
else if (raw.length <= MAX_EVM_BYTES) {
|
|
342
|
+
bytes = raw;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
return fail(`file exceeds the ${MAX_EVM_BYTES}-byte bytecode cap`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const info = analyzeEvm(bytes, input.includeDisassembly === true);
|
|
349
|
+
const sc = new Screener();
|
|
350
|
+
const strings = await sc.screen(info.strings);
|
|
351
|
+
return ok({ ...info, strings, redactedCount: sc.redacted, screened: sc.screened });
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
return fail(`evm analysis failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
];
|
|
359
|
+
//# sourceMappingURL=rure-tools.js.map
|
|
@@ -54,6 +54,15 @@ export declare function siblingAgentDbPath(dbPath: string): string | null;
|
|
|
54
54
|
export declare function ensureBridgeSchema(db: {
|
|
55
55
|
exec: (sql: string) => unknown;
|
|
56
56
|
}): boolean;
|
|
57
|
+
/** @internal Exported for the multi-store rescue regression test. */
|
|
58
|
+
export declare function rescueAgentdbEmbedder(agentdb: {
|
|
59
|
+
embedder?: {
|
|
60
|
+
pipeline?: unknown;
|
|
61
|
+
embed?: (t: string) => Promise<Float32Array>;
|
|
62
|
+
embedBatch?: (ts: string[]) => Promise<Float32Array[]>;
|
|
63
|
+
__ruvectorRescued?: boolean;
|
|
64
|
+
};
|
|
65
|
+
}): Promise<void>;
|
|
57
66
|
/**
|
|
58
67
|
* Store an entry via AgentDB v3.
|
|
59
68
|
* Phase 2-5: Routes through MutationGuard → TieredCache → DB → AttestationLog.
|
|
@@ -102,6 +111,14 @@ export declare function bridgeStoreEntry(options: {
|
|
|
102
111
|
* Phase 2: BM25 hybrid scoring replaces naive String.includes() keyword fallback.
|
|
103
112
|
* Combines cosine similarity (semantic) with BM25 (lexical) via reciprocal rank fusion.
|
|
104
113
|
*/
|
|
114
|
+
/**
|
|
115
|
+
* #4397: how many of the newest rows the pre-rank SELECT hands to scoring.
|
|
116
|
+
* #2982 added ORDER BY updated_at DESC but kept LIMIT 1000, so rows outside
|
|
117
|
+
* the newest 1000 became unfindable (the sql.js path and 3.38.3 used 10000).
|
|
118
|
+
* Override with CLAUDE_FLOW_SEARCH_WINDOW (clamped to 100..100000).
|
|
119
|
+
*/
|
|
120
|
+
export declare const DEFAULT_BRIDGE_SEARCH_WINDOW = 10000;
|
|
121
|
+
export declare function resolveBridgeSearchWindow(env?: NodeJS.ProcessEnv): number;
|
|
105
122
|
export declare function bridgeSearchEntries(options: {
|
|
106
123
|
query: string;
|
|
107
124
|
namespace?: string;
|
|
@@ -896,13 +896,10 @@ function getDb(registry) {
|
|
|
896
896
|
rescueAgentdbEmbedder(agentdb).catch(() => { });
|
|
897
897
|
return { db, agentdb };
|
|
898
898
|
}
|
|
899
|
-
//
|
|
900
|
-
//
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
async function rescueAgentdbEmbedder(agentdb) {
|
|
904
|
-
if (_embedderPatched)
|
|
905
|
-
return;
|
|
899
|
+
// The rescue is idempotent per embedder via __ruvectorRescued. A process-wide
|
|
900
|
+
// flag would skip later dbPath registries and leave their embedder on mock (#4410).
|
|
901
|
+
/** @internal Exported for the multi-store rescue regression test. */
|
|
902
|
+
export async function rescueAgentdbEmbedder(agentdb) {
|
|
906
903
|
const emb = agentdb?.embedder;
|
|
907
904
|
if (!emb || emb.__ruvectorRescued)
|
|
908
905
|
return;
|
|
@@ -953,7 +950,6 @@ async function rescueAgentdbEmbedder(agentdb) {
|
|
|
953
950
|
emb.embed = newEmbed;
|
|
954
951
|
emb.embedBatch = async (texts) => Promise.all(texts.map(t => newEmbed(t)));
|
|
955
952
|
emb.__ruvectorRescued = true;
|
|
956
|
-
_embedderPatched = true;
|
|
957
953
|
}
|
|
958
954
|
/** AgentDB uses a null pipeline while its transformer failed and it serves mock vectors. */
|
|
959
955
|
function isMockAgentdbEmbedder(embedder) {
|
|
@@ -1291,6 +1287,19 @@ export async function bridgeStoreEntry(options) {
|
|
|
1291
1287
|
* Phase 2: BM25 hybrid scoring replaces naive String.includes() keyword fallback.
|
|
1292
1288
|
* Combines cosine similarity (semantic) with BM25 (lexical) via reciprocal rank fusion.
|
|
1293
1289
|
*/
|
|
1290
|
+
/**
|
|
1291
|
+
* #4397: how many of the newest rows the pre-rank SELECT hands to scoring.
|
|
1292
|
+
* #2982 added ORDER BY updated_at DESC but kept LIMIT 1000, so rows outside
|
|
1293
|
+
* the newest 1000 became unfindable (the sql.js path and 3.38.3 used 10000).
|
|
1294
|
+
* Override with CLAUDE_FLOW_SEARCH_WINDOW (clamped to 100..100000).
|
|
1295
|
+
*/
|
|
1296
|
+
export const DEFAULT_BRIDGE_SEARCH_WINDOW = 10000;
|
|
1297
|
+
export function resolveBridgeSearchWindow(env = process.env) {
|
|
1298
|
+
const n = Number.parseInt(env.CLAUDE_FLOW_SEARCH_WINDOW ?? "", 10);
|
|
1299
|
+
if (!Number.isFinite(n))
|
|
1300
|
+
return DEFAULT_BRIDGE_SEARCH_WINDOW;
|
|
1301
|
+
return Math.min(100000, Math.max(100, n));
|
|
1302
|
+
}
|
|
1294
1303
|
export async function bridgeSearchEntries(options) {
|
|
1295
1304
|
if (!operationContext.getStore()?.active)
|
|
1296
1305
|
return withBridgeOperation(() => bridgeSearchEntries(options));
|
|
@@ -1339,7 +1348,7 @@ export async function bridgeSearchEntries(options) {
|
|
|
1339
1348
|
FROM memory_entries
|
|
1340
1349
|
WHERE ${liveMemoryRowSql()} ${whereExtra}
|
|
1341
1350
|
ORDER BY updated_at DESC
|
|
1342
|
-
LIMIT
|
|
1351
|
+
LIMIT ${resolveBridgeSearchWindow()}
|
|
1343
1352
|
`);
|
|
1344
1353
|
rows = filterParams.length > 0 ? stmt.all(...filterParams) : stmt.all();
|
|
1345
1354
|
}
|
|
@@ -15,6 +15,18 @@ export interface ClaudeInstall {
|
|
|
15
15
|
export type VersionOf = (path: string) => string | null;
|
|
16
16
|
/** a < b for dotted numeric versions. */
|
|
17
17
|
export declare function versionLess(a: string, b: string): boolean;
|
|
18
|
+
export interface VersionProbe {
|
|
19
|
+
command: string;
|
|
20
|
+
args: string[];
|
|
21
|
+
windowsVerbatimArguments?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* #4397: Node refuses to execFile a .cmd/.bat shim on Windows (EINVAL), so the
|
|
25
|
+
* npm-global `claude.cmd` read as "version unknown". Shims go through cmd.exe
|
|
26
|
+
* with the path quoted; a path holding cmd metacharacters is not probed at
|
|
27
|
+
* all (null) rather than risk interpreting it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function versionProbe(path: string, platform?: NodeJS.Platform, comspec?: string): VersionProbe | null;
|
|
18
30
|
/** `claude --version` of one binary, bounded; null when it cannot say. */
|
|
19
31
|
export declare const defaultVersionOf: VersionOf;
|
|
20
32
|
/** Every `claude` on PATH, in PATH order (the first is what runs), plus ~/.local/bin. */
|
|
@@ -22,10 +22,30 @@ export function versionLess(a, b) {
|
|
|
22
22
|
}
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* #4397: Node refuses to execFile a .cmd/.bat shim on Windows (EINVAL), so the
|
|
27
|
+
* npm-global `claude.cmd` read as "version unknown". Shims go through cmd.exe
|
|
28
|
+
* with the path quoted; a path holding cmd metacharacters is not probed at
|
|
29
|
+
* all (null) rather than risk interpreting it.
|
|
30
|
+
*/
|
|
31
|
+
export function versionProbe(path, platform = process.platform, comspec = process.env.ComSpec || 'cmd.exe') {
|
|
32
|
+
if (platform === 'win32' && /\.(cmd|bat)$/i.test(path)) {
|
|
33
|
+
if (/["&|<>^%\r\n]/.test(path))
|
|
34
|
+
return null;
|
|
35
|
+
return { command: comspec, args: ['/d', '/s', '/c', `""${path}" --version"`], windowsVerbatimArguments: true };
|
|
36
|
+
}
|
|
37
|
+
return { command: path, args: ['--version'] };
|
|
38
|
+
}
|
|
25
39
|
/** `claude --version` of one binary, bounded; null when it cannot say. */
|
|
26
40
|
export const defaultVersionOf = (path) => {
|
|
41
|
+
const probe = versionProbe(path);
|
|
42
|
+
if (!probe)
|
|
43
|
+
return null;
|
|
27
44
|
try {
|
|
28
|
-
const out = execFileSync(
|
|
45
|
+
const out = execFileSync(probe.command, probe.args, {
|
|
46
|
+
encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
47
|
+
...(probe.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
48
|
+
});
|
|
29
49
|
return out.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
|
|
30
50
|
}
|
|
31
51
|
catch {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIDefence content gate for ruRE (ADR-0001 §dual-use policy).
|
|
3
|
+
*
|
|
4
|
+
* Every string literal, comment, and identifier ruRE extracts comes from a
|
|
5
|
+
* target the user may not control. Before any of it is returned to the
|
|
6
|
+
* caller (and therefore to the model that synthesizes an explanation), it is
|
|
7
|
+
* routed through this gate. Mirrors the lazy-load pattern used by
|
|
8
|
+
* browser-intent-tools.ts's private gateWithAIDefence — @claude-flow/aidefence
|
|
9
|
+
* is itself an optional dependency, so absence degrades to pass-through
|
|
10
|
+
* rather than blocking analysis.
|
|
11
|
+
*/
|
|
12
|
+
/** Longest attacker-derived string returned or scanned; anything longer is clipped first. */
|
|
13
|
+
export declare const MAX_GATED_CHARS = 2048;
|
|
14
|
+
export declare function clipText(text: string): string;
|
|
15
|
+
export interface GatedText {
|
|
16
|
+
text: string;
|
|
17
|
+
flagged: boolean;
|
|
18
|
+
gated: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function gateWithAIDefence(input: string): Promise<GatedText>;
|
|
21
|
+
/** Gate every string in an array, preserving order and length. Sequential —
|
|
22
|
+
* gating is cheap, and this avoids firing the first-ever (memoized) module
|
|
23
|
+
* resolution attempt from several calls at once. */
|
|
24
|
+
export declare function gateAll(texts: string[]): Promise<GatedText[]>;
|
|
25
|
+
//# sourceMappingURL=aidefence-gate.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIDefence content gate for ruRE (ADR-0001 §dual-use policy).
|
|
3
|
+
*
|
|
4
|
+
* Every string literal, comment, and identifier ruRE extracts comes from a
|
|
5
|
+
* target the user may not control. Before any of it is returned to the
|
|
6
|
+
* caller (and therefore to the model that synthesizes an explanation), it is
|
|
7
|
+
* routed through this gate. Mirrors the lazy-load pattern used by
|
|
8
|
+
* browser-intent-tools.ts's private gateWithAIDefence — @claude-flow/aidefence
|
|
9
|
+
* is itself an optional dependency, so absence degrades to pass-through
|
|
10
|
+
* rather than blocking analysis.
|
|
11
|
+
*/
|
|
12
|
+
/** Longest attacker-derived string returned or scanned; anything longer is clipped first. */
|
|
13
|
+
export const MAX_GATED_CHARS = 2048;
|
|
14
|
+
export function clipText(text) {
|
|
15
|
+
return text.length > MAX_GATED_CHARS ? `${text.slice(0, MAX_GATED_CHARS)}…[truncated ${text.length - MAX_GATED_CHARS} chars]` : text;
|
|
16
|
+
}
|
|
17
|
+
// Memoized: resolving a genuinely-absent optional module repeatedly on every
|
|
18
|
+
// call is wasteful, and a single shared attempt avoids any ambiguity from
|
|
19
|
+
// multiple concurrent dynamic-import resolutions of the same bare specifier
|
|
20
|
+
// (gateAll calls this for every string in a batch).
|
|
21
|
+
let aidefenceModulePromise = null;
|
|
22
|
+
function loadAIDefenceModule() {
|
|
23
|
+
aidefenceModulePromise ??= import('@claude-flow/aidefence');
|
|
24
|
+
return aidefenceModulePromise;
|
|
25
|
+
}
|
|
26
|
+
let defenderPromise = null;
|
|
27
|
+
function loadDefender() {
|
|
28
|
+
defenderPromise ??= loadAIDefenceModule().then((mod) => mod.createAIDefence({ enableLearning: false }));
|
|
29
|
+
return defenderPromise;
|
|
30
|
+
}
|
|
31
|
+
export async function gateWithAIDefence(input) {
|
|
32
|
+
if (!input)
|
|
33
|
+
return { text: input, flagged: false, gated: false };
|
|
34
|
+
const text = clipText(input);
|
|
35
|
+
try {
|
|
36
|
+
const defender = await loadDefender();
|
|
37
|
+
let flagged = false;
|
|
38
|
+
// A scan that throws is NOT a clean result: report gated:false so callers can disclose it.
|
|
39
|
+
const quick = defender.quickScan(text);
|
|
40
|
+
flagged = !!quick?.threat;
|
|
41
|
+
if (!flagged && defender.hasPII(text))
|
|
42
|
+
flagged = true;
|
|
43
|
+
if (flagged) {
|
|
44
|
+
return { text: '[content redacted by AIDefence — threat or PII detected]', flagged: true, gated: true };
|
|
45
|
+
}
|
|
46
|
+
return { text, flagged: false, gated: true };
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// @claude-flow/aidefence is optional, or its scan failed: pass the clipped text through
|
|
50
|
+
// but say so (gated:false). Tool outputs surface this as screened:false.
|
|
51
|
+
return { text, flagged: false, gated: false };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Gate every string in an array, preserving order and length. Sequential —
|
|
55
|
+
* gating is cheap, and this avoids firing the first-ever (memoized) module
|
|
56
|
+
* resolution attempt from several calls at once. */
|
|
57
|
+
export async function gateAll(texts) {
|
|
58
|
+
const results = [];
|
|
59
|
+
for (const t of texts)
|
|
60
|
+
results.push(await gateWithAIDefence(t));
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=aidefence-gate.js.map
|