[pitboss] phase 11: Track J.9 + Track L.9 — CRYPTO, JSON_PARSE, UNAUTHORIZED_ID, DATA_EXFIL corpora

This commit is contained in:
pitboss 2026-05-18 09:37:37 -05:00
parent 61a9e4e5df
commit 6784d73e25
85 changed files with 2508 additions and 30 deletions

View file

@ -48,9 +48,12 @@ pub mod audit;
pub mod registry;
mod cmdi;
mod crypto;
mod data_exfil;
mod deserialize;
mod fmt_string;
mod header_injection;
mod json_parse;
mod ldap;
mod open_redirect;
mod path_trav;
@ -58,6 +61,7 @@ mod prototype_pollution;
mod sqli;
mod ssrf;
mod ssti;
mod unauthorized_id;
mod xpath;
mod xss;
mod xxe;
@ -98,7 +102,8 @@ pub use crate::dynamic::oracle::Oracle;
/// | 12 | 2026-05-18 | Phase 08 / Track J.6: `HEADER_INJECTION` cap lit for Java / Python / PHP / Ruby / JS / Go / Rust; `ProbeKind::HeaderEmit` + `ProbePredicate::HeaderInjected`; per-lang `setHeader` shims |
/// | 13 | 2026-05-18 | Phase 09 / Track J.7: `OPEN_REDIRECT` cap lit for Java / Python / PHP / Ruby / JS / Go / Rust; `ProbeKind::Redirect` + `ProbePredicate::RedirectHostNotIn`; per-lang `sendRedirect` / `redirect()` shims |
/// | 14 | 2026-05-18 | Phase 10 / Track J.8: `PROTOTYPE_POLLUTION` cap lit for JS / TS; `ProbeKind::PrototypePollution` + `ProbePredicate::PrototypeCanaryTouched`; Node harness installs `Proxy`-style canary trap on `Object.prototype.__nyx_canary` |
pub const CORPUS_VERSION: u32 = 14;
/// | 15 | 2026-05-18 | Phase 11 / Track J.9: `CRYPTO` (Java/Python/PHP/Go/Rust) + `JSON_PARSE` (JS/Python/Ruby) + `UNAUTHORIZED_ID` (7 langs) + `DATA_EXFIL` (7 langs); `ProbeKind::{WeakKey,IdorAccess,OutboundNetwork}` + `ProbePredicate::{WeakKeyEntropy,IdorBoundaryCrossed,OutboundHostNotIn}`; `UnsupportedReason::SoundOracleUnavailable` for caps with no sound oracle |
pub const CORPUS_VERSION: u32 = 15;
/// Where a payload originated.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -0,0 +1,44 @@
//! Go `Cap::CRYPTO` payloads — `math/rand.Intn` weak-key
//! generation.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const WEAK_BITS: u32 = 16;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-go-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/go/vuln.go"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
benign_control: Some(PayloadRef {
label: "crypto-go-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-go-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/go/benign.go"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,55 @@
//! Java `Cap::CRYPTO` payloads — `java.util.Random.nextBytes`
//! weak-key generation.
//!
//! Vuln payload: marker bytes that signal the harness to drive its
//! `java.util.Random` key-generation path. The harness emits a key
//! bounded inside a 16-bit search space and writes a
//! [`crate::dynamic::probe::ProbeKind::WeakKey`] probe — the
//! [`crate::dynamic::oracle::ProbePredicate::WeakKeyEntropy`]
//! predicate fires for `key_int < 2^16`.
//!
//! Benign control: marker bytes that route the harness through
//! `java.security.SecureRandom`, producing a 256-bit key whose
//! integer view trivially exceeds the budget.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const WEAK_BITS: u32 = 16;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-java-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/java/vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
benign_control: Some(PayloadRef {
label: "crypto-java-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-java-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/java/benign.java"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,26 @@
//! Weak-crypto (`Cap::CRYPTO`) per-language payload slices.
//!
//! Phase 11 (Track J.9) carves a weak-key entropy oracle across the
//! five backend languages where homegrown key generation is common
//! enough to matter: Java (`java.util.Random.nextBytes` → key bytes),
//! Python (`random.randint(0, 0xFFFF)`), PHP (`mt_rand(0, 0xFFFF)`),
//! Go (`math/rand.Intn(0x10000)`), Rust (`rand::thread_rng` truncated
//! to 16 bits). Every vuln payload triggers the harness's
//! instrumented key-generation path with a seed that produces an
//! attacker-derivable key bounded inside the 16-bit search space.
//! The harness shim writes a
//! [`crate::dynamic::probe::ProbeKind::WeakKey { key_int }`] probe
//! with the produced integer view of the key bytes; the
//! [`crate::dynamic::oracle::ProbePredicate::WeakKeyEntropy`]
//! predicate fires when `key_int < 2^max_bits` (`max_bits = 16` by
//! default). The paired benign control routes the same harness
//! through a CSPRNG (`SecureRandom`, `secrets.token_bytes`,
//! `random_bytes(32)`, `crypto/rand.Read`, `rand::rngs::OsRng`) so
//! the produced `key_int` trivially exceeds the budget and the
//! predicate stays clear.
pub mod go;
pub mod java;
pub mod php;
pub mod python;
pub mod rust;

View file

@ -0,0 +1,43 @@
//! PHP `Cap::CRYPTO` payloads — `mt_rand` weak-key generation.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const WEAK_BITS: u32 = 16;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-php-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/php/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
benign_control: Some(PayloadRef {
label: "crypto-php-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-php-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/php/benign.php"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,53 @@
//! Python `Cap::CRYPTO` payloads — `random.randint` weak-key
//! generation.
//!
//! Vuln payload: marker bytes that route the harness through
//! `random.randint(0, 0xFFFF)`; the harness emits a
//! [`crate::dynamic::probe::ProbeKind::WeakKey`] probe and the
//! [`crate::dynamic::oracle::ProbePredicate::WeakKeyEntropy`]
//! predicate fires.
//!
//! Benign control: marker bytes that route the harness through
//! `secrets.token_bytes(32)`.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const WEAK_BITS: u32 = 16;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-python-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
benign_control: Some(PayloadRef {
label: "crypto-python-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-python-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/python/benign.py"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,44 @@
//! Rust `Cap::CRYPTO` payloads — `rand::thread_rng` weak-key
//! generation truncated to 16 bits.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const WEAK_BITS: u32 = 16;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-rust-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/rust/vuln.rs"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
benign_control: Some(PayloadRef {
label: "crypto-rust-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-rust-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/rust/benign.rs"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,43 @@
//! go `Cap::DATA_EXFIL` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"attacker.test",
label: "data-exfil-go-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/go/vuln.go"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
benign_control: Some(PayloadRef {
label: "data-exfil-go-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"127.0.0.1",
label: "data-exfil-go-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/go/benign.go"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,43 @@
//! java `Cap::DATA_EXFIL` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"attacker.test",
label: "data-exfil-java-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/java/vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
benign_control: Some(PayloadRef {
label: "data-exfil-java-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"127.0.0.1",
label: "data-exfil-java-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/java/benign.java"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,43 @@
//! js `Cap::DATA_EXFIL` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"attacker.test",
label: "data-exfil-js-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/js/vuln.js"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
benign_control: Some(PayloadRef {
label: "data-exfil-js-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"127.0.0.1",
label: "data-exfil-js-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/js/benign.js"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,22 @@
//! Data-exfiltration (`Cap::DATA_EXFIL`) per-language payload
//! slices.
//!
//! Phase 11 (Track J.9) carves an outbound-network oracle across
//! all seven backend-capable languages. Each harness stands up a
//! mock HTTP client that records the destination host of every
//! outbound request via a
//! [`crate::dynamic::probe::ProbeKind::OutboundNetwork { host }`]
//! probe. The
//! [`crate::dynamic::oracle::ProbePredicate::OutboundHostNotIn`]
//! predicate fires when the captured `host` falls outside the
//! configured loopback allowlist (`&["127.0.0.1", "localhost"]`).
//! The vuln payload supplies `attacker.test`; the paired benign
//! control supplies `127.0.0.1` so the predicate stays clear.
pub mod go;
pub mod java;
pub mod js;
pub mod php;
pub mod python;
pub mod ruby;
pub mod rust;

View file

@ -0,0 +1,43 @@
//! php `Cap::DATA_EXFIL` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"attacker.test",
label: "data-exfil-php-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/php/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
benign_control: Some(PayloadRef {
label: "data-exfil-php-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"127.0.0.1",
label: "data-exfil-php-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/php/benign.php"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,43 @@
//! python `Cap::DATA_EXFIL` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"attacker.test",
label: "data-exfil-python-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
benign_control: Some(PayloadRef {
label: "data-exfil-python-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"127.0.0.1",
label: "data-exfil-python-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/python/benign.py"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,43 @@
//! ruby `Cap::DATA_EXFIL` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"attacker.test",
label: "data-exfil-ruby-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
benign_control: Some(PayloadRef {
label: "data-exfil-ruby-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"127.0.0.1",
label: "data-exfil-ruby-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/ruby/benign.rb"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,43 @@
//! rust `Cap::DATA_EXFIL` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"attacker.test",
label: "data-exfil-rust-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/rust/vuln.rs"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
benign_control: Some(PayloadRef {
label: "data-exfil-rust-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"127.0.0.1",
label: "data-exfil-rust-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/rust/benign.rs"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,51 @@
//! JavaScript `Cap::JSON_PARSE` payloads — `JSON.parse` then deep
//! assign / `Object.assign` chain.
//!
//! Same canary oracle as the Phase 10 PROTOTYPE_POLLUTION corpus
//! ([`crate::dynamic::oracle::ProbePredicate::PrototypeCanaryTouched`]).
//! The harness routes both payloads through `JSON.parse` first to
//! exercise the parse-then-assign flow specifically (whereas the
//! Phase 10 corpus passes the JSON literal directly to the deep-merge
//! sink without an intervening parse).
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const CANARY: &str = "__nyx_canary";
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: br#"{"__proto__":{"__nyx_canary":"pwned"}}"#,
label: "json-parse-js-proto-key",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse/javascript/vuln.js"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
benign_control: Some(PayloadRef {
label: "json-parse-js-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: br#"{"data":{"__nyx_canary":"pwned"}}"#,
label: "json-parse-js-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse/javascript/benign.js"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,21 @@
//! JSON-parse pollution (`Cap::JSON_PARSE`) per-language payload
//! slices.
//!
//! Phase 11 (Track J.9) reuses the prototype-canary oracle from
//! Phase 10 across the three languages whose JSON parsers have a
//! published pollution surface: JavaScript (`JSON.parse` then deep
//! assign), Python (`json.loads` then `dict.update` /
//! `setattr`-driven attribute pollution), Ruby (`JSON.parse` then
//! recursive merge). Every vuln payload binds a JSON literal whose
//! top-level key is `__proto__`; the per-language harness's
//! instrumented canary trap (`Object.prototype.__nyx_canary` in JS,
//! a `dict`/class-scoped sentinel in Python, an `Object.prepend`
//! flag in Ruby) records a
//! [`crate::dynamic::probe::ProbeKind::PrototypePollution`] probe
//! once the malicious key reaches the shared chain. The paired
//! benign control sends a JSON literal whose top-level key is the
//! regular property `data`, leaving the chain untouched.
pub mod javascript;
pub mod python;
pub mod ruby;

View file

@ -0,0 +1,45 @@
//! Python `Cap::JSON_PARSE` payloads — `json.loads` then
//! attribute-pollution via `setattr` / `dict.update` on a shared
//! sentinel object.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const CANARY: &str = "__nyx_canary";
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: br#"{"__proto__":{"__nyx_canary":"pwned"}}"#,
label: "json-parse-python-proto-key",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
benign_control: Some(PayloadRef {
label: "json-parse-python-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: br#"{"data":{"__nyx_canary":"pwned"}}"#,
label: "json-parse-python-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse/python/benign.py"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,44 @@
//! Ruby `Cap::JSON_PARSE` payloads — `JSON.parse` then recursive
//! `Hash#deep_merge!` on a shared sentinel object.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const CANARY: &str = "__nyx_canary";
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: br#"{"__proto__":{"__nyx_canary":"pwned"}}"#,
label: "json-parse-ruby-proto-key",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
benign_control: Some(PayloadRef {
label: "json-parse-ruby-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: br#"{"data":{"__nyx_canary":"pwned"}}"#,
label: "json-parse-ruby-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse/ruby/benign.rb"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -24,8 +24,9 @@ use std::collections::HashMap;
use std::sync::OnceLock;
use super::{
cmdi, deserialize, fmt_string, header_injection, ldap, open_redirect, path_trav,
prototype_pollution, sqli, ssrf, ssti, xpath, xss, xxe,
cmdi, crypto, data_exfil, deserialize, fmt_string, header_injection, json_parse, ldap,
open_redirect, path_trav, prototype_pollution, sqli, ssrf, ssti, unauthorized_id, xpath, xss,
xxe,
};
use super::{CapCorpus, CuratedPayload, Oracle};
use crate::dynamic::oracle::ProbePredicate;
@ -36,13 +37,42 @@ use crate::symbol::Lang;
/// and sinks we cannot yet model with a reliable oracle. The
/// [`super::audit`] module asserts that the union of caps covered by
/// [`CORPUS::entries`] and this constant equals [`Cap::all`].
pub const CORPUS_UNSUPPORTED_LANG_NEUTRAL: u32 = Cap::ENV_VAR.bits()
| Cap::SHELL_ESCAPE.bits()
| Cap::URL_ENCODE.bits()
| Cap::JSON_PARSE.bits()
| Cap::CRYPTO.bits()
| Cap::UNAUTHORIZED_ID.bits()
| Cap::DATA_EXFIL.bits();
///
/// Phase 11 (Track J.9) carved `CRYPTO`, `JSON_PARSE`,
/// `UNAUTHORIZED_ID`, and `DATA_EXFIL` corpora; the remaining caps
/// here (`ENV_VAR`, `SHELL_ESCAPE`, `URL_ENCODE`) are pure
/// sources / sanitizers with no sink behaviour and route through
/// [`crate::evidence::UnsupportedReason::SoundOracleUnavailable`]
/// at run time.
pub const CORPUS_UNSUPPORTED_LANG_NEUTRAL: u32 =
Cap::ENV_VAR.bits() | Cap::SHELL_ESCAPE.bits() | Cap::URL_ENCODE.bits();
/// Caps for which no sound oracle exists — emitted as
/// [`crate::evidence::UnsupportedReason::SoundOracleUnavailable`]
/// instead of [`crate::evidence::UnsupportedReason::NoPayloadsForCap`]
/// so the unsupported budget accounting reflects the structural
/// impossibility rather than a missing-payload gap. Currently the
/// same set as [`CORPUS_UNSUPPORTED_LANG_NEUTRAL`]; kept as a
/// distinct constant so future caps that legitimately cannot be
/// oracled (e.g. side-channel timing) can land here without
/// expanding the lang-neutral unsupported set.
pub const CORPUS_SOUND_ORACLE_UNAVAILABLE: u32 =
Cap::ENV_VAR.bits() | Cap::SHELL_ESCAPE.bits() | Cap::URL_ENCODE.bits();
/// Human-actionable hint for [`CORPUS_SOUND_ORACLE_UNAVAILABLE`]
/// caps, surfaced via
/// [`crate::evidence::UnsupportedReason::SoundOracleUnavailable::hint`].
pub fn sound_oracle_unavailable_hint(cap: Cap) -> &'static str {
if cap == Cap::ENV_VAR {
"ENV_VAR is a source cap with no externally-observable sink behaviour"
} else if cap == Cap::SHELL_ESCAPE {
"SHELL_ESCAPE is a sanitizer cap whose effect is observed at the wrapping sink"
} else if cap == Cap::URL_ENCODE {
"URL_ENCODE is a sanitizer cap whose effect is observed at the wrapping sink"
} else {
"no sound oracle is currently available for this cap"
}
}
/// Flat `(Cap, Lang, slice)` table. A single cap can carry per-language
/// variants — that's the whole reason this layer exists.
@ -98,6 +128,28 @@ const ENTRIES: &[(Cap, Lang, &[CuratedPayload])] = &[
Lang::TypeScript,
prototype_pollution::typescript::PAYLOADS,
),
(Cap::CRYPTO, Lang::Java, crypto::java::PAYLOADS),
(Cap::CRYPTO, Lang::Python, crypto::python::PAYLOADS),
(Cap::CRYPTO, Lang::Php, crypto::php::PAYLOADS),
(Cap::CRYPTO, Lang::Go, crypto::go::PAYLOADS),
(Cap::CRYPTO, Lang::Rust, crypto::rust::PAYLOADS),
(Cap::JSON_PARSE, Lang::JavaScript, json_parse::javascript::PAYLOADS),
(Cap::JSON_PARSE, Lang::Python, json_parse::python::PAYLOADS),
(Cap::JSON_PARSE, Lang::Ruby, json_parse::ruby::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Python, unauthorized_id::python::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Ruby, unauthorized_id::ruby::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Java, unauthorized_id::java::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Php, unauthorized_id::php::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::JavaScript, unauthorized_id::js::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Go, unauthorized_id::go::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Rust, unauthorized_id::rust::PAYLOADS),
(Cap::DATA_EXFIL, Lang::Python, data_exfil::python::PAYLOADS),
(Cap::DATA_EXFIL, Lang::Ruby, data_exfil::ruby::PAYLOADS),
(Cap::DATA_EXFIL, Lang::Java, data_exfil::java::PAYLOADS),
(Cap::DATA_EXFIL, Lang::Php, data_exfil::php::PAYLOADS),
(Cap::DATA_EXFIL, Lang::JavaScript, data_exfil::js::PAYLOADS),
(Cap::DATA_EXFIL, Lang::Go, data_exfil::go::PAYLOADS),
(Cap::DATA_EXFIL, Lang::Rust, data_exfil::rust::PAYLOADS),
];
/// Reserved for per-cap oracle defaults. Empty in Phase 02; populated by
@ -312,19 +364,18 @@ mod tests {
assert!(!payloads_for(Cap::HEADER_INJECTION).is_empty());
assert!(!payloads_for(Cap::OPEN_REDIRECT).is_empty());
assert!(!payloads_for(Cap::PROTOTYPE_POLLUTION).is_empty());
assert!(!payloads_for(Cap::CRYPTO).is_empty());
assert!(!payloads_for(Cap::JSON_PARSE).is_empty());
assert!(!payloads_for(Cap::UNAUTHORIZED_ID).is_empty());
assert!(!payloads_for(Cap::DATA_EXFIL).is_empty());
}
#[test]
fn unsupported_caps_return_empty() {
let unsupported = [
Cap::ENV_VAR,
Cap::SHELL_ESCAPE,
Cap::URL_ENCODE,
Cap::JSON_PARSE,
Cap::CRYPTO,
Cap::UNAUTHORIZED_ID,
Cap::DATA_EXFIL,
];
// Phase 11 (Track J.9): only pure-source / pure-sanitizer
// caps remain unsupported. CRYPTO / JSON_PARSE /
// UNAUTHORIZED_ID / DATA_EXFIL now carry payloads.
let unsupported = [Cap::ENV_VAR, Cap::SHELL_ESCAPE, Cap::URL_ENCODE];
for cap in unsupported {
assert!(
payloads_for(cap).is_empty(),
@ -333,6 +384,62 @@ mod tests {
}
}
#[test]
fn phase_11_caps_have_payloads() {
assert!(!payloads_for(Cap::CRYPTO).is_empty());
assert!(!payloads_for(Cap::JSON_PARSE).is_empty());
assert!(!payloads_for(Cap::UNAUTHORIZED_ID).is_empty());
assert!(!payloads_for(Cap::DATA_EXFIL).is_empty());
}
#[test]
fn phase_11_caps_pair_benign_controls_per_lang() {
let cases: &[(Cap, &[Lang])] = &[
(Cap::CRYPTO, &[Lang::Java, Lang::Python, Lang::Php, Lang::Go, Lang::Rust]),
(Cap::JSON_PARSE, &[Lang::JavaScript, Lang::Python, Lang::Ruby]),
(
Cap::UNAUTHORIZED_ID,
&[
Lang::Python,
Lang::Ruby,
Lang::Java,
Lang::Php,
Lang::JavaScript,
Lang::Go,
Lang::Rust,
],
),
(
Cap::DATA_EXFIL,
&[
Lang::Python,
Lang::Ruby,
Lang::Java,
Lang::Php,
Lang::JavaScript,
Lang::Go,
Lang::Rust,
],
),
];
for (cap, langs) in cases {
for lang in *langs {
let slice = payloads_for_lang(*cap, *lang);
assert!(
!slice.is_empty(),
"({cap:?}, {lang:?}) must have payloads",
);
let vuln = slice
.iter()
.find(|p| !p.is_benign)
.unwrap_or_else(|| panic!("missing vuln for ({cap:?}, {lang:?})"));
let resolved = resolve_benign_control_lang(vuln, *cap, *lang)
.unwrap_or_else(|| panic!("missing benign for ({cap:?}, {lang:?})"));
assert!(resolved.is_benign);
}
}
}
#[test]
fn fileio_has_benign_payload() {
assert!(benign_payload_for(Cap::FILE_IO).is_some());
@ -359,6 +466,10 @@ mod tests {
Cap::HEADER_INJECTION,
Cap::OPEN_REDIRECT,
Cap::PROTOTYPE_POLLUTION,
Cap::CRYPTO,
Cap::JSON_PARSE,
Cap::UNAUTHORIZED_ID,
Cap::DATA_EXFIL,
] {
let has_vuln = payloads_for(cap).iter().any(|p| !p.is_benign);
assert!(has_vuln, "{cap:?} must have at least one vuln payload");
@ -413,6 +524,10 @@ mod tests {
Cap::HEADER_INJECTION,
Cap::OPEN_REDIRECT,
Cap::PROTOTYPE_POLLUTION,
Cap::CRYPTO,
Cap::JSON_PARSE,
Cap::UNAUTHORIZED_ID,
Cap::DATA_EXFIL,
];
for cap in caps {
for p in payloads_for(cap) {
@ -442,6 +557,10 @@ mod tests {
Cap::HEADER_INJECTION,
Cap::OPEN_REDIRECT,
Cap::PROTOTYPE_POLLUTION,
Cap::CRYPTO,
Cap::JSON_PARSE,
Cap::UNAUTHORIZED_ID,
Cap::DATA_EXFIL,
];
for cap in caps {
for p in payloads_for(cap) {
@ -558,6 +677,10 @@ mod tests {
Cap::HEADER_INJECTION,
Cap::OPEN_REDIRECT,
Cap::PROTOTYPE_POLLUTION,
Cap::CRYPTO,
Cap::JSON_PARSE,
Cap::UNAUTHORIZED_ID,
Cap::DATA_EXFIL,
];
for cap in caps {
for p in payloads_for(cap).iter().filter(|p| p.is_benign) {

View file

@ -0,0 +1,41 @@
//! go `Cap::UNAUTHORIZED_ID` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"bob",
label: "idor-go-cross-tenant",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/go/vuln.go"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
benign_control: Some(PayloadRef {
label: "idor-go-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"alice",
label: "idor-go-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/go/benign.go"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,41 @@
//! java `Cap::UNAUTHORIZED_ID` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"bob",
label: "idor-java-cross-tenant",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/java/vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
benign_control: Some(PayloadRef {
label: "idor-java-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"alice",
label: "idor-java-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/java/benign.java"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,41 @@
//! js `Cap::UNAUTHORIZED_ID` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"bob",
label: "idor-js-cross-tenant",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/js/vuln.js"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
benign_control: Some(PayloadRef {
label: "idor-js-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"alice",
label: "idor-js-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/js/benign.js"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,23 @@
//! IDOR / unauthorized-id-access (`Cap::UNAUTHORIZED_ID`)
//! per-language payload slices.
//!
//! Phase 11 (Track J.9) carves an IDOR oracle across all seven
//! backend-capable languages. Each harness stands up a mock data
//! store keyed by `owner_id` and a hard-coded `caller_id`
//! (`"alice"`). The vuln payload supplies an `owner_id` that
//! belongs to another user (`"bob"`); the harness's instrumented
//! lookup returns the record without an authorization check and
//! writes a [`crate::dynamic::probe::ProbeKind::IdorAccess { caller_id,
//! owner_id }`] probe. The
//! [`crate::dynamic::oracle::ProbePredicate::IdorBoundaryCrossed`]
//! predicate fires whenever `caller_id != owner_id`. The paired
//! benign control asks for the caller's own record (`"alice"`), so
//! the probe records matching ids and the predicate stays clear.
pub mod go;
pub mod java;
pub mod js;
pub mod php;
pub mod python;
pub mod ruby;
pub mod rust;

View file

@ -0,0 +1,41 @@
//! php `Cap::UNAUTHORIZED_ID` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"bob",
label: "idor-php-cross-tenant",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/php/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
benign_control: Some(PayloadRef {
label: "idor-php-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"alice",
label: "idor-php-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/php/benign.php"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,41 @@
//! Python `Cap::UNAUTHORIZED_ID` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"bob",
label: "idor-python-cross-tenant",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
benign_control: Some(PayloadRef {
label: "idor-python-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"alice",
label: "idor-python-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/python/benign.py"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,41 @@
//! ruby `Cap::UNAUTHORIZED_ID` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"bob",
label: "idor-ruby-cross-tenant",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
benign_control: Some(PayloadRef {
label: "idor-ruby-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"alice",
label: "idor-ruby-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/ruby/benign.rb"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -0,0 +1,41 @@
//! rust `Cap::UNAUTHORIZED_ID` payloads.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
bytes: b"bob",
label: "idor-rust-cross-tenant",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/rust/vuln.rs"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
benign_control: Some(PayloadRef {
label: "idor-rust-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"alice",
label: "idor-rust-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/rust/benign.rs"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -315,6 +315,54 @@ pub enum ProbePredicate {
/// [`ProbeKind::PrototypePollution::property`].
canary: &'static str,
},
/// Phase 11 (Track J.9): CRYPTO weak-key entropy predicate.
///
/// Fires when at least one drained probe carries
/// [`ProbeKind::WeakKey`] whose `key_int` is strictly less than
/// `2^max_bits` — i.e. the produced key actually fits inside the
/// search-space budget the predicate models. A benign control
/// generated by a cryptographically strong RNG produces a
/// `key_int` whose magnitude trivially exceeds the budget, so the
/// predicate stays clear.
///
/// Cross-cutting in the same sense as
/// [`Self::DeserializeGadgetInvoked`] /
/// [`Self::XxeEntityExpanded`] — evaluated across every drained
/// probe rather than against a single record.
WeakKeyEntropy {
/// Maximum effective-entropy budget (in bits) the produced
/// key may consume before the oracle fires. 16 matches the
/// brief's "fits in a 16-bit search space" oracle.
max_bits: u32,
},
/// Phase 11 (Track J.9): UNAUTHORIZED_ID IDOR boundary predicate.
///
/// Fires when at least one drained probe carries
/// [`ProbeKind::IdorAccess`] whose `caller_id` differs from
/// `owner_id`. Cross-cutting in the same sense as
/// [`Self::DeserializeGadgetInvoked`] /
/// [`Self::XxeEntityExpanded`] — evaluated across every drained
/// probe rather than against a single record.
IdorBoundaryCrossed,
/// Phase 11 (Track J.9): DATA_EXFIL outbound-host predicate.
///
/// Fires when at least one drained probe carries
/// [`ProbeKind::OutboundNetwork`] whose `host` falls outside the
/// `allowlist`. Hosts are compared case-insensitively; the
/// canonical allowlist for benign controls is `&["127.0.0.1",
/// "localhost"]` so a vulnerable host that exfiltrates to
/// `attacker.test` resolves off-list and confirms.
///
/// Cross-cutting in the same sense as
/// [`Self::DeserializeGadgetInvoked`] /
/// [`Self::XxeEntityExpanded`] — evaluated across every drained
/// probe rather than against a single record.
OutboundHostNotIn {
/// Allowlist of permitted egress hosts (e.g.
/// `&["127.0.0.1", "localhost"]`). A probe whose `host`
/// matches any entry is treated as same-origin.
allowlist: &'static [&'static str],
},
/// Phase 06 (Track J.4) / Phase 07 (Track J.5): result-count
/// predicate shared by LDAP-filter and XPath-expression injection.
///
@ -524,6 +572,35 @@ pub fn oracle_fired_with_stubs(
if !canary_ok {
return false;
}
// Phase 11 (Track J.9): CRYPTO weak-key, UNAUTHORIZED_ID
// IDOR, DATA_EXFIL outbound-host cross-cutting predicates.
let weak_key_ok = cross.iter().all(|p| match p {
ProbePredicate::WeakKeyEntropy { max_bits } => {
probes_satisfy_weak_key(probes, *max_bits)
}
_ => true,
});
if !weak_key_ok {
return false;
}
let idor_ok = cross.iter().all(|p| match p {
ProbePredicate::IdorBoundaryCrossed => {
probes_satisfy_idor_crossed(probes)
}
_ => true,
});
if !idor_ok {
return false;
}
let outbound_ok = cross.iter().all(|p| match p {
ProbePredicate::OutboundHostNotIn { allowlist } => {
probes_satisfy_outbound_off_list(probes, allowlist)
}
_ => true,
});
if !outbound_ok {
return false;
}
// Phase 04 (Track J.2): SSTI render-equality cross-cutting
// predicates. Each `TemplateEvalEqual { expected }` consults
// the captured stdout body — see [`stdout_template_equals`].
@ -558,7 +635,10 @@ pub fn oracle_fired_with_stubs(
| ProbeKind::Xpath { .. }
| ProbeKind::HeaderEmit { .. }
| ProbeKind::Redirect { .. }
| ProbeKind::PrototypePollution { .. } => false,
| ProbeKind::PrototypePollution { .. }
| ProbeKind::WeakKey { .. }
| ProbeKind::IdorAccess { .. }
| ProbeKind::OutboundNetwork { .. } => false,
}),
Oracle::OutputContains(needle) => {
let nb = needle.as_bytes();
@ -588,6 +668,9 @@ fn is_cross_cutting(pred: &ProbePredicate) -> bool {
| ProbePredicate::HeaderInjected { .. }
| ProbePredicate::RedirectHostNotIn { .. }
| ProbePredicate::PrototypeCanaryTouched { .. }
| ProbePredicate::WeakKeyEntropy { .. }
| ProbePredicate::IdorBoundaryCrossed
| ProbePredicate::OutboundHostNotIn { .. }
)
}
@ -624,6 +707,11 @@ fn cross_cutting_satisfied(pred: &ProbePredicate, stub_events: &[StubEvent]) ->
// log* rather than stub events; evaluated separately in
// [`probes_satisfy_prototype_canary`] below.
ProbePredicate::PrototypeCanaryTouched { .. } => true,
// Phase 11 (Track J.9) cross-cutters are all probe-log
// backed and evaluated by their dedicated helpers below.
ProbePredicate::WeakKeyEntropy { .. } => true,
ProbePredicate::IdorBoundaryCrossed => true,
ProbePredicate::OutboundHostNotIn { .. } => true,
_ => true,
}
}
@ -744,6 +832,60 @@ fn probes_satisfy_prototype_canary(probes: &[SinkProbe], canary: &str) -> bool {
})
}
/// True when at least one drained probe is a [`ProbeKind::WeakKey`]
/// record whose `key_int` is strictly less than `2^max_bits`. Powers
/// [`ProbePredicate::WeakKeyEntropy`] (Phase 11 — Track J.9).
///
/// `max_bits >= 64` is treated as "never fires" — a 64-bit key
/// trivially exceeds any sub-search-space budget once you cap the
/// integer view at `u64`. The brief calls for a 16-bit search-space
/// oracle, so the real threshold sits far below `2^64`.
fn probes_satisfy_weak_key(probes: &[SinkProbe], max_bits: u32) -> bool {
if max_bits == 0 {
return false;
}
if max_bits >= 64 {
return probes
.iter()
.any(|p| matches!(p.kind, ProbeKind::WeakKey { .. }));
}
let budget = 1u64 << max_bits;
probes.iter().any(|p| match &p.kind {
ProbeKind::WeakKey { key_int } => *key_int < budget,
_ => false,
})
}
/// True when at least one drained probe is a
/// [`ProbeKind::IdorAccess`] record whose `caller_id` differs from
/// `owner_id`. Powers
/// [`ProbePredicate::IdorBoundaryCrossed`] (Phase 11 — Track J.9).
fn probes_satisfy_idor_crossed(probes: &[SinkProbe]) -> bool {
probes.iter().any(|p| match &p.kind {
ProbeKind::IdorAccess { caller_id, owner_id } => caller_id != owner_id,
_ => false,
})
}
/// True when at least one drained probe is a
/// [`ProbeKind::OutboundNetwork`] record whose `host` falls outside
/// `allowlist` (case-insensitive). Powers
/// [`ProbePredicate::OutboundHostNotIn`] (Phase 11 — Track J.9).
fn probes_satisfy_outbound_off_list(probes: &[SinkProbe], allowlist: &[&str]) -> bool {
probes.iter().any(|p| match &p.kind {
ProbeKind::OutboundNetwork { host } => {
let h = host.trim().to_ascii_lowercase();
if h.is_empty() {
return false;
}
!allowlist
.iter()
.any(|a| h == a.trim().to_ascii_lowercase())
}
_ => false,
})
}
/// Returns `true` when `location` redirects to a host that is neither
/// `request_host` nor any entry of `allowlist`. Crate-visible so the
/// in-crate predicate above and the colocated tests can share one
@ -851,7 +993,10 @@ fn probe_satisfies_one(probe: &SinkProbe, pred: &ProbePredicate) -> bool {
| ProbePredicate::QueryResultCountGreaterThan { .. }
| ProbePredicate::HeaderInjected { .. }
| ProbePredicate::RedirectHostNotIn { .. }
| ProbePredicate::PrototypeCanaryTouched { .. } => true,
| ProbePredicate::PrototypeCanaryTouched { .. }
| ProbePredicate::WeakKeyEntropy { .. }
| ProbePredicate::IdorBoundaryCrossed
| ProbePredicate::OutboundHostNotIn { .. } => true,
}
}
@ -880,7 +1025,10 @@ pub fn probe_crash_signal(probe: &SinkProbe) -> Option<Signal> {
| ProbeKind::Xpath { .. }
| ProbeKind::HeaderEmit { .. }
| ProbeKind::Redirect { .. }
| ProbeKind::PrototypePollution { .. } => None,
| ProbeKind::PrototypePollution { .. }
| ProbeKind::WeakKey { .. }
| ProbeKind::IdorAccess { .. }
| ProbeKind::OutboundNetwork { .. } => None,
}
}

View file

@ -260,6 +260,49 @@ pub enum ProbeKind {
/// that traversed the chain.
value: String,
},
/// Phase 11 (Track J.9) weak-key entropy observation. Stamped by
/// the per-language CRYPTO harness shim when the instrumented
/// key-generation path produces a key whose effective entropy
/// fits inside the search space the oracle pins. `key_int` is
/// the integer-decoded view of the produced key bytes (truncated
/// to a `u64`); the
/// [`crate::dynamic::oracle::ProbePredicate::WeakKeyEntropy`]
/// predicate fires when `key_int < 2^max_bits`.
WeakKey {
/// Truncated integer view of the produced key bytes. Big
/// keys (e.g. an honest 2048-bit RSA modulus) hash down via
/// `from_be_bytes` so a benign control with a strong key
/// trivially exceeds any plausible `max_bits` budget.
key_int: u64,
},
/// Phase 11 (Track J.9) IDOR / authorization-bypass observation.
/// Stamped by the per-language UNAUTHORIZED_ID harness shim when
/// the instrumented mock data store materialises a record whose
/// `owner_id` differs from the harness's `caller_id`. The
/// [`crate::dynamic::oracle::ProbePredicate::IdorBoundaryCrossed`]
/// predicate fires whenever `caller_id != owner_id`.
IdorAccess {
/// Authenticated principal the harness modelled the request
/// as arriving from. Compared case-sensitively against
/// `owner_id`.
caller_id: String,
/// Owner of the record the host produced for the caller.
owner_id: String,
},
/// Phase 11 (Track J.9) DATA_EXFIL outbound-network observation.
/// Stamped by the per-language harness shim's mock HTTP client
/// when the instrumented egress entry point (`http.post`,
/// `requests.post`, `HttpURLConnection`, `Net::HTTP`, `fetch`,
/// `http.NewRequest`, `reqwest::Client`) attempts to route the
/// captured request body to a non-loopback host. The
/// [`crate::dynamic::oracle::ProbePredicate::OutboundHostNotIn`]
/// predicate fires when the captured host falls outside the
/// configured allowlist (typically `127.0.0.1` / `localhost`).
OutboundNetwork {
/// Host the harness's mock HTTP client recorded. Compared
/// case-insensitively against the allowlist entries.
host: String,
},
}
impl Default for ProbeKind {

View file

@ -97,6 +97,15 @@ pub struct Attempt {
#[derive(Debug)]
pub enum RunError {
NoPayloadsForCap,
/// Phase 11 (Track J.9): the requested cap is in the structural
/// "no sound oracle" set
/// ([`crate::dynamic::corpus::registry::CORPUS_SOUND_ORACLE_UNAVAILABLE`]).
/// Surfaces as
/// [`crate::evidence::UnsupportedReason::SoundOracleUnavailable`]
/// at the verify boundary so unsupported-budget accounting
/// distinguishes "no oracle exists" from "no payloads carved
/// yet".
SoundOracleUnavailable { cap: crate::labels::Cap, lang: Lang, hint: String },
Harness(HarnessError),
Sandbox(SandboxError),
BuildFailed { stderr: String, attempts: u32 },
@ -131,6 +140,22 @@ pub fn run_spec(spec: &HarnessSpec, opts: &SandboxOptions) -> Result<RunOutcome,
payloads_for(spec.expected_cap)
};
if payloads.is_empty() {
// Phase 11 (Track J.9): route caps with no sound oracle to a
// distinct error so the unsupported budget reflects
// structural impossibility rather than a missing payload.
if (spec.expected_cap.bits()
& crate::dynamic::corpus::registry::CORPUS_SOUND_ORACLE_UNAVAILABLE)
!= 0
{
return Err(RunError::SoundOracleUnavailable {
cap: spec.expected_cap,
lang: spec.lang,
hint: crate::dynamic::corpus::registry::sound_oracle_unavailable_hint(
spec.expected_cap,
)
.to_owned(),
});
}
return Err(RunError::NoPayloadsForCap);
}

View file

@ -60,7 +60,7 @@ pub const NYX_VERSION: &str = env!("CARGO_PKG_VERSION");
/// [`crate::dynamic::corpus::CORPUS_VERSION`]; the compile-time assertion
/// below + the [`corpus_version_const_matches_corpus_module`] runtime test
/// jointly guard drift.
pub const CORPUS_VERSION: &str = "14";
pub const CORPUS_VERSION: &str = "15";
/// Compile-time guard that pins [`CORPUS_VERSION`] (this module) to the
/// textual form of [`crate::dynamic::corpus::CORPUS_VERSION`]. Bumping the

View file

@ -1182,6 +1182,20 @@ fn build_verdict(
wrong: None,
hardening_outcome: None,
},
Err(RunError::SoundOracleUnavailable { cap, lang, hint }) => VerifyResult {
finding_id: finding_id.to_owned(),
status: VerifyStatus::Unsupported,
triggered_payload: None,
reason: Some(UnsupportedReason::SoundOracleUnavailable { cap, lang, hint }),
inconclusive_reason: None,
detail: None,
attempts: vec![],
toolchain_match: None,
differential: None,
replay_stable: None,
wrong: None,
hardening_outcome: None,
},
Err(RunError::Harness(e)) => {
// Defence-in-depth residual for `EntryKindUnsupported` from the
// lang dispatcher. Promote to `Inconclusive(EntryKindUnsupported)`