mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
* chore: Exclude CLAUDE.md from Cargo.toml * feat: add callgraph module and integrate into main analysis flow * feat: enhance CLI with new severity filtering and analysis modes * feat: update CHANGELOG with recent enhancements and fixes to severity filtering and output handling * feat: implement state-model dataflow analysis for resource lifecycle and auth state * feat: enhance diagnostic output formatting and add evidence structure * feat: implement attack surface ranking for diagnostics with scoring and sorting * feat: add comprehensive documentation for installation, usage, and rules reference * feat: add multiple language support for command execution and evaluation endpoints * feat: implement inline suppression for findings using `nyx:ignore` comments * feat: add confidence levels to AST patterns and update output structure * feat: implement low-noise prioritization system with category filtering, rollup grouping, and configurable budgets * feat: bump version to 0.4.0 and update changelog with new features and improvements * feat: add dead code allowances to various functions in mod.rs and real_world_tests.rs
102 lines
3.5 KiB
Rust
102 lines
3.5 KiB
Rust
//! Regression tests for taint BFS termination.
|
|
//!
|
|
//! Before the fix in taint/mod.rs (MAX_BFS_ITERATIONS / MAX_SEEN_STATES),
|
|
//! files with many tainted variables and loops caused the BFS to run
|
|
//! forever because each loop iteration produced a distinct taint-map hash,
|
|
//! bypassing the `(node, taint_hash)` seen-state dedup.
|
|
|
|
use nyx_scanner::commands::scan::Diag;
|
|
use nyx_scanner::utils::Config;
|
|
use std::path::Path;
|
|
use std::sync::OnceLock;
|
|
use std::time::{Duration, Instant};
|
|
|
|
/// Shared result so we only run the scan once across all assertions.
|
|
fn scan_fixture() -> &'static Vec<Diag> {
|
|
static DIAGS: OnceLock<Vec<Diag>> = OnceLock::new();
|
|
DIAGS.get_or_init(|| {
|
|
let fixture =
|
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/taint_termination");
|
|
let cfg = Config::default();
|
|
nyx_scanner::scan_no_index(&fixture, &cfg).expect("scan should succeed")
|
|
})
|
|
}
|
|
|
|
/// The scan must complete in a reasonable time. The old code hung forever
|
|
/// on this fixture; with the BFS limit it should finish in well under 10s.
|
|
#[test]
|
|
fn taint_bfs_terminates_within_timeout() {
|
|
let start = Instant::now();
|
|
let _diags = scan_fixture();
|
|
let elapsed = start.elapsed();
|
|
|
|
assert!(
|
|
elapsed < Duration::from_secs(10),
|
|
"Taint BFS took {:?} — should complete in <10s (was infinite before fix)",
|
|
elapsed
|
|
);
|
|
}
|
|
|
|
/// The scan should still produce meaningful findings even after bail-out.
|
|
#[test]
|
|
fn taint_bfs_produces_findings_after_bailout() {
|
|
let diags = scan_fixture();
|
|
// We should get at least *some* findings (cfg-unguarded-sink at minimum,
|
|
// possibly taint findings depending on how far the BFS got).
|
|
assert!(
|
|
!diags.is_empty(),
|
|
"Expected at least some findings from heavy_loop.js fixture"
|
|
);
|
|
}
|
|
|
|
/// Scan a single-file fixture directory via --no-index path. This is the
|
|
/// exact code path that hung: `scan_filesystem` → `par_iter().fold().reduce()`.
|
|
#[test]
|
|
fn scan_no_index_completes() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/taint_termination");
|
|
let cfg = Config::default();
|
|
|
|
let start = Instant::now();
|
|
let result = nyx_scanner::scan_no_index(&fixture, &cfg);
|
|
let elapsed = start.elapsed();
|
|
|
|
assert!(result.is_ok(), "scan should not error");
|
|
assert!(
|
|
elapsed < Duration::from_secs(10),
|
|
"scan took {:?} on small fixture",
|
|
elapsed
|
|
);
|
|
}
|
|
|
|
/// Indexed path: build_index + scan_with_index_parallel must also complete.
|
|
#[test]
|
|
fn scan_with_index_completes() {
|
|
use nyx_scanner::commands::scan::scan_with_index_parallel;
|
|
use nyx_scanner::database::index::Indexer;
|
|
use std::sync::Arc;
|
|
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/taint_termination");
|
|
let td = tempfile::tempdir().unwrap();
|
|
let db_path = td.path().join("test.sqlite");
|
|
let cfg = Config::default();
|
|
|
|
let start = Instant::now();
|
|
|
|
// Build index
|
|
nyx_scanner::commands::index::build_index("test", &fixture, &db_path, &cfg, false)
|
|
.expect("build_index should succeed");
|
|
|
|
// Scan with index
|
|
let pool = Indexer::init(&db_path).unwrap();
|
|
let diags = scan_with_index_parallel("test", Arc::clone(&pool), &cfg, false)
|
|
.expect("indexed scan should succeed");
|
|
|
|
let elapsed = start.elapsed();
|
|
assert!(
|
|
elapsed < Duration::from_secs(10),
|
|
"Indexed scan took {:?} on small fixture",
|
|
elapsed
|
|
);
|
|
// Should produce findings just like the no-index path
|
|
assert!(!diags.is_empty(), "Expected findings from indexed scan");
|
|
}
|