mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss/grind] deferred session-0016 (20260516T052512Z-20f8)
This commit is contained in:
parent
f701b43152
commit
608929194d
5 changed files with 287 additions and 108 deletions
|
|
@ -273,7 +273,7 @@ fn is_go_stdlib(path: &str) -> bool {
|
|||
/// Track C.1). Variadic over `string` so callers can pass any number of
|
||||
/// captured args at the sink site.
|
||||
pub fn probe_shim() -> &'static str {
|
||||
r#"
|
||||
r##"
|
||||
// ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ──────
|
||||
var __nyx_deny_substrings = []string{
|
||||
"TOKEN","SECRET","PASSWORD","PASSWD","API_KEY","APIKEY","PRIVATE_KEY",
|
||||
|
|
@ -402,7 +402,38 @@ func __nyx_recover_crash(sinkCallee string) func() {
|
|||
}
|
||||
}
|
||||
}
|
||||
"#
|
||||
|
||||
// Phase 10 (Track D.3) HTTP recording helper. When the verifier
|
||||
// spawned an HttpStub it publishes the side-channel log path
|
||||
// through NYX_HTTP_LOG; a sink call site whose outbound request
|
||||
// never reaches the on-the-wire listener (DNS-mocked,
|
||||
// network-isolated sandbox, pre-flight check) can call this helper
|
||||
// to surface the attempted call. Hash-prefixed detail lines plus a
|
||||
// trailing summary line match the Python / Node / PHP siblings so
|
||||
// the host-side HttpStub merger parses all four streams identically.
|
||||
// No-op when NYX_HTTP_LOG is unset so the same harness still runs
|
||||
// cleanly under modes that did not spawn a stub.
|
||||
func __nyx_stub_http_record(method, url, body string, detail map[string]string) {
|
||||
p := os.Getenv("NYX_HTTP_LOG")
|
||||
if p == "" {
|
||||
return
|
||||
}
|
||||
f, err := os.OpenFile(p, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
f.WriteString("# method: " + method + "\n")
|
||||
f.WriteString("# url: " + url + "\n")
|
||||
if body != "" {
|
||||
f.WriteString("# body: " + body + "\n")
|
||||
}
|
||||
for k, v := range detail {
|
||||
f.WriteString("# " + k + ": " + v + "\n")
|
||||
}
|
||||
f.WriteString(method + " " + url + "\n")
|
||||
}
|
||||
"##
|
||||
}
|
||||
|
||||
/// Emit a Go harness for `spec`.
|
||||
|
|
@ -877,6 +908,19 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_shim_publishes_stub_http_recorder() {
|
||||
let shim = probe_shim();
|
||||
assert!(
|
||||
shim.contains("func __nyx_stub_http_record"),
|
||||
"Go probe shim must define __nyx_stub_http_record"
|
||||
);
|
||||
assert!(
|
||||
shim.contains("NYX_HTTP_LOG"),
|
||||
"stub recorder must read NYX_HTTP_LOG"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_step_splices_probe_shim_for_composite_reverify() {
|
||||
let step = chain_step(Some(b"<prev>"));
|
||||
|
|
|
|||
27
tests/dynamic_fixtures/stubs_e2e/go/http/vuln/main.go
Normal file
27
tests/dynamic_fixtures/stubs_e2e/go/http/vuln/main.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Phase 10 (Track D.3) stub-end-to-end fixture: Go + HTTP.
|
||||
//
|
||||
// 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_HTTP_ENDPOINT — http://127.0.0.1:{port} the HttpStub listens on.
|
||||
// NYX_HTTP_LOG — companion log path the harness appends attempted
|
||||
// outbound calls to so the host HttpStub picks
|
||||
// them up on drain_events() even when the request
|
||||
// bypasses the on-the-wire listener (DNS-mocked,
|
||||
// network-isolated sandbox, pre-flight check).
|
||||
//
|
||||
// This fragment records an attempted SSRF call to
|
||||
// http://169.254.169.254/latest/meta-data/ through the Go shim helper
|
||||
// __nyx_stub_http_record without issuing the actual network call.
|
||||
method := "GET"
|
||||
url := "http://169.254.169.254/latest/meta-data/"
|
||||
body := ""
|
||||
__nyx_stub_http_record(method, url, body, map[string]string{"driver": "net/http"})
|
||||
// Echo so the host can confirm the driver ran end-to-end.
|
||||
fmt.Print(os.Getenv("NYX_HTTP_ENDPOINT"))
|
||||
|
|
@ -455,20 +455,12 @@ mod go_fixture_tests {
|
|||
|
||||
#[cfg(feature = "dynamic")]
|
||||
mod phase15_shape_tests {
|
||||
use crate::common::fixture_harness::run_shape_fixture_lang;
|
||||
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
|
||||
use nyx_scanner::dynamic::spec::PayloadSlot;
|
||||
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
|
||||
use nyx_scanner::labels::Cap;
|
||||
use nyx_scanner::symbol::Lang;
|
||||
|
||||
fn go_available() -> bool {
|
||||
std::process::Command::new("go")
|
||||
.arg("version")
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn assert_confirmed(shape: &str, result: &VerifyResult) {
|
||||
assert_eq!(
|
||||
result.status,
|
||||
|
|
@ -504,8 +496,15 @@ mod phase15_shape_tests {
|
|||
sink_line: u32,
|
||||
kind: EntryKind,
|
||||
slot: PayloadSlot,
|
||||
) -> VerifyResult {
|
||||
run_shape_fixture_lang(
|
||||
) -> Option<VerifyResult> {
|
||||
// Phase 29 (Track I): replace the bespoke `go_available()` +
|
||||
// per-test `eprintln!("SKIP ..."); return;` blocks with the
|
||||
// structured `Prerequisite::CommandAvailable("go")` gate. The
|
||||
// helper emits the same SKIP line and returns `None` so each
|
||||
// test can short-circuit via `let Some(r) = run(...) else {
|
||||
// return; };`.
|
||||
run_shape_fixture_lang_or_skip(
|
||||
&[Prerequisite::CommandAvailable("go")],
|
||||
Lang::Go, "go", shape, file, func, cap, sink_line, kind, slot,
|
||||
)
|
||||
}
|
||||
|
|
@ -514,27 +513,23 @@ mod phase15_shape_tests {
|
|||
|
||||
#[test]
|
||||
fn handler_func_vuln_is_confirmed() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"handler_func", "vuln.go", "Handle", Cap::CODE_EXEC, 17,
|
||||
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed("handler_func", &r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handler_func_benign_not_confirmed() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"handler_func", "benign.go", "Handle", Cap::CODE_EXEC, 14,
|
||||
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_not_confirmed("handler_func", &r);
|
||||
}
|
||||
|
||||
|
|
@ -542,27 +537,23 @@ mod phase15_shape_tests {
|
|||
|
||||
#[test]
|
||||
fn gin_handler_vuln_is_confirmed() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"gin_handler", "vuln.go", "Handle", Cap::CODE_EXEC, 16,
|
||||
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed("gin_handler", &r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gin_handler_benign_not_confirmed() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"gin_handler", "benign.go", "Handle", Cap::CODE_EXEC, 14,
|
||||
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_not_confirmed("gin_handler", &r);
|
||||
}
|
||||
|
||||
|
|
@ -570,27 +561,23 @@ mod phase15_shape_tests {
|
|||
|
||||
#[test]
|
||||
fn flag_cli_vuln_is_confirmed() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"flag_cli", "vuln.go", "Run", Cap::CODE_EXEC, 19,
|
||||
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed("flag_cli", &r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flag_cli_benign_not_confirmed() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"flag_cli", "benign.go", "Run", Cap::CODE_EXEC, 15,
|
||||
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_not_confirmed("flag_cli", &r);
|
||||
}
|
||||
|
||||
|
|
@ -598,27 +585,23 @@ mod phase15_shape_tests {
|
|||
|
||||
#[test]
|
||||
fn fuzz_variadic_vuln_is_confirmed() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"fuzz_variadic", "vuln.go", "FuzzHandle", Cap::CODE_EXEC, 14,
|
||||
EntryKind::Function, PayloadSlot::Param(0),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed("fuzz_variadic", &r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_variadic_benign_not_confirmed() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"fuzz_variadic", "benign.go", "FuzzHandle", Cap::CODE_EXEC, 14,
|
||||
EntryKind::Function, PayloadSlot::Param(0),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_not_confirmed("fuzz_variadic", &r);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -455,20 +455,12 @@ mod php_fixture_tests {
|
|||
|
||||
#[cfg(feature = "dynamic")]
|
||||
mod phase15_shape_tests {
|
||||
use crate::common::fixture_harness::run_shape_fixture_lang;
|
||||
use crate::common::fixture_harness::{run_shape_fixture_lang_or_skip, Prerequisite};
|
||||
use nyx_scanner::dynamic::spec::PayloadSlot;
|
||||
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
|
||||
use nyx_scanner::labels::Cap;
|
||||
use nyx_scanner::symbol::Lang;
|
||||
|
||||
fn php_available() -> bool {
|
||||
std::process::Command::new("php")
|
||||
.arg("--version")
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn assert_confirmed(shape: &str, result: &VerifyResult) {
|
||||
assert_eq!(
|
||||
result.status,
|
||||
|
|
@ -504,8 +496,15 @@ mod phase15_shape_tests {
|
|||
sink_line: u32,
|
||||
kind: EntryKind,
|
||||
slot: PayloadSlot,
|
||||
) -> VerifyResult {
|
||||
run_shape_fixture_lang(
|
||||
) -> Option<VerifyResult> {
|
||||
// Phase 29 (Track I): replace the bespoke `php_available()` +
|
||||
// per-test `eprintln!("SKIP ..."); return;` blocks with the
|
||||
// structured `Prerequisite::CommandAvailable("php")` gate. The
|
||||
// helper emits the same SKIP line and returns `None` so each
|
||||
// test can short-circuit via `let Some(r) = run(...) else {
|
||||
// return; };`.
|
||||
run_shape_fixture_lang_or_skip(
|
||||
&[Prerequisite::CommandAvailable("php")],
|
||||
Lang::Php, "php", shape, file, func, cap, sink_line, kind, slot,
|
||||
)
|
||||
}
|
||||
|
|
@ -514,27 +513,23 @@ mod phase15_shape_tests {
|
|||
|
||||
#[test]
|
||||
fn route_closure_vuln_is_confirmed() {
|
||||
if !php_available() {
|
||||
eprintln!("SKIP: php not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"route_closure", "vuln.php", "run", Cap::CODE_EXEC, 10,
|
||||
EntryKind::HttpRoute, PayloadSlot::Param(0),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed("route_closure", &r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn route_closure_benign_not_confirmed() {
|
||||
if !php_available() {
|
||||
eprintln!("SKIP: php not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"route_closure", "benign.php", "run", Cap::CODE_EXEC, 11,
|
||||
EntryKind::HttpRoute, PayloadSlot::Param(0),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_not_confirmed("route_closure", &r);
|
||||
}
|
||||
|
||||
|
|
@ -542,27 +537,23 @@ mod phase15_shape_tests {
|
|||
|
||||
#[test]
|
||||
fn cli_script_vuln_is_confirmed() {
|
||||
if !php_available() {
|
||||
eprintln!("SKIP: php not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"cli_script", "vuln.php", "main", Cap::CODE_EXEC, 8,
|
||||
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed("cli_script", &r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_script_benign_not_confirmed() {
|
||||
if !php_available() {
|
||||
eprintln!("SKIP: php not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"cli_script", "benign.php", "main", Cap::CODE_EXEC, 11,
|
||||
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_not_confirmed("cli_script", &r);
|
||||
}
|
||||
|
||||
|
|
@ -570,27 +561,23 @@ mod phase15_shape_tests {
|
|||
|
||||
#[test]
|
||||
fn top_level_script_vuln_is_confirmed() {
|
||||
if !php_available() {
|
||||
eprintln!("SKIP: php not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"top_level_script", "vuln.php", "", Cap::CODE_EXEC, 8,
|
||||
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed("top_level_script", &r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_script_benign_not_confirmed() {
|
||||
if !php_available() {
|
||||
eprintln!("SKIP: php not available");
|
||||
return;
|
||||
}
|
||||
let r = run(
|
||||
let Some(r) = run(
|
||||
"top_level_script", "benign.php", "", Cap::CODE_EXEC, 10,
|
||||
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
|
||||
);
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
assert_not_confirmed("top_level_script", &r);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#![cfg(feature = "dynamic")]
|
||||
|
||||
use nyx_scanner::dynamic::lang::go::probe_shim as go_probe_shim;
|
||||
use nyx_scanner::dynamic::lang::javascript::probe_shim as node_probe_shim;
|
||||
use nyx_scanner::dynamic::lang::php::probe_shim as php_probe_shim;
|
||||
use nyx_scanner::dynamic::lang::python::probe_shim as python_probe_shim;
|
||||
|
|
@ -52,6 +53,39 @@ fn php_available() -> bool {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn go_available() -> bool {
|
||||
Command::new("go")
|
||||
.arg("version")
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Wrap the body-only Go HTTP fixture in a complete `package main`
|
||||
/// program: stdlib imports needed by the spliced probe shim plus the
|
||||
/// fragment's own `fmt` / `os` references, the shim itself, and the
|
||||
/// fragment as the body of `func main`. Comments inside the body
|
||||
/// remain valid Go.
|
||||
fn wrap_go_fragment(body: &str, shim: &str) -> String {
|
||||
format!(
|
||||
"package main\n\
|
||||
\n\
|
||||
import (\n\
|
||||
\t\"encoding/json\"\n\
|
||||
\t\"fmt\"\n\
|
||||
\t\"os\"\n\
|
||||
\t\"os/signal\"\n\
|
||||
\t\"strings\"\n\
|
||||
\t\"syscall\"\n\
|
||||
\t\"time\"\n\
|
||||
)\n\
|
||||
{shim}\n\
|
||||
func main() {{\n\
|
||||
{body}\n\
|
||||
}}\n"
|
||||
)
|
||||
}
|
||||
|
||||
fn fixture_path(rel: &str) -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
|
|
@ -655,6 +689,110 @@ fn php_http_shim_recorder_is_noop_without_log_env() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn go_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
||||
// Phase 10 (Track D.3) HTTP recording: Go leg of the side-channel
|
||||
// `__nyx_stub_http_record` helper. Mirrors the Python HTTP test —
|
||||
// records an SSRF attempt without issuing the actual network call.
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
.recording_endpoint()
|
||||
.expect("HttpStub must publish a recording endpoint");
|
||||
|
||||
// Go fragments need wrapping: the file under tests/dynamic_fixtures
|
||||
// is a body-only fragment, not a standalone program.
|
||||
let fragment = std::fs::read_to_string(fixture_path("go/http/vuln/main.go"))
|
||||
.expect("read go fragment");
|
||||
let combined = wrap_go_fragment(&fragment, go_probe_shim());
|
||||
|
||||
let script_path = workdir.path().join("driver_http.go");
|
||||
std::fs::write(&script_path, combined).expect("write go driver");
|
||||
|
||||
let output = Command::new("go")
|
||||
.arg("run")
|
||||
.arg(&script_path)
|
||||
.env("NYX_HTTP_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(),
|
||||
"HttpStub must capture at least one event after the Go shim recorder fires"
|
||||
);
|
||||
let hit = events
|
||||
.iter()
|
||||
.find(|e| e.summary.contains("169.254.169.254"))
|
||||
.expect("recorded URL must contain the SSRF marker");
|
||||
assert_eq!(
|
||||
hit.detail.get("method").map(String::as_str),
|
||||
Some("GET"),
|
||||
"method detail must surface on the recorded event"
|
||||
);
|
||||
assert_eq!(
|
||||
hit.detail.get("url").map(String::as_str),
|
||||
Some("http://169.254.169.254/latest/meta-data/"),
|
||||
);
|
||||
assert_eq!(
|
||||
hit.detail.get("driver").map(String::as_str),
|
||||
Some("net/http"),
|
||||
"detail map passed to __nyx_stub_http_record must surface as event detail entries"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn go_http_shim_recorder_is_noop_without_log_env() {
|
||||
if !go_available() {
|
||||
eprintln!("SKIP: go not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment = std::fs::read_to_string(fixture_path("go/http/vuln/main.go"))
|
||||
.expect("read go fragment");
|
||||
let combined = wrap_go_fragment(&fragment, go_probe_shim());
|
||||
|
||||
let script_path = workdir.path().join("driver_http_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_HTTP_ENDPOINT", &endpoint)
|
||||
.env_remove("NYX_HTTP_LOG")
|
||||
.output()
|
||||
.expect("go driver");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0 even without NYX_HTTP_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 node_sql_shim_recorder_is_noop_without_log_env() {
|
||||
if !node_available() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue