[pitboss/grind] deferred session-0001 (20260522T163126Z-7d60)

This commit is contained in:
pitboss 2026-05-22 12:04:56 -05:00
parent fd50549582
commit e4258d63ed
7 changed files with 625 additions and 13 deletions

View file

@ -1,17 +1,19 @@
//! JavaScript `Cap::JSON_PARSE` payloads — `JSON.parse` then deep
//! assign / `Object.assign` chain.
//! JavaScript `Cap::JSON_PARSE` payloads.
//!
//! Same canary oracle as the Phase 10 PROTOTYPE_POLLUTION corpus
//! ([`crate::dynamic::oracle::ProbePredicate::PrototypeCanaryTouched`]).
//! The harness routes both payloads through `JSON.parse` first to
//! exercise the parse-then-assign flow specifically (whereas the
//! Phase 10 corpus passes the JSON literal directly to the deep-merge
//! sink without an intervening parse).
//! Covers two oracle shapes: the prototype-canary pair reuses the
//! Phase 10 PROTOTYPE_POLLUTION canary
//! ([`crate::dynamic::oracle::ProbePredicate::PrototypeCanaryTouched`])
//! against a `JSON.parse`-then-deep-merge fixture, and the depth-bomb
//! pair drives `JSON.parse` past the 64-level depth budget for the
//! [`crate::dynamic::oracle::ProbePredicate::JsonParseExcessiveDepth`]
//! oracle. The depth pair shares a single fixture; the payload tag
//! (`NYX_JSON_DEEP` vs `NYX_JSON_SHALLOW`) picks the branch.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const CANARY: &str = "__nyx_canary";
const MAX_DEPTH: u32 = 64;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
@ -48,4 +50,44 @@ pub const PAYLOADS: &[CuratedPayload] = &[
benign_control: None,
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_JSON_DEEP",
label: "json-parse-js-depth-bomb",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
max_depth: MAX_DEPTH,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
max_depth: MAX_DEPTH,
}],
benign_control: Some(PayloadRef {
label: "json-parse-js-depth-shallow",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_JSON_SHALLOW",
label: "json-parse-js-depth-shallow",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
max_depth: MAX_DEPTH,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -1,10 +1,18 @@
//! Ruby `Cap::JSON_PARSE` payloads — `JSON.parse` then recursive
//! `Hash#deep_merge!` on a shared sentinel object.
//! Ruby `Cap::JSON_PARSE` payloads.
//!
//! Covers two oracle shapes: the prototype-canary pair reuses the
//! Phase 10 PROTOTYPE_POLLUTION canary against a `JSON.parse` then
//! recursive `Hash#deep_merge!` fixture, and the depth-bomb pair
//! drives `JSON.parse` past the 64-level depth budget for the
//! [`crate::dynamic::oracle::ProbePredicate::JsonParseExcessiveDepth`]
//! oracle. The depth pair shares a single fixture; the payload tag
//! (`NYX_JSON_DEEP` vs `NYX_JSON_SHALLOW`) picks the branch.
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
use crate::dynamic::oracle::ProbePredicate;
const CANARY: &str = "__nyx_canary";
const MAX_DEPTH: u32 = 64;
pub const PAYLOADS: &[CuratedPayload] = &[
CuratedPayload {
@ -41,4 +49,44 @@ pub const PAYLOADS: &[CuratedPayload] = &[
benign_control: None,
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_JSON_DEEP",
label: "json-parse-ruby-depth-bomb",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
max_depth: MAX_DEPTH,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
max_depth: MAX_DEPTH,
}],
benign_control: Some(PayloadRef {
label: "json-parse-ruby-depth-shallow",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
bytes: b"NYX_JSON_SHALLOW",
label: "json-parse-ruby-depth-shallow",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
max_depth: MAX_DEPTH,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,
no_benign_control_rationale: None,
},
];

View file

@ -629,6 +629,18 @@ pub fn emit(spec: &HarnessSpec, is_typescript: bool) -> Result<HarnessSource, Un
return Ok(emit_prototype_pollution_harness(spec));
}
// Phase 11 (Track J.9): JSON_PARSE depth-bomb short-circuit. The
// synthetic harness monkey-patches `JSON.parse`, walks the parsed
// value iteratively to record maximum nesting depth, emits a
// `ProbeKind::JsonParse { depth, excessive_depth }` record, then
// routes the payload through the fixture entry. RangeError-style
// V8 stack-exhaustion paths emit `JsonParse { depth: 0,
// excessive_depth: true }` so the predicate still fires when the
// engine rejects the input outright.
if spec.expected_cap == crate::labels::Cap::JSON_PARSE {
return Ok(emit_json_parse_harness(spec));
}
// Phase 19 (Track M.1): ClassMethod short-circuit. Same shape gap
// closer as the Python emitter — instantiate the class via its
// zero-arg constructor (falling back to a stubbed-dependency ctor
@ -1957,6 +1969,135 @@ console.log(JSON.stringify({{
}
}
/// Phase 11 (Track J.9) — JSON_PARSE depth-bomb harness for Node.
///
/// Monkey-patches `JSON.parse` with a wrapper that calls the original
/// parser, walks the resulting value iteratively (no recursion stack)
/// to compute maximum nesting depth, emits a
/// `ProbeKind::JsonParse { depth, excessive_depth }` record, then
/// returns the parsed value verbatim. A `RangeError` raised by V8 on
/// excessively-deep input is caught and converted into a
/// `JsonParse { depth: 0, excessive_depth: true }` probe before the
/// error is re-thrown — matching the Python harness's
/// `RecursionError` handling.
///
/// Mirrors `crate::dynamic::lang::python::emit_json_parse_harness`.
pub fn emit_json_parse_harness(spec: &HarnessSpec) -> HarnessSource {
let shim = probe_shim();
let entry_stem = derive_js_entry_stem(&spec.entry_file);
let entry_name = if spec.entry_name.is_empty() {
"run".to_owned()
} else {
spec.entry_name.clone()
};
let body = format!(
r#"// Nyx dynamic harness — JSON_PARSE depth checks (Phase 11 / Track J.9).
{shim}
const _NYX_MAX_WALK = 4096;
function _nyx_count_depth(parsed) {{
let maxDepth = 0;
const stack = [[parsed, 1]];
let visited = 0;
while (stack.length > 0) {{
const [cur, depth] = stack.pop();
visited += 1;
if (visited > _NYX_MAX_WALK) break;
if (depth > maxDepth) maxDepth = depth;
if (cur !== null && typeof cur === 'object') {{
if (Array.isArray(cur)) {{
for (let i = 0; i < cur.length; i += 1) {{
stack.push([cur[i], depth + 1]);
}}
}} else {{
for (const k of Object.keys(cur)) {{
stack.push([cur[k], depth + 1]);
}}
}}
}}
}}
return maxDepth;
}}
function _nyx_json_parse_probe(depth, excessive) {{
const p = process.env.NYX_PROBE_PATH;
if (!p) return;
const rec = {{
sink_callee: 'JSON.parse',
args: [{{ kind: 'Int', value: depth | 0 }}],
captured_at_ns: Number(process.hrtime.bigint()),
payload_id: process.env.NYX_PAYLOAD_ID || '',
kind: {{
kind: 'JsonParse',
depth: depth | 0,
excessive_depth: !!excessive,
}},
witness: __nyx_witness('JSON.parse', [depth | 0]),
}};
try {{
require('fs').appendFileSync(p, JSON.stringify(rec) + '\n');
}} catch (e) {{
// best-effort
}}
}}
const _nyx_orig_json_parse = JSON.parse;
JSON.parse = function _nyx_json_parse_with_depth(text, reviver) {{
let parsed;
try {{
parsed = _nyx_orig_json_parse(text, reviver);
}} catch (e) {{
// V8 raises `RangeError: Maximum call stack size exceeded` on
// deeply-nested input. Emit the excessive-depth probe before
// re-raising so the oracle still fires.
if (e instanceof RangeError) {{
_nyx_json_parse_probe(0, true);
}}
throw e;
}}
const depth = _nyx_count_depth(parsed);
_nyx_json_parse_probe(depth, depth > 64);
return parsed;
}};
function _nyx_json_parse_via_fixture(payload) {{
let _entry;
try {{
_entry = require('./{entry_stem}');
}} catch (e) {{
process.stderr.write('NYX_IMPORT_ERROR: ' + e.message + '\n');
process.exit(77);
}}
const fn =
_entry && (typeof _entry === 'function' ? _entry : _entry['{entry_name}']);
if (typeof fn !== 'function') {{
return false;
}}
try {{
fn(payload);
}} catch (e) {{
// Parser errors / depth-induced throws are expected on the vuln
// payload; the probe is already emitted.
}}
return true;
}}
const payload = process.env.NYX_PAYLOAD || '';
_nyx_json_parse_via_fixture(payload);
console.log('__NYX_SINK_HIT__');
"#
);
HarnessSource {
source: body,
filename: "harness.js".to_owned(),
command: vec!["node".to_owned(), "harness.js".to_owned()],
extra_files: Vec::new(),
entry_subpath: None,
}
}
/// Phase 26 — Node chain-step harness (shared between JS + TS emitters).
///
/// Splices the Node probe shim ([`probe_shim`]) in front of a minimal
@ -3436,4 +3577,92 @@ mod tests {
);
let _ = std::fs::remove_dir_all(&dir);
}
fn make_json_parse_spec(entry_file: &str, entry_name: &str) -> HarnessSpec {
let mut spec = make_spec(EntryKind::Function, entry_name, PayloadSlot::Param(0));
spec.expected_cap = Cap::JSON_PARSE;
spec.entry_file = entry_file.into();
spec.entry_name = entry_name.into();
spec
}
#[test]
fn emit_dispatches_to_json_parse_harness_when_cap_is_json_parse() {
let h = emit(
&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js",
"run",
),
false,
)
.unwrap();
assert!(
h.source.contains("_nyx_json_parse_with_depth"),
"dispatcher must select the JSON_PARSE depth harness: {}",
h.source
);
assert!(
h.source.contains("kind: 'JsonParse'"),
"JSON_PARSE harness must emit JsonParse probes",
);
}
#[test]
fn emit_json_parse_harness_monkey_patches_json_parse() {
let h = emit_json_parse_harness(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js",
"run",
));
assert!(h.source.contains("const _nyx_orig_json_parse = JSON.parse"));
assert!(
h.source
.contains("JSON.parse = function _nyx_json_parse_with_depth")
);
assert!(h.source.contains("function _nyx_count_depth(parsed)"));
}
#[test]
fn emit_json_parse_harness_emits_depth_fields() {
let h = emit_json_parse_harness(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js",
"run",
));
assert!(h.source.contains("depth: depth | 0"));
assert!(h.source.contains("excessive_depth: !!excessive"));
assert!(h.source.contains("depth > 64"));
assert!(h.source.contains("__NYX_SINK_HIT__"));
}
#[test]
fn emit_json_parse_harness_handles_range_error() {
let h = emit_json_parse_harness(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js",
"run",
));
assert!(h.source.contains("e instanceof RangeError"));
assert!(h.source.contains("_nyx_json_parse_probe(0, true)"));
}
#[test]
fn emit_json_parse_harness_routes_through_fixture_require() {
let h = emit_json_parse_harness(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js",
"run",
));
assert!(
h.source
.contains("function _nyx_json_parse_via_fixture(payload)")
);
assert!(h.source.contains("require('./vuln')"));
assert!(h.source.contains("_entry['run']"));
assert_eq!(h.filename, "harness.js");
assert!(h.extra_files.is_empty());
}
#[test]
fn emit_json_parse_harness_derives_entry_stem_from_entry_file() {
let h =
emit_json_parse_harness(&make_json_parse_spec("/abs/path/benign.js", "run"));
assert!(h.source.contains("require('./benign')"));
}
}

View file

@ -447,6 +447,18 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
return Ok(emit_open_redirect_harness(spec));
}
// Phase 11 (Track J.9): JSON_PARSE depth-bomb short-circuit. The
// synthetic harness rebinds `JSON.parse` to a depth-counting
// wrapper, walks the parsed value iteratively, and emits a
// `ProbeKind::JsonParse { depth, excessive_depth }` record before
// returning the parsed value. `JSON::NestingError` (raised by the
// Ruby json gem when the input exceeds `max_nesting`) is caught
// and converted into a `JsonParse { depth: 0, excessive_depth:
// true }` probe before the error is re-raised.
if spec.expected_cap == crate::labels::Cap::JSON_PARSE {
return Ok(emit_json_parse_harness(spec));
}
// Phase 19 (Track M.1): ClassMethod short-circuit.
if let crate::evidence::EntryKind::ClassMethod { class, method } = &spec.entry_kind {
return Ok(emit_class_method_harness(class, method));
@ -1596,6 +1608,131 @@ _nyx_run
}
}
/// Phase 11 (Track J.9) — JSON_PARSE depth-bomb harness for Ruby.
///
/// Rebinds `JSON.parse` to a depth-counting wrapper that calls the
/// original parser, walks the resulting value iteratively (no
/// recursion stack) to compute maximum nesting depth, emits a
/// `ProbeKind::JsonParse { depth, excessive_depth }` record, then
/// returns the parsed value verbatim. `JSON::NestingError` raised by
/// the Ruby json gem (default `max_nesting` is 100) is caught and
/// converted into a `JsonParse { depth: 0, excessive_depth: true }`
/// probe before the error is re-raised — matching the Python harness's
/// `RecursionError` handling and the JS harness's `RangeError`
/// handling.
///
/// Mirrors `crate::dynamic::lang::python::emit_json_parse_harness` and
/// `crate::dynamic::lang::js_shared::emit_json_parse_harness`.
pub fn emit_json_parse_harness(spec: &HarnessSpec) -> HarnessSource {
let shim = probe_shim();
let entry_basename = derive_entry_basename(&spec.entry_file);
let entry_name = if spec.entry_name.is_empty() {
"run".to_owned()
} else {
spec.entry_name.clone()
};
let body = format!(
r#"# Nyx dynamic harness — JSON_PARSE depth checks (Phase 11 / Track J.9).
require 'json'
{shim}
NYX_MAX_WALK = 4096
def _nyx_count_depth(parsed)
max_depth = 0
stack = [[parsed, 1]]
visited = 0
until stack.empty?
cur, depth = stack.pop
visited += 1
break if visited > NYX_MAX_WALK
max_depth = depth if depth > max_depth
case cur
when Hash
cur.each_value {{ |v| stack.push([v, depth + 1]) }}
when Array
cur.each {{ |v| stack.push([v, depth + 1]) }}
end
end
max_depth
end
def _nyx_json_parse_probe(depth, excessive)
p = ENV['NYX_PROBE_PATH']
return if p.nil? || p.empty?
rec = {{
'sink_callee' => 'JSON.parse',
'args' => [{{ 'kind' => 'Int', 'value' => depth.to_i }}],
'captured_at_ns' => Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond),
'payload_id' => ENV['NYX_PAYLOAD_ID'] || '',
'kind' => {{
'kind' => 'JsonParse',
'depth' => depth.to_i,
'excessive_depth' => !!excessive,
}},
'witness' => __nyx_witness('JSON.parse', [depth.to_i]),
}}
begin
File.open(p, 'a') {{ |f| f.write(rec.to_json + "\n") }}
rescue StandardError
# best-effort
end
end
_nyx_orig_json_parse = JSON.method(:parse)
JSON.define_singleton_method(:parse) do |source, *args, **opts|
begin
parsed = _nyx_orig_json_parse.call(source, *args, **opts)
rescue JSON::NestingError => e
# The json gem raises NestingError once `max_nesting` (default 100)
# is exceeded. Emit the excessive-depth probe before re-raising so
# the oracle still fires when the parser rejects the input.
_nyx_json_parse_probe(0, true)
raise e
end
depth = _nyx_count_depth(parsed)
_nyx_json_parse_probe(depth, depth > 64)
parsed
end
def _nyx_json_parse_via_fixture(payload)
$LOAD_PATH.unshift('.')
begin
require_relative './{entry_basename}'
rescue LoadError, ScriptError => e
STDERR.puts("NYX_IMPORT_ERROR: #{{e.message}}")
exit 77
end
fn_sym = :'{entry_name}'
unless Object.respond_to?(fn_sym, true) || self.respond_to?(fn_sym, true)
return false
end
begin
send(fn_sym, payload)
rescue StandardError
# Parser errors / depth-induced raises are expected on the vuln
# payload; the probe is already emitted.
end
true
end
payload = ENV['NYX_PAYLOAD'] || ''
_nyx_json_parse_via_fixture(payload)
STDOUT.puts '__NYX_SINK_HIT__'
STDOUT.flush
"#
);
HarnessSource {
source: body,
filename: "harness.rb".to_owned(),
command: vec!["ruby".to_owned(), "harness.rb".to_owned()],
extra_files: vec![],
entry_subpath: None,
}
}
fn generate_source(spec: &HarnessSpec, shape: RubyShape) -> String {
let entry_fn = &spec.entry_name;
let pre_call = build_pre_call(spec);
@ -2434,4 +2571,90 @@ mod tests {
);
let _ = std::fs::remove_dir_all(&dir);
}
fn make_json_parse_spec(entry_file: &str, entry_name: &str) -> HarnessSpec {
let mut spec = make_spec(PayloadSlot::Param(0));
spec.expected_cap = Cap::JSON_PARSE;
spec.entry_file = entry_file.to_owned();
spec.entry_name = entry_name.to_owned();
spec
}
#[test]
fn emit_dispatches_to_json_parse_harness_when_cap_is_json_parse() {
let h = emit(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb",
"run",
))
.unwrap();
assert!(
h.source.contains("JSON.define_singleton_method(:parse)"),
"dispatcher must select the JSON_PARSE depth harness: {}",
h.source
);
assert!(
h.source.contains("=> 'JsonParse'"),
"JSON_PARSE harness must emit JsonParse probes: {}",
h.source
);
}
#[test]
fn emit_json_parse_harness_monkey_patches_json_parse() {
let h = emit_json_parse_harness(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb",
"run",
));
assert!(h.source.contains("_nyx_orig_json_parse = JSON.method(:parse)"));
assert!(
h.source.contains("JSON.define_singleton_method(:parse)"),
"must rebind JSON.parse: {}",
h.source
);
assert!(h.source.contains("def _nyx_count_depth(parsed)"));
}
#[test]
fn emit_json_parse_harness_emits_depth_fields() {
let h = emit_json_parse_harness(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb",
"run",
));
assert!(h.source.contains("'depth' => depth.to_i"));
assert!(h.source.contains("'excessive_depth' => !!excessive"));
assert!(h.source.contains("depth > 64"));
assert!(h.source.contains("__NYX_SINK_HIT__"));
}
#[test]
fn emit_json_parse_harness_handles_nesting_error() {
let h = emit_json_parse_harness(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb",
"run",
));
assert!(h.source.contains("rescue JSON::NestingError => e"));
assert!(h.source.contains("_nyx_json_parse_probe(0, true)"));
}
#[test]
fn emit_json_parse_harness_routes_through_fixture_require() {
let h = emit_json_parse_harness(&make_json_parse_spec(
"tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb",
"run",
));
assert!(
h.source
.contains("def _nyx_json_parse_via_fixture(payload)")
);
assert!(h.source.contains("require_relative './vuln'"));
assert!(h.source.contains("fn_sym = :'run'"));
assert_eq!(h.filename, "harness.rb");
assert!(h.extra_files.is_empty());
}
#[test]
fn emit_json_parse_harness_derives_entry_basename_from_entry_file() {
let h = emit_json_parse_harness(&make_json_parse_spec("/abs/path/benign.rb", "run"));
assert!(h.source.contains("require_relative './benign'"));
}
}

View file

@ -0,0 +1,23 @@
// JavaScript JSON_PARSE depth-bomb vuln fixture.
//
// Models a config-driven JSON ingest endpoint that picks the parser
// input based on the request payload tag — `*_DEEP` routes through a
// deeply-nested array literal (256 levels) that drives `JSON.parse`
// past the 64-level depth budget; `*_SHALLOW` routes through a flat
// `[]` parse that leaves the predicate clear. This shape is needed
// by the differential runner: the vuln-payload attempt and the
// benign-control attempt both load the same fixture, and only the
// payload-routed deep branch trips the `JsonParseExcessiveDepth`
// predicate.
function run(value) {
const text = Buffer.isBuffer(value)
? value.toString('utf8')
: String(value);
if (text.indexOf('DEEP') !== -1) {
const nested = '['.repeat(256) + ']'.repeat(256);
return JSON.parse(nested);
}
return JSON.parse('[]');
}
module.exports = { run };

View file

@ -0,0 +1,23 @@
# Ruby JSON_PARSE depth-bomb vuln fixture.
#
# Models a config-driven JSON ingest endpoint that picks the parser
# input based on the request payload tag — `*_DEEP` routes through a
# deeply-nested array literal (256 levels) that drives `JSON.parse`
# past the 64-level depth budget; `*_SHALLOW` routes through a flat
# `[]` parse that leaves the predicate clear. This shape is needed
# by the differential runner: the vuln-payload attempt and the
# benign-control attempt both load the same fixture, and only the
# payload-routed deep branch trips the `JsonParseExcessiveDepth`
# predicate. `max_nesting: false` disables the json gem's depth
# guard so the harness's depth walker sees the full 256-level shape
# rather than triggering `JSON::NestingError` at depth 100.
require 'json'
def run(value)
text = value.to_s
if text.include?('DEEP')
nested = '[' * 256 + ']' * 256
return JSON.parse(nested, max_nesting: false)
end
JSON.parse('[]')
end

View file

@ -128,7 +128,9 @@ mod e2e_json_parse_depth {
.join("tests/dynamic_fixtures/json_parse_depth")
.join(match lang {
Lang::Python => "python",
_ => unreachable!("JSON_PARSE depth e2e covers Python only"),
Lang::JavaScript => "javascript",
Lang::Ruby => "ruby",
_ => unreachable!("JSON_PARSE depth e2e covers Python + JavaScript + Ruby only"),
})
.join(fixture);
let tmp = TempDir::new().expect("create tempdir");
@ -167,8 +169,14 @@ mod e2e_json_parse_depth {
}
fn run(lang: Lang, fixture: &str, entry_name: &str) -> Option<RunOutcome> {
if !command_available("python3") {
eprintln!("SKIP {lang:?} {fixture}: missing toolchain python3");
let required = match lang {
Lang::Python => "python3",
Lang::JavaScript => "node",
Lang::Ruby => "ruby",
_ => unreachable!("JSON_PARSE depth e2e covers Python + JavaScript + Ruby only"),
};
if !command_available(required) {
eprintln!("SKIP {lang:?} {fixture}: missing toolchain {required}");
return None;
}
let _guard = FIXTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
@ -208,6 +216,22 @@ mod e2e_json_parse_depth {
};
assert_confirmed(Lang::Python, &outcome);
}
#[test]
fn javascript_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else {
return;
};
assert_confirmed(Lang::JavaScript, &outcome);
}
#[test]
fn ruby_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Ruby, "vuln.rb", "run") else {
return;
};
assert_confirmed(Lang::Ruby, &outcome);
}
}
#[test]