cargo fmt

This commit is contained in:
elipeter 2026-05-21 14:35:42 -05:00
parent bec7bbf96c
commit 3a35cd6c8f
294 changed files with 6809 additions and 3911 deletions

View file

@ -15,7 +15,7 @@ mod common;
#[cfg(feature = "dynamic")]
mod c_fixture_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -64,7 +64,16 @@ mod c_fixture_tests {
slot: PayloadSlot,
) -> Option<VerifyResult> {
run_shape_fixture_lang_or_skip(
CC_REQ, Lang::C, "c", shape, file, func, cap, sink_line, kind, slot,
CC_REQ,
Lang::C,
"c",
shape,
file,
func,
cap,
sink_line,
kind,
slot,
)
}
@ -73,18 +82,32 @@ mod c_fixture_tests {
#[test]
fn main_argv_vuln_is_confirmed() {
let Some(r) = run(
"main_argv", "vuln.c", "nyx_entry_main", Cap::CODE_EXEC, 23,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
) else { return; };
"main_argv",
"vuln.c",
"nyx_entry_main",
Cap::CODE_EXEC,
23,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
assert_confirmed("main_argv", &r);
}
#[test]
fn main_argv_benign_not_confirmed() {
let Some(r) = run(
"main_argv", "benign.c", "nyx_entry_main", Cap::CODE_EXEC, 11,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
) else { return; };
"main_argv",
"benign.c",
"nyx_entry_main",
Cap::CODE_EXEC,
11,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
assert_not_confirmed("main_argv", &r);
}
@ -93,18 +116,32 @@ mod c_fixture_tests {
#[test]
fn libfuzzer_vuln_is_confirmed() {
let Some(r) = run(
"libfuzzer", "vuln.c", "LLVMFuzzerTestOneInput", Cap::CODE_EXEC, 16,
EntryKind::LibraryApi, PayloadSlot::Param(0),
) else { return; };
"libfuzzer",
"vuln.c",
"LLVMFuzzerTestOneInput",
Cap::CODE_EXEC,
16,
EntryKind::LibraryApi,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("libfuzzer", &r);
}
#[test]
fn libfuzzer_benign_not_confirmed() {
let Some(r) = run(
"libfuzzer", "benign.c", "LLVMFuzzerTestOneInput", Cap::CODE_EXEC, 10,
EntryKind::LibraryApi, PayloadSlot::Param(0),
) else { return; };
"libfuzzer",
"benign.c",
"LLVMFuzzerTestOneInput",
Cap::CODE_EXEC,
10,
EntryKind::LibraryApi,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("libfuzzer", &r);
}
@ -113,18 +150,32 @@ mod c_fixture_tests {
#[test]
fn free_fn_vuln_is_confirmed() {
let Some(r) = run(
"free_fn", "vuln.c", "run", Cap::CODE_EXEC, 15,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"free_fn",
"vuln.c",
"run",
Cap::CODE_EXEC,
15,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("free_fn", &r);
}
#[test]
fn free_fn_benign_not_confirmed() {
let Some(r) = run(
"free_fn", "benign.c", "run", Cap::CODE_EXEC, 10,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"free_fn",
"benign.c",
"run",
Cap::CODE_EXEC,
10,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("free_fn", &r);
}
}

View file

@ -82,10 +82,7 @@ fn single_edge(diag: Diag, surface: &SurfaceMap) -> ChainEdge {
#[test]
fn rule_cmdi_alone_maps_to_rce() {
let surface = synthetic_surface("app.py", "/run");
let edge = single_edge(
diag_with_caps("app.py", 12, Cap::CODE_EXEC),
&surface,
);
let edge = single_edge(diag_with_caps("app.py", 12, Cap::CODE_EXEC), &surface);
assert_eq!(edge.primary_cap, Cap::CODE_EXEC);
assert!(matches!(edge.reach, Reach::Reachable { .. }));
assert_eq!(
@ -97,10 +94,7 @@ fn rule_cmdi_alone_maps_to_rce() {
#[test]
fn rule_deserialize_alone_maps_to_rce() {
let surface = synthetic_surface("app.py", "/load");
let edge = single_edge(
diag_with_caps("app.py", 7, Cap::DESERIALIZE),
&surface,
);
let edge = single_edge(diag_with_caps("app.py", 7, Cap::DESERIALIZE), &surface);
assert_eq!(edge.primary_cap, Cap::DESERIALIZE);
assert_eq!(
lookup_impact(edge.primary_cap, None),
@ -111,10 +105,7 @@ fn rule_deserialize_alone_maps_to_rce() {
#[test]
fn rule_ssrf_alone_maps_to_internal_network_access() {
let surface = synthetic_surface("fetch.py", "/proxy");
let edge = single_edge(
diag_with_caps("fetch.py", 4, Cap::SSRF),
&surface,
);
let edge = single_edge(diag_with_caps("fetch.py", 4, Cap::SSRF), &surface);
assert_eq!(edge.primary_cap, Cap::SSRF);
assert_eq!(
lookup_impact(edge.primary_cap, None),
@ -186,9 +177,6 @@ fn finding_in_file_with_no_entry_point_is_unreachable() {
#[test]
fn feasibility_defaults_to_unverified() {
let surface = synthetic_surface("app.py", "/");
let edge = single_edge(
diag_with_caps("app.py", 1, Cap::CODE_EXEC),
&surface,
);
let edge = single_edge(diag_with_caps("app.py", 1, Cap::CODE_EXEC), &surface);
assert_eq!(edge.feasibility, Feasibility::Unverified);
}

View file

@ -88,7 +88,12 @@ fn fixture_findings() -> Vec<Diag> {
d
};
vec![
mk(10, "cfg-cors-allow-all", Cap::HEADER_INJECTION, Severity::Medium),
mk(
10,
"cfg-cors-allow-all",
Cap::HEADER_INJECTION,
Severity::Medium,
),
mk(15, "cfg-auth-gap", Cap::UNAUTHORIZED_ID, Severity::Medium),
mk(25, "taint-shell-exec", Cap::CODE_EXEC, Severity::High),
]
@ -129,7 +134,11 @@ fn cors_plus_noauth_plus_websocket_emits_one_critical_chain() {
min_score: 0.0,
},
);
assert_eq!(chains.len(), 1, "expected exactly one chain, got {chains:?}");
assert_eq!(
chains.len(),
1,
"expected exactly one chain, got {chains:?}"
);
let chain = &chains[0];
assert_eq!(chain.implied_impact, ImpactCategory::BrowserToLocalRce);
assert_eq!(chain.severity, ChainSeverity::Critical);
@ -213,11 +222,7 @@ fn sarif_output_validates_against_v210_shape() {
min_score: 0.0,
},
);
let sarif = build_sarif_with_chains(
&findings,
&chains,
std::path::Path::new("."),
);
let sarif = build_sarif_with_chains(&findings, &chains, std::path::Path::new("."));
// Surface-level v2.1.0 invariants — the SARIF schema requires
// these fields and we want a tripwire if any disappear.

View file

@ -311,8 +311,8 @@ fn flask_eval_chain_dynamic_verdict_is_null_when_verify_disabled() {
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone())
.expect("nyx scan stdout is valid UTF-8");
let value: Value = serde_json::from_str(&stdout)
.expect("nyx scan --format json produced invalid JSON");
let value: Value =
serde_json::from_str(&stdout).expect("nyx scan --format json produced invalid JSON");
let chains = value
.get("chains")

View file

@ -193,8 +193,14 @@ fn compose_chain_step_threads_prev_output_for_every_emitter() {
"{lang:?} emitter must thread NYX_PREV_OUTPUT via extra_env; got {:?}",
step.extra_env
);
assert!(!step.source.is_empty(), "{lang:?} step source must be non-empty");
assert!(!step.command.is_empty(), "{lang:?} step command must be non-empty");
assert!(
!step.source.is_empty(),
"{lang:?} step source must be non-empty"
);
assert!(
!step.command.is_empty(),
"{lang:?} step command must be non-empty"
);
assert!(
!step.source.contains(ChainStepHarness::SINK_HIT_SENTINEL),
"{lang:?} non-terminal step must NOT carry the sink-hit sentinel; got source:\n{}",

View file

@ -16,7 +16,7 @@
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
use nyx_scanner::dynamic::stubs::{mock_source, MockKind};
use nyx_scanner::dynamic::stubs::{MockKind, mock_source};
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;

View file

@ -28,11 +28,9 @@ mod dynamic_sandbox_cli {
fn unsafe_sandbox_with_docker_backend_is_rejected() {
let mut cmd = scan_cmd_with_fresh_env();
cmd.args(["--unsafe-sandbox", "--backend", "docker"]);
cmd.assert()
.failure()
.stderr(predicate::str::contains(
"--unsafe-sandbox and --backend docker are mutually exclusive",
));
cmd.assert().failure().stderr(predicate::str::contains(
"--unsafe-sandbox and --backend docker are mutually exclusive",
));
}
/// `--unsafe-sandbox` alone (no explicit --backend) must NOT trigger the

View file

@ -14,10 +14,10 @@
//! failure, prompting an explicit golden update.
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, EntryKind, Evidence, FlowStep, FlowStepKind, InconclusiveReason,
UnsupportedReason, VerifyResult, VerifyStatus,
Confidence, EntryKind, Evidence, FlowStep, FlowStepKind, InconclusiveReason, UnsupportedReason,
VerifyResult, VerifyStatus,
};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
@ -187,10 +187,7 @@ pub fn check_prerequisites(reqs: &[Prerequisite]) -> Result<(), SkipReason> {
Err(_) => return Err(SkipReason::MissingStaticLib(lib)),
};
use std::io::Write;
let mut handle = match std::fs::OpenOptions::new()
.write(true)
.open(probe.path())
{
let mut handle = match std::fs::OpenOptions::new().write(true).open(probe.path()) {
Ok(h) => h,
Err(_) => return Err(SkipReason::MissingStaticLib(lib)),
};
@ -207,7 +204,9 @@ pub fn check_prerequisites(reqs: &[Prerequisite]) -> Result<(), SkipReason> {
};
let status = std::process::Command::new("cc")
.args([
"-x", "c", "-static",
"-x",
"c",
"-static",
probe.path().to_str().unwrap_or(""),
"-o",
out.to_str().unwrap_or(""),
@ -327,9 +326,8 @@ pub fn run_fixture_and_compare_to_golden(spec: &FixtureSpec<'_>) {
current_json.push('\n');
if std::env::var("NYX_UPDATE_GOLDENS").is_ok_and(|v| v == "1") {
std::fs::write(&golden_path, &current_json).unwrap_or_else(|e| {
panic!("write golden {}: {e}", golden_path.display())
});
std::fs::write(&golden_path, &current_json)
.unwrap_or_else(|e| panic!("write golden {}: {e}", golden_path.display()));
return;
}
@ -365,7 +363,9 @@ fn fixture_dir(lang_dir: &str) -> PathBuf {
fn stage_fixture(src: &Path, tmp: &TempDir, copy: CopyStrategy) -> PathBuf {
match copy {
CopyStrategy::PreserveName => {
let dst = tmp.path().join(src.file_name().expect("fixture has filename"));
let dst = tmp
.path()
.join(src.file_name().expect("fixture has filename"));
std::fs::copy(src, &dst).expect("copy fixture into tempdir");
dst
}
@ -435,7 +435,7 @@ pub fn run_shape_fixture_lang(
entry_kind: EntryKind,
payload_slot: nyx_scanner::dynamic::spec::PayloadSlot,
) -> VerifyResult {
use nyx_scanner::dynamic::runner::{run_spec, RunError};
use nyx_scanner::dynamic::runner::{RunError, run_spec};
use nyx_scanner::dynamic::sandbox::SandboxOptions;
use nyx_scanner::dynamic::spec::{HarnessSpec, SpecDerivationStrategy};
@ -801,9 +801,8 @@ pub fn run_harness_snapshot_lang(
.replace(file, "<ENTRY_FILE>");
if std::env::var("NYX_UPDATE_GOLDENS").is_ok_and(|v| v == "1") {
std::fs::write(&snapshot_path, &normalised).unwrap_or_else(|e| {
panic!("write harness snapshot {}: {e}", snapshot_path.display())
});
std::fs::write(&snapshot_path, &normalised)
.unwrap_or_else(|e| panic!("write harness snapshot {}: {e}", snapshot_path.display()));
return;
}

View file

@ -15,7 +15,7 @@ mod common;
#[cfg(feature = "dynamic")]
mod cpp_fixture_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -64,7 +64,16 @@ mod cpp_fixture_tests {
slot: PayloadSlot,
) -> Option<VerifyResult> {
run_shape_fixture_lang_or_skip(
CXX_REQ, Lang::Cpp, "cpp", shape, file, func, cap, sink_line, kind, slot,
CXX_REQ,
Lang::Cpp,
"cpp",
shape,
file,
func,
cap,
sink_line,
kind,
slot,
)
}
@ -73,18 +82,32 @@ mod cpp_fixture_tests {
#[test]
fn main_argv_vuln_is_confirmed() {
let Some(r) = run(
"main_argv", "vuln.cpp", "nyx_entry_main", Cap::CODE_EXEC, 16,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
) else { return; };
"main_argv",
"vuln.cpp",
"nyx_entry_main",
Cap::CODE_EXEC,
16,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
assert_confirmed("main_argv", &r);
}
#[test]
fn main_argv_benign_not_confirmed() {
let Some(r) = run(
"main_argv", "benign.cpp", "nyx_entry_main", Cap::CODE_EXEC, 11,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
) else { return; };
"main_argv",
"benign.cpp",
"nyx_entry_main",
Cap::CODE_EXEC,
11,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
assert_not_confirmed("main_argv", &r);
}
@ -93,18 +116,32 @@ mod cpp_fixture_tests {
#[test]
fn libfuzzer_vuln_is_confirmed() {
let Some(r) = run(
"libfuzzer", "vuln.cpp", "LLVMFuzzerTestOneInput", Cap::CODE_EXEC, 15,
EntryKind::LibraryApi, PayloadSlot::Param(0),
) else { return; };
"libfuzzer",
"vuln.cpp",
"LLVMFuzzerTestOneInput",
Cap::CODE_EXEC,
15,
EntryKind::LibraryApi,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("libfuzzer", &r);
}
#[test]
fn libfuzzer_benign_not_confirmed() {
let Some(r) = run(
"libfuzzer", "benign.cpp", "LLVMFuzzerTestOneInput", Cap::CODE_EXEC, 10,
EntryKind::LibraryApi, PayloadSlot::Param(0),
) else { return; };
"libfuzzer",
"benign.cpp",
"LLVMFuzzerTestOneInput",
Cap::CODE_EXEC,
10,
EntryKind::LibraryApi,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("libfuzzer", &r);
}
@ -113,18 +150,32 @@ mod cpp_fixture_tests {
#[test]
fn free_fn_vuln_is_confirmed() {
let Some(r) = run(
"free_fn", "vuln.cpp", "run", Cap::CODE_EXEC, 12,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"free_fn",
"vuln.cpp",
"run",
Cap::CODE_EXEC,
12,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("free_fn", &r);
}
#[test]
fn free_fn_benign_not_confirmed() {
let Some(r) = run(
"free_fn", "benign.cpp", "run", Cap::CODE_EXEC, 10,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"free_fn",
"benign.cpp",
"run",
Cap::CODE_EXEC,
10,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("free_fn", &r);
}
}

View file

@ -13,20 +13,14 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::corpus::{payloads_for_lang, resolve_benign_control_lang};
use nyx_scanner::dynamic::oracle::{oracle_fired, Oracle, ProbePredicate};
use nyx_scanner::dynamic::oracle::{Oracle, ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;
use std::time::Duration;
const LANGS: &[Lang] = &[
Lang::Java,
Lang::Python,
Lang::Php,
Lang::Go,
Lang::Rust,
];
const LANGS: &[Lang] = &[Lang::Java, Lang::Python, Lang::Php, Lang::Go, Lang::Rust];
fn outcome() -> SandboxOutcome {
SandboxOutcome {
@ -72,19 +66,17 @@ fn corpus_registers_crypto_for_each_supported_lang() {
fn crypto_payloads_pair_benign_controls_per_lang() {
for lang in LANGS {
let slice = payloads_for_lang(Cap::CRYPTO, *lang);
let vuln = slice
.iter()
.find(|p| !p.is_benign)
.expect("vuln payload");
let resolved = resolve_benign_control_lang(vuln, Cap::CRYPTO, *lang)
.expect("benign control resolves");
let vuln = slice.iter().find(|p| !p.is_benign).expect("vuln payload");
let resolved =
resolve_benign_control_lang(vuln, Cap::CRYPTO, *lang).expect("benign control resolves");
assert!(resolved.is_benign);
match &vuln.oracle {
Oracle::SinkProbe { predicates } => {
assert!(predicates.iter().any(|p| matches!(
p,
ProbePredicate::WeakKeyEntropy { max_bits: 16 }
)));
assert!(
predicates
.iter()
.any(|p| matches!(p, ProbePredicate::WeakKeyEntropy { max_bits: 16 }))
);
}
other => panic!("expected SinkProbe, got {other:?}"),
}
@ -119,7 +111,13 @@ fn weak_key_entropy_clears_with_no_probe() {
#[test]
fn crypto_unsupported_for_other_langs() {
for lang in [Lang::C, Lang::Cpp, Lang::Ruby, Lang::JavaScript, Lang::TypeScript] {
for lang in [
Lang::C,
Lang::Cpp,
Lang::Ruby,
Lang::JavaScript,
Lang::TypeScript,
] {
assert!(
payloads_for_lang(Cap::CRYPTO, lang).is_empty(),
"CRYPTO has unexpected payloads for {lang:?}",

View file

@ -14,7 +14,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::corpus::{payloads_for_lang, resolve_benign_control_lang};
use nyx_scanner::dynamic::oracle::{oracle_fired, Oracle, ProbePredicate};
use nyx_scanner::dynamic::oracle::{Oracle, ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::labels::Cap;
@ -76,10 +76,11 @@ fn data_exfil_payloads_pair_benign_per_lang() {
.expect("benign control resolves");
assert!(resolved.is_benign);
match &vuln.oracle {
Oracle::SinkProbe { predicates } => assert!(predicates.iter().any(|p| matches!(
p,
ProbePredicate::OutboundHostNotIn { .. }
))),
Oracle::SinkProbe { predicates } => assert!(
predicates
.iter()
.any(|p| matches!(p, ProbePredicate::OutboundHostNotIn { .. }))
),
other => panic!("expected SinkProbe, got {other:?}"),
}
}

View file

@ -13,8 +13,8 @@
mod common;
use nyx_scanner::dynamic::corpus::{
audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang, Oracle,
Oracle, audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang,
};
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::lang;
@ -105,7 +105,9 @@ fn payload_oracle_carries_deserialize_predicate() {
assert!(
predicates.iter().any(|p| matches!(
p,
ProbePredicate::DeserializeGadgetInvoked { require_invoked: true }
ProbePredicate::DeserializeGadgetInvoked {
require_invoked: true
}
)),
"{lang:?} vuln payload missing DeserializeGadgetInvoked predicate",
);
@ -166,8 +168,8 @@ fn lang_emitter_dispatches_to_deserialize_harness() {
),
] {
let spec = make_spec(lang, entry_file, entry_name);
let harness = lang::emit(&spec)
.unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
let harness =
lang::emit(&spec).unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
assert!(
harness.source.contains("NYX_GADGET_CLASS:"),
"{lang:?} deserialize harness must parse NYX_GADGET_CLASS marker",
@ -187,10 +189,19 @@ fn framework_adapters_detect_deserialize_sink() {
// EntryKind::Function binding when the fixture contains the
// canonical sink call.
for (lang, fixture) in [
(Lang::Java, "tests/dynamic_fixtures/deserialize/java/Vuln.java"),
(Lang::Python, "tests/dynamic_fixtures/deserialize/python/vuln.py"),
(
Lang::Java,
"tests/dynamic_fixtures/deserialize/java/Vuln.java",
),
(
Lang::Python,
"tests/dynamic_fixtures/deserialize/python/vuln.py",
),
(Lang::Php, "tests/dynamic_fixtures/deserialize/php/vuln.php"),
(Lang::Ruby, "tests/dynamic_fixtures/deserialize/ruby/vuln.rb"),
(
Lang::Ruby,
"tests/dynamic_fixtures/deserialize/ruby/vuln.rb",
),
] {
let bytes = std::fs::read(fixture).expect("fixture exists");
let ts_lang = ts_language_for(lang);
@ -204,19 +215,15 @@ fn framework_adapters_detect_deserialize_sink() {
..Default::default()
};
let registry_slice = adapters_for(lang);
assert!(
!registry_slice.is_empty(),
"{lang:?} adapter slice empty",
);
assert!(!registry_slice.is_empty(), "{lang:?} adapter slice empty",);
let binding = nyx_scanner::dynamic::framework::detect_binding(
&summary,
tree.root_node(),
&bytes,
lang,
);
let b = binding.unwrap_or_else(|| {
panic!("{lang:?} adapter must detect the deserialize sink fixture")
});
let b = binding
.unwrap_or_else(|| panic!("{lang:?} adapter must detect the deserialize sink fixture"));
assert_eq!(b.kind, EntryKind::Function);
assert!(!b.adapter.is_empty());
}
@ -262,10 +269,10 @@ fn slug(lang: Lang) -> &'static str {
mod e2e_phase_03 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::SandboxOptions;
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -383,7 +390,9 @@ mod e2e_phase_03 {
/// an allow-listed class name and writes no probe).
#[test]
fn java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else { return };
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Java DESERIALIZE vuln must Confirm via run_spec; got {outcome:?}",
@ -401,7 +410,9 @@ mod e2e_phase_03 {
#[test]
fn python_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else { return };
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Python DESERIALIZE vuln must Confirm via run_spec; got {outcome:?}",
@ -415,7 +426,9 @@ mod e2e_phase_03 {
#[test]
fn php_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else { return };
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"PHP DESERIALIZE vuln must Confirm via run_spec; got {outcome:?}",
@ -429,7 +442,9 @@ mod e2e_phase_03 {
#[test]
fn ruby_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else { return };
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Ruby DESERIALIZE vuln must Confirm via run_spec; got {outcome:?}",

View file

@ -15,7 +15,7 @@
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::telemetry::{self, SamplingPolicy};
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{Confidence, Evidence, VerifyStatus};
use nyx_scanner::patterns::{FindingCategory, Severity};
use serde_json::Value;
@ -99,10 +99,7 @@ fn ten_runs_produce_byte_identical_telemetry_minus_timestamps() {
// Drop `differential` and any future timestamped field by
// round-tripping through serde; structural equality is the
// contract.
verdict_jsons.insert(
serde_json::to_string(&result)
.expect("VerifyResult serialises"),
);
verdict_jsons.insert(serde_json::to_string(&result).expect("VerifyResult serialises"));
}
assert_eq!(
verdict_jsons.len(),
@ -243,10 +240,7 @@ fn confirmed_run_is_byte_identical_across_runs() {
// every run reads + writes the same absolute paths (the per-run path
// would otherwise leak into VerifyResult and break determinism).
unsafe {
std::env::set_var(
"NYX_REPRO_BASE",
tmp.path().join("repro").to_str().unwrap(),
);
std::env::set_var("NYX_REPRO_BASE", tmp.path().join("repro").to_str().unwrap());
std::env::set_var(
"NYX_TELEMETRY_PATH",
tmp.path().join("events.jsonl").to_str().unwrap(),
@ -370,10 +364,7 @@ fn policy_deny_excerpt_is_stable_across_runs() {
.inconclusive_reason
.expect("expected PolicyDeniedDynamic on deny path")
{
nyx_scanner::evidence::InconclusiveReason::PolicyDeniedDynamic {
excerpt,
..
} => {
nyx_scanner::evidence::InconclusiveReason::PolicyDeniedDynamic { excerpt, .. } => {
excerpts.insert(excerpt);
}
other => panic!("expected PolicyDeniedDynamic, got {other:?}"),

View file

@ -16,8 +16,8 @@
#[cfg(feature = "dynamic")]
mod parity_tests {
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
@ -118,8 +118,11 @@ mod parity_tests {
}
/// Assert two verdicts agree on status (and on reason for non-Confirmed).
fn assert_parity(fixture: &str, process_result: &nyx_scanner::evidence::VerifyResult,
docker_result: &nyx_scanner::evidence::VerifyResult) {
fn assert_parity(
fixture: &str,
process_result: &nyx_scanner::evidence::VerifyResult,
docker_result: &nyx_scanner::evidence::VerifyResult,
) {
// Docker reachability fluctuates per host: `docker info` may exit 0
// (daemon listening) while the sandbox's container-start path still
// fails (image not pulled, socket gated by Docker Desktop's
@ -128,16 +131,20 @@ mod parity_tests {
// where the error surfaces, so the skip predicate looks at the
// reason text, not the verdict status.
if let Some(ref r) = docker_result.reason
&& format!("{r:?}").contains("BackendUnavailable") {
return; // Docker absent — skip comparison.
}
&& format!("{r:?}").contains("BackendUnavailable")
{
return; // Docker absent — skip comparison.
}
assert_eq!(
process_result.status, docker_result.status,
process_result.status,
docker_result.status,
"fixture {fixture}: status mismatch: process={:?} docker={:?}\n\
process detail: {:?}\ndocker detail: {:?}",
process_result.status, docker_result.status,
process_result.detail, docker_result.detail,
process_result.status,
docker_result.status,
process_result.detail,
docker_result.detail,
);
// For non-Confirmed statuses, the reason must also match.
@ -154,7 +161,9 @@ mod parity_tests {
/// Helper: run a fixture through both backends and assert parity.
fn parity_check(fixture: &str, function: &str, sink_line: u32, cap: Cap) {
if !docker_available() { return; }
if !docker_available() {
return;
}
let diag = python_diag(fixture, function, sink_line, cap);
let process_result = verify_finding(&diag, &process_opts());
@ -266,7 +275,9 @@ mod parity_tests {
/// Rust finding (lang unsupported) must return same status on both backends.
#[test]
fn parity_rust_lang_unsupported() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let diag = python_diag("src/handler.rs", "handle_request", 10, Cap::SQL_QUERY);
let process_result = verify_finding(&diag, &process_opts());

View file

@ -115,7 +115,8 @@ mod escape_tests {
let stdout = std::str::from_utf8(&outcome.stdout).unwrap_or("");
let stderr = std::str::from_utf8(&outcome.stderr).unwrap_or("");
assert!(
!stdout.contains("NYX_ESCAPE_SUCCESS") && !stderr.contains("NYX_ESCAPE_SUCCESS"),
!stdout.contains("NYX_ESCAPE_SUCCESS")
&& !stderr.contains("NYX_ESCAPE_SUCCESS"),
"fixture {fixture}: escape succeeded!\nstdout: {stdout}\nstderr: {stderr}"
);
@ -143,7 +144,9 @@ mod escape_tests {
($name:ident, $fixture:literal) => {
#[test]
fn $name() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let (_tmpdir, harness) = harness_for_fixture($fixture);
let result = sandbox::run(&harness, &noop_payload(), &escape_opts());
assert_no_escape(result, $fixture, None);
@ -157,7 +160,9 @@ mod escape_tests {
#[cfg(target_os = "linux")]
#[test]
fn $name() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let (_tmpdir, harness) = harness_for_fixture($fixture);
let result = sandbox::run(&harness, &noop_payload(), &escape_opts());
assert_no_escape(result, $fixture, None);
@ -166,7 +171,9 @@ mod escape_tests {
($name:ident, $fixture:literal, marker = $marker:expr) => {
#[test]
fn $name() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let marker: PathBuf = PathBuf::from($marker);
// Remove stale marker before test.
let _ = fs::remove_file(&marker);
@ -181,7 +188,9 @@ mod escape_tests {
#[cfg(target_os = "linux")]
#[test]
fn $name() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let marker: PathBuf = PathBuf::from($marker);
let _ = fs::remove_file(&marker);
let (_tmpdir, harness) = harness_for_fixture($fixture);
@ -236,20 +245,20 @@ mod escape_tests {
/// Skips gracefully when Docker is unavailable or `rust:slim` is not pulled.
#[test]
fn escape_rust_malicious_build_rs() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let tmpdir = tempfile::TempDir::new().expect("temp dir");
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/dynamic_fixtures/escape/rust_build_rs");
copy_dir_recursive(&fixture, tmpdir.path())
.expect("copy rust_build_rs fixture");
copy_dir_recursive(&fixture, tmpdir.path()).expect("copy rust_build_rs fixture");
let marker: PathBuf = PathBuf::from("/tmp/pwned_build_rs");
let _ = fs::remove_file(&marker);
// Run Docker-isolated cargo build. Returns Err if Docker/image unavailable.
let result =
nyx_scanner::dynamic::build_sandbox::prepare_rust_in_docker(tmpdir.path());
let result = nyx_scanner::dynamic::build_sandbox::prepare_rust_in_docker(tmpdir.path());
if result.is_err() {
// Docker or rust:slim unavailable — no container ran.
return;
@ -274,19 +283,19 @@ mod escape_tests {
/// Skips gracefully when Docker is unavailable or `node:20-slim` is not pulled.
#[test]
fn escape_npm_malicious_lifecycle() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let tmpdir = tempfile::TempDir::new().expect("temp dir");
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/dynamic_fixtures/escape/npm_malicious_lifecycle");
copy_dir_recursive(&fixture, tmpdir.path())
.expect("copy npm_malicious_lifecycle fixture");
copy_dir_recursive(&fixture, tmpdir.path()).expect("copy npm_malicious_lifecycle fixture");
let marker: PathBuf = PathBuf::from("/tmp/pwned_npm_lifecycle");
let _ = fs::remove_file(&marker);
let result =
nyx_scanner::dynamic::build_sandbox::prepare_node_in_docker(tmpdir.path());
let result = nyx_scanner::dynamic::build_sandbox::prepare_node_in_docker(tmpdir.path());
if result.is_err() {
return;
}
@ -310,20 +319,20 @@ mod escape_tests {
/// Skips gracefully when Docker is unavailable or `golang:1.21-slim` is not pulled.
#[test]
fn escape_go_malicious_init() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let tmpdir = tempfile::TempDir::new().expect("temp dir");
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/dynamic_fixtures/escape/go_malicious_init_main");
copy_dir_recursive(&fixture, tmpdir.path())
.expect("copy go_malicious_init_main fixture");
copy_dir_recursive(&fixture, tmpdir.path()).expect("copy go_malicious_init_main fixture");
let marker: PathBuf = PathBuf::from("/tmp/pwned_go_init");
let _ = fs::remove_file(&marker);
// Docker-isolated go build: init() does not run during compilation.
let result =
nyx_scanner::dynamic::build_sandbox::prepare_go_in_docker(tmpdir.path());
let result = nyx_scanner::dynamic::build_sandbox::prepare_go_in_docker(tmpdir.path());
if result.is_err() {
return;
}
@ -346,19 +355,19 @@ mod escape_tests {
/// Skips gracefully when Docker is unavailable or the Maven image is not pulled.
#[test]
fn escape_maven_malicious_plugin() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let tmpdir = tempfile::TempDir::new().expect("temp dir");
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/dynamic_fixtures/escape/maven_malicious_plugin");
copy_dir_recursive(&fixture, tmpdir.path())
.expect("copy maven_malicious_plugin fixture");
copy_dir_recursive(&fixture, tmpdir.path()).expect("copy maven_malicious_plugin fixture");
let marker: PathBuf = PathBuf::from("/tmp/pwned_maven_plugin");
let _ = fs::remove_file(&marker);
let result =
nyx_scanner::dynamic::build_sandbox::prepare_java_in_docker(tmpdir.path());
let result = nyx_scanner::dynamic::build_sandbox::prepare_java_in_docker(tmpdir.path());
if result.is_err() {
return;
}
@ -380,7 +389,9 @@ mod escape_tests {
/// Skips gracefully when Docker is unavailable or `composer:2` is not pulled.
#[test]
fn escape_composer_malicious_postinstall() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let tmpdir = tempfile::TempDir::new().expect("temp dir");
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
@ -391,8 +402,7 @@ mod escape_tests {
let marker: PathBuf = PathBuf::from("/tmp/pwned_composer_postinstall");
let _ = fs::remove_file(&marker);
let result =
nyx_scanner::dynamic::build_sandbox::prepare_php_in_docker(tmpdir.path());
let result = nyx_scanner::dynamic::build_sandbox::prepare_php_in_docker(tmpdir.path());
if result.is_err() {
return;
}
@ -434,12 +444,17 @@ mod escape_tests {
let container_name = format!("nyx-posctl-{}", std::process::id());
let status = std::process::Command::new("docker")
.args([
"run", "-d", "--rm",
"--name", &container_name,
"run",
"-d",
"--rm",
"--name",
&container_name,
"--cap-add=SYS_ADMIN",
"--network", "none",
"--network",
"none",
"python:3-slim",
"sleep", "60",
"sleep",
"60",
])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
@ -470,8 +485,10 @@ mod escape_tests {
// Run the fixture and capture output.
let out = std::process::Command::new("docker")
.args([
"exec", &container_name,
"python3", "/workdir/cap_sys_admin_positive_control.py",
"exec",
&container_name,
"python3",
"/workdir/cap_sys_admin_positive_control.py",
])
.output()
.expect("docker exec positive control");
@ -503,7 +520,9 @@ mod escape_tests {
/// the container registry holds one entry (started once, reused once).
#[test]
fn docker_exec_reuse_for_same_workdir() {
if !docker_available() { return; }
if !docker_available() {
return;
}
let (_tmpdir, harness) = harness_for_fixture("dns_leak.py");
let opts = escape_opts();
@ -524,7 +543,9 @@ mod escape_tests {
// Verify the container is still running (not torn down between calls).
// Container name is derived from the workdir path.
let spec_hash = _tmpdir.path().file_name()
let spec_hash = _tmpdir
.path()
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
let container_name = format!("nyx-{spec_hash}");
@ -535,10 +556,7 @@ mod escape_tests {
match out {
Ok(o) if o.status.success() => {
let running = std::str::from_utf8(&o.stdout)
.unwrap_or("")
.trim()
== "true";
let running = std::str::from_utf8(&o.stdout).unwrap_or("").trim() == "true";
// Container should still be running (exec reuse kept it alive).
assert!(
running,

View file

@ -18,8 +18,10 @@
#[cfg(feature = "dynamic")]
mod verify_e2e {
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::evidence::{Confidence, Evidence, FlowStep, FlowStepKind, UnsupportedReason, VerifyStatus};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, UnsupportedReason, VerifyStatus,
};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
@ -243,9 +245,15 @@ mod verify_e2e {
let v: serde_json::Value = serde_json::from_str(&json).expect("must be valid JSON");
assert!(v.get("status").is_some(), "status field must be present");
assert!(v.get("triggered_payload").is_none(), "triggered_payload must be absent");
assert!(
v.get("triggered_payload").is_none(),
"triggered_payload must be absent"
);
assert!(v.get("detail").is_none(), "detail must be absent");
assert!(v.get("attempts").is_none(), "attempts must be absent (empty vec skipped)");
assert!(
v.get("attempts").is_none(),
"attempts must be absent (empty vec skipped)"
);
assert!(v["finding_id"].is_string());
}
}

View file

@ -23,8 +23,8 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::environment::{
capture_project_dependencies, capture_project_dependencies_with_context,
stage_workdir_full, MAX_WORKDIR_BYTES,
MAX_WORKDIR_BYTES, capture_project_dependencies, capture_project_dependencies_with_context,
stage_workdir_full,
};
use nyx_scanner::dynamic::lang::materialize_runtime;
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy};
@ -108,7 +108,11 @@ fn capture_returns_three_deps_plus_flask() {
assert!(!captured.toolchain.toolchain_drift);
// Manifests resolved: requirements.txt and pyproject.toml.
assert!(captured.lockfile.is_some(), "lockfile = {:?}", captured.lockfile);
assert!(
captured.lockfile.is_some(),
"lockfile = {:?}",
captured.lockfile
);
let manifest_names: Vec<String> = captured
.manifests
.iter()
@ -255,7 +259,7 @@ fn callgraph_context_extends_source_closure() {
// reverse-edge walk discovered (here just one file because the
// fixture is single-file).
use nyx_scanner::ast::analyse_file_fused;
use nyx_scanner::callgraph::{build_call_graph};
use nyx_scanner::callgraph::build_call_graph;
use nyx_scanner::summary::GlobalSummaries;
use nyx_scanner::utils::config::{AnalysisMode, Config};
@ -268,8 +272,8 @@ fn callgraph_context_extends_source_closure() {
let root = fixture_root();
let app = root.join("app.py");
let bytes = std::fs::read(&app).unwrap();
let result = analyse_file_fused(&bytes, &app, &cfg, None, Some(&root))
.expect("analyse fixture");
let result =
analyse_file_fused(&bytes, &app, &cfg, None, Some(&root)).expect("analyse fixture");
let root_str = root.to_string_lossy();
let mut gs = GlobalSummaries::new();
for s in result.summaries {

View file

@ -13,8 +13,8 @@
mod common;
use nyx_scanner::baseline::{
check_gate, compute_verdict_diff, diags_to_baseline_entries, load_baseline, write_baseline,
BaselineEntry, Transition, GATE_NO_NEW_CONFIRMED, GATE_RESOLVE_ALL_CONFIRMED,
BaselineEntry, GATE_NO_NEW_CONFIRMED, GATE_RESOLVE_ALL_CONFIRMED, Transition, check_gate,
compute_verdict_diff, diags_to_baseline_entries, load_baseline, write_baseline,
};
use nyx_scanner::commands::scan::compute_stable_hash;
use nyx_scanner::evidence::{Evidence, VerifyResult, VerifyStatus};
@ -32,10 +32,7 @@ fn scan_with_hashes(dir: &Path) -> Vec<nyx_scanner::commands::scan::Diag> {
}
/// Attach a simulated dynamic verdict to every finding in the list.
fn set_verdict(
diags: &mut [nyx_scanner::commands::scan::Diag],
status: VerifyStatus,
) {
fn set_verdict(diags: &mut [nyx_scanner::commands::scan::Diag], status: VerifyStatus) {
for d in diags.iter_mut() {
let fid = format!("{:016x}", d.stable_hash);
let ev = d.evidence.get_or_insert_with(Evidence::default);
@ -89,7 +86,10 @@ fn fix_resolves_confirmed_finding() {
// Step 1: scan vulnerable, simulate Confirmed verdict.
let mut vuln_diags = scan_with_hashes(vuln_path);
assert!(!vuln_diags.is_empty(), "Need at least one SQL injection finding");
assert!(
!vuln_diags.is_empty(),
"Need at least one SQL injection finding"
);
set_verdict(&mut vuln_diags, VerifyStatus::Confirmed);
// Step 2: write stripped baseline.
@ -260,7 +260,6 @@ fn load_baseline_accepts_full_diag_json() {
// Hashes must round-trip.
let loaded_hashes: std::collections::HashSet<u64> =
loaded.iter().map(|e| e.stable_hash).collect();
let diag_hashes: std::collections::HashSet<u64> =
diags.iter().map(|d| d.stable_hash).collect();
let diag_hashes: std::collections::HashSet<u64> = diags.iter().map(|d| d.stable_hash).collect();
assert_eq!(loaded_hashes, diag_hashes);
}

View file

@ -14,7 +14,7 @@ mod common;
#[cfg(feature = "dynamic")]
mod go_fixture_tests {
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, InconclusiveReason, UnsupportedReason,
VerifyStatus,
@ -456,7 +456,7 @@ mod go_fixture_tests {
#[cfg(feature = "dynamic")]
mod phase15_shape_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -506,7 +506,15 @@ mod phase15_shape_tests {
// return; };`.
run_shape_fixture_lang_or_skip(
&[Prerequisite::CommandAvailable("go")],
Lang::Go, "go", shape, file, func, cap, sink_line, kind, slot,
Lang::Go,
"go",
shape,
file,
func,
cap,
sink_line,
kind,
slot,
)
}
@ -515,8 +523,13 @@ mod phase15_shape_tests {
#[test]
fn handler_func_vuln_is_confirmed() {
let Some(r) = run(
"handler_func", "vuln.go", "Handle", Cap::CODE_EXEC, 17,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
"handler_func",
"vuln.go",
"Handle",
Cap::CODE_EXEC,
17,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("payload".into()),
) else {
return;
};
@ -526,8 +539,13 @@ mod phase15_shape_tests {
#[test]
fn handler_func_benign_not_confirmed() {
let Some(r) = run(
"handler_func", "benign.go", "Handle", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
"handler_func",
"benign.go",
"Handle",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("payload".into()),
) else {
return;
};
@ -539,8 +557,13 @@ mod phase15_shape_tests {
#[test]
fn gin_handler_vuln_is_confirmed() {
let Some(r) = run(
"gin_handler", "vuln.go", "Handle", Cap::CODE_EXEC, 16,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
"gin_handler",
"vuln.go",
"Handle",
Cap::CODE_EXEC,
16,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("payload".into()),
) else {
return;
};
@ -550,8 +573,13 @@ mod phase15_shape_tests {
#[test]
fn gin_handler_benign_not_confirmed() {
let Some(r) = run(
"gin_handler", "benign.go", "Handle", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
"gin_handler",
"benign.go",
"Handle",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("payload".into()),
) else {
return;
};
@ -563,8 +591,13 @@ mod phase15_shape_tests {
#[test]
fn flag_cli_vuln_is_confirmed() {
let Some(r) = run(
"flag_cli", "vuln.go", "Run", Cap::CODE_EXEC, 19,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"flag_cli",
"vuln.go",
"Run",
Cap::CODE_EXEC,
19,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
@ -574,8 +607,13 @@ mod phase15_shape_tests {
#[test]
fn flag_cli_benign_not_confirmed() {
let Some(r) = run(
"flag_cli", "benign.go", "Run", Cap::CODE_EXEC, 15,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"flag_cli",
"benign.go",
"Run",
Cap::CODE_EXEC,
15,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
@ -587,8 +625,13 @@ mod phase15_shape_tests {
#[test]
fn fuzz_variadic_vuln_is_confirmed() {
let Some(r) = run(
"fuzz_variadic", "vuln.go", "FuzzHandle", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
"fuzz_variadic",
"vuln.go",
"FuzzHandle",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
@ -598,8 +641,13 @@ mod phase15_shape_tests {
#[test]
fn fuzz_variadic_benign_not_confirmed() {
let Some(r) = run(
"fuzz_variadic", "benign.go", "FuzzHandle", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
"fuzz_variadic",
"benign.go",
"FuzzHandle",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};

View file

@ -11,7 +11,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::framework::{detect_binding, HttpMethod};
use nyx_scanner::dynamic::framework::{HttpMethod, detect_binding};
use nyx_scanner::evidence::EntryKind;
use nyx_scanner::summary::FuncSummary;
use nyx_scanner::symbol::Lang;

View file

@ -16,12 +16,12 @@
mod common;
use nyx_scanner::dynamic::corpus::{
audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang, Oracle,
Oracle, audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang,
};
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::oracle::{oracle_fired, ProbePredicate};
use nyx_scanner::dynamic::oracle::{ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
@ -311,8 +311,8 @@ fn lang_emitter_dispatches_to_header_injection_harness() {
),
] {
let spec = make_spec(lang, entry_file, entry_name);
let harness = lang::emit(&spec)
.unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
let harness =
lang::emit(&spec).unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
assert!(
harness.source.contains("HeaderEmit"),
"{lang:?} header harness must carry the HeaderEmit probe kind",
@ -396,8 +396,8 @@ fn framework_adapters_detect_header_sink() {
&bytes,
lang,
);
let b = binding
.unwrap_or_else(|| panic!("{lang:?} adapter must detect the header fixture"));
let b =
binding.unwrap_or_else(|| panic!("{lang:?} adapter must detect the header fixture"));
assert_eq!(b.kind, EntryKind::Function);
assert!(!b.adapter.is_empty());
}
@ -457,10 +457,10 @@ fn slug(lang: Lang) -> &'static str {
mod e2e_phase_08 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -588,43 +588,57 @@ mod e2e_phase_08 {
#[test]
fn java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else { return };
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
return;
};
assert_confirmed(Lang::Java, &outcome);
}
#[test]
fn python_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else { return };
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
return;
};
assert_confirmed(Lang::Python, &outcome);
}
#[test]
fn php_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else { return };
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else {
return;
};
assert_confirmed(Lang::Php, &outcome);
}
#[test]
fn ruby_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else { return };
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else {
return;
};
assert_confirmed(Lang::Ruby, &outcome);
}
#[test]
fn js_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else { return };
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else {
return;
};
assert_confirmed(Lang::JavaScript, &outcome);
}
#[test]
fn go_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Go, "vuln.go", "Run") else { return };
let Some(outcome) = run(Lang::Go, "vuln.go", "Run") else {
return;
};
assert_confirmed(Lang::Go, &outcome);
}
#[test]
fn rust_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Rust, "vuln.rs", "run") else { return };
let Some(outcome) = run(Lang::Rust, "vuln.rs", "run") else {
return;
};
assert_confirmed(Lang::Rust, &outcome);
}
}

View file

@ -22,7 +22,7 @@ mod common;
#[cfg(feature = "dynamic")]
mod java_fixture_tests {
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, InconclusiveReason, UnsupportedReason,
VerifyStatus,
@ -464,7 +464,7 @@ mod java_fixture_tests {
#[cfg(feature = "dynamic")]
mod phase14_shape_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -517,7 +517,15 @@ mod phase14_shape_tests {
Prerequisite::CommandAvailable("javac"),
Prerequisite::CommandAvailable("java"),
],
Lang::Java, "java", shape, file, func, cap, sink_line, kind, slot,
Lang::Java,
"java",
shape,
file,
func,
cap,
sink_line,
kind,
slot,
)
}
@ -526,8 +534,13 @@ mod phase14_shape_tests {
#[test]
fn static_method_vuln_is_confirmed() {
let Some(r) = run(
"static_method", "Vuln.java", "processInput", Cap::CODE_EXEC, 12,
EntryKind::Function, PayloadSlot::Param(0),
"static_method",
"Vuln.java",
"processInput",
Cap::CODE_EXEC,
12,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
@ -537,8 +550,13 @@ mod phase14_shape_tests {
#[test]
fn static_method_benign_not_confirmed() {
let Some(r) = run(
"static_method", "Benign.java", "processInput", Cap::CODE_EXEC, 13,
EntryKind::Function, PayloadSlot::Param(0),
"static_method",
"Benign.java",
"processInput",
Cap::CODE_EXEC,
13,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
@ -550,8 +568,13 @@ mod phase14_shape_tests {
#[test]
fn static_main_vuln_is_confirmed() {
let Some(r) = run(
"static_main", "Vuln.java", "main", Cap::CODE_EXEC, 13,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"static_main",
"Vuln.java",
"main",
Cap::CODE_EXEC,
13,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
@ -561,8 +584,13 @@ mod phase14_shape_tests {
#[test]
fn static_main_benign_not_confirmed() {
let Some(r) = run(
"static_main", "Benign.java", "main", Cap::CODE_EXEC, 12,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"static_main",
"Benign.java",
"main",
Cap::CODE_EXEC,
12,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
@ -574,8 +602,13 @@ mod phase14_shape_tests {
#[test]
fn servlet_doget_vuln_is_confirmed() {
let Some(r) = run(
"servlet_doget", "Vuln.java", "doGet", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
"servlet_doget",
"Vuln.java",
"doGet",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("payload".into()),
) else {
return;
};
@ -585,8 +618,13 @@ mod phase14_shape_tests {
#[test]
fn servlet_doget_benign_not_confirmed() {
let Some(r) = run(
"servlet_doget", "Benign.java", "doGet", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
"servlet_doget",
"Benign.java",
"doGet",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("payload".into()),
) else {
return;
};
@ -598,8 +636,13 @@ mod phase14_shape_tests {
#[test]
fn servlet_dopost_vuln_is_confirmed() {
let Some(r) = run(
"servlet_dopost", "Vuln.java", "doPost", Cap::CODE_EXEC, 13,
EntryKind::HttpRoute, PayloadSlot::HttpBody,
"servlet_dopost",
"Vuln.java",
"doPost",
Cap::CODE_EXEC,
13,
EntryKind::HttpRoute,
PayloadSlot::HttpBody,
) else {
return;
};
@ -609,8 +652,13 @@ mod phase14_shape_tests {
#[test]
fn servlet_dopost_benign_not_confirmed() {
let Some(r) = run(
"servlet_dopost", "Benign.java", "doPost", Cap::CODE_EXEC, 12,
EntryKind::HttpRoute, PayloadSlot::HttpBody,
"servlet_dopost",
"Benign.java",
"doPost",
Cap::CODE_EXEC,
12,
EntryKind::HttpRoute,
PayloadSlot::HttpBody,
) else {
return;
};
@ -622,8 +670,13 @@ mod phase14_shape_tests {
#[test]
fn spring_controller_vuln_is_confirmed() {
let Some(r) = run(
"spring_controller", "Vuln.java", "run", Cap::CODE_EXEC, 16,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"spring_controller",
"Vuln.java",
"run",
Cap::CODE_EXEC,
16,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -633,8 +686,13 @@ mod phase14_shape_tests {
#[test]
fn spring_controller_benign_not_confirmed() {
let Some(r) = run(
"spring_controller", "Benign.java", "run", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"spring_controller",
"Benign.java",
"run",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -646,8 +704,13 @@ mod phase14_shape_tests {
#[test]
fn junit_test_vuln_is_confirmed() {
let Some(r) = run(
"junit_test", "Vuln.java", "testRun", Cap::CODE_EXEC, 17,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"junit_test",
"Vuln.java",
"testRun",
Cap::CODE_EXEC,
17,
EntryKind::Function,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
) else {
return;
};
@ -657,8 +720,13 @@ mod phase14_shape_tests {
#[test]
fn junit_test_benign_not_confirmed() {
let Some(r) = run(
"junit_test", "Benign.java", "testRun", Cap::CODE_EXEC, 15,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"junit_test",
"Benign.java",
"testRun",
Cap::CODE_EXEC,
15,
EntryKind::Function,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
) else {
return;
};
@ -670,8 +738,13 @@ mod phase14_shape_tests {
#[test]
fn quarkus_route_vuln_is_confirmed() {
let Some(r) = run(
"quarkus_route", "Vuln.java", "run", Cap::CODE_EXEC, 17,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"quarkus_route",
"Vuln.java",
"run",
Cap::CODE_EXEC,
17,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -681,8 +754,13 @@ mod phase14_shape_tests {
#[test]
fn quarkus_route_benign_not_confirmed() {
let Some(r) = run(
"quarkus_route", "Benign.java", "run", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"quarkus_route",
"Benign.java",
"run",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};

View file

@ -16,7 +16,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::framework::{detect_binding, HttpMethod, ParamSource};
use nyx_scanner::dynamic::framework::{HttpMethod, ParamSource, detect_binding};
use nyx_scanner::evidence::EntryKind;
use nyx_scanner::summary::FuncSummary;
use nyx_scanner::symbol::Lang;
@ -143,10 +143,12 @@ fn servlet_doget_vuln_fixture_binds_route() {
// path defaults to `"/"`.
assert_eq!(route.path, "/");
// The (req, resp) pair should classify as Implicit.
assert!(binding
.request_params
.iter()
.all(|p| matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.all(|p| matches!(p.source, ParamSource::Implicit))
);
}
#[test]

View file

@ -18,7 +18,7 @@ mod common;
#[cfg(feature = "dynamic")]
mod javascript_fixture_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -89,9 +89,16 @@ mod javascript_fixture_tests {
fn commonjs_export_vuln_is_confirmed() {
let Some(r) = run(
NODE_REQ,
"commonjs_export", "vuln.js", "runPing", Cap::CODE_EXEC, 11,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"commonjs_export",
"vuln.js",
"runPing",
Cap::CODE_EXEC,
11,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("commonjs_export", &r);
}
@ -99,9 +106,16 @@ mod javascript_fixture_tests {
fn commonjs_export_benign_not_confirmed() {
let Some(r) = run(
NODE_REQ,
"commonjs_export", "benign.js", "runPing", Cap::CODE_EXEC, 11,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"commonjs_export",
"benign.js",
"runPing",
Cap::CODE_EXEC,
11,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("commonjs_export", &r);
}
@ -111,9 +125,16 @@ mod javascript_fixture_tests {
fn async_function_vuln_is_confirmed() {
let Some(r) = run(
NODE_REQ,
"async_function", "vuln.js", "runPing", Cap::CODE_EXEC, 15,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"async_function",
"vuln.js",
"runPing",
Cap::CODE_EXEC,
15,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("async_function", &r);
}
@ -121,9 +142,16 @@ mod javascript_fixture_tests {
fn async_function_benign_not_confirmed() {
let Some(r) = run(
NODE_REQ,
"async_function", "benign.js", "runPing", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"async_function",
"benign.js",
"runPing",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("async_function", &r);
}
@ -133,9 +161,16 @@ mod javascript_fixture_tests {
fn esm_default_vuln_is_confirmed() {
let Some(r) = run(
NODE_REQ,
"esm_default", "vuln.js", "runPing", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"esm_default",
"vuln.js",
"runPing",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("esm_default", &r);
}
@ -143,9 +178,16 @@ mod javascript_fixture_tests {
fn esm_default_benign_not_confirmed() {
let Some(r) = run(
NODE_REQ,
"esm_default", "benign.js", "runPing", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"esm_default",
"benign.js",
"runPing",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("esm_default", &r);
}
@ -158,9 +200,16 @@ mod javascript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("express"),
],
"express", "vuln.js", "ping", Cap::CODE_EXEC, 15,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"express",
"vuln.js",
"ping",
Cap::CODE_EXEC,
15,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_confirmed("express", &r);
}
@ -171,9 +220,16 @@ mod javascript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("express"),
],
"express", "benign.js", "ping", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"express",
"benign.js",
"ping",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_not_confirmed("express", &r);
}
@ -186,9 +242,16 @@ mod javascript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("koa"),
],
"koa", "vuln.js", "ping", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"koa",
"vuln.js",
"ping",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_confirmed("koa", &r);
}
@ -199,9 +262,16 @@ mod javascript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("koa"),
],
"koa", "benign.js", "ping", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"koa",
"benign.js",
"ping",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_not_confirmed("koa", &r);
}
@ -214,9 +284,16 @@ mod javascript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("next"),
],
"next_route", "vuln.js", "handler", Cap::CODE_EXEC, 17,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"next_route",
"vuln.js",
"handler",
Cap::CODE_EXEC,
17,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_confirmed("next_route", &r);
}
@ -227,9 +304,16 @@ mod javascript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("next"),
],
"next_route", "benign.js", "handler", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"next_route",
"benign.js",
"handler",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_not_confirmed("next_route", &r);
}
@ -242,9 +326,16 @@ mod javascript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("jsdom"),
],
"browser_event", "vuln.js", "clickHandler", Cap::HTML_ESCAPE, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"browser_event",
"vuln.js",
"clickHandler",
Cap::HTML_ESCAPE,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("browser_event", &r);
}
@ -255,9 +346,16 @@ mod javascript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("jsdom"),
],
"browser_event", "benign.js", "clickHandler", Cap::HTML_ESCAPE, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"browser_event",
"benign.js",
"clickHandler",
Cap::HTML_ESCAPE,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("browser_event", &r);
}
}

View file

@ -12,7 +12,7 @@
#[cfg(feature = "dynamic")]
mod js_fixture_tests {
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, InconclusiveReason, UnsupportedReason,
VerifyStatus,

View file

@ -11,7 +11,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::framework::{detect_binding, HttpMethod, ParamSource};
use nyx_scanner::dynamic::framework::{HttpMethod, ParamSource, detect_binding};
use nyx_scanner::evidence::EntryKind;
use nyx_scanner::summary::FuncSummary;
use nyx_scanner::symbol::Lang;
@ -45,10 +45,12 @@ fn express_vuln_fixture_binds_route() {
let route = binding.route.as_ref().expect("route");
assert_eq!(route.path, "/run");
assert_eq!(route.method, HttpMethod::GET);
assert!(binding
.request_params
.iter()
.any(|p| p.name == "req" && matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "req" && matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -77,10 +79,12 @@ fn koa_vuln_fixture_binds_router_route() {
let route = binding.route.as_ref().expect("route");
assert_eq!(route.path, "/run");
assert_eq!(route.method, HttpMethod::GET);
assert!(binding
.request_params
.iter()
.any(|p| p.name == "ctx" && matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "ctx" && matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -107,14 +111,18 @@ fn fastify_vuln_fixture_binds_route() {
let route = binding.route.as_ref().expect("route");
assert_eq!(route.path, "/run");
assert_eq!(route.method, HttpMethod::GET);
assert!(binding
.request_params
.iter()
.any(|p| p.name == "request" && matches!(p.source, ParamSource::Implicit)));
assert!(binding
.request_params
.iter()
.any(|p| p.name == "reply" && matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "request" && matches!(p.source, ParamSource::Implicit))
);
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "reply" && matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -176,7 +184,6 @@ fn express_adapter_runs_before_fastify_for_express_files() {
app.get('/x', h);\n";
let tree = parse_js(src);
let summary = summary_for("h", "synthetic.js");
let binding =
detect_binding(&summary, tree.root_node(), src, Lang::JavaScript).expect("fires");
let binding = detect_binding(&summary, tree.root_node(), src, Lang::JavaScript).expect("fires");
assert_eq!(binding.adapter, "js-express");
}

View file

@ -11,7 +11,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::corpus::{payloads_for_lang, resolve_benign_control_lang};
use nyx_scanner::dynamic::oracle::{oracle_fired, Oracle, ProbePredicate};
use nyx_scanner::dynamic::oracle::{Oracle, ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::labels::Cap;
@ -68,7 +68,9 @@ fn json_parse_pairs_benign_per_lang_via_canary_predicate() {
match &vuln.oracle {
Oracle::SinkProbe { predicates } => assert!(predicates.iter().any(|p| matches!(
p,
ProbePredicate::PrototypeCanaryTouched { canary: "__nyx_canary" }
ProbePredicate::PrototypeCanaryTouched {
canary: "__nyx_canary"
}
))),
other => panic!("expected SinkProbe, got {other:?}"),
}
@ -82,8 +84,16 @@ fn canary_predicate_fires_only_on_canary_property() {
canary: "__nyx_canary",
}],
};
assert!(oracle_fired(&oracle, &outcome(), &[canary_probe("__nyx_canary")]));
assert!(!oracle_fired(&oracle, &outcome(), &[canary_probe("__data__")]));
assert!(oracle_fired(
&oracle,
&outcome(),
&[canary_probe("__nyx_canary")]
));
assert!(!oracle_fired(
&oracle,
&outcome(),
&[canary_probe("__data__")]
));
assert!(!oracle_fired(&oracle, &outcome(), &[]));
}

View file

@ -6,9 +6,7 @@
//! `skip_serializing_if = "Option::is_none"`).
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::evidence::{
AttemptSummary, Evidence, VerifyResult, VerifyStatus,
};
use nyx_scanner::evidence::{AttemptSummary, Evidence, VerifyResult, VerifyStatus};
use nyx_scanner::patterns::{FindingCategory, Severity};
fn base_diag() -> Diag {

View file

@ -16,8 +16,8 @@
mod common;
use nyx_scanner::dynamic::corpus::{
audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang, Oracle,
Oracle, audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang,
};
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::lang;
@ -57,7 +57,10 @@ fn make_spec(lang: Lang, entry_file: &str, entry_name: &str) -> HarnessSpec {
fn corpus_registers_ldap_for_every_supported_lang() {
for lang in LANGS {
let slice = payloads_for_lang(Cap::LDAP_INJECTION, *lang);
assert!(!slice.is_empty(), "LDAP_INJECTION has no payloads for {lang:?}");
assert!(
!slice.is_empty(),
"LDAP_INJECTION has no payloads for {lang:?}"
);
let has_vuln = slice.iter().any(|p| !p.is_benign);
let has_benign = slice.iter().any(|p| p.is_benign);
assert!(has_vuln, "{lang:?} LDAP missing vuln payload");
@ -104,10 +107,9 @@ fn payload_oracle_carries_ldap_result_count_predicate() {
match &vuln.oracle {
Oracle::SinkProbe { predicates } => {
assert!(
predicates.iter().any(|p| matches!(
p,
ProbePredicate::QueryResultCountGreaterThan { n: 1 }
)),
predicates
.iter()
.any(|p| matches!(p, ProbePredicate::QueryResultCountGreaterThan { n: 1 })),
"{lang:?} vuln payload missing QueryResultCountGreaterThan {{ n: 1 }}",
);
}
@ -146,7 +148,9 @@ fn marker_collisions_clean_with_phase_06_additions() {
#[test]
fn probe_kind_ldap_serdes() {
let original = ProbeKind::Ldap { entries_returned: 3 };
let original = ProbeKind::Ldap {
entries_returned: 3,
};
let json = serde_json::to_string(&original).unwrap();
assert!(json.contains("Ldap"));
assert!(json.contains("entries_returned"));
@ -181,8 +185,8 @@ fn lang_emitter_dispatches_to_ldap_harness() {
),
] {
let spec = make_spec(lang, entry_file, entry_name);
let harness = lang::emit(&spec)
.unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
let harness =
lang::emit(&spec).unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
assert!(
harness.source.contains("entries_returned"),
"{lang:?} ldap harness must carry the entries_returned probe field",
@ -246,8 +250,7 @@ fn framework_adapters_detect_ldap_sink() {
&bytes,
lang,
);
let b = binding
.unwrap_or_else(|| panic!("{lang:?} adapter must detect the LDAP fixture"));
let b = binding.unwrap_or_else(|| panic!("{lang:?} adapter must detect the LDAP fixture"));
assert_eq!(b.kind, EntryKind::Function);
assert!(!b.adapter.is_empty());
}
@ -279,7 +282,10 @@ fn stub_ldap_server_returns_three_for_wildcard_filter() {
let stub = LdapStub::start().expect("ldap stub starts");
let mal = LdapStub::evaluate("(|(uid=alice)(uid=*))");
let benign = LdapStub::evaluate("(uid=alice)");
assert!(mal.len() > 1, "malicious filter must match > 1 entry, got {mal:?}");
assert!(
mal.len() > 1,
"malicious filter must match > 1 entry, got {mal:?}"
);
assert_eq!(benign.len(), 1, "benign filter must match exactly 1 entry");
assert_eq!(stub.kind(), StubKind::Ldap);
}
@ -302,10 +308,10 @@ fn stub_kind_for_cap_routes_ldap_injection() {
mod e2e_phase_06 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -413,7 +419,9 @@ mod e2e_phase_06 {
#[test]
fn java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else { return };
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Java LDAP vuln must Confirm via run_spec; got {outcome:?}",
@ -427,7 +435,9 @@ mod e2e_phase_06 {
#[test]
fn python_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else { return };
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Python LDAP vuln must Confirm via run_spec; got {outcome:?}",
@ -441,7 +451,9 @@ mod e2e_phase_06 {
#[test]
fn php_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else { return };
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"PHP LDAP vuln must Confirm via run_spec; got {outcome:?}",

View file

@ -95,7 +95,9 @@ fn no_marker_is_substring_of_another_caps_payload() {
continue;
}
for payload in payloads_for(cap).iter().filter(|p| !p.is_benign) {
let payload_contains_marker = payload.bytes.windows(marker_bytes.len())
let payload_contains_marker = payload
.bytes
.windows(marker_bytes.len())
.any(|w| w == marker_bytes);
if payload_contains_marker {
@ -215,7 +217,8 @@ fn all_vuln_payloads_have_non_empty_oracle_marker() {
assert!(
marker.len() >= 4,
"payload {:?} for {cap:?} has very short marker {:?} (< 4 chars) — collision risk",
payload.label, marker
payload.label,
marker
);
}
}

View file

@ -17,7 +17,7 @@
mod common;
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::framework::{detect_binding, FrameworkBinding};
use nyx_scanner::dynamic::framework::{FrameworkBinding, detect_binding};
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
use nyx_scanner::labels::Cap;
@ -32,13 +32,7 @@ const SUPPORTED_LANGS: &[Lang] = &[
Lang::Go,
];
const UNSUPPORTED_LANGS: &[Lang] = &[
Lang::Php,
Lang::Ruby,
Lang::Rust,
Lang::C,
Lang::Cpp,
];
const UNSUPPORTED_LANGS: &[Lang] = &[Lang::Php, Lang::Ruby, Lang::Rust, Lang::C, Lang::Cpp];
fn entry_file(broker_lang: &str) -> &'static str {
// Phase 20 fixtures live at tests/dynamic_fixtures/message_handler/{broker_lang}/{vuln,benign}.
@ -222,29 +216,29 @@ fn kafka_python_adapter_binds_message_handler_kind() {
#[test]
fn kafka_java_adapter_binds_message_handler_kind() {
let b = detect_for(Lang::Java, entry_file("kafka_java"), "onMessage")
.expect("kafka-java detect");
let b =
detect_for(Lang::Java, entry_file("kafka_java"), "onMessage").expect("kafka-java detect");
assert!(matches!(b.kind, EntryKind::MessageHandler { .. }));
}
#[test]
fn sqs_python_adapter_binds_message_handler_kind() {
let b = detect_for(Lang::Python, entry_file("sqs_python"), "handler")
.expect("sqs-python detect");
let b =
detect_for(Lang::Python, entry_file("sqs_python"), "handler").expect("sqs-python detect");
assert!(matches!(b.kind, EntryKind::MessageHandler { .. }));
}
#[test]
fn sqs_java_adapter_binds_message_handler_kind() {
let b = detect_for(Lang::Java, entry_file("sqs_java"), "handleMessage")
.expect("sqs-java detect");
let b =
detect_for(Lang::Java, entry_file("sqs_java"), "handleMessage").expect("sqs-java detect");
assert!(matches!(b.kind, EntryKind::MessageHandler { .. }));
}
#[test]
fn sqs_node_adapter_binds_message_handler_kind() {
let b = detect_for(Lang::JavaScript, entry_file("sqs_node"), "handler")
.expect("sqs-node detect");
let b =
detect_for(Lang::JavaScript, entry_file("sqs_node"), "handler").expect("sqs-node detect");
assert!(matches!(b.kind, EntryKind::MessageHandler { .. }));
}
@ -257,8 +251,7 @@ fn pubsub_python_adapter_binds_message_handler_kind() {
#[test]
fn pubsub_go_adapter_binds_message_handler_kind() {
let b = detect_for(Lang::Go, entry_file("pubsub_go"), "OnMessage")
.expect("pubsub-go detect");
let b = detect_for(Lang::Go, entry_file("pubsub_go"), "OnMessage").expect("pubsub-go detect");
assert!(matches!(b.kind, EntryKind::MessageHandler { .. }));
}
@ -271,24 +264,20 @@ fn rabbit_python_adapter_binds_message_handler_kind() {
#[test]
fn rabbit_java_adapter_binds_message_handler_kind() {
let b = detect_for(Lang::Java, entry_file("rabbit_java"), "onMessage")
.expect("rabbit-java detect");
let b =
detect_for(Lang::Java, entry_file("rabbit_java"), "onMessage").expect("rabbit-java detect");
assert!(matches!(b.kind, EntryKind::MessageHandler { .. }));
}
#[test]
fn nats_go_adapter_binds_message_handler_kind() {
let b = detect_for(Lang::Go, entry_file("nats_go"), "OnMessage")
.expect("nats-go detect");
let b = detect_for(Lang::Go, entry_file("nats_go"), "OnMessage").expect("nats-go detect");
assert!(matches!(b.kind, EntryKind::MessageHandler { .. }));
}
#[test]
fn registry_slices_include_phase_20_adapters() {
let java_names: Vec<&'static str> = adapters_for(Lang::Java)
.iter()
.map(|a| a.name())
.collect();
let java_names: Vec<&'static str> = adapters_for(Lang::Java).iter().map(|a| a.name()).collect();
assert!(java_names.contains(&"kafka-java"));
assert!(java_names.contains(&"sqs-java"));
assert!(java_names.contains(&"rabbit-java"));
@ -302,10 +291,7 @@ fn registry_slices_include_phase_20_adapters() {
assert!(python_names.contains(&"pubsub-python"));
assert!(python_names.contains(&"rabbit-python"));
let go_names: Vec<&'static str> = adapters_for(Lang::Go)
.iter()
.map(|a| a.name())
.collect();
let go_names: Vec<&'static str> = adapters_for(Lang::Go).iter().map(|a| a.name()).collect();
assert!(go_names.contains(&"pubsub-go"));
assert!(go_names.contains(&"nats-go"));
@ -327,10 +313,10 @@ fn registry_slices_include_phase_20_adapters() {
mod e2e_phase_20 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::SandboxOptions;
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -468,9 +454,7 @@ mod e2e_phase_20 {
);
None
}
Err(e) => panic!(
"run_spec({lang:?} {fixture_dir}/{fixture_file}) errored: {e:?}",
),
Err(e) => panic!("run_spec({lang:?} {fixture_dir}/{fixture_file}) errored: {e:?}",),
}
}
@ -497,8 +481,7 @@ mod e2e_phase_20 {
#[test]
fn sqs_python_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Python, "sqs_python", "vuln.py", "handler", "jobs")
else {
let Some(outcome) = run(Lang::Python, "sqs_python", "vuln.py", "handler", "jobs") else {
return;
};
assert!(outcome.triggered_by.is_some());
@ -540,8 +523,7 @@ mod e2e_phase_20 {
#[test]
fn sqs_node_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::JavaScript, "sqs_node", "vuln.js", "handler", "jobs")
else {
let Some(outcome) = run(Lang::JavaScript, "sqs_node", "vuln.js", "handler", "jobs") else {
return;
};
assert!(

View file

@ -59,7 +59,9 @@ fn oob_outbound_carries_listener() {
return;
};
let listener = Arc::new(listener);
let p = NetworkPolicy::OobOutbound { listener: Arc::clone(&listener) };
let p = NetworkPolicy::OobOutbound {
listener: Arc::clone(&listener),
};
assert!(p.allows_network());
let got = p.oob_listener().expect("listener present");
assert!(

View file

@ -16,12 +16,12 @@
mod common;
use nyx_scanner::dynamic::corpus::{
audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang, Oracle,
Oracle, audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang,
};
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::oracle::{oracle_fired, ProbePredicate};
use nyx_scanner::dynamic::oracle::{ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
@ -72,10 +72,7 @@ fn corpus_registers_open_redirect_for_every_supported_lang() {
let has_vuln = slice.iter().any(|p| !p.is_benign);
let has_benign = slice.iter().any(|p| p.is_benign);
assert!(has_vuln, "{lang:?} OPEN_REDIRECT missing vuln payload");
assert!(
has_benign,
"{lang:?} OPEN_REDIRECT missing benign control"
);
assert!(has_benign, "{lang:?} OPEN_REDIRECT missing benign control");
}
}
@ -94,8 +91,8 @@ fn benign_control_resolves_within_lang_slice() {
for lang in LANGS {
let slice = payloads_for_lang(Cap::OPEN_REDIRECT, *lang);
let vuln = slice.iter().find(|p| !p.is_benign).unwrap();
let resolved = resolve_benign_control_lang(vuln, Cap::OPEN_REDIRECT, *lang)
.expect("paired control");
let resolved =
resolve_benign_control_lang(vuln, Cap::OPEN_REDIRECT, *lang).expect("paired control");
assert!(resolved.is_benign);
let direct = benign_payload_for_lang(Cap::OPEN_REDIRECT, *lang).unwrap();
assert_eq!(direct.label, resolved.label);
@ -110,10 +107,9 @@ fn payload_oracle_carries_redirect_host_not_in_predicate() {
match &vuln.oracle {
Oracle::SinkProbe { predicates } => {
assert!(
predicates.iter().any(|p| matches!(
p,
ProbePredicate::RedirectHostNotIn { .. }
)),
predicates
.iter()
.any(|p| matches!(p, ProbePredicate::RedirectHostNotIn { .. })),
"{lang:?} vuln payload missing RedirectHostNotIn predicate",
);
}
@ -275,8 +271,8 @@ fn lang_emitter_dispatches_to_open_redirect_harness() {
),
] {
let spec = make_spec(lang, entry_file, entry_name);
let harness = lang::emit(&spec)
.unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
let harness =
lang::emit(&spec).unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
assert!(
harness.source.contains("Redirect"),
"{lang:?} redirect harness must carry the Redirect probe kind",
@ -361,8 +357,8 @@ fn framework_adapters_detect_redirect_sink() {
&bytes,
lang,
);
let b = binding
.unwrap_or_else(|| panic!("{lang:?} adapter must detect the redirect fixture"));
let b =
binding.unwrap_or_else(|| panic!("{lang:?} adapter must detect the redirect fixture"));
assert_eq!(b.kind, EntryKind::Function);
assert!(!b.adapter.is_empty());
}
@ -423,10 +419,10 @@ fn slug(lang: Lang) -> &'static str {
mod e2e_phase_09 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -554,43 +550,57 @@ mod e2e_phase_09 {
#[test]
fn java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else { return };
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
return;
};
assert_confirmed(Lang::Java, &outcome);
}
#[test]
fn python_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else { return };
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
return;
};
assert_confirmed(Lang::Python, &outcome);
}
#[test]
fn php_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else { return };
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else {
return;
};
assert_confirmed(Lang::Php, &outcome);
}
#[test]
fn ruby_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else { return };
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else {
return;
};
assert_confirmed(Lang::Ruby, &outcome);
}
#[test]
fn js_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else { return };
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else {
return;
};
assert_confirmed(Lang::JavaScript, &outcome);
}
#[test]
fn go_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Go, "vuln.go", "Run") else { return };
let Some(outcome) = run(Lang::Go, "vuln.go", "Run") else {
return;
};
assert_confirmed(Lang::Go, &outcome);
}
#[test]
fn rust_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Rust, "vuln.rs", "run") else { return };
let Some(outcome) = run(Lang::Rust, "vuln.rs", "run") else {
return;
};
assert_confirmed(Lang::Rust, &outcome);
}
}

View file

@ -81,7 +81,11 @@ fn sample_probe(callee: &str, arg: &str, label: &str) -> SinkProbe {
#[test]
fn build_outcome_confirmed_carries_both_traces() {
let vuln = vec![sample_probe("os.system", "; echo NYX_PWN_CMDI", "cmdi-echo-marker")];
let vuln = vec![sample_probe(
"os.system",
"; echo NYX_PWN_CMDI",
"cmdi-echo-marker",
)];
let benign = vec![sample_probe("os.system", "benign_safe_cmdi", "cmdi-benign")];
let outcome = build_outcome(
"cmdi-echo-marker",
@ -106,7 +110,10 @@ fn build_outcome_oracle_collision_keeps_both_traces() {
let vuln = vec![sample_probe("os.system", "a", "v")];
let benign = vec![sample_probe("os.system", "b", "b")];
let outcome = build_outcome("v", true, &vuln, "b", true, &benign);
assert_eq!(outcome.verdict, DifferentialVerdict::OracleCollisionSuspected);
assert_eq!(
outcome.verdict,
DifferentialVerdict::OracleCollisionSuspected
);
assert_eq!(outcome.vuln_probes.len(), 1);
assert_eq!(outcome.benign_probes.len(), 1);
}

View file

@ -21,13 +21,9 @@
mod common;
use nyx_scanner::dynamic::oracle::{
oracle_fired, probe_crash_signal, Oracle, Signal, SignalSet,
};
use nyx_scanner::dynamic::oracle::{Oracle, Signal, SignalSet, oracle_fired, probe_crash_signal};
use nyx_scanner::dynamic::policy;
use nyx_scanner::dynamic::probe::{
ProbeArg, ProbeChannel, ProbeKind, ProbeWitness, SinkProbe,
};
use nyx_scanner::dynamic::probe::{ProbeArg, ProbeChannel, ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::evidence::InconclusiveReason;
use std::time::Duration;
@ -116,7 +112,10 @@ fn case_b_outside_sink_crash_does_not_fire_and_is_unrelated() {
let dir = TempDir::new().unwrap();
let channel = ProbeChannel::for_workdir(dir.path()).unwrap();
let probes = channel.drain();
assert!(probes.is_empty(), "no probe written from outside-sink abort");
assert!(
probes.is_empty(),
"no probe written from outside-sink abort"
);
let oracle = Oracle::SinkCrash {
signals: SignalSet::all(),
@ -131,8 +130,7 @@ fn case_b_outside_sink_crash_does_not_fire_and_is_unrelated() {
// outcome + no probe with a crash signal. Lock the predicate
// here so the runner's wiring in src/dynamic/runner.rs stays in
// sync with what the test labels expect.
let process_crashed =
crashed_outcome().exit_code.is_none() && !crashed_outcome().timed_out;
let process_crashed = crashed_outcome().exit_code.is_none() && !crashed_outcome().timed_out;
let has_sink_crash_probe = probes.iter().any(|p| probe_crash_signal(p).is_some());
let is_sink_crash_oracle = matches!(oracle, Oracle::SinkCrash { .. });
assert!(is_sink_crash_oracle && process_crashed && !has_sink_crash_probe);
@ -209,7 +207,10 @@ fn case_c_witness_capture_is_bounded_and_scrubbed() {
assert_eq!(witness.cwd, "/tmp/nyx-run-1");
assert_eq!(witness.callee, "exec");
assert_eq!(witness.args_repr, vec!["arg0".to_owned(), "arg1".to_owned()]);
assert_eq!(
witness.args_repr,
vec!["arg0".to_owned(), "arg1".to_owned()]
);
}
#[test]
@ -266,13 +267,11 @@ fn signal_wire_format_accepts_canonical_and_short_aliases() {
// The per-language shims write SIGSEGV / SIGABRT / etc. as the
// signal value; downstream JSON consumers and the host-side oracle
// both need to deserialise the same wire format.
let canonical =
serde_json::from_str::<Signal>("\"SIGSEGV\"").expect("canonical SIG name");
let canonical = serde_json::from_str::<Signal>("\"SIGSEGV\"").expect("canonical SIG name");
assert_eq!(canonical, Signal::Sigsegv);
let short = serde_json::from_str::<Signal>("\"SEGV\"").expect("short alias");
assert_eq!(short, Signal::Sigsegv);
let title =
serde_json::from_str::<Signal>("\"Sigsegv\"").expect("derive-default alias");
let title = serde_json::from_str::<Signal>("\"Sigsegv\"").expect("derive-default alias");
assert_eq!(title, Signal::Sigsegv);
}
@ -310,10 +309,10 @@ fn signal_set_const_construction_is_order_independent() {
mod e2e_phase_08 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunOutcome};
use nyx_scanner::dynamic::runner::{RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::SandboxOptions;
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;
@ -387,7 +386,9 @@ mod e2e_phase_08 {
#[test]
fn setup_fault_routes_to_unrelated_crash() {
let Some(outcome) = run("setup_fault.c") else { return };
let Some(outcome) = run("setup_fault.c") else {
return;
};
assert!(
outcome.triggered_by.is_none(),
"setup_fault must not Confirm — handler is never installed: {outcome:?}",
@ -408,7 +409,9 @@ mod e2e_phase_08 {
#[test]
fn sink_fault_confirms_via_sink_crash_probe() {
let Some(outcome) = run("sink_fault.c") else { return };
let Some(outcome) = run("sink_fault.c") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"sink_fault must Confirm via SinkCrash + differential: {outcome:?}",

View file

@ -17,9 +17,9 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::oracle::{oracle_fired, Oracle, ProbePredicate};
use nyx_scanner::dynamic::oracle::{Oracle, ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::probe::{
ProbeArg, ProbeChannel, ProbeKind, ProbeWitness, SinkProbe, PROBE_PATH_ENV,
PROBE_PATH_ENV, ProbeArg, ProbeChannel, ProbeKind, ProbeWitness, SinkProbe,
};
use std::time::Duration;
use tempfile::TempDir;
@ -59,7 +59,9 @@ fn synthetic_harness_fires_probe(
kind: ProbeKind::Normal,
witness: ProbeWitness::empty(),
};
channel.write(&probe).expect("synthetic harness probe write");
channel
.write(&probe)
.expect("synthetic harness probe write");
}
/// "Control" harness — runs the same way but does NOT write a probe.

View file

@ -121,12 +121,7 @@ fn graphql_resolver_supported_in_target_langs() {
#[test]
fn websocket_supported_in_target_langs() {
for lang in [
Lang::Python,
Lang::JavaScript,
Lang::TypeScript,
Lang::Ruby,
] {
for lang in [Lang::Python, Lang::JavaScript, Lang::TypeScript, Lang::Ruby] {
assert!(
lang::entry_kinds_supported(lang).contains(&EntryKindTag::WebSocket),
"{lang:?} must advertise WebSocket after Phase 21",

View file

@ -14,7 +14,7 @@ mod common;
#[cfg(feature = "dynamic")]
mod php_fixture_tests {
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, InconclusiveReason, UnsupportedReason,
VerifyStatus,
@ -456,7 +456,7 @@ mod php_fixture_tests {
#[cfg(feature = "dynamic")]
mod phase15_shape_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -506,7 +506,15 @@ mod phase15_shape_tests {
// return; };`.
run_shape_fixture_lang_or_skip(
&[Prerequisite::CommandAvailable("php")],
Lang::Php, "php", shape, file, func, cap, sink_line, kind, slot,
Lang::Php,
"php",
shape,
file,
func,
cap,
sink_line,
kind,
slot,
)
}
@ -515,8 +523,13 @@ mod phase15_shape_tests {
#[test]
fn route_closure_vuln_is_confirmed() {
let Some(r) = run(
"route_closure", "vuln.php", "run", Cap::CODE_EXEC, 10,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"route_closure",
"vuln.php",
"run",
Cap::CODE_EXEC,
10,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -526,8 +539,13 @@ mod phase15_shape_tests {
#[test]
fn route_closure_benign_not_confirmed() {
let Some(r) = run(
"route_closure", "benign.php", "run", Cap::CODE_EXEC, 11,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"route_closure",
"benign.php",
"run",
Cap::CODE_EXEC,
11,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -539,8 +557,13 @@ mod phase15_shape_tests {
#[test]
fn cli_script_vuln_is_confirmed() {
let Some(r) = run(
"cli_script", "vuln.php", "main", Cap::CODE_EXEC, 8,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"cli_script",
"vuln.php",
"main",
Cap::CODE_EXEC,
8,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
@ -550,8 +573,13 @@ mod phase15_shape_tests {
#[test]
fn cli_script_benign_not_confirmed() {
let Some(r) = run(
"cli_script", "benign.php", "main", Cap::CODE_EXEC, 11,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"cli_script",
"benign.php",
"main",
Cap::CODE_EXEC,
11,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
@ -563,8 +591,13 @@ mod phase15_shape_tests {
#[test]
fn top_level_script_vuln_is_confirmed() {
let Some(r) = run(
"top_level_script", "vuln.php", "", Cap::CODE_EXEC, 8,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"top_level_script",
"vuln.php",
"",
Cap::CODE_EXEC,
8,
EntryKind::Function,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
) else {
return;
};
@ -574,8 +607,13 @@ mod phase15_shape_tests {
#[test]
fn top_level_script_benign_not_confirmed() {
let Some(r) = run(
"top_level_script", "benign.php", "", Cap::CODE_EXEC, 10,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"top_level_script",
"benign.php",
"",
Cap::CODE_EXEC,
10,
EntryKind::Function,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
) else {
return;
};

View file

@ -11,7 +11,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::framework::{detect_binding, HttpMethod, ParamSource};
use nyx_scanner::dynamic::framework::{HttpMethod, ParamSource, detect_binding};
use nyx_scanner::evidence::EntryKind;
use nyx_scanner::summary::FuncSummary;
use nyx_scanner::symbol::Lang;

View file

@ -12,7 +12,7 @@
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::policy::{self, DenyRule, PolicyDecision};
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, InconclusiveReason, SpanEvidence, VerifyStatus,
};
@ -78,9 +78,7 @@ fn allow_returns_for_diag_without_secrets() {
fn credentials_rule_fires_on_aws_key_in_flow_step_snippet() {
let mut diag = empty_diag();
let mut ev = Evidence::default();
ev.flow_steps = vec![flow_step_with_snippet(
"key=AKIAFAKETEST00000000",
)];
ev.flow_steps = vec![flow_step_with_snippet("key=AKIAFAKETEST00000000")];
diag.evidence = Some(ev);
match policy::evaluate(&diag) {
PolicyDecision::Deny {
@ -116,9 +114,7 @@ fn credentials_rule_fires_on_bearer_header_note() {
fn private_key_rule_fires_on_pem_block_in_snippet() {
let mut diag = empty_diag();
let mut ev = Evidence::default();
ev.source = Some(span_with_snippet(
"-----BEGIN OPENSSH PRIVATE KEY-----",
));
ev.source = Some(span_with_snippet("-----BEGIN OPENSSH PRIVATE KEY-----"));
diag.evidence = Some(ev);
match policy::evaluate(&diag) {
PolicyDecision::Deny { rule, .. } => {
@ -185,9 +181,7 @@ fn credentials_rule_fires_before_other_rules() {
// endpoint name. Order asserted by the policy.evaluate impl.
let mut diag = empty_diag();
let mut ev = Evidence::default();
ev.notes = vec![
"deploying key=AKIAFAKETEST00000000 to api.prod.example.com".to_owned(),
];
ev.notes = vec!["deploying key=AKIAFAKETEST00000000 to api.prod.example.com".to_owned()];
diag.evidence = Some(ev);
match policy::evaluate(&diag) {
PolicyDecision::Deny { rule, .. } => {

View file

@ -16,12 +16,12 @@
mod common;
use nyx_scanner::dynamic::corpus::{
audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang, Oracle,
Oracle, audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang,
};
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::oracle::{oracle_fired, ProbePredicate};
use nyx_scanner::dynamic::oracle::{ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
@ -63,7 +63,10 @@ fn corpus_registers_prototype_pollution_for_js_and_ts() {
);
let has_vuln = slice.iter().any(|p| !p.is_benign);
let has_benign = slice.iter().any(|p| p.is_benign);
assert!(has_vuln, "{lang:?} PROTOTYPE_POLLUTION missing vuln payload");
assert!(
has_vuln,
"{lang:?} PROTOTYPE_POLLUTION missing vuln payload"
);
assert!(
has_benign,
"{lang:?} PROTOTYPE_POLLUTION missing benign control"
@ -111,10 +114,9 @@ fn payload_oracle_carries_prototype_canary_predicate() {
match &vuln.oracle {
Oracle::SinkProbe { predicates } => {
assert!(
predicates.iter().any(|p| matches!(
p,
ProbePredicate::PrototypeCanaryTouched { .. }
)),
predicates
.iter()
.any(|p| matches!(p, ProbePredicate::PrototypeCanaryTouched { .. })),
"{lang:?} vuln payload missing PrototypeCanaryTouched predicate",
);
}
@ -246,7 +248,9 @@ fn lang_emitter_dispatches_to_prototype_pollution_harness() {
"{lang:?} harness must reference the canary property name",
);
assert!(
harness.source.contains("Object.defineProperty(Object.prototype"),
harness
.source
.contains("Object.defineProperty(Object.prototype"),
"{lang:?} harness must install the canary trap on Object.prototype",
);
assert!(
@ -408,10 +412,10 @@ fn slug(lang: Lang) -> &'static str {
mod e2e_phase_10 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -523,13 +527,17 @@ mod e2e_phase_10 {
#[test]
fn js_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else { return };
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else {
return;
};
assert_confirmed(Lang::JavaScript, &outcome);
}
#[test]
fn ts_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::TypeScript, "vuln.ts", "run") else { return };
let Some(outcome) = run(Lang::TypeScript, "vuln.ts", "run") else {
return;
};
assert_confirmed(Lang::TypeScript, &outcome);
}
}

View file

@ -14,15 +14,14 @@ mod common;
#[cfg(feature = "dynamic")]
mod python_fixture_tests {
use crate::common::fixture_harness::{
run_fixture_and_compare_to_golden, run_harness_snapshot, run_shape_fixture,
CopyStrategy, FixtureSpec, Prerequisite,
CopyStrategy, FixtureSpec, Prerequisite, run_fixture_and_compare_to_golden,
run_harness_snapshot, run_shape_fixture,
};
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, EntryKind, Evidence, FlowStep, FlowStepKind, UnsupportedReason,
VerifyStatus,
Confidence, EntryKind, Evidence, FlowStep, FlowStepKind, UnsupportedReason, VerifyStatus,
};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
@ -39,7 +38,12 @@ mod python_fixture_tests {
.unwrap_or(false)
}
fn spec(fixture: &'static str, func: &'static str, cap: Cap, sink_line: u32) -> FixtureSpec<'static> {
fn spec(
fixture: &'static str,
func: &'static str,
cap: Cap,
sink_line: u32,
) -> FixtureSpec<'static> {
FixtureSpec {
lang_dir: "python",
fixture,
@ -82,13 +86,19 @@ mod python_fixture_tests {
#[test]
fn sqli_positive_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec("sqli_positive.py", "login", Cap::SQL_QUERY, 17));
}
#[test]
fn sqli_negative_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec("sqli_negative.py", "login", Cap::SQL_QUERY, 12));
}
@ -104,22 +114,46 @@ mod python_fixture_tests {
#[test]
fn sqli_adversarial_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
run_fixture_and_compare_to_golden(&spec("sqli_adversarial.py", "get_value", Cap::SQL_QUERY, 999));
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"sqli_adversarial.py",
"get_value",
Cap::SQL_QUERY,
999,
));
}
// ── Command injection ────────────────────────────────────────────────────
#[test]
fn cmdi_positive_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
run_fixture_and_compare_to_golden(&spec("cmdi_positive.py", "run_ping", Cap::CODE_EXEC, 13));
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"cmdi_positive.py",
"run_ping",
Cap::CODE_EXEC,
13,
));
}
#[test]
fn cmdi_negative_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
run_fixture_and_compare_to_golden(&spec("cmdi_negative.py", "run_ping", Cap::CODE_EXEC, 17));
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"cmdi_negative.py",
"run_ping",
Cap::CODE_EXEC,
17,
));
}
#[test]
@ -134,7 +168,10 @@ mod python_fixture_tests {
#[test]
fn cmdi_adversarial_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"cmdi_adversarial.py",
"process_input",
@ -147,14 +184,30 @@ mod python_fixture_tests {
#[test]
fn fileio_positive_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
run_fixture_and_compare_to_golden(&spec("fileio_positive.py", "read_file", Cap::FILE_IO, 11));
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"fileio_positive.py",
"read_file",
Cap::FILE_IO,
11,
));
}
#[test]
fn fileio_negative_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
run_fixture_and_compare_to_golden(&spec("fileio_negative.py", "read_file", Cap::FILE_IO, 18));
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"fileio_negative.py",
"read_file",
Cap::FILE_IO,
18,
));
}
#[test]
@ -169,21 +222,35 @@ mod python_fixture_tests {
#[test]
fn fileio_adversarial_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
run_fixture_and_compare_to_golden(&spec("fileio_adversarial.py", "read_file", Cap::FILE_IO, 999));
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"fileio_adversarial.py",
"read_file",
Cap::FILE_IO,
999,
));
}
// ── SSRF ─────────────────────────────────────────────────────────────────
#[test]
fn ssrf_positive_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec("ssrf_positive.py", "fetch_url", Cap::SSRF, 11));
}
#[test]
fn ssrf_negative_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec("ssrf_negative.py", "fetch_url", Cap::SSRF, 26));
}
@ -194,15 +261,26 @@ mod python_fixture_tests {
#[test]
fn ssrf_adversarial_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
run_fixture_and_compare_to_golden(&spec("ssrf_adversarial.py", "fetch_url", Cap::SSRF, 999));
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"ssrf_adversarial.py",
"fetch_url",
Cap::SSRF,
999,
));
}
// ── XSS ──────────────────────────────────────────────────────────────────
#[test]
fn xss_positive_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"xss_positive.py",
"render_comment",
@ -213,7 +291,10 @@ mod python_fixture_tests {
#[test]
fn xss_negative_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"xss_negative.py",
"render_comment",
@ -234,7 +315,10 @@ mod python_fixture_tests {
#[test]
fn xss_adversarial_matches_golden() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
run_fixture_and_compare_to_golden(&spec(
"xss_adversarial.py",
"render_comment",
@ -342,20 +426,36 @@ mod python_fixture_tests {
#[test]
fn generic_vuln_is_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
let r = run_shape_fixture(
"generic", "vuln.py", "run_ping", Cap::CODE_EXEC, 12,
EntryKind::Function, PayloadSlot::Param(0),
"generic",
"vuln.py",
"run_ping",
Cap::CODE_EXEC,
12,
EntryKind::Function,
PayloadSlot::Param(0),
);
assert_confirmed("generic", &r);
}
#[test]
fn generic_benign_not_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
let r = run_shape_fixture(
"generic", "benign.py", "run_ping", Cap::CODE_EXEC, 20,
EntryKind::Function, PayloadSlot::Param(0),
"generic",
"benign.py",
"run_ping",
Cap::CODE_EXEC,
20,
EntryKind::Function,
PayloadSlot::Param(0),
);
assert_not_confirmed("generic", &r);
}
@ -363,8 +463,13 @@ mod python_fixture_tests {
#[test]
fn generic_harness_snapshot_matches_golden() {
run_harness_snapshot(
"generic", "vuln.py", "run_ping", Cap::CODE_EXEC, 12,
EntryKind::Function, PayloadSlot::Param(0),
"generic",
"vuln.py",
"run_ping",
Cap::CODE_EXEC,
12,
EntryKind::Function,
PayloadSlot::Param(0),
);
}
@ -372,20 +477,36 @@ mod python_fixture_tests {
#[test]
fn cli_vuln_is_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
let r = run_shape_fixture(
"cli", "vuln.py", "main", Cap::CODE_EXEC, 14,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"cli",
"vuln.py",
"main",
Cap::CODE_EXEC,
14,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
);
assert_confirmed("cli", &r);
}
#[test]
fn cli_benign_not_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
let r = run_shape_fixture(
"cli", "benign.py", "main", Cap::CODE_EXEC, 11,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"cli",
"benign.py",
"main",
Cap::CODE_EXEC,
11,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
);
assert_not_confirmed("cli", &r);
}
@ -393,8 +514,13 @@ mod python_fixture_tests {
#[test]
fn cli_harness_snapshot_matches_golden() {
run_harness_snapshot(
"cli", "vuln.py", "main", Cap::CODE_EXEC, 14,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"cli",
"vuln.py",
"main",
Cap::CODE_EXEC,
14,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
);
}
@ -402,20 +528,36 @@ mod python_fixture_tests {
#[test]
fn pytest_vuln_is_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
let r = run_shape_fixture(
"pytest", "vuln.py", "test_run_ping", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"pytest",
"vuln.py",
"test_run_ping",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
assert_confirmed("pytest", &r);
}
#[test]
fn pytest_benign_not_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
let r = run_shape_fixture(
"pytest", "benign.py", "test_run_ping", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"pytest",
"benign.py",
"test_run_ping",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
assert_not_confirmed("pytest", &r);
}
@ -423,8 +565,13 @@ mod python_fixture_tests {
#[test]
fn pytest_harness_snapshot_matches_golden() {
run_harness_snapshot(
"pytest", "vuln.py", "test_run_ping", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"pytest",
"vuln.py",
"test_run_ping",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
}
@ -432,20 +579,36 @@ mod python_fixture_tests {
#[test]
fn async_vuln_is_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
let r = run_shape_fixture(
"async", "vuln.py", "run_ping", Cap::CODE_EXEC, 13,
EntryKind::Function, PayloadSlot::Param(0),
"async",
"vuln.py",
"run_ping",
Cap::CODE_EXEC,
13,
EntryKind::Function,
PayloadSlot::Param(0),
);
assert_confirmed("async", &r);
}
#[test]
fn async_benign_not_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
let r = run_shape_fixture(
"async", "benign.py", "run_ping", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
"async",
"benign.py",
"run_ping",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
);
assert_not_confirmed("async", &r);
}
@ -453,8 +616,13 @@ mod python_fixture_tests {
#[test]
fn async_harness_snapshot_matches_golden() {
run_harness_snapshot(
"async", "vuln.py", "run_ping", Cap::CODE_EXEC, 13,
EntryKind::Function, PayloadSlot::Param(0),
"async",
"vuln.py",
"run_ping",
Cap::CODE_EXEC,
13,
EntryKind::Function,
PayloadSlot::Param(0),
);
}
@ -462,28 +630,44 @@ mod python_fixture_tests {
#[test]
fn celery_vuln_is_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
if !python_module_available("celery") {
eprintln!("SKIP: celery not importable");
return;
}
let r = run_shape_fixture(
"celery", "vuln.py", "run_job", Cap::CODE_EXEC, 17,
EntryKind::Function, PayloadSlot::Param(0),
"celery",
"vuln.py",
"run_job",
Cap::CODE_EXEC,
17,
EntryKind::Function,
PayloadSlot::Param(0),
);
assert_confirmed("celery", &r);
}
#[test]
fn celery_benign_not_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
if !python_module_available("celery") {
eprintln!("SKIP: celery not importable");
return;
}
let r = run_shape_fixture(
"celery", "benign.py", "run_job", Cap::CODE_EXEC, 17,
EntryKind::Function, PayloadSlot::Param(0),
"celery",
"benign.py",
"run_job",
Cap::CODE_EXEC,
17,
EntryKind::Function,
PayloadSlot::Param(0),
);
assert_not_confirmed("celery", &r);
}
@ -491,8 +675,13 @@ mod python_fixture_tests {
#[test]
fn celery_harness_snapshot_matches_golden() {
run_harness_snapshot(
"celery", "vuln.py", "run_job", Cap::CODE_EXEC, 17,
EntryKind::Function, PayloadSlot::Param(0),
"celery",
"vuln.py",
"run_job",
Cap::CODE_EXEC,
17,
EntryKind::Function,
PayloadSlot::Param(0),
);
}
@ -500,28 +689,44 @@ mod python_fixture_tests {
#[test]
fn flask_vuln_is_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
if !python_module_available("flask") {
eprintln!("SKIP: flask not importable");
return;
}
let r = run_shape_fixture(
"flask", "vuln.py", "ping", Cap::CODE_EXEC, 18,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"flask",
"vuln.py",
"ping",
Cap::CODE_EXEC,
18,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
assert_confirmed("flask", &r);
}
#[test]
fn flask_benign_not_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
if !python_module_available("flask") {
eprintln!("SKIP: flask not importable");
return;
}
let r = run_shape_fixture(
"flask", "benign.py", "ping", Cap::CODE_EXEC, 17,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"flask",
"benign.py",
"ping",
Cap::CODE_EXEC,
17,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
assert_not_confirmed("flask", &r);
}
@ -529,8 +734,13 @@ mod python_fixture_tests {
#[test]
fn flask_harness_snapshot_matches_golden() {
run_harness_snapshot(
"flask", "vuln.py", "ping", Cap::CODE_EXEC, 18,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"flask",
"vuln.py",
"ping",
Cap::CODE_EXEC,
18,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
}
@ -538,28 +748,44 @@ mod python_fixture_tests {
#[test]
fn fastapi_vuln_is_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
if !python_module_available("fastapi") {
eprintln!("SKIP: fastapi not importable");
return;
}
let r = run_shape_fixture(
"fastapi", "vuln.py", "ping", Cap::CODE_EXEC, 16,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"fastapi",
"vuln.py",
"ping",
Cap::CODE_EXEC,
16,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
assert_confirmed("fastapi", &r);
}
#[test]
fn fastapi_benign_not_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
if !python_module_available("fastapi") {
eprintln!("SKIP: fastapi not importable");
return;
}
let r = run_shape_fixture(
"fastapi", "benign.py", "ping", Cap::CODE_EXEC, 16,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"fastapi",
"benign.py",
"ping",
Cap::CODE_EXEC,
16,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
assert_not_confirmed("fastapi", &r);
}
@ -567,8 +793,13 @@ mod python_fixture_tests {
#[test]
fn fastapi_harness_snapshot_matches_golden() {
run_harness_snapshot(
"fastapi", "vuln.py", "ping", Cap::CODE_EXEC, 16,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"fastapi",
"vuln.py",
"ping",
Cap::CODE_EXEC,
16,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
}
@ -576,28 +807,44 @@ mod python_fixture_tests {
#[test]
fn django_vuln_is_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
if !python_module_available("django") {
eprintln!("SKIP: django not importable");
return;
}
let r = run_shape_fixture(
"django", "vuln.py", "ping", Cap::CODE_EXEC, 15,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"django",
"vuln.py",
"ping",
Cap::CODE_EXEC,
15,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
assert_confirmed("django", &r);
}
#[test]
fn django_benign_not_confirmed() {
if !python3_available() { eprintln!("SKIP: python3 not available"); return; }
if !python3_available() {
eprintln!("SKIP: python3 not available");
return;
}
if !python_module_available("django") {
eprintln!("SKIP: django not importable");
return;
}
let r = run_shape_fixture(
"django", "benign.py", "ping", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"django",
"benign.py",
"ping",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
assert_not_confirmed("django", &r);
}
@ -605,8 +852,13 @@ mod python_fixture_tests {
#[test]
fn django_harness_snapshot_matches_golden() {
run_harness_snapshot(
"django", "vuln.py", "ping", Cap::CODE_EXEC, 15,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
"django",
"vuln.py",
"ping",
Cap::CODE_EXEC,
15,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
);
}

View file

@ -21,7 +21,7 @@
mod common;
use nyx_scanner::dynamic::framework::{detect_binding, HttpMethod, ParamSource};
use nyx_scanner::dynamic::framework::{HttpMethod, ParamSource, detect_binding};
use nyx_scanner::evidence::EntryKind;
use nyx_scanner::summary::FuncSummary;
use nyx_scanner::symbol::Lang;
@ -193,10 +193,10 @@ fn fastapi_adapter_runs_before_starlette_for_fastapi_files() {
#[cfg(feature = "dynamic")]
mod e2e_phase_12 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::SandboxOptions;
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -277,7 +277,9 @@ mod e2e_phase_12 {
}
fn assert_confirmed(fixture_subdir: &str) {
let Some(outcome) = run(fixture_subdir) else { return };
let Some(outcome) = run(fixture_subdir) else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"{fixture_subdir} CODE_EXEC vuln must Confirm via run_spec; got {outcome:?}",

View file

@ -90,16 +90,19 @@ mod repro_determinism_tests {
// Write repro bundle (first time).
let artifact1 = repro::write(
&spec, &opts, &outcome, &verdict,
&spec,
&opts,
&outcome,
&verdict,
"# harness source v1\n",
"def login(x): pass\n",
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
).expect("first repro write must succeed");
)
.expect("first repro write must succeed");
let outcome_json_1 =
std::fs::read_to_string(artifact1.root.join("expected/outcome.json"))
let outcome_json_1 = std::fs::read_to_string(artifact1.root.join("expected/outcome.json"))
.expect("outcome.json must exist after first write");
// Write repro bundle (second time, same inputs).
@ -107,16 +110,19 @@ mod repro_determinism_tests {
std::fs::remove_dir_all(&artifact1.root).unwrap();
let artifact2 = repro::write(
&spec, &opts, &outcome, &verdict,
&spec,
&opts,
&outcome,
&verdict,
"# harness source v1\n",
"def login(x): pass\n",
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
).expect("second repro write must succeed");
)
.expect("second repro write must succeed");
let outcome_json_2 =
std::fs::read_to_string(artifact2.root.join("expected/outcome.json"))
let outcome_json_2 = std::fs::read_to_string(artifact2.root.join("expected/outcome.json"))
.expect("outcome.json must exist after second write");
assert_eq!(
@ -141,9 +147,17 @@ mod repro_determinism_tests {
let verdict = make_confirmed_verdict("determinism00002");
let artifact = repro::write(
&spec, &opts, &outcome, &verdict,
"# harness", "# entry", b"payload", "label", None,
).expect("repro write must succeed");
&spec,
&opts,
&outcome,
&verdict,
"# harness",
"# entry",
b"payload",
"label",
None,
)
.expect("repro write must succeed");
let outcome_json =
std::fs::read_to_string(artifact.root.join("expected/outcome.json")).unwrap();
@ -262,8 +276,7 @@ fn main() {
None,
)
.expect("first Rust repro write");
let json1 =
std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
let json1 = std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
std::fs::remove_dir_all(&artifact1.root).unwrap();
@ -279,8 +292,7 @@ fn main() {
None,
)
.expect("second Rust repro write");
let json2 =
std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
let json2 = std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
assert_eq!(
json1, json2,
@ -325,24 +337,39 @@ fn main() {
let entry_src = "function login(username) { console.log(username); }\n";
let artifact1 = repro::write(
&spec, &opts, &outcome, &verdict,
"// harness js\n", entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--", "sqli-union-nyx", None,
).expect("first JS repro write");
let json1 =
std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
&spec,
&opts,
&outcome,
&verdict,
"// harness js\n",
entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
)
.expect("first JS repro write");
let json1 = std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
std::fs::remove_dir_all(&artifact1.root).unwrap();
let artifact2 = repro::write(
&spec, &opts, &outcome, &verdict,
"// harness js\n", entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--", "sqli-union-nyx", None,
).expect("second JS repro write");
let json2 =
std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
&spec,
&opts,
&outcome,
&verdict,
"// harness js\n",
entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
)
.expect("second JS repro write");
let json2 = std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
assert_eq!(json1, json2, "JS outcome.json must be byte-identical across two writes");
assert_eq!(
json1, json2,
"JS outcome.json must be byte-identical across two writes"
);
unsafe { std::env::remove_var("NYX_REPRO_BASE") };
}
@ -382,24 +409,39 @@ fn main() {
let entry_src = "package entry\nfunc Login(username string) {}\n";
let artifact1 = repro::write(
&spec, &opts, &outcome, &verdict,
"// harness go\n", entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--", "sqli-union-nyx", None,
).expect("first Go repro write");
let json1 =
std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
&spec,
&opts,
&outcome,
&verdict,
"// harness go\n",
entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
)
.expect("first Go repro write");
let json1 = std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
std::fs::remove_dir_all(&artifact1.root).unwrap();
let artifact2 = repro::write(
&spec, &opts, &outcome, &verdict,
"// harness go\n", entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--", "sqli-union-nyx", None,
).expect("second Go repro write");
let json2 =
std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
&spec,
&opts,
&outcome,
&verdict,
"// harness go\n",
entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
)
.expect("second Go repro write");
let json2 = std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
assert_eq!(json1, json2, "Go outcome.json must be byte-identical across two writes");
assert_eq!(
json1, json2,
"Go outcome.json must be byte-identical across two writes"
);
unsafe { std::env::remove_var("NYX_REPRO_BASE") };
}
@ -439,24 +481,39 @@ fn main() {
let entry_src = "public class Entry { public static void login(String u) {} }\n";
let artifact1 = repro::write(
&spec, &opts, &outcome, &verdict,
"// NyxHarness.java\n", entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--", "sqli-union-nyx", None,
).expect("first Java repro write");
let json1 =
std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
&spec,
&opts,
&outcome,
&verdict,
"// NyxHarness.java\n",
entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
)
.expect("first Java repro write");
let json1 = std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
std::fs::remove_dir_all(&artifact1.root).unwrap();
let artifact2 = repro::write(
&spec, &opts, &outcome, &verdict,
"// NyxHarness.java\n", entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--", "sqli-union-nyx", None,
).expect("second Java repro write");
let json2 =
std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
&spec,
&opts,
&outcome,
&verdict,
"// NyxHarness.java\n",
entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
)
.expect("second Java repro write");
let json2 = std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
assert_eq!(json1, json2, "Java outcome.json must be byte-identical across two writes");
assert_eq!(
json1, json2,
"Java outcome.json must be byte-identical across two writes"
);
unsafe { std::env::remove_var("NYX_REPRO_BASE") };
}
@ -496,24 +553,39 @@ fn main() {
let entry_src = "<?php\nfunction login($username) {}\n";
let artifact1 = repro::write(
&spec, &opts, &outcome, &verdict,
"<?php // harness\n", entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--", "sqli-union-nyx", None,
).expect("first PHP repro write");
let json1 =
std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
&spec,
&opts,
&outcome,
&verdict,
"<?php // harness\n",
entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
)
.expect("first PHP repro write");
let json1 = std::fs::read_to_string(artifact1.root.join("expected/outcome.json")).unwrap();
std::fs::remove_dir_all(&artifact1.root).unwrap();
let artifact2 = repro::write(
&spec, &opts, &outcome, &verdict,
"<?php // harness\n", entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--", "sqli-union-nyx", None,
).expect("second PHP repro write");
let json2 =
std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
&spec,
&opts,
&outcome,
&verdict,
"<?php // harness\n",
entry_src,
b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
"sqli-union-nyx",
None,
)
.expect("second PHP repro write");
let json2 = std::fs::read_to_string(artifact2.root.join("expected/outcome.json")).unwrap();
assert_eq!(json1, json2, "PHP outcome.json must be byte-identical across two writes");
assert_eq!(
json1, json2,
"PHP outcome.json must be byte-identical across two writes"
);
unsafe { std::env::remove_var("NYX_REPRO_BASE") };
}
@ -530,9 +602,17 @@ fn main() {
let verdict = make_confirmed_verdict("determinism00003");
let artifact = repro::write(
&spec, &opts, &outcome, &verdict,
"# harness", "# entry", b"payload", "label", None,
).expect("repro write must succeed");
&spec,
&opts,
&outcome,
&verdict,
"# harness",
"# entry",
b"payload",
"label",
None,
)
.expect("repro write must succeed");
let verdict_json =
std::fs::read_to_string(artifact.root.join("expected/verdict.json")).unwrap();

View file

@ -21,10 +21,10 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::repro::{self, replay_bundle, ReplayResult};
use nyx_scanner::dynamic::repro::{self, ReplayResult, replay_bundle};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions, SandboxOutcome};
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
use nyx_scanner::dynamic::spec::SpecDerivationStrategy;
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
use nyx_scanner::evidence::{AttemptSummary, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;
@ -123,8 +123,7 @@ fn flask_eval_verdict() -> VerifyResult {
reason: None,
inconclusive_reason: None,
detail: Some(
"flask_eval chain composer fixture: eval(NYX_PAYLOAD) under python-3.11"
.into(),
"flask_eval chain composer fixture: eval(NYX_PAYLOAD) under python-3.11".into(),
),
attempts: vec![AttemptSummary {
payload_label: FLASK_EVAL_PAYLOAD_LABEL.into(),
@ -167,10 +166,8 @@ fn flask_eval_bundle_root() -> PathBuf {
}
fn read_json(path: &Path) -> serde_json::Value {
let bytes = std::fs::read(path)
.unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
serde_json::from_slice(&bytes)
.unwrap_or_else(|e| panic!("parse {}: {e}", path.display()))
let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
serde_json::from_slice(&bytes).unwrap_or_else(|e| panic!("parse {}: {e}", path.display()))
}
/// Regenerate the committed flask_eval bundle. Run with `--ignored` to
@ -206,8 +203,7 @@ fn regen_python_3_11_flask_eval_bundle() {
}
assert_eq!(
artifact.root,
bundle_root,
artifact.root, bundle_root,
"bundle wrote to unexpected path",
);
}

View file

@ -29,7 +29,7 @@
#[cfg(feature = "dynamic")]
mod repro_hermetic_tests {
use nyx_scanner::dynamic::repro;
use nyx_scanner::dynamic::repro::{replay_bundle, ReplayResult};
use nyx_scanner::dynamic::repro::{ReplayResult, replay_bundle};
use nyx_scanner::dynamic::sandbox::{SandboxOptions, SandboxOutcome};
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
use nyx_scanner::evidence::{AttemptSummary, VerifyResult, VerifyStatus};
@ -110,7 +110,8 @@ mod repro_hermetic_tests {
b"' OR 1=1-- NYX",
"sqli-or-1",
None,
).unwrap();
)
.unwrap();
let lock_path = artifact.root.join("toolchain.lock");
assert!(lock_path.exists(), "toolchain.lock missing from bundle");
@ -135,10 +136,16 @@ mod repro_hermetic_tests {
b"' OR 1=1-- NYX",
"sqli-or-1",
None,
).unwrap();
let lock2: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(artifact2.root.join("toolchain.lock")).unwrap()).unwrap();
assert_eq!(lock["files"], lock2["files"], "lock file hashes must be deterministic");
)
.unwrap();
let lock2: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(artifact2.root.join("toolchain.lock")).unwrap(),
)
.unwrap();
assert_eq!(
lock["files"], lock2["files"],
"lock file hashes must be deterministic"
);
unsafe { std::env::remove_var("NYX_REPRO_BASE") };
}
@ -162,7 +169,8 @@ mod repro_hermetic_tests {
b"payload",
"label",
None,
).unwrap();
)
.unwrap();
// Simulate "no language toolchain installed" by stripping PATH
// down to /usr/bin (where `sh`, `grep`, `cat` live) before
@ -188,15 +196,14 @@ mod repro_hermetic_tests {
// running the (broken) harness. Detect that and skip — Phase
// 28 acceptance is about the refusal path, not the host-has-it
// path.
let host_has_python =
std::process::Command::new("sh")
.arg("-c")
.arg("command -v python3")
.env_clear()
.env("PATH", &minimal_path)
.output()
.map(|o| o.status.success())
.unwrap_or(false);
let host_has_python = std::process::Command::new("sh")
.arg("-c")
.arg("command -v python3")
.env_clear()
.env("PATH", &minimal_path)
.output()
.map(|o| o.status.success())
.unwrap_or(false);
if host_has_python {
eprintln!("skip: host has python3 in minimal PATH; cannot simulate clean CI image");
return;
@ -234,14 +241,16 @@ mod repro_hermetic_tests {
std::fs::write(
bundle.join("reproduce.sh"),
"#!/bin/sh\necho 'host toolchain missing' >&2\nexit 3\n",
).unwrap();
)
.unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(
bundle.join("reproduce.sh"),
std::fs::Permissions::from_mode(0o755),
).unwrap();
)
.unwrap();
}
assert_eq!(replay_bundle(&bundle, &[]), ReplayResult::ToolchainMismatch);
}
@ -254,14 +263,16 @@ mod repro_hermetic_tests {
std::fs::write(
bundle.join("reproduce.sh"),
"#!/bin/sh\necho 'PASS: simulated green'\nexit 0\n",
).unwrap();
)
.unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(
bundle.join("reproduce.sh"),
std::fs::Permissions::from_mode(0o755),
).unwrap();
)
.unwrap();
}
assert_eq!(replay_bundle(&bundle, &[]), ReplayResult::Pass);
}
@ -284,11 +295,15 @@ mod repro_hermetic_tests {
&SandboxOptions::default(),
&make_outcome(),
&make_verdict(),
"# harness", "# entry", b"payload", "label", None,
).unwrap();
"# harness",
"# entry",
b"payload",
"label",
None,
)
.unwrap();
let pinned =
nyx_scanner::dynamic::toolchain::pinned_image_ref(&spec.toolchain_id);
let pinned = nyx_scanner::dynamic::toolchain::pinned_image_ref(&spec.toolchain_id);
if pinned.is_some() {
assert!(
artifact.root.join("docker_pull.sh").exists(),

View file

@ -13,7 +13,7 @@ mod common;
#[cfg(feature = "dynamic")]
mod phase15_shape_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -77,8 +77,13 @@ mod phase15_shape_tests {
#[test]
fn sinatra_route_vuln_is_confirmed() {
let Some(r) = run(
"sinatra_route", "vuln.rb", "run", Cap::CODE_EXEC, 7,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"sinatra_route",
"vuln.rb",
"run",
Cap::CODE_EXEC,
7,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -88,8 +93,13 @@ mod phase15_shape_tests {
#[test]
fn sinatra_route_benign_not_confirmed() {
let Some(r) = run(
"sinatra_route", "benign.rb", "run", Cap::CODE_EXEC, 10,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"sinatra_route",
"benign.rb",
"run",
Cap::CODE_EXEC,
10,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -101,8 +111,13 @@ mod phase15_shape_tests {
#[test]
fn rails_action_vuln_is_confirmed() {
let Some(r) = run(
"rails_action", "vuln.rb", "index", Cap::CODE_EXEC, 17,
EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"rails_action",
"vuln.rb",
"index",
Cap::CODE_EXEC,
17,
EntryKind::HttpRoute,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
) else {
return;
};
@ -112,8 +127,13 @@ mod phase15_shape_tests {
#[test]
fn rails_action_benign_not_confirmed() {
let Some(r) = run(
"rails_action", "benign.rb", "index", Cap::CODE_EXEC, 20,
EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"rails_action",
"benign.rb",
"index",
Cap::CODE_EXEC,
20,
EntryKind::HttpRoute,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
) else {
return;
};
@ -125,8 +145,13 @@ mod phase15_shape_tests {
#[test]
fn rack_middleware_vuln_is_confirmed() {
let Some(r) = run(
"rack_middleware", "vuln.rb", "call", Cap::CODE_EXEC, 9,
EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"rack_middleware",
"vuln.rb",
"call",
Cap::CODE_EXEC,
9,
EntryKind::HttpRoute,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
) else {
return;
};
@ -136,8 +161,13 @@ mod phase15_shape_tests {
#[test]
fn rack_middleware_benign_not_confirmed() {
let Some(r) = run(
"rack_middleware", "benign.rb", "call", Cap::CODE_EXEC, 11,
EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
"rack_middleware",
"benign.rb",
"call",
Cap::CODE_EXEC,
11,
EntryKind::HttpRoute,
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
) else {
return;
};
@ -149,8 +179,13 @@ mod phase15_shape_tests {
#[test]
fn controller_method_vuln_is_confirmed() {
let Some(r) = run(
"controller_method", "vuln.rb", "authenticate", Cap::CODE_EXEC, 7,
EntryKind::Function, PayloadSlot::Param(0),
"controller_method",
"vuln.rb",
"authenticate",
Cap::CODE_EXEC,
7,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
@ -160,8 +195,13 @@ mod phase15_shape_tests {
#[test]
fn controller_method_benign_not_confirmed() {
let Some(r) = run(
"controller_method", "benign.rb", "authenticate", Cap::CODE_EXEC, 10,
EntryKind::Function, PayloadSlot::Param(0),
"controller_method",
"benign.rb",
"authenticate",
Cap::CODE_EXEC,
10,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};

View file

@ -11,7 +11,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::framework::{detect_binding, HttpMethod, ParamSource};
use nyx_scanner::dynamic::framework::{HttpMethod, ParamSource, detect_binding};
use nyx_scanner::evidence::EntryKind;
use nyx_scanner::summary::FuncSummary;
use nyx_scanner::symbol::Lang;
@ -155,8 +155,8 @@ fn sinatra_does_not_fire_on_rails_controller() {
let bytes = std::fs::read(path).expect("rails vuln fixture exists");
let tree = parse_ruby(&bytes);
let summary = summary_for("index", path);
let binding = detect_binding(&summary, tree.root_node(), &bytes, Lang::Ruby)
.expect("adapter binds");
let binding =
detect_binding(&summary, tree.root_node(), &bytes, Lang::Ruby).expect("adapter binds");
// First-match-wins ordering must produce `ruby-rails`, not
// `ruby-sinatra`, even if both adapters could in theory match.
assert_eq!(binding.adapter, "ruby-rails");

View file

@ -12,18 +12,21 @@ mod common;
#[cfg(feature = "dynamic")]
mod rust_fixture_tests {
use crate::common::fixture_harness::{
run_fixture_and_compare_to_golden, CopyStrategy, FixtureSpec, Prerequisite,
CopyStrategy, FixtureSpec, Prerequisite, run_fixture_and_compare_to_golden,
};
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind,
};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{Confidence, Evidence, FlowStep, FlowStepKind};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
use std::path::{Path, PathBuf};
fn spec(fixture: &'static str, func: &'static str, cap: Cap, sink_line: u32) -> FixtureSpec<'static> {
fn spec(
fixture: &'static str,
func: &'static str,
cap: Cap,
sink_line: u32,
) -> FixtureSpec<'static> {
FixtureSpec {
lang_dir: "rust",
fixture,
@ -290,7 +293,7 @@ mod rust_fixture_tests {
#[cfg(feature = "dynamic")]
mod phase16_shape_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -357,8 +360,13 @@ mod phase16_shape_tests {
#[test]
fn actix_route_vuln_is_confirmed() {
let Some(r) = run(
"actix_route", "vuln.rs", "handler", Cap::CODE_EXEC, 16,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"actix_route",
"vuln.rs",
"handler",
Cap::CODE_EXEC,
16,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -368,8 +376,13 @@ mod phase16_shape_tests {
#[test]
fn actix_route_benign_not_confirmed() {
let Some(r) = run(
"actix_route", "benign.rs", "handler", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"actix_route",
"benign.rs",
"handler",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -381,8 +394,13 @@ mod phase16_shape_tests {
#[test]
fn axum_handler_vuln_is_confirmed() {
let Some(r) = run(
"axum_handler", "vuln.rs", "handler", Cap::CODE_EXEC, 15,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"axum_handler",
"vuln.rs",
"handler",
Cap::CODE_EXEC,
15,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -392,8 +410,13 @@ mod phase16_shape_tests {
#[test]
fn axum_handler_benign_not_confirmed() {
let Some(r) = run(
"axum_handler", "benign.rs", "handler", Cap::CODE_EXEC, 13,
EntryKind::HttpRoute, PayloadSlot::Param(0),
"axum_handler",
"benign.rs",
"handler",
Cap::CODE_EXEC,
13,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
return;
};
@ -405,8 +428,13 @@ mod phase16_shape_tests {
#[test]
fn clap_cli_vuln_is_confirmed() {
let Some(r) = run(
"clap_cli", "vuln.rs", "run", Cap::CODE_EXEC, 17,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"clap_cli",
"vuln.rs",
"run",
Cap::CODE_EXEC,
17,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
@ -416,8 +444,13 @@ mod phase16_shape_tests {
#[test]
fn clap_cli_benign_not_confirmed() {
let Some(r) = run(
"clap_cli", "benign.rs", "run", Cap::CODE_EXEC, 13,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
"clap_cli",
"benign.rs",
"run",
Cap::CODE_EXEC,
13,
EntryKind::CliSubcommand,
PayloadSlot::Argv(0),
) else {
return;
};
@ -429,8 +462,13 @@ mod phase16_shape_tests {
#[test]
fn libfuzzer_target_vuln_is_confirmed() {
let Some(r) = run(
"libfuzzer_target", "vuln.rs", "fuzz_target", Cap::CODE_EXEC, 15,
EntryKind::LibraryApi, PayloadSlot::Param(0),
"libfuzzer_target",
"vuln.rs",
"fuzz_target",
Cap::CODE_EXEC,
15,
EntryKind::LibraryApi,
PayloadSlot::Param(0),
) else {
return;
};
@ -440,8 +478,13 @@ mod phase16_shape_tests {
#[test]
fn libfuzzer_target_benign_not_confirmed() {
let Some(r) = run(
"libfuzzer_target", "benign.rs", "fuzz_target", Cap::CODE_EXEC, 13,
EntryKind::LibraryApi, PayloadSlot::Param(0),
"libfuzzer_target",
"benign.rs",
"fuzz_target",
Cap::CODE_EXEC,
13,
EntryKind::LibraryApi,
PayloadSlot::Param(0),
) else {
return;
};

View file

@ -11,7 +11,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::framework::{detect_binding, HttpMethod};
use nyx_scanner::dynamic::framework::{HttpMethod, detect_binding};
use nyx_scanner::evidence::EntryKind;
use nyx_scanner::summary::FuncSummary;
use nyx_scanner::symbol::Lang;

View file

@ -16,8 +16,8 @@
use nyx_scanner::dynamic::harness::BuiltHarness;
use nyx_scanner::dynamic::sandbox::docker::{
ensure_image_pulled, image_reference_for_toolchain, network_args, stub_mount_args,
toolchain_is_pinned, workdir_mount_args, STUB_MOUNT_ROOT, WORK_MOUNT_PATH,
STUB_MOUNT_ROOT, WORK_MOUNT_PATH, ensure_image_pulled, image_reference_for_toolchain,
network_args, stub_mount_args, toolchain_is_pinned, workdir_mount_args,
};
use nyx_scanner::dynamic::sandbox::{
self, HostPort, NetworkPolicy, SandboxBackend, SandboxOptions,
@ -87,12 +87,20 @@ fn stub_mount_args_uses_indexed_fixed_paths() {
#[test]
fn network_args_translate_every_policy() {
assert!(network_args(&NetworkPolicy::None).iter().any(|a| a == "none"));
assert!(
network_args(&NetworkPolicy::None)
.iter()
.any(|a| a == "none")
);
let stubs = NetworkPolicy::StubsOnly {
allow: vec![HostPort::new("sql", 5432)],
};
let stubs_args = network_args(&stubs);
assert!(stubs_args.iter().any(|a| a == "--add-host=sql:host-gateway"));
assert!(
stubs_args
.iter()
.any(|a| a == "--add-host=sql:host-gateway")
);
let open = network_args(&NetworkPolicy::Open);
assert!(open.iter().any(|a| a == "bridge"));
assert!(!open.iter().any(|a| a.starts_with("--add-host=")));
@ -117,9 +125,15 @@ fn toolchain_pinning_state_is_observable() {
let pinned = toolchain_is_pinned("python-3.11");
let r = image_reference_for_toolchain("python-3.11").unwrap();
if pinned {
assert!(r.contains("@sha256:"), "pinned ref must carry digest, got {r}");
assert!(
r.contains("@sha256:"),
"pinned ref must carry digest, got {r}"
);
} else {
assert!(!r.contains("@sha256:"), "unpinned ref must not carry digest, got {r}");
assert!(
!r.contains("@sha256:"),
"unpinned ref must not carry digest, got {r}"
);
}
}
@ -131,8 +145,8 @@ fn ensure_image_pulled_returns_true_for_python_slim() {
eprintln!("docker unavailable — skipping");
return;
}
let r = image_reference_for_toolchain("python-3.11")
.expect("python-3.11 must be in the catalogue");
let r =
image_reference_for_toolchain("python-3.11").expect("python-3.11 must be in the catalogue");
assert!(
ensure_image_pulled(r),
"ensure_image_pulled must succeed for `{r}` when docker is available",
@ -170,8 +184,7 @@ fn harness_workdir_is_mounted_at_fixed_work_path() {
return;
}
let tmp = tempfile::TempDir::new().expect("tempdir");
std::fs::write(tmp.path().join("token.txt"), "phase-19-mount-token\n")
.expect("write fixture");
std::fs::write(tmp.path().join("token.txt"), "phase-19-mount-token\n").expect("write fixture");
write_harness_script(
tmp.path(),
// Read from the fixed /work mount path — this passes only when the

View file

@ -155,7 +155,10 @@ mod escape_suite {
unsafe { std::env::set_var(format!("NYX_ESCAPE_DYN_{technique}_{variant}"), "1") };
}
builds().lock().unwrap().insert(key.clone(), Some(out_bin.clone()));
builds()
.lock()
.unwrap()
.insert(key.clone(), Some(out_bin.clone()));
Some(out_bin)
}
@ -291,34 +294,58 @@ mod escape_suite {
// keep the build dependency-free.
#[test]
fn chmod_4755_benign() { let _ = assert_contained("chmod_4755", "benign"); }
fn chmod_4755_benign() {
let _ = assert_contained("chmod_4755", "benign");
}
#[test]
fn chmod_4755_vuln() { let _ = assert_contained("chmod_4755", "vuln"); }
fn chmod_4755_vuln() {
let _ = assert_contained("chmod_4755", "vuln");
}
#[test]
fn etc_write_benign() { let _ = assert_contained("etc_write", "benign"); }
fn etc_write_benign() {
let _ = assert_contained("etc_write", "benign");
}
#[test]
fn etc_write_vuln() { let _ = assert_contained("etc_write", "vuln"); }
fn etc_write_vuln() {
let _ = assert_contained("etc_write", "vuln");
}
#[test]
fn dlopen_outside_chroot_benign() { let _ = assert_contained("dlopen_outside_chroot", "benign"); }
fn dlopen_outside_chroot_benign() {
let _ = assert_contained("dlopen_outside_chroot", "benign");
}
#[test]
fn dlopen_outside_chroot_vuln() { let _ = assert_contained("dlopen_outside_chroot", "vuln"); }
fn dlopen_outside_chroot_vuln() {
let _ = assert_contained("dlopen_outside_chroot", "vuln");
}
#[test]
fn proc_root_passwd_benign() { let _ = assert_contained("proc_root_passwd", "benign"); }
fn proc_root_passwd_benign() {
let _ = assert_contained("proc_root_passwd", "benign");
}
#[test]
fn proc_root_passwd_vuln() { let _ = assert_contained("proc_root_passwd", "vuln"); }
fn proc_root_passwd_vuln() {
let _ = assert_contained("proc_root_passwd", "vuln");
}
#[test]
fn raw_socket_bind_benign() { let _ = assert_contained("raw_socket_bind", "benign"); }
fn raw_socket_bind_benign() {
let _ = assert_contained("raw_socket_bind", "benign");
}
#[test]
fn raw_socket_bind_vuln() { let _ = assert_contained("raw_socket_bind", "vuln"); }
fn raw_socket_bind_vuln() {
let _ = assert_contained("raw_socket_bind", "vuln");
}
#[test]
fn setuid_zero_benign() { let _ = assert_contained("setuid_zero", "benign"); }
fn setuid_zero_benign() {
let _ = assert_contained("setuid_zero", "benign");
}
#[test]
fn setuid_zero_vuln() { let _ = assert_contained("setuid_zero", "vuln"); }
fn setuid_zero_vuln() {
let _ = assert_contained("setuid_zero", "vuln");
}
// ── Track-B regression tripwire ──────────────────────────────────────────

View file

@ -27,9 +27,9 @@ mod hardening_tests {
self, HardeningRecord, ProcessHardeningProfile, SandboxBackend, SandboxOptions,
};
fn linux_outcome(out: &sandbox::SandboxOutcome)
-> Option<nyx_scanner::dynamic::sandbox::process_linux::HardeningOutcome>
{
fn linux_outcome(
out: &sandbox::SandboxOutcome,
) -> Option<nyx_scanner::dynamic::sandbox::process_linux::HardeningOutcome> {
match out.hardening_outcome.as_ref()? {
HardeningRecord::Linux(o) => Some(*o),
#[allow(unreachable_patterns)]
@ -43,9 +43,7 @@ mod hardening_tests {
static PROBE_BINARY: OnceLock<Option<PathBuf>> = OnceLock::new();
fn probe_path() -> Option<&'static Path> {
PROBE_BINARY
.get_or_init(|| build_probe_once())
.as_deref()
PROBE_BINARY.get_or_init(|| build_probe_once()).as_deref()
}
fn build_probe_once() -> Option<PathBuf> {
@ -310,7 +308,9 @@ mod hardening_tests {
fn chroot_blocks_etc_passwd() {
let Some(_) = probe_path() else { return };
if !probe_is_static() {
eprintln!("SKIP: probe is dynamically linked — chroot would block its loader before main()");
eprintln!(
"SKIP: probe is dynamically linked — chroot would block its loader before main()"
);
return;
}
let tmp = workdir();
@ -372,7 +372,8 @@ mod hardening_tests {
"sink hit should be absent on a traversal-blocked run"
);
assert!(
stdout.contains("chroot blocked") || stdout.contains("chroot:blocked")
stdout.contains("chroot blocked")
|| stdout.contains("chroot:blocked")
|| stdout.contains("traverse:blocked"),
"expected `chroot blocked` marker in probe stdout; got:\n{stdout}"
);
@ -505,10 +506,8 @@ mod hardening_tests {
}
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus,
};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
use nyx_scanner::utils::config::Config;
@ -521,10 +520,7 @@ mod hardening_tests {
std::fs::copy(&fixture_src, &dst).expect("stage fixture into tempdir");
unsafe {
std::env::set_var(
"NYX_REPRO_BASE",
tmp.path().join("repro").to_str().unwrap(),
);
std::env::set_var("NYX_REPRO_BASE", tmp.path().join("repro").to_str().unwrap());
std::env::set_var(
"NYX_TELEMETRY_PATH",
tmp.path().join("events.jsonl").to_str().unwrap(),
@ -688,10 +684,8 @@ mod hardening_tests {
}
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus,
};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
use nyx_scanner::utils::config::Config;
@ -704,10 +698,7 @@ mod hardening_tests {
std::fs::copy(&fixture_src, &dst).expect("stage fixture into tempdir");
unsafe {
std::env::set_var(
"NYX_REPRO_BASE",
tmp.path().join("repro").to_str().unwrap(),
);
std::env::set_var("NYX_REPRO_BASE", tmp.path().join("repro").to_str().unwrap());
std::env::set_var(
"NYX_TELEMETRY_PATH",
tmp.path().join("events.jsonl").to_str().unwrap(),
@ -871,4 +862,3 @@ mod non_linux_placeholder {
);
}
}

View file

@ -21,17 +21,16 @@ mod hardening_tests {
use nyx_scanner::dynamic::harness::BuiltHarness;
use nyx_scanner::dynamic::sandbox::process_macos::{
clear_profile_path_cache_for_tests, profile_for_caps, profile_path,
sandbox_exec_available, HardeningLevel, SANDBOX_EXEC_BIN_ENV, SB_DENY_DEFAULT_ENV,
SB_SEED_DIR_ENV,
HardeningLevel, SANDBOX_EXEC_BIN_ENV, SB_DENY_DEFAULT_ENV, SB_SEED_DIR_ENV,
clear_profile_path_cache_for_tests, profile_for_caps, profile_path, sandbox_exec_available,
};
use nyx_scanner::dynamic::sandbox::{
self, HardeningRecord, ProcessHardeningProfile, SandboxBackend, SandboxOptions,
};
fn macos_outcome(out: &sandbox::SandboxOutcome)
-> Option<&nyx_scanner::dynamic::sandbox::process_macos::HardeningOutcome>
{
fn macos_outcome(
out: &sandbox::SandboxOutcome,
) -> Option<&nyx_scanner::dynamic::sandbox::process_macos::HardeningOutcome> {
match out.hardening_outcome.as_ref()? {
HardeningRecord::Macos(o) => Some(o),
#[allow(unreachable_patterns)]
@ -120,8 +119,7 @@ except Exception as exc:
/// the harness workdir at run time so the sandbox-exec narrow
/// `/Users/<user>/Library/...` denies cannot accidentally shadow a
/// home-relative script-load path.
const XXE_PROBE_SOURCE: &str =
include_str!("dynamic_fixtures/hardening/xxe_probe.py");
const XXE_PROBE_SOURCE: &str = include_str!("dynamic_fixtures/hardening/xxe_probe.py");
fn write_xxe_probe(workdir: &Path) -> PathBuf {
let path = workdir.join("xxe_probe.py");
@ -427,10 +425,8 @@ except Exception as exc:
}
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus,
};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
use nyx_scanner::utils::config::Config;
@ -443,10 +439,7 @@ except Exception as exc:
std::fs::copy(&fixture_src, &dst).expect("stage fixture into tempdir");
unsafe {
std::env::set_var(
"NYX_REPRO_BASE",
tmp.path().join("repro").to_str().unwrap(),
);
std::env::set_var("NYX_REPRO_BASE", tmp.path().join("repro").to_str().unwrap());
std::env::set_var(
"NYX_TELEMETRY_PATH",
tmp.path().join("events.jsonl").to_str().unwrap(),
@ -562,10 +555,8 @@ except Exception as exc:
}
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus,
};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{Confidence, Evidence, FlowStep, FlowStepKind, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
use nyx_scanner::utils::config::Config;
@ -578,10 +569,7 @@ except Exception as exc:
std::fs::copy(&fixture_src, &dst).expect("stage fixture into tempdir");
unsafe {
std::env::set_var(
"NYX_REPRO_BASE",
tmp.path().join("repro").to_str().unwrap(),
);
std::env::set_var("NYX_REPRO_BASE", tmp.path().join("repro").to_str().unwrap());
std::env::set_var(
"NYX_TELEMETRY_PATH",
tmp.path().join("events.jsonl").to_str().unwrap(),
@ -786,7 +774,7 @@ except Exception as exc:
/// downstream replay reads the same fields back.
#[test]
fn hardening_summary_round_trips_through_json() {
use nyx_scanner::evidence::{HardeningSummary, HardeningPrimitive};
use nyx_scanner::evidence::{HardeningPrimitive, HardeningSummary};
let summary = HardeningSummary {
backend: "macos-process".into(),
level: "sandboxed".into(),

View file

@ -82,8 +82,7 @@ fn sarif_confirmed_verdict_sets_partial_fingerprint() {
let result = sarif_result(diag_with_verdict(verdict));
assert_eq!(
result["partialFingerprints"]["dynamic_verdict_status"],
"Confirmed",
result["partialFingerprints"]["dynamic_verdict_status"], "Confirmed",
"partialFingerprints.dynamic_verdict_status must be 'Confirmed'"
);
assert!(
@ -92,8 +91,7 @@ fn sarif_confirmed_verdict_sets_partial_fingerprint() {
result["properties"]["nyx_dynamic_verdict"]
);
assert_eq!(
result["properties"]["nyx_dynamic_verdict"]["status"],
"Confirmed",
result["properties"]["nyx_dynamic_verdict"]["status"], "Confirmed",
"nyx_dynamic_verdict.status must be 'Confirmed'"
);
}
@ -118,8 +116,7 @@ fn sarif_not_confirmed_verdict_sets_partial_fingerprint() {
let result = sarif_result(diag_with_verdict(verdict));
assert_eq!(
result["partialFingerprints"]["dynamic_verdict_status"],
"NotConfirmed",
result["partialFingerprints"]["dynamic_verdict_status"], "NotConfirmed",
"partialFingerprints.dynamic_verdict_status must be 'NotConfirmed'"
);
assert!(
@ -148,8 +145,7 @@ fn sarif_unsupported_verdict_sets_partial_fingerprint() {
let result = sarif_result(diag_with_verdict(verdict));
assert_eq!(
result["partialFingerprints"]["dynamic_verdict_status"],
"Unsupported",
result["partialFingerprints"]["dynamic_verdict_status"], "Unsupported",
"partialFingerprints.dynamic_verdict_status must be 'Unsupported'"
);
assert!(
@ -157,8 +153,7 @@ fn sarif_unsupported_verdict_sets_partial_fingerprint() {
"properties.nyx_dynamic_verdict must be an object"
);
assert_eq!(
result["properties"]["nyx_dynamic_verdict"]["reason"],
"NoPayloadsForCap",
result["properties"]["nyx_dynamic_verdict"]["reason"], "NoPayloadsForCap",
"nyx_dynamic_verdict must carry the unsupported reason"
);
}
@ -183,8 +178,7 @@ fn sarif_inconclusive_verdict_sets_partial_fingerprint() {
let result = sarif_result(diag_with_verdict(verdict));
assert_eq!(
result["partialFingerprints"]["dynamic_verdict_status"],
"Inconclusive",
result["partialFingerprints"]["dynamic_verdict_status"], "Inconclusive",
"partialFingerprints.dynamic_verdict_status must be 'Inconclusive'"
);
assert!(
@ -192,8 +186,7 @@ fn sarif_inconclusive_verdict_sets_partial_fingerprint() {
"properties.nyx_dynamic_verdict must be an object"
);
assert_eq!(
result["properties"]["nyx_dynamic_verdict"]["inconclusive_reason"],
"BuildFailed",
result["properties"]["nyx_dynamic_verdict"]["inconclusive_reason"], "BuildFailed",
"nyx_dynamic_verdict must carry the inconclusive reason"
);
}
@ -204,12 +197,14 @@ fn sarif_no_dynamic_verdict_omits_both_keys() {
let result = sarif_result(diag);
assert!(
result["partialFingerprints"].is_null() || result["partialFingerprints"] == serde_json::Value::Null,
result["partialFingerprints"].is_null()
|| result["partialFingerprints"] == serde_json::Value::Null,
"partialFingerprints must be absent when no dynamic verdict: {}",
result["partialFingerprints"]
);
assert!(
result["properties"]["nyx_dynamic_verdict"].is_null() || result["properties"]["nyx_dynamic_verdict"] == serde_json::Value::Null,
result["properties"]["nyx_dynamic_verdict"].is_null()
|| result["properties"]["nyx_dynamic_verdict"] == serde_json::Value::Null,
"properties.nyx_dynamic_verdict must be absent when no dynamic verdict"
);
}
@ -234,8 +229,7 @@ fn sarif_confirmed_verdict_nyx_dynamic_verdict_contains_triggered_payload() {
let result = sarif_result(diag_with_verdict(verdict));
assert_eq!(
result["properties"]["nyx_dynamic_verdict"]["triggered_payload"],
"cmd-injection-semicolon",
result["properties"]["nyx_dynamic_verdict"]["triggered_payload"], "cmd-injection-semicolon",
"triggered_payload must appear in nyx_dynamic_verdict"
);
}
@ -268,8 +262,7 @@ fn sarif_all_four_statuses_produce_partial_fingerprint() {
let result = sarif_result(diag_with_verdict(verdict));
assert_eq!(
result["partialFingerprints"]["dynamic_verdict_status"],
expected_str,
result["partialFingerprints"]["dynamic_verdict_status"], expected_str,
"status {expected_str}: partialFingerprints.dynamic_verdict_status mismatch"
);
assert!(

View file

@ -8,7 +8,7 @@
#[cfg(feature = "dynamic")]
mod scrubber_pii_tests {
use nyx_scanner::dynamic::policy::{Scrubber, SCRUB_HASH_PREFIX};
use nyx_scanner::dynamic::policy::{SCRUB_HASH_PREFIX, Scrubber};
use nyx_scanner::dynamic::probe::ProbeWitness;
#[test]
@ -68,7 +68,8 @@ mod scrubber_pii_tests {
#[test]
fn scrubber_recognises_pem_block() {
let s = Scrubber::project_default();
let value = "-----BEGIN RSA PRIVATE KEY-----\nMIIEoQIBAAKCAQ\n-----END RSA PRIVATE KEY-----";
let value =
"-----BEGIN RSA PRIVATE KEY-----\nMIIEoQIBAAKCAQ\n-----END RSA PRIVATE KEY-----";
assert!(s.matches_any(value));
let out = s.scrub_string(value);
assert!(!out.contains("MIIEoQIBAAKCAQ"));
@ -126,10 +127,14 @@ mod scrubber_pii_tests {
);
let serialised = serde_json::to_string(&witness).unwrap();
assert!(!serialised.contains("deadbeef-feedface"),
"raw secret leaked into serialised witness: {serialised}");
assert!(serialised.contains(SCRUB_HASH_PREFIX),
"expected scrubbed-hash marker; got {serialised}");
assert!(
!serialised.contains("deadbeef-feedface"),
"raw secret leaked into serialised witness: {serialised}"
);
assert!(
serialised.contains(SCRUB_HASH_PREFIX),
"expected scrubbed-hash marker; got {serialised}"
);
}
#[test]
@ -137,12 +142,9 @@ mod scrubber_pii_tests {
// An env var keyed past the deny-list (so scrub_env keeps the
// value verbatim) but whose textual value contains a secret
// pattern must still be hashed by the Phase 28 scrubber pass.
let env: Vec<(String, String)> = vec![
("USER_DATA".to_owned(), "AKIAFAKETEST00000000".to_owned()),
];
let witness = ProbeWitness::from_inputs(
env, "/x", b"", "fn", vec![],
);
let env: Vec<(String, String)> =
vec![("USER_DATA".to_owned(), "AKIAFAKETEST00000000".to_owned())];
let witness = ProbeWitness::from_inputs(env, "/x", b"", "fn", vec![]);
let value = witness.env_snapshot.get("USER_DATA").unwrap();
assert!(value.starts_with(SCRUB_HASH_PREFIX), "got {value}");
}

View file

@ -22,7 +22,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::environment::{
build_secret_bag, derive_secret, extract_env_var_references, SECRET_VALUE_PREFIX,
SECRET_VALUE_PREFIX, build_secret_bag, derive_secret, extract_env_var_references,
};
use nyx_scanner::symbol::Lang;
use std::path::{Path, PathBuf};
@ -228,7 +228,10 @@ fn flask_fixture_boots_with_derived_secret_env() {
// Spawn python3 in the fixture directory, env-clear, layer the bag
// on top, and confirm the module imports without raising.
let mut cmd = std::process::Command::new("python3");
cmd.args(["-c", "import sys; sys.path.insert(0, '.'); import app; print('OK')"]);
cmd.args([
"-c",
"import sys; sys.path.insert(0, '.'); import app; print('OK')",
]);
cmd.current_dir(&fixture);
cmd.env_clear();
// PATH is required so python3 can re-locate its stdlib; the

View file

@ -13,7 +13,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::corpus::registry::{
sound_oracle_unavailable_hint, CORPUS_SOUND_ORACLE_UNAVAILABLE,
CORPUS_SOUND_ORACLE_UNAVAILABLE, sound_oracle_unavailable_hint,
};
use nyx_scanner::labels::Cap;

View file

@ -15,11 +15,9 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::ast::analyse_file_fused;
use nyx_scanner::callgraph::{analyse, build_call_graph, CallGraph, CallGraphAnalysis};
use nyx_scanner::callgraph::{CallGraph, CallGraphAnalysis, analyse, build_call_graph};
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::spec::{
is_entry_point, EntryKind, HarnessSpec, SpecDerivationStrategy,
};
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, SpecDerivationStrategy, is_entry_point};
use nyx_scanner::evidence::{Confidence, Evidence, FlowStep, FlowStepKind};
use nyx_scanner::labels::Cap;
use nyx_scanner::patterns::{FindingCategory, Severity};
@ -50,8 +48,7 @@ fn build_context(file: &Path) -> (GlobalSummaries, CallGraph, CallGraphAnalysis)
let root = file.parent().unwrap();
let root_str = root.to_string_lossy();
let bytes = std::fs::read(file).expect("read fixture");
let result = analyse_file_fused(&bytes, file, &cfg, None, Some(root))
.expect("analyse fixture");
let result = analyse_file_fused(&bytes, file, &cfg, None, Some(root)).expect("analyse fixture");
let mut gs = GlobalSummaries::new();
for s in result.summaries {
let key = s.func_key(Some(&root_str));

View file

@ -20,10 +20,10 @@
mod spec_strategies {
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::dynamic::spec::{
derive_from_callgraph_entry, derive_from_func_summary, derive_from_rule_namespace,
EntryKind, EntryKindTag, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
derive_from_callgraph_entry, derive_from_func_summary, derive_from_rule_namespace,
};
use nyx_scanner::dynamic::verify::{verify_finding, VerifyOptions};
use nyx_scanner::dynamic::verify::{VerifyOptions, verify_finding};
use nyx_scanner::evidence::{
Confidence, Evidence, FlowStep, FlowStepKind, InconclusiveReason, UnsupportedReason,
VerifyStatus,
@ -98,7 +98,10 @@ mod spec_strategies {
);
let mut ev = Evidence::default();
ev.flow_steps = vec![
source_step("tests/dynamic_fixtures/spec_strategies/flow_steps_taint.py", "handle_request"),
source_step(
"tests/dynamic_fixtures/spec_strategies/flow_steps_taint.py",
"handle_request",
),
sink_step("tests/dynamic_fixtures/spec_strategies/flow_steps_taint.py"),
];
ev.sink_caps = Cap::SHELL_ESCAPE.bits();
@ -132,11 +135,7 @@ mod spec_strategies {
#[test]
fn from_rule_namespace_called_directly_returns_some() {
let mut diag = make_diag(
"java.deser.readobject",
"src/Main.java",
12,
);
let mut diag = make_diag("java.deser.readobject", "src/Main.java", 12);
let mut ev = Evidence::default();
ev.sink_caps = Cap::DESERIALIZE.bits();
diag.evidence = Some(ev.clone());
@ -212,9 +211,8 @@ mod spec_strategies {
hierarchy_edges: vec![],
entry_kind: None,
};
let spec =
derive_from_func_summary(&diag, diag.evidence.as_ref().unwrap(), Some(&summary))
.expect("summary strategy must succeed");
let spec = derive_from_func_summary(&diag, diag.evidence.as_ref().unwrap(), Some(&summary))
.expect("summary strategy must succeed");
assert_eq!(spec.derivation, SpecDerivationStrategy::FromFuncSummaryWalk);
assert!(matches!(spec.payload_slot, PayloadSlot::Param(1)));
assert_eq!(spec.entry_name, "read_path");
@ -240,11 +238,7 @@ mod spec_strategies {
#[test]
fn from_callgraph_entry_called_directly_returns_some() {
let mut diag = make_diag(
"rs.cli.subcommand_parse",
"src/main.rs",
10,
);
let mut diag = make_diag("rs.cli.subcommand_parse", "src/main.rs", 10);
let mut ev = Evidence::default();
ev.sink_caps = Cap::SHELL_ESCAPE.bits();
diag.evidence = Some(ev.clone());
@ -305,7 +299,10 @@ mod spec_strategies {
);
let mut ev = Evidence::default();
ev.flow_steps = vec![
source_step("tests/dynamic_fixtures/spec_strategies/flow_steps_taint.py", "handle_request"),
source_step(
"tests/dynamic_fixtures/spec_strategies/flow_steps_taint.py",
"handle_request",
),
sink_step("tests/dynamic_fixtures/spec_strategies/flow_steps_taint.py"),
];
ev.sink_caps = Cap::SHELL_ESCAPE.bits();
@ -379,9 +376,7 @@ mod spec_strategies {
"hint must name the attempted entry kind; got {hint:?}"
);
}
other => panic!(
"expected InconclusiveReason::EntryKindUnsupported, got {other:?}"
),
other => panic!("expected InconclusiveReason::EntryKindUnsupported, got {other:?}"),
}
}
}

View file

@ -14,12 +14,12 @@
mod common;
use nyx_scanner::dynamic::corpus::{
audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang, Oracle,
Oracle, audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang,
};
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::oracle::{oracle_fired, ProbePredicate};
use nyx_scanner::dynamic::oracle::{ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
use nyx_scanner::labels::Cap;
@ -72,13 +72,7 @@ fn corpus_registers_ssti_for_every_supported_lang() {
fn ssti_unsupported_caps_unchanged_for_other_langs() {
// Phase 04 only fills Python/Ruby/PHP/Java/JS — TypeScript / Rust /
// C / Cpp / Go remain empty.
for lang in [
Lang::Rust,
Lang::C,
Lang::Cpp,
Lang::Go,
Lang::TypeScript,
] {
for lang in [Lang::Rust, Lang::C, Lang::Cpp, Lang::Go, Lang::TypeScript] {
assert!(
payloads_for_lang(Cap::SSTI, lang).is_empty(),
"unexpected SSTI payloads registered for {lang:?}",
@ -91,8 +85,7 @@ fn benign_control_resolves_within_lang_slice() {
for lang in LANGS {
let slice = payloads_for_lang(Cap::SSTI, *lang);
let vuln = slice.iter().find(|p| !p.is_benign).unwrap();
let resolved =
resolve_benign_control_lang(vuln, Cap::SSTI, *lang).expect("paired control");
let resolved = resolve_benign_control_lang(vuln, Cap::SSTI, *lang).expect("paired control");
assert!(resolved.is_benign);
let direct = benign_payload_for_lang(Cap::SSTI, *lang).unwrap();
assert_eq!(direct.label, resolved.label);
@ -106,9 +99,9 @@ fn payload_oracle_carries_template_eval_predicate() {
let vuln = slice.iter().find(|p| !p.is_benign).unwrap();
match &vuln.oracle {
Oracle::SinkProbe { predicates } => {
let has_predicate = predicates.iter().any(|p| {
matches!(p, ProbePredicate::TemplateEvalEqual { expected: 49 })
});
let has_predicate = predicates
.iter()
.any(|p| matches!(p, ProbePredicate::TemplateEvalEqual { expected: 49 }));
assert!(
has_predicate,
"{lang:?} vuln payload missing TemplateEvalEqual{{expected:49}}",
@ -205,8 +198,8 @@ fn lang_emitter_dispatches_to_ssti_harness() {
),
] {
let spec = make_spec(lang, entry_file, entry_name);
let harness = lang::emit(&spec)
.unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
let harness =
lang::emit(&spec).unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
assert!(
harness.source.contains(marker),
"{lang:?} ssti harness must splice {marker:?}",
@ -277,10 +270,13 @@ fn framework_adapters_detect_ssti_sink() {
.push(nyx_scanner::summary::CalleeSite::bare(sink_callee));
let registry_slice = adapters_for(lang);
assert!(!registry_slice.is_empty(), "{lang:?} adapter slice empty");
let binding =
nyx_scanner::dynamic::framework::detect_binding(&summary, tree.root_node(), &bytes, lang);
let b =
binding.unwrap_or_else(|| panic!("{lang:?} adapter must detect the SSTI fixture"));
let binding = nyx_scanner::dynamic::framework::detect_binding(
&summary,
tree.root_node(),
&bytes,
lang,
);
let b = binding.unwrap_or_else(|| panic!("{lang:?} adapter must detect the SSTI fixture"));
assert_eq!(b.kind, EntryKind::Function);
assert!(!b.adapter.is_empty());
}
@ -292,9 +288,7 @@ fn ts_language_for(lang: Lang) -> tree_sitter::Language {
Lang::Ruby => tree_sitter::Language::from(tree_sitter_ruby::LANGUAGE),
Lang::Php => tree_sitter::Language::from(tree_sitter_php::LANGUAGE_PHP),
Lang::Java => tree_sitter::Language::from(tree_sitter_java::LANGUAGE),
Lang::JavaScript => {
tree_sitter::Language::from(tree_sitter_javascript::LANGUAGE)
}
Lang::JavaScript => tree_sitter::Language::from(tree_sitter_javascript::LANGUAGE),
other => panic!("unsupported test lang {other:?}"),
}
}
@ -338,10 +332,10 @@ fn slug(lang: Lang) -> &'static str {
mod e2e_phase_04 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -454,7 +448,9 @@ mod e2e_phase_04 {
#[test]
fn python_jinja2_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else { return };
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Python Jinja2 SSTI vuln must Confirm via run_spec; got {outcome:?}",
@ -468,7 +464,9 @@ mod e2e_phase_04 {
#[test]
fn ruby_erb_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else { return };
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Ruby ERB SSTI vuln must Confirm via run_spec; got {outcome:?}",
@ -482,7 +480,9 @@ mod e2e_phase_04 {
#[test]
fn php_twig_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else { return };
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"PHP Twig SSTI vuln must Confirm via run_spec; got {outcome:?}",
@ -496,7 +496,9 @@ mod e2e_phase_04 {
#[test]
fn js_handlebars_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else { return };
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"JS Handlebars SSTI vuln must Confirm via run_spec; got {outcome:?}",
@ -510,7 +512,9 @@ mod e2e_phase_04 {
#[test]
fn java_thymeleaf_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "vuln.java", "run") else { return };
let Some(outcome) = run(Lang::Java, "vuln.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Java Thymeleaf SSTI vuln must Confirm via run_spec; got {outcome:?}",

View file

@ -862,8 +862,8 @@ fn go_http_stub_captures_attempted_outbound_via_shim_recorder() {
// Go fragments need wrapping: the file under tests/dynamic_fixtures
// is a body-only fragment, not a standalone program.
let fragment = std::fs::read_to_string(fixture_path("go/http/vuln/main.go"))
.expect("read go fragment");
let fragment =
std::fs::read_to_string(fixture_path("go/http/vuln/main.go")).expect("read go fragment");
let combined = wrap_go_fragment(&fragment, go_probe_shim());
let script_path = workdir.path().join("driver_http.go");
@ -918,8 +918,8 @@ fn go_http_shim_recorder_is_noop_without_log_env() {
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
let endpoint = stub.endpoint();
let fragment = std::fs::read_to_string(fixture_path("go/http/vuln/main.go"))
.expect("read go fragment");
let fragment =
std::fs::read_to_string(fixture_path("go/http/vuln/main.go")).expect("read go fragment");
let combined = wrap_go_fragment(&fragment, go_probe_shim());
let script_path = workdir.path().join("driver_http_no_log.go");
@ -1589,8 +1589,11 @@ fn rust_http_shim_recorder_is_noop_without_log_env() {
let crate_dir = workdir.path().join("driver_no_log");
std::fs::create_dir_all(&crate_dir).expect("create crate dir");
std::fs::write(crate_dir.join("Cargo.toml"), rust_stub_cargo_toml("http_no_log"))
.expect("write Cargo.toml");
std::fs::write(
crate_dir.join("Cargo.toml"),
rust_stub_cargo_toml("http_no_log"),
)
.expect("write Cargo.toml");
std::fs::write(crate_dir.join("main.rs"), source).expect("write main.rs");
let output = Command::new("cargo")
@ -1702,8 +1705,11 @@ fn rust_sql_shim_recorder_is_noop_without_log_env() {
let crate_dir = workdir.path().join("driver_sql_no_log");
std::fs::create_dir_all(&crate_dir).expect("create crate dir");
std::fs::write(crate_dir.join("Cargo.toml"), rust_stub_cargo_toml("sql_no_log"))
.expect("write Cargo.toml");
std::fs::write(
crate_dir.join("Cargo.toml"),
rust_stub_cargo_toml("sql_no_log"),
)
.expect("write Cargo.toml");
std::fs::write(crate_dir.join("main.rs"), source).expect("write main.rs");
let output = Command::new("cargo")

View file

@ -18,9 +18,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::oracle::{
oracle_fired_with_stubs, Oracle, ProbePredicate,
};
use nyx_scanner::dynamic::oracle::{Oracle, ProbePredicate, oracle_fired_with_stubs};
use nyx_scanner::dynamic::probe::{ProbeArg, ProbeChannel, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::dynamic::stubs::{
@ -77,7 +75,10 @@ fn sql_stub_vuln_fixture_confirms_with_captured_query() {
// Synthetic harness: read the vuln fixture, record the executed
// query against the stub, then evaluate the oracle.
let payload = extract_payload(&read_fixture("sql", "vuln.txt"));
assert!(payload.contains("OR 1=1"), "vuln fixture must carry a tautology");
assert!(
payload.contains("OR 1=1"),
"vuln fixture must carry a tautology"
);
stub.record_query(&payload).unwrap();
let oracle = Oracle::StubEvent {
@ -85,7 +86,11 @@ fn sql_stub_vuln_fixture_confirms_with_captured_query() {
needle: "OR 1=1",
};
let events = stub.drain_events();
assert_eq!(events.len(), 1, "stub must have captured the executed query");
assert_eq!(
events.len(),
1,
"stub must have captured the executed query"
);
assert!(
events[0].summary.contains("OR 1=1"),
"captured query must be visible in probe output: {:?}",
@ -103,7 +108,10 @@ fn sql_stub_benign_fixture_does_not_confirm() {
let stub = SqlStub::start(dir.path()).unwrap();
let payload = extract_payload(&read_fixture("sql", "benign.txt"));
assert!(!payload.contains("OR 1=1"), "benign control must lack tautology");
assert!(
!payload.contains("OR 1=1"),
"benign control must lack tautology"
);
stub.record_query(&payload).unwrap();
let oracle = Oracle::StubEvent {
@ -161,7 +169,10 @@ fn http_stub_vuln_fixture_confirms_recorded_request() {
let workdir = TempDir::new().unwrap();
let stub = HttpStub::start(workdir.path()).unwrap();
let payload = extract_payload(&read_fixture("http", "vuln.txt"));
assert!(payload.contains("169.254"), "vuln fixture must carry metadata host");
assert!(
payload.contains("169.254"),
"vuln fixture must carry metadata host"
);
stub.record(payload.clone());
let events = stub.drain_events();
@ -172,7 +183,12 @@ fn http_stub_vuln_fixture_confirms_recorded_request() {
kind: StubKind::Http,
needle: "169.254",
};
assert!(oracle_fired_with_stubs(&oracle, &empty_outcome(), &[], &events));
assert!(oracle_fired_with_stubs(
&oracle,
&empty_outcome(),
&[],
&events
));
}
#[test]
@ -187,7 +203,12 @@ fn http_stub_benign_fixture_does_not_confirm() {
kind: StubKind::Http,
needle: "169.254",
};
assert!(!oracle_fired_with_stubs(&oracle, &empty_outcome(), &[], &events));
assert!(!oracle_fired_with_stubs(
&oracle,
&empty_outcome(),
&[],
&events
));
}
// ── Redis stub ───────────────────────────────────────────────────────
@ -204,7 +225,12 @@ fn redis_stub_vuln_fixture_confirms_destructive_command() {
kind: StubKind::Redis,
needle: "FLUSHALL",
};
assert!(oracle_fired_with_stubs(&oracle, &empty_outcome(), &[], &events));
assert!(oracle_fired_with_stubs(
&oracle,
&empty_outcome(),
&[],
&events
));
}
#[test]
@ -221,7 +247,12 @@ fn redis_stub_benign_fixture_does_not_confirm() {
kind: StubKind::Redis,
needle: "FLUSHALL",
};
assert!(!oracle_fired_with_stubs(&oracle, &empty_outcome(), &[], &events));
assert!(!oracle_fired_with_stubs(
&oracle,
&empty_outcome(),
&[],
&events
));
}
// ── Filesystem stub ──────────────────────────────────────────────────
@ -239,7 +270,12 @@ fn filesystem_stub_vuln_fixture_confirms_path_traversal() {
kind: StubKind::Filesystem,
needle: "/etc/passwd",
};
assert!(oracle_fired_with_stubs(&oracle, &empty_outcome(), &[], &events));
assert!(oracle_fired_with_stubs(
&oracle,
&empty_outcome(),
&[],
&events
));
}
#[test]
@ -255,7 +291,12 @@ fn filesystem_stub_benign_fixture_does_not_confirm() {
kind: StubKind::Filesystem,
needle: "/etc/passwd",
};
assert!(!oracle_fired_with_stubs(&oracle, &empty_outcome(), &[], &events));
assert!(!oracle_fired_with_stubs(
&oracle,
&empty_outcome(),
&[],
&events
));
}
// ── Performance invariant ────────────────────────────────────────────

View file

@ -9,8 +9,8 @@ use nyx_scanner::callgraph::CallGraph;
use nyx_scanner::commands::surface::{load_or_build, render_dot, render_text};
use nyx_scanner::summary::GlobalSummaries;
use nyx_scanner::surface::{
build::{build_surface_map, SurfaceBuildInputs},
SurfaceMap,
build::{SurfaceBuildInputs, build_surface_map},
};
use nyx_scanner::utils::config::Config;
use std::path::{Path, PathBuf};
@ -78,7 +78,8 @@ fn text_output_matches_golden_for_flask_fixture() {
let expected = std::fs::read_to_string(GOLDEN_PATH)
.expect("read tests/dynamic_fixtures/surface/cli_output.golden.txt");
assert_eq!(
actual, expected,
actual,
expected,
"render_text output drifted from golden; re-run with UPDATE_GOLDEN=1 if intentional.\nfixture: {}",
dir.display()
);
@ -98,7 +99,10 @@ fn json_output_round_trips_byte_identical() {
let bytes = map.to_json().expect("canonical JSON");
let mut rt = SurfaceMap::from_json(&bytes).expect("from_json");
let rt_bytes = rt.to_json().expect("re-serialise");
assert_eq!(bytes, rt_bytes, "canonical JSON must round-trip identically");
assert_eq!(
bytes, rt_bytes,
"canonical JSON must round-trip identically"
);
}
#[test]

View file

@ -15,7 +15,7 @@ use nyx_scanner::callgraph::CallGraph;
use nyx_scanner::summary::GlobalSummaries;
use nyx_scanner::surface::{
Framework, SurfaceMap, SurfaceNode,
build::{build_surface_map, SurfaceBuildInputs},
build::{SurfaceBuildInputs, build_surface_map},
};
use nyx_scanner::utils::config::Config;
use std::path::{Path, PathBuf};

View file

@ -101,7 +101,11 @@ fn surface_map_captures_five_flask_routes() {
let ep = map.entry_for_route(method, route).unwrap_or_else(|| {
panic!("missing route {method:?} {route}; map = {entries:#?}");
});
assert_eq!(ep.framework, Framework::Flask, "framework mismatch on {route}");
assert_eq!(
ep.framework,
Framework::Flask,
"framework mismatch on {route}"
);
assert_eq!(ep.handler_name, handler, "handler mismatch on {route}");
assert_eq!(
ep.auth_required, auth,

View file

@ -13,11 +13,11 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::telemetry::{
self, RankDeltaEvent, SamplingPolicy, TelemetryEvent, TelemetryReadError, CORPUS_VERSION,
NYX_VERSION, SCHEMA_VERSION,
};
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy};
use nyx_scanner::dynamic::telemetry::{
self, CORPUS_VERSION, NYX_VERSION, RankDeltaEvent, SCHEMA_VERSION, SamplingPolicy,
TelemetryEvent, TelemetryReadError,
};
use nyx_scanner::evidence::VerifyStatus;
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;

View file

@ -9,15 +9,14 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::framework::{detect_binding, HttpMethod, ParamSource};
use nyx_scanner::dynamic::framework::{HttpMethod, ParamSource, detect_binding};
use nyx_scanner::evidence::EntryKind;
use nyx_scanner::summary::FuncSummary;
use nyx_scanner::symbol::Lang;
fn parse_ts(src: &[u8]) -> tree_sitter::Tree {
let mut parser = tree_sitter::Parser::new();
let lang =
tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT);
let lang = tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT);
parser.set_language(&lang).unwrap();
parser.parse(src, None).unwrap()
}

View file

@ -10,7 +10,7 @@ mod common;
#[cfg(feature = "dynamic")]
mod typescript_fixture_tests {
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
@ -79,9 +79,16 @@ mod typescript_fixture_tests {
fn commonjs_export_vuln_is_confirmed() {
let Some(r) = run(
NODE_REQ,
"commonjs_export", "vuln.ts", "runPing", Cap::CODE_EXEC, 11,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"commonjs_export",
"vuln.ts",
"runPing",
Cap::CODE_EXEC,
11,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("commonjs_export", &r);
}
@ -89,9 +96,16 @@ mod typescript_fixture_tests {
fn commonjs_export_benign_not_confirmed() {
let Some(r) = run(
NODE_REQ,
"commonjs_export", "benign.ts", "runPing", Cap::CODE_EXEC, 11,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"commonjs_export",
"benign.ts",
"runPing",
Cap::CODE_EXEC,
11,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("commonjs_export", &r);
}
@ -101,9 +115,16 @@ mod typescript_fixture_tests {
fn async_function_vuln_is_confirmed() {
let Some(r) = run(
NODE_REQ,
"async_function", "vuln.ts", "runPing", Cap::CODE_EXEC, 15,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"async_function",
"vuln.ts",
"runPing",
Cap::CODE_EXEC,
15,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("async_function", &r);
}
@ -111,9 +132,16 @@ mod typescript_fixture_tests {
fn async_function_benign_not_confirmed() {
let Some(r) = run(
NODE_REQ,
"async_function", "benign.ts", "runPing", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"async_function",
"benign.ts",
"runPing",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("async_function", &r);
}
@ -123,9 +151,16 @@ mod typescript_fixture_tests {
fn esm_default_vuln_is_confirmed() {
let Some(r) = run(
NODE_REQ,
"esm_default", "vuln.ts", "runPing", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"esm_default",
"vuln.ts",
"runPing",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("esm_default", &r);
}
@ -133,9 +168,16 @@ mod typescript_fixture_tests {
fn esm_default_benign_not_confirmed() {
let Some(r) = run(
NODE_REQ,
"esm_default", "benign.ts", "runPing", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"esm_default",
"benign.ts",
"runPing",
Cap::CODE_EXEC,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("esm_default", &r);
}
@ -148,9 +190,16 @@ mod typescript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("express"),
],
"express", "vuln.ts", "ping", Cap::CODE_EXEC, 15,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"express",
"vuln.ts",
"ping",
Cap::CODE_EXEC,
15,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_confirmed("express", &r);
}
@ -161,9 +210,16 @@ mod typescript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("express"),
],
"express", "benign.ts", "ping", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"express",
"benign.ts",
"ping",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_not_confirmed("express", &r);
}
@ -176,9 +232,16 @@ mod typescript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("koa"),
],
"koa", "vuln.ts", "ping", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"koa",
"vuln.ts",
"ping",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_confirmed("koa", &r);
}
@ -189,9 +252,16 @@ mod typescript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("koa"),
],
"koa", "benign.ts", "ping", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"koa",
"benign.ts",
"ping",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_not_confirmed("koa", &r);
}
@ -204,9 +274,16 @@ mod typescript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("next"),
],
"next_route", "vuln.ts", "handler", Cap::CODE_EXEC, 17,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"next_route",
"vuln.ts",
"handler",
Cap::CODE_EXEC,
17,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_confirmed("next_route", &r);
}
@ -217,9 +294,16 @@ mod typescript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("next"),
],
"next_route", "benign.ts", "handler", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("host".into()),
) else { return; };
"next_route",
"benign.ts",
"handler",
Cap::CODE_EXEC,
14,
EntryKind::HttpRoute,
PayloadSlot::QueryParam("host".into()),
) else {
return;
};
assert_not_confirmed("next_route", &r);
}
@ -232,9 +316,16 @@ mod typescript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("jsdom"),
],
"browser_event", "vuln.ts", "clickHandler", Cap::HTML_ESCAPE, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"browser_event",
"vuln.ts",
"clickHandler",
Cap::HTML_ESCAPE,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_confirmed("browser_event", &r);
}
@ -245,9 +336,16 @@ mod typescript_fixture_tests {
Prerequisite::CommandAvailable("node"),
Prerequisite::NodeModuleAvailable("jsdom"),
],
"browser_event", "benign.ts", "clickHandler", Cap::HTML_ESCAPE, 14,
EntryKind::Function, PayloadSlot::Param(0),
) else { return; };
"browser_event",
"benign.ts",
"clickHandler",
Cap::HTML_ESCAPE,
14,
EntryKind::Function,
PayloadSlot::Param(0),
) else {
return;
};
assert_not_confirmed("browser_event", &r);
}
}

View file

@ -12,7 +12,7 @@
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::corpus::{payloads_for_lang, resolve_benign_control_lang};
use nyx_scanner::dynamic::oracle::{oracle_fired, Oracle, ProbePredicate};
use nyx_scanner::dynamic::oracle::{Oracle, ProbePredicate, oracle_fired};
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::labels::Cap;
@ -60,10 +60,7 @@ fn idor_probe(caller: &str, owner: &str) -> SinkProbe {
fn corpus_registers_unauthorized_id_for_each_supported_lang() {
for lang in LANGS {
let slice = payloads_for_lang(Cap::UNAUTHORIZED_ID, *lang);
assert!(
!slice.is_empty(),
"UNAUTHORIZED_ID missing for {lang:?}"
);
assert!(!slice.is_empty(), "UNAUTHORIZED_ID missing for {lang:?}");
assert!(slice.iter().any(|p| !p.is_benign));
assert!(slice.iter().any(|p| p.is_benign));
}
@ -74,9 +71,8 @@ fn idor_payloads_pair_benign_per_lang() {
for lang in LANGS {
let slice = payloads_for_lang(Cap::UNAUTHORIZED_ID, *lang);
let vuln = slice.iter().find(|p| !p.is_benign).expect("vuln");
let resolved =
resolve_benign_control_lang(vuln, Cap::UNAUTHORIZED_ID, *lang)
.expect("benign control resolves");
let resolved = resolve_benign_control_lang(vuln, Cap::UNAUTHORIZED_ID, *lang)
.expect("benign control resolves");
assert!(resolved.is_benign);
match &vuln.oracle {
Oracle::SinkProbe { predicates } => assert!(
@ -94,7 +90,11 @@ fn idor_predicate_fires_on_boundary_crossing() {
let oracle = Oracle::SinkProbe {
predicates: &[ProbePredicate::IdorBoundaryCrossed],
};
assert!(oracle_fired(&oracle, &outcome(), &[idor_probe("alice", "bob")]));
assert!(oracle_fired(
&oracle,
&outcome(),
&[idor_probe("alice", "bob")]
));
assert!(!oracle_fired(
&oracle,
&outcome(),

View file

@ -17,14 +17,12 @@
mod common;
use nyx_scanner::dynamic::corpus::{
audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang, Oracle,
Oracle, audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang,
};
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::oracle::{
oracle_fired, ProbePredicate, SignalSet,
};
use nyx_scanner::dynamic::oracle::{ProbePredicate, SignalSet, oracle_fired};
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
@ -63,7 +61,10 @@ fn make_spec(lang: Lang, entry_file: &str, entry_name: &str) -> HarnessSpec {
fn corpus_registers_xpath_for_every_supported_lang() {
for lang in LANGS {
let slice = payloads_for_lang(Cap::XPATH_INJECTION, *lang);
assert!(!slice.is_empty(), "XPATH_INJECTION has no payloads for {lang:?}");
assert!(
!slice.is_empty(),
"XPATH_INJECTION has no payloads for {lang:?}"
);
let has_vuln = slice.iter().any(|p| !p.is_benign);
let has_benign = slice.iter().any(|p| p.is_benign);
assert!(has_vuln, "{lang:?} XPath missing vuln payload");
@ -109,10 +110,9 @@ fn payload_oracle_carries_query_result_count_predicate() {
match &vuln.oracle {
Oracle::SinkProbe { predicates } => {
assert!(
predicates.iter().any(|p| matches!(
p,
ProbePredicate::QueryResultCountGreaterThan { n: 1 }
)),
predicates
.iter()
.any(|p| matches!(p, ProbePredicate::QueryResultCountGreaterThan { n: 1 })),
"{lang:?} vuln payload missing QueryResultCountGreaterThan {{ n: 1 }}",
);
}
@ -221,7 +221,9 @@ fn query_result_count_predicate_also_matches_ldap_probe() {
args: vec![],
captured_at_ns: 1,
payload_id: "phase07".into(),
kind: ProbeKind::Ldap { entries_returned: 3 },
kind: ProbeKind::Ldap {
entries_returned: 3,
},
witness: ProbeWitness::empty(),
}];
let outcome = SandboxOutcome {
@ -269,8 +271,8 @@ fn lang_emitter_dispatches_to_xpath_harness() {
),
] {
let spec = make_spec(lang, entry_file, entry_name);
let harness = lang::emit(&spec)
.unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
let harness =
lang::emit(&spec).unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
assert!(
harness.source.contains("nodes_returned"),
"{lang:?} xpath harness must carry the nodes_returned probe field",
@ -354,8 +356,7 @@ fn framework_adapters_detect_xpath_sink() {
&bytes,
lang,
);
let b = binding
.unwrap_or_else(|| panic!("{lang:?} adapter must detect the XPath fixture"));
let b = binding.unwrap_or_else(|| panic!("{lang:?} adapter must detect the XPath fixture"));
assert_eq!(b.kind, EntryKind::Function);
assert!(!b.adapter.is_empty());
}
@ -407,10 +408,10 @@ fn staged_corpus_carries_three_users() {
mod e2e_phase_07 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -520,7 +521,9 @@ mod e2e_phase_07 {
#[test]
fn java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else { return };
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Java XPath vuln must Confirm via run_spec; got {outcome:?}",
@ -534,7 +537,9 @@ mod e2e_phase_07 {
#[test]
fn python_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else { return };
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Python XPath vuln must Confirm via run_spec; got {outcome:?}",
@ -548,7 +553,9 @@ mod e2e_phase_07 {
#[test]
fn php_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else { return };
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"PHP XPath vuln must Confirm via run_spec; got {outcome:?}",
@ -562,7 +569,9 @@ mod e2e_phase_07 {
#[test]
fn javascript_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else { return };
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"JavaScript XPath vuln must Confirm via run_spec; got {outcome:?}",

View file

@ -14,8 +14,8 @@
mod common;
use nyx_scanner::dynamic::corpus::{
audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang, Oracle,
Oracle, audit_marker_collisions, benign_payload_for_lang, payloads_for_lang,
resolve_benign_control_lang,
};
use nyx_scanner::dynamic::framework::registry::adapters_for;
use nyx_scanner::dynamic::lang;
@ -89,8 +89,7 @@ fn benign_control_resolves_within_lang_slice() {
.iter()
.find(|p| !p.is_benign && !p.oob_nonce_slot)
.unwrap();
let resolved =
resolve_benign_control_lang(vuln, Cap::XXE, *lang).expect("paired control");
let resolved = resolve_benign_control_lang(vuln, Cap::XXE, *lang).expect("paired control");
assert!(resolved.is_benign);
let direct = benign_payload_for_lang(Cap::XXE, *lang).unwrap();
assert_eq!(direct.label, resolved.label);
@ -113,7 +112,9 @@ fn payload_oracle_carries_xxe_entity_expanded_predicate() {
assert!(
predicates.iter().any(|p| matches!(
p,
ProbePredicate::XxeEntityExpanded { require_expanded: true }
ProbePredicate::XxeEntityExpanded {
require_expanded: true
}
)),
"{lang:?} vuln payload missing XxeEntityExpanded{{require_expanded:true}}",
);
@ -208,8 +209,8 @@ fn lang_emitter_dispatches_to_xxe_harness() {
),
] {
let spec = make_spec(lang, entry_file, entry_name);
let harness = lang::emit(&spec)
.unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
let harness =
lang::emit(&spec).unwrap_or_else(|e| panic!("emit failed for {lang:?}: {e:?}"));
assert!(
harness.source.contains("entity_expanded"),
"{lang:?} xxe harness must carry the entity_expanded probe field",
@ -250,11 +251,7 @@ fn framework_adapters_detect_xxe_sink() {
"tests/dynamic_fixtures/xxe/php/vuln.php",
"simplexml_load_string",
),
(
Lang::Ruby,
"tests/dynamic_fixtures/xxe/ruby/vuln.rb",
"new",
),
(Lang::Ruby, "tests/dynamic_fixtures/xxe/ruby/vuln.rb", "new"),
(
Lang::Go,
"tests/dynamic_fixtures/xxe/go/vuln.go",
@ -283,8 +280,7 @@ fn framework_adapters_detect_xxe_sink() {
&bytes,
lang,
);
let b = binding
.unwrap_or_else(|| panic!("{lang:?} adapter must detect the XXE fixture"));
let b = binding.unwrap_or_else(|| panic!("{lang:?} adapter must detect the XXE fixture"));
assert_eq!(b.kind, EntryKind::Function);
assert!(!b.adapter.is_empty());
}
@ -344,10 +340,10 @@ fn slug(lang: Lang) -> &'static str {
mod e2e_phase_05 {
use crate::common::fixture_harness::FIXTURE_LOCK;
use nyx_scanner::dynamic::runner::{run_spec, RunError, RunOutcome};
use nyx_scanner::dynamic::runner::{RunError, RunOutcome, run_spec};
use nyx_scanner::dynamic::sandbox::{SandboxBackend, SandboxOptions};
use nyx_scanner::dynamic::spec::{
default_toolchain_id, EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy,
EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy, default_toolchain_id,
};
use nyx_scanner::evidence::DifferentialVerdict;
use nyx_scanner::labels::Cap;
@ -454,9 +450,7 @@ mod e2e_phase_05 {
match run_spec(&spec, &opts) {
Ok(outcome) => {
if is_jvm_cwd_flake(&outcome) && attempt < 2 {
eprintln!(
"RETRY {lang:?} {fixture}: JVM cwd flake on attempt {attempt}",
);
eprintln!("RETRY {lang:?} {fixture}: JVM cwd flake on attempt {attempt}",);
std::thread::sleep(std::time::Duration::from_millis(200));
continue;
}
@ -485,7 +479,9 @@ mod e2e_phase_05 {
#[test]
fn java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else { return };
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Java XXE vuln must Confirm via run_spec; got {outcome:?}",
@ -499,7 +495,9 @@ mod e2e_phase_05 {
#[test]
fn python_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else { return };
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Python XXE vuln must Confirm via run_spec; got {outcome:?}",
@ -513,7 +511,9 @@ mod e2e_phase_05 {
#[test]
fn php_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else { return };
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"PHP XXE vuln must Confirm via run_spec; got {outcome:?}",
@ -527,7 +527,9 @@ mod e2e_phase_05 {
#[test]
fn ruby_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else { return };
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Ruby XXE vuln must Confirm via run_spec; got {outcome:?}",
@ -541,7 +543,9 @@ mod e2e_phase_05 {
#[test]
fn go_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Go, "vuln.go", "run") else { return };
let Some(outcome) = run(Lang::Go, "vuln.go", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Go XXE vuln must Confirm via run_spec; got {outcome:?}",
@ -657,31 +661,41 @@ mod e2e_phase_05 {
#[test]
fn python_xxe_oob_loopback_records_callback() {
let Some(outcome) = run_oob(Lang::Python, "vuln.py", "run") else { return };
let Some(outcome) = run_oob(Lang::Python, "vuln.py", "run") else {
return;
};
assert_oob_recorded(&outcome, "xxe-python-oob-nonce");
}
#[test]
fn java_xxe_oob_loopback_records_callback() {
let Some(outcome) = run_oob(Lang::Java, "Vuln.java", "run") else { return };
let Some(outcome) = run_oob(Lang::Java, "Vuln.java", "run") else {
return;
};
assert_oob_recorded(&outcome, "xxe-java-oob-nonce");
}
#[test]
fn php_xxe_oob_loopback_records_callback() {
let Some(outcome) = run_oob(Lang::Php, "vuln.php", "run") else { return };
let Some(outcome) = run_oob(Lang::Php, "vuln.php", "run") else {
return;
};
assert_oob_recorded(&outcome, "xxe-php-oob-nonce");
}
#[test]
fn ruby_xxe_oob_loopback_records_callback() {
let Some(outcome) = run_oob(Lang::Ruby, "vuln.rb", "run") else { return };
let Some(outcome) = run_oob(Lang::Ruby, "vuln.rb", "run") else {
return;
};
assert_oob_recorded(&outcome, "xxe-ruby-oob-nonce");
}
#[test]
fn go_xxe_oob_loopback_records_callback() {
let Some(outcome) = run_oob(Lang::Go, "vuln.go", "run") else { return };
let Some(outcome) = run_oob(Lang::Go, "vuln.go", "run") else {
return;
};
assert_oob_recorded(&outcome, "xxe-go-oob-nonce");
}
}