fix(cli): apply repository triage file during scans

This commit is contained in:
elipeter 2026-06-05 10:50:25 -05:00
parent 991c84a1eb
commit 1148e65f36
42 changed files with 571 additions and 20 deletions

View file

@ -99,6 +99,8 @@ fn make_diag(
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -52,6 +52,8 @@ fn diag_with_caps(path: &str, line: usize, caps: Cap) -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -79,6 +79,8 @@ fn fixture_findings() -> Vec<Diag> {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: Vec::new(),

View file

@ -14,8 +14,9 @@
//! reproducible.
use assert_cmd::Command;
use nyx_scanner::commands::scan::Diag;
use predicates::prelude::*;
use serde_json::Value;
use serde_json::{Value, json};
use std::path::PathBuf;
/// Build a scan command with a fresh config dir and a writable tempdir as
@ -197,6 +198,91 @@ fn scan_json_stdout_is_machine_clean_when_tracing_warns() {
);
}
#[test]
fn scan_respects_committed_triage_file_for_cli_output_and_fail_on() {
let home = tempfile::tempdir().unwrap();
let target = tempfile::tempdir().unwrap();
std::fs::write(
target.path().join("app.js"),
b"const q = req.query.x;\neval(q);\n",
)
.unwrap();
let canonical_target = target.path().canonicalize().unwrap();
let scan_args = [
"--format",
"json",
"--quiet",
"--index",
"off",
"--no-verify",
"--all",
"--include-quality",
"--parse-timeout-ms",
"0",
];
let (mut first_cmd, _) = scan_cmd(home.path(), target.path());
first_cmd.args(scan_args);
let first = first_cmd.assert().success();
let first_json = assert_stdout_is_json_from_byte_zero(
&first.get_output().stdout,
"initial nyx scan --format json",
);
let findings = first_json["findings"]
.as_array()
.expect("scan JSON must include findings");
assert!(
!findings.is_empty(),
"fixture should emit at least one finding"
);
let decisions: Vec<Value> = findings
.iter()
.map(|finding| {
let diag: Diag = serde_json::from_value(finding.clone()).unwrap();
json!({
"fingerprint": nyx_scanner::server::models::compute_portable_fingerprint(
&diag,
&canonical_target,
),
"state": "false_positive",
"note": "fixture triaged by committed file",
"rule_id": diag.id,
"path": diag.path.strip_prefix(canonical_target.to_string_lossy().as_ref())
.unwrap_or(&diag.path)
.trim_start_matches('/')
})
})
.collect();
let nyx_dir = target.path().join(".nyx");
std::fs::create_dir(&nyx_dir).unwrap();
std::fs::write(
nyx_dir.join("triage.json"),
serde_json::to_vec_pretty(&json!({
"version": 1,
"decisions": decisions,
"suppression_rules": []
}))
.unwrap(),
)
.unwrap();
let (mut second_cmd, _) = scan_cmd(home.path(), target.path());
second_cmd.args(scan_args).args(["--fail-on", "HIGH"]);
let second = second_cmd.assert().success();
let second_json = assert_stdout_is_json_from_byte_zero(
&second.get_output().stdout,
"triaged nyx scan --format json",
);
assert_eq!(
second_json["findings"].as_array().unwrap().len(),
0,
"terminal triage decisions from .nyx/triage.json should be hidden by default"
);
}
#[test]
fn scan_sarif_stdout_is_machine_clean_when_tracing_warns() {
let home = tempfile::tempdir().unwrap();

View file

@ -970,6 +970,8 @@ fn make_diag(path: &Path, func: &str, cap: Cap, sink_line: u32) -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -47,6 +47,8 @@ fn base_diag() -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: Vec::new(),

View file

@ -61,6 +61,8 @@ fn deny_diag(stable_hash: u64) -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],
@ -312,6 +314,8 @@ fn confirmed_run_is_byte_identical_across_runs() {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -88,6 +88,8 @@ mod parity_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -80,6 +80,8 @@ mod verify_e2e {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],
@ -111,6 +113,8 @@ mod verify_e2e {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -66,6 +66,8 @@ fn high_confidence_taint_diag(path: &str, line: u32) -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: Vec::new(),

View file

@ -454,6 +454,8 @@ mod go_fixture_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -49,6 +49,8 @@ fn diag(severity: Severity, id: &str, conf: Option<Confidence>) -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: Vec::new(),

View file

@ -452,6 +452,8 @@ mod java_fixture_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -447,6 +447,8 @@ mod js_fixture_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -27,6 +27,8 @@ fn base_diag() -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: Vec::new(),

View file

@ -57,6 +57,8 @@ mod lang_detect {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -442,6 +442,8 @@ mod php_fixture_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -36,6 +36,8 @@ fn empty_diag() -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -930,6 +930,8 @@ mod python_fixture_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -281,6 +281,8 @@ mod rust_fixture_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -754,6 +754,8 @@ mod hardening_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],
@ -947,6 +949,8 @@ mod hardening_tests {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -649,6 +649,8 @@ finally:
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],
@ -787,6 +789,8 @@ finally:
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -31,6 +31,8 @@ fn base_diag() -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: "deadbeef01234567".into(),
alternative_finding_ids: Vec::new(),

View file

@ -80,6 +80,8 @@ fn make_diag(id: &str, path: &str, line: usize) -> Diag {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -50,6 +50,8 @@ mod spec_strategies {
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],

View file

@ -75,6 +75,8 @@ fn make_diag(path: &str, handler: &str, line: usize, cap: Cap, rule_id: &str) ->
rank_reason: None,
suppressed: false,
suppression: None,
triage_state: "open".to_string(),
triage_note: String::new(),
rollup: None,
finding_id: String::new(),
alternative_finding_ids: vec![],