mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss/grind] deferred session-0022 (20260522T043516Z-29b8)
This commit is contained in:
parent
cc183a8186
commit
e9c35150de
4 changed files with 457 additions and 3 deletions
|
|
@ -53,4 +53,62 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
|||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Phase 08 tier-(b): raw-socket wire-frame smuggling payload.
|
||||
// Same CRLF-bearing bytes as the Node payload above, but pinned to
|
||||
// the `js_raw` fixture (a `net.createServer` callback writing raw
|
||||
// bytes via `socket.write`). The wire frame captured off the
|
||||
// response socket carries two distinct `Set-Cookie:` lines, so
|
||||
// `HeaderSmuggledInWire { primary: "Set-Cookie", smuggled:
|
||||
// "Set-Cookie" }` fires — proving the smuggled header survived to
|
||||
// the actual wire instead of being CRLF-stripped en route.
|
||||
//
|
||||
// Distinct payload (not just an extra predicate on the Node row)
|
||||
// because Node's `http.ServerResponse#setHeader` validator strips
|
||||
// CRLF at the wire-write boundary, so the wire-frame predicate
|
||||
// would never fire against the canonical Node fixture. See
|
||||
// `.pitboss/play/deferred.md` (Phase 08 wire-frame option A) for
|
||||
// the framework-level CRLF-strip empirical from session-0018.
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-js-raw-wire-smuggle",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/js_raw/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-js-raw-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-js-raw-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/js_raw/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1365,6 +1365,116 @@ pub fn emit_header_injection_harness(spec: &HarnessSpec) -> HarnessSource {
|
|||
|| entry_source.contains("from \"http\"")
|
||||
|| entry_source.contains("from 'express'")
|
||||
|| entry_source.contains("from \"express\"");
|
||||
// Phase 08 tier-(b): a fixture that uses `net.createServer` writes
|
||||
// bytes straight to the response socket via `socket.write`, bypassing
|
||||
// every framework-level CRLF validator (Node's
|
||||
// `http.ServerResponse#setHeader` / Express / axum / Tomcat all
|
||||
// strip CRLF before write). The harness boots the server on a
|
||||
// loopback port and captures the raw response-header block as a
|
||||
// `ProbeKind::HeaderWireFrame` probe. Mirrors the Python tier-(b)
|
||||
// at `src/dynamic/lang/python.rs::emit_header_injection_harness`.
|
||||
let uses_raw_socket = entry_source.contains("net.createServer")
|
||||
|| entry_source.contains("require('net')")
|
||||
|| entry_source.contains("require(\"net\")")
|
||||
|| entry_source.contains("from 'net'")
|
||||
|| entry_source.contains("from \"net\"");
|
||||
|
||||
let wire_frame_via_fixture = if uses_raw_socket {
|
||||
format!(
|
||||
r#"async function nyxWireFrameViaFixture(payload) {{
|
||||
// Phase 08 tier-(b): boot the fixture's net.Server on 127.0.0.1:0,
|
||||
// issue one raw-socket GET, read the bytes the handler wrote to the
|
||||
// response socket up to the CRLF-CRLF boundary. Returns the captured
|
||||
// header-block bytes on success, or `null` on import / boot failure so
|
||||
// the caller can fall back to the inline synthetic probe.
|
||||
const _net = require('net');
|
||||
let mod;
|
||||
try {{
|
||||
mod = require('./{entry_stem}');
|
||||
}} catch (e) {{
|
||||
return null;
|
||||
}}
|
||||
if (!mod || typeof mod.createServer !== 'function' || typeof mod.setCookieValue !== 'function') {{
|
||||
return null;
|
||||
}}
|
||||
try {{
|
||||
if (Buffer.isBuffer(payload)) {{
|
||||
mod.setCookieValue(payload);
|
||||
}} else {{
|
||||
mod.setCookieValue(Buffer.from(String(payload), 'utf8'));
|
||||
}}
|
||||
}} catch (e) {{
|
||||
return null;
|
||||
}}
|
||||
let server;
|
||||
try {{
|
||||
server = mod.createServer();
|
||||
}} catch (e) {{
|
||||
return null;
|
||||
}}
|
||||
const listenPort = await new Promise((resolve) => {{
|
||||
server.once('error', () => resolve(null));
|
||||
server.listen(0, '127.0.0.1', () => {{
|
||||
const addr = server.address();
|
||||
resolve(addr && typeof addr === 'object' ? addr.port : null);
|
||||
}});
|
||||
}});
|
||||
if (listenPort === null) {{
|
||||
try {{ server.close(); }} catch (e) {{}}
|
||||
return null;
|
||||
}}
|
||||
let raw = Buffer.alloc(0);
|
||||
await new Promise((resolve) => {{
|
||||
const client = _net.createConnection({{ host: '127.0.0.1', port: listenPort }}, () => {{
|
||||
try {{
|
||||
client.write('GET / HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n');
|
||||
}} catch (e) {{}}
|
||||
}});
|
||||
const timer = setTimeout(() => {{
|
||||
try {{ client.destroy(); }} catch (e) {{}}
|
||||
resolve();
|
||||
}}, 5000);
|
||||
client.on('data', (chunk) => {{
|
||||
raw = Buffer.concat([raw, chunk]);
|
||||
if (raw.length >= 65536 || raw.indexOf('\r\n\r\n') !== -1) {{
|
||||
try {{ client.end(); }} catch (e) {{}}
|
||||
}}
|
||||
}});
|
||||
client.on('end', () => {{ clearTimeout(timer); resolve(); }});
|
||||
client.on('error', () => {{ clearTimeout(timer); resolve(); }});
|
||||
client.on('close', () => {{ clearTimeout(timer); resolve(); }});
|
||||
}});
|
||||
try {{ server.close(); }} catch (e) {{}}
|
||||
const sep = raw.indexOf('\r\n\r\n');
|
||||
if (sep === -1) {{
|
||||
return raw;
|
||||
}}
|
||||
return raw.subarray(0, sep);
|
||||
}}
|
||||
|
||||
function nyxWireFrameProbe(rawBytes) {{
|
||||
const p = process.env.NYX_PROBE_PATH;
|
||||
if (!p) return;
|
||||
const rec = {{
|
||||
sink_callee: 'net.Server.socket.write',
|
||||
args: [],
|
||||
captured_at_ns: Number(process.hrtime.bigint()),
|
||||
payload_id: process.env.NYX_PAYLOAD_ID || '',
|
||||
kind: {{ kind: 'HeaderWireFrame', raw_bytes: Array.from(rawBytes) }},
|
||||
witness: __nyx_witness('net.Server.socket.write', []),
|
||||
}};
|
||||
try {{
|
||||
require('fs').appendFileSync(p, JSON.stringify(rec) + '\n');
|
||||
}} catch (e) {{
|
||||
// best-effort
|
||||
}}
|
||||
}}
|
||||
|
||||
"#
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let via_fixture = if uses_node_writer {
|
||||
format!(
|
||||
|
|
@ -1434,8 +1544,74 @@ pub fn emit_header_injection_harness(spec: &HarnessSpec) -> HarnessSource {
|
|||
"const name = 'Set-Cookie';\nconst value = payload;\nnyxHeaderProbe(name, value);\nconsole.log('__NYX_SINK_HIT__');\nconsole.log(JSON.stringify({ name: name, value: value }));\n"
|
||||
};
|
||||
|
||||
let body = format!(
|
||||
r#"// Nyx dynamic harness — HEADER_INJECTION http.ServerResponse#setHeader (Phase 08 / Track J.6).
|
||||
// Phase 08 tier-(b): when the fixture imports `net.createServer`, run
|
||||
// the wire-frame branch first (async IIFE awaits the loopback round
|
||||
// trip). When it succeeds, emit a `HeaderWireFrame` probe plus a
|
||||
// derived `HeaderEmit` per Set-Cookie line and exit. When it returns
|
||||
// null (require/boot failure), fall through to the existing sync
|
||||
// tier-(a) / synthetic path so the harness still produces some
|
||||
// signal.
|
||||
let body = if uses_raw_socket {
|
||||
format!(
|
||||
r#"// Nyx dynamic harness — HEADER_INJECTION net.Server raw-socket wire-frame (Phase 08 / Track J.6).
|
||||
{shim}
|
||||
|
||||
function nyxHeaderProbe(name, value) {{
|
||||
const p = process.env.NYX_PROBE_PATH;
|
||||
if (!p) return;
|
||||
const rec = {{
|
||||
sink_callee: 'http.ServerResponse#setHeader',
|
||||
args: [
|
||||
{{ kind: 'String', value: name }},
|
||||
{{ kind: 'String', value: value }},
|
||||
],
|
||||
captured_at_ns: Number(process.hrtime.bigint()),
|
||||
payload_id: process.env.NYX_PAYLOAD_ID || '',
|
||||
kind: {{ kind: 'HeaderEmit', name: name, value: value, protocol: 'in-process' }},
|
||||
witness: __nyx_witness('http.ServerResponse#setHeader', [name, value]),
|
||||
}};
|
||||
try {{
|
||||
require('fs').appendFileSync(p, JSON.stringify(rec) + '\n');
|
||||
}} catch (e) {{
|
||||
// best-effort
|
||||
}}
|
||||
}}
|
||||
|
||||
{wire_frame_via_fixture}(async () => {{
|
||||
const payload = process.env.NYX_PAYLOAD || '';
|
||||
const rawBytes = await nyxWireFrameViaFixture(payload);
|
||||
if (rawBytes !== null && rawBytes !== undefined) {{
|
||||
nyxWireFrameProbe(rawBytes);
|
||||
// Also emit a HeaderEmit record per Set-Cookie line so the tier-(a)
|
||||
// HeaderInjected predicate fires on the same payload that trips
|
||||
// HeaderSmuggledInWire. The wire-frame branch is the source of
|
||||
// truth; the HeaderEmit records are derived from the same captured
|
||||
// bytes.
|
||||
const headerText = rawBytes.toString('binary');
|
||||
for (const line of headerText.split('\r\n')) {{
|
||||
const sep = line.indexOf(': ');
|
||||
if (sep < 0) continue;
|
||||
const hname = line.slice(0, sep);
|
||||
if (hname.toLowerCase() !== 'set-cookie') continue;
|
||||
const hvalue = line.slice(sep + 2);
|
||||
nyxHeaderProbe(hname, hvalue);
|
||||
}}
|
||||
console.log('__NYX_SINK_HIT__');
|
||||
console.log(JSON.stringify({{ wire_frame_len: rawBytes.length }}));
|
||||
return;
|
||||
}}
|
||||
// Synthetic fallback — wire-frame branch did not produce bytes.
|
||||
const name = 'Set-Cookie';
|
||||
const value = payload;
|
||||
nyxHeaderProbe(name, value);
|
||||
console.log('__NYX_SINK_HIT__');
|
||||
console.log(JSON.stringify({{ name: name, value: value }}));
|
||||
}})();
|
||||
"#
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
r#"// Nyx dynamic harness — HEADER_INJECTION http.ServerResponse#setHeader (Phase 08 / Track J.6).
|
||||
{shim}
|
||||
|
||||
function nyxHeaderProbe(name, value) {{
|
||||
|
|
@ -1461,7 +1637,8 @@ function nyxHeaderProbe(name, value) {{
|
|||
|
||||
{via_fixture}const payload = process.env.NYX_PAYLOAD || '';
|
||||
{invoke_via_fixture}"#
|
||||
);
|
||||
)
|
||||
};
|
||||
HarnessSource {
|
||||
source: body,
|
||||
filename: "harness.js".to_owned(),
|
||||
|
|
@ -3028,6 +3205,93 @@ mod tests {
|
|||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_header_injection_harness_routes_through_wire_frame_when_net_create_server_imported() {
|
||||
let dir = std::env::temp_dir().join("nyx_phase08_js_test_wire_frame");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let entry = dir.join("vuln.js");
|
||||
std::fs::write(
|
||||
&entry,
|
||||
"const net = require('net');\nlet cookieValue = Buffer.alloc(0);\nfunction setCookieValue(v) { cookieValue = Buffer.from(String(v)); }\nfunction createServer() { return net.createServer((s) => { s.write(Buffer.concat([Buffer.from('HTTP/1.0 200 OK\\r\\nSet-Cookie: '), cookieValue, Buffer.from('\\r\\n\\r\\nok')])); s.end(); }); }\nmodule.exports = { setCookieValue, createServer };\n",
|
||||
)
|
||||
.unwrap();
|
||||
let h = emit_header_injection_harness(&make_header_spec(
|
||||
entry.to_str().unwrap(),
|
||||
"run",
|
||||
));
|
||||
assert!(
|
||||
h.source.contains("async function nyxWireFrameViaFixture(payload)"),
|
||||
"tier-(b) harness must define the async wire-frame helper: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("require('./vuln')"),
|
||||
"tier-(b) harness must require the staged fixture: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("mod.createServer()"),
|
||||
"tier-(b) harness must boot the fixture's net.Server: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("'GET / HTTP/1.0\\r\\nHost: 127.0.0.1\\r\\n\\r\\n'"),
|
||||
"tier-(b) harness must issue a raw GET over the client socket: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source
|
||||
.contains("kind: 'HeaderWireFrame', raw_bytes: Array.from(rawBytes)"),
|
||||
"tier-(b) harness must emit a HeaderWireFrame probe carrying the raw header-block bytes: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("wire_frame_len: rawBytes.length"),
|
||||
"tier-(b) harness must print the wire_frame_len stdout marker: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("if (hname.toLowerCase() !== 'set-cookie')"),
|
||||
"tier-(b) harness must derive a HeaderEmit probe per Set-Cookie line: {}",
|
||||
h.source
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_header_injection_harness_drops_wire_frame_branch_when_only_http_required() {
|
||||
let dir = std::env::temp_dir().join("nyx_phase08_js_test_no_wire_frame");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let entry = dir.join("vuln.js");
|
||||
std::fs::write(
|
||||
&entry,
|
||||
"const http = require('http');\nfunction run(res, value) { res.setHeader('Set-Cookie', value); }\nmodule.exports = { run };\n",
|
||||
)
|
||||
.unwrap();
|
||||
let h = emit_header_injection_harness(&make_header_spec(
|
||||
entry.to_str().unwrap(),
|
||||
"run",
|
||||
));
|
||||
assert!(
|
||||
!h.source.contains("async function nyxWireFrameViaFixture"),
|
||||
"http-only harness must not emit the wire-frame helper: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
!h.source.contains("HeaderWireFrame"),
|
||||
"http-only harness must not emit the HeaderWireFrame probe shape: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
!h.source.contains("wire_frame_len"),
|
||||
"http-only harness must not emit the wire_frame_len stdout marker: {}",
|
||||
h.source
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_header_injection_harness_derives_entry_stem_from_entry_file() {
|
||||
let dir = std::env::temp_dir().join("nyx_phase08_js_test_stem_derive");
|
||||
|
|
|
|||
50
tests/dynamic_fixtures/header_injection/js_raw/vuln.js
Normal file
50
tests/dynamic_fixtures/header_injection/js_raw/vuln.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Phase 08 (Track J.6) — JavaScript raw-socket HEADER_INJECTION vuln fixture.
|
||||
//
|
||||
// Writes the response status line and headers directly to the wire via
|
||||
// `socket.write`, bypassing the framework-level CRLF validator that
|
||||
// Node's `http.ServerResponse#setHeader` / Express / axum / Tomcat
|
||||
// would otherwise interpose. A payload carrying `\r\nSet-Cookie: ...`
|
||||
// splits the single Set-Cookie header into two on the wire, producing
|
||||
// the canonical smuggled-second-header shape that
|
||||
// `ProbeKind::HeaderWireFrame` is designed to catch.
|
||||
//
|
||||
// The harness (`src/dynamic/lang/js_shared.rs::emit_header_injection_harness`)
|
||||
// detects the `net.createServer` import in this file and routes
|
||||
// through the tier-(b) wire-frame branch: boot a `net.Server` on a
|
||||
// loopback port, issue one `GET /` over a raw socket, read the bytes
|
||||
// the handler wrote to the response socket, and emit them as a
|
||||
// `ProbeKind::HeaderWireFrame` record.
|
||||
const net = require('net');
|
||||
|
||||
// Set by the harness before each request. Bytes go straight onto
|
||||
// the wire with no encoding pass.
|
||||
let cookieValue = Buffer.alloc(0);
|
||||
|
||||
function setCookieValue(value) {
|
||||
if (Buffer.isBuffer(value)) {
|
||||
cookieValue = value;
|
||||
} else {
|
||||
cookieValue = Buffer.from(String(value), 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
function createServer() {
|
||||
return net.createServer((socket) => {
|
||||
socket.once('data', () => {
|
||||
const body = Buffer.from('ok\n');
|
||||
const head = Buffer.concat([
|
||||
Buffer.from('HTTP/1.0 200 OK\r\n'),
|
||||
Buffer.from('Content-Length: ' + body.length + '\r\n'),
|
||||
Buffer.from('Set-Cookie: '),
|
||||
cookieValue,
|
||||
Buffer.from('\r\n'),
|
||||
Buffer.from('\r\n'),
|
||||
]);
|
||||
socket.write(Buffer.concat([head, body]));
|
||||
socket.end();
|
||||
});
|
||||
socket.on('error', () => {});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { setCookieValue, createServer };
|
||||
|
|
@ -726,6 +726,88 @@ mod e2e_phase_08 {
|
|||
(spec, tmp)
|
||||
}
|
||||
|
||||
// Phase 08 tier-(b): JavaScript raw-socket wire-frame fixture.
|
||||
// `tests/dynamic_fixtures/header_injection/js_raw/vuln.js` boots a
|
||||
// `net.Server` whose callback writes raw bytes via `socket.write`,
|
||||
// bypassing Node's `http.ServerResponse#setHeader` CRLF strip. The
|
||||
// harness boots the server on a loopback port, reads the response-
|
||||
// header block off the socket, and emits a
|
||||
// `ProbeKind::HeaderWireFrame` record. Asserts the test exercises
|
||||
// the wire-frame branch (not the synthetic fallback) by pinning
|
||||
// `wire_frame_len` in the captured stdout — that literal only
|
||||
// appears in the tier-(b) write path.
|
||||
fn build_js_raw_spec(entry_name: &str) -> (HarnessSpec, TempDir) {
|
||||
let fixture_src = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/dynamic_fixtures/header_injection/js_raw/vuln.js");
|
||||
let tmp = TempDir::new().expect("create tempdir");
|
||||
let dst = tmp.path().join("vuln.js");
|
||||
std::fs::copy(&fixture_src, &dst).expect("copy js_raw fixture into tempdir");
|
||||
let entry_file = dst.to_string_lossy().into_owned();
|
||||
let mut digest = blake3::Hasher::new();
|
||||
digest.update(b"phase08-e2e-header-injection|js_raw|vuln.js");
|
||||
let spec_hash = format!("{:016x}", {
|
||||
let bytes = digest.finalize();
|
||||
u64::from_le_bytes(bytes.as_bytes()[..8].try_into().unwrap())
|
||||
});
|
||||
let spec = HarnessSpec {
|
||||
finding_id: spec_hash.clone(),
|
||||
entry_file: entry_file.clone(),
|
||||
entry_name: entry_name.to_owned(),
|
||||
entry_kind: EntryKind::Function,
|
||||
lang: Lang::JavaScript,
|
||||
toolchain_id: default_toolchain_id(Lang::JavaScript).into(),
|
||||
payload_slot: PayloadSlot::Param(0),
|
||||
expected_cap: Cap::HEADER_INJECTION,
|
||||
constraint_hints: vec![],
|
||||
sink_file: entry_file,
|
||||
sink_line: 1,
|
||||
spec_hash: spec_hash.clone(),
|
||||
derivation: SpecDerivationStrategy::FromFlowSteps,
|
||||
stubs_required: vec![],
|
||||
framework: None,
|
||||
java_toolchain: nyx_scanner::dynamic::spec::JavaToolchain::default(),
|
||||
};
|
||||
(spec, tmp)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_raw_socket_vuln_confirms_via_wire_frame_probe() {
|
||||
if !command_available("node") {
|
||||
eprintln!("SKIP js_raw: missing node");
|
||||
return;
|
||||
}
|
||||
let _guard = FIXTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let (spec, _tmp) = build_js_raw_spec("run");
|
||||
let opts = SandboxOptions {
|
||||
backend: SandboxBackend::Process,
|
||||
..SandboxOptions::default()
|
||||
};
|
||||
let outcome = match run_spec(&spec, &opts) {
|
||||
Ok(outcome) => outcome,
|
||||
Err(RunError::BuildFailed { stderr, attempts }) => {
|
||||
eprintln!(
|
||||
"SKIP js_raw: harness build failed after {attempts} attempts: {stderr}",
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(e) => panic!("run_spec(js_raw) errored: {e:?}"),
|
||||
};
|
||||
assert_confirmed(Lang::JavaScript, &outcome);
|
||||
let any_wire_frame_marker = outcome.attempts.iter().any(|a| {
|
||||
String::from_utf8_lossy(&a.outcome.stdout).contains("wire_frame_len")
|
||||
});
|
||||
assert!(
|
||||
any_wire_frame_marker,
|
||||
"js_raw fixture must exercise the tier-(b) wire-frame harness branch; \
|
||||
expected `wire_frame_len` substring in at least one attempt's stdout, got attempts={:?}",
|
||||
outcome
|
||||
.attempts
|
||||
.iter()
|
||||
.map(|a| String::from_utf8_lossy(&a.outcome.stdout).into_owned())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_raw_socket_vuln_confirms_via_wire_frame_probe() {
|
||||
if !command_available("python3") {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue