mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-06 19:35:13 +02:00
264 lines
7.9 KiB
Rust
264 lines
7.9 KiB
Rust
//! Ruby fixture integration tests (Phase 15 acceptance gate).
|
|
//!
|
|
//! Per-shape acceptance for the Ruby emitter shapes shipped in Phase 15
|
|
//! (Track B Ruby vertical): Sinatra route, Rails action, Rack middleware,
|
|
//! and generic controller method. Each shape ships a `vuln.rb` + `benign.rb`
|
|
//! pair under `tests/dynamic_fixtures/ruby/<shape>/`.
|
|
//!
|
|
//! Prerequisites: skips cleanly when `ruby` is unavailable on the host.
|
|
//!
|
|
//! Run with: `cargo nextest run --features dynamic --test ruby_fixtures`
|
|
|
|
mod common;
|
|
|
|
#[cfg(feature = "dynamic")]
|
|
mod phase15_shape_tests {
|
|
use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip};
|
|
use nyx_scanner::dynamic::spec::PayloadSlot;
|
|
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
|
|
use nyx_scanner::labels::Cap;
|
|
use nyx_scanner::symbol::Lang;
|
|
|
|
fn assert_confirmed(shape: &str, result: &VerifyResult) {
|
|
assert_eq!(
|
|
result.status,
|
|
VerifyStatus::Confirmed,
|
|
"{shape}/vuln: expected Confirmed, got {:?} ({:?})",
|
|
result.status,
|
|
result.detail,
|
|
);
|
|
}
|
|
|
|
fn assert_not_confirmed(shape: &str, result: &VerifyResult) {
|
|
assert!(
|
|
matches!(
|
|
result.status,
|
|
VerifyStatus::NotConfirmed | VerifyStatus::Inconclusive
|
|
),
|
|
"{shape}/benign: expected NotConfirmed (or Inconclusive), got {:?} ({:?})",
|
|
result.status,
|
|
result.detail,
|
|
);
|
|
assert_ne!(
|
|
result.status,
|
|
VerifyStatus::Confirmed,
|
|
"{shape}/benign: must not confirm",
|
|
);
|
|
}
|
|
|
|
fn run(
|
|
shape: &str,
|
|
file: &str,
|
|
func: &str,
|
|
cap: Cap,
|
|
sink_line: u32,
|
|
kind: EntryKind,
|
|
slot: PayloadSlot,
|
|
) -> Option<VerifyResult> {
|
|
// Phase 29 (Track I): structured prerequisite gating replaces
|
|
// the bespoke `ruby_available()` + per-test
|
|
// `eprintln!("SKIP ..."); return;` pattern.
|
|
let mut requires = vec![Prerequisite::CommandAvailable("ruby")];
|
|
match shape {
|
|
"sinatra_route" => {
|
|
requires.push(Prerequisite::CommandAvailable("bundle"));
|
|
requires.push(Prerequisite::RubyRequireAvailable("sinatra/base"));
|
|
}
|
|
"rails_action" => {
|
|
requires.push(Prerequisite::CommandAvailable("bundle"));
|
|
requires.push(Prerequisite::RubyRequireAvailable("action_controller"));
|
|
}
|
|
"hanami_action" => {
|
|
requires.push(Prerequisite::CommandAvailable("bundle"));
|
|
requires.push(Prerequisite::RubyRequireAvailable("hanami/action"));
|
|
}
|
|
"rack_middleware" => {
|
|
requires.push(Prerequisite::CommandAvailable("bundle"));
|
|
requires.push(Prerequisite::RubyRequireAvailable("rack/mock"));
|
|
}
|
|
_ => {}
|
|
}
|
|
run_shape_fixture_lang_or_skip(
|
|
&requires,
|
|
Lang::Ruby,
|
|
"ruby",
|
|
shape,
|
|
file,
|
|
func,
|
|
cap,
|
|
sink_line,
|
|
kind,
|
|
slot,
|
|
)
|
|
}
|
|
|
|
// ── sinatra_route ────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn sinatra_route_vuln_is_confirmed() {
|
|
let Some(r) = run(
|
|
"sinatra_route",
|
|
"vuln.rb",
|
|
"run",
|
|
Cap::CODE_EXEC,
|
|
12,
|
|
EntryKind::HttpRoute,
|
|
PayloadSlot::Param(0),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_confirmed("sinatra_route", &r);
|
|
}
|
|
|
|
#[test]
|
|
fn sinatra_route_benign_not_confirmed() {
|
|
let Some(r) = run(
|
|
"sinatra_route",
|
|
"benign.rb",
|
|
"run",
|
|
Cap::CODE_EXEC,
|
|
15,
|
|
EntryKind::HttpRoute,
|
|
PayloadSlot::Param(0),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_not_confirmed("sinatra_route", &r);
|
|
}
|
|
|
|
// ── rails_action ─────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn rails_action_vuln_is_confirmed() {
|
|
let Some(r) = run(
|
|
"rails_action",
|
|
"vuln.rb",
|
|
"index",
|
|
Cap::CODE_EXEC,
|
|
14,
|
|
EntryKind::HttpRoute,
|
|
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_confirmed("rails_action", &r);
|
|
}
|
|
|
|
#[test]
|
|
fn rails_action_benign_not_confirmed() {
|
|
let Some(r) = run(
|
|
"rails_action",
|
|
"benign.rb",
|
|
"index",
|
|
Cap::CODE_EXEC,
|
|
17,
|
|
EntryKind::HttpRoute,
|
|
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_not_confirmed("rails_action", &r);
|
|
}
|
|
|
|
// ── hanami_action ───────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn hanami_action_vuln_is_confirmed() {
|
|
let Some(r) = run(
|
|
"hanami_action",
|
|
"vuln.rb",
|
|
"call",
|
|
Cap::CODE_EXEC,
|
|
19,
|
|
EntryKind::HttpRoute,
|
|
PayloadSlot::QueryParam("payload".into()),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_confirmed("hanami_action", &r);
|
|
}
|
|
|
|
#[test]
|
|
fn hanami_action_benign_not_confirmed() {
|
|
let Some(r) = run(
|
|
"hanami_action",
|
|
"benign.rb",
|
|
"call",
|
|
Cap::CODE_EXEC,
|
|
21,
|
|
EntryKind::HttpRoute,
|
|
PayloadSlot::QueryParam("payload".into()),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_not_confirmed("hanami_action", &r);
|
|
}
|
|
|
|
// ── rack_middleware ──────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn rack_middleware_vuln_is_confirmed() {
|
|
let Some(r) = run(
|
|
"rack_middleware",
|
|
"vuln.rb",
|
|
"call",
|
|
Cap::CODE_EXEC,
|
|
10,
|
|
EntryKind::HttpRoute,
|
|
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_confirmed("rack_middleware", &r);
|
|
}
|
|
|
|
#[test]
|
|
fn rack_middleware_benign_not_confirmed() {
|
|
let Some(r) = run(
|
|
"rack_middleware",
|
|
"benign.rb",
|
|
"call",
|
|
Cap::CODE_EXEC,
|
|
11,
|
|
EntryKind::HttpRoute,
|
|
PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_not_confirmed("rack_middleware", &r);
|
|
}
|
|
|
|
// ── controller_method ────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn controller_method_vuln_is_confirmed() {
|
|
let Some(r) = run(
|
|
"controller_method",
|
|
"vuln.rb",
|
|
"authenticate",
|
|
Cap::CODE_EXEC,
|
|
7,
|
|
EntryKind::Function,
|
|
PayloadSlot::Param(0),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_confirmed("controller_method", &r);
|
|
}
|
|
|
|
#[test]
|
|
fn controller_method_benign_not_confirmed() {
|
|
let Some(r) = run(
|
|
"controller_method",
|
|
"benign.rb",
|
|
"authenticate",
|
|
Cap::CODE_EXEC,
|
|
10,
|
|
EntryKind::Function,
|
|
PayloadSlot::Param(0),
|
|
) else {
|
|
return;
|
|
};
|
|
assert_not_confirmed("controller_method", &r);
|
|
}
|
|
}
|