mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-27 20:29:39 +02:00
[pitboss/grind] deferred session-0020 (20260516T052512Z-20f8)
This commit is contained in:
parent
04b3d88eb4
commit
aa209148b0
8 changed files with 649 additions and 13 deletions
29
tests/dynamic_fixtures/stubs_e2e/go/sql/vuln/main.go
Normal file
29
tests/dynamic_fixtures/stubs_e2e/go/sql/vuln/main.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Phase 10 (Track D.3) stub-end-to-end fixture: Go + SQL.
|
||||
//
|
||||
// Body-only fragment, not a standalone `go run`-able program. The
|
||||
// companion test in `tests/stubs_e2e_per_lang.rs` wraps these lines
|
||||
// in `package main` + the union of stdlib imports required by both
|
||||
// the spliced probe shim and this fragment, places the Go probe
|
||||
// shim ahead of `func main`, and then invokes `go run` on the
|
||||
// resulting file.
|
||||
//
|
||||
// The verifier publishes:
|
||||
//
|
||||
// NYX_SQL_ENDPOINT — absolute path of a SQLite DB the SqlStub owns.
|
||||
// NYX_SQL_LOG — companion log path the harness appends executed
|
||||
// queries to so the host SqlStub picks them up on
|
||||
// drain_events() even when the harness never opens
|
||||
// an on-the-wire driver (no go-sqlite3 / pgx /
|
||||
// mysql dep on the dynamic CI matrix; query
|
||||
// pre-flighted before sql.Open).
|
||||
//
|
||||
// This fragment records the tautology query through the Go shim
|
||||
// helper __nyx_stub_sql_record as `driver = "manual"` so the test
|
||||
// stays stdlib-only — no `database/sql` import, no go.mod driver
|
||||
// dep, no libsqlite3-dev system package. Mirrors the Phase 26
|
||||
// "no live driver available" path that real Go sink callsites take
|
||||
// when the build matrix lacks a driver.
|
||||
query := "SELECT 1 WHERE 'a' = 'a' OR 1=1 --"
|
||||
__nyx_stub_sql_record(query, map[string]string{"driver": "manual"})
|
||||
// Echo so the host can confirm the driver ran end-to-end.
|
||||
fmt.Print(os.Getenv("NYX_SQL_ENDPOINT"))
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Phase 10 (Track D.3) stub-end-to-end fixture: Java + SQL.
|
||||
//
|
||||
// The verifier publishes:
|
||||
//
|
||||
// * NYX_SQL_ENDPOINT — absolute path of a SQLite DB the SqlStub owns.
|
||||
// * NYX_SQL_LOG — companion log path the harness appends executed
|
||||
// queries to so the host SqlStub picks them up on drain_events()
|
||||
// even when the harness never opens an on-the-wire JDBC connection
|
||||
// (classpath lacks sqlite-jdbc, SQL string is pre-flighted before
|
||||
// DriverManager.getConnection, sandbox blocks file-DB access).
|
||||
//
|
||||
// This file is a body-only fragment: the companion test in
|
||||
// tests/stubs_e2e_per_lang.rs wraps it with a `public class Main { … }`
|
||||
// shell that splices the Java probe shim as class members ahead of
|
||||
// `public static void main`, so the shim's __nyx_stub_sql_record helper
|
||||
// is in scope. The fixture stays JDK-stdlib only — no java.sql import,
|
||||
// no sqlite-jdbc jar on the classpath — by recording the attempted
|
||||
// tautology with `driver = "manual"`. This mirrors the Phase 26
|
||||
// "no live driver available" path that real Java sink callsites take
|
||||
// when the build matrix lacks a JDBC driver.
|
||||
String query = "SELECT 1 WHERE 'a' = 'a' OR 1=1 --";
|
||||
java.util.Map<String,String> detail = new java.util.LinkedHashMap<>();
|
||||
detail.put("driver", "manual");
|
||||
__nyx_stub_sql_record(query, detail);
|
||||
String ep = System.getenv("NYX_SQL_ENDPOINT");
|
||||
System.out.println(ep == null ? "no-endpoint" : ep);
|
||||
21
tests/dynamic_fixtures/stubs_e2e/ruby/sql/vuln/main.rb
Normal file
21
tests/dynamic_fixtures/stubs_e2e/ruby/sql/vuln/main.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Phase 10 (Track D.3) stub-end-to-end fixture: Ruby + SQL.
|
||||
#
|
||||
# The verifier publishes:
|
||||
#
|
||||
# * NYX_SQL_ENDPOINT — absolute path of a SQLite DB the SqlStub owns.
|
||||
# * NYX_SQL_LOG — companion log path the harness appends executed
|
||||
# queries to so the host SqlStub picks them up on drain_events()
|
||||
# even when the harness never opens an on-the-wire driver (sqlite3
|
||||
# gem absent on minimal CI images, query pre-flighted before
|
||||
# SQLite3::Database.open).
|
||||
#
|
||||
# This fixture stays gem-free by recording the tautology through
|
||||
# __nyx_stub_sql_record as driver = 'manual'. No sqlite3 require, no
|
||||
# Gemfile dep, no Prerequisite::GemAvailable variant required. Mirrors
|
||||
# the Phase 26 "no live driver available" path that real Ruby sink
|
||||
# callsites take when the build matrix lacks a driver.
|
||||
|
||||
query = "SELECT 1 WHERE 'a' = 'a' OR 1=1 --"
|
||||
__nyx_stub_sql_record(query, driver: 'manual')
|
||||
# Echo so the host can confirm the driver ran end-to-end.
|
||||
$stdout.puts(ENV['NYX_SQL_ENDPOINT'] || 'no-endpoint')
|
||||
18
tests/dynamic_fixtures/stubs_e2e/rust/sql/vuln/main.rs
Normal file
18
tests/dynamic_fixtures/stubs_e2e/rust/sql/vuln/main.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Phase 10 (Track D.3) — Rust SQL recorder body-only fragment.
|
||||
//
|
||||
// Wrapped at test time by `wrap_rust_fragment(body, shim)` in
|
||||
// `tests/stubs_e2e_per_lang.rs`: the wrapper prepends the Rust probe
|
||||
// shim (which carries `__nyx_stub_sql_record`) and a one-line
|
||||
// `Cargo.toml` so `cargo run --quiet` builds the program in place.
|
||||
//
|
||||
// Rust has no stdlib SQLite client (rusqlite is a heavyweight C-link
|
||||
// dep that would force a libsqlite3-dev prereq on the dynamic CI
|
||||
// matrix). The fixture surfaces the attempted tautology query
|
||||
// through the shim recorder so the host-side SqlStub captures it as
|
||||
// `driver = "manual"`, mirroring the Phase 26 "no live driver
|
||||
// available" path that real Rust sink callsites take when the build
|
||||
// matrix lacks a DB driver.
|
||||
let _endpoint = std::env::var("NYX_SQL_ENDPOINT").unwrap_or_default();
|
||||
let query = "SELECT 1 WHERE 'a' = 'a' OR 1=1 --";
|
||||
let detail: &[(&str, &str)] = &[("driver", "manual")];
|
||||
__nyx_stub_sql_record(query, detail);
|
||||
|
|
@ -153,21 +153,29 @@ fn wrap_rust_fragment(body: &str, shim: &str) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
/// One-line Cargo.toml for the Rust stub-recorder driver. Mirrors
|
||||
/// Per-fixture Cargo.toml for the Rust stub-recorder driver. Mirrors
|
||||
/// the Phase 26 chain_step manifest (session 0014) — `[[bin]]` points
|
||||
/// at `main.rs` so `cargo run --quiet` builds the source the test
|
||||
/// just wrote, and `libc = "0.2"` is unconditionally pinned because
|
||||
/// the spliced probe shim's `__nyx_install_crash_guard` references
|
||||
/// `libc::sigaction` on Unix.
|
||||
const RUST_STUB_CARGO_TOML: &str = "[package]\n\
|
||||
name = \"nyx-stub-driver\"\n\
|
||||
version = \"0.0.1\"\n\
|
||||
edition = \"2021\"\n\n\
|
||||
[[bin]]\n\
|
||||
name = \"stub_driver\"\n\
|
||||
path = \"main.rs\"\n\n\
|
||||
[dependencies]\n\
|
||||
libc = \"0.2\"\n";
|
||||
/// `libc::sigaction` on Unix. Caller supplies a unique `slug` per
|
||||
/// test so the package + binary names do not collide in the shared
|
||||
/// `CARGO_TARGET_DIR` when nextest runs the Rust stub tests in
|
||||
/// parallel (every test still benefits from the cached `libc` build,
|
||||
/// only the final `nyx-stub-driver-<slug>` link is per-test).
|
||||
fn rust_stub_cargo_toml(slug: &str) -> String {
|
||||
format!(
|
||||
"[package]\n\
|
||||
name = \"nyx-stub-driver-{slug}\"\n\
|
||||
version = \"0.0.1\"\n\
|
||||
edition = \"2021\"\n\n\
|
||||
[[bin]]\n\
|
||||
name = \"stub_driver_{slug}\"\n\
|
||||
path = \"main.rs\"\n\n\
|
||||
[dependencies]\n\
|
||||
libc = \"0.2\"\n"
|
||||
)
|
||||
}
|
||||
|
||||
fn fixture_path(rel: &str) -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
|
|
@ -876,6 +884,103 @@ fn go_http_shim_recorder_is_noop_without_log_env() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn go_sql_stub_captures_tautology_query_via_shim_recorder() {
|
||||
// Phase 10 (Track D.3) SQL recording: Go leg of the side-channel
|
||||
// `__nyx_stub_sql_record` helper. Mirrors the Python / Node / PHP /
|
||||
// Rust / Java SQL tests — the Go fragment never opens a live
|
||||
// `database/sql` handle (no driver imported; pulling go-sqlite3 /
|
||||
// pgx / mysql would force a go.mod dep onto every dynamic CI matrix
|
||||
// row) so it surfaces the attempted tautology query through the
|
||||
// shim recorder as `driver = "manual"`.
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = SqlStub::start(workdir.path()).expect("SqlStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
.recording_endpoint()
|
||||
.expect("SqlStub must publish a recording endpoint");
|
||||
|
||||
let fragment =
|
||||
std::fs::read_to_string(fixture_path("go/sql/vuln/main.go")).expect("read go fragment");
|
||||
let combined = wrap_go_fragment(&fragment, go_probe_shim());
|
||||
|
||||
let script_path = workdir.path().join("driver_sql.go");
|
||||
std::fs::write(&script_path, combined).expect("write go driver");
|
||||
|
||||
let output = Command::new("go")
|
||||
.arg("run")
|
||||
.arg(&script_path)
|
||||
.env("NYX_SQL_ENDPOINT", &endpoint)
|
||||
.env(recording.0, &recording.1)
|
||||
.output()
|
||||
.expect("go driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0; stderr = {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let events = stub.drain_events();
|
||||
assert!(
|
||||
!events.is_empty(),
|
||||
"SqlStub must capture at least one event after the Go shim recorder fires"
|
||||
);
|
||||
let tautology = events
|
||||
.iter()
|
||||
.find(|e| e.summary.contains("OR 1=1"))
|
||||
.expect("recorded query must contain the tautology marker");
|
||||
assert_eq!(
|
||||
tautology.detail.get("driver").map(String::as_str),
|
||||
Some("manual"),
|
||||
"detail map entries passed to __nyx_stub_sql_record must surface as event detail entries"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn go_sql_shim_recorder_is_noop_without_log_env() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = SqlStub::start(workdir.path()).expect("SqlStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment =
|
||||
std::fs::read_to_string(fixture_path("go/sql/vuln/main.go")).expect("read go fragment");
|
||||
let combined = wrap_go_fragment(&fragment, go_probe_shim());
|
||||
|
||||
let script_path = workdir.path().join("driver_sql_no_log.go");
|
||||
std::fs::write(&script_path, combined).expect("write go driver");
|
||||
|
||||
let output = Command::new("go")
|
||||
.arg("run")
|
||||
.arg(&script_path)
|
||||
.env("NYX_SQL_ENDPOINT", &endpoint)
|
||||
.env_remove("NYX_SQL_LOG")
|
||||
.output()
|
||||
.expect("go driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0 even without NYX_SQL_LOG; stderr = {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let events = stub.drain_events();
|
||||
assert!(
|
||||
events.is_empty(),
|
||||
"no events expected when the recording env var is unset, got {} entries",
|
||||
events.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruby_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
||||
// Phase 10 (Track D.3) HTTP recording: Ruby leg of the side-channel
|
||||
|
|
@ -983,6 +1088,105 @@ fn ruby_http_shim_recorder_is_noop_without_log_env() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruby_sql_stub_captures_tautology_query_via_shim_recorder() {
|
||||
// Phase 10 (Track D.3) SQL recording: Ruby leg of the side-channel
|
||||
// `__nyx_stub_sql_record` helper. Mirrors the Python / Node / PHP /
|
||||
// Rust / Java / Go SQL tests — the Ruby fragment never opens a live
|
||||
// sqlite3 handle (no require, no gem dep) so it surfaces the
|
||||
// attempted tautology query through the shim recorder as
|
||||
// `driver = "manual"`.
|
||||
if !ruby_available() {
|
||||
eprintln!("SKIP: ruby not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = SqlStub::start(workdir.path()).expect("SqlStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
.recording_endpoint()
|
||||
.expect("SqlStub must publish a recording endpoint");
|
||||
|
||||
let fixture =
|
||||
std::fs::read_to_string(fixture_path("ruby/sql/vuln/main.rb")).expect("read fixture");
|
||||
let mut combined = String::with_capacity(ruby_probe_shim().len() + fixture.len() + 64);
|
||||
combined.push_str(ruby_probe_shim());
|
||||
combined.push_str("\n# ── fixture begins ─\n");
|
||||
combined.push_str(&fixture);
|
||||
|
||||
let script_path = workdir.path().join("driver_sql.rb");
|
||||
std::fs::write(&script_path, combined).expect("write driver");
|
||||
|
||||
let output = Command::new("ruby")
|
||||
.arg(&script_path)
|
||||
.env("NYX_SQL_ENDPOINT", &endpoint)
|
||||
.env(recording.0, &recording.1)
|
||||
.output()
|
||||
.expect("ruby driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0; stderr = {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let events = stub.drain_events();
|
||||
assert!(
|
||||
!events.is_empty(),
|
||||
"SqlStub must capture at least one event after the Ruby shim recorder fires"
|
||||
);
|
||||
let tautology = events
|
||||
.iter()
|
||||
.find(|e| e.summary.contains("OR 1=1"))
|
||||
.expect("recorded query must contain the tautology marker");
|
||||
assert_eq!(
|
||||
tautology.detail.get("driver").map(String::as_str),
|
||||
Some("manual"),
|
||||
"kwargs passed to __nyx_stub_sql_record must surface as event detail entries"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruby_sql_shim_recorder_is_noop_without_log_env() {
|
||||
if !ruby_available() {
|
||||
eprintln!("SKIP: ruby not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = SqlStub::start(workdir.path()).expect("SqlStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fixture =
|
||||
std::fs::read_to_string(fixture_path("ruby/sql/vuln/main.rb")).expect("read fixture");
|
||||
let mut combined = String::new();
|
||||
combined.push_str(ruby_probe_shim());
|
||||
combined.push('\n');
|
||||
combined.push_str(&fixture);
|
||||
let script_path = workdir.path().join("driver_sql_no_log.rb");
|
||||
std::fs::write(&script_path, combined).expect("write driver");
|
||||
|
||||
let output = Command::new("ruby")
|
||||
.arg(&script_path)
|
||||
.env("NYX_SQL_ENDPOINT", &endpoint)
|
||||
.env_remove("NYX_SQL_LOG")
|
||||
.output()
|
||||
.expect("ruby driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0 even without NYX_SQL_LOG; stderr = {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let events = stub.drain_events();
|
||||
assert!(
|
||||
events.is_empty(),
|
||||
"no events expected when the recording env var is unset, got {} entries",
|
||||
events.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
||||
// Phase 10 (Track D.3) HTTP recording: Java leg of the side-channel
|
||||
|
|
@ -1051,6 +1255,100 @@ fn java_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_sql_stub_captures_tautology_query_via_shim_recorder() {
|
||||
// Phase 10 (Track D.3) SQL recording: Java leg of the side-channel
|
||||
// `__nyx_stub_sql_record` helper. Mirrors the Python / Node / PHP /
|
||||
// Rust SQL tests — the Java fragment never opens a live JDBC handle
|
||||
// (sqlite-jdbc is not stdlib; pulling it would force a classpath
|
||||
// prereq onto the dynamic CI matrix) so it surfaces the attempted
|
||||
// tautology query through the shim recorder as `driver = "manual"`.
|
||||
if !java_available() {
|
||||
eprintln!("SKIP: java not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = SqlStub::start(workdir.path()).expect("SqlStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
.recording_endpoint()
|
||||
.expect("SqlStub must publish a recording endpoint");
|
||||
|
||||
let fragment = std::fs::read_to_string(fixture_path("java/sql/vuln/main.java.fragment"))
|
||||
.expect("read java sql fragment");
|
||||
let combined = wrap_java_fragment(&fragment, java_probe_shim());
|
||||
|
||||
let script_path = workdir.path().join("Main.java");
|
||||
std::fs::write(&script_path, combined).expect("write java driver");
|
||||
|
||||
let output = Command::new("java")
|
||||
.arg(&script_path)
|
||||
.env("NYX_SQL_ENDPOINT", &endpoint)
|
||||
.env(recording.0, &recording.1)
|
||||
.output()
|
||||
.expect("java driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0; stderr = {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let events = stub.drain_events();
|
||||
assert!(
|
||||
!events.is_empty(),
|
||||
"SqlStub must capture at least one event after the Java shim recorder fires"
|
||||
);
|
||||
let tautology = events
|
||||
.iter()
|
||||
.find(|e| e.summary.contains("OR 1=1"))
|
||||
.expect("recorded query must contain the tautology marker");
|
||||
assert_eq!(
|
||||
tautology.detail.get("driver").map(String::as_str),
|
||||
Some("manual"),
|
||||
"detail map entries passed to __nyx_stub_sql_record must surface as event detail entries"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_sql_shim_recorder_is_noop_without_log_env() {
|
||||
if !java_available() {
|
||||
eprintln!("SKIP: java not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = SqlStub::start(workdir.path()).expect("SqlStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment = std::fs::read_to_string(fixture_path("java/sql/vuln/main.java.fragment"))
|
||||
.expect("read java sql fragment");
|
||||
let combined = wrap_java_fragment(&fragment, java_probe_shim());
|
||||
|
||||
let script_path = workdir.path().join("Main.java");
|
||||
std::fs::write(&script_path, combined).expect("write java driver");
|
||||
|
||||
let output = Command::new("java")
|
||||
.arg(&script_path)
|
||||
.env("NYX_SQL_ENDPOINT", &endpoint)
|
||||
.env_remove("NYX_SQL_LOG")
|
||||
.output()
|
||||
.expect("java driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0 even without NYX_SQL_LOG; stderr = {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let events = stub.drain_events();
|
||||
assert!(
|
||||
events.is_empty(),
|
||||
"no events expected when the recording env var is unset, got {} entries",
|
||||
events.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_http_shim_recorder_is_noop_without_log_env() {
|
||||
if !java_available() {
|
||||
|
|
@ -1166,7 +1464,7 @@ fn rust_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
|
||||
let crate_dir = workdir.path().join("driver");
|
||||
std::fs::create_dir_all(&crate_dir).expect("create crate dir");
|
||||
std::fs::write(crate_dir.join("Cargo.toml"), RUST_STUB_CARGO_TOML)
|
||||
std::fs::write(crate_dir.join("Cargo.toml"), rust_stub_cargo_toml("http"))
|
||||
.expect("write Cargo.toml");
|
||||
std::fs::write(crate_dir.join("main.rs"), source).expect("write main.rs");
|
||||
|
||||
|
|
@ -1229,7 +1527,7 @@ fn rust_http_shim_recorder_is_noop_without_log_env() {
|
|||
|
||||
let crate_dir = workdir.path().join("driver_no_log");
|
||||
std::fs::create_dir_all(&crate_dir).expect("create crate dir");
|
||||
std::fs::write(crate_dir.join("Cargo.toml"), RUST_STUB_CARGO_TOML)
|
||||
std::fs::write(crate_dir.join("Cargo.toml"), rust_stub_cargo_toml("http_no_log"))
|
||||
.expect("write Cargo.toml");
|
||||
std::fs::write(crate_dir.join("main.rs"), source).expect("write main.rs");
|
||||
|
||||
|
|
@ -1257,3 +1555,116 @@ fn rust_http_shim_recorder_is_noop_without_log_env() {
|
|||
events.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_sql_stub_captures_tautology_query_via_shim_recorder() {
|
||||
// Phase 10 (Track D.3) SQL recording: Rust leg of the side-channel
|
||||
// `__nyx_stub_sql_record` helper. Mirrors the Python / Node / PHP
|
||||
// SQL tests — the Rust fragment never opens a live SQLite handle
|
||||
// (no stdlib driver; rusqlite would force libsqlite3-dev onto the
|
||||
// CI matrix) so it surfaces the attempted tautology query through
|
||||
// the shim recorder as `driver = "manual"`. Uses the same
|
||||
// `extra_files`-driven `Cargo.toml` shape as the HTTP siblings so
|
||||
// `cargo run --quiet` resolves `libc` (referenced by the spliced
|
||||
// probe shim's `__nyx_install_crash_guard`).
|
||||
if !cargo_available() {
|
||||
eprintln!("SKIP: cargo not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = SqlStub::start(workdir.path()).expect("SqlStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
.recording_endpoint()
|
||||
.expect("SqlStub must publish a recording endpoint");
|
||||
|
||||
let fragment = std::fs::read_to_string(fixture_path("rust/sql/vuln/main.rs"))
|
||||
.expect("read rust sql fragment");
|
||||
let source = wrap_rust_fragment(&fragment, rust_probe_shim());
|
||||
|
||||
let crate_dir = workdir.path().join("driver_sql");
|
||||
std::fs::create_dir_all(&crate_dir).expect("create crate dir");
|
||||
std::fs::write(crate_dir.join("Cargo.toml"), rust_stub_cargo_toml("sql"))
|
||||
.expect("write Cargo.toml");
|
||||
std::fs::write(crate_dir.join("main.rs"), source).expect("write main.rs");
|
||||
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(crate_dir.join("Cargo.toml"))
|
||||
.env("CARGO_TARGET_DIR", rust_stub_target_dir())
|
||||
.env("NYX_SQL_ENDPOINT", &endpoint)
|
||||
.env(recording.0, &recording.1)
|
||||
.output()
|
||||
.expect("cargo run rust sql driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0; stdout = {}\nstderr = {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let events = stub.drain_events();
|
||||
assert!(
|
||||
!events.is_empty(),
|
||||
"SqlStub must capture at least one event after the Rust shim recorder fires"
|
||||
);
|
||||
let tautology = events
|
||||
.iter()
|
||||
.find(|e| e.summary.contains("OR 1=1"))
|
||||
.expect("recorded query must contain the tautology marker");
|
||||
assert_eq!(
|
||||
tautology.detail.get("driver").map(String::as_str),
|
||||
Some("manual"),
|
||||
"detail slice passed to __nyx_stub_sql_record must surface as event detail entries"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_sql_shim_recorder_is_noop_without_log_env() {
|
||||
if !cargo_available() {
|
||||
eprintln!("SKIP: cargo not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = SqlStub::start(workdir.path()).expect("SqlStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment = std::fs::read_to_string(fixture_path("rust/sql/vuln/main.rs"))
|
||||
.expect("read rust sql fragment");
|
||||
let source = wrap_rust_fragment(&fragment, rust_probe_shim());
|
||||
|
||||
let crate_dir = workdir.path().join("driver_sql_no_log");
|
||||
std::fs::create_dir_all(&crate_dir).expect("create crate dir");
|
||||
std::fs::write(crate_dir.join("Cargo.toml"), rust_stub_cargo_toml("sql_no_log"))
|
||||
.expect("write Cargo.toml");
|
||||
std::fs::write(crate_dir.join("main.rs"), source).expect("write main.rs");
|
||||
|
||||
let output = Command::new("cargo")
|
||||
.arg("run")
|
||||
.arg("--quiet")
|
||||
.arg("--manifest-path")
|
||||
.arg(crate_dir.join("Cargo.toml"))
|
||||
.env("CARGO_TARGET_DIR", rust_stub_target_dir())
|
||||
.env("NYX_SQL_ENDPOINT", &endpoint)
|
||||
.env_remove("NYX_SQL_LOG")
|
||||
.output()
|
||||
.expect("cargo run rust sql driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0 even without NYX_SQL_LOG; stdout = {}\nstderr = {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let events = stub.drain_events();
|
||||
assert!(
|
||||
events.is_empty(),
|
||||
"no events expected when the recording env var is unset, got {} entries",
|
||||
events.len()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue