mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
[pitboss/grind] deferred session-0014 (20260516T052512Z-20f8)
This commit is contained in:
parent
a2cc5f7700
commit
6a169f51b8
23 changed files with 737 additions and 29 deletions
|
|
@ -336,6 +336,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -278,6 +278,35 @@ function __nyx_stub_sql_record(query, detail) {
|
|||
// best-effort: stub recorder write failure is non-fatal.
|
||||
}
|
||||
}
|
||||
|
||||
// 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. Format matches the SQL
|
||||
// helper so the host-side merger parses both streams identically.
|
||||
function __nyx_stub_http_record(method, url, body, detail) {
|
||||
const _p = process.env.NYX_HTTP_LOG;
|
||||
if (!_p) return;
|
||||
const _fs = require('fs');
|
||||
try {
|
||||
let _buf = '';
|
||||
_buf += '# method: ' + String(method) + '\n';
|
||||
_buf += '# url: ' + String(url) + '\n';
|
||||
if (body !== undefined && body !== null) {
|
||||
_buf += '# body: ' + String(body) + '\n';
|
||||
}
|
||||
if (detail && typeof detail === 'object') {
|
||||
for (const _k of Object.keys(detail)) {
|
||||
_buf += '# ' + String(_k) + ': ' + String(detail[_k]) + '\n';
|
||||
}
|
||||
}
|
||||
_buf += String(method) + ' ' + String(url) + '\n';
|
||||
_fs.appendFileSync(_p, _buf);
|
||||
} catch (e) {
|
||||
// best-effort: stub recorder write failure is non-fatal.
|
||||
}
|
||||
}
|
||||
"#
|
||||
}
|
||||
|
||||
|
|
@ -465,6 +494,7 @@ pub fn chain_step(prev_output: Option<&[u8]>, is_typescript: bool) -> ChainStepH
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1074,4 +1104,17 @@ mod tests {
|
|||
"stub recorder must append to the log file"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_shim_publishes_stub_http_recorder() {
|
||||
let shim = probe_shim();
|
||||
assert!(
|
||||
shim.contains("function __nyx_stub_http_record"),
|
||||
"Node probe shim must define __nyx_stub_http_record"
|
||||
);
|
||||
assert!(
|
||||
shim.contains("NYX_HTTP_LOG"),
|
||||
"stub recorder must read NYX_HTTP_LOG"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,14 @@ pub struct ChainStepHarness {
|
|||
pub filename: String,
|
||||
pub command: Vec<String>,
|
||||
pub extra_env: Vec<(String, String)>,
|
||||
/// Companion files staged alongside [`Self::source`] in the chain
|
||||
/// step's workdir. Each entry is `(relative_path, content)`;
|
||||
/// subdirectories in `relative_path` are created automatically.
|
||||
/// Mirrors [`HarnessSource::extra_files`] so an emitter whose chain
|
||||
/// step needs a build manifest (Rust's `Cargo.toml`, future
|
||||
/// `pom.xml`, etc.) can ship it without smuggling everything into
|
||||
/// `source`.
|
||||
pub extra_files: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl ChainStepHarness {
|
||||
|
|
@ -156,6 +164,7 @@ pub fn default_chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -352,6 +353,28 @@ function __nyx_stub_sql_record($query, array $detail = []): void {
|
|||
if (substr($q, -1) !== "\n") $buf .= "\n";
|
||||
@file_put_contents($p, $buf, FILE_APPEND);
|
||||
}
|
||||
|
||||
// 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. Format matches the SQL
|
||||
// helper so the host-side merger parses both streams identically.
|
||||
function __nyx_stub_http_record($method, $url, $body = null, array $detail = []): void {
|
||||
$p = getenv('NYX_HTTP_LOG');
|
||||
if ($p === false || $p === '') return;
|
||||
$buf = '';
|
||||
$buf .= '# method: ' . (string)$method . "\n";
|
||||
$buf .= '# url: ' . (string)$url . "\n";
|
||||
if ($body !== null) {
|
||||
$buf .= '# body: ' . (string)$body . "\n";
|
||||
}
|
||||
foreach ($detail as $k => $v) {
|
||||
$buf .= '# ' . (string)$k . ': ' . (string)$v . "\n";
|
||||
}
|
||||
$buf .= (string)$method . ' ' . (string)$url . "\n";
|
||||
@file_put_contents($p, $buf, FILE_APPEND);
|
||||
}
|
||||
"#
|
||||
}
|
||||
|
||||
|
|
@ -751,6 +774,19 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_shim_publishes_stub_http_recorder() {
|
||||
let shim = probe_shim();
|
||||
assert!(
|
||||
shim.contains("function __nyx_stub_http_record"),
|
||||
"PHP 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>"));
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -382,6 +383,29 @@ def __nyx_stub_sql_record(query, **detail):
|
|||
_f.write('\n')
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# 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. Format matches the SQL
|
||||
# helper so the host-side merger parses both streams identically.
|
||||
def __nyx_stub_http_record(method, url, body=None, **detail):
|
||||
import os
|
||||
p = os.environ.get("NYX_HTTP_LOG")
|
||||
if not p:
|
||||
return
|
||||
try:
|
||||
with open(p, "a") as _f:
|
||||
_f.write('# method: %s\n' % str(method))
|
||||
_f.write('# url: %s\n' % str(url))
|
||||
if body is not None:
|
||||
_f.write('# body: %s\n' % str(body))
|
||||
for k, v in detail.items():
|
||||
_f.write('# %s: %s\n' % (str(k), str(v)))
|
||||
_f.write('%s %s\n' % (str(method), str(url)))
|
||||
except OSError:
|
||||
pass
|
||||
"#
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,24 +71,31 @@ impl LangEmitter for RustEmitter {
|
|||
|
||||
/// Phase 26 — Rust chain-step harness.
|
||||
///
|
||||
/// Emits a minimal `step.rs` file that reads `NYX_PREV_OUTPUT` and writes
|
||||
/// it on stdout. The chain composer drives the step with `rustc step.rs`
|
||||
/// (single-file build) — full Cargo crate scaffolding is reserved for
|
||||
/// chain members whose underlying finding already produced a HarnessSpec
|
||||
/// via the standard emit path.
|
||||
/// Splices the Rust probe shim ([`probe_shim`]) in front of a minimal
|
||||
/// driver that reads `NYX_PREV_OUTPUT` and writes it on stdout. The
|
||||
/// shim references `libc::*` from its `__nyx_install_crash_guard`
|
||||
/// definition, so a single-file `rustc step.rs` build cannot resolve
|
||||
/// the symbols. Instead the step ships a companion `Cargo.toml`
|
||||
/// pinning `libc = "0.2"` via [`ChainStepHarness::extra_files`] and
|
||||
/// drives the build through `cargo run --quiet`.
|
||||
fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
||||
let source = "use std::env;\nuse std::io::{self, Write};\n\nfn main() {\n let prev = env::var(\"NYX_PREV_OUTPUT\").unwrap_or_default();\n let _ = io::stdout().write_all(prev.as_bytes());\n}\n".to_owned();
|
||||
// Shell-wrap build + run so the step actually executes the compiled binary.
|
||||
// `ChainStepHarness.command` models a single process; without the wrap the
|
||||
// step ends after `rustc` exits and the next chain member sees no output.
|
||||
let shim = probe_shim();
|
||||
let driver = "use std::env;\nuse std::io::{self, Write};\n\nfn main() {\n let prev = env::var(\"NYX_PREV_OUTPUT\").unwrap_or_default();\n let _ = io::stdout().write_all(prev.as_bytes());\n}\n";
|
||||
let source = format!("{shim}\n{driver}");
|
||||
let cargo_toml = "[package]\n\
|
||||
name = \"nyx-chain-step\"\n\
|
||||
version = \"0.0.1\"\n\
|
||||
edition = \"2021\"\n\n\
|
||||
[[bin]]\n\
|
||||
name = \"step\"\n\
|
||||
path = \"step.rs\"\n\n\
|
||||
[dependencies]\n\
|
||||
libc = \"0.2\"\n"
|
||||
.to_owned();
|
||||
ChainStepHarness {
|
||||
source,
|
||||
filename: "step.rs".to_owned(),
|
||||
command: vec![
|
||||
"sh".to_owned(),
|
||||
"-c".to_owned(),
|
||||
"rustc step.rs -o step && ./step".to_owned(),
|
||||
],
|
||||
command: vec!["cargo".to_owned(), "run".to_owned(), "--quiet".to_owned()],
|
||||
extra_env: prev_output
|
||||
.map(|bytes| {
|
||||
vec![(
|
||||
|
|
@ -97,6 +104,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
|
|||
)]
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
extra_files: vec![("Cargo.toml".to_owned(), cargo_toml)],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -878,4 +886,67 @@ mod tests {
|
|||
let _ = generate_cargo_toml(Cap::CODE_EXEC);
|
||||
let _ = generate_cargo_toml(Cap::SSRF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_step_splices_probe_shim_for_composite_reverify() {
|
||||
// Phase 26 follow-up: Rust chain_step now splices the probe
|
||||
// shim ahead of the driver so a chain step that terminates at
|
||||
// a sink can drive the `__nyx_probe` channel directly. The
|
||||
// shim references `libc::*` so the step also ships a companion
|
||||
// `Cargo.toml` via `extra_files` and drives the build through
|
||||
// `cargo run --quiet` rather than single-file `rustc`.
|
||||
let step = chain_step(Some(b"prev-output"));
|
||||
assert!(
|
||||
step.source.contains("__nyx_probe shim (Phase 06"),
|
||||
"probe_shim banner missing from chain step source",
|
||||
);
|
||||
assert!(
|
||||
step.source.contains("fn __nyx_install_crash_guard("),
|
||||
"install_crash_guard missing from chain step source",
|
||||
);
|
||||
let shim_pos = step
|
||||
.source
|
||||
.find("__nyx_probe shim (Phase 06")
|
||||
.expect("shim banner");
|
||||
let main_pos = step.source.find("fn main()").expect("main fn");
|
||||
assert!(
|
||||
shim_pos < main_pos,
|
||||
"shim must be spliced before fn main(): shim={shim_pos} main={main_pos}",
|
||||
);
|
||||
assert_eq!(step.filename, "step.rs");
|
||||
assert_eq!(
|
||||
step.command,
|
||||
vec!["cargo".to_owned(), "run".to_owned(), "--quiet".to_owned()],
|
||||
);
|
||||
assert!(
|
||||
step.extra_env
|
||||
.iter()
|
||||
.any(|(k, v)| k == ChainStepHarness::PREV_OUTPUT_ENV && v == "prev-output"),
|
||||
"prev_output must be threaded through extra_env, got {:?}",
|
||||
step.extra_env,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_step_emits_cargo_toml_with_libc_dep() {
|
||||
let step = chain_step(None);
|
||||
let cargo = step
|
||||
.extra_files
|
||||
.iter()
|
||||
.find(|(n, _)| n == "Cargo.toml")
|
||||
.expect("Cargo.toml must be in extra_files for cargo run");
|
||||
let body = &cargo.1;
|
||||
assert!(
|
||||
body.contains("libc = \"0.2\""),
|
||||
"Cargo.toml must pin libc for the probe shim's sigaction path, got: {body}",
|
||||
);
|
||||
assert!(
|
||||
body.contains("path = \"step.rs\""),
|
||||
"[[bin]] must point at step.rs so cargo run picks it up, got: {body}",
|
||||
);
|
||||
assert!(
|
||||
body.contains("edition = \"2021\""),
|
||||
"Cargo.toml must declare edition 2021, got: {body}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue