ADR 0018: Bound legacy verification behind a separate active authority
- Status: Accepted
- Date: 2026-08-14
- Supersedes: ADR 0016's phase-five-to-nine direct-I/O inventory
- Retains: ADR 0016's discovery authority and whole-run accounting decision
- Relates to: ADR 0009, ADR 0012, and ADR 0017
Context
ADR 0016 moved legacy discovery phases two through four behind one bounded authority while accurately recording that phases five through nine still used the raw compatibility client. Those later phases mixed transport success with broad security labels, and several probes lacked the controls required to separate a target-specific response from baseline text, generic error pages, network jitter, encoding, or a request-delivery receipt.
The ordered runner remains a historical compatibility surface. Improving its
built-in verification phases must not imply that it has joined
StandardWebDecisionRuntime, that its whole-run resource use is metered, or
that a successful request is a vulnerability verdict.
Decision
- Built-in phases five through nine share one context-owned
LegacyVerificationAuthority, separate from the passive discovery authority. It accepts only bodyless HTTP(S) requests at the target's exact normalized origin, rejects URL credentials, disables automatic redirects and retries, and accounts dispatches at theActivestage. - A configurable
VerificationLimitsenvelope bounds total requests, per-request timeout, shared monotonic wall time, cumulative delivered response-body bytes, and retained bytes per response. Its finite budget cannot be reset during a run and cannot consume or replenish the discovery envelope. The default shared request ceiling is 96. The five built-in consumers also retain phase-local ceilings of 20, 18, 16, 16, and 16 requests respectively, so one early phase cannot consume the authority intended for every later built-in. - SQL behavior uses negative baselines and randomized controls. A diagnostic
must be absent from baseline/control and reproduced by the candidate. Timing
uses repeated control/test pairs, alternating randomized order, median and
median-absolute-deviation thresholds, and a bounded request ceiling. Either
accepted category is only eligible for
NeedsReview. - XSS-named compatibility code is an exact-reflection observer. It sends a
benign nonce, requires a negative baseline and consistent exact replay, and
records media type plus a bounded syntactic context. Without browser
execution evidence it remains
Unknown, including in script or attribute text. - Template arithmetic uses randomized operands, an exact expected result, a
syntactically similar non-evaluating control, a negative baseline, and exact
replay. An accepted differential is only eligible for
NeedsReview; it does not identify an engine, sandbox escape, or code execution. - LFI/XXE is inert by default. An SDK host may explicitly configure a benign,
scan-specific local-file canary on an authorized fixture using independent
random identifiers for the file name and expected content. A negative
baseline, randomized missing-file control, and two exact positive replays
are required before
NeedsReviewis eligible. The compatibility OOB string never dispatches XXE traffic because no trusted callback verifier exists. - SSRF is inert by default. With an explicitly configured, validated bare DNS OOB domain, the phase may deliver a nonce-bearing callback URL through an already observed parameter at the authorized origin. It records only the target request's status as typed probe evidence. It does not collect callbacks, and HTTP 200, 401, or 403 cannot establish an SSRF conclusion. No localhost, cloud-metadata, or sensitive-file probe is compiled as a default.
- A separate context bridge accepts typed public outcomes only from the
allowlisted SQL-behavior, template-arithmetic, and local-file-canary action
IDs. Each report must be verifier-produced at the
Activestage, origin-scoped, case-correlated, backed by evidence in the sameKnowledgeBase, markedNeedsReview, and configured as knowledge-only so it cannot transition a hypothesis. Raw descriptions and severity strings never gain this authority. ScanRunnercheckpoints the typed outcome ledger around each phase. A normal return may publish the new verifier outcomes and suppress the same phase's raw aggregate. Error, panic, timeout, cancellation, or authority exhaustion discards that phase's pending public outcome projection.
Consequences
- Built-in phases two through nine no longer use the public raw client, and the architecture gate rejects raw-client reacquisition, direct dispatch, and crossing between the passive and active authority seams.
- The two scoped authorities do not make the complete ordered runner metered.
Phase one and host-defined custom
ScanPhaseextensions can still use raw direct I/O, so request and body accounting for a legacy run remainsUnmetered; elapsed wall time remains observed. LegacyVerificationAuthorityis notStandardWebDecisionRuntime, andVerificationLimitsis not the standard runtime'sRuntimeBudget.- Same-origin is request scope, not authorization. The operator or SDK host remains responsible for permission to send every probe, provision a canary, and use an OOB callback domain.
- SQL timing, reflection, template arithmetic, local-file canary observation,
request delivery, and callback receipt are distinct facts. The first three
eligible differential categories and the explicit canary stop at
NeedsReview; reflection stops atUnknown; SSRF has no verifier outcome.
Alternatives considered
- Reuse the passive discovery envelope. Rejected because active probe traffic needs a distinct policy, stage, and budget that cannot consume or reset discovery work.
- Move the phases directly into
StandardWebDecisionRuntime. Rejected for this focused migration because the historical ordered runner has different orchestration, output, and extension contracts. - Promote every positive heuristic record to a typed outcome. Rejected because raw strings, HTTP status, exact reflection, and request delivery do not supply verifier authority.
- Keep default local-file, XXE, localhost, or cloud-metadata probes. Rejected because safe defaults require inert behavior or an explicit benign fixture and a trustworthy receipt boundary.