feat(arsenal): real invocations for the reverse-engineering / mobile / smart-contract analysis loadout

The Kali+ adapter factory only carries bespoke ARG_TEMPLATES for the network
and source/supply-chain scanners. The reverse-engineering, mobile and
smart-contract analysers catalogued by the cloud/mobile/binary-RE loadouts fall
through to DEFAULT_TEMPLATE and spawn a bare `<binary> <file>` — a broken
invocation for a subcommand/flag-driven tool, and for radare2 one that drops
into an INTERACTIVE prompt and hangs the agent loop until the timeout burns out.

Add conservative, read-only ARG_TEMPLATES (mirroring each catalog commandHint)
for objdump, readelf, checksec, r2, exiftool, myth (mythril), apkleaks, slither
and mobsfscan. File-oriented tools resolve their artifact through a new
`artifactPath` helper that refuses an absent path or an inherited http(s) URL by
throwing, which the handler turns into a clean {success:false} result (never a
bare `objdump ''`). No intrusive or code-executing flag is added — risk stays
where the catalog put it, and the existing option-looking-target / scope gates
still apply.

Add an invocation-honesty guard test (sibling of the tool-count honesty test):
every mintable adapter must be either templated or explicitly filed under one
fall-through reason — POSITIONAL_TARGET_OK, OPERATOR_DRIVEN, or KNOWN_DEBT — so a
newly catalogued subcommand tool can never silently ship a broken positional
invocation, and the remaining debt (feroxbuster, osv-scanner, hashcat, apktool,
yara) is tracked in the open rather than hidden.

All offline gate checks pass (lint typecheck test doctor verify-claims
test:no-fitting test:no-self-fitting test:gate prompt:audit smoke
test:arsenal-tools arsenal:smoke).
This commit is contained in:
Pazificateur69 committed 2026-07-08 10:28:23 +02:00
1 parent a122c567f1
commit 353ea33a23
2 files changed
+198

No files matched your search

+92
View File
@@ -93,6 +93,24 @@ const scanPath = (target: string, params: Record<string, unknown>): string => {
return p;
};
/**
* Resolve the local FILE/artifact a reverse-engineering or mobile analyser runs against (objdump,
* readelf, r2, myth, apkleaks, …). Unlike a directory scanner, a binary tool has no sensible cwd
* default — a bare `objdump .` is meaningless — so instead of silently degrading this REFUSES an
* absent or non-local path by throwing, which the factory handler turns into a clean
* `{ success:false }` result (never an unhandled rejection). The two rejected shapes:
* - no path at all → the tool would spawn `<binary> ''` / `<binary>` and produce nothing,
* - an http(s) URL (e.g. a networked mission target inherited via context.target.address) → not a
* local artifact. (A leading `-` is already refused upstream by the factory's option-looking-
* target guard, since these templates read the artifact from their `targetParam`.)
*/
const artifactPath = (target: string, params: Record<string, unknown>): string => {
const p = str(params.file) ?? str(params.path) ?? str(params.artifact) ?? (target || undefined);
if (!p) throw new Error('requires a file/artifact path (none was provided).');
if (/^https?:\/\//i.test(p)) throw new Error(`'${p}' is a URL, not a local artifact path.`);
return p;
};
/**
* Per-binary arg templates for the common command-ready adapters. Keyed by `adapter.binary` (the
* process name), with `adapter.id` also accepted as a fallback key so callers can template by either.
@@ -273,6 +291,70 @@ const ARG_TEMPLATES: Record<string, ArgTemplate> = {
defaultTimeoutMs: 180_000,
build: (target, params) => ['-d', scanPath(target, params), '-o', 'json'],
},
// ── Reverse-engineering / mobile / smart-contract static analysis (local_read, operate on a FILE) ─
// Without these each falls through to DEFAULT_TEMPLATE and spawns `<binary> <file>` — which for a
// subcommand- or flag-driven analyser is a broken (or, for r2, an INTERACTIVE-shell-hanging)
// invocation. Templates mirror the catalog `commandHint` and hardcode the READ-ONLY subcommand +
// machine-readable output flags; the only tunable is the artifact/scan path (see artifactPath /
// scanPath). None add an intrusive or code-executing flag — risk stays where the catalog put it.
objdump: {
// `objdump <file>` errors ("no options given"); disassembly needs an action flag.
targetParam: 'file',
defaultTimeoutMs: 60_000,
build: (target, params) => ['-d', '-M', 'intel', artifactPath(target, params)],
},
readelf: {
// `readelf <file>` errors ("Warning: Nothing to do"); `-a` dumps all ELF headers.
targetParam: 'file',
defaultTimeoutMs: 60_000,
build: (target, params) => ['-a', artifactPath(target, params)],
},
checksec: {
// checksec takes its subject as `--file=<path>`, not a bare positional.
targetParam: 'file',
defaultTimeoutMs: 30_000,
build: (target, params) => ['--file=' + artifactPath(target, params)],
},
r2: {
// A bare `r2 <file>` drops into an INTERACTIVE prompt and blocks the agent loop until the
// per-adapter timeout burns out. `-q -c ij` runs ONE read-only info command as JSON, then quits;
// `scr.color=0` keeps the JSON free of ANSI escapes. No `w`/`!`/analysis-write commands are used.
targetParam: 'file',
defaultTimeoutMs: 60_000,
build: (target, params) => ['-q', '-e', 'scr.color=0', '-c', 'ij', artifactPath(target, params)],
},
exiftool: {
// `exiftool <file>` works but prints human text; `-json` gives a structured, parseable channel.
targetParam: 'file',
defaultTimeoutMs: 30_000,
build: (target, params) => ['-json', artifactPath(target, params)],
},
myth: {
// `myth <file>` errors; Mythril needs the `analyze` subcommand. `-o json` emits machine output.
targetParam: 'file',
defaultTimeoutMs: 300_000,
build: (target, params) => ['analyze', artifactPath(target, params), '-o', 'json'],
},
apkleaks: {
// `apkleaks <file>` errors; the APK is supplied via `-f`. Results print to stdout.
targetParam: 'file',
defaultTimeoutMs: 180_000,
build: (target, params) => ['-f', artifactPath(target, params)],
},
slither: {
// Slither accepts a positional target, but a bare run prints human text; `--json -` streams the
// findings as JSON to stdout. Directory-oriented, so it may default to the working dir.
targetParam: 'path',
defaultTimeoutMs: 300_000,
build: (target, params) => [scanPath(target, params), '--json', '-'],
},
mobsfscan: {
// Directory-oriented static scanner; `--json` gives a structured channel over the default text.
targetParam: 'path',
defaultTimeoutMs: 180_000,
build: (target, params) => ['--json', scanPath(target, params)],
},
};
/** Fallback for any mintable adapter without a bespoke template: pass the target as a positional arg. */
@@ -289,6 +371,16 @@ function resolveTemplate(adapter: ToolAdapter): ArgTemplate {
return ARG_TEMPLATES[adapter.binary] ?? ARG_TEMPLATES[adapter.id] ?? DEFAULT_TEMPLATE;
}
/**
* True when the adapter has a bespoke `ARG_TEMPLATE` (a real, hardcoded invocation) rather than
* falling through to `DEFAULT_TEMPLATE`'s bare `<binary> <target>`. Exported so the invocation-honesty
* guard test can assert that every mintable adapter's invocation correctness is explicitly classified
* — and that a newly-catalogued subcommand tool cannot silently ship a broken positional invocation.
*/
export function hasArgTemplate(adapter: ToolAdapter): boolean {
return Boolean(ARG_TEMPLATES[adapter.binary] ?? ARG_TEMPLATES[adapter.id]);
}
/**
* Resolve the target string for an adapter call: the template's preferred param first, then the other
* common target keys, then `context.target.address`. Returns undefined if none is present.