diff --git a/assets/screenshots/cli-scan.gif b/assets/screenshots/cli-scan.gif index 5c002bae..a3e84915 100644 Binary files a/assets/screenshots/cli-scan.gif and b/assets/screenshots/cli-scan.gif differ diff --git a/assets/screenshots/cli-scan.png b/assets/screenshots/cli-scan.png index 91e43979..86d668ac 100644 Binary files a/assets/screenshots/cli-scan.png and b/assets/screenshots/cli-scan.png differ diff --git a/assets/screenshots/cli-scan_raw.gif b/assets/screenshots/cli-scan_raw.gif index fdd9a9fc..63151fb2 100644 Binary files a/assets/screenshots/cli-scan_raw.gif and b/assets/screenshots/cli-scan_raw.gif differ diff --git a/assets/screenshots/cli-scan_raw.png b/assets/screenshots/cli-scan_raw.png index ef68b052..0f14efa9 100644 Binary files a/assets/screenshots/cli-scan_raw.png and b/assets/screenshots/cli-scan_raw.png differ diff --git a/assets/screenshots/demo-combo.gif b/assets/screenshots/demo-combo.gif index fb9f394c..943fe777 100644 Binary files a/assets/screenshots/demo-combo.gif and b/assets/screenshots/demo-combo.gif differ diff --git a/assets/screenshots/demo.gif b/assets/screenshots/demo.gif index 382f767b..164cd629 100644 Binary files a/assets/screenshots/demo.gif and b/assets/screenshots/demo.gif differ diff --git a/assets/screenshots/demo_raw.gif b/assets/screenshots/demo_raw.gif index 8e0a3d3d..b18283af 100644 Binary files a/assets/screenshots/demo_raw.gif and b/assets/screenshots/demo_raw.gif differ diff --git a/assets/screenshots/docs/cli-failon.png b/assets/screenshots/docs/cli-failon.png index 91e43979..86d668ac 100644 Binary files a/assets/screenshots/docs/cli-failon.png and b/assets/screenshots/docs/cli-failon.png differ diff --git a/assets/screenshots/docs/cli-failon_raw.png b/assets/screenshots/docs/cli-failon_raw.png index ef68b052..0f14efa9 100644 Binary files a/assets/screenshots/docs/cli-failon_raw.png and b/assets/screenshots/docs/cli-failon_raw.png differ diff --git a/assets/screenshots/docs/cli-idxstatus.png b/assets/screenshots/docs/cli-idxstatus.png index 38853548..b2eb8bc3 100644 Binary files a/assets/screenshots/docs/cli-idxstatus.png and b/assets/screenshots/docs/cli-idxstatus.png differ diff --git a/assets/screenshots/docs/cli-idxstatus_raw.png b/assets/screenshots/docs/cli-idxstatus_raw.png index 376ca8d7..9548b999 100644 Binary files a/assets/screenshots/docs/cli-idxstatus_raw.png and b/assets/screenshots/docs/cli-idxstatus_raw.png differ diff --git a/assets/screenshots/docs/serve-config.png b/assets/screenshots/docs/serve-config.png index 0a1e1b19..34845f87 100644 Binary files a/assets/screenshots/docs/serve-config.png and b/assets/screenshots/docs/serve-config.png differ diff --git a/assets/screenshots/docs/serve-config_raw.png b/assets/screenshots/docs/serve-config_raw.png index 5d27260e..13b57350 100644 Binary files a/assets/screenshots/docs/serve-config_raw.png and b/assets/screenshots/docs/serve-config_raw.png differ diff --git a/assets/screenshots/docs/serve-explorer.png b/assets/screenshots/docs/serve-explorer.png index a769ff70..e1c2edc5 100644 Binary files a/assets/screenshots/docs/serve-explorer.png and b/assets/screenshots/docs/serve-explorer.png differ diff --git a/assets/screenshots/docs/serve-explorer_raw.png b/assets/screenshots/docs/serve-explorer_raw.png index c3daaf73..f39b74fc 100644 Binary files a/assets/screenshots/docs/serve-explorer_raw.png and b/assets/screenshots/docs/serve-explorer_raw.png differ diff --git a/assets/screenshots/docs/serve-finding-detail.png b/assets/screenshots/docs/serve-finding-detail.png index 69a65180..c6da3236 100644 Binary files a/assets/screenshots/docs/serve-finding-detail.png and b/assets/screenshots/docs/serve-finding-detail.png differ diff --git a/assets/screenshots/docs/serve-finding-detail_raw.png b/assets/screenshots/docs/serve-finding-detail_raw.png index 5cb2eb1e..b9de52ba 100644 Binary files a/assets/screenshots/docs/serve-finding-detail_raw.png and b/assets/screenshots/docs/serve-finding-detail_raw.png differ diff --git a/assets/screenshots/docs/serve-findings-list.png b/assets/screenshots/docs/serve-findings-list.png index d8e88250..3c07f9c7 100644 Binary files a/assets/screenshots/docs/serve-findings-list.png and b/assets/screenshots/docs/serve-findings-list.png differ diff --git a/assets/screenshots/docs/serve-findings-list_raw.png b/assets/screenshots/docs/serve-findings-list_raw.png index 29ee7f61..27fa1d22 100644 Binary files a/assets/screenshots/docs/serve-findings-list_raw.png and b/assets/screenshots/docs/serve-findings-list_raw.png differ diff --git a/assets/screenshots/docs/serve-overview.png b/assets/screenshots/docs/serve-overview.png index 4d86004f..016ff0d4 100644 Binary files a/assets/screenshots/docs/serve-overview.png and b/assets/screenshots/docs/serve-overview.png differ diff --git a/assets/screenshots/docs/serve-overview_raw.png b/assets/screenshots/docs/serve-overview_raw.png index eec47b6b..0bc9054f 100644 Binary files a/assets/screenshots/docs/serve-overview_raw.png and b/assets/screenshots/docs/serve-overview_raw.png differ diff --git a/assets/screenshots/docs/serve-rules.png b/assets/screenshots/docs/serve-rules.png index 18b28ad5..2c273239 100644 Binary files a/assets/screenshots/docs/serve-rules.png and b/assets/screenshots/docs/serve-rules.png differ diff --git a/assets/screenshots/docs/serve-rules_raw.png b/assets/screenshots/docs/serve-rules_raw.png index 15d6229f..93aab005 100644 Binary files a/assets/screenshots/docs/serve-rules_raw.png and b/assets/screenshots/docs/serve-rules_raw.png differ diff --git a/assets/screenshots/docs/serve-scan-detail.png b/assets/screenshots/docs/serve-scan-detail.png index a0d9420b..6da1ee71 100644 Binary files a/assets/screenshots/docs/serve-scan-detail.png and b/assets/screenshots/docs/serve-scan-detail.png differ diff --git a/assets/screenshots/docs/serve-scan-detail_raw.png b/assets/screenshots/docs/serve-scan-detail_raw.png index 0e25c072..e7535a19 100644 Binary files a/assets/screenshots/docs/serve-scan-detail_raw.png and b/assets/screenshots/docs/serve-scan-detail_raw.png differ diff --git a/assets/screenshots/docs/serve-scans.png b/assets/screenshots/docs/serve-scans.png index b422c53c..b3952ff4 100644 Binary files a/assets/screenshots/docs/serve-scans.png and b/assets/screenshots/docs/serve-scans.png differ diff --git a/assets/screenshots/docs/serve-scans_raw.png b/assets/screenshots/docs/serve-scans_raw.png index 8ada75ef..024cc675 100644 Binary files a/assets/screenshots/docs/serve-scans_raw.png and b/assets/screenshots/docs/serve-scans_raw.png differ diff --git a/assets/screenshots/docs/serve-triage.png b/assets/screenshots/docs/serve-triage.png index 4c8e91e7..8f1c3bd0 100644 Binary files a/assets/screenshots/docs/serve-triage.png and b/assets/screenshots/docs/serve-triage.png differ diff --git a/assets/screenshots/docs/serve-triage_raw.png b/assets/screenshots/docs/serve-triage_raw.png index 0b72b704..6f64c8e3 100644 Binary files a/assets/screenshots/docs/serve-triage_raw.png and b/assets/screenshots/docs/serve-triage_raw.png differ diff --git a/assets/screenshots/overview.png b/assets/screenshots/overview.png index 4d86004f..016ff0d4 100644 Binary files a/assets/screenshots/overview.png and b/assets/screenshots/overview.png differ diff --git a/src/dynamic/harness.rs b/src/dynamic/harness.rs index 4e6ee3d7..a5c23663 100644 --- a/src/dynamic/harness.rs +++ b/src/dynamic/harness.rs @@ -16,6 +16,7 @@ use crate::dynamic::lang; use crate::dynamic::spec::HarnessSpec; use crate::evidence::UnsupportedReason; +use crate::labels::Cap; use std::fs; use std::io; use std::path::{Path, PathBuf}; @@ -23,6 +24,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; static WORKDIR_COUNTER: AtomicU64 = AtomicU64::new(0); +const HARNESS_BASE_ENV: &str = "NYX_HARNESS_BASE"; /// A built harness ready to hand off to the sandbox. #[derive(Debug, Clone)] @@ -69,6 +71,11 @@ pub fn build(spec: &HarnessSpec) -> Result { /// shallow (resolves to `/private/tmp` on macOS, `/tmp` on Linux) and keeps /// payload depth assumptions portable. /// +/// Tests and hermetic callers may set `NYX_HARNESS_BASE` to redirect this +/// scratch tree away from a small system `/tmp`. `CARGO_TARGET_TMPDIR` is used +/// automatically for non-FILE_IO cargo test runs; FILE_IO keeps the shallow +/// default unless the explicit override is present. +/// /// The per-run suffix is intentional: the workdir contains mutable build /// products, probe channels, and sometimes a long-lived Docker container /// mount. Reusing `/tmp/nyx-harness/{spec_hash}` across concurrent @@ -78,11 +85,7 @@ fn stage_harness( spec: &HarnessSpec, harness_src: &lang::HarnessSource, ) -> Result { - let base_dir = if cfg!(unix) { - PathBuf::from("/tmp/nyx-harness") - } else { - std::env::temp_dir().join("nyx-harness") - }; + let base_dir = harness_base_dir(spec); let workdir = unique_workdir(&base_dir, &spec.spec_hash); fs::create_dir_all(&workdir)?; @@ -120,6 +123,37 @@ fn stage_harness( Ok(workdir) } +fn harness_base_dir(spec: &HarnessSpec) -> PathBuf { + let explicit = std::env::var_os(HARNESS_BASE_ENV) + .filter(|p| !p.is_empty()) + .map(PathBuf::from); + harness_base_dir_from( + explicit, + std::env::var_os("CARGO_TARGET_TMPDIR").map(PathBuf::from), + spec, + ) +} + +fn harness_base_dir_from( + explicit: Option, + cargo_target_tmpdir: Option, + spec: &HarnessSpec, +) -> PathBuf { + if let Some(base) = explicit { + return base; + } + if !spec.expected_cap.contains(Cap::FILE_IO) + && let Some(base) = cargo_target_tmpdir.filter(|p| !p.as_os_str().is_empty()) + { + return base.join("nyx-harness"); + } + if cfg!(unix) { + PathBuf::from("/tmp/nyx-harness") + } else { + std::env::temp_dir().join("nyx-harness") + } +} + fn unique_workdir(base_dir: &Path, spec_hash: &str) -> PathBuf { let seq = WORKDIR_COUNTER.fetch_add(1, Ordering::Relaxed); let pid = std::process::id(); diff --git a/src/dynamic/runner.rs b/src/dynamic/runner.rs index 9ef9d365..532ea53f 100644 --- a/src/dynamic/runner.rs +++ b/src/dynamic/runner.rs @@ -181,6 +181,25 @@ pub enum RunError { }, } +struct HarnessWorkdirCleanup { + path: PathBuf, +} + +impl HarnessWorkdirCleanup { + fn new(path: PathBuf) -> Self { + Self { path } + } +} + +impl Drop for HarnessWorkdirCleanup { + fn drop(&mut self) { + if std::env::var_os("NYX_KEEP_HARNESS").is_some() { + return; + } + let _ = std::fs::remove_dir_all(&self.path); + } +} + impl From for RunError { fn from(e: SandboxError) -> Self { RunError::Sandbox(e) @@ -273,6 +292,7 @@ pub fn run_spec(spec: &HarnessSpec, opts: &SandboxOptions) -> Result return Err(RunError::Harness(e)), } }; + let _harness_workdir_cleanup = HarnessWorkdirCleanup::new(harness.workdir.clone()); // Build-time isolation and dependency setup — dispatched by language. match spec.lang { diff --git a/tests/common/fixture_harness.rs b/tests/common/fixture_harness.rs index b4318c85..ac05610f 100644 --- a/tests/common/fixture_harness.rs +++ b/tests/common/fixture_harness.rs @@ -327,8 +327,17 @@ pub fn run_fixture_and_compare_to_golden(spec: &FixtureSpec<'_>) { let fixture_src = fixture_root.join(spec.fixture); let golden_path = fixture_root.join(format!("{}.golden.json", spec.fixture)); - let tmp = TempDir::new().expect("create tempdir"); + let tmp = fixture_tempdir(); let diag_path = stage_fixture(&fixture_src, &tmp, spec.copy); + let previous_harness_base = if should_redirect_harness_base(spec.cap) { + let previous = std::env::var_os("NYX_HARNESS_BASE"); + unsafe { + std::env::set_var("NYX_HARNESS_BASE", tmp.path().join("harness")); + } + Some(previous) + } else { + None + }; // SAFETY: env mutation is serialised by FIXTURE_LOCK and the vars are // cleared before the lock guard drops at end of function. @@ -361,6 +370,12 @@ pub fn run_fixture_and_compare_to_golden(spec: &FixtureSpec<'_>) { unsafe { std::env::remove_var("NYX_REPRO_BASE"); std::env::remove_var("NYX_TELEMETRY_PATH"); + if let Some(previous) = previous_harness_base { + match previous { + Some(path) => std::env::set_var("NYX_HARNESS_BASE", path), + None => std::env::remove_var("NYX_HARNESS_BASE"), + } + } } let current = GoldenVerdict::from(&result); @@ -403,6 +418,28 @@ fn fixture_dir(lang_dir: &str) -> PathBuf { .join(lang_dir) } +fn fixture_scratch_root() -> PathBuf { + std::env::var_os("CARGO_TARGET_TMPDIR") + .filter(|p| !p.is_empty()) + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target")) + .join("nyx-fixtures") +} + +fn fixture_tempdir() -> TempDir { + let root = fixture_scratch_root(); + std::fs::create_dir_all(&root) + .unwrap_or_else(|e| panic!("create fixture scratch root {}: {e}", root.display())); + tempfile::Builder::new() + .prefix("nyx-fixture-") + .tempdir_in(&root) + .expect("create fixture tempdir") +} + +fn should_redirect_harness_base(cap: Cap) -> bool { + !cap.contains(Cap::FILE_IO) +} + fn stage_fixture(src: &Path, tmp: &TempDir, copy: CopyStrategy) -> PathBuf { match copy { CopyStrategy::PreserveName => { @@ -490,9 +527,18 @@ pub fn run_shape_fixture_lang( .join(shape_dir); let fixture_src = fixture_root.join(file); - let tmp = TempDir::new().expect("create tempdir"); + let tmp = fixture_tempdir(); let dst = tmp.path().join(file); std::fs::copy(&fixture_src, &dst).expect("copy fixture into tempdir"); + let previous_harness_base = if should_redirect_harness_base(cap) { + let previous = std::env::var_os("NYX_HARNESS_BASE"); + unsafe { + std::env::set_var("NYX_HARNESS_BASE", tmp.path().join("harness")); + } + Some(previous) + } else { + None + }; // SAFETY: env mutation is serialised by FIXTURE_LOCK and cleared at end. unsafe { @@ -575,6 +621,12 @@ pub fn run_shape_fixture_lang( unsafe { std::env::remove_var("NYX_REPRO_BASE"); std::env::remove_var("NYX_TELEMETRY_PATH"); + if let Some(previous) = previous_harness_base { + match previous { + Some(path) => std::env::set_var("NYX_HARNESS_BASE", path), + None => std::env::remove_var("NYX_HARNESS_BASE"), + } + } } // Project the [`RunOutcome`] / [`RunError`] back onto a @@ -859,7 +911,7 @@ pub fn run_harness_snapshot_lang( // Stage into tempdir so the spec.entry_file path matches what the // verifier sees at runtime. - let tmp = TempDir::new().expect("create tempdir"); + let tmp = fixture_tempdir(); let dst = tmp.path().join(file); std::fs::copy(&fixture_src, &dst).expect("copy fixture into tempdir"); let entry_file = dst.to_string_lossy().into_owned();