fix(arsenal): run real scanner invocations for source/supply-chain tools

The opt-in code/supply-chain scanners (semgrep, gitleaks, trufflehog, trivy,
syft, grype, checkov) are subcommand-driven and non-networked, but had no
bespoke arg template — so adapterToCustomTool fell through to DEFAULT_TEMPLATE
and spawned `<binary> <target>`. For these that is a broken invocation:
`semgrep <host>` never runs a scan, and with no target it is `semgrep ''`.
semgrep in particular is advertised on the arsenal's headline tool list.

Add per-binary templates that mirror each tool's catalog commandHint and
operate on a filesystem PATH (default: the working dir "."):
  semgrep    → scan --config auto --json <path>
  gitleaks   → detect --source <path> --report-format json --redact --no-banner
  trufflehog → filesystem <path> --json --no-update
  trivy      → fs --format json <path>
  syft       → dir:<path> -o cyclonedx-json
  grype      → dir:<path> -o json
  checkov    → -d <path> -o json

Subcommand + output flags are hardcoded (never taken from an LLM string),
matching the conservative pattern of the existing templates. The path is the
only tunable and is sanitised by scanPath(): a leading '-' (flag injection) or
an inherited http(s) mission target is rejected in favour of ".". Option-looking
targets are still refused up front by the factory's existing guard (no spawn).

Adds table-driven tests asserting the exact argv for each scanner, the "."
default, http(s)-target rejection, and the option-looking-path refusal.
This commit is contained in:
Pazificateur69 committed 2026-07-06 10:50:43 +02:00
1 parent a5667374bf
commit 8522a60fec
2 files changed
+100

No files matched your search

+56
View File
@@ -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. */