mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
cargo fmt
This commit is contained in:
parent
bec7bbf96c
commit
3a35cd6c8f
294 changed files with 6809 additions and 3911 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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{}",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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, ¤t_json).unwrap_or_else(|e| {
|
||||
panic!("write golden {}: {e}", golden_path.display())
|
||||
});
|
||||
std::fs::write(&golden_path, ¤t_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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:?}",
|
||||
|
|
|
|||
|
|
@ -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:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:?}",
|
||||
|
|
|
|||
|
|
@ -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:?}"),
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(), &[]));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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:?}",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:?}",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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, .. } => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:?}",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ──────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:?}",
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 ────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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:?}",
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue