[pitboss/grind] deferred session-0011 (20260520T233019Z-6958)

This commit is contained in:
pitboss 2026-05-21 06:40:08 -05:00
parent 280121607e
commit 227675021b
5 changed files with 115 additions and 8 deletions

View file

@ -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("<!DOCTYPE … &xxe;".into())],
captured_at_ns: 1,
payload_id: "xxe-python-oob-nonce".into(),
kind: ProbeKind::Normal,
witness: ProbeWitness::empty(),
}];
let outcome = build_oob_self_confirmed_outcome("xxe-python-oob-nonce", &vuln);
assert_eq!(outcome.verdict, DifferentialVerdict::ConfirmedProvenOob);
assert_eq!(outcome.vuln_label, "xxe-python-oob-nonce");
assert!(outcome.benign_label.is_empty());
assert_eq!(outcome.vuln_probes.len(), 1);
assert!(outcome.benign_probes.is_empty());
}
#[test]
fn build_outcome_carries_both_traces() {
use crate::dynamic::probe::{ProbeArg, ProbeKind, ProbeWitness, SinkProbe};

View file

@ -485,8 +485,25 @@ pub fn run_spec(spec: &HarnessSpec, opts: &SandboxOptions) -> Result<RunOutcome,
};
match resolved {
None => {
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<RunOutcome,
&benign_probes,
&benign_stub_events,
);
let outcome_record = differential::build_outcome(
let mut outcome_record = differential::build_outcome(
payload.label,
vuln_fired,
&vuln_probes,
@ -520,7 +537,23 @@ pub fn run_spec(spec: &HarnessSpec, opts: &SandboxOptions) -> Result<RunOutcome,
benign_fired,
&benign_probes,
);
let confirmed = outcome_record.verdict == DifferentialVerdict::Confirmed;
// Phase 05 OOB closure: when an OOB-nonce payload also
// carries a paired benign control, promote
// `Confirmed` → `ConfirmedProvenOob` whenever the
// listener observed the per-finding nonce. The
// upgrade preserves the differential trace (benign
// run still recorded) and surfaces the stronger
// network-level evidence to operators.
if outcome_record.verdict == DifferentialVerdict::Confirmed
&& payload.oob_nonce_slot
&& outcome.oob_callback_seen
{
outcome_record.verdict = DifferentialVerdict::ConfirmedProvenOob;
}
let confirmed = matches!(
outcome_record.verdict,
DifferentialVerdict::Confirmed | DifferentialVerdict::ConfirmedProvenOob
);
differential_outcome = Some(outcome_record);
confirmed
}

View file

@ -1117,6 +1117,7 @@ fn build_verdict(
}
}
crate::evidence::DifferentialVerdict::Confirmed
| crate::evidence::DifferentialVerdict::ConfirmedProvenOob
| crate::evidence::DifferentialVerdict::NotConfirmed => VerifyResult {
finding_id: finding_id.to_owned(),
status: VerifyStatus::NotConfirmed,

View file

@ -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`].