From 227675021bf594bb3d9b2710b0eee338cb63290a Mon Sep 17 00:00:00 2001 From: pitboss Date: Thu, 21 May 2026 06:40:08 -0500 Subject: [PATCH] [pitboss/grind] deferred session-0011 (20260520T233019Z-6958) --- src/dynamic/differential.rs | 42 +++++++++++++++++++++++++++++++++++++ src/dynamic/runner.rs | 41 ++++++++++++++++++++++++++++++++---- src/dynamic/verify.rs | 1 + src/evidence.rs | 11 ++++++++++ tests/xxe_corpus.rs | 28 +++++++++++++++++++++---- 5 files changed, 115 insertions(+), 8 deletions(-) diff --git a/src/dynamic/differential.rs b/src/dynamic/differential.rs index 460aca59..3861bd73 100644 --- a/src/dynamic/differential.rs +++ b/src/dynamic/differential.rs @@ -66,6 +66,29 @@ pub fn build_outcome( } } +/// Build a self-confirming [`DifferentialOutcome`] for OOB-nonce payloads. +/// +/// When a payload carries +/// [`crate::dynamic::corpus::CuratedPayload::oob_nonce_slot`] = `true` and +/// the [`crate::dynamic::oob::OobListener`] observed the per-finding nonce +/// callback, the OOB observation is independent network-level evidence +/// that the sink fired. A benign URL structurally cannot hit a per- +/// finding nonce, so no paired benign control is required. The runner +/// emits this outcome with [`DifferentialVerdict::ConfirmedProvenOob`] +/// in place of the usual two-payload differential rule. +pub fn build_oob_self_confirmed_outcome( + vuln_label: &str, + vuln_probes: &[SinkProbe], +) -> DifferentialOutcome { + DifferentialOutcome { + verdict: DifferentialVerdict::ConfirmedProvenOob, + vuln_label: vuln_label.to_owned(), + benign_label: String::new(), + vuln_probes: vuln_probes.iter().map(sink_probe_to_record).collect(), + benign_probes: Vec::new(), + } +} + fn sink_probe_to_record(p: &SinkProbe) -> DifferentialProbeRecord { use crate::dynamic::probe::ProbeArg; DifferentialProbeRecord { @@ -108,6 +131,25 @@ mod tests { assert_eq!(evaluate(false, true), DifferentialVerdict::ReversedDifferential); } + #[test] + fn oob_self_confirmed_outcome_carries_only_vuln_trace() { + use crate::dynamic::probe::{ProbeArg, ProbeKind, ProbeWitness, SinkProbe}; + let vuln = vec![SinkProbe { + sink_callee: "lxml.etree.XMLParser.parse".into(), + args: vec![ProbeArg::String(" Result { - no_benign_control = true; - false + // Phase 05 OOB closure: OOB-nonce payloads with + // `benign_control = None` are structurally self- + // confirming when the listener observed the callback. + // A benign URL cannot hit a per-finding nonce, so the + // OOB observation is independent network-level + // evidence the sink fired. Skip the no-benign-control + // downgrade and emit + // [`DifferentialVerdict::ConfirmedProvenOob`]. + if payload.oob_nonce_slot && outcome.oob_callback_seen { + let outcome_record = differential::build_oob_self_confirmed_outcome( + payload.label, + &vuln_probes, + ); + differential_outcome = Some(outcome_record); + true + } else { + no_benign_control = true; + false + } } Some(benign) => { let benign_bytes = materialise_bytes(benign, None) @@ -512,7 +529,7 @@ pub fn run_spec(spec: &HarnessSpec, opts: &SandboxOptions) -> Result Result VerifyResult { finding_id: finding_id.to_owned(), status: VerifyStatus::NotConfirmed, diff --git a/src/evidence.rs b/src/evidence.rs index 49c45c23..74f411f6 100644 --- a/src/evidence.rs +++ b/src/evidence.rs @@ -764,6 +764,17 @@ pub struct AttemptSummary { pub enum DifferentialVerdict { /// Vulnerable payload fired the oracle and the benign control did not. Confirmed, + /// Stronger tier of [`DifferentialVerdict::Confirmed`]: in addition to + /// the in-process oracle firing, an out-of-band callback to the + /// per-finding nonce was observed by the + /// [`crate::dynamic::oob::OobListener`]. Emitted when the runner + /// exercised a payload with + /// [`crate::dynamic::corpus::CuratedPayload::oob_nonce_slot`] = `true` + /// and the listener saw the nonce. Such payloads are structurally + /// self-confirming (a benign URL cannot hit a per-finding nonce), so + /// the verdict is treated as terminal positive evidence even when + /// `benign_control` is `None`. + ConfirmedProvenOob, /// Both vulnerable and benign payloads fired the oracle — the oracle /// cannot discriminate; downgrade to /// [`InconclusiveReason::OracleCollisionSuspected`]. diff --git a/tests/xxe_corpus.rs b/tests/xxe_corpus.rs index 92532915..fd6b7260 100644 --- a/tests/xxe_corpus.rs +++ b/tests/xxe_corpus.rs @@ -557,10 +557,12 @@ mod e2e_phase_05 { /// is attached and the runner exercises the `xxe--oob-nonce` /// payload, the parser's external-entity hook performs a real HTTP /// GET against the loopback nonce URL and the listener records the - /// hit. Asserts the observation half of the Phase 05 OOB closure; - /// the verdict-tier promotion (Confirmed → Confirmed+ProvenOob) is - /// broader runner-rework tracked separately in - /// `.pitboss/play/deferred.md`. + /// hit. Asserts both halves of the Phase 05 OOB closure: the + /// callback observation AND the verdict-tier promotion from + /// `Confirmed` to `ConfirmedProvenOob` (the runner's + /// `build_oob_self_confirmed_outcome` path treats the OOB-nonce + /// payload as self-confirming since a benign URL structurally + /// cannot hit a per-finding nonce). fn run_oob(lang: Lang, fixture: &str, entry_name: &str) -> Option { use nyx_scanner::dynamic::oob::OobListener; use nyx_scanner::dynamic::sandbox::NetworkPolicy; @@ -633,6 +635,24 @@ mod e2e_phase_05 { oob_attempt.outcome.oob_callback_seen, "parser external-entity hook must fetch loopback URL so OOB listener records the nonce; got attempt={oob_attempt:?}", ); + // Phase 05 OOB closure: the listener observation must promote the + // verdict tier from `Confirmed` to `ConfirmedProvenOob`. The + // payload carries `oob_nonce_slot: true` + `benign_control: None` + // so the runner's self-confirming path emits the upgraded verdict + // and sets `triggered_by` on the OOB attempt itself. + assert!( + oob_attempt.triggered, + "OOB attempt must mark triggered=true under the self-confirming OOB path; got attempt={oob_attempt:?}", + ); + let diff = outcome + .differential + .as_ref() + .expect("self-confirming OOB run must carry a DifferentialOutcome"); + assert_eq!( + diff.verdict, + DifferentialVerdict::ConfirmedProvenOob, + "OOB callback observation must promote verdict tier; got diff={diff:?}", + ); } #[test]