Merge pull request #54 from Pazificateur69/feat/arsenal-re-analysis-invocations
feat(arsenal): real invocations for the reverse-engineering / mobile / smart-contract analysis loadout
This commit is contained in:
2 files changed
+198
No files matched your search
@@ -14,6 +14,8 @@ import {
|
||||
adapterToCustomTool,
|
||||
buildAdapterTools,
|
||||
toolNameFor,
|
||||
isMintable,
|
||||
hasArgTemplate,
|
||||
type AdapterToolDeps,
|
||||
type SubprocessResult,
|
||||
} from '../arsenal/adapter-tools.js';
|
||||
@@ -261,3 +263,107 @@ describe('source / supply-chain scanners run a real invocation (not `<binary> <t
|
||||
expect(deps.spawns.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reverse / mobile / smart-contract analysers run a real invocation (not `<binary> <file>`)', () => {
|
||||
// Each entry: [adapter id, params, expected argv]. Without bespoke templates these all fell through
|
||||
// to DEFAULT_TEMPLATE and spawned `['<file>']` — a broken invocation for a subcommand/flag-driven
|
||||
// analyser (and, for r2, one that drops into an interactive shell and hangs the loop).
|
||||
const CASES: Array<[string, Record<string, unknown>, string[]]> = [
|
||||
['objdump', { file: 'a.out' }, ['-d', '-M', 'intel', 'a.out']],
|
||||
['readelf', { file: 'a.out' }, ['-a', 'a.out']],
|
||||
['checksec', { file: 'a.out' }, ['--file=a.out']],
|
||||
['radare2', { file: 'a.out' }, ['-q', '-e', 'scr.color=0', '-c', 'ij', 'a.out']],
|
||||
['exiftool', { file: 'a.out' }, ['-json', 'a.out']],
|
||||
['mythril', { file: 'Vault.sol' }, ['analyze', 'Vault.sol', '-o', 'json']],
|
||||
['apkleaks', { file: 'app.apk' }, ['-f', 'app.apk']],
|
||||
['slither', { path: 'contracts' }, ['contracts', '--json', '-']],
|
||||
['mobsfscan', { path: 'app-src' }, ['--json', 'app-src']],
|
||||
];
|
||||
|
||||
it.each(CASES)('%s builds its real analyser argv', async (id, params, expected) => {
|
||||
const deps = makeDeps();
|
||||
await mint(id, deps).handler(ctx(params));
|
||||
expect(deps.spawns[0]).toEqual(expected);
|
||||
});
|
||||
|
||||
it('radare2 never drops into an interactive shell (batch `-q -c`, never a bare file)', async () => {
|
||||
const deps = makeDeps();
|
||||
await mint('radare2', deps).handler(ctx({ file: 'a.out' }));
|
||||
const argv = deps.spawns[0];
|
||||
expect(argv).toContain('-q'); // quit after the command
|
||||
expect(argv).toContain('-c'); // run one command, non-interactive
|
||||
expect(argv).not.toEqual(['a.out']); // the old broken/hanging positional default
|
||||
});
|
||||
|
||||
it('a file-oriented analyser with no path degrades (clean failure, no spawn)', async () => {
|
||||
const deps = makeDeps();
|
||||
const res = await mint('objdump', deps).handler(ctx({}));
|
||||
expect(res.success).toBe(false);
|
||||
expect(res.error).toMatch(/file\/artifact path/);
|
||||
expect(deps.spawns.length).toBe(0); // never spawned `objdump ''`
|
||||
});
|
||||
|
||||
it('a file-oriented analyser refuses an http(s) URL as a local artifact (no spawn)', async () => {
|
||||
const deps = makeDeps();
|
||||
// A networked mission address inherited via context must not become `objdump https://x`.
|
||||
const res = await mint('readelf', deps).handler(ctx({ file: 'https://victim.example/firmware.bin' }));
|
||||
expect(res.success).toBe(false);
|
||||
expect(deps.spawns.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invocation-honesty guard — every mintable adapter is classified, none silently falls through', () => {
|
||||
// Every adapter that CAN be minted either carries a bespoke ARG_TEMPLATE (a real invocation) or is
|
||||
// explicitly filed under exactly one reason for legitimately falling through to `<binary> <target>`.
|
||||
// This is the invocation-correctness sibling of the tool-count honesty test: a newly-catalogued
|
||||
// subcommand tool (e.g. a future `zzuf`/`radare2`-style binary) FAILS this test until someone
|
||||
// decides whether it needs a template — it can never silently ship a broken positional invocation.
|
||||
|
||||
// `<binary> <target>` is genuinely a correct, useful invocation for these.
|
||||
const POSITIONAL_TARGET_OK = new Set([
|
||||
'file', 'strings', 'binwalk', 'radamsa', 'jadx', 'class-dump', 'solhint', 'echidna', 'john',
|
||||
]);
|
||||
// No single safe default exists: the operator supplies the full command (cloud CLIs, model-config
|
||||
// red-team harnesses, device-runtime tools, project-scaffolded RE, or a target-executing debugger).
|
||||
const OPERATOR_DRIVEN = new Set([
|
||||
'prowler', 'scoutsuite', 'cloudfox', 'pmapper', 'aws-cli', 'az-cli', 'gcloud-cli',
|
||||
'garak', 'promptfoo', 'foundry-forge', 'foundry-cast', 'openssl', 'afl-fuzz', 'ghidra',
|
||||
'gdb', 'objection', 'drozer',
|
||||
]);
|
||||
// KNOWN DEBT: the positional default is broken/degraded and these SHOULD get a template later.
|
||||
// Tracked honestly here rather than hidden — a good follow-up PR shrinks this set.
|
||||
const KNOWN_DEBT = new Set([
|
||||
'feroxbuster', 'osv-scanner', 'hashcat', 'apktool', 'yara',
|
||||
]);
|
||||
|
||||
const mintable = TOOL_ADAPTERS.filter(isMintable);
|
||||
const classified = [POSITIONAL_TARGET_OK, OPERATOR_DRIVEN, KNOWN_DEBT];
|
||||
|
||||
it('every mintable adapter is either templated or explicitly filed under one fall-through reason', () => {
|
||||
const unaccounted = mintable
|
||||
.filter(a => !hasArgTemplate(a))
|
||||
.map(a => a.id)
|
||||
.filter(id => !classified.some(s => s.has(id)));
|
||||
expect(unaccounted, 'these mintable adapters silently fall through to `<binary> <target>`').toEqual([]);
|
||||
});
|
||||
|
||||
it('the fall-through allow-lists are disjoint and never overlap a templated adapter', () => {
|
||||
const seen = new Set<string>();
|
||||
for (const set of classified) {
|
||||
for (const id of set) {
|
||||
expect(seen.has(id), `${id} is listed in more than one fall-through set`).toBe(false);
|
||||
seen.add(id);
|
||||
const a = TOOL_ADAPTERS.find(x => x.id === id);
|
||||
expect(a, `allow-listed id '${id}' is not in the catalog`).toBeTruthy();
|
||||
expect(isMintable(a as ToolAdapter), `${id} is not mintable — remove it`).toBe(true);
|
||||
expect(hasArgTemplate(a as ToolAdapter), `${id} IS templated — remove it from the fall-through lists`).toBe(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('the reverse/mobile/smart-contract loadout is actually templated (regression pin)', () => {
|
||||
for (const id of ['objdump', 'readelf', 'checksec', 'radare2', 'exiftool', 'mythril', 'apkleaks', 'slither', 'mobsfscan']) {
|
||||
expect(hasArgTemplate(adapter(id)), `${id} lost its ARG_TEMPLATE`).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user