refactor(dynamic): add multi-method support to RouteShape, update framework bindings, and improve test coverage

This commit is contained in:
elipeter 2026-05-23 10:08:41 -05:00
parent 4bcdec3a1b
commit ca075a7141
55 changed files with 524 additions and 215 deletions

View file

@ -1,8 +1,11 @@
// Phase 20 (Track M.2) Kafka Java benign control.
// `org.springframework.kafka` adapter marker preserved.
import org.springframework.kafka.annotation.KafkaListener;
public class Benign {
public Benign() {}
@KafkaListener(topics = "orders")
public void onMessage(String body) throws Exception {
new ProcessBuilder("echo", body).inheritIO().start().waitFor();
}

View file

@ -1,13 +1,11 @@
// Phase 20 (Track M.2) Kafka Java vuln fixture.
//
// Marker line so the kafka-java framework adapter binds:
// `org.springframework.kafka` consumer entry point. Annotation is
// elided so javac compiles without the Spring jar; the dynamic harness
// invokes onMessage reflectively.
import org.springframework.kafka.annotation.KafkaListener;
public class Vuln {
public Vuln() {}
@KafkaListener(topics = "orders")
public void onMessage(String body) throws Exception {
// SINK: tainted body concatenated into shell command
new ProcessBuilder("sh", "-c", "echo " + body).inheritIO().start().waitFor();

View file

@ -1,9 +1,11 @@
// Phase 20 (Track M.2) RabbitMQ Java benign control.
// `org.springframework.amqp.rabbit` adapter marker preserved.
import org.springframework.amqp.rabbit.annotation.RabbitListener;
public class Benign {
public Benign() {}
@RabbitListener(queues = "work")
public void onMessage(String messageId, String body) throws Exception {
new ProcessBuilder("echo", body).inheritIO().start().waitFor();
}

View file

@ -1,10 +1,11 @@
// Phase 20 (Track M.2) RabbitMQ Java vuln fixture.
// `org.springframework.amqp.rabbit` consumer marker preserved;
// annotation elided so javac compiles without the Spring AMQP jar.
import org.springframework.amqp.rabbit.annotation.RabbitListener;
public class Vuln {
public Vuln() {}
@RabbitListener(queues = "work")
public void onMessage(String messageId, String body) throws Exception {
// SINK: tainted body concatenated into shell command
new ProcessBuilder("sh", "-c", "echo " + body).inheritIO().start().waitFor();

View file

@ -1,9 +1,11 @@
// Phase 20 (Track M.2) SQS Java benign control.
// `io.awspring.cloud.sqs` adapter marker preserved.
import io.awspring.cloud.sqs.annotation.SqsListener;
public class Benign {
public Benign() {}
@SqsListener("jobs")
public void handleMessage(java.util.Map<String, String> env) throws Exception {
String body = env != null ? env.getOrDefault("Body", "") : "";
new ProcessBuilder("echo", body).inheritIO().start().waitFor();

View file

@ -1,10 +1,11 @@
// Phase 20 (Track M.2) SQS Java vuln fixture.
// `io.awspring.cloud.sqs` consumer entry point annotation elided so
// javac compiles without the Spring Cloud AWS jar.
import io.awspring.cloud.sqs.annotation.SqsListener;
public class Vuln {
public Vuln() {}
@SqsListener("jobs")
public void handleMessage(java.util.Map<String, String> env) throws Exception {
String body = env != null ? env.getOrDefault("Body", "") : "";
// SINK: tainted Body concatenated into shell command

View file

@ -183,7 +183,9 @@ mod e2e_json_parse_depth {
Lang::Go => "go",
Lang::Rust => "rust",
Lang::Java => "java",
_ => unreachable!("JSON_PARSE depth e2e covers JS / Python / Ruby / PHP / Go / Rust / Java only"),
_ => unreachable!(
"JSON_PARSE depth e2e covers JS / Python / Ruby / PHP / Go / Rust / Java only"
),
})
.join(fixture);
let tmp = TempDir::new().expect("create tempdir");
@ -230,7 +232,9 @@ mod e2e_json_parse_depth {
Lang::Go => "go",
Lang::Rust => "cargo",
Lang::Java => "javac",
_ => unreachable!("JSON_PARSE depth e2e covers JS / Python / Ruby / PHP / Go / Rust / Java only"),
_ => unreachable!(
"JSON_PARSE depth e2e covers JS / Python / Ruby / PHP / Go / Rust / Java only"
),
};
if !command_available(required) {
eprintln!("SKIP {lang:?} {fixture}: missing toolchain {required}");

View file

@ -326,8 +326,9 @@ mod e2e_phase_20 {
use tempfile::TempDir;
fn command_available(bin: &str) -> bool {
let version_arg = if bin == "go" { "version" } else { "--version" };
Command::new(bin)
.arg("--version")
.arg(version_arg)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
@ -484,7 +485,10 @@ mod e2e_phase_20 {
let Some(outcome) = run(Lang::Python, "sqs_python", "vuln.py", "handler", "jobs") else {
return;
};
assert!(outcome.triggered_by.is_some());
assert!(
outcome.triggered_by.is_some(),
"sqs-python MessageHandler vuln must Confirm via run_spec; got {outcome:?}",
);
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
@ -500,7 +504,10 @@ mod e2e_phase_20 {
) else {
return;
};
assert!(outcome.triggered_by.is_some());
assert!(
outcome.triggered_by.is_some(),
"pubsub-python MessageHandler vuln must Confirm via run_spec; got {outcome:?}",
);
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
@ -516,7 +523,10 @@ mod e2e_phase_20 {
) else {
return;
};
assert!(outcome.triggered_by.is_some());
assert!(
outcome.triggered_by.is_some(),
"rabbit-python MessageHandler vuln must Confirm via run_spec; got {outcome:?}",
);
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
@ -534,4 +544,71 @@ mod e2e_phase_20 {
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
#[test]
fn kafka_java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "kafka_java", "Vuln.java", "onMessage", "orders")
else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"kafka-java MessageHandler vuln must Confirm via run_spec; got {outcome:?}",
);
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
#[test]
fn sqs_java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "sqs_java", "Vuln.java", "handleMessage", "jobs")
else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"sqs-java MessageHandler vuln must Confirm via run_spec; got {outcome:?}",
);
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
#[test]
fn rabbit_java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "rabbit_java", "Vuln.java", "onMessage", "work") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"rabbit-java MessageHandler vuln must Confirm via run_spec; got {outcome:?}",
);
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
#[test]
fn pubsub_go_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Go, "pubsub_go", "vuln.go", "OnMessage", "my-sub") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"pubsub-go MessageHandler vuln must Confirm via run_spec; got {outcome:?}",
);
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
#[test]
fn nats_go_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Go, "nats_go", "vuln.go", "OnMessage", "events") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"nats-go MessageHandler vuln must Confirm via run_spec; got {outcome:?}",
);
let diff = outcome.differential.as_ref().expect("Confirmed");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
}