voyage/tests/hooks
Kjell Tore Guttormsen b208e4ee04 fix(voyage): S21 — close IPv4-mapped IPv6 SSRF bypass + security/safety audit (blind spot #2)
S21 = devil's-advocate blind spot #2 (security/safety), operator scope
"security-core": fix genuine defects, honestly record the rest.

SSRF fix (CWE-918). validateOtlpEndpoint classified the host by literal
string match. Decimal/hex/octal/trailing-dot encodings are already caught
(the WHATWG URL parser canonicalizes them), but IPv4-mapped IPv6 literals
(::ffff:127.0.0.1, ::ffff:192.168.x, ::ffff:169.254.169.254) render as
::ffff:HHHH:HHHH and matched neither the loopback set, the RFC-1918 regex,
link-local, nor HARD_BLOCKED_HOSTS — they passed over https and would reach
loopback / private / cloud-metadata, defeating the comment's "PERMANENTLY
block metadata" promise. Added mappedV4() to decode the embedded IPv4
(dotted + hex-pair forms) and classify on it. TDD: 4 tests failing-first —
mapped loopback/RFC-1918/metadata rejected (metadata stays HARD_BLOCKED even
with VOYAGE_OTEL_ALLOW_PRIVATE=1), mapped public ::ffff:8.8.8.8 still valid
(no over-block). Threat model narrow (endpoint is operator-set env; export
opt-in; a brief cannot set env).

Survivor #18 honest-residual. T2-bakeoff §3 "Classifier interference: 0"
read as satisfied, but the auto/bypass-mode re-run that matters for headless
trekreview was never run (mode is operator-set, not settable in-session) —
footnoted, not gating. Per recommendation #9, moved to an OPEN RESIDUAL
(untested), guarded by a new doc-consistency pin.

Audit record. ## S21 resolution block in devils-advocate-results.md
dispositions all four sub-questions: SSRF (fixed), hooks-block (verified;
advisory-rail residuals: Write-only matcher, Bash-redirect, regex gaps — by
design), malicious-brief-headless (catastrophe-blocked, exfil NOT blocked —
inherent limit). S21b flagged (NOT done): operations.md:15 mis-describes
autonomy-gate.mjs state machine + --gates table.

Test count: real baseline 700 (698 pass / 2 skip), NOT 715 — the node:test
headline carried in S20 commit msgs was stale/miscounted (census figures
were correct). Now 705 (703 pass / 2 skip / 0 fail); census behavior
601->605, doc-pins 71->72, total 672->677.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-19 20:02:56 +02:00
..
bash-guard.test.mjs test(ultraplan-local): add path-guard + bash-guard baseline hook tests (SC8 baseline) 2026-05-04 08:55:49 +02:00
hooks-json-stop-wired.test.mjs chore(voyage): W3 hardening (S2) — exec-form hooks, enforced disallowed-tools, F2 decision 2026-06-18 12:03:11 +02:00
otel-export-otlp.test.mjs feat(voyage): add lib/exporters/otlp-format.mjs — OTLP/JSON enum-integer SC #13 2026-05-09 09:32:29 +02:00
otel-export-textfile.test.mjs feat(voyage): add lib/exporters/textfile-format.mjs — Prometheus text-format pure transform SC #12 2026-05-09 09:30:58 +02:00
otel-export-validators.test.mjs fix(voyage): S21 — close IPv4-mapped IPv6 SSRF bypass + security/safety audit (blind spot #2) 2026-06-19 20:02:56 +02:00
otel-export.test.mjs feat(voyage): add hooks/scripts/otel-export.mjs — Stop-hook orchestration SC #14, opt-in via VOYAGE_EXPORT_MODE 2026-05-09 09:44:13 +02:00
path-guard.test.mjs test(ultraplan-local): add path-guard + bash-guard baseline hook tests (SC8 baseline) 2026-05-04 08:55:49 +02:00
post-compact-flush.test.mjs feat(ultraplan-local): add PostCompact rehydrate hook to re-inject session-state after compaction 2026-05-04 07:57:42 +02:00
worktree-guard.test.mjs feat(ultraplan-local): harden Phase 2.6 wave executor (11 sub-changes for plugin-in-monorepo + gitignored-state topology) 2026-05-04 07:49:45 +02:00