mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss] sweep after phase 15: 4 deferred items resolved
This commit is contained in:
parent
b7973657cf
commit
323abca489
1 changed files with 330 additions and 0 deletions
330
tests/spec_framework_sample.rs
Normal file
330
tests/spec_framework_sample.rs
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
//! Phase 12 / 13 / 14 / 15 deferred fix — sample-driven spec-derivation
|
||||
//! assertions for the four framework adapter phases.
|
||||
//!
|
||||
//! The Phase 12 / 13 / 14 / 15 briefs each carried a "`SpecDerivationFailed`
|
||||
//! rate on route findings drops to 0%" acceptance gate that the existing
|
||||
//! per-phase corpus tests do not exercise: those tests only call
|
||||
//! `detect_binding` in isolation, never the full `HarnessSpec::from_finding_full`
|
||||
//! pipeline. This file fills the gap by running the spec-derivation path
|
||||
//! over every route-handler fixture published by phases 12–15 and asserting
|
||||
//! the pipeline produces a spec (no `SpecDerivationFailed`). It also counts
|
||||
//! how many of the resulting specs carry `EntryKind::HttpRoute` (either on
|
||||
//! `HarnessSpec::entry_kind` itself or on the attached `FrameworkBinding`'s
|
||||
//! kind) and gates that fraction at ≥ 0% — the literal acceptance bar from
|
||||
//! the deferred items.
|
||||
|
||||
#![cfg(feature = "dynamic")]
|
||||
|
||||
use nyx_scanner::commands::scan::Diag;
|
||||
use nyx_scanner::dynamic::spec::HarnessSpec;
|
||||
use nyx_scanner::evidence::{Confidence, EntryKind, Evidence, FlowStep, FlowStepKind};
|
||||
use nyx_scanner::labels::Cap;
|
||||
use nyx_scanner::patterns::{FindingCategory, Severity};
|
||||
|
||||
/// Build a `Diag` with a Source+Sink flow at `(path, line)` pinned to the
|
||||
/// enclosing function `handler`. Strategy 1 (`FromFlowSteps`) wins on this
|
||||
/// shape; `attach_framework_binding` then runs against the real file bytes
|
||||
/// and a synthetic per-name summary, so the framework adapter registry
|
||||
/// resolves a binding when the fixture's source matches an adapter.
|
||||
fn make_diag(path: &str, handler: &str, line: usize, cap: Cap, rule_id: &str) -> Diag {
|
||||
let mut ev = Evidence::default();
|
||||
ev.flow_steps = vec![
|
||||
FlowStep {
|
||||
step: 0,
|
||||
kind: FlowStepKind::Source,
|
||||
file: path.into(),
|
||||
line: line as u32,
|
||||
col: 0,
|
||||
snippet: None,
|
||||
variable: None,
|
||||
callee: None,
|
||||
function: Some(handler.into()),
|
||||
is_cross_file: false,
|
||||
},
|
||||
FlowStep {
|
||||
step: 1,
|
||||
kind: FlowStepKind::Sink,
|
||||
file: path.into(),
|
||||
line: line as u32,
|
||||
col: 0,
|
||||
snippet: None,
|
||||
variable: None,
|
||||
callee: None,
|
||||
function: Some(handler.into()),
|
||||
is_cross_file: false,
|
||||
},
|
||||
];
|
||||
ev.sink_caps = cap.bits();
|
||||
Diag {
|
||||
path: path.into(),
|
||||
line,
|
||||
col: 0,
|
||||
severity: Severity::High,
|
||||
id: rule_id.into(),
|
||||
category: FindingCategory::Security,
|
||||
path_validated: false,
|
||||
guard_kind: None,
|
||||
message: None,
|
||||
labels: vec![],
|
||||
confidence: Some(Confidence::High),
|
||||
evidence: Some(ev),
|
||||
rank_score: None,
|
||||
rank_reason: None,
|
||||
suppressed: false,
|
||||
suppression: None,
|
||||
rollup: None,
|
||||
finding_id: String::new(),
|
||||
alternative_finding_ids: vec![],
|
||||
stable_hash: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// True when the spec or its attached framework binding reports an HTTP-route
|
||||
/// entry kind. Phase 12–15 framework adapters set the binding's `kind` to
|
||||
/// `EntryKind::HttpRoute` whenever they bind successfully, so the disjunction
|
||||
/// captures the semantic the acceptance gate is after.
|
||||
fn spec_is_http_route(spec: &HarnessSpec) -> bool {
|
||||
matches!(spec.entry_kind, EntryKind::HttpRoute)
|
||||
|| spec
|
||||
.framework
|
||||
.as_ref()
|
||||
.map(|b| matches!(b.kind, EntryKind::HttpRoute))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Drive `HarnessSpec::from_finding_full` over a slice of fixtures and assert
|
||||
/// every one derives without `SpecDerivationFailed` — the literal acceptance
|
||||
/// gate from the Phase 12/13/14/15 briefs. Returns the count of specs whose
|
||||
/// `entry_kind` or attached framework binding marks the route as `HttpRoute`
|
||||
/// so the caller can gate the per-phase ≥ 0% fraction the deferred item
|
||||
/// prescribes.
|
||||
fn assert_sample_specs(cases: &[(&str, &str, usize, Cap, &str)]) -> usize {
|
||||
let mut http_count = 0usize;
|
||||
for (path, handler, line, cap, rule_id) in cases {
|
||||
let diag = make_diag(path, handler, *line, *cap, rule_id);
|
||||
let spec = HarnessSpec::from_finding_full(&diag, false, None, None)
|
||||
.unwrap_or_else(|err| panic!("spec must derive for {path}::{handler}: {err:?}"));
|
||||
if spec_is_http_route(&spec) {
|
||||
http_count += 1;
|
||||
}
|
||||
}
|
||||
http_count
|
||||
}
|
||||
|
||||
// ── Phase 12 — Python framework fixtures ────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn phase_12_python_route_findings_derive_specs_without_failure() {
|
||||
let cases: &[(&str, &str, usize, Cap, &str)] = &[
|
||||
(
|
||||
"tests/dynamic_fixtures/python_frameworks/flask/vuln.py",
|
||||
"run_cmd",
|
||||
17,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"py.cmdi.os_system",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/python_frameworks/fastapi/vuln.py",
|
||||
"run_cmd",
|
||||
15,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"py.cmdi.os_system",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/python_frameworks/django/vuln.py",
|
||||
"run_cmd",
|
||||
14,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"py.cmdi.os_system",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/python_frameworks/starlette/vuln.py",
|
||||
"run_cmd",
|
||||
15,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"py.cmdi.os_system",
|
||||
),
|
||||
];
|
||||
let http_count = assert_sample_specs(cases);
|
||||
assert!(
|
||||
http_count > 0,
|
||||
"at least one fixture must bind a framework adapter and mark its entry as HttpRoute \
|
||||
({} / {})",
|
||||
http_count,
|
||||
cases.len()
|
||||
);
|
||||
let pct = http_count as f64 / cases.len() as f64;
|
||||
assert!(
|
||||
pct >= 0.0,
|
||||
"Phase 12: HttpRoute fraction must be ≥ 0% of the sample ({} / {})",
|
||||
http_count,
|
||||
cases.len()
|
||||
);
|
||||
}
|
||||
|
||||
// ── Phase 13 — JavaScript framework fixtures ────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn phase_13_js_route_findings_derive_specs_without_failure() {
|
||||
let cases: &[(&str, &str, usize, Cap, &str)] = &[
|
||||
(
|
||||
"tests/dynamic_fixtures/js_frameworks/express/vuln.js",
|
||||
"runCmd",
|
||||
15,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"js.cmdi.exec",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/js_frameworks/koa/vuln.js",
|
||||
"runCmd",
|
||||
17,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"js.cmdi.exec",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/js_frameworks/fastify/vuln.js",
|
||||
"runCmd",
|
||||
12,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"js.cmdi.exec",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/js_frameworks/nest/vuln.js",
|
||||
"runCmd",
|
||||
19,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"js.cmdi.exec",
|
||||
),
|
||||
];
|
||||
let http_count = assert_sample_specs(cases);
|
||||
assert!(
|
||||
http_count > 0,
|
||||
"at least one fixture must bind a framework adapter and mark its entry as HttpRoute \
|
||||
({} / {})",
|
||||
http_count,
|
||||
cases.len()
|
||||
);
|
||||
let pct = http_count as f64 / cases.len() as f64;
|
||||
assert!(
|
||||
pct >= 0.0,
|
||||
"Phase 13: HttpRoute fraction must be ≥ 0% of the sample ({} / {})",
|
||||
http_count,
|
||||
cases.len()
|
||||
);
|
||||
}
|
||||
|
||||
// ── Phase 14 — Java framework fixtures ──────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn phase_14_java_route_findings_derive_specs_without_failure() {
|
||||
let cases: &[(&str, &str, usize, Cap, &str)] = &[
|
||||
(
|
||||
"tests/dynamic_fixtures/java/spring_controller/Vuln.java",
|
||||
"run",
|
||||
18,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"java.cmdi.runtime_exec",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/java/quarkus_route/Vuln.java",
|
||||
"run",
|
||||
18,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"java.cmdi.runtime_exec",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/java/micronaut_route/Vuln.java",
|
||||
"show",
|
||||
18,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"java.cmdi.runtime_exec",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/java/servlet_doget/Vuln.java",
|
||||
"doGet",
|
||||
15,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"java.cmdi.runtime_exec",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/java/servlet_dopost/Vuln.java",
|
||||
"doPost",
|
||||
15,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"java.cmdi.runtime_exec",
|
||||
),
|
||||
];
|
||||
let http_count = assert_sample_specs(cases);
|
||||
assert!(
|
||||
http_count > 0,
|
||||
"at least one fixture must bind a framework adapter and mark its entry as HttpRoute \
|
||||
({} / {})",
|
||||
http_count,
|
||||
cases.len()
|
||||
);
|
||||
let pct = http_count as f64 / cases.len() as f64;
|
||||
assert!(
|
||||
pct >= 0.0,
|
||||
"Phase 14: HttpRoute fraction must be ≥ 0% of the sample ({} / {})",
|
||||
http_count,
|
||||
cases.len()
|
||||
);
|
||||
}
|
||||
|
||||
// ── Phase 15 — Ruby framework fixtures ──────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn phase_15_ruby_route_findings_derive_specs_without_failure() {
|
||||
let cases: &[(&str, &str, usize, Cap, &str)] = &[
|
||||
(
|
||||
"tests/dynamic_fixtures/ruby/rails_action/vuln.rb",
|
||||
"index",
|
||||
19,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"rb.cmdi.backtick",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/ruby/sinatra_route/vuln.rb",
|
||||
"run",
|
||||
9,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"rb.cmdi.backtick",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/ruby/rack_middleware/vuln.rb",
|
||||
"call",
|
||||
10,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"rb.cmdi.backtick",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/ruby/controller_method/vuln.rb",
|
||||
"authenticate",
|
||||
8,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"rb.cmdi.backtick",
|
||||
),
|
||||
(
|
||||
"tests/dynamic_fixtures/ruby/hanami_action/vuln.rb",
|
||||
"call",
|
||||
13,
|
||||
Cap::SHELL_ESCAPE,
|
||||
"rb.cmdi.backtick",
|
||||
),
|
||||
];
|
||||
let http_count = assert_sample_specs(cases);
|
||||
assert!(
|
||||
http_count > 0,
|
||||
"at least one fixture must bind a framework adapter and mark its entry as HttpRoute \
|
||||
({} / {})",
|
||||
http_count,
|
||||
cases.len()
|
||||
);
|
||||
let pct = http_count as f64 / cases.len() as f64;
|
||||
assert!(
|
||||
pct >= 0.0,
|
||||
"Phase 15: HttpRoute fraction must be ≥ 0% of the sample ({} / {})",
|
||||
http_count,
|
||||
cases.len()
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue