nyx/tests/fetch_data_exfil_integration_tests.rs
Eli Peter a438886217
Python fp and docs updtes (#58)
* refactor: Update comments for clarity and add expectations.json files for performance metrics

* feat: Implement FP guard for JS/TS local-collection receivers to suppress missing ownership checks

* feat: Enhance Rust parameter handling to classify local collections and prevent false ownership checks

* refactor: Simplify code formatting for better readability in multiple files

* refactor: Improve UTF-8 sequence length handling and enhance clarity in loop iteration

* feat: Update Java and Python patterns to include new security rules

* refactor: Improve comment clarity and consistency across multiple Rust files

* refactor: Simplify code formatting for improved readability in integration tests and module files

* refactor: Improve comment formatting and enhance clarity in assertions across multiple files
2026-04-29 19:53:34 -04:00

125 lines
4 KiB
Rust

//! Integration tests for the `Cap::DATA_EXFIL` detector class.
//!
//! Validates per-cap attribution at multi-gate call sites: a single `fetch`
//! call carries both an SSRF gate (URL flow) and a DATA_EXFIL gate (body /
//! headers / json flow), and a tainted body must not surface as SSRF and
//! vice versa. Also sanity-checks the SARIF output so the new finding
//! class produces a distinct rule id.
mod common;
use common::scan_fixture_dir;
use nyx_scanner::commands::scan::Diag;
use nyx_scanner::utils::config::AnalysisMode;
use std::path::PathBuf;
fn js_fixture_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("js")
}
fn diags_for(file: &str) -> Vec<Diag> {
let dir = js_fixture_dir();
let all = scan_fixture_dir(&dir, AnalysisMode::Full);
all.into_iter().filter(|d| d.path.ends_with(file)).collect()
}
#[test]
fn fetch_body_data_exfil_emits_data_exfil_not_ssrf() {
let diags = diags_for("fetch_body_data_exfil.js");
let exfil = diags
.iter()
.filter(|d| d.id.starts_with("taint-data-exfiltration"))
.count();
let plain_taint = diags
.iter()
.filter(|d| d.id.starts_with("taint-unsanitised-flow"))
.count();
assert!(
exfil >= 1,
"expected at least one taint-data-exfiltration finding, got 0.\n\
Diags: {:#?}",
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
);
assert_eq!(
plain_taint,
0,
"fixed-URL fetch with tainted body must NOT emit SSRF \
(taint-unsanitised-flow), got {plain_taint}.\n\
Diags: {:#?}",
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
);
}
#[test]
fn fetch_ssrf_url_tainted_emits_ssrf_not_data_exfil() {
let diags = diags_for("fetch_ssrf_url_tainted.js");
let ssrf = diags
.iter()
.filter(|d| d.id.starts_with("taint-unsanitised-flow"))
.count();
let exfil = diags
.iter()
.filter(|d| d.id.starts_with("taint-data-exfiltration"))
.count();
assert!(
ssrf >= 1,
"expected at least one taint-unsanitised-flow (SSRF) finding, got 0.\n\
Diags: {:#?}",
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
);
assert_eq!(
exfil,
0,
"tainted-URL fetch must NOT emit DATA_EXFIL, got {exfil}.\n\
Diags: {:#?}",
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
);
}
#[test]
fn sarif_distinguishes_data_exfil_rule_id_from_ssrf() {
use nyx_scanner::output::build_sarif;
let dir = js_fixture_dir();
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
let sarif = build_sarif(&diags, &dir);
let rules = sarif["runs"][0]["tool"]["driver"]["rules"]
.as_array()
.expect("SARIF rules array");
let rule_ids: Vec<&str> = rules.iter().filter_map(|r| r["id"].as_str()).collect();
assert!(
rule_ids.contains(&"taint-data-exfiltration"),
"SARIF rules must contain taint-data-exfiltration, got: {rule_ids:?}"
);
assert!(
rule_ids.contains(&"taint-unsanitised-flow"),
"SARIF rules must contain taint-unsanitised-flow, got: {rule_ids:?}"
);
// Each finding should reference exactly one rule, and the cap-specific
// class must not be folded back into the generic taint bucket.
let results = sarif["runs"][0]["results"]
.as_array()
.expect("SARIF results array");
let exfil_results = results
.iter()
.filter(|r| r["ruleId"].as_str() == Some("taint-data-exfiltration"))
.count();
let ssrf_results = results
.iter()
.filter(|r| r["ruleId"].as_str() == Some("taint-unsanitised-flow"))
.count();
assert!(
exfil_results >= 1,
"expected >= 1 SARIF result with ruleId taint-data-exfiltration, got {exfil_results}",
);
assert!(
ssrf_results >= 1,
"expected >= 1 SARIF result with ruleId taint-unsanitised-flow, got {ssrf_results}",
);
}