Documentation
¶
Overview ¶
Package verify holds the deterministic, LLM-free primitives of the autonomous pentester's 4-gate verifier (see docs/AUTONOMOUS-PENTEST.md §5): Gate 1, differential reproduction, and the mechanism behind Gate 3, out-of-band (OOB) confirmation.
Both primitives turn a *candidate* vulnerability into machine-proven ground truth without any LLM in the loop:
- Gate 1 re-issues a payload request and a matched baseline/control N times and requires a class-specific oracle (reflection, DB error, boolean-length split, or timing) to hold on every attempt — a single flaky observation rejects the candidate.
- Gate 3's mechanism sends a probe carrying a globally-unique OOB token and polls for a callback to that exact token; a hit is proof the payload executed server-side.
The package is deliberately decoupled from the concrete sender/OOB catcher: it depends only on the small Sender / OOBPoller interfaces defined here, injected by the caller. Real adapters over internal/sender and internal/oob are wired in Phase 2; the unit tests inject scripted fakes, so nothing here touches the network or the wall clock. Every class oracle is an exported pure function.
Index ¶
- Constants
- func BooleanLengthHeld(baseline, payloadTrue, payloadFalse Exchange) bool
- func ErrorSignatureHeld(baseline, payload Exchange) bool
- func ReflectedMarkerHeld(baseline, payload Exchange, marker string) bool
- func TimingHeld(baseline, payload, control Exchange) bool
- type AgentVerifier
- type Candidate
- type ConfirmResult
- type Confirmer
- type DiffResult
- type DiffSpec
- type Exchange
- type OOBPoller
- type OOBResult
- type OOBSpec
- type Proof
- type Request
- type Sender
- type Severity
- type Verdict
- type Verification
- type VerifyDeps
- type VulnClass
Constants ¶
const ( GateDifferential = "differential" // Gate 1 GateAgent = "agent" // Gate 2 GateOOB = "oob" // Gate 3 GateHuman = "human" // Gate 4 )
Gate name constants — the values RejectedAt takes and the keys of the Gates map.
Variables ¶
This section is empty.
Functions ¶
func BooleanLengthHeld ¶
BooleanLengthHeld models the two-payload boolean-SQLi oracle: a TRUE condition response should match the baseline length while a FALSE condition diverges clearly from the TRUE response. Guards:
- baseline must be non-trivial (>= booleanBaselineFloor bytes) — on tiny bodies natural variation reads as a large relative divergence (false positives), the same floor the active-scan boolean check applies;
- all three exchanges must have completed;
- true ≈ baseline within a small tolerance, AND false differs from true beyond a larger floor.
Pure; the three Exchange bodies are supplied by the caller.
func ErrorSignatureHeld ¶
ErrorSignatureHeld reports whether a DB/interpreter error signature appears in the payload response but not in the baseline. Requiring absence in the baseline avoids flagging an endpoint that always echoes such a string. Pure.
func ReflectedMarkerHeld ¶
ReflectedMarkerHeld reports whether a unique marker is present in the payload response but absent from the baseline — the reflected-XSS/injection oracle. The marker must be non-empty; an empty marker never "reflects" (guards against a vacuously-true match). Pure and directly testable.
func TimingHeld ¶
TimingHeld reports whether the timing oracle holds: the payload response is at least timingSlowMs, while both a zero-delay control and the baseline are fast (below timingFastMs) and actually completed. A blocked/errored control returns DurMs 0 and would falsely pass a naive "<threshold" test, so the control and baseline must both be ok(). Pure.
Types ¶
type AgentVerifier ¶
type AgentVerifier interface {
Disprove(ctx context.Context, c Candidate, evidence DiffResult) Verdict
}
AgentVerifier is Gate 2: an adversarial verifier agent, given the candidate plus the Gate-1 differential evidence and told to disprove it. The concrete impl is built in Phase 2 over internal/aiagent; tests inject a scripted fake.
type Candidate ¶
type Candidate struct {
VulnClass string // e.g. "sqli-boolean","xss-reflected","ssrf-blind","cmdi-time"
Severity Severity // drives whether Gate 4 applies
Target string // url/endpoint
Point string // injection point description
Blind bool // requires OOB proof (Gate 3)
Diff DiffSpec // for Gate 1
OOB *OOBSpec // for Gate 3 when Blind; nil for non-blind classes
Summary string // human-readable candidate description for the agent/human gates
}
Candidate is everything the verifier needs to prove one candidate vulnerability. It carries the class-specific differential (Gate 1) and, for blind classes, the OOB spec (Gate 3), plus enough human-readable context for the agent and human gates. A Candidate is never a Finding: it only becomes one if Verify proves it.
type ConfirmResult ¶
ConfirmResult is Gate 4's answer: a human's one-click decision for Critical/High candidates before filing.
type Confirmer ¶
type Confirmer interface {
Confirm(ctx context.Context, c Candidate, proof Proof) ConfirmResult
}
Confirmer is Gate 4: a human confirm for Critical/High candidates. The concrete impl is built in Phase 2 over the humaninput surface; tests inject a fake.
type DiffResult ¶
type DiffResult struct {
Reproduced bool // oracle held N consecutive times
Times int // how many consecutive passes were observed (== N on success)
Baseline []int64 // recorded baseline/control flow ids gathered across attempts
PayloadFlows []int64 // recorded payload flow ids gathered across attempts
Detail string // human/AI-readable explanation
}
DiffResult is the outcome of ReproduceDifferential.
func ReproduceDifferential ¶
func ReproduceDifferential(ctx context.Context, s Sender, spec DiffSpec) DiffResult
ReproduceDifferential re-issues the class's requests up to N times and requires the class oracle to hold on *every* attempt; the first failure short-circuits and rejects the candidate (fluke rejection). It respects ctx cancellation: if ctx is done, it stops and returns a not-reproduced result. Deterministic given a deterministic Sender — no real network, no sleeps.
type DiffSpec ¶
type DiffSpec struct {
Class VulnClass
Baseline Request
Payload Request // the confirming payload (reflected / error / timing)
// Boolean-length variant.
PayloadTrue Request
PayloadFalse Request
// Timing variant: a zero-delay control that must return fast.
Control Request
// Marker is the unique string the reflected-marker oracle looks for.
Marker string
// N is how many consecutive times the oracle must hold. Defaults to 3 when
// <= 0 (see effectiveN).
N int
}
DiffSpec describes a differential-reproduction attempt (Gate 1). It carries the requests needed by the chosen oracle plus how many consecutive passes are required. The set of requests used depends on Class:
- ClassReflected / ClassError: Baseline + Payload (Marker for reflected).
- ClassBoolean: Baseline + PayloadTrue + PayloadFalse.
- ClassTiming: Baseline + Payload + Control (zero-delay).
type Exchange ¶
type Exchange struct {
Status int
Headers map[string][]string
Body []byte
DurMs int64
FlowID int64 // the recorded flow, for PoC attachment later
Err error
}
Exchange is a single HTTP exchange the verifier can reason about, kept minimal and provider-agnostic so a real sender adapter and a test fake can both produce it. It captures only what the oracles need (status, headers, body, duration) plus the recorded FlowID for later PoC attachment and any transport Err.
type OOBPoller ¶
OOBPoller answers "did a callback arrive for this token?". Phase 2 injects an adapter over internal/oob.Catcher (counting interactions whose token matches); tests inject a fake that "arrives" after k polls.
type OOBResult ¶
type OOBResult struct {
Confirmed bool // a callback for Token arrived within the window
Token string // echoed for the proof record
ProbeFlow int64 // the recorded probe flow id (for PoC attachment)
Polls int // how many times the poller was queried
Detail string
}
OOBResult is the outcome of ConfirmOOB.
func ConfirmOOB ¶
ConfirmOOB sends the probe once, then polls poll.HitsForToken(token) until a hit arrives or the window elapses. A callback to a globally-unique URL only we injected is proof the payload executed server-side, so a single hit confirms. No hit within the window ⇒ not confirmed (blind candidates are never filed on inference alone). Respects ctx cancellation: a cancelled ctx stops polling and returns not-confirmed.
The poll cadence is fully injectable (Window/Interval + a fake sleep), so tests drive it without real time.Sleep. The number of poll iterations is deterministic: ceil(window/interval)+1 (an immediate check, then one per interval across the window).
type OOBSpec ¶
type OOBSpec struct {
Probe Request // request with the token URL already injected
Token string // the exact token to correlate an interaction against
Window time.Duration // total time to wait for a callback (default 30s)
Interval time.Duration // gap between polls (default 500ms)
// contains filtered or unexported fields
}
OOBSpec describes an out-of-band confirmation attempt (Gate 3 mechanism). The Probe request must already carry the token's callback URL injected as its payload (the caller mints the token and builds the URL); ConfirmOOB does not construct payloads, it only sends and correlates.
type Proof ¶
type Proof struct {
Proven bool
RejectedAt string // "" if proven, else GateDifferential|GateAgent|GateOOB|GateHuman
ReproCount int
OOBToken string
BaselineFlow int64
PayloadFlow int64
Confidence int // 0-100, derived from which gates a class required AND passed
Gates map[string]any // per-gate detail for finding_verification.gates
AgentVerdict Verdict
HumanConfirm ConfirmResult
}
Proof is the machine proof-record the verifier emits for one candidate. It is the source Phase 2 serializes into a store.FindingVerification row (via ToVerification / Verification): Proven says whether the candidate cleared every applicable gate; RejectedAt names the first gate that failed (empty when proven); Gates carries per-gate detail JSON-serializable straight into finding_verification.gates.
func Verify ¶
func Verify(ctx context.Context, c Candidate, d VerifyDeps) Proof
Verify runs the 4-gate verifier over one candidate in cost order. Any gate that fails rejects immediately: Verify returns Proven=false with RejectedAt naming that gate, so an unproven candidate never becomes a Finding. Gates run in this order (later gates only run if all earlier ones held):
- Gate 1 (always): differential reproduction. Not reproduced ⇒ reject.
- Gate 2 (always): adversarial agent. Verdict != "real" ⇒ reject.
- Gate 3 (only if Candidate.Blind): OOB callback. Not confirmed ⇒ reject.
- Gate 4 (only if Severity >= High): human confirm. Declined ⇒ reject.
Confidence is derived from which gates the candidate required and passed (see confidence()). ctx cancellation surfaces as the current gate failing (the underlying primitives return not-reproduced / not-confirmed on a cancelled ctx).
func (Proof) ToVerification ¶
ToVerification is the map form of Verification, for callers that serialize the proof-record generically (e.g. straight into a JSON body or a generic store upsert). Keys match store.FindingVerification's JSON tags so Phase 2 can decode them into the struct without a manual field map.
func (Proof) Verification ¶
func (p Proof) Verification(findingID, runID int64, vulnClass, oobToken string) Verification
Verification builds the persistable proof-record fields for this proof. It does not import internal/store; Phase 2 constructs the store row from the returned struct. oobToken overrides the proof's recorded token when non-empty (the caller may know the run-scoped token); otherwise Proof.OOBToken is used.
type Request ¶
Request describes a request variant to (re-)issue. It mirrors the shape of internal/sender.Request but is redeclared here to keep the package free of a hard dependency on that concrete type; the Phase-2 adapter maps between them.
type Sender ¶
Sender re-issues a request variant and returns the recorded exchange. Phase 2 injects a real adapter over internal/sender; tests inject a scripted fake.
type Severity ¶
type Severity int
Severity ranks a candidate's impact. It drives whether Gate 4 (human confirm) applies: only High and Critical candidates require a human touchpoint before filing; Medium/Low/Info auto-file once the machine gates pass.
func ParseSeverity ¶
ParseSeverity maps a case-insensitive severity name to a Severity. Unknown or empty strings fall back to SeverityInfo (the safest default: it never triggers the human gate on its own, but such a candidate still needs Gates 1-2).
type Verdict ¶
Verdict is Gate 2's answer: an adversarial verifier agent told to *disprove* the candidate. Result is one of "real" | "refuted" | "uncertain"; anything but "real" rejects.
type Verification ¶
type Verification struct {
FindingID int64
RunID int64
VulnClass string
Gates string // JSON of Proof.Gates
ReproCount int
OOBToken string
BaselineFlow int64
PayloadFlow int64
Confidence int
}
Verification mirrors the persistable fields of store.FindingVerification without importing internal/store (keeping this package free of that coupling). Phase 2 maps it 1:1 onto a store.FindingVerification row. Gates is the JSON-encoded per-gate detail (Proof.Gates marshaled); the numeric/string fields are copied straight across.
type VerifyDeps ¶
type VerifyDeps struct {
Sender Sender
OOB OOBPoller
Agent AgentVerifier
Human Confirmer
}
VerifyDeps injects the four gate collaborators. Sender/OOB drive the two deterministic primitives (Gate 1 / Gate 3); Agent/Human are the two LLM/human gates. The OOB poll cadence is injected on the Candidate's OOBSpec (its sleep hook), so no separate clock is needed here — Verify itself never sleeps.
type VulnClass ¶
type VulnClass string
VulnClass names the differential oracle to apply. Kept as string constants so callers (and the Phase-2 verifier) can pass a plain candidate class through.
const ( ClassReflected VulnClass = "reflected-marker" // marker echoed in the payload response ClassError VulnClass = "error-signature" // interpreter/DB error surfaced by the payload ClassBoolean VulnClass = "boolean-length" // true≈baseline, false diverges (two-payload) ClassTiming VulnClass = "timing" // payload slow, control + baseline fast )