mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
refactor(dynamic): add multi-method support to RouteShape, update framework bindings, and improve test coverage
This commit is contained in:
parent
4bcdec3a1b
commit
ca075a7141
55 changed files with 524 additions and 215 deletions
|
|
@ -1531,11 +1531,7 @@ func nyxJsonParseViaFixture(payload string) (int, bool, bool) {{
|
|||
"##
|
||||
);
|
||||
let invoke = "\tdepth, excessive, fixtureInvoked := nyxJsonParseViaFixture(payload)\n\tif !fixtureInvoked {\n\t\tdepth = 0\n\t\texcessive = false\n\t}\n\tnyxJsonParseProbe(depth, excessive)\n".to_owned();
|
||||
(
|
||||
"\n\t\"nyx-harness/internal/vulnentry\"\n",
|
||||
decl,
|
||||
invoke,
|
||||
)
|
||||
("\n\t\"nyx-harness/internal/vulnentry\"\n", decl, invoke)
|
||||
} else {
|
||||
(
|
||||
"",
|
||||
|
|
@ -1775,10 +1771,10 @@ func nyxInstallHttpTransport() {
|
|||
|
||||
func nyxDataExfilViaFixture(payload string) {
|
||||
defer func() { _ = recover() }()
|
||||
vulnentry."##.to_owned()
|
||||
vulnentry."##
|
||||
.to_owned()
|
||||
+ &format!("{entry_fn}(payload)\n}}\n\n");
|
||||
let invoke =
|
||||
"\tnyxInstallHttpTransport()\n\tnyxDataExfilViaFixture(payload)\n".to_owned();
|
||||
let invoke = "\tnyxInstallHttpTransport()\n\tnyxDataExfilViaFixture(payload)\n".to_owned();
|
||||
(
|
||||
"\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"nyx-harness/internal/vulnentry\"\n",
|
||||
decl,
|
||||
|
|
@ -1855,9 +1851,15 @@ fn emit_class_method_harness(class: &str, method: &str) -> HarnessSource {
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"nyx-harness/entry"
|
||||
)
|
||||
|
|
@ -1883,6 +1885,11 @@ func nyxPayload() string {{
|
|||
if v := os.Getenv("NYX_PAYLOAD"); v != "" {{
|
||||
return v
|
||||
}}
|
||||
if b64 := os.Getenv("NYX_PAYLOAD_B64"); b64 != "" {{
|
||||
if data, err := base64.StdEncoding.DecodeString(b64); err == nil {{
|
||||
return string(data)
|
||||
}}
|
||||
}}
|
||||
return ""
|
||||
}}
|
||||
|
||||
|
|
@ -2037,7 +2044,7 @@ fn emit_message_handler_harness(spec: &HarnessSpec, queue: &str) -> HarnessSourc
|
|||
if got.Type().AssignableTo(want) {{
|
||||
args[i] = got
|
||||
}} else if want.Kind() == reflect.String {{
|
||||
args[i] = reflect.ValueOf(os.Getenv("NYX_PAYLOAD"))
|
||||
args[i] = reflect.ValueOf(nyxPayload())
|
||||
}} else {{
|
||||
args[i] = reflect.Zero(want)
|
||||
}}
|
||||
|
|
@ -2053,9 +2060,15 @@ fn emit_message_handler_harness(spec: &HarnessSpec, queue: &str) -> HarnessSourc
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"nyx-harness/entry"
|
||||
)
|
||||
|
|
@ -2070,6 +2083,11 @@ func nyxPayload() string {{
|
|||
if v := os.Getenv("NYX_PAYLOAD"); v != "" {{
|
||||
return v
|
||||
}}
|
||||
if b64 := os.Getenv("NYX_PAYLOAD_B64"); b64 != "" {{
|
||||
if data, err := base64.StdEncoding.DecodeString(b64); err == nil {{
|
||||
return string(data)
|
||||
}}
|
||||
}}
|
||||
return ""
|
||||
}}
|
||||
|
||||
|
|
@ -3170,7 +3188,8 @@ mod tests {
|
|||
"Go UNAUTHORIZED_ID harness must pin caller_id to \"alice\"",
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("nyxIdorAccessProbe(_NYX_CALLER_ID, payload)"),
|
||||
h.source
|
||||
.contains("nyxIdorAccessProbe(_NYX_CALLER_ID, payload)"),
|
||||
"Go UNAUTHORIZED_ID harness must call probe with caller_id + payload-as-owner",
|
||||
);
|
||||
}
|
||||
|
|
@ -3182,7 +3201,8 @@ mod tests {
|
|||
"Run",
|
||||
));
|
||||
assert!(
|
||||
h.source.contains("if nyxUnauthorizedIdViaFixture(payload) {"),
|
||||
h.source
|
||||
.contains("if nyxUnauthorizedIdViaFixture(payload) {"),
|
||||
"Go UNAUTHORIZED_ID harness must gate probe emission on a present record so the benign fixture's empty-string rejection clears the predicate",
|
||||
);
|
||||
assert!(
|
||||
|
|
@ -3236,7 +3256,8 @@ mod tests {
|
|||
"fallback path must not stage a vulnentry copy when the fixture cannot be read",
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("nyxIdorAccessProbe(_NYX_CALLER_ID, payload)"),
|
||||
h.source
|
||||
.contains("nyxIdorAccessProbe(_NYX_CALLER_ID, payload)"),
|
||||
"fallback path must still emit an IDOR probe so the universal sink-hit path fires",
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2360,7 +2360,10 @@ public class NyxHarness {{
|
|||
".".to_owned(),
|
||||
"NyxHarness".to_owned(),
|
||||
],
|
||||
extra_files: vec![("NyxJsonProbe.java".to_owned(), nyx_json_probe_source().to_owned())],
|
||||
extra_files: vec![(
|
||||
"NyxJsonProbe.java".to_owned(),
|
||||
nyx_json_probe_source().to_owned(),
|
||||
)],
|
||||
entry_subpath: Some(format!("{entry_class}.java")),
|
||||
}
|
||||
}
|
||||
|
|
@ -3572,11 +3575,51 @@ public class NyxHarness {{
|
|||
".".to_owned(),
|
||||
"NyxHarness".to_owned(),
|
||||
],
|
||||
extra_files: vec![],
|
||||
extra_files: message_handler_annotation_stubs(),
|
||||
entry_subpath: Some(format!("{entry_class}.java")),
|
||||
}
|
||||
}
|
||||
|
||||
fn message_handler_annotation_stubs() -> Vec<(String, String)> {
|
||||
vec![
|
||||
(
|
||||
"org/springframework/kafka/annotation/KafkaListener.java".to_owned(),
|
||||
r#"package org.springframework.kafka.annotation;
|
||||
|
||||
public @interface KafkaListener {
|
||||
String[] value() default {};
|
||||
String[] topics() default {};
|
||||
}
|
||||
"#
|
||||
.to_owned(),
|
||||
),
|
||||
(
|
||||
"io/awspring/cloud/sqs/annotation/SqsListener.java".to_owned(),
|
||||
r#"package io.awspring.cloud.sqs.annotation;
|
||||
|
||||
public @interface SqsListener {
|
||||
String[] value() default {};
|
||||
String[] queueNames() default {};
|
||||
String queueName() default "";
|
||||
String queueUrl() default "";
|
||||
}
|
||||
"#
|
||||
.to_owned(),
|
||||
),
|
||||
(
|
||||
"org/springframework/amqp/rabbit/annotation/RabbitListener.java".to_owned(),
|
||||
r#"package org.springframework.amqp.rabbit.annotation;
|
||||
|
||||
public @interface RabbitListener {
|
||||
String[] value() default {};
|
||||
String[] queues() default {};
|
||||
}
|
||||
"#
|
||||
.to_owned(),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
// ── Phase 21 (Track M.3) — synthetic entry-kind harnesses ─────────────────────
|
||||
|
||||
fn emit_scheduled_job_harness(
|
||||
|
|
@ -5363,4 +5406,26 @@ mod tests {
|
|||
"Java DATA_EXFIL harness must catch InvocationTargetException so a fixture-side throw after a partial outbound call still drains CAPTURED_HOSTS",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_message_handler_harness_ships_broker_annotation_stubs() {
|
||||
let mut spec = make_spec(PayloadSlot::Param(0));
|
||||
spec.entry_file = "tests/dynamic_fixtures/message_handler/kafka_java/Vuln.java".to_owned();
|
||||
spec.entry_name = "onMessage".to_owned();
|
||||
spec.entry_kind = EntryKind::MessageHandler {
|
||||
queue: "orders".to_owned(),
|
||||
message_schema: None,
|
||||
};
|
||||
let h = emit(&spec).unwrap();
|
||||
for path in [
|
||||
"org/springframework/kafka/annotation/KafkaListener.java",
|
||||
"io/awspring/cloud/sqs/annotation/SqsListener.java",
|
||||
"org/springframework/amqp/rabbit/annotation/RabbitListener.java",
|
||||
] {
|
||||
assert!(
|
||||
h.extra_files.iter().any(|(name, _)| name == path),
|
||||
"Java MessageHandler harness must stage {path} so annotated broker fixtures compile without real Spring jars",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3253,10 +3253,7 @@ mod tests {
|
|||
spec.framework = Some(FrameworkBinding {
|
||||
adapter: "test-adapter".into(),
|
||||
kind: EntryKind::HttpRoute,
|
||||
route: Some(RouteShape {
|
||||
method: HttpMethod::GET,
|
||||
path: route_path.into(),
|
||||
}),
|
||||
route: Some(RouteShape::single(HttpMethod::GET, route_path)),
|
||||
request_params: vec![],
|
||||
response_writer: None,
|
||||
middleware: vec![],
|
||||
|
|
|
|||
|
|
@ -3986,10 +3986,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn emit_unauthorized_id_harness_derives_basename_from_entry_file() {
|
||||
let h = emit_unauthorized_id_harness(&make_unauthorized_id_spec(
|
||||
"/abs/path/benign.php",
|
||||
"run",
|
||||
));
|
||||
let h =
|
||||
emit_unauthorized_id_harness(&make_unauthorized_id_spec("/abs/path/benign.php", "run"));
|
||||
assert!(
|
||||
h.source.contains("\"benign.php\""),
|
||||
"PHP UNAUTHORIZED_ID harness must use the entry-file basename, not a hard-coded literal: {}",
|
||||
|
|
|
|||
|
|
@ -4706,8 +4706,7 @@ mod tests {
|
|||
"run",
|
||||
));
|
||||
assert!(
|
||||
h.source
|
||||
.contains("def _nyx_idor_via_fixture(payload):"),
|
||||
h.source.contains("def _nyx_idor_via_fixture(payload):"),
|
||||
"Python UNAUTHORIZED_ID harness must define the fixture-routing helper",
|
||||
);
|
||||
assert!(h.source.contains("importlib.import_module(\"vuln\")"));
|
||||
|
|
@ -4718,10 +4717,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn emit_unauthorized_id_harness_derives_module_name_from_entry_file() {
|
||||
let h = emit_unauthorized_id_harness(&make_unauthorized_id_spec(
|
||||
"/abs/path/benign.py",
|
||||
"run",
|
||||
));
|
||||
let h =
|
||||
emit_unauthorized_id_harness(&make_unauthorized_id_spec("/abs/path/benign.py", "run"));
|
||||
assert!(h.source.contains("importlib.import_module(\"benign\")"));
|
||||
}
|
||||
|
||||
|
|
@ -4758,7 +4755,10 @@ mod tests {
|
|||
"run",
|
||||
));
|
||||
assert!(h.source.contains("urllib.request.urlopen = _nyx_urlopen"));
|
||||
assert!(h.source.contains("def _nyx_urlopen(url, data=None, timeout=None, *args, **kwargs):"));
|
||||
assert!(
|
||||
h.source
|
||||
.contains("def _nyx_urlopen(url, data=None, timeout=None, *args, **kwargs):")
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("class _NyxFakeResponse(io.BytesIO):"),
|
||||
"harness must return a fake response so the fixture does not block on real network egress",
|
||||
|
|
@ -4806,10 +4806,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn emit_data_exfil_harness_derives_module_name_from_entry_file() {
|
||||
let h = emit_data_exfil_harness(&make_data_exfil_spec(
|
||||
"/abs/path/benign.py",
|
||||
"run",
|
||||
));
|
||||
let h = emit_data_exfil_harness(&make_data_exfil_spec("/abs/path/benign.py", "run"));
|
||||
assert!(h.source.contains("importlib.import_module(\"benign\")"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2864,7 +2864,10 @@ mod tests {
|
|||
"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("_nyx_orig_json_parse = JSON.method(:parse)")
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("JSON.define_singleton_method(:parse)"),
|
||||
"must rebind JSON.parse: {}",
|
||||
|
|
@ -2956,8 +2959,7 @@ mod tests {
|
|||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source
|
||||
.contains("_nyx_idor_probe(NYX_CALLER_ID, payload)"),
|
||||
h.source.contains("_nyx_idor_probe(NYX_CALLER_ID, payload)"),
|
||||
"harness must emit the IDOR probe with the hard-coded caller and the payload owner_id: {}",
|
||||
h.source
|
||||
);
|
||||
|
|
@ -2983,8 +2985,7 @@ mod tests {
|
|||
"run",
|
||||
));
|
||||
assert!(
|
||||
h.source
|
||||
.contains("def _nyx_idor_via_fixture(payload)"),
|
||||
h.source.contains("def _nyx_idor_via_fixture(payload)"),
|
||||
"Ruby UNAUTHORIZED_ID harness must define the fixture-routing helper: {}",
|
||||
h.source
|
||||
);
|
||||
|
|
@ -2996,10 +2997,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn emit_unauthorized_id_harness_derives_entry_basename_from_entry_file() {
|
||||
let h = emit_unauthorized_id_harness(&make_unauthorized_id_spec(
|
||||
"/abs/path/benign.rb",
|
||||
"run",
|
||||
));
|
||||
let h =
|
||||
emit_unauthorized_id_harness(&make_unauthorized_id_spec("/abs/path/benign.rb", "run"));
|
||||
assert!(h.source.contains("require_relative './benign'"));
|
||||
}
|
||||
|
||||
|
|
@ -3019,8 +3018,7 @@ mod tests {
|
|||
))
|
||||
.unwrap();
|
||||
assert!(
|
||||
h.source
|
||||
.contains("Net::HTTP.define_singleton_method(:get)"),
|
||||
h.source.contains("Net::HTTP.define_singleton_method(:get)"),
|
||||
"dispatcher must short-circuit Cap::DATA_EXFIL into emit_data_exfil_harness: {}",
|
||||
h.source
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1593,8 +1593,7 @@ pub fn emit_unauthorized_id_harness(spec: &HarnessSpec) -> HarnessSource {
|
|||
)
|
||||
} else {
|
||||
let extras: Vec<(String, String)> = vec![("Cargo.toml".into(), cargo_toml)];
|
||||
let invoke =
|
||||
" nyx_idor_access_probe(_NYX_CALLER_ID, &payload);\n".to_owned();
|
||||
let invoke = " nyx_idor_access_probe(_NYX_CALLER_ID, &payload);\n".to_owned();
|
||||
(
|
||||
String::new(),
|
||||
invoke,
|
||||
|
|
@ -1700,11 +1699,12 @@ pub fn emit_data_exfil_harness(spec: &HarnessSpec) -> HarnessSource {
|
|||
let extras: Vec<(String, String)> = vec![
|
||||
("Cargo.toml".into(), cargo_toml),
|
||||
("src/entry.rs".into(), rewritten),
|
||||
("src/nyx_http.rs".into(), nyx_http_module_source().to_owned()),
|
||||
(
|
||||
"src/nyx_http.rs".into(),
|
||||
nyx_http_module_source().to_owned(),
|
||||
),
|
||||
];
|
||||
let invoke = format!(
|
||||
" let _ = entry::{entry_fn}(&payload);\n",
|
||||
);
|
||||
let invoke = format!(" let _ = entry::{entry_fn}(&payload);\n",);
|
||||
(
|
||||
"mod entry;\nmod nyx_http;\n".to_owned(),
|
||||
invoke,
|
||||
|
|
@ -3702,8 +3702,7 @@ mod tests {
|
|||
"run",
|
||||
));
|
||||
assert!(
|
||||
h.source
|
||||
.contains("const _NYX_CALLER_ID: &str = \"alice\";"),
|
||||
h.source.contains("const _NYX_CALLER_ID: &str = \"alice\";"),
|
||||
"Rust UNAUTHORIZED_ID harness must pin caller_id to \"alice\"",
|
||||
);
|
||||
assert!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue