mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
* 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
131 lines
4.8 KiB
Rust
131 lines
4.8 KiB
Rust
//! Regression tests for the `EngineNote` provenance system. Each
|
|
//! test forces a specific cap-site to fire on a tiny fixture by
|
|
//! overriding the engine's safety cap, then asserts either that the
|
|
//! corresponding observability counter moved *or* that the note
|
|
//! propagated to a produced finding, whichever is the more stable
|
|
//! signal for that cap.
|
|
|
|
mod common;
|
|
|
|
use common::scan_fixture_dir;
|
|
use nyx_scanner::commands::scan::set_scc_fixpoint_cap_override;
|
|
use nyx_scanner::engine_notes::EngineNote;
|
|
use nyx_scanner::taint::ssa_transfer::{
|
|
origins_truncation_count, reset_origins_observability, reset_worklist_observability,
|
|
set_max_origins_override, set_worklist_cap_override, worklist_cap_hit_count,
|
|
};
|
|
use nyx_scanner::utils::config::AnalysisMode;
|
|
use std::path::Path;
|
|
use std::sync::Mutex;
|
|
|
|
/// Process-wide atomics for cap overrides mean tests that fiddle with
|
|
/// them must run serially, cargo test defaults to parallel.
|
|
static CAP_GUARD: Mutex<()> = Mutex::new(());
|
|
|
|
fn fixture(name: &str) -> std::path::PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests/fixtures")
|
|
.join(name)
|
|
}
|
|
|
|
#[test]
|
|
fn worklist_cap_trips_observability_counter() {
|
|
let _guard = CAP_GUARD.lock().unwrap_or_else(|e| e.into_inner());
|
|
// Force a very tight worklist budget so every body with > 0 blocks
|
|
// trips the cap. The observability counter is the stable signal ,
|
|
// note attribution to a specific finding may be lost on bodies that
|
|
// capped *before* emitting their sink event.
|
|
reset_worklist_observability();
|
|
set_worklist_cap_override(1);
|
|
set_max_origins_override(0);
|
|
set_scc_fixpoint_cap_override(0);
|
|
|
|
let dir = fixture("cross_file_context_deep_chain");
|
|
let _ = scan_fixture_dir(&dir, AnalysisMode::Full);
|
|
|
|
set_worklist_cap_override(0);
|
|
|
|
assert!(
|
|
worklist_cap_hit_count() > 0,
|
|
"Expected worklist_cap_hit_count() > 0 when cap is forced to 1; got 0. \
|
|
Either the override is not wired into run_ssa_taint_full or the \
|
|
scan path no longer exercises the worklist."
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn origins_cap_trips_observability_on_multi_source_fixture() {
|
|
let _guard = CAP_GUARD.lock().unwrap_or_else(|e| e.into_inner());
|
|
// Set origins to 1 and scan a fixture with multiple top-level
|
|
// sources flowing into the same sink. Any non-trivial taint flow
|
|
// will produce at least one tainted value whose origin list hit the
|
|
// cap, detected by the post-hoc saturation scan at the end of
|
|
// `run_ssa_taint_internal`.
|
|
reset_origins_observability();
|
|
set_max_origins_override(1);
|
|
set_worklist_cap_override(0);
|
|
set_scc_fixpoint_cap_override(0);
|
|
|
|
// Scan a larger fixture so taint flows through several blocks.
|
|
let dir = fixture("cross_file_scc_deep_cycle");
|
|
let _ = scan_fixture_dir(&dir, AnalysisMode::Full);
|
|
|
|
set_max_origins_override(0);
|
|
|
|
assert!(
|
|
origins_truncation_count() > 0,
|
|
"Expected origins_truncation_count() > 0 with MAX_ORIGINS forced \
|
|
to 1; got 0. The override is not wired or the fixture never \
|
|
exercises a block state with tainted values."
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn scc_cap_attaches_cross_file_fixpoint_note() {
|
|
let _guard = CAP_GUARD.lock().unwrap_or_else(|e| e.into_inner());
|
|
// Force SCC to fail to converge: cap=1 means no refinement round
|
|
// runs, so any batch with mutual recursion is unconverged and
|
|
// `tag_unconverged_findings` runs.
|
|
reset_worklist_observability();
|
|
set_scc_fixpoint_cap_override(1);
|
|
set_worklist_cap_override(0);
|
|
set_max_origins_override(0);
|
|
|
|
let dir = fixture("cross_file_scc_deep_cycle");
|
|
let diags = scan_fixture_dir(&dir, AnalysisMode::Full);
|
|
|
|
set_scc_fixpoint_cap_override(0);
|
|
|
|
let has_cross_file_capped = diags.iter().any(|d| {
|
|
d.evidence
|
|
.as_ref()
|
|
.map(|ev| {
|
|
ev.engine_notes
|
|
.iter()
|
|
.any(|n| matches!(n, EngineNote::CrossFileFixpointCapped { .. }))
|
|
})
|
|
.unwrap_or(false)
|
|
});
|
|
assert!(
|
|
has_cross_file_capped,
|
|
"Expected at least one CrossFileFixpointCapped engine note \
|
|
when SCC cap is forced to 1.",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn engine_note_serializes_with_snake_case_tag() {
|
|
// Sanity check the SARIF / JSON shape that downstream consumers
|
|
// rely on: `{ "kind": "worklist_capped", "iterations": N }`.
|
|
let note = EngineNote::WorklistCapped { iterations: 42 };
|
|
let json = serde_json::to_string(¬e).expect("serialize");
|
|
assert!(json.contains("\"kind\":\"worklist_capped\""));
|
|
assert!(json.contains("\"iterations\":42"));
|
|
}
|
|
|
|
#[test]
|
|
fn lowers_confidence_distinguishes_informational_notes() {
|
|
assert!(EngineNote::WorklistCapped { iterations: 10 }.lowers_confidence());
|
|
assert!(EngineNote::ParseTimeout { timeout_ms: 1000 }.lowers_confidence());
|
|
assert!(!EngineNote::InlineCacheReused.lowers_confidence());
|
|
}
|