claude-flow 3.59.16 → 3.60.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 (53) hide show
  1. package/.claude/proven-config.json +1 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/README.md +2 -1
  4. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  5. package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts.map +1 -1
  6. package/node_modules/@claude-flow/mcp/dist/session-manager.js +4 -1
  7. package/node_modules/@claude-flow/mcp/dist/session-manager.js.map +1 -1
  8. package/node_modules/@claude-flow/mcp/package.json +1 -1
  9. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  10. package/package.json +3 -2
  11. package/v3/@claude-flow/cli/README.md +2 -1
  12. package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
  13. package/v3/@claude-flow/cli/dist/src/commands/agntcy/publish.js +1 -1
  14. package/v3/@claude-flow/cli/dist/src/commands/agntcy/runtime.d.ts +4 -1
  15. package/v3/@claude-flow/cli/dist/src/commands/agntcy/runtime.js +15 -2
  16. package/v3/@claude-flow/cli/dist/src/commands/agntcy/swarm-join.js +1 -1
  17. package/v3/@claude-flow/cli/dist/src/commands/agntcy/transport.js +1 -1
  18. package/v3/@claude-flow/cli/dist/src/commands/update.js +5 -3
  19. package/v3/@claude-flow/cli/dist/src/init/executor.js +2 -1
  20. package/v3/@claude-flow/cli/dist/src/mcp-client.js +4 -0
  21. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.js +7 -0
  22. package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +17 -0
  23. package/v3/@claude-flow/cli/dist/src/mcp-tools/daa-tools.js +20 -12
  24. package/v3/@claude-flow/cli/dist/src/mcp-tools/github-tools.js +78 -75
  25. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +11 -17
  26. package/v3/@claude-flow/cli/dist/src/mcp-tools/rure-tools.d.ts +11 -0
  27. package/v3/@claude-flow/cli/dist/src/mcp-tools/rure-tools.js +359 -0
  28. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +17 -0
  29. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +18 -9
  30. package/v3/@claude-flow/cli/dist/src/mods/claude-installs.d.ts +12 -0
  31. package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +21 -1
  32. package/v3/@claude-flow/cli/dist/src/rure/aidefence-gate.d.ts +25 -0
  33. package/v3/@claude-flow/cli/dist/src/rure/aidefence-gate.js +63 -0
  34. package/v3/@claude-flow/cli/dist/src/rure/analyze-js-app.d.ts +17 -0
  35. package/v3/@claude-flow/cli/dist/src/rure/analyze-js-app.js +26 -0
  36. package/v3/@claude-flow/cli/dist/src/rure/binary-parse.d.ts +38 -0
  37. package/v3/@claude-flow/cli/dist/src/rure/binary-parse.js +347 -0
  38. package/v3/@claude-flow/cli/dist/src/rure/dotnet-parse.d.ts +30 -0
  39. package/v3/@claude-flow/cli/dist/src/rure/dotnet-parse.js +299 -0
  40. package/v3/@claude-flow/cli/dist/src/rure/evm-parse.d.ts +52 -0
  41. package/v3/@claude-flow/cli/dist/src/rure/evm-parse.js +233 -0
  42. package/v3/@claude-flow/cli/dist/src/rure/extract-asar.d.ts +33 -0
  43. package/v3/@claude-flow/cli/dist/src/rure/extract-asar.js +70 -0
  44. package/v3/@claude-flow/cli/dist/src/rure/find-feature.d.ts +26 -0
  45. package/v3/@claude-flow/cli/dist/src/rure/find-feature.js +69 -0
  46. package/v3/@claude-flow/cli/dist/src/rure/ts-walk.d.ts +47 -0
  47. package/v3/@claude-flow/cli/dist/src/rure/ts-walk.js +158 -0
  48. package/v3/@claude-flow/cli/dist/src/rure/walk-files.d.ts +21 -0
  49. package/v3/@claude-flow/cli/dist/src/rure/walk-files.js +71 -0
  50. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +17 -3
  51. package/v3/@claude-flow/cli/dist/src/update/checker.d.ts +1 -0
  52. package/v3/@claude-flow/cli/dist/src/update/checker.js +4 -2
  53. package/v3/@claude-flow/cli/package.json +4 -3
@@ -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
- // One-shot guard so we only patch once per process — the embedder is shared
900
- // by ReflexionMemory/SkillLibrary/CausalRecall and they all see the same
901
- // object reference.
902
- let _embedderPatched = false;
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 1000
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(path, ['--version'], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] });
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
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ruRE Phase 1 — whole-app analysis orchestration (ADR-0002).
3
+ *
4
+ * Walks a directory of JS/Electron source, parses every analyzable file,
5
+ * and aggregates the per-module findings. Parses only; never executes.
6
+ */
7
+ import { type ModuleAnalysis } from './ts-walk.js';
8
+ export interface AppAnalysis {
9
+ modules: ModuleAnalysis[];
10
+ filesScanned: number;
11
+ truncated: boolean;
12
+ isElectron: boolean;
13
+ /** Files the parser could not handle (e.g. pathological nesting); their boundaries are NOT reported. */
14
+ skippedFiles: number;
15
+ }
16
+ export declare function analyzeJsApp(root: string): Promise<AppAnalysis>;
17
+ //# sourceMappingURL=analyze-js-app.d.ts.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ruRE Phase 1 — whole-app analysis orchestration (ADR-0002).
3
+ *
4
+ * Walks a directory of JS/Electron source, parses every analyzable file,
5
+ * and aggregates the per-module findings. Parses only; never executes.
6
+ */
7
+ import { walkAnalyzableFiles } from './walk-files.js';
8
+ import { analyzeModuleSource } from './ts-walk.js';
9
+ export async function analyzeJsApp(root) {
10
+ const { files, truncated } = walkAnalyzableFiles(root);
11
+ const modules = [];
12
+ let skippedFiles = 0;
13
+ for (const f of files) {
14
+ try {
15
+ modules.push(await analyzeModuleSource(f.path, f.text));
16
+ }
17
+ catch {
18
+ // Unparseable file (corrupt source, parser stack overflow) — skip it rather
19
+ // than fail the whole analysis, but count it so the gap is visible.
20
+ skippedFiles += 1;
21
+ }
22
+ }
23
+ const isElectron = modules.some((m) => m.electronBoundaries.length > 0);
24
+ return { modules, filesScanned: files.length, truncated, isElectron, skippedFiles };
25
+ }
26
+ //# sourceMappingURL=analyze-js-app.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * ruRE Phase 2 — container-level static inspection of ELF / PE / Mach-O
3
+ * (ADR-0003). Reads integers and C-strings from a Buffer at bounds-checked
4
+ * offsets. Never loads, maps, or executes the target.
5
+ */
6
+ export declare const MAX_BINARY_BYTES: number;
7
+ export interface SectionInfo {
8
+ name: string;
9
+ offset: number;
10
+ size: number;
11
+ }
12
+ export interface BinaryInfo {
13
+ format: 'elf' | 'pe' | 'macho' | 'macho-fat' | 'unknown';
14
+ arch: string;
15
+ bits: 32 | 64 | null;
16
+ endianness: 'little' | 'big';
17
+ kind: string;
18
+ entryPoint: number | null;
19
+ sections: SectionInfo[];
20
+ libraries: string[];
21
+ imports: string[];
22
+ exports: string[];
23
+ archs?: string[];
24
+ warnings: string[];
25
+ truncated: boolean;
26
+ }
27
+ /** Detects the container format from magic bytes and parses it. Never throws on hostile input. */
28
+ export declare function parseBinary(b: Buffer): BinaryInfo;
29
+ export interface ExtractedString {
30
+ value: string;
31
+ offset: number;
32
+ }
33
+ /** Printable-ASCII runs, capped. Deterministic, bounded by file size cap. */
34
+ export declare function extractStrings(b: Buffer, minLen?: number, maxCount?: number): {
35
+ strings: ExtractedString[];
36
+ truncated: boolean;
37
+ };
38
+ //# sourceMappingURL=binary-parse.d.ts.map
@@ -0,0 +1,347 @@
1
+ /**
2
+ * ruRE Phase 2 — container-level static inspection of ELF / PE / Mach-O
3
+ * (ADR-0003). Reads integers and C-strings from a Buffer at bounds-checked
4
+ * offsets. Never loads, maps, or executes the target.
5
+ */
6
+ export const MAX_BINARY_BYTES = 128 * 1024 * 1024;
7
+ const MAX_SECTIONS = 4096;
8
+ const MAX_SYMBOLS = 2000;
9
+ const MAX_PE_SECTIONS = 96; // PE format limit
10
+ const ITERATION_BUDGET = 200_000;
11
+ // Total table-walk steps allowed per parseBinary call. Header-declared counts and
12
+ // overlapping regions can multiply work far beyond file size; this bounds it.
13
+ let budget = ITERATION_BUDGET;
14
+ function tick() {
15
+ if (--budget < 0)
16
+ throw new RangeError('iteration budget exhausted');
17
+ }
18
+ function empty(format) {
19
+ return {
20
+ format, arch: 'unknown', bits: null, endianness: 'little', kind: 'unknown', entryPoint: null,
21
+ sections: [], libraries: [], imports: [], exports: [], warnings: [], truncated: false,
22
+ };
23
+ }
24
+ function describeError(err) {
25
+ if (err instanceof RangeError)
26
+ return err.message === 'iteration budget exhausted' ? err.message : 'out-of-bounds read';
27
+ return String(err);
28
+ }
29
+ /** Runs one structure parse; a failure becomes a warning, not an abort. */
30
+ function guarded(info, label, fn) {
31
+ try {
32
+ fn();
33
+ }
34
+ catch (err) {
35
+ info.warnings.push(`${label}: ${describeError(err)}`);
36
+ }
37
+ }
38
+ function cstr(b, off, max = 512) {
39
+ if (!Number.isInteger(off) || off < 0 || off >= b.length)
40
+ throw new RangeError('string offset');
41
+ const limit = Math.min(b.length, off + max);
42
+ let end = off;
43
+ while (end < limit && b[end] !== 0)
44
+ end++;
45
+ return b.toString('latin1', off, end);
46
+ }
47
+ function safeNum(v) {
48
+ if (v > BigInt(Number.MAX_SAFE_INTEGER))
49
+ throw new RangeError('value exceeds safe integer');
50
+ return Number(v);
51
+ }
52
+ function pushCapped(info, list, value) {
53
+ if (!value)
54
+ return;
55
+ if (list.length >= MAX_SYMBOLS) {
56
+ info.truncated = true;
57
+ return;
58
+ }
59
+ list.push(value);
60
+ }
61
+ const ELF_MACHINE = {
62
+ 3: 'x86', 8: 'mips', 20: 'ppc', 21: 'ppc64', 40: 'arm', 62: 'x86-64', 183: 'aarch64', 243: 'riscv',
63
+ };
64
+ function parseElf(b) {
65
+ const info = empty('elf');
66
+ const is64 = b[4] === 2;
67
+ const le = b[5] !== 2;
68
+ info.bits = is64 ? 64 : 32;
69
+ info.endianness = le ? 'little' : 'big';
70
+ const u16 = (o) => (le ? b.readUInt16LE(o) : b.readUInt16BE(o));
71
+ const u32 = (o) => (le ? b.readUInt32LE(o) : b.readUInt32BE(o));
72
+ const u64 = (o) => safeNum(le ? b.readBigUInt64LE(o) : b.readBigUInt64BE(o));
73
+ const word = (o) => (is64 ? u64(o) : u32(o));
74
+ info.arch = ELF_MACHINE[u16(18)] ?? `machine-${u16(18)}`;
75
+ const type = u16(16);
76
+ info.kind = type === 2 ? 'executable' : type === 3 ? 'shared-or-pie' : type === 1 ? 'object' : type === 4 ? 'core' : 'unknown';
77
+ guarded(info, 'entry point', () => { info.entryPoint = word(24); });
78
+ const shdrs = [];
79
+ guarded(info, 'section headers', () => {
80
+ const shoff = word(is64 ? 40 : 32);
81
+ const shentsize = u16(is64 ? 58 : 46);
82
+ let shnum = u16(is64 ? 60 : 48);
83
+ if (shoff === 0 || shentsize === 0) {
84
+ info.warnings.push('no section headers: libraries and symbols are unavailable');
85
+ return;
86
+ }
87
+ if (shnum > MAX_SECTIONS) {
88
+ shnum = MAX_SECTIONS;
89
+ info.truncated = true;
90
+ }
91
+ for (let i = 0; i < shnum; i++) {
92
+ const o = shoff + i * shentsize;
93
+ shdrs.push(is64
94
+ ? { name: u32(o), type: u32(o + 4), offset: u64(o + 24), size: u64(o + 32), link: u32(o + 40), entsize: u64(o + 56) }
95
+ : { name: u32(o), type: u32(o + 4), offset: u32(o + 16), size: u32(o + 20), link: u32(o + 24), entsize: u32(o + 36) });
96
+ }
97
+ const strIdx = u16(is64 ? 62 : 50);
98
+ const strtab = shdrs[strIdx];
99
+ for (const s of shdrs) {
100
+ let name = '';
101
+ if (strtab) {
102
+ try {
103
+ name = cstr(b, strtab.offset + s.name, 128);
104
+ }
105
+ catch { /* unnamed */ }
106
+ }
107
+ info.sections.push({ name, offset: s.offset, size: s.size });
108
+ }
109
+ });
110
+ guarded(info, 'dynamic', () => {
111
+ for (const d of shdrs.filter((s) => s.type === 6)) {
112
+ const dynstr = shdrs[d.link];
113
+ if (!dynstr)
114
+ continue;
115
+ const step = is64 ? 16 : 8;
116
+ for (let o = d.offset; o + step <= d.offset + d.size && o + step <= b.length; o += step) {
117
+ tick();
118
+ const tag = word(o);
119
+ if (tag === 0)
120
+ break;
121
+ if (tag === 1)
122
+ pushCapped(info, info.libraries, cstr(b, dynstr.offset + word(o + (is64 ? 8 : 4))));
123
+ }
124
+ }
125
+ });
126
+ guarded(info, 'dynamic symbols', () => {
127
+ for (const d of shdrs.filter((s) => s.type === 11)) {
128
+ const strs = shdrs[d.link];
129
+ const minSym = is64 ? 24 : 16;
130
+ const step = d.entsize >= minSym ? d.entsize : minSym;
131
+ if (!strs)
132
+ continue;
133
+ for (let o = d.offset; o + step <= d.offset + d.size && o + step <= b.length; o += step) {
134
+ tick();
135
+ const nameOff = u32(o);
136
+ const shndx = u16(o + (is64 ? 6 : 14));
137
+ const bind = b[o + (is64 ? 4 : 12)] >> 4;
138
+ if (nameOff === 0)
139
+ continue;
140
+ const name = cstr(b, strs.offset + nameOff);
141
+ if (shndx === 0)
142
+ pushCapped(info, info.imports, name);
143
+ else if (bind === 1 || bind === 2)
144
+ pushCapped(info, info.exports, name);
145
+ }
146
+ }
147
+ });
148
+ return info;
149
+ }
150
+ const PE_MACHINE = { 0x14c: 'x86', 0x8664: 'x86-64', 0x1c0: 'arm', 0x1c4: 'arm-thumb2', 0xaa64: 'aarch64' };
151
+ function parsePe(b) {
152
+ const info = empty('pe');
153
+ const pe = b.readUInt32LE(0x3c);
154
+ if (b.toString('latin1', pe, pe + 4) !== 'PE\0\0') {
155
+ info.warnings.push('missing PE signature');
156
+ return info;
157
+ }
158
+ const coff = pe + 4;
159
+ const machine = b.readUInt16LE(coff);
160
+ const nSections = b.readUInt16LE(coff + 2);
161
+ const optSize = b.readUInt16LE(coff + 16);
162
+ const chars = b.readUInt16LE(coff + 18);
163
+ const opt = coff + 20;
164
+ const plus = b.readUInt16LE(opt) === 0x20b;
165
+ info.arch = PE_MACHINE[machine] ?? `machine-0x${machine.toString(16)}`;
166
+ info.bits = plus ? 64 : 32;
167
+ info.kind = chars & 0x2000 ? 'dll' : 'executable';
168
+ info.entryPoint = b.readUInt32LE(opt + 16);
169
+ const secs = [];
170
+ guarded(info, 'sections', () => {
171
+ const n = Math.min(nSections, MAX_PE_SECTIONS);
172
+ if (n < nSections)
173
+ info.truncated = true;
174
+ for (let i = 0; i < n; i++) {
175
+ const o = opt + optSize + i * 40;
176
+ const vsize = b.readUInt32LE(o + 8);
177
+ const va = b.readUInt32LE(o + 12);
178
+ const rawSize = b.readUInt32LE(o + 16);
179
+ const raw = b.readUInt32LE(o + 20);
180
+ info.sections.push({ name: b.toString('latin1', o, o + 8).replace(/\0+$/, ''), offset: raw, size: rawSize });
181
+ secs.push({ va, span: Math.max(vsize, rawSize), raw });
182
+ }
183
+ });
184
+ const toOff = (rva) => {
185
+ const s = secs.find((x) => rva >= x.va && rva < x.va + x.span);
186
+ if (!s)
187
+ throw new RangeError('unmapped RVA');
188
+ return s.raw + (rva - s.va);
189
+ };
190
+ const dirBase = opt + (plus ? 112 : 96);
191
+ const dir = (i) => ({ rva: b.readUInt32LE(dirBase + i * 8), size: b.readUInt32LE(dirBase + i * 8 + 4) });
192
+ guarded(info, 'imports', () => {
193
+ const imp = dir(1);
194
+ if (!imp.rva)
195
+ return;
196
+ const step = plus ? 8 : 4;
197
+ for (let o = toOff(imp.rva), n = 0; n < MAX_SYMBOLS; o += 20, n++) {
198
+ tick();
199
+ const origThunk = b.readUInt32LE(o);
200
+ const nameRva = b.readUInt32LE(o + 12);
201
+ const firstThunk = b.readUInt32LE(o + 16);
202
+ if (!origThunk && !nameRva && !firstThunk)
203
+ break;
204
+ const lib = cstr(b, toOff(nameRva), 256);
205
+ pushCapped(info, info.libraries, lib);
206
+ let t = toOff(origThunk || firstThunk);
207
+ for (let k = 0; k < MAX_SYMBOLS; k++, t += step) {
208
+ tick();
209
+ const entry = plus ? b.readBigUInt64LE(t) : BigInt(b.readUInt32LE(t));
210
+ if (entry === 0n)
211
+ break;
212
+ const byOrdinal = entry >> BigInt(plus ? 63 : 31);
213
+ if (byOrdinal)
214
+ pushCapped(info, info.imports, `${lib}!#${entry & 0xffffn}`);
215
+ else
216
+ pushCapped(info, info.imports, `${lib}!${cstr(b, toOff(Number(entry & 0x7fffffffn)) + 2, 256)}`);
217
+ }
218
+ }
219
+ });
220
+ guarded(info, 'exports', () => {
221
+ const exp = dir(0);
222
+ if (!exp.rva)
223
+ return;
224
+ const base = toOff(exp.rva);
225
+ const count = Math.min(b.readUInt32LE(base + 24), MAX_SYMBOLS);
226
+ const names = toOff(b.readUInt32LE(base + 32));
227
+ for (let i = 0; i < count; i++) {
228
+ tick();
229
+ pushCapped(info, info.exports, cstr(b, toOff(b.readUInt32LE(names + i * 4)), 256));
230
+ }
231
+ });
232
+ return info;
233
+ }
234
+ const MACHO_CPU = { 7: 'x86', 0x01000007: 'x86-64', 12: 'arm', 0x0100000c: 'aarch64', 18: 'ppc', 0x01000012: 'ppc64' };
235
+ const MACHO_FILETYPE = { 1: 'object', 2: 'executable', 6: 'dylib', 8: 'bundle', 10: 'dsym' };
236
+ const LC_DYLIBS = new Set([0xc, 0x80000018, 0x8000001f, 0x80000023]);
237
+ function parseMachO(b, base = 0) {
238
+ const info = empty('macho');
239
+ const magic = b.readUInt32BE(base);
240
+ const le = magic === 0xcefaedfe || magic === 0xcffaedfe;
241
+ const is64 = magic === 0xfeedfacf || magic === 0xcffaedfe;
242
+ info.bits = is64 ? 64 : 32;
243
+ info.endianness = le ? 'little' : 'big';
244
+ const u32 = (o) => (le ? b.readUInt32LE(base + o) : b.readUInt32BE(base + o));
245
+ const u64 = (o) => safeNum(le ? b.readBigUInt64LE(base + o) : b.readBigUInt64BE(base + o));
246
+ const cpu = u32(4);
247
+ info.arch = MACHO_CPU[cpu] ?? `cpu-0x${cpu.toString(16)}`;
248
+ info.kind = MACHO_FILETYPE[u32(12)] ?? 'unknown';
249
+ const ncmds = Math.min(u32(16), MAX_SECTIONS);
250
+ guarded(info, 'load commands', () => {
251
+ let o = is64 ? 32 : 28;
252
+ for (let i = 0; i < ncmds; i++) {
253
+ const cmd = u32(o);
254
+ const size = u32(o + 4);
255
+ if (size < 8)
256
+ throw new RangeError('bad load command size');
257
+ if (LC_DYLIBS.has(cmd))
258
+ pushCapped(info, info.libraries, cstr(b, base + o + u32(o + 8), 512));
259
+ else if (cmd === 0x80000028)
260
+ info.entryPoint = u64(o + 8);
261
+ else if (cmd === 0x1 || cmd === 0x19) {
262
+ const seg64 = cmd === 0x19;
263
+ const nsects = u32(o + (seg64 ? 64 : 48));
264
+ let so = o + (seg64 ? 72 : 56);
265
+ for (let s = 0; s < nsects && info.sections.length < MAX_SECTIONS; s++, so += seg64 ? 80 : 68) {
266
+ info.sections.push({
267
+ name: `${b.toString('latin1', base + so + 16, base + so + 32).replace(/\0+$/, '')},${b.toString('latin1', base + so, base + so + 16).replace(/\0+$/, '')}`,
268
+ offset: u32(so + (seg64 ? 48 : 40)),
269
+ size: seg64 ? u64(so + 40) : u32(so + 36),
270
+ });
271
+ }
272
+ }
273
+ o += size;
274
+ }
275
+ });
276
+ return info;
277
+ }
278
+ function parseFat(b) {
279
+ const info = empty('macho-fat');
280
+ info.endianness = 'big';
281
+ const n = Math.min(b.readUInt32BE(4), 16);
282
+ let first = null;
283
+ for (let i = 0; i < n; i++) {
284
+ const o = 8 + i * 20;
285
+ const cpu = b.readUInt32BE(o);
286
+ (info.archs ??= []).push(MACHO_CPU[cpu] ?? `cpu-0x${cpu.toString(16)}`);
287
+ if (first === null)
288
+ first = b.readUInt32BE(o + 8);
289
+ }
290
+ info.arch = (info.archs ?? []).join('+') || 'unknown';
291
+ if (first !== null) {
292
+ guarded(info, 'first slice', () => {
293
+ const slice = parseMachO(b, first);
294
+ Object.assign(info, { bits: slice.bits, kind: slice.kind, entryPoint: slice.entryPoint, sections: slice.sections, libraries: slice.libraries });
295
+ info.warnings.push(...slice.warnings);
296
+ info.warnings.push('fat binary: sections and libraries are from the first slice only');
297
+ });
298
+ }
299
+ return info;
300
+ }
301
+ /** Detects the container format from magic bytes and parses it. Never throws on hostile input. */
302
+ export function parseBinary(b) {
303
+ budget = ITERATION_BUDGET;
304
+ try {
305
+ if (b.length >= 20 && b[0] === 0x7f && b.toString('latin1', 1, 4) === 'ELF')
306
+ return parseElf(b);
307
+ if (b.length >= 0x40 && b[0] === 0x4d && b[1] === 0x5a)
308
+ return parsePe(b);
309
+ if (b.length >= 32) {
310
+ const m = b.readUInt32BE(0);
311
+ if (m === 0xfeedface || m === 0xfeedfacf || m === 0xcefaedfe || m === 0xcffaedfe)
312
+ return parseMachO(b);
313
+ if (m === 0xcafebabe && b.readUInt32BE(4) >= 1 && b.readUInt32BE(4) <= 16)
314
+ return parseFat(b);
315
+ }
316
+ }
317
+ catch (err) {
318
+ const info = empty('unknown');
319
+ info.warnings.push(`header parse failed: ${describeError(err)}`);
320
+ return info;
321
+ }
322
+ const info = empty('unknown');
323
+ info.warnings.push('unrecognized container format (expected ELF, PE, or Mach-O)');
324
+ return info;
325
+ }
326
+ /** Printable-ASCII runs, capped. Deterministic, bounded by file size cap. */
327
+ export function extractStrings(b, minLen = 6, maxCount = 200) {
328
+ const strings = [];
329
+ let start = -1;
330
+ for (let i = 0; i <= b.length; i++) {
331
+ const printable = i < b.length && b[i] >= 0x20 && b[i] < 0x7f;
332
+ if (printable) {
333
+ if (start < 0)
334
+ start = i;
335
+ }
336
+ else if (start >= 0) {
337
+ if (i - start >= minLen) {
338
+ if (strings.length >= maxCount)
339
+ return { strings, truncated: true };
340
+ strings.push({ value: b.toString('latin1', start, Math.min(i, start + 256)), offset: start });
341
+ }
342
+ start = -1;
343
+ }
344
+ }
345
+ return { strings, truncated: false };
346
+ }
347
+ //# sourceMappingURL=binary-parse.js.map