mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-24 20:28:06 +02:00
Added Cap::DATA_EXFIL and taint fp and fn fixes on real repos (#59)
* feat: Enhance data exfiltration detection with source sensitivity gating for cookies and headers * feat: Implement cross-file data exfiltration detection with parameter-specific gate filters * feat: Add calibration tests and refine DATA_EXFIL severity scoring logic * feat: Introduce per-detector configuration for data exfiltration suppression * feat: Enhance DATA_EXFIL findings with destination field tracking in diagnostics and SARIF output * feat: Add tainted body and URL handling for data exfiltration detection * feat: Add integration tests and fixtures for DATA_EXFIL and SSRF detection in Go * feat: Add Java integration tests and fixtures for DATA_EXFIL detection across multiple HTTP clients * feat: Add synthetic externals handling for closure-captured variables in SSA * feat: Implement closure-based suppression for resource leak findings * feat: Add regression guards for shell-injection and taint propagation in for-of destructure patterns * feat: Implement constructor cap narrowing for data exfiltration detection in HTTP request builders * feat: Add gated sinks for data exfiltration detection in C and C++ using curl_easy_setopt * feat: Implement DATA_EXFIL cap parity for backwards analysis and add integration tests * feat: Add data exfiltration sinks for various languages and enhance documentation * refactor: Simplify formatting and improve readability in various files * refactor: Improve readability by simplifying conditional statements and adding clippy linting * docs: Update CHANGELOG and comments for data exfiltration features and configuration * docs: Clarify configuration instructions for data exfiltration trusted destinations * docs: Enhance comments for evidence routing logic in data exfiltration
This commit is contained in:
parent
a438886217
commit
58f1794a4e
189 changed files with 8421 additions and 383 deletions
142
tests/fetch_data_exfil_suppression_tests.rs
Normal file
142
tests/fetch_data_exfil_suppression_tests.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//! `Cap::DATA_EXFIL` suppression-layer integration tests.
|
||||
//!
|
||||
//! Three layers are exercised:
|
||||
//!
|
||||
//! 1. Sanitizer convention. `logEvent({user: req.cookies.session})`
|
||||
//! routes a Sensitive cookie source through a named telemetry
|
||||
//! boundary; the default sanitizer rule for `logEvent` clears the
|
||||
//! cap.
|
||||
//! 2. Per-project destination allowlist. With
|
||||
//! `detectors.data_exfil.trusted_destinations = ["https://api.internal/"]`
|
||||
//! installed via the runtime, a `fetch('https://api.internal/...',
|
||||
//! {body: tainted})` call has the cap suppressed for that gate only;
|
||||
//! a `fetch('https://untrusted.example.com/...', ...)` call on a
|
||||
//! destination NOT in the allowlist still emits the finding.
|
||||
//! 3. Detector-class enabled toggle. When
|
||||
//! `detectors.data_exfil.enabled = false` is installed, no
|
||||
//! `taint-data-exfiltration` finding is emitted regardless of which
|
||||
//! gate would have fired.
|
||||
//!
|
||||
//! All sub-cases run inside a single `#[test]` so the global
|
||||
//! `detector_options` runtime is mutated sequentially. Each sub-case
|
||||
//! installs its own configuration via `reinstall` and resets to defaults
|
||||
//! at the end so other test binaries are unaffected.
|
||||
|
||||
mod common;
|
||||
|
||||
use common::scan_fixture_dir;
|
||||
use nyx_scanner::commands::scan::Diag;
|
||||
use nyx_scanner::utils::config::AnalysisMode;
|
||||
use nyx_scanner::utils::detector_options::{DataExfilDetectorOptions, DetectorOptions, reinstall};
|
||||
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()
|
||||
}
|
||||
|
||||
fn count_data_exfil(diags: &[Diag]) -> usize {
|
||||
diags
|
||||
.iter()
|
||||
.filter(|d| d.id.starts_with("taint-data-exfiltration"))
|
||||
.count()
|
||||
}
|
||||
|
||||
fn install_default_detectors() {
|
||||
reinstall(DetectorOptions::default());
|
||||
}
|
||||
|
||||
fn install_with_trusted(prefixes: &[&str]) {
|
||||
reinstall(DetectorOptions {
|
||||
data_exfil: DataExfilDetectorOptions {
|
||||
enabled: true,
|
||||
trusted_destinations: prefixes.iter().map(|s| (*s).to_string()).collect(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn install_disabled() {
|
||||
reinstall(DetectorOptions {
|
||||
data_exfil: DataExfilDetectorOptions {
|
||||
enabled: false,
|
||||
trusted_destinations: Vec::new(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_exfil_suppression_suite() {
|
||||
// ── 1. sanitizer-convention: `logEvent` clears the cap.
|
||||
install_default_detectors();
|
||||
let diags = diags_for("fetch_data_exfil_sanitizer_wrap.js");
|
||||
assert_eq!(
|
||||
count_data_exfil(&diags),
|
||||
0,
|
||||
"logEvent default sanitizer must clear DATA_EXFIL.\n\
|
||||
Diags: {:#?}",
|
||||
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// ── 2a. allowlist drops cap on trusted destination.
|
||||
install_with_trusted(&["https://api.internal/"]);
|
||||
let diags = diags_for("fetch_data_exfil_allowlist_suppressed.js");
|
||||
assert_eq!(
|
||||
count_data_exfil(&diags),
|
||||
0,
|
||||
"trusted destination prefix must drop DATA_EXFIL for that filter.\n\
|
||||
Diags: {:#?}",
|
||||
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// ── 2b. negative: a destination NOT in the allowlist still fires.
|
||||
install_with_trusted(&["https://api.internal/"]);
|
||||
let diags = diags_for("fetch_data_exfil_external_destination.js");
|
||||
assert!(
|
||||
count_data_exfil(&diags) >= 1,
|
||||
"destination not in allowlist must still emit DATA_EXFIL.\n\
|
||||
Diags: {:#?}",
|
||||
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// ── 3a. detector toggle off ⇒ no DATA_EXFIL anywhere.
|
||||
install_disabled();
|
||||
let diags_internal = diags_for("fetch_data_exfil_allowlist_suppressed.js");
|
||||
let diags_external = diags_for("fetch_data_exfil_external_destination.js");
|
||||
let diags_classic = diags_for("fetch_body_data_exfil.js");
|
||||
assert_eq!(
|
||||
count_data_exfil(&diags_internal),
|
||||
0,
|
||||
"enabled=false must suppress DATA_EXFIL on the internal-destination fixture",
|
||||
);
|
||||
assert_eq!(
|
||||
count_data_exfil(&diags_external),
|
||||
0,
|
||||
"enabled=false must suppress DATA_EXFIL on the external-destination fixture",
|
||||
);
|
||||
assert_eq!(
|
||||
count_data_exfil(&diags_classic),
|
||||
0,
|
||||
"enabled=false must suppress DATA_EXFIL on the original cookie-leak fixture",
|
||||
);
|
||||
|
||||
// ── 3b. re-enable ⇒ classic cookie-leak fixture fires again
|
||||
// (regression guard for the toggle).
|
||||
install_default_detectors();
|
||||
let diags_classic = diags_for("fetch_body_data_exfil.js");
|
||||
assert!(
|
||||
count_data_exfil(&diags_classic) >= 1,
|
||||
"after re-enabling, the classic cookie-leak fixture must emit DATA_EXFIL again",
|
||||
);
|
||||
|
||||
// Reset to defaults so other test binaries running later in the same
|
||||
// process pick up the documented baseline.
|
||||
install_default_detectors();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue