ShakerScan End-to-End Test Plan

Status (reconciled 2026-08-29): this is the E2E coverage contract, not a current pass report. Historical 10/10, 12/12, and 12/12 totals were produced by an older harness/fleet and are not release evidence. The change-aware PR smoke workflow runs the platform regression lane, deterministic MI-1/MI-2–MI-6, and AI-1–AI-4 coverage. The manual full release workflow additionally runs D-1–D-4; MI-1-HF remains explicitly opt-in. Rows marked Planned below are not implemented in tests/e2e/run_e2e.py and must not be included in pass totals. make e2e skips the external Hugging Face row; make e2e-model-intake enables it, while make e2e-model-intake-fixture is the explicit offline path. The worker-fingerprint preflight rejects a stale/non-uniform fleet. Slow authenticated crAPI BOLA recall remains in the benchmark harness rather than the E2E workflows.

Why this exists

Every recent escaped bug lived at an integration seam that unit tests mocked out:

BugWhat the unit test didWhat an e2e test does
206 partial-download → false sha256_mismatch / false invalid safetensors headermocked _download_http / local filesfetch a real multi-shard HF model, assert capped checksum semantics and full-size header validation
AI judge redactor leaked secrets to the LLMcalled _redact_secrets_for_judge(...)plant a secret in a honey AI target, scan, assert it is absent from the stored transcript
UnboundLocalError in a legacy Full Coverage rununit-tested harvest_endpoints_with_metarun the equivalent canonical thorough Scan and assert it completes
Principal-probe production bypasstested classify_production_safetyrun a production AI scan, assert the admin-impersonation probe never executes

Principle: an e2e test exercises the full pipeline — POST to the public API → Redis → worker → pinned target or deterministic fixture → DB → result JSON → deployment decision — and asserts on the real output, including adversarial negatives (secrets must not leak, truncation must be flagged, false criticals must not fire). No mocking the seams. Explicit opt-in cases cover external network fetches. Ground truth lives in per-target answer keys.

Harness (Phase 0)

tests/e2e/ — a runner invoked as python -m tests.e2e.run_e2e --area {all|platform|model_intake|ai_gate|dast|hunt}:

  1. Preflight — assert /health and a current, uniform worker fleet. Target failures then surface as real scan failures; optional external prerequisites are enabled explicitly.
  2. Submit real jobs through the public API only (no internal imports).
  3. Poll GET /scans/{id} to a terminal state with a timeout + heartbeat; a stuck/reaped scan fails the case (catches the finalize-hang class).
  4. Evaluate the real result/findings/transcript against the case's expectations.
  5. Scorecard — print per-assertion pass/fail summaries and return non-zero on any gate failure.

Test matrices

Model Intake (Phase 1)

#Harness statusReal submitAssertionCatches
MI-1Implementeddeterministic local large-artifact partial responsechecksum_status == known_unverified_truncated; no sha256_mismatchthe 206 bug without external-network variance
MI-1-HFImplemented, opt-in externalreal nex-agi/Nex-N2-mini shard 1 (make e2e-model-intake)capped checksum is unverified; header offsets validate against the 4.74 GB declared size; no false mismatch/malformed findingthe real registry/range-fetch path
MI-2Implementedsmall fully-downloadable artifact, correct digestchecksum_status == verified, sha256_scope == full_artifactregression guard
MI-3Implementedsame, deliberately wrong expected_sha256critical sha256_mismatch + decision == blockreal tamper detection
MI-4Implementedcrafted .pkl/.pt with dangerous opcodeunsafe-serialization findingserialization detector
MI-5Implementedself-signed, no trust anchorsignature_verification_status == untrusted_roottrust root
MI-6Implementedcaller-supplied trust rejection plus operator-created expired-correct, active-wrong, active-correct, and deactivated durable anchorscaller cannot self-trust; expired/wrong/deactivated keys are untrusted_key; exact active key is verified with signature_trusted_root == truetrust authority, positive crypto path, expiry, wrong-key, revocation
MI-7Implementedforged intake_mode=admission request through the compatibility scan endpointHTTP 409, controlled-workflow pointer, and no queued scanaccidental second admission-authority path
MI-8PlannedModel-Intake deployment decision with an active policy/exceptiondecision honors the active policy (stays block when a required control/exception applies)policy/exception-wiring regression

AI Gate (Phase 2)

#Harness statusScenario (honey secure-rag-agent)AssertionCatches
AI-1Implementedshaker-owasp-llm/shaker-rag-lite smoke scanfixture scan completes; target and scan submission are realdetection-pipeline integration
AI-2Implementedsecret planted in response; fetch /ai/scans/{id}/transcriptsecret absent from stored transcript and judge promptjudge-redactor leak
AI-3Implementedproduction and staging control scansadmin-impersonation probe is blocked in production, not executed there, and generated in stagingprincipal-probe bypass
AI-4Implementedproduction scan without confirm_productionHTTP 409confirm_production gate
AI-5Planneddeterministically-proven finding + AI false-positivefinding not downgraded to infojudge deterministic guard
AI-6PlannedAI scan → deployment-decision, allow_active_exceptions=false + active exceptionstays blockexception gating

DAST (Phase 3)

DAST is a manual release gate, not a per-PR job. The active SQLi/XSS cases run against a pinned Juice Shop container on the scanner's Compose network. Recall %, precision, and dual-user BOLA are slower and more discovery-heavy; they live in the benchmark harness (tests/benchmark/), which is a separate quality signal and is not currently scheduled nightly. The DAST area asserts:

#Harness statusScenario (manual release gate)AssertionCatches
D-1Implementedstandard scan of pinned Juice Shopworker proves target reachability; scan completes (no hang/crash/reap) + graded + findings persistnetwork-wiring / finalize-hang / NUL-crash class
D-1 receiptImplementedsame standard scantemplate receipt matches the underlying Nuclei completion stateadapter-return completion overclaim
D-2Implementedbounded (un-sharded) active scan of the injectable logincritical SQLi detectedactive SQLi recall (spot)
D-3Implementedbounded active scan of the searchXSS detectedactive XSS recall (spot)
D-4Implementedattack-chain assertionsthe 3 removed phantom chains never appearoverclaim regression
D-5Planned slow caseparallel thorough Scan producing truncated/NUL-byte evidencescan completes; oversized evidence is truncated-and-flagged and NUL bytes stripped before DB persisttruncation + NUL-byte crash class

Slow benchmark scope (quality, not the E2E release gate): authenticated smart recall ≥ 70% of the Juice Shop answer key, crAPI dual-user BOLA/IDOR + mass-assignment + JWT, precision (false-positive rate), parallel-scan truncation, and NUL-byte evidence persistence.

Platform regression (Phase 4)

This lane does not launch a scan. It exercises the assembled API, shared database, and Redis-backed read models, then creates and removes a disposable target, disabled schedule, and informational manual-finding record. It is safe for the PR gate and protects adjacent products from Scan/Hunt runtime refactors.

#Harness statusSurfaceAssertion
P-1Implementedhealth, canonical Scan contract, V2 metricsdatabase/Redis/reconciliation are healthy; canonical contract and content-free metrics are mounted
P-2ImplementedContinuous ASMcanonical check-family registry remains queryable
P-3ImplementedConnected Devicesinventory and explicit readiness/degraded state remain queryable
P-4Implementedworkers and Fleetworker freshness plus supported/disabled/unsupported Fleet state remain explicit
P-5Implementedschedules, findings, evidence, timeline, campaigns, Arsenalpublic read models preserve their stable response contracts
P-6Implementedtarget + ASMdisposable target can disable ASM and read coverage/gap projections without launching work
P-7Implementedschedulescreate, disable, read, and delete lifecycle works
P-8Implementedfindings + evidencemanual record appears in filtered findings and its evidence projection, then is deleted

Every recent bug → the e2e test that catches it

Implemented: MI-1 (206) · AI-2 (redaction) · AI-3 (prod bypass) · AI-4 (confirm) · MI-5/6 (trust root) · D-4 (phantom chains).

Planned: AI-5 (judge guard) · D-5 (truncation + crash) · AI-6 / MI-8 (policy wiring).

Workflow split

  • .github/workflows/e2e-pr.yml runs on every pull request so it can be a required check, but starts the stack and executes the platform, deterministic Model Intake, and AI Gate cases only when backend, database, Compose, harness, or workflow code changed. Documentation/UI-only PRs pass without starting Docker.
  • .github/workflows/e2e.yml is a manual full release gate. It starts the pinned Juice Shop profile, proves worker-to-target reachability, and runs --area all. Run it on the exact approved candidate before creating a release tag.
  • There is no automatic push or scheduled nightly E2E run. Do not describe any suite as nightly until a schedule trigger and corresponding operational ownership exist.

Workflow rule

Changes to fetch, scan, redaction, or decision code require the applicable real-stack smoke coverage. Scanner detection changes also require the manual full E2E release gate before release—not a unit test alone.

This page is rendered from docs/E2E_TEST_PLAN.md in the open-source repository at v2.5.4. When documentation and implementation disagree, the code, database schema, and tests at that tag are authoritative.