mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
[pitboss] sweep after phase 07: 6 deferred items resolved
This commit is contained in:
parent
bfdfcb9d1a
commit
25e8b0eb0e
6 changed files with 730 additions and 8 deletions
|
|
@ -16,6 +16,7 @@
|
|||
//! | `src/commands/scan.rs` | enrichment loop lives here |
|
||||
//! | `src/commands/mod.rs` | `verify-feedback` subcommand (§21.2) |
|
||||
//! | `src/server/` (any file) | server start_scan verify wiring |
|
||||
//! | `src/rank.rs` | M7 rank-delta telemetry hook (§21 / M7) |
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -28,6 +29,7 @@ const ALLOWED: &[&str] = &[
|
|||
"commands/scan.rs",
|
||||
"commands/mod.rs",
|
||||
"server/",
|
||||
"rank.rs",
|
||||
// The dynamic module itself is obviously allowed.
|
||||
"dynamic/",
|
||||
];
|
||||
|
|
|
|||
256
tests/sarif_dynamic_verdict_tests.rs
Normal file
256
tests/sarif_dynamic_verdict_tests.rs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
//! SARIF output tests for the dynamic verification vendor extension (§5.4).
|
||||
//!
|
||||
//! Acceptance criterion: SARIF output contains both
|
||||
//! `partialFingerprints.dynamic_verdict_status` and
|
||||
//! `properties.nyx_dynamic_verdict` for every `VerifyStatus` variant, and
|
||||
//! both keys are absent when no dynamic verdict is attached.
|
||||
|
||||
use nyx_scanner::commands::scan::Diag;
|
||||
use nyx_scanner::evidence::{
|
||||
AttemptSummary, Evidence, InconclusiveReason, UnsupportedReason, VerifyResult, VerifyStatus,
|
||||
};
|
||||
use nyx_scanner::output::build_sarif;
|
||||
use nyx_scanner::patterns::{FindingCategory, Severity};
|
||||
use std::path::Path;
|
||||
|
||||
fn base_diag() -> Diag {
|
||||
Diag {
|
||||
path: "/scan_root/src/main.rs".into(),
|
||||
line: 10,
|
||||
col: 5,
|
||||
severity: Severity::High,
|
||||
id: "taint-unsanitised-flow".into(),
|
||||
category: FindingCategory::Security,
|
||||
path_validated: false,
|
||||
guard_kind: None,
|
||||
message: None,
|
||||
labels: vec![],
|
||||
confidence: None,
|
||||
evidence: None,
|
||||
rank_score: None,
|
||||
rank_reason: None,
|
||||
suppressed: false,
|
||||
suppression: None,
|
||||
rollup: None,
|
||||
finding_id: "deadbeef01234567".into(),
|
||||
alternative_finding_ids: Vec::new(),
|
||||
stable_hash: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn diag_with_verdict(verdict: VerifyResult) -> Diag {
|
||||
let mut d = base_diag();
|
||||
d.evidence = Some(Evidence {
|
||||
dynamic_verdict: Some(verdict),
|
||||
..Default::default()
|
||||
});
|
||||
d
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
fn sarif_result(diag: Diag) -> serde_json::Value {
|
||||
let sarif = build_sarif(&[diag], Path::new("/scan_root"));
|
||||
sarif["runs"][0]["results"][0].clone()
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn sarif_confirmed_verdict_sets_partial_fingerprint() {
|
||||
let verdict = VerifyResult {
|
||||
finding_id: "deadbeef01234567".into(),
|
||||
status: VerifyStatus::Confirmed,
|
||||
triggered_payload: Some("sqli-tautology".into()),
|
||||
reason: None,
|
||||
inconclusive_reason: None,
|
||||
detail: None,
|
||||
attempts: vec![AttemptSummary {
|
||||
payload_label: "sqli-tautology".into(),
|
||||
exit_code: Some(0),
|
||||
timed_out: false,
|
||||
triggered: true,
|
||||
sink_hit: true,
|
||||
}],
|
||||
toolchain_match: Some("exact".into()),
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
||||
assert_eq!(
|
||||
result["partialFingerprints"]["dynamic_verdict_status"],
|
||||
"Confirmed",
|
||||
"partialFingerprints.dynamic_verdict_status must be 'Confirmed'"
|
||||
);
|
||||
assert!(
|
||||
result["properties"]["nyx_dynamic_verdict"].is_object(),
|
||||
"properties.nyx_dynamic_verdict must be an object: {}",
|
||||
result["properties"]["nyx_dynamic_verdict"]
|
||||
);
|
||||
assert_eq!(
|
||||
result["properties"]["nyx_dynamic_verdict"]["status"],
|
||||
"Confirmed",
|
||||
"nyx_dynamic_verdict.status must be 'Confirmed'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sarif_not_confirmed_verdict_sets_partial_fingerprint() {
|
||||
let verdict = VerifyResult {
|
||||
finding_id: "deadbeef01234567".into(),
|
||||
status: VerifyStatus::NotConfirmed,
|
||||
triggered_payload: None,
|
||||
reason: None,
|
||||
inconclusive_reason: None,
|
||||
detail: None,
|
||||
attempts: vec![],
|
||||
toolchain_match: Some("exact".into()),
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
||||
assert_eq!(
|
||||
result["partialFingerprints"]["dynamic_verdict_status"],
|
||||
"NotConfirmed",
|
||||
"partialFingerprints.dynamic_verdict_status must be 'NotConfirmed'"
|
||||
);
|
||||
assert!(
|
||||
result["properties"]["nyx_dynamic_verdict"].is_object(),
|
||||
"properties.nyx_dynamic_verdict must be an object"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sarif_unsupported_verdict_sets_partial_fingerprint() {
|
||||
let verdict = VerifyResult {
|
||||
finding_id: "deadbeef01234567".into(),
|
||||
status: VerifyStatus::Unsupported,
|
||||
triggered_payload: None,
|
||||
reason: Some(UnsupportedReason::NoPayloadsForCap),
|
||||
inconclusive_reason: None,
|
||||
detail: None,
|
||||
attempts: vec![],
|
||||
toolchain_match: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
||||
assert_eq!(
|
||||
result["partialFingerprints"]["dynamic_verdict_status"],
|
||||
"Unsupported",
|
||||
"partialFingerprints.dynamic_verdict_status must be 'Unsupported'"
|
||||
);
|
||||
assert!(
|
||||
result["properties"]["nyx_dynamic_verdict"].is_object(),
|
||||
"properties.nyx_dynamic_verdict must be an object"
|
||||
);
|
||||
assert_eq!(
|
||||
result["properties"]["nyx_dynamic_verdict"]["reason"],
|
||||
"NoPayloadsForCap",
|
||||
"nyx_dynamic_verdict must carry the unsupported reason"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sarif_inconclusive_verdict_sets_partial_fingerprint() {
|
||||
let verdict = VerifyResult {
|
||||
finding_id: "deadbeef01234567".into(),
|
||||
status: VerifyStatus::Inconclusive,
|
||||
triggered_payload: None,
|
||||
reason: None,
|
||||
inconclusive_reason: Some(InconclusiveReason::BuildFailed),
|
||||
detail: Some("build failed after 3 attempts".into()),
|
||||
attempts: vec![],
|
||||
toolchain_match: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
||||
assert_eq!(
|
||||
result["partialFingerprints"]["dynamic_verdict_status"],
|
||||
"Inconclusive",
|
||||
"partialFingerprints.dynamic_verdict_status must be 'Inconclusive'"
|
||||
);
|
||||
assert!(
|
||||
result["properties"]["nyx_dynamic_verdict"].is_object(),
|
||||
"properties.nyx_dynamic_verdict must be an object"
|
||||
);
|
||||
assert_eq!(
|
||||
result["properties"]["nyx_dynamic_verdict"]["inconclusive_reason"],
|
||||
"BuildFailed",
|
||||
"nyx_dynamic_verdict must carry the inconclusive reason"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sarif_no_dynamic_verdict_omits_both_keys() {
|
||||
let diag = base_diag();
|
||||
let result = sarif_result(diag);
|
||||
|
||||
assert!(
|
||||
result["partialFingerprints"].is_null() || result["partialFingerprints"] == serde_json::Value::Null,
|
||||
"partialFingerprints must be absent when no dynamic verdict: {}",
|
||||
result["partialFingerprints"]
|
||||
);
|
||||
assert!(
|
||||
result["properties"]["nyx_dynamic_verdict"].is_null() || result["properties"]["nyx_dynamic_verdict"] == serde_json::Value::Null,
|
||||
"properties.nyx_dynamic_verdict must be absent when no dynamic verdict"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sarif_confirmed_verdict_nyx_dynamic_verdict_contains_triggered_payload() {
|
||||
let verdict = VerifyResult {
|
||||
finding_id: "deadbeef01234567".into(),
|
||||
status: VerifyStatus::Confirmed,
|
||||
triggered_payload: Some("cmd-injection-semicolon".into()),
|
||||
reason: None,
|
||||
inconclusive_reason: None,
|
||||
detail: None,
|
||||
attempts: vec![],
|
||||
toolchain_match: Some("exact".into()),
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
||||
assert_eq!(
|
||||
result["properties"]["nyx_dynamic_verdict"]["triggered_payload"],
|
||||
"cmd-injection-semicolon",
|
||||
"triggered_payload must appear in nyx_dynamic_verdict"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sarif_all_four_statuses_produce_partial_fingerprint() {
|
||||
let statuses = [
|
||||
(VerifyStatus::Confirmed, "Confirmed"),
|
||||
(VerifyStatus::NotConfirmed, "NotConfirmed"),
|
||||
(VerifyStatus::Unsupported, "Unsupported"),
|
||||
(VerifyStatus::Inconclusive, "Inconclusive"),
|
||||
];
|
||||
|
||||
for (status, expected_str) in statuses {
|
||||
let verdict = VerifyResult {
|
||||
finding_id: "deadbeef01234567".into(),
|
||||
status,
|
||||
triggered_payload: None,
|
||||
reason: None,
|
||||
inconclusive_reason: None,
|
||||
detail: None,
|
||||
attempts: vec![],
|
||||
toolchain_match: None,
|
||||
};
|
||||
|
||||
let result = sarif_result(diag_with_verdict(verdict));
|
||||
|
||||
assert_eq!(
|
||||
result["partialFingerprints"]["dynamic_verdict_status"],
|
||||
expected_str,
|
||||
"status {expected_str}: partialFingerprints.dynamic_verdict_status mismatch"
|
||||
);
|
||||
assert!(
|
||||
result["properties"]["nyx_dynamic_verdict"].is_object(),
|
||||
"status {expected_str}: properties.nyx_dynamic_verdict must be an object"
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue