mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss/grind] deferred session-0030 (20260517T044708Z-e058)
This commit is contained in:
parent
19d13a085d
commit
b41b24c416
5 changed files with 124 additions and 9 deletions
|
|
@ -307,14 +307,43 @@ fn source_ext_for_lang(lang: &crate::symbol::Lang) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
fn dockerfile_for_spec(spec: &HarnessSpec) -> String {
|
||||
/// Resolve the `FROM` reference for `toolchain_id`.
|
||||
///
|
||||
/// Prefers the pinned digest from
|
||||
/// [`crate::dynamic::toolchain::pinned_image_ref`] so the emitted
|
||||
/// Dockerfile is hermetic across hosts. Falls back to a tag-only
|
||||
/// reference derived from `toolchain_id` when the catalogue has no
|
||||
/// digest for the toolchain.
|
||||
fn resolve_dockerfile_from(spec: &HarnessSpec) -> String {
|
||||
use crate::symbol::Lang;
|
||||
|
||||
if let Some(pinned) = crate::dynamic::toolchain::pinned_image_ref(&spec.toolchain_id) {
|
||||
return pinned.to_owned();
|
||||
}
|
||||
|
||||
match spec.lang {
|
||||
Lang::Rust => {
|
||||
let toolchain = spec.toolchain_id.strip_prefix("rust-").unwrap_or("stable");
|
||||
format!("rust:{toolchain}-slim")
|
||||
}
|
||||
Lang::Python => {
|
||||
format!("python:{}", spec.toolchain_id.strip_prefix("python-").unwrap_or("3"))
|
||||
}
|
||||
_ => "ubuntu:latest".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dockerfile_for_spec(spec: &HarnessSpec) -> String {
|
||||
use crate::symbol::Lang;
|
||||
let image = resolve_dockerfile_from(spec);
|
||||
match spec.lang {
|
||||
Lang::Rust => {
|
||||
// Multi-stage: build with Rust, run the binary directly.
|
||||
// The builder stage uses the resolved (pinned-or-tag) image;
|
||||
// the runtime stage stays on debian:bookworm-slim because the
|
||||
// resulting nyx_harness binary is self-contained.
|
||||
format!(
|
||||
"FROM rust:{toolchain}-slim AS builder\n\
|
||||
"FROM {image} AS builder\n\
|
||||
WORKDIR /harness\n\
|
||||
COPY Cargo.toml Cargo.lock* ./\n\
|
||||
COPY src/ src/\n\
|
||||
|
|
@ -326,13 +355,12 @@ fn dockerfile_for_spec(spec: &HarnessSpec) -> String {
|
|||
)
|
||||
}
|
||||
Lang::Python => {
|
||||
let image = format!("python:{}", spec.toolchain_id.strip_prefix("python-").unwrap_or("3"));
|
||||
format!(
|
||||
"FROM {image}\nWORKDIR /harness\nCOPY harness.py .\nCMD [\"python3\", \"harness.py\"]\n"
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!("# Unsupported language: {:?}\nFROM ubuntu:latest\n", spec.lang)
|
||||
format!("# Unsupported language: {:?}\nFROM {image}\n", spec.lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -759,6 +787,47 @@ mod tests {
|
|||
unsafe { std::env::remove_var("NYX_REPRO_BASE") };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dockerfile_for_pinned_toolchain_uses_pinned_digest() {
|
||||
// python-3.11 is in the image catalogue with a pinned digest, so the
|
||||
// emitted Dockerfile must `FROM <base>@sha256:…` for hermeticity.
|
||||
let spec = make_spec();
|
||||
let pinned = crate::dynamic::toolchain::pinned_image_ref(&spec.toolchain_id)
|
||||
.expect("python-3.11 should resolve to a pinned digest in images.toml");
|
||||
assert!(
|
||||
pinned.contains("@sha256:"),
|
||||
"pinned_image_ref returned a non-pinned value: {pinned}",
|
||||
);
|
||||
let dockerfile = dockerfile_for_spec(&spec);
|
||||
let expected_from = format!("FROM {pinned}");
|
||||
assert!(
|
||||
dockerfile.contains(&expected_from),
|
||||
"dockerfile did not embed pinned digest;\n expected substring: {expected_from}\n got:\n{dockerfile}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dockerfile_falls_back_to_tag_when_toolchain_absent_from_catalogue() {
|
||||
// Unpinned toolchain id: no entry in IMAGE_DIGESTS, so the emitter
|
||||
// must fall back to a tag-only `FROM` so an operator can still build
|
||||
// the bundle (with a docker_pull.sh that is not emitted in this case).
|
||||
let mut spec = make_spec();
|
||||
spec.toolchain_id = "python-2.7".into();
|
||||
assert!(
|
||||
crate::dynamic::toolchain::pinned_image_ref(&spec.toolchain_id).is_none(),
|
||||
"test precondition: python-2.7 must NOT be in the catalogue",
|
||||
);
|
||||
let dockerfile = dockerfile_for_spec(&spec);
|
||||
assert!(
|
||||
dockerfile.contains("FROM python:2.7"),
|
||||
"fallback dockerfile missing tag-only FROM line:\n{dockerfile}",
|
||||
);
|
||||
assert!(
|
||||
!dockerfile.contains("@sha256:"),
|
||||
"fallback dockerfile must not invent a digest:\n{dockerfile}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reproduce_sh_contains_toolchain_check_and_exit_codes() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
|
|
|||
|
|
@ -85,6 +85,19 @@ pub struct VerifyOptions {
|
|||
/// Default `false`. [`Self::from_config`] honours the
|
||||
/// `NYX_VERIFY_REPLAY_STABLE` environment variable (`1` / `true`).
|
||||
pub replay_stable_check: bool,
|
||||
/// Phase 31 follow-up: when `true` and `replay_stable_check` is also
|
||||
/// `true`, the verifier passes `--docker` to `reproduce.sh` instead of
|
||||
/// running it through the host's process backend. Lets the eval-corpus
|
||||
/// driver mark `replay_stable` based on the bare-image replay path so
|
||||
/// the M7 ship-gate's Gate 5 reflects the docker bundle's green/red
|
||||
/// signal — required when the corpus walks a host that has stripped
|
||||
/// the language toolchains (the bare-image CI matrix at
|
||||
/// `.github/workflows/repro-bare.yml`).
|
||||
///
|
||||
/// Default `false`. [`Self::from_config`] honours the
|
||||
/// `NYX_VERIFY_REPLAY_DOCKER` environment variable (`1` / `true`).
|
||||
/// The flag is inert when `replay_stable_check == false`.
|
||||
pub replay_use_docker: bool,
|
||||
}
|
||||
|
||||
impl VerifyOptions {
|
||||
|
|
@ -141,6 +154,9 @@ impl VerifyOptions {
|
|||
let replay_stable_check = std::env::var("NYX_VERIFY_REPLAY_STABLE")
|
||||
.map(|v| matches!(v.as_str(), "1" | "true" | "TRUE"))
|
||||
.unwrap_or(false);
|
||||
let replay_use_docker = std::env::var("NYX_VERIFY_REPLAY_DOCKER")
|
||||
.map(|v| matches!(v.as_str(), "1" | "true" | "TRUE"))
|
||||
.unwrap_or(false);
|
||||
|
||||
Self {
|
||||
sandbox: SandboxOptions {
|
||||
|
|
@ -158,6 +174,7 @@ impl VerifyOptions {
|
|||
telemetry_policy: SamplingPolicy::from_config(&config.telemetry),
|
||||
trace_verbose: false,
|
||||
replay_stable_check,
|
||||
replay_use_docker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -760,7 +777,8 @@ pub fn verify_finding(diag: &Diag, opts: &VerifyOptions) -> VerifyResult {
|
|||
&& let Some(bundle) = crate::dynamic::repro::bundle_root_for(&spec.spec_hash)
|
||||
&& bundle.join("reproduce.sh").exists()
|
||||
{
|
||||
let replay = crate::dynamic::repro::replay_bundle(&bundle, &[]);
|
||||
let replay_args: &[&str] = if opts.replay_use_docker { &["--docker"] } else { &[] };
|
||||
let replay = crate::dynamic::repro::replay_bundle(&bundle, replay_args);
|
||||
verdict.replay_stable = crate::dynamic::repro::replay_stability(&replay);
|
||||
}
|
||||
|
||||
|
|
@ -1273,6 +1291,33 @@ mod tests {
|
|||
unsafe { std::env::remove_var("NYX_VERIFY_REPLAY_STABLE") };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_config_defaults_replay_use_docker_off() {
|
||||
// Same hermeticity concern as `replay_stable_check`: clear any
|
||||
// stale process-wide setting so the default is observable.
|
||||
unsafe { std::env::remove_var("NYX_VERIFY_REPLAY_DOCKER") };
|
||||
let opts = VerifyOptions::from_config(&Config::default());
|
||||
assert!(
|
||||
!opts.replay_use_docker,
|
||||
"NYX_VERIFY_REPLAY_DOCKER absent must leave the opt-in off so \
|
||||
interactive `nyx scan` does not require docker for the replay step"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_config_picks_up_replay_docker_env_flag() {
|
||||
unsafe { std::env::set_var("NYX_VERIFY_REPLAY_DOCKER", "1") };
|
||||
let opts = VerifyOptions::from_config(&Config::default());
|
||||
assert!(opts.replay_use_docker);
|
||||
unsafe { std::env::set_var("NYX_VERIFY_REPLAY_DOCKER", "true") };
|
||||
let opts = VerifyOptions::from_config(&Config::default());
|
||||
assert!(opts.replay_use_docker);
|
||||
unsafe { std::env::set_var("NYX_VERIFY_REPLAY_DOCKER", "0") };
|
||||
let opts = VerifyOptions::from_config(&Config::default());
|
||||
assert!(!opts.replay_use_docker);
|
||||
unsafe { std::env::remove_var("NYX_VERIFY_REPLAY_DOCKER") };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_config_defaults_process_hardening_to_standard() {
|
||||
use crate::dynamic::sandbox::ProcessHardeningProfile;
|
||||
|
|
|
|||
|
|
@ -258,8 +258,9 @@ fn python_3_11_flask_eval_bundle_structural_invariants() {
|
|||
|
||||
let dockerfile = std::fs::read_to_string(root.join("harness/Dockerfile.harness")).unwrap();
|
||||
assert!(
|
||||
dockerfile.contains("FROM python:3.11"),
|
||||
"dockerfile missing pinned FROM line",
|
||||
dockerfile.contains("FROM python:3.11-slim@sha256:"),
|
||||
"dockerfile missing pinned FROM line (expected `FROM python:3.11-slim@sha256:…` so the \
|
||||
bundle is hermetic across hosts); got:\n{dockerfile}",
|
||||
);
|
||||
|
||||
let payload = std::fs::read(root.join("payload/payload.bin")).unwrap();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.11
|
||||
FROM python:3.11-slim@sha256:9a7765b36773a37061455b332f18e265e7f58f6fea9c419a550d2a8b0e9db834
|
||||
WORKDIR /harness
|
||||
COPY harness.py .
|
||||
CMD ["python3", "harness.py"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"files": {
|
||||
"entry/extracted_source.py": "d18631435ec059c8cabafe7854f18d45e06a5c62da6274710712cf862cf9afa8",
|
||||
"harness/Dockerfile.harness": "88bfe406a6305222207469e68777e09e68c558e66b4b15ca7f31670cb74f91b5",
|
||||
"harness/Dockerfile.harness": "9ae78bdafc9cf11e9530f8c88deebc62b4c754c7ffa4759a40c80049c5a84586",
|
||||
"harness/harness.py": "15cc817251cf0c8915be782996b4af9b5b456f0b8fd75c360dcda153e071961c",
|
||||
"payload/payload.bin": "f3dc1d1a3d5a282cb6f171544ad5c8a5e78a6065a6decf6955c20763302bd574"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue