refactor(dynamic): replace Spring annotation stubs with real dependencies, integrate MockMvc-based invocation for Spring controllers, and enhance runtime classpath logic

This commit is contained in:
elipeter 2026-05-26 09:57:31 -05:00
parent c57cd233fc
commit 61bfc0cf96
16 changed files with 214 additions and 98 deletions

View file

@ -57,7 +57,7 @@ Real disclosed CVEs reduced to minimal reproducers, vulnerable + patched pair pe
| CVE-2019-18634 | C | sudo (pwfeedback) | ISC | memory_safety | detected |
| CVE-2019-13132 | C++ | ZeroMQ libzmq | MPL-2.0 | memory_safety | detected |
| CVE-2022-1941 | C++ | Protocol Buffers | BSD-3-Clause | memory_safety | detected |
| CVE-2026-25544 | TypeScript | Payload (Drizzle adapter) | MIT | sql_injection | deferred |
| CVE-2026-25544 | TypeScript | Payload (Drizzle adapter) | MIT | sql_injection | detected |
| CVE-2026-42353 | JavaScript | i18next-http-middleware | MIT | path_traversal | detected |
Deferred entries are real bugs Nyx can't yet detect. The fixture stays committed with `disabled: true` in ground truth so the gap remains visible.
@ -83,6 +83,7 @@ Most recent first. Metrics are rule-level on the corpus size at that point.
| Date | Change | Corpus | P | R | F1 |
|------------|------------------------------------------------------------------------------|--------|-------|-------|-------|
| 2026-05-26 | Benchmark docs corrected for CVE-2026-25544: the Payload Drizzle SQL injection fixture is enabled and detected in `ground_truth.json`; only CVE-2017-1000117 remains deferred in the real-CVE table | 565 | 1.000 | 1.000 | 1.000 |
| 2026-05-04 | C cvehunt session-0014: CVE-2017-1000117 (git ssh:// hostname-as-argv injection) added in corpus disabled — three-layer C engine gap: (a) array-element taint propagation through `args[i] = ssh_host;` writes, (b) missing `c.cmdi.exec*` AST patterns in `src/patterns/c.rs`, (c) sanitizer recognition of the upstream `if (ssh_host[0] == '-') die(...)` dash-prefix guard | 565 | 1.000 | 1.000 | 1.000 |
| 2026-05-04 | JS/TS array-method validator-callback narrowing (`try_array_method_validator_callback_narrowing` in `src/taint/ssa_transfer/mod.rs`) — `<arr>.filter(<isSafeXxx>)` / `.find` / `.findLast` strips `Cap::all()` from the call result when the callback resolves to a `BooleanTrueIsValid` validator; CVE-2026-42353 (i18next-http-middleware path traversal) re-enabled in ground truth, deferred queue cleared | 563 | 1.000 | 1.000 | 1.000 |
| 2026-05-04 | JS/TS ternary-RHS source-classification fix in `src/cfg/conditions.rs::lower_ternary_branch` (segment-strip first_member_label on the branch AST) — `let arr = cond ? req.query.lng : "";` now propagates taint through the diamond's join phi instead of lowering both branches to labelless Assign-with-empty-uses; CVE-2026-42353 (i18next-http-middleware path traversal / SSRF) added in corpus disabled — needs Array.prototype.filter(known_validator_callback) precision bridge | 561 | 1.000 | 1.000 | 1.000 |

View file

@ -495,8 +495,8 @@ pub fn run_shape_fixture_lang(
java_toolchain: nyx_scanner::dynamic::spec::JavaToolchain::default(),
};
// Phase 14: Java shape fixtures bundle annotation / type stubs as
// sibling `*.java` files alongside `Vuln.java` / `Benign.java`.
// Phase 14: Java shape fixtures bundle helper sources and sometimes a
// Maven manifest alongside `Vuln.java` / `Benign.java`.
// Stage those sidecars next to the temp-copied entry file so the
// harness builder can copy them into its per-run workdir. Skip the
// alternate Vuln/Benign file to keep public class declarations from
@ -519,7 +519,7 @@ pub fn run_shape_fixture_lang(
if name == file || name == alt_file {
continue;
}
if p.extension().map(|e| e == "java").unwrap_or(false) {
if name == "pom.xml" || p.extension().map(|e| e == "java").unwrap_or(false) {
let _ = std::fs::copy(&p, tmp.path().join(&name));
}
}
@ -539,6 +539,26 @@ pub fn run_shape_fixture_lang(
// [`VerifyStatus`] directly without learning the runner's API.
match outcome {
Ok(run) => {
let detail = if run.triggered_by.is_none() {
Some(format!(
"attempts={:?}",
run.attempts
.iter()
.map(|a| format!(
"{} fired={} triggered={} sink_hit={} exit={:?} stdout={:?} stderr={:?}",
a.payload_label,
a.oracle_fired,
a.triggered,
a.outcome.sink_hit,
a.outcome.exit_code,
String::from_utf8_lossy(&a.outcome.stdout),
String::from_utf8_lossy(&a.outcome.stderr)
))
.collect::<Vec<_>>()
))
} else {
None
};
let (status, inconclusive_reason) = if run.triggered_by.is_some() {
(VerifyStatus::Confirmed, None)
} else if run.oracle_collision {
@ -569,7 +589,7 @@ pub fn run_shape_fixture_lang(
.map(|a| a.payload_label.to_owned()),
reason: None,
inconclusive_reason,
detail: None,
detail,
attempts: vec![],
toolchain_match: None,
differential: None,

View file

@ -1,13 +0,0 @@
// Phase 14 fixture stub minimal `@Autowired` annotation.
// Lives in the default package so the fixture's @Autowired field
// compiles under plain javac (no Spring Maven dep required).
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface Autowired {
}

View file

@ -1,15 +1,22 @@
// Phase 14 Spring `@RestController`, benign.
// Spring `@RestController`, benign.
//
// Same shape as the vuln but the controller runs a fixed echo and
// drops `payload`.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/run")
public class Benign {
@Autowired
private CommandRunner runner;
public String run(String payload) throws Exception {
@GetMapping
public String run(@RequestParam("payload") String payload) throws Exception {
System.out.print("__NYX_SINK_HIT__\n");
CommandRunner r = (runner != null) ? runner : new CommandRunner();
String out = r.run("echo hello");

View file

@ -1,11 +1,4 @@
// Phase 14 fixture stub Spring-injected helper service.
// The fixture's controller declares `@Autowired CommandRunner runner;`
// so the harness exercises the Phase 09 import-extraction path
// (`@Autowired` is the marker that flags `org.springframework` as a
// transitive dep). At runtime the harness instantiates the controller
// via reflection's default ctor the @Autowired field stays null
// because there is no Spring container; the controller's handler
// guards against null and constructs a fresh CommandRunner on demand.
// Spring-injected helper service used by the controller fixtures.
import java.io.BufferedReader;
import java.io.InputStreamReader;

View file

@ -1,12 +0,0 @@
// Phase 14 fixture stub minimal Spring `@RequestMapping`.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequestMapping {
String value() default "";
}

View file

@ -1,11 +0,0 @@
// Phase 14 fixture stub minimal Spring `@RestController`.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RestController {
}

View file

@ -1,10 +1,10 @@
// Phase 14 Spring `@RestController`, vulnerable.
//
// Controller declares an `@Autowired CommandRunner` field so the
// Phase 09 Java import-extractor sees the Spring annotation surface.
// The harness instantiates the controller via reflection and invokes
// `run(payload)`; the field stays null at runtime (no Spring DI), so
// the handler constructs the helper on demand.
// Spring `@RestController`, vulnerable.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/run")
@ -12,7 +12,8 @@ public class Vuln {
@Autowired
private CommandRunner runner;
public String run(String payload) throws Exception {
@GetMapping
public String run(@RequestParam("payload") String payload) throws Exception {
System.out.print("__NYX_SINK_HIT__\n");
CommandRunner r = (runner != null) ? runner : new CommandRunner();
String out = r.run("echo hello " + payload);

View file

@ -14,10 +14,26 @@
<artifactId>spring-web</artifactId>
<version>6.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
</dependencies>
</project>

View file

@ -674,7 +674,7 @@ mod phase14_shape_tests {
"Vuln.java",
"run",
Cap::CODE_EXEC,
16,
19,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {
@ -690,7 +690,7 @@ mod phase14_shape_tests {
"Benign.java",
"run",
Cap::CODE_EXEC,
14,
22,
EntryKind::HttpRoute,
PayloadSlot::Param(0),
) else {