Merge pull request #40 from Pazificateur69/fix/arsenal-scanner-invocation
fix(arsenal): run real scanner invocations for source/supply-chain tools
This commit is contained in:
2 files changed
+100
No files matched your search
@@ -217,3 +217,47 @@ describe('buildAdapterTools', () => {
|
||||
expect(tools.map(t => t.name)).not.toContain(toolNameFor(adapter('nmap')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('source / supply-chain scanners run a real invocation (not `<binary> <target>`)', () => {
|
||||
// Each entry: [adapter id, argv for `path: 'src'`]. Without bespoke templates these all fell
|
||||
// through to DEFAULT_TEMPLATE and spawned `['src']` (or `['']` with no path) — a broken scan.
|
||||
const CASES: Array<[string, string[]]> = [
|
||||
['semgrep', ['scan', '--config', 'auto', '--json', 'src']],
|
||||
['gitleaks', ['detect', '--source', 'src', '--report-format', 'json', '--redact', '--no-banner']],
|
||||
['trufflehog', ['filesystem', 'src', '--json', '--no-update']],
|
||||
['trivy', ['fs', '--format', 'json', 'src']],
|
||||
['syft', ['dir:src', '-o', 'cyclonedx-json']],
|
||||
['grype', ['dir:src', '-o', 'json']],
|
||||
['checkov', ['-d', 'src', '-o', 'json']],
|
||||
];
|
||||
|
||||
it.each(CASES)('%s builds its real scanner argv for an explicit path', async (id, expected) => {
|
||||
const deps = makeDeps();
|
||||
await mint(id, deps).handler(ctx({ path: 'src' }));
|
||||
expect(deps.spawns[0]).toEqual(expected);
|
||||
});
|
||||
|
||||
it.each(CASES)('%s defaults to the working dir "." when no path is given', async (id) => {
|
||||
const deps = makeDeps();
|
||||
await mint(id, deps).handler(ctx({}));
|
||||
// The path slot resolves to "." — assert "." is present and the argv is more than a bare target.
|
||||
expect(deps.spawns[0].some(a => a === '.' || a === 'dir:.')).toBe(true);
|
||||
expect(deps.spawns[0].length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('never inherits an http(s) mission target as a scan path (falls back to ".")', async () => {
|
||||
const deps = makeDeps();
|
||||
// A networked mission address must not become `semgrep scan --config auto --json https://x` —
|
||||
// that is not a filesystem path. scanPath rejects it and falls back to ".".
|
||||
await mint('semgrep', deps).handler(ctx({ target: 'https://victim.example' }));
|
||||
expect(deps.spawns[0]).toEqual(['scan', '--config', 'auto', '--json', '.']);
|
||||
});
|
||||
|
||||
it('refuses an option-looking path (leading "-" would be a scanner flag) — no spawn', async () => {
|
||||
const deps = makeDeps();
|
||||
// The factory's option-looking-target guard fires before argv is built, so the scanner never runs.
|
||||
const result = await mint('trivy', deps).handler(ctx({ path: '--output=/etc/x' }));
|
||||
expect(result.success).toBe(false);
|
||||
expect(deps.spawns.length).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -78,6 +78,21 @@ interface ArgTemplate {
|
||||
const str = (v: unknown): string | undefined =>
|
||||
typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
||||
|
||||
/**
|
||||
* Resolve the filesystem PATH a source/supply-chain scanner should run against (semgrep, gitleaks,
|
||||
* trivy, …). These adapters are non-networked and operate on a directory, defaulting to the working
|
||||
* dir (`.`). The subcommand + output flags in each template are HARDCODED — only this path is
|
||||
* tunable, and it is sanitised so it cannot smuggle a flag or an inherited URL past the gate:
|
||||
* - a leading `-` would be reparsed as a scanner flag (arg injection) → fall back to `.`,
|
||||
* - an http(s) URL (e.g. a networked mission target inherited via context.target.address) is not a
|
||||
* scan path → fall back to `.`.
|
||||
*/
|
||||
const scanPath = (target: string, params: Record<string, unknown>): string => {
|
||||
const p = str(params.path) ?? str(params.target) ?? (target || undefined);
|
||||
if (!p || /^-/.test(p) || /^https?:\/\//i.test(p)) return '.';
|
||||
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.
|
||||
@@ -217,6 +232,47 @@ const ARG_TEMPLATES: Record<string, ArgTemplate> = {
|
||||
return args;
|
||||
},
|
||||
},
|
||||
|
||||
// ── Source / supply-chain scanners (non-networked, operate on a PATH) ───────────────────────────
|
||||
// Without these, each falls through to DEFAULT_TEMPLATE and spawns `<binary> <target>` — which for
|
||||
// a subcommand-driven scanner is a broken invocation (e.g. `semgrep .` never runs a scan, and with
|
||||
// no path it is `semgrep ''`). Templates mirror the catalog `commandHint` and hardcode the
|
||||
// subcommand + machine-readable output flags; the only tunable is the scan path (see scanPath).
|
||||
semgrep: {
|
||||
targetParam: 'path',
|
||||
defaultTimeoutMs: 300_000,
|
||||
build: (target, params) => ['scan', '--config', 'auto', '--json', scanPath(target, params)],
|
||||
},
|
||||
gitleaks: {
|
||||
targetParam: 'path',
|
||||
defaultTimeoutMs: 180_000,
|
||||
build: (target, params) => ['detect', '--source', scanPath(target, params), '--report-format', 'json', '--redact', '--no-banner'],
|
||||
},
|
||||
trufflehog: {
|
||||
targetParam: 'path',
|
||||
defaultTimeoutMs: 300_000,
|
||||
build: (target, params) => ['filesystem', scanPath(target, params), '--json', '--no-update'],
|
||||
},
|
||||
trivy: {
|
||||
targetParam: 'path',
|
||||
defaultTimeoutMs: 300_000,
|
||||
build: (target, params) => ['fs', '--format', 'json', scanPath(target, params)],
|
||||
},
|
||||
syft: {
|
||||
targetParam: 'path',
|
||||
defaultTimeoutMs: 180_000,
|
||||
build: (target, params) => ['dir:' + scanPath(target, params), '-o', 'cyclonedx-json'],
|
||||
},
|
||||
grype: {
|
||||
targetParam: 'path',
|
||||
defaultTimeoutMs: 180_000,
|
||||
build: (target, params) => ['dir:' + scanPath(target, params), '-o', 'json'],
|
||||
},
|
||||
checkov: {
|
||||
targetParam: 'path',
|
||||
defaultTimeoutMs: 180_000,
|
||||
build: (target, params) => ['-d', scanPath(target, params), '-o', 'json'],
|
||||
},
|
||||
};
|
||||
|
||||
/** Fallback for any mintable adapter without a bespoke template: pass the target as a positional arg. */
|
||||
|
||||
Reference in new issue
Block a user