llm-security/tests/hooks
Kjell Tore Guttormsen dfb663fa61 fix(llm-security): F-2/F-3 — contain path traversal + kill npm-view shell injection
Session B of the security-fix track. Both sinks trusted untrusted strings at a
filesystem/subprocess boundary; same fix family as F-1.

F-2 (HIGH, arbitrary file write) — scanners/auto-cleaner.mjs: applyFixes() did
resolve(targetPath, f.file) with no containment, then wrote the cleaned content
back. f.file is untrusted (scanned-repo filenames, or a fully attacker-chosen
--findings file), so file: "../../.claude/settings.json" let the cleaner modify
files OUTSIDE the scanned tree. Add a prefix-containment check before grouping:
absPath must equal targetPath or start with targetPath + sep, else the finding
is refused and reported as skipped. (Documented residual gap: prefix containment
does not stop a symlink inside the tree pointing out — noted inline.)

F-3 (HIGH, command injection, pre-confirmation) — hooks/scripts/
pre-install-supply-chain.mjs: inspectNpmPackage ran execSafe(`npm view ${spec}
--json`), a shell string. spec derives from package tokens parsed out of the
scanned Bash command, so a metachar-bearing token reached the shell on
PreToolUse(Bash) — BEFORE the install, so it ran even if the user then denied
the command. Switch to spawnSync('npm', ['view', spec, '--json']) (no shell);
spec is passed as one argv element. The static `npm audit --json` execSafe call
is left as-is (no interpolation).

TDD (repro -> red -> green):
- tests/scanners/auto-cleaner-traversal.test.mjs: a "../secret.txt" finding must
  not rewrite the outside file; contained files still get cleaned.
- tests/hooks/supply-chain-injection.test.mjs: `npm install $(>/abs/PWNED)`
  (redirect-only $(...) survives normalizeBashExpansion + the whitespace split)
  must not create the sentinel.

Closing gates: full node --test suite 1863/0 (was 1860; +3); gitleaks clean;
F-1 regression re-run GREEN (sink still closed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3s6WnubSSrFjAQTLQdVbG
2026-06-20 11:09:13 +02:00
..
hook-helper.mjs feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command 2026-04-08 08:58:35 +02:00
post-mcp-verify.test.mjs feat(post-mcp-verify): E14 part 2 — cumulative-drift MEDIUM advisory [skip-docs] 2026-04-30 16:40:52 +02:00
post-session-guard.test.mjs feat(post-session-guard): E17 — configurable escalation window + 20-call MEDIUM advisory 2026-04-29 14:26:18 +02:00
pre-bash-destructive.test.mjs feat(pre-bash-destructive): T8 — base64-pipe-shell idiom (E9) 2026-04-30 15:15:29 +02:00
pre-compact-scan.test.mjs chore(llm-security): v7.3.1 — stabilization patch for forkers and downstream users 2026-05-01 06:14:03 +02:00
pre-edit-secrets.test.mjs feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command 2026-04-08 08:58:35 +02:00
pre-install-supply-chain.test.mjs feat(pre-install-supply-chain): E13 — npm scope-hopping MEDIUM advisory with allowlist 2026-04-30 15:38:28 +02:00
pre-prompt-inject-scan.test.mjs feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command 2026-04-08 08:58:35 +02:00
pre-write-pathguard.test.mjs fix(llm-security): B1 pathguard regex — match multi-segment .env.*.* 2026-04-19 23:59:38 +02:00
probe-rm.mjs feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command 2026-04-08 08:58:35 +02:00
probe-secrets.mjs feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command 2026-04-08 08:58:35 +02:00
supply-chain-injection.test.mjs fix(llm-security): F-2/F-3 — contain path traversal + kill npm-view shell injection 2026-06-20 11:09:13 +02:00
update-check.test.mjs feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command 2026-04-08 08:58:35 +02:00