cargo fmt

This commit is contained in:
elipeter 2026-05-21 14:35:42 -05:00
parent bec7bbf96c
commit 3a35cd6c8f
294 changed files with 6809 additions and 3911 deletions

View file

@ -42,7 +42,11 @@ pub fn prepare_rust(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
// Cache hit: binary already compiled and stored.
let binary = cache_path.join("nyx_harness");
if binary.exists() {
return Ok(BuildResult { venv_path: cache_path, cache_hit: true, duration: Duration::ZERO });
return Ok(BuildResult {
venv_path: cache_path,
cache_hit: true,
duration: Duration::ZERO,
});
}
let start = Instant::now();
@ -72,7 +76,10 @@ pub fn prepare_rust(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
}
}
Err(BuildError::BuildFailed { stderr: last_err, attempts: MAX_ATTEMPTS })
Err(BuildError::BuildFailed {
stderr: last_err,
attempts: MAX_ATTEMPTS,
})
}
fn try_build_rust_binary(workdir: &Path, binary_dest: &Path) -> Result<(), String> {
@ -86,10 +93,14 @@ fn try_build_rust_binary(workdir: &Path, binary_dest: &Path) -> Result<(), Strin
.env("PATH", std::env::var("PATH").unwrap_or_default())
.env("HOME", std::env::var("HOME").unwrap_or_default())
// Inherit CARGO_HOME so the local registry cache is reused.
.env("CARGO_HOME", std::env::var("CARGO_HOME").unwrap_or_else(|_| {
dirs_next_cargo_home()
}))
.env("RUSTUP_HOME", std::env::var("RUSTUP_HOME").unwrap_or_default())
.env(
"CARGO_HOME",
std::env::var("CARGO_HOME").unwrap_or_else(|_| dirs_next_cargo_home()),
)
.env(
"RUSTUP_HOME",
std::env::var("RUSTUP_HOME").unwrap_or_default(),
)
.output()
.map_err(|e| format!("cargo build: {e}"))?;
@ -101,8 +112,7 @@ fn try_build_rust_binary(workdir: &Path, binary_dest: &Path) -> Result<(), Strin
// Copy binary to cache location.
let compiled = workdir.join("target").join("release").join("nyx_harness");
if compiled.exists() {
std::fs::copy(&compiled, binary_dest)
.map_err(|e| format!("copy binary: {e}"))?;
std::fs::copy(&compiled, binary_dest).map_err(|e| format!("copy binary: {e}"))?;
}
Ok(())
@ -137,7 +147,10 @@ fn compute_rust_lockfile_hash(workdir: &Path) -> String {
h.update(&content);
}
let out = h.finalize();
format!("{:016x}", u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap()))
format!(
"{:016x}",
u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap())
)
}
/// Result of a successful build.
@ -168,10 +181,7 @@ impl From<std::io::Error> for BuildError {
///
/// If a compatible cache entry exists, returns it immediately. Otherwise
/// builds in isolation and caches the result.
pub fn prepare_python(
spec: &HarnessSpec,
workdir: &Path,
) -> Result<BuildResult, BuildError> {
pub fn prepare_python(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, BuildError> {
let lockfile_hash = compute_lockfile_hash(workdir);
let cache_path = build_cache_path(&lockfile_hash, "python", &spec.toolchain_id)?;
@ -217,11 +227,7 @@ pub fn prepare_python(
})
}
fn try_build_venv(
venv_path: &Path,
workdir: &Path,
spec: &HarnessSpec,
) -> Result<(), String> {
fn try_build_venv(venv_path: &Path, workdir: &Path, spec: &HarnessSpec) -> Result<(), String> {
// Find python binary.
let python = python_binary(spec);
@ -262,10 +268,7 @@ fn try_build_venv(
fn python_binary(spec: &HarnessSpec) -> String {
// Try the pinned version first; fall back to python3.
let ver = spec
.toolchain_id
.strip_prefix("python-")
.unwrap_or("3");
let ver = spec.toolchain_id.strip_prefix("python-").unwrap_or("3");
let candidate = format!("python{ver}");
if which_exists(&candidate) {
return candidate;
@ -290,7 +293,10 @@ fn compute_lockfile_hash(workdir: &Path) -> String {
}
}
let out = h.finalize();
format!("{:016x}", u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap()))
format!(
"{:016x}",
u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap())
)
}
fn build_cache_path(
@ -308,9 +314,7 @@ fn build_cache_path(
"cannot determine cache dir",
))
})?;
dirs.cache_dir()
.join("dynamic")
.join("build-cache")
dirs.cache_dir().join("dynamic").join("build-cache")
};
let name = format!("{lockfile_hash}-{language}-{toolchain_id}");
@ -366,7 +370,9 @@ pub fn prepare_node(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
for attempt in 0..MAX_ATTEMPTS {
if attempt > 0 {
std::thread::sleep(std::time::Duration::from_secs(BACKOFF[attempt as usize - 1]));
std::thread::sleep(std::time::Duration::from_secs(
BACKOFF[attempt as usize - 1],
));
}
match try_npm_install(workdir) {
Ok(()) => {
@ -389,7 +395,10 @@ pub fn prepare_node(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
}
}
Err(BuildError::BuildFailed { stderr: last_err, attempts: MAX_ATTEMPTS })
Err(BuildError::BuildFailed {
stderr: last_err,
attempts: MAX_ATTEMPTS,
})
}
fn try_npm_install(workdir: &Path) -> Result<(), String> {
@ -430,14 +439,22 @@ fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> {
fn compute_node_lockfile_hash(workdir: &Path) -> String {
let mut h = Hasher::new();
for fname in &["package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"] {
for fname in &[
"package.json",
"package-lock.json",
"yarn.lock",
"pnpm-lock.yaml",
] {
if let Ok(content) = std::fs::read(workdir.join(fname)) {
h.update(fname.as_bytes());
h.update(&content);
}
}
let out = h.finalize();
format!("{:016x}", u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap()))
format!(
"{:016x}",
u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap())
)
}
// ── Go build sandbox ──────────────────────────────────────────────────────────
@ -470,7 +487,9 @@ pub fn prepare_go(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bui
for attempt in 0..MAX_ATTEMPTS {
if attempt > 0 {
std::thread::sleep(std::time::Duration::from_secs(BACKOFF[attempt as usize - 1]));
std::thread::sleep(std::time::Duration::from_secs(
BACKOFF[attempt as usize - 1],
));
}
let _ = std::fs::remove_dir_all(&cache_path);
std::fs::create_dir_all(&cache_path)?;
@ -490,23 +509,41 @@ pub fn prepare_go(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bui
}
}
Err(BuildError::BuildFailed { stderr: last_err, attempts: MAX_ATTEMPTS })
Err(BuildError::BuildFailed {
stderr: last_err,
attempts: MAX_ATTEMPTS,
})
}
fn try_build_go_binary(workdir: &Path, binary_dest: &Path) -> Result<(), String> {
let go_bin = std::env::var("NYX_GO_BIN").unwrap_or_else(|_| "go".to_owned());
let output = Command::new(&go_bin)
.args(["build", "-o", binary_dest.to_str().unwrap_or("nyx_harness"), "."])
.args([
"build",
"-o",
binary_dest.to_str().unwrap_or("nyx_harness"),
".",
])
.current_dir(workdir)
.env_clear()
.env("PATH", std::env::var("PATH").unwrap_or_default())
.env("HOME", std::env::var("HOME").unwrap_or_default())
.env("GOPATH", std::env::var("GOPATH").unwrap_or_else(|_| {
std::env::var("HOME").map(|h| format!("{h}/go")).unwrap_or_else(|_| "/tmp/go".to_owned())
}))
.env("GOMODCACHE", std::env::var("GOMODCACHE").unwrap_or_else(|_| {
std::env::var("HOME").map(|h| format!("{h}/go/pkg/mod")).unwrap_or_else(|_| "/tmp/gomod".to_owned())
}))
.env(
"GOPATH",
std::env::var("GOPATH").unwrap_or_else(|_| {
std::env::var("HOME")
.map(|h| format!("{h}/go"))
.unwrap_or_else(|_| "/tmp/go".to_owned())
}),
)
.env(
"GOMODCACHE",
std::env::var("GOMODCACHE").unwrap_or_else(|_| {
std::env::var("HOME")
.map(|h| format!("{h}/go/pkg/mod"))
.unwrap_or_else(|_| "/tmp/gomod".to_owned())
}),
)
.output()
.map_err(|e| format!("go build: {e}"))?;
@ -529,7 +566,10 @@ fn compute_go_source_hash(workdir: &Path) -> String {
h.update(&content);
}
let out = h.finalize();
format!("{:016x}", u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap()))
format!(
"{:016x}",
u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap())
)
}
// ── Java build sandbox ────────────────────────────────────────────────────────
@ -592,7 +632,9 @@ pub fn prepare_java(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
for attempt in 0..MAX_ATTEMPTS {
if attempt > 0 {
std::thread::sleep(std::time::Duration::from_secs(BACKOFF[attempt as usize - 1]));
std::thread::sleep(std::time::Duration::from_secs(
BACKOFF[attempt as usize - 1],
));
}
match try_compile_java(workdir, &cache_path, target_release) {
Ok(()) => {
@ -622,7 +664,10 @@ pub fn prepare_java(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
}
}
Err(BuildError::BuildFailed { stderr: last_err, attempts: MAX_ATTEMPTS })
Err(BuildError::BuildFailed {
stderr: last_err,
attempts: MAX_ATTEMPTS,
})
}
/// Parse the bytecode target release from a `java-NN` toolchain id.
@ -652,7 +697,11 @@ fn java_target_release(toolchain_id: &str) -> Option<u32> {
}
}
fn try_compile_java(workdir: &Path, cache_path: &Path, target_release: Option<u32>) -> Result<(), String> {
fn try_compile_java(
workdir: &Path,
cache_path: &Path,
target_release: Option<u32>,
) -> Result<(), String> {
let javac = std::env::var("NYX_JAVAC_BIN").unwrap_or_else(|_| "javac".to_owned());
// If the harness emitter shipped a `pom.xml`, stage Maven-resolved
@ -792,9 +841,10 @@ fn collect_class_files(root: &Path) -> Vec<PathBuf> {
if path.is_dir() {
stack.push(path);
} else if path.extension().map(|e| e == "class").unwrap_or(false)
&& let Ok(rel) = path.strip_prefix(root) {
out.push(rel.to_path_buf());
}
&& let Ok(rel) = path.strip_prefix(root)
{
out.push(rel.to_path_buf());
}
}
}
out.sort();
@ -826,7 +876,10 @@ fn compute_java_source_hash(workdir: &Path, target_release: Option<u32>) -> Stri
h.update(b":release=host");
}
let out = h.finalize();
format!("{:016x}", u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap()))
format!(
"{:016x}",
u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap())
)
}
// ── PHP build sandbox ─────────────────────────────────────────────────────────
@ -869,7 +922,9 @@ pub fn prepare_php(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bu
for attempt in 0..MAX_ATTEMPTS {
if attempt > 0 {
std::thread::sleep(std::time::Duration::from_secs(BACKOFF[attempt as usize - 1]));
std::thread::sleep(std::time::Duration::from_secs(
BACKOFF[attempt as usize - 1],
));
}
match try_composer_install(workdir) {
Ok(()) => {
@ -892,7 +947,10 @@ pub fn prepare_php(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bu
}
}
Err(BuildError::BuildFailed { stderr: last_err, attempts: MAX_ATTEMPTS })
Err(BuildError::BuildFailed {
stderr: last_err,
attempts: MAX_ATTEMPTS,
})
}
fn try_composer_install(workdir: &Path) -> Result<(), String> {
@ -922,7 +980,10 @@ fn compute_php_lockfile_hash(workdir: &Path) -> String {
}
}
let out = h.finalize();
format!("{:016x}", u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap()))
format!(
"{:016x}",
u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap())
)
}
// ── C build sandbox ───────────────────────────────────────────────────────────
@ -959,7 +1020,9 @@ pub fn prepare_c(
for attempt in 0..MAX_ATTEMPTS {
if attempt > 0 {
std::thread::sleep(std::time::Duration::from_secs(BACKOFF[attempt as usize - 1]));
std::thread::sleep(std::time::Duration::from_secs(
BACKOFF[attempt as usize - 1],
));
}
let _ = std::fs::remove_dir_all(&cache_path);
std::fs::create_dir_all(&cache_path)?;
@ -979,7 +1042,10 @@ pub fn prepare_c(
}
}
Err(BuildError::BuildFailed { stderr: last_err, attempts: MAX_ATTEMPTS })
Err(BuildError::BuildFailed {
stderr: last_err,
attempts: MAX_ATTEMPTS,
})
}
fn try_build_c_binary(workdir: &Path, binary_dest: &Path, static_link: bool) -> Result<(), String> {
@ -1032,7 +1098,12 @@ pub(crate) fn static_link_env_override() -> bool {
)
}
fn run_cc(cc_bin: &str, workdir: &Path, binary_dest: &Path, leading_flags: &[&str]) -> Result<(), String> {
fn run_cc(
cc_bin: &str,
workdir: &Path,
binary_dest: &Path,
leading_flags: &[&str],
) -> Result<(), String> {
let binary_str = binary_dest.to_str().unwrap_or("nyx_harness");
let mut args: Vec<&str> = leading_flags.to_vec();
args.extend(["-o", binary_str, "main.c"]);
@ -1067,7 +1138,10 @@ fn compute_c_source_hash(workdir: &Path, static_link: bool) -> String {
h.update(b"static");
}
let out = h.finalize();
format!("{:016x}", u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap()))
format!(
"{:016x}",
u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap())
)
}
// ── C++ build sandbox ─────────────────────────────────────────────────────────
@ -1093,7 +1167,9 @@ pub fn prepare_cpp(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bu
for attempt in 0..MAX_ATTEMPTS {
if attempt > 0 {
std::thread::sleep(std::time::Duration::from_secs(BACKOFF[attempt as usize - 1]));
std::thread::sleep(std::time::Duration::from_secs(
BACKOFF[attempt as usize - 1],
));
}
let _ = std::fs::remove_dir_all(&cache_path);
std::fs::create_dir_all(&cache_path)?;
@ -1113,7 +1189,10 @@ pub fn prepare_cpp(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bu
}
}
Err(BuildError::BuildFailed { stderr: last_err, attempts: MAX_ATTEMPTS })
Err(BuildError::BuildFailed {
stderr: last_err,
attempts: MAX_ATTEMPTS,
})
}
fn try_build_cpp_binary(workdir: &Path, binary_dest: &Path) -> Result<(), String> {
@ -1122,7 +1201,14 @@ fn try_build_cpp_binary(workdir: &Path, binary_dest: &Path) -> Result<(), String
"c++".to_owned()
});
let output = Command::new(&cxx_bin)
.args(["-O0", "-g", "-std=c++17", "-o", binary_dest.to_str().unwrap_or("nyx_harness"), "main.cpp"])
.args([
"-O0",
"-g",
"-std=c++17",
"-o",
binary_dest.to_str().unwrap_or("nyx_harness"),
"main.cpp",
])
.current_dir(workdir)
.env_clear()
.env("PATH", std::env::var("PATH").unwrap_or_default())
@ -1145,7 +1231,10 @@ fn compute_cpp_source_hash(workdir: &Path) -> String {
}
}
let out = h.finalize();
format!("{:016x}", u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap()))
format!(
"{:016x}",
u64::from_le_bytes(out.as_bytes()[..8].try_into().unwrap())
)
}
// ── Uniform per-language build dispatch (Phase 26 — composite chains) ────────
@ -1251,10 +1340,14 @@ fn start_isolated_build_container(
network_none: bool,
) -> bool {
let mut args: Vec<&str> = vec![
"run", "-d", "--rm",
"--name", name,
"run",
"-d",
"--rm",
"--name",
name,
"--cap-drop=ALL",
"--security-opt", "no-new-privileges:true",
"--security-opt",
"no-new-privileges:true",
];
if network_none {
args.extend_from_slice(&["--network", "none"]);
@ -1319,16 +1412,22 @@ pub fn prepare_rust_in_docker(workdir: &Path) -> Result<(), String> {
return Err("failed to start rust:slim build container; image may not be available".into());
}
let _guard = BuildContainerGuard { docker: docker.clone(), name: container.clone() };
let _guard = BuildContainerGuard {
docker: docker.clone(),
name: container.clone(),
};
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
// CARGO_NET_OFFLINE prevents any registry contact; std lib is pre-built in the image.
let _ = std::process::Command::new(&docker)
.args([
"exec",
"-e", "CARGO_NET_OFFLINE=true",
"-e",
"CARGO_NET_OFFLINE=true",
&container,
"sh", "-c", "cd /build && cargo build --release 2>&1",
"sh",
"-c",
"cd /build && cargo build --release 2>&1",
])
.output();
@ -1347,10 +1446,15 @@ pub fn prepare_node_in_docker(workdir: &Path) -> Result<(), String> {
let container = build_container_id("nodebuild", workdir);
if !start_isolated_build_container(&docker, &container, "node:20-slim", true) {
return Err("failed to start node:20-slim build container; image may not be available".into());
return Err(
"failed to start node:20-slim build container; image may not be available".into(),
);
}
let _guard = BuildContainerGuard { docker: docker.clone(), name: container.clone() };
let _guard = BuildContainerGuard {
docker: docker.clone(),
name: container.clone(),
};
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
// npm install may fail if the registry is unreachable (--network none), but the
@ -1359,7 +1463,8 @@ pub fn prepare_node_in_docker(workdir: &Path) -> Result<(), String> {
.args([
"exec",
&container,
"sh", "-c",
"sh",
"-c",
"cd /build && npm install --no-save --no-audit --no-fund 2>&1",
])
.output();
@ -1379,20 +1484,29 @@ pub fn prepare_go_in_docker(workdir: &Path) -> Result<(), String> {
let container = build_container_id("gobuild", workdir);
if !start_isolated_build_container(&docker, &container, "golang:1.21-slim", true) {
return Err("failed to start golang:1.21-slim build container; image may not be available".into());
return Err(
"failed to start golang:1.21-slim build container; image may not be available".into(),
);
}
let _guard = BuildContainerGuard { docker: docker.clone(), name: container.clone() };
let _guard = BuildContainerGuard {
docker: docker.clone(),
name: container.clone(),
};
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
// GOPROXY=off prevents module downloads; std library is pre-compiled in the image.
let _ = std::process::Command::new(&docker)
.args([
"exec",
"-e", "GOPROXY=off",
"-e", "GONOSUMDB=*",
"-e",
"GOPROXY=off",
"-e",
"GONOSUMDB=*",
&container,
"sh", "-c", "cd /build && go build ./... 2>&1",
"sh",
"-c",
"cd /build && go build ./... 2>&1",
])
.output();
@ -1413,26 +1527,26 @@ pub fn prepare_java_in_docker(workdir: &Path) -> Result<(), String> {
// Bridge network: Maven must download exec-maven-plugin from Maven Central.
// Filesystem isolation still holds: /tmp inside the container is private.
if !start_isolated_build_container(
&docker,
&container,
"maven:3.9-eclipse-temurin-21",
false,
) {
if !start_isolated_build_container(&docker, &container, "maven:3.9-eclipse-temurin-21", false) {
return Err(
"failed to start maven:3.9-eclipse-temurin-21 build container; image may not be available"
.into(),
);
}
let _guard = BuildContainerGuard { docker: docker.clone(), name: container.clone() };
let _guard = BuildContainerGuard {
docker: docker.clone(),
name: container.clone(),
};
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
let _ = std::process::Command::new(&docker)
.args([
"exec",
&container,
"sh", "-c", "cd /build && mvn --no-transfer-progress validate 2>&1",
"sh",
"-c",
"cd /build && mvn --no-transfer-progress validate 2>&1",
])
.output();
@ -1451,10 +1565,15 @@ pub fn prepare_php_in_docker(workdir: &Path) -> Result<(), String> {
let container = build_container_id("phpbuild", workdir);
if !start_isolated_build_container(&docker, &container, "composer:2", true) {
return Err("failed to start composer:2 build container; image may not be available".into());
return Err(
"failed to start composer:2 build container; image may not be available".into(),
);
}
let _guard = BuildContainerGuard { docker: docker.clone(), name: container.clone() };
let _guard = BuildContainerGuard {
docker: docker.clone(),
name: container.clone(),
};
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
// Empty require{} means no packages to fetch; post-install-cmd still fires.
@ -1462,7 +1581,8 @@ pub fn prepare_php_in_docker(workdir: &Path) -> Result<(), String> {
.args([
"exec",
&container,
"sh", "-c",
"sh",
"-c",
"cd /build && composer install --no-dev --no-interaction --prefer-dist 2>&1",
])
.output();
@ -1519,11 +1639,7 @@ mod tests {
#[test]
fn java_source_hash_differs_across_target_release() {
let dir = tempfile::TempDir::new().unwrap();
std::fs::write(
dir.path().join("Vuln.java"),
"public class Vuln {}\n",
)
.unwrap();
std::fs::write(dir.path().join("Vuln.java"), "public class Vuln {}\n").unwrap();
let h_none = compute_java_source_hash(dir.path(), None);
let h17 = compute_java_source_hash(dir.path(), Some(17));
let h21 = compute_java_source_hash(dir.path(), Some(21));
@ -1568,7 +1684,10 @@ mod tests {
copy_dir_all(src.path(), dst.path()).unwrap();
assert_eq!(std::fs::read(dst.path().join("a.txt")).unwrap(), b"hello");
assert_eq!(std::fs::read(dst.path().join("sub").join("b.txt")).unwrap(), b"world");
assert_eq!(
std::fs::read(dst.path().join("sub").join("b.txt")).unwrap(),
b"world"
);
}
#[test]
@ -1760,7 +1879,11 @@ mod tests {
let result = dispatch_prepare(&spec, dir.path(), ProcessHardeningProfile::Standard)
.expect("TypeScript dispatch must succeed on a workdir with no package.json");
assert_eq!(result.lang, Lang::TypeScript, "lang field must echo the spec's");
assert_eq!(
result.lang,
Lang::TypeScript,
"lang field must echo the spec's"
);
assert!(
!result.cache_hit,
"first dispatch on a fresh cache must be a cache miss; got {result:?}",

View file

@ -67,9 +67,9 @@ mod xss;
mod xxe;
pub use registry::{
audit_marker_collisions, benign_payload_for, benign_payload_for_lang, materialise_bytes,
payloads_for, payloads_for_lang, resolve_benign_control, resolve_benign_control_lang,
CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL,
CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL, audit_marker_collisions, benign_payload_for,
benign_payload_for_lang, materialise_bytes, payloads_for, payloads_for_lang,
resolve_benign_control, resolve_benign_control_lang,
};
/// Re-exported canonical [`Oracle`] type.

View file

@ -19,8 +19,8 @@
//! The runtime `corpus_registry::audit` test mirrors both checks so
//! failure surfaces in `cargo test` output, not just `cargo build`.
use super::registry::{CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL};
use super::CuratedPayload;
use super::registry::{CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL};
use crate::labels::Cap;
/// Byte-level equality for `&'static str` usable in const eval.
@ -121,9 +121,7 @@ pub fn audit_benign_controls_runtime() -> Result<(), String> {
}
match p.benign_control {
Some(r) => {
let found = slice
.iter()
.any(|q| q.is_benign && q.label == r.label);
let found = slice.iter().any(|q| q.is_benign && q.label == r.label);
if !found {
return Err(format!(
"({:?}, {:?}) vuln payload {:?} references missing \
@ -180,17 +178,18 @@ pub fn audit_benign_label_uniqueness_runtime() -> Result<(), String> {
continue;
}
if let Some(prev_lang) = bucket.insert(p.label, lang)
&& prev_lang != lang {
return Err(format!(
"benign label {:?} for cap {:#x} is registered in both \
&& prev_lang != lang
{
return Err(format!(
"benign label {:?} for cap {:#x} is registered in both \
{:?} and {:?} lang-agnostic resolve_benign_control \
could match the wrong language",
p.label,
cap.bits(),
prev_lang,
lang,
));
}
p.label,
cap.bits(),
prev_lang,
lang,
));
}
}
}
Ok(())
@ -206,7 +205,6 @@ mod corpus_registry {
fn audit() {
audit_benign_controls_runtime().expect("benign_control audit failed");
audit_cap_coverage_runtime().expect("cap coverage audit failed");
audit_benign_label_uniqueness_runtime()
.expect("benign label uniqueness audit failed");
audit_benign_label_uniqueness_runtime().expect("benign label uniqueness audit failed");
}
}

View file

@ -19,7 +19,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-c" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-c",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-cpp" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-cpp",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -19,7 +19,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-go" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-go",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -17,7 +17,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-java" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-java",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -17,7 +17,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-javascript" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-javascript",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -17,7 +17,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-php" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-php",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-python" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-python",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -18,7 +18,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-ruby" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-ruby",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign",
}),
no_benign_control_rationale: None,
},
// Benign control: plain text that should never produce the cmdi marker.

View file

@ -17,7 +17,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "cmdi-benign-typescript" }),
benign_control: Some(PayloadRef {
label: "cmdi-benign-typescript",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -11,7 +11,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-go-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -19,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/go/vuln.go"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
benign_control: Some(PayloadRef {
label: "crypto-go-benign",
}),
@ -29,7 +33,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-go-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-java-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -30,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/java/vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
benign_control: Some(PayloadRef {
label: "crypto-java-benign",
}),
@ -40,7 +44,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-java-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-php-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/php/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
benign_control: Some(PayloadRef {
label: "crypto-php-benign",
}),
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-php-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -20,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-python-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -28,7 +30,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
benign_control: Some(PayloadRef {
label: "crypto-python-benign",
}),
@ -38,7 +42,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-python-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -11,7 +11,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_WEAK",
label: "crypto-rust-weak-random",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -19,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/crypto/rust/vuln.rs"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
benign_control: Some(PayloadRef {
label: "crypto-rust-benign",
}),
@ -29,7 +33,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"NYX_CRYPTO_STRONG",
label: "crypto-rust-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
predicates: &[ProbePredicate::WeakKeyEntropy {
max_bits: WEAK_BITS,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"attacker.test",
label: "data-exfil-go-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/go/vuln.go"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "data-exfil-go-benign",
}),
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"127.0.0.1",
label: "data-exfil-go-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"attacker.test",
label: "data-exfil-java-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/java/vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "data-exfil-java-benign",
}),
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"127.0.0.1",
label: "data-exfil-java-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"attacker.test",
label: "data-exfil-js-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/js/vuln.js"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "data-exfil-js-benign",
}),
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"127.0.0.1",
label: "data-exfil-js-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"attacker.test",
label: "data-exfil-php-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/php/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "data-exfil-php-benign",
}),
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"127.0.0.1",
label: "data-exfil-php-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"attacker.test",
label: "data-exfil-python-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "data-exfil-python-benign",
}),
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"127.0.0.1",
label: "data-exfil-python-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"attacker.test",
label: "data-exfil-ruby-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "data-exfil-ruby-benign",
}),
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"127.0.0.1",
label: "data-exfil-ruby-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"attacker.test",
label: "data-exfil-rust-external",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/data_exfil/rust/vuln.rs"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "data-exfil-rust-benign",
}),
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"127.0.0.1",
label: "data-exfil-rust-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::OutboundHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -29,9 +29,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 7,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/deserialize/java/Vuln.java",
],
fixture_paths: &["tests/dynamic_fixtures/deserialize/java/Vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
require_invoked: true,
@ -55,9 +53,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 7,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/deserialize/java/Benign.java",
],
fixture_paths: &["tests/dynamic_fixtures/deserialize/java/Benign.java"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -26,9 +26,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 7,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/deserialize/php/vuln.php",
],
fixture_paths: &["tests/dynamic_fixtures/deserialize/php/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
require_invoked: true,
@ -53,9 +51,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 7,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/deserialize/php/benign.php",
],
fixture_paths: &["tests/dynamic_fixtures/deserialize/php/benign.php"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -25,9 +25,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 7,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/deserialize/python/vuln.py",
],
fixture_paths: &["tests/dynamic_fixtures/deserialize/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
require_invoked: true,
@ -49,9 +47,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 7,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/deserialize/python/benign.py",
],
fixture_paths: &["tests/dynamic_fixtures/deserialize/python/benign.py"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -26,9 +26,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 7,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/deserialize/ruby/vuln.rb",
],
fixture_paths: &["tests/dynamic_fixtures/deserialize/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
require_invoked: true,
@ -50,9 +48,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 7,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/deserialize/ruby/benign.rb",
],
fixture_paths: &["tests/dynamic_fixtures/deserialize/ruby/benign.rb"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -32,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "fmt-string-benign" }),
benign_control: Some(PayloadRef {
label: "fmt-string-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -21,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"https://attacker.test/",
label: "open-redirect-go-absolute",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -29,7 +31,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/open_redirect/go/vuln.go"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "open-redirect-go-benign",
}),
@ -39,7 +43,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"/dashboard",
label: "open-redirect-go-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -26,7 +26,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"https://attacker.test/",
label: "open-redirect-java-absolute",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -34,7 +36,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/open_redirect/java/Vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "open-redirect-java-benign",
}),
@ -44,7 +48,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"/dashboard",
label: "open-redirect-java-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -20,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"https://attacker.test/",
label: "open-redirect-js-absolute",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -28,7 +30,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/open_redirect/js/vuln.js"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "open-redirect-js-benign",
}),
@ -38,7 +42,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"/dashboard",
label: "open-redirect-js-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"https://attacker.test/",
label: "open-redirect-php-absolute",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -30,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/open_redirect/php/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "open-redirect-php-benign",
}),
@ -40,7 +44,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"/dashboard",
label: "open-redirect-php-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -21,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"https://attacker.test/",
label: "open-redirect-python-absolute",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -29,7 +31,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/open_redirect/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "open-redirect-python-benign",
}),
@ -39,7 +43,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"/dashboard",
label: "open-redirect-python-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -20,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"https://attacker.test/",
label: "open-redirect-ruby-absolute",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -28,7 +30,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/open_redirect/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "open-redirect-ruby-benign",
}),
@ -38,7 +42,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"/dashboard",
label: "open-redirect-ruby-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -20,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"https://attacker.test/",
label: "open-redirect-rust-absolute",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: false,
provenance: PayloadProvenance::Curated,
@ -28,7 +30,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
deprecated_at_corpus_version: None,
fixture_paths: &["tests/dynamic_fixtures/open_redirect/rust/vuln.rs"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
benign_control: Some(PayloadRef {
label: "open-redirect-rust-benign",
}),
@ -38,7 +42,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
bytes: b"/dashboard",
label: "open-redirect-rust-benign",
oracle: Oracle::SinkProbe {
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
predicates: &[ProbePredicate::RedirectHostNotIn {
allowlist: ALLOWLIST,
}],
},
is_benign: true,
provenance: PayloadProvenance::Curated,

View file

@ -21,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "path-traversal-benign" }),
benign_control: Some(PayloadRef {
label: "path-traversal-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -23,12 +23,12 @@
use std::collections::HashMap;
use std::sync::OnceLock;
use super::{CapCorpus, CuratedPayload, Oracle};
use super::{
cmdi, crypto, data_exfil, deserialize, fmt_string, header_injection, json_parse, ldap,
open_redirect, path_trav, prototype_pollution, sqli, ssrf, ssti, unauthorized_id, xpath, xss,
xxe,
};
use super::{CapCorpus, CuratedPayload, Oracle};
use crate::dynamic::oracle::ProbePredicate;
use crate::labels::Cap;
use crate::symbol::Lang;
@ -93,7 +93,11 @@ const ENTRIES: &[(Cap, Lang, &[CuratedPayload])] = &[
(Cap::HTML_ESCAPE, Lang::Rust, xss::rust::PAYLOADS),
(Cap::FMT_STRING, Lang::C, fmt_string::c::PAYLOADS),
(Cap::DESERIALIZE, Lang::Java, deserialize::java::PAYLOADS),
(Cap::DESERIALIZE, Lang::Python, deserialize::python::PAYLOADS),
(
Cap::DESERIALIZE,
Lang::Python,
deserialize::python::PAYLOADS,
),
(Cap::DESERIALIZE, Lang::Php, deserialize::php::PAYLOADS),
(Cap::DESERIALIZE, Lang::Ruby, deserialize::ruby::PAYLOADS),
(Cap::SSTI, Lang::Python, ssti::python_jinja2::PAYLOADS),
@ -113,20 +117,68 @@ const ENTRIES: &[(Cap, Lang, &[CuratedPayload])] = &[
(Cap::XPATH_INJECTION, Lang::Python, xpath::python::PAYLOADS),
(Cap::XPATH_INJECTION, Lang::Php, xpath::php::PAYLOADS),
(Cap::XPATH_INJECTION, Lang::JavaScript, xpath::js::PAYLOADS),
(Cap::HEADER_INJECTION, Lang::Java, header_injection::java::PAYLOADS),
(Cap::HEADER_INJECTION, Lang::Python, header_injection::python::PAYLOADS),
(Cap::HEADER_INJECTION, Lang::Php, header_injection::php::PAYLOADS),
(Cap::HEADER_INJECTION, Lang::Ruby, header_injection::ruby::PAYLOADS),
(Cap::HEADER_INJECTION, Lang::JavaScript, header_injection::js::PAYLOADS),
(Cap::HEADER_INJECTION, Lang::Go, header_injection::go::PAYLOADS),
(Cap::HEADER_INJECTION, Lang::Rust, header_injection::rust::PAYLOADS),
(Cap::OPEN_REDIRECT, Lang::Java, open_redirect::java::PAYLOADS),
(Cap::OPEN_REDIRECT, Lang::Python, open_redirect::python::PAYLOADS),
(
Cap::HEADER_INJECTION,
Lang::Java,
header_injection::java::PAYLOADS,
),
(
Cap::HEADER_INJECTION,
Lang::Python,
header_injection::python::PAYLOADS,
),
(
Cap::HEADER_INJECTION,
Lang::Php,
header_injection::php::PAYLOADS,
),
(
Cap::HEADER_INJECTION,
Lang::Ruby,
header_injection::ruby::PAYLOADS,
),
(
Cap::HEADER_INJECTION,
Lang::JavaScript,
header_injection::js::PAYLOADS,
),
(
Cap::HEADER_INJECTION,
Lang::Go,
header_injection::go::PAYLOADS,
),
(
Cap::HEADER_INJECTION,
Lang::Rust,
header_injection::rust::PAYLOADS,
),
(
Cap::OPEN_REDIRECT,
Lang::Java,
open_redirect::java::PAYLOADS,
),
(
Cap::OPEN_REDIRECT,
Lang::Python,
open_redirect::python::PAYLOADS,
),
(Cap::OPEN_REDIRECT, Lang::Php, open_redirect::php::PAYLOADS),
(Cap::OPEN_REDIRECT, Lang::Ruby, open_redirect::ruby::PAYLOADS),
(Cap::OPEN_REDIRECT, Lang::JavaScript, open_redirect::js::PAYLOADS),
(
Cap::OPEN_REDIRECT,
Lang::Ruby,
open_redirect::ruby::PAYLOADS,
),
(
Cap::OPEN_REDIRECT,
Lang::JavaScript,
open_redirect::js::PAYLOADS,
),
(Cap::OPEN_REDIRECT, Lang::Go, open_redirect::go::PAYLOADS),
(Cap::OPEN_REDIRECT, Lang::Rust, open_redirect::rust::PAYLOADS),
(
Cap::OPEN_REDIRECT,
Lang::Rust,
open_redirect::rust::PAYLOADS,
),
(
Cap::PROTOTYPE_POLLUTION,
Lang::JavaScript,
@ -142,16 +194,48 @@ const ENTRIES: &[(Cap, Lang, &[CuratedPayload])] = &[
(Cap::CRYPTO, Lang::Php, crypto::php::PAYLOADS),
(Cap::CRYPTO, Lang::Go, crypto::go::PAYLOADS),
(Cap::CRYPTO, Lang::Rust, crypto::rust::PAYLOADS),
(Cap::JSON_PARSE, Lang::JavaScript, json_parse::javascript::PAYLOADS),
(
Cap::JSON_PARSE,
Lang::JavaScript,
json_parse::javascript::PAYLOADS,
),
(Cap::JSON_PARSE, Lang::Python, json_parse::python::PAYLOADS),
(Cap::JSON_PARSE, Lang::Ruby, json_parse::ruby::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Python, unauthorized_id::python::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Ruby, unauthorized_id::ruby::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Java, unauthorized_id::java::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Php, unauthorized_id::php::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::JavaScript, unauthorized_id::js::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Go, unauthorized_id::go::PAYLOADS),
(Cap::UNAUTHORIZED_ID, Lang::Rust, unauthorized_id::rust::PAYLOADS),
(
Cap::UNAUTHORIZED_ID,
Lang::Python,
unauthorized_id::python::PAYLOADS,
),
(
Cap::UNAUTHORIZED_ID,
Lang::Ruby,
unauthorized_id::ruby::PAYLOADS,
),
(
Cap::UNAUTHORIZED_ID,
Lang::Java,
unauthorized_id::java::PAYLOADS,
),
(
Cap::UNAUTHORIZED_ID,
Lang::Php,
unauthorized_id::php::PAYLOADS,
),
(
Cap::UNAUTHORIZED_ID,
Lang::JavaScript,
unauthorized_id::js::PAYLOADS,
),
(
Cap::UNAUTHORIZED_ID,
Lang::Go,
unauthorized_id::go::PAYLOADS,
),
(
Cap::UNAUTHORIZED_ID,
Lang::Rust,
unauthorized_id::rust::PAYLOADS,
),
(Cap::DATA_EXFIL, Lang::Python, data_exfil::python::PAYLOADS),
(Cap::DATA_EXFIL, Lang::Ruby, data_exfil::ruby::PAYLOADS),
(Cap::DATA_EXFIL, Lang::Java, data_exfil::java::PAYLOADS),
@ -355,7 +439,7 @@ pub fn audit_marker_collisions() -> Vec<(&'static str, &'static str, &'static st
#[cfg(test)]
mod tests {
use super::*;
use crate::dynamic::corpus::{benign_payload_for, CORPUS_VERSION};
use crate::dynamic::corpus::{CORPUS_VERSION, benign_payload_for};
#[test]
fn supported_caps_have_payloads() {
@ -404,8 +488,14 @@ mod tests {
#[test]
fn phase_11_caps_pair_benign_controls_per_lang() {
let cases: &[(Cap, &[Lang])] = &[
(Cap::CRYPTO, &[Lang::Java, Lang::Python, Lang::Php, Lang::Go, Lang::Rust]),
(Cap::JSON_PARSE, &[Lang::JavaScript, Lang::Python, Lang::Ruby]),
(
Cap::CRYPTO,
&[Lang::Java, Lang::Python, Lang::Php, Lang::Go, Lang::Rust],
),
(
Cap::JSON_PARSE,
&[Lang::JavaScript, Lang::Python, Lang::Ruby],
),
(
Cap::UNAUTHORIZED_ID,
&[
@ -434,10 +524,7 @@ mod tests {
for (cap, langs) in cases {
for lang in *langs {
let slice = payloads_for_lang(*cap, *lang);
assert!(
!slice.is_empty(),
"({cap:?}, {lang:?}) must have payloads",
);
assert!(!slice.is_empty(), "({cap:?}, {lang:?}) must have payloads",);
let vuln = slice
.iter()
.find(|p| !p.is_benign)
@ -596,7 +683,10 @@ mod tests {
#[test]
fn ssrf_has_oob_nonce_slot() {
let has_oob = payloads_for(Cap::SSRF).iter().any(|p| p.oob_nonce_slot);
assert!(has_oob, "SSRF corpus must include an OOB-nonce-slot payload");
assert!(
has_oob,
"SSRF corpus must include an OOB-nonce-slot payload"
);
}
#[test]
@ -617,8 +707,7 @@ mod tests {
.find(|p| p.oob_nonce_slot)
.expect("must have OOB payload");
let url = "http://127.0.0.1:54321/mynonce";
let bytes =
materialise_bytes(p, Some(url)).expect("OOB payload materialises with URL");
let bytes = materialise_bytes(p, Some(url)).expect("OOB payload materialises with URL");
assert_eq!(&*bytes, url.as_bytes());
}
@ -637,7 +726,11 @@ mod tests {
(Cap::SQL_QUERY, "sqli-tautology", "sqli-benign"),
(Cap::SQL_QUERY, "sqli-union-nyx", "sqli-benign"),
(Cap::CODE_EXEC, "cmdi-echo-marker", "cmdi-benign"),
(Cap::FILE_IO, "path-traversal-passwd", "path-traversal-benign"),
(
Cap::FILE_IO,
"path-traversal-passwd",
"path-traversal-benign",
),
(Cap::SSRF, "ssrf-file-scheme", "ssrf-benign"),
(Cap::HTML_ESCAPE, "xss-script-marker", "xss-benign-text"),
];
@ -723,7 +816,10 @@ mod tests {
let mut entries_by_cap: HashMap<u32, Vec<(Lang, &'static [CuratedPayload])>> =
HashMap::new();
for &(cap, lang, slice) in CORPUS.entries {
entries_by_cap.entry(cap.bits()).or_default().push((lang, slice));
entries_by_cap
.entry(cap.bits())
.or_default()
.push((lang, slice));
}
for (cap_bits, langs) in &entries_by_cap {
if langs.len() != 1 {
@ -899,9 +995,8 @@ mod tests {
.iter()
.find(|p| !p.is_benign)
.expect("each lang must have an LDAP vuln payload");
let resolved =
super::resolve_benign_control_lang(vuln, Cap::LDAP_INJECTION, lang)
.expect("lang-aware benign control must resolve");
let resolved = super::resolve_benign_control_lang(vuln, Cap::LDAP_INJECTION, lang)
.expect("lang-aware benign control must resolve");
assert!(resolved.is_benign);
}
}
@ -941,9 +1036,8 @@ mod tests {
.iter()
.find(|p| !p.is_benign)
.expect("each lang must have an XPath vuln payload");
let resolved =
super::resolve_benign_control_lang(vuln, Cap::XPATH_INJECTION, lang)
.expect("lang-aware benign control must resolve");
let resolved = super::resolve_benign_control_lang(vuln, Cap::XPATH_INJECTION, lang)
.expect("lang-aware benign control must resolve");
assert!(resolved.is_benign);
}
}
@ -992,9 +1086,8 @@ mod tests {
.iter()
.find(|p| !p.is_benign)
.expect("each lang must have a HEADER_INJECTION vuln payload");
let resolved =
super::resolve_benign_control_lang(vuln, Cap::HEADER_INJECTION, lang)
.expect("lang-aware benign control must resolve");
let resolved = super::resolve_benign_control_lang(vuln, Cap::HEADER_INJECTION, lang)
.expect("lang-aware benign control must resolve");
assert!(resolved.is_benign);
}
}
@ -1036,9 +1129,8 @@ mod tests {
.iter()
.find(|p| !p.is_benign)
.expect("each lang must have a PROTOTYPE_POLLUTION vuln payload");
let resolved =
super::resolve_benign_control_lang(vuln, Cap::PROTOTYPE_POLLUTION, lang)
.expect("lang-aware benign control must resolve");
let resolved = super::resolve_benign_control_lang(vuln, Cap::PROTOTYPE_POLLUTION, lang)
.expect("lang-aware benign control must resolve");
assert!(resolved.is_benign);
}
}

View file

@ -18,7 +18,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "sqli-benign" }),
benign_control: Some(PayloadRef {
label: "sqli-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {
@ -32,7 +34,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "sqli-benign" }),
benign_control: Some(PayloadRef {
label: "sqli-benign",
}),
no_benign_control_rationale: None,
},
// Benign control: ordinary value that should never produce the SQL marker.

View file

@ -26,7 +26,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
fixture_paths: &["tests/benchmark/corpus/rust/ssrf/ssrf_reqwest.rs"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "ssrf-benign" }),
benign_control: Some(PayloadRef {
label: "ssrf-benign",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -19,9 +19,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/java_thymeleaf/vuln.java",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/java_thymeleaf/vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
benign_control: Some(PayloadRef {
@ -39,9 +37,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/java_thymeleaf/benign.java",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/java_thymeleaf/benign.java"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -25,9 +25,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/js_handlebars/vuln.js",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/js_handlebars/vuln.js"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
benign_control: Some(PayloadRef {
@ -45,9 +43,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/js_handlebars/benign.js",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/js_handlebars/benign.js"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -19,9 +19,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/php_twig/vuln.php",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/php_twig/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
benign_control: Some(PayloadRef {
@ -39,9 +37,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/php_twig/benign.php",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/php_twig/benign.php"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -26,9 +26,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/python_jinja2/vuln.py",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/python_jinja2/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
benign_control: Some(PayloadRef {
@ -46,9 +44,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/python_jinja2/benign.py",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/python_jinja2/benign.py"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -19,9 +19,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/ruby_erb/vuln.rb",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/ruby_erb/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
benign_control: Some(PayloadRef {
@ -39,9 +37,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 8,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/ssti/ruby_erb/benign.rb",
],
fixture_paths: &["tests/dynamic_fixtures/ssti/ruby_erb/benign.rb"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -18,7 +18,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
fixture_paths: &["tests/benchmark/corpus/rust/xss/axum_html/main.rs"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: Some(PayloadRef { label: "xss-benign-text" }),
benign_control: Some(PayloadRef {
label: "xss-benign-text",
}),
no_benign_control_rationale: None,
},
CuratedPayload {

View file

@ -29,9 +29,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/go/vuln.go",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/go/vuln.go"],
oob_nonce_slot: true,
probe_predicates: &[],
benign_control: None,
@ -57,9 +55,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/go/vuln.go",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/go/vuln.go"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
require_expanded: true,
@ -82,9 +78,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/go/benign.go",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/go/benign.go"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -31,9 +31,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/java/Vuln.java",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Vuln.java"],
oob_nonce_slot: true,
probe_predicates: &[],
benign_control: None,
@ -59,9 +57,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/java/Vuln.java",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Vuln.java"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
require_expanded: true,
@ -84,9 +80,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/java/Benign.java",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Benign.java"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -29,9 +29,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/php/vuln.php",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/php/vuln.php"],
oob_nonce_slot: true,
probe_predicates: &[],
benign_control: None,
@ -57,9 +55,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/php/vuln.php",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/php/vuln.php"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
require_expanded: true,
@ -82,9 +78,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/php/benign.php",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/php/benign.php"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -39,9 +39,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/python/vuln.py",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/python/vuln.py"],
oob_nonce_slot: true,
probe_predicates: &[],
benign_control: None,
@ -68,9 +66,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/python/vuln.py",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/python/vuln.py"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
require_expanded: true,
@ -93,9 +89,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/python/benign.py",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/python/benign.py"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -28,9 +28,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 15,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/ruby/vuln.rb",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/vuln.rb"],
oob_nonce_slot: true,
probe_predicates: &[],
benign_control: None,
@ -56,9 +54,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/ruby/vuln.rb",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/vuln.rb"],
oob_nonce_slot: false,
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
require_expanded: true,
@ -81,9 +77,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
provenance: PayloadProvenance::Curated,
since_corpus_version: 9,
deprecated_at_corpus_version: None,
fixture_paths: &[
"tests/dynamic_fixtures/xxe/ruby/benign.rb",
],
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/benign.rb"],
oob_nonce_slot: false,
probe_predicates: &[],
benign_control: None,

View file

@ -113,7 +113,10 @@ mod tests {
#[test]
fn rule_a_both_fire_is_collision() {
assert_eq!(evaluate(true, true), DifferentialVerdict::OracleCollisionSuspected);
assert_eq!(
evaluate(true, true),
DifferentialVerdict::OracleCollisionSuspected
);
}
#[test]
@ -128,7 +131,10 @@ mod tests {
#[test]
fn rule_d_only_benign_fires_is_reversed() {
assert_eq!(evaluate(false, true), DifferentialVerdict::ReversedDifferential);
assert_eq!(
evaluate(false, true),
DifferentialVerdict::ReversedDifferential
);
}
#[test]

View file

@ -33,12 +33,12 @@
//! source file. The 10 MiB ceiling protects against runaway full-tree
//! copy regressions called out in the Phase 09 acceptance.
use crate::callgraph::{callers_of, CallGraph};
use crate::callgraph::{CallGraph, callers_of};
use crate::dynamic::spec::HarnessSpec;
use crate::dynamic::toolchain::{self, ToolchainResolution};
use crate::summary::GlobalSummaries;
use crate::symbol::{FuncKey, Lang};
use crate::utils::project::{detect_frameworks, DetectedFramework};
use crate::utils::project::{DetectedFramework, detect_frameworks};
use std::collections::HashSet;
use std::io;
use std::path::{Path, PathBuf};
@ -139,7 +139,12 @@ pub fn extract_env_var_references(entry_file: &Path, lang: Lang) -> Vec<String>
],
Lang::JavaScript | Lang::TypeScript => &["process.env.", "process.env["],
Lang::Java => &["System.getenv(", "getenv("],
Lang::Rust => &["std::env::var(", "env::var(", "env::var_os(", "std::env::var_os("],
Lang::Rust => &[
"std::env::var(",
"env::var(",
"env::var_os(",
"std::env::var_os(",
],
Lang::Go => &["os.Getenv(", "os.LookupEnv("],
Lang::Php => &["getenv(", "$_ENV[", "$_SERVER["],
Lang::Ruby => &["ENV[", "ENV.fetch(", "ENV.fetch "],
@ -161,9 +166,12 @@ pub fn extract_env_var_references(entry_file: &Path, lang: Lang) -> Vec<String>
_ => extract_quoted_arg(tail),
};
if let Some(name) = name
&& !name.is_empty() && is_env_var_name(&name) && seen.insert(name.clone()) {
out.push(name);
}
&& !name.is_empty()
&& is_env_var_name(&name)
&& seen.insert(name.clone())
{
out.push(name);
}
}
}
out
@ -199,7 +207,9 @@ fn extract_quoted_arg(s: &str) -> Option<String> {
if i >= bytes.len() {
return None;
}
std::str::from_utf8(&bytes[start..i]).ok().map(|s| s.to_owned())
std::str::from_utf8(&bytes[start..i])
.ok()
.map(|s| s.to_owned())
}
/// Extract a bare identifier (e.g. `FOO` in `process.env.FOO`). Stops at
@ -241,11 +251,7 @@ fn is_env_var_name(s: &str) -> bool {
///
/// Returned in deterministic source-order so two runs against the same
/// inputs produce byte-identical env layouts.
pub fn build_secret_bag(
entry_file: &Path,
lang: Lang,
spec_hash: &str,
) -> Vec<(String, String)> {
pub fn build_secret_bag(entry_file: &Path, lang: Lang, spec_hash: &str) -> Vec<(String, String)> {
let mut out: Vec<(String, String)> = Vec::new();
for name in extract_env_var_references(entry_file, lang) {
let val = derive_secret(spec_hash, &name);
@ -288,9 +294,33 @@ const CONFIG_FILE_CANDIDATES: &[&str] = &[
/// user's pinned dependency set. Order is significant only insofar as
/// the first match wins for [`CapturedDeps::lockfile_origin`].
const MANIFEST_FILES_BY_LANG: &[(Lang, &[&str])] = &[
(Lang::Python, &["requirements.txt", "pyproject.toml", "Pipfile", "Pipfile.lock"]),
(Lang::JavaScript, &["package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"]),
(Lang::TypeScript, &["package.json", "package-lock.json", "yarn.lock", "tsconfig.json"]),
(
Lang::Python,
&[
"requirements.txt",
"pyproject.toml",
"Pipfile",
"Pipfile.lock",
],
),
(
Lang::JavaScript,
&[
"package.json",
"package-lock.json",
"yarn.lock",
"pnpm-lock.yaml",
],
),
(
Lang::TypeScript,
&[
"package.json",
"package-lock.json",
"yarn.lock",
"tsconfig.json",
],
),
(Lang::Rust, &["Cargo.toml", "Cargo.lock"]),
(Lang::Go, &["go.mod", "go.sum"]),
(Lang::Java, &["pom.xml", "build.gradle", "build.gradle.kts"]),
@ -470,7 +500,8 @@ pub fn capture_project_dependencies_with_context(
let manifests = collect_manifest_files(spec.lang, project_root);
let lockfile = manifests.first().cloned();
let source_closure = compute_source_closure(&entry_file, project_root, spec, summaries, callgraph);
let source_closure =
compute_source_closure(&entry_file, project_root, spec, summaries, callgraph);
CapturedDeps {
project_root: project_root.to_path_buf(),
@ -575,13 +606,8 @@ pub fn stage_workdir_full(
Some(r) => r,
None => continue,
};
running_bytes = copy_into_workdir(
manifest,
workdir,
&rel,
running_bytes,
&mut staged_sources,
)?;
running_bytes =
copy_into_workdir(manifest, workdir, &rel, running_bytes, &mut staged_sources)?;
if lockfile_in_workdir.is_none() {
lockfile_in_workdir = Some(workdir.join(&rel));
}
@ -596,8 +622,7 @@ pub fn stage_workdir_full(
Some(r) => r,
None => PathBuf::from(cfg.file_name().unwrap_or_default()),
};
running_bytes =
copy_into_workdir(cfg, workdir, &rel, running_bytes, &mut staged_sources)?;
running_bytes = copy_into_workdir(cfg, workdir, &rel, running_bytes, &mut staged_sources)?;
}
// Phase 11 — Track D.4: populate the per-spec secret bag for every
@ -642,14 +667,12 @@ fn copy_into_workdir(
};
let size = metadata.len();
if running_bytes.saturating_add(size) > MAX_WORKDIR_BYTES {
return Err(io::Error::other(
format!(
"staged workdir would exceed {} bytes (next file `{}` = {} bytes)",
MAX_WORKDIR_BYTES,
rel.display(),
size
),
));
return Err(io::Error::other(format!(
"staged workdir would exceed {} bytes (next file `{}` = {} bytes)",
MAX_WORKDIR_BYTES,
rel.display(),
size
)));
}
let dest = workdir.join(rel);
if let Some(parent) = dest.parent() {
@ -669,8 +692,14 @@ fn resolve_under_root(project_root: &Path, entry_file: &str) -> PathBuf {
}
fn rel_under_root(path: &Path, root: &Path) -> Option<PathBuf> {
let abs_path = path.canonicalize().ok().unwrap_or_else(|| path.to_path_buf());
let abs_root = root.canonicalize().ok().unwrap_or_else(|| root.to_path_buf());
let abs_path = path
.canonicalize()
.ok()
.unwrap_or_else(|| path.to_path_buf());
let abs_root = root
.canonicalize()
.ok()
.unwrap_or_else(|| root.to_path_buf());
abs_path
.strip_prefix(&abs_root)
.ok()
@ -729,9 +758,11 @@ fn collect_config_files(entry_file: &Path, project_root: &Path) -> Vec<PathBuf>
let mut v = Vec::new();
v.push(project_root.to_path_buf());
if let Some(parent) = entry_file.parent()
&& parent != project_root && parent.starts_with(project_root) {
v.push(parent.to_path_buf());
}
&& parent != project_root
&& parent.starts_with(project_root)
{
v.push(parent.to_path_buf());
}
v
};
for dir in &dirs {
@ -1253,7 +1284,11 @@ import './local-thing';
"from flask import Flask, request\nimport os\nimport requests\n",
)
.unwrap();
fs::write(root.join("requirements.txt"), "Flask==2.3.0\nrequests>=2.28\n").unwrap();
fs::write(
root.join("requirements.txt"),
"Flask==2.3.0\nrequests>=2.28\n",
)
.unwrap();
let spec = fake_spec("app.py", Lang::Python);
let captured = capture_project_dependencies(root, &spec);
assert!(captured.direct_deps.contains(&"flask".to_owned()));

View file

@ -119,8 +119,10 @@ mod tests {
fn skips_when_chi_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";
let tree = parse(src);
assert!(GoChiAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none());
assert!(
GoChiAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -120,8 +120,10 @@ mod tests {
fn skips_when_echo_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";
let tree = parse(src);
assert!(GoEchoAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none());
assert!(
GoEchoAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -126,8 +126,10 @@ mod tests {
fn skips_when_fiber_not_imported() {
let src: &[u8] = b"package main\nfunc Show() {}\n";
let tree = parse(src);
assert!(GoFiberAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none());
assert!(
GoFiberAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -124,9 +124,11 @@ mod tests {
fn skips_when_gin_not_imported() {
let src: &[u8] = b"package main\nfunc Show(id string) {}\n";
let tree = parse(src);
assert!(GoGinAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none());
assert!(
GoGinAdapter
.detect(&summary("Show"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -134,9 +136,11 @@ mod tests {
let src: &[u8] =
b"package main\nimport \"github.com/gin-gonic/gin\"\nfunc init() { r := gin.Default(); r.GET(\"/users\", Show) }\nfunc Helper(x string) {}\n";
let tree = parse(src);
assert!(GoGinAdapter
.detect(&summary("Helper"), tree.root_node(), src)
.is_none());
assert!(
GoGinAdapter
.detect(&summary("Helper"), tree.root_node(), src)
.is_none()
);
}
#[test]

View file

@ -83,22 +83,13 @@ fn contains_any(haystack: &[u8], needles: &[&[u8]]) -> bool {
/// Find a top-level `function_declaration` or a `method_declaration`
/// whose name equals `target`. Returns the matching node.
pub fn find_go_function<'a>(
root: Node<'a>,
bytes: &'a [u8],
target: &str,
) -> Option<Node<'a>> {
pub fn find_go_function<'a>(root: Node<'a>, bytes: &'a [u8], target: &str) -> Option<Node<'a>> {
let mut hit: Option<Node<'a>> = None;
walk_go(root, bytes, target, &mut hit);
hit
}
fn walk_go<'a>(
node: Node<'a>,
bytes: &'a [u8],
target: &str,
out: &mut Option<Node<'a>>,
) {
fn walk_go<'a>(node: Node<'a>, bytes: &'a [u8], target: &str, out: &mut Option<Node<'a>>) {
if out.is_some() {
return;
}
@ -136,9 +127,10 @@ pub fn go_formal_names(func: Node<'_>, bytes: &[u8]) -> Vec<String> {
let mut pc = p.walk();
for c in p.named_children(&mut pc) {
if c.kind() == "identifier"
&& let Ok(text) = c.utf8_text(bytes) {
out.push(text.to_owned());
}
&& let Ok(text) = c.utf8_text(bytes)
{
out.push(text.to_owned());
}
}
}
out
@ -428,8 +420,7 @@ mod tests {
let src: &[u8] =
b"package main\nfunc init() { r := gin.New(); r.GET(\"/u/:id\", Show) }\nfunc Show(c interface{}) {}\n";
let tree = parse(src);
let (method, path) =
find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
let (method, path) = find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
assert_eq!(method, HttpMethod::GET);
assert_eq!(path, "/u/:id");
}
@ -439,8 +430,7 @@ mod tests {
let src: &[u8] =
b"package main\nfunc init() { r := chi.NewRouter(); r.Get(\"/x\", controllers.Show) }\n";
let tree = parse(src);
let (method, path) =
find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
let (method, path) = find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
assert_eq!(method, HttpMethod::GET);
assert_eq!(path, "/x");
}

View file

@ -133,9 +133,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("Set")],
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -146,9 +148,11 @@ mod tests {
name: "Add".into(),
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -174,9 +178,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -195,9 +201,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -213,8 +221,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderGoAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -17,7 +17,15 @@ const ADAPTER_NAME: &str = "header-java";
fn callee_is_header_setter(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "setHeader" | "addHeader" | "setDateHeader" | "addDateHeader" | "setIntHeader" | "addIntHeader")
matches!(
last,
"setHeader"
| "addHeader"
| "setDateHeader"
| "addDateHeader"
| "setIntHeader"
| "addIntHeader"
)
}
fn source_imports_servlet(file_bytes: &[u8]) -> bool {
@ -110,9 +118,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("setHeader")],
..Default::default()
};
assert!(HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -123,9 +133,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -143,8 +155,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderJavaAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -18,7 +18,10 @@ const ADAPTER_NAME: &str = "header-js";
fn callee_is_header_setter(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "setHeader" | "header" | "set" | "writeHead" | "append")
matches!(
last,
"setHeader" | "header" | "set" | "writeHead" | "append"
)
}
fn source_uses_node_http(file_bytes: &[u8]) -> bool {
@ -115,9 +118,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("setHeader")],
..Default::default()
};
assert!(HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -128,9 +133,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -146,8 +153,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -106,9 +106,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("header")],
..Default::default()
};
assert!(HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -119,15 +121,16 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_value_url_encoded() {
let src: &[u8] =
b"<?php\nfunction run($v) { header('Set-Cookie: ' . urlencode($v)); }\n";
let src: &[u8] = b"<?php\nfunction run($v) { header('Set-Cookie: ' . urlencode($v)); }\n";
let tree = parse_php(src);
let summary = FuncSummary {
name: "run".into(),
@ -137,8 +140,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -21,7 +21,10 @@ fn callee_is_header_setter(name: &str) -> bool {
matches!(
last,
"__setitem__" | "set_header" | "setdefault" | "add_header" | "append"
) || matches!(name, "Response.headers.__setitem__" | "make_response" | "Response.headers.add")
) || matches!(
name,
"Response.headers.__setitem__" | "make_response" | "Response.headers.add"
)
}
fn source_imports_python_web(file_bytes: &[u8]) -> bool {
@ -116,9 +119,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("__setitem__")],
..Default::default()
};
assert!(HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -129,9 +134,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -149,8 +156,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -132,9 +132,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("set_header")],
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -145,9 +147,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -168,9 +172,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -188,9 +194,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -207,8 +215,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRubyAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -132,9 +132,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("insert")],
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -145,9 +147,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -173,9 +177,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -193,9 +199,11 @@ mod tests {
}],
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -215,8 +223,10 @@ mod tests {
],
..Default::default()
};
assert!(HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
HeaderRustAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -90,8 +90,10 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(JavaDeserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JavaDeserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -45,10 +45,7 @@ fn class_path_prefix(class: Node<'_>, bytes: &[u8]) -> Option<String> {
hit
}
fn method_verb_and_path(
method: Node<'_>,
bytes: &[u8],
) -> Option<(HttpMethod, String)> {
fn method_verb_and_path(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
let mut hit: Option<(HttpMethod, String)> = None;
iter_annotations(method, bytes, |ann, name| {
if hit.is_some() {
@ -155,17 +152,21 @@ mod tests {
fn skips_non_micronaut_file() {
let src: &[u8] = b"@Controller\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaMicronautAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none());
assert!(
JavaMicronautAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_method_without_micronaut_verb() {
let src: &[u8] = b"import io.micronaut.http.annotation.Controller;\n@Controller(\"/api\")\npublic class V {\n public String helper() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaMicronautAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
JavaMicronautAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -39,17 +39,15 @@ fn class_path_prefix(class: Node<'_>, bytes: &[u8]) -> String {
let mut prefix = String::new();
iter_annotations(class, bytes, |ann, name| {
if name == "Path"
&& let Some(p) = annotation_string_arg(ann, bytes) {
prefix = p;
}
&& let Some(p) = annotation_string_arg(ann, bytes)
{
prefix = p;
}
});
prefix
}
fn method_verb_and_path(
method: Node<'_>,
bytes: &[u8],
) -> Option<(HttpMethod, String)> {
fn method_verb_and_path(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
let mut verb: Option<HttpMethod> = None;
let mut path = String::new();
iter_annotations(method, bytes, |ann, name| {
@ -57,9 +55,10 @@ fn method_verb_and_path(
verb = Some(v);
}
if name == "Path"
&& let Some(p) = annotation_string_arg(ann, bytes) {
path = p;
}
&& let Some(p) = annotation_string_arg(ann, bytes)
{
path = p;
}
});
Some((verb?, path))
}
@ -157,17 +156,21 @@ mod tests {
fn skips_non_quarkus_file() {
let src: &[u8] = b"@RestController\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaQuarkusAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none());
assert!(
JavaQuarkusAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_method_without_verb_annotation() {
let src: &[u8] = b"import jakarta.ws.rs.Path;\n@Path(\"/api\")\npublic class V {\n public String helper() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaQuarkusAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
JavaQuarkusAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -77,11 +77,7 @@ pub fn source_imports_micronaut(bytes: &[u8]) -> bool {
pub fn source_imports_servlet(bytes: &[u8]) -> bool {
let has_canonical = contains_any(
bytes,
&[
b"javax.servlet",
b"jakarta.servlet",
b"extends HttpServlet",
],
&[b"javax.servlet", b"jakarta.servlet", b"extends HttpServlet"],
);
if has_canonical {
return true;
@ -113,12 +109,7 @@ pub fn find_class_with_method<'a>(
hit
}
fn walk<'a>(
node: Node<'a>,
bytes: &[u8],
target: &str,
out: &mut Option<(Node<'a>, Node<'a>)>,
) {
fn walk<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<(Node<'a>, Node<'a>)>) {
if out.is_some() {
return;
}
@ -126,21 +117,22 @@ fn walk<'a>(
&& let Some(body) = node
.child_by_field_name("body")
.or_else(|| named_child_of_kind(node, "class_body"))
{
let mut cur = body.walk();
for member in body.children(&mut cur) {
if member.kind() != "method_declaration" {
continue;
}
if let Some(name) = member
.child_by_field_name("name")
.and_then(|n| n.utf8_text(bytes).ok())
&& name == target {
*out = Some((node, member));
return;
}
{
let mut cur = body.walk();
for member in body.children(&mut cur) {
if member.kind() != "method_declaration" {
continue;
}
if let Some(name) = member
.child_by_field_name("name")
.and_then(|n| n.utf8_text(bytes).ok())
&& name == target
{
*out = Some((node, member));
return;
}
}
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
walk(child, bytes, target, out);
@ -173,7 +165,10 @@ pub fn annotation_string_arg(ann: Node<'_>, bytes: &[u8]) -> Option<String> {
// Try `value = "…"` / `path = "…"` first so the keyword form is
// not accidentally captured by the bare-string scan.
for key in ["value", "path"] {
if let Some(start) = raw.find(&format!("{key} = ")).or_else(|| raw.find(&format!("{key}="))) {
if let Some(start) = raw
.find(&format!("{key} = "))
.or_else(|| raw.find(&format!("{key}=")))
{
let after = &raw[start..];
if let Some(open) = after.find('"') {
let rest = &after[open + 1..];
@ -300,16 +295,17 @@ pub fn extract_path_placeholders(path: &str) -> Vec<String> {
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'{'
&& let Some(end) = bytes[i + 1..].iter().position(|&b| b == b'}') {
let inner = &path[i + 1..i + 1 + end];
let inner_name = inner.split(':').next().unwrap_or(inner).trim();
let name = inner_name.strip_prefix('*').unwrap_or(inner_name);
if !name.is_empty() && !out.iter().any(|n| n == name) {
out.push(name.to_owned());
}
i += end + 2;
continue;
&& let Some(end) = bytes[i + 1..].iter().position(|&b| b == b'}')
{
let inner = &path[i + 1..i + 1 + end];
let inner_name = inner.split(':').next().unwrap_or(inner).trim();
let name = inner_name.strip_prefix('*').unwrap_or(inner_name);
if !name.is_empty() && !out.iter().any(|n| n == name) {
out.push(name.to_owned());
}
i += end + 2;
continue;
}
i += 1;
}
out
@ -469,8 +465,7 @@ mod tests {
#[test]
fn class_extends_detects_servlet() {
let src: &[u8] =
b"public class V extends HttpServlet { public void doGet() {} }\n";
let src: &[u8] = b"public class V extends HttpServlet { public void doGet() {} }\n";
let tree = parse(src);
let (class, _) = find_class_with_method(tree.root_node(), src, "doGet").unwrap();
assert!(class_extends(class, src, "HttpServlet"));

View file

@ -126,10 +126,12 @@ mod tests {
let route = binding.route.unwrap();
assert_eq!(route.method, HttpMethod::GET);
assert_eq!(route.path, "/admin");
assert!(binding
.request_params
.iter()
.all(|p| matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.all(|p| matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -157,19 +159,24 @@ mod tests {
#[test]
fn skips_when_method_name_is_not_a_servlet_verb() {
let src: &[u8] = b"public class V extends HttpServlet { public void run(HttpServletRequest req) {} }\n";
let src: &[u8] =
b"public class V extends HttpServlet { public void run(HttpServletRequest req) {} }\n";
let tree = parse(src);
assert!(JavaServletAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none());
assert!(
JavaServletAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_no_servlet_signature_markers() {
let src: &[u8] = b"public class V {\n public void doGet(String x) {}\n}\n";
let tree = parse(src);
assert!(JavaServletAdapter
.detect(&summary("doGet"), tree.root_node(), src)
.is_none());
assert!(
JavaServletAdapter
.detect(&summary("doGet"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -49,17 +49,15 @@ fn class_route_prefix(class: Node<'_>, bytes: &[u8]) -> String {
let mut prefix = String::new();
iter_annotations(class, bytes, |ann, name| {
if name == "RequestMapping"
&& let Some(p) = annotation_string_arg(ann, bytes) {
prefix = p;
}
&& let Some(p) = annotation_string_arg(ann, bytes)
{
prefix = p;
}
});
prefix
}
fn method_route(
method: Node<'_>,
bytes: &[u8],
) -> Option<(HttpMethod, String)> {
fn method_route(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
let mut hit: Option<(HttpMethod, String)> = None;
iter_annotations(method, bytes, |ann, name| {
if hit.is_some() {
@ -100,7 +98,10 @@ impl FrameworkAdapter for JavaSpringAdapter {
// Quarkus / JAX-RS files often re-use `@Path` but the brief
// routes those through `java-quarkus`; skip when the file
// looks like Quarkus and is not also a Spring controller.
if source_imports_quarkus(file_bytes) && !file_bytes.windows(15).any(|w| w == b"@RestController") && !file_bytes.windows(11).any(|w| w == b"@Controller") {
if source_imports_quarkus(file_bytes)
&& !file_bytes.windows(15).any(|w| w == b"@RestController")
&& !file_bytes.windows(11).any(|w| w == b"@Controller")
{
return None;
}
let (class, method) = find_class_with_method(ast, file_bytes, &summary.name)?;
@ -210,26 +211,32 @@ mod tests {
let src: &[u8] =
b"@RequestMapping(\"/api\")\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaSpringAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none());
assert!(
JavaSpringAdapter
.detect(&summary("x"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_quarkus_file() {
let src: &[u8] = b"import io.quarkus.runtime.Quarkus;\nimport jakarta.ws.rs.GET;\nimport jakarta.ws.rs.Path;\n@Path(\"/run\")\npublic class Q {\n @GET\n public String run() { return \"\"; }\n}\n";
let tree = parse(src);
assert!(JavaSpringAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none());
assert!(
JavaSpringAdapter
.detect(&summary("run"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_plain_function() {
let src: &[u8] = b"public class C { public int add(int a, int b) { return a + b; } }\n";
let tree = parse(src);
assert!(JavaSpringAdapter
.detect(&summary("add"), tree.root_node(), src)
.is_none());
assert!(
JavaSpringAdapter
.detect(&summary("add"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -123,9 +123,11 @@ mod tests {
let src: &[u8] = b"import org.thymeleaf.TemplateEngine;\npublic class V { public static String run(String body) { TemplateEngine e = new TemplateEngine(); return e.process(body, null); } }\n";
let tree = parse_java(src);
let summary = summary_for("run", &["body"], &[0]);
assert!(JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -137,9 +139,11 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -149,9 +153,11 @@ mod tests {
let src: &[u8] = b"// org.thymeleaf.TemplateEngine is great\npublic class V { public static String run(String body) { TemplateEngine e = new TemplateEngine(); return e.process(\"static\", null); } }\n";
let tree = parse_java(src);
let summary = summary_for("run", &["body"], &[0]);
assert!(JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -159,8 +165,10 @@ mod tests {
let src: &[u8] = b"import org.thymeleaf.TemplateEngine;\npublic class V { public static String run(String body) { TemplateEngine e = new TemplateEngine(); return e.process(body, null); } }\n";
let tree = parse_java(src);
let summary = summary_for("run", &["body"], &[]);
assert!(JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JavaThymeleafAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -107,10 +107,18 @@ mod tests {
let route = binding.route.as_ref().unwrap();
assert_eq!(route.method, HttpMethod::GET);
assert_eq!(route.path, "/users/:id");
assert!(binding.request_params.iter().any(|p| p.name == "req"
&& matches!(p.source, ParamSource::Implicit)));
assert!(binding.request_params.iter().any(|p| p.name == "res"
&& matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "req" && matches!(p.source, ParamSource::Implicit))
);
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "res" && matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -147,9 +155,11 @@ mod tests {
function handler(ctx) { ctx.body = 'ok'; }\n\
app.get('/x', handler);\n";
let tree = parse_js(src);
assert!(JsExpressAdapter
.detect(&summary("handler"), tree.root_node(), src)
.is_none());
assert!(
JsExpressAdapter
.detect(&summary("handler"), tree.root_node(), src)
.is_none()
);
}
#[test]
@ -159,8 +169,10 @@ mod tests {
function other(req, res) { res.send('x'); }\n\
app.get('/x', other);\n";
let tree = parse_js(src);
assert!(JsExpressAdapter
.detect(&summary("missing"), tree.root_node(), src)
.is_none());
assert!(
JsExpressAdapter
.detect(&summary("missing"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -148,8 +148,10 @@ mod tests {
function h(req, res) {}\n\
app.get('/x', h);\n";
let tree = parse_js(src);
assert!(JsFastifyAdapter
.detect(&summary("h"), tree.root_node(), src)
.is_none());
assert!(
JsFastifyAdapter
.detect(&summary("h"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -139,9 +139,11 @@ mod tests {
let src: &[u8] = b"const Handlebars = require('handlebars');\nfunction render(body) {\n return Handlebars.compile(body)({});\n}\n";
let tree = parse_js(src);
let summary = summary_for("render", &["body"], &[0]);
assert!(JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -152,9 +154,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -162,9 +166,11 @@ mod tests {
let src: &[u8] = b"// uses Handlebars\nfunction render(body) {\n return Handlebars.compile(\"static\")({});\n}\n";
let tree = parse_js(src);
let summary = summary_for("render", &["body"], &[0]);
assert!(JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -172,8 +178,10 @@ mod tests {
let src: &[u8] = b"const Handlebars = require('handlebars');\nfunction render(body) {\n return Handlebars.compile(body)({});\n}\n";
let tree = parse_js(src);
let summary = summary_for("render", &["body"], &[]);
assert!(JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
JsHandlebarsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -39,22 +39,13 @@ fn receiver_looks_like_koa(name: &str) -> bool {
/// that reference `target`. Returns the matched call node so callers
/// can stamp a middleware-shape binding when the verb-based dispatch
/// fails to fire.
fn find_use_middleware<'a>(
root: Node<'a>,
bytes: &[u8],
target: &str,
) -> Option<Node<'a>> {
fn find_use_middleware<'a>(root: Node<'a>, bytes: &[u8], target: &str) -> Option<Node<'a>> {
let mut hit: Option<Node<'a>> = None;
walk_for_use(root, bytes, target, &mut hit);
hit
}
fn walk_for_use<'a>(
node: Node<'a>,
bytes: &[u8],
target: &str,
out: &mut Option<Node<'a>>,
) {
fn walk_for_use<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<Node<'a>>) {
if out.is_some() {
return;
}
@ -108,8 +99,7 @@ impl FrameworkAdapter for JsKoaAdapter {
.unwrap_or_default();
bind_path_params(&formals, path)
};
if let Some((method, path)) =
find_route_registration(ast, file_bytes, &summary.name, &recv)
if let Some((method, path)) = find_route_registration(ast, file_bytes, &summary.name, &recv)
{
let request_params = formals_for(&path);
return Some(FrameworkBinding {
@ -180,8 +170,12 @@ mod tests {
let route = binding.route.as_ref().unwrap();
assert_eq!(route.method, HttpMethod::GET);
assert_eq!(route.path, "/users/:id");
assert!(binding.request_params.iter().any(|p| p.name == "ctx"
&& matches!(p.source, ParamSource::Implicit)));
assert!(
binding
.request_params
.iter()
.any(|p| p.name == "ctx" && matches!(p.source, ParamSource::Implicit))
);
}
#[test]
@ -205,8 +199,10 @@ mod tests {
function h(req, res) {}\n\
router.get('/x', h);\n";
let tree = parse_js(src);
assert!(JsKoaAdapter
.detect(&summary("h"), tree.root_node(), src)
.is_none());
assert!(
JsKoaAdapter
.detect(&summary("h"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -84,8 +84,7 @@ fn detect_nest(
if !source_imports_nest(file_bytes) {
return None;
}
let (class_node, method_node) =
find_class_method(ast, file_bytes, &summary.name)?;
let (class_node, method_node) = find_class_method(ast, file_bytes, &summary.name)?;
let prefix = class_controller_prefix(class_node, file_bytes)?;
let (method, sub_path) = method_verb_and_path(method_node, file_bytes)?;
let full_path = join_paths(&prefix, &sub_path);
@ -213,10 +212,7 @@ fn class_controller_prefix(class_node: Node<'_>, bytes: &[u8]) -> Option<String>
/// with one of the Nest verb decorators (`@Get`, `@Post`, ...). The
/// `sub_path` is `""` when the decorator carries no argument
/// (`@Get()` mounts at the controller prefix root).
fn method_verb_and_path(
method_node: Node<'_>,
bytes: &[u8],
) -> Option<(HttpMethod, String)> {
fn method_verb_and_path(method_node: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
const VERBS: &[&str] = &[
"Get", "Head", "Post", "Put", "Patch", "Delete", "Options", "All",
];
@ -461,8 +457,7 @@ mod tests {
fn parse_ts(src: &[u8]) -> tree_sitter::Tree {
let mut parser = tree_sitter::Parser::new();
let lang =
tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT);
let lang = tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT);
parser.set_language(&lang).unwrap();
parser.parse(src, None).unwrap()
}
@ -562,8 +557,10 @@ mod tests {
compute(x: number) { return x + 1; }\n\
}\n";
let tree = parse_ts(src);
assert!(TsNestAdapter
.detect(&summary("compute", "typescript"), tree.root_node(), src)
.is_none());
assert!(
TsNestAdapter
.detect(&summary("compute", "typescript"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -140,22 +140,13 @@ pub fn strip_quotes(raw: &str) -> &str {
/// arrow function whose binding name equals `target`. Returns the
/// `formal_parameters` (or `formal_parameter` for shorthand arrows)
/// node so callers can enumerate parameter names.
pub fn find_function_params<'a>(
root: Node<'a>,
bytes: &[u8],
target: &str,
) -> Option<Node<'a>> {
pub fn find_function_params<'a>(root: Node<'a>, bytes: &[u8], target: &str) -> Option<Node<'a>> {
let mut hit: Option<Node<'a>> = None;
walk_for_params(root, bytes, target, &mut hit);
hit
}
fn walk_for_params<'a>(
node: Node<'a>,
bytes: &[u8],
target: &str,
out: &mut Option<Node<'a>>,
) {
fn walk_for_params<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<Node<'a>>) {
if out.is_some() {
return;
}
@ -311,15 +302,7 @@ pub fn bind_path_params(formals: &[String], path: &str) -> Vec<ParamBinding> {
fn is_implicit_formal(name: &str) -> bool {
matches!(
name,
"req"
| "request"
| "res"
| "response"
| "reply"
| "ctx"
| "context"
| "next"
| "done"
"req" | "request" | "res" | "response" | "reply" | "ctx" | "context" | "next" | "done"
)
}
@ -349,9 +332,7 @@ pub fn extract_path_placeholders(path: &str) -> Vec<String> {
b':' => {
let start = i + 1;
let mut j = start;
while j < bytes.len()
&& (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_')
{
while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
j += 1;
}
if j > start {
@ -456,10 +437,11 @@ fn walk_for_registration<'a>(
&& receiver_accepts(last_segment(object_text))
&& let Some(args) = node.child_by_field_name("arguments")
&& call_args_reference_target(args, bytes, target)
&& let Some(path) = first_string_arg(args, bytes) {
*out = Some((method, path));
return;
}
&& let Some(path) = first_string_arg(args, bytes)
{
*out = Some((method, path));
return;
}
// Fastify options-object: `fastify.route({ method, url, handler })`.
if prop_text == "route"
&& receiver_accepts(last_segment(object_text))
@ -507,11 +489,7 @@ pub fn first_string_arg(args: Node<'_>, bytes: &[u8]) -> Option<String> {
/// Parse a Fastify options-object call `fastify.route({ method, url,
/// handler })` returning the bound `(method, url)` when the
/// `handler:` property references `target`.
fn parse_options_route(
args: Node<'_>,
bytes: &[u8],
target: &str,
) -> Option<(HttpMethod, String)> {
fn parse_options_route(args: Node<'_>, bytes: &[u8], target: &str) -> Option<(HttpMethod, String)> {
let mut cur = args.walk();
for c in args.named_children(&mut cur) {
if c.kind() != "object" {
@ -525,7 +503,9 @@ fn parse_options_route(
if pair.kind() != "pair" {
continue;
}
let Some(key) = pair.child_by_field_name("key").and_then(|n| n.utf8_text(bytes).ok())
let Some(key) = pair
.child_by_field_name("key")
.and_then(|n| n.utf8_text(bytes).ok())
else {
continue;
};

View file

@ -35,7 +35,12 @@ fn source_imports_kafka(file_bytes: &[u8]) -> bool {
fn extract_topic(file_bytes: &[u8]) -> String {
let text = std::str::from_utf8(file_bytes).unwrap_or("");
for needle in ["topics = \"", "topics=\"", "topics = {\"", "subscribe(Arrays.asList(\""] {
for needle in [
"topics = \"",
"topics=\"",
"topics = {\"",
"subscribe(Arrays.asList(\"",
] {
if let Some(idx) = text.find(needle) {
let after = &text[idx + needle.len()..];
if let Some(end) = after.find('"') {

View file

@ -129,8 +129,10 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(KafkaPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
KafkaPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -173,9 +173,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("ldap_search")],
..Default::default()
};
assert!(LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -186,9 +188,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -203,8 +207,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("ldap_search")],
..Default::default()
};
assert!(LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapPhpAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -168,9 +168,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("search_s")],
..Default::default()
};
assert!(LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -181,9 +183,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -198,8 +202,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("search_s")],
..Default::default()
};
assert!(LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapPythonAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -205,9 +205,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(LdapSpringAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapSpringAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -225,8 +227,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("search")],
..Default::default()
};
assert!(LdapSpringAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
LdapSpringAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -17,11 +17,7 @@ fn callee_is_django_middleware(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"process_request"
| "process_response"
| "process_view"
| "process_exception"
| "__call__"
"process_request" | "process_response" | "process_view" | "process_exception" | "__call__"
)
}

View file

@ -15,10 +15,7 @@ const ADAPTER_NAME: &str = "middleware-express";
fn callee_is_express(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"use" | "next" | "json" | "urlencoded" | "static"
)
matches!(last, "use" | "next" | "json" | "urlencoded" | "static")
}
fn source_imports_express(file_bytes: &[u8]) -> bool {
@ -27,11 +24,7 @@ fn source_imports_express(file_bytes: &[u8]) -> bool {
// import. Many non-middleware Express fixtures import the framework
// but never declare middleware; gating on the registration shape
// keeps the adapter focused on the function the brief targets.
const NEEDLES: &[&[u8]] = &[
b"app.use(",
b"router.use(",
b"express.Router()",
];
const NEEDLES: &[&[u8]] = &[b"app.use(", b"router.use(", b"express.Router()"];
NEEDLES
.iter()
.any(|n| file_bytes.windows(n.len()).any(|w| w == *n))

View file

@ -17,8 +17,7 @@ fn callee_is_rails_migration(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"up"
| "down"
"up" | "down"
| "change"
| "create_table"
| "add_column"

View file

@ -17,13 +17,7 @@ fn callee_is_sequelize_migration(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"up"
| "down"
| "createTable"
| "addColumn"
| "dropTable"
| "removeColumn"
| "addIndex"
"up" | "down" | "createTable" | "addColumn" | "dropTable" | "removeColumn" | "addIndex"
)
}

View file

@ -11,6 +11,16 @@
//! the route / framework adapters; the per-cap sink adapters live
//! here so the per-language verticals can ship independently.
pub mod go_chi;
pub mod go_echo;
pub mod go_fiber;
pub mod go_gin;
pub mod go_routes;
pub mod graphql_apollo;
pub mod graphql_gqlgen;
pub mod graphql_graphene;
pub mod graphql_juniper;
pub mod graphql_relay;
pub mod header_go;
pub mod header_java;
pub mod header_js;
@ -18,11 +28,6 @@ pub mod header_php;
pub mod header_python;
pub mod header_ruby;
pub mod header_rust;
pub mod go_chi;
pub mod go_echo;
pub mod go_fiber;
pub mod go_gin;
pub mod go_routes;
pub mod java_deserialize;
pub mod java_micronaut;
pub mod java_quarkus;
@ -36,11 +41,6 @@ pub mod js_handlebars;
pub mod js_koa;
pub mod js_nest;
pub mod js_routes;
pub mod graphql_apollo;
pub mod graphql_gqlgen;
pub mod graphql_graphene;
pub mod graphql_juniper;
pub mod graphql_relay;
pub mod kafka_java;
pub mod kafka_python;
pub mod ldap_php;
@ -117,6 +117,15 @@ pub mod xxe_php;
pub mod xxe_python;
pub mod xxe_ruby;
pub use go_chi::GoChiAdapter;
pub use go_echo::GoEchoAdapter;
pub use go_fiber::GoFiberAdapter;
pub use go_gin::GoGinAdapter;
pub use graphql_apollo::GraphqlApolloAdapter;
pub use graphql_gqlgen::GraphqlGqlgenAdapter;
pub use graphql_graphene::GraphqlGrapheneAdapter;
pub use graphql_juniper::GraphqlJuniperAdapter;
pub use graphql_relay::GraphqlRelayAdapter;
pub use header_go::HeaderGoAdapter;
pub use header_java::HeaderJavaAdapter;
pub use header_js::HeaderJsAdapter;
@ -124,10 +133,6 @@ pub use header_php::HeaderPhpAdapter;
pub use header_python::HeaderPythonAdapter;
pub use header_ruby::HeaderRubyAdapter;
pub use header_rust::HeaderRustAdapter;
pub use go_chi::GoChiAdapter;
pub use go_echo::GoEchoAdapter;
pub use go_fiber::GoFiberAdapter;
pub use go_gin::GoGinAdapter;
pub use java_deserialize::JavaDeserializeAdapter;
pub use java_micronaut::JavaMicronautAdapter;
pub use java_quarkus::JavaQuarkusAdapter;
@ -139,11 +144,6 @@ pub use js_fastify::JsFastifyAdapter;
pub use js_handlebars::JsHandlebarsAdapter;
pub use js_koa::JsKoaAdapter;
pub use js_nest::{JsNestAdapter, TsNestAdapter};
pub use graphql_apollo::GraphqlApolloAdapter;
pub use graphql_gqlgen::GraphqlGqlgenAdapter;
pub use graphql_graphene::GraphqlGrapheneAdapter;
pub use graphql_juniper::GraphqlJuniperAdapter;
pub use graphql_relay::GraphqlRelayAdapter;
pub use kafka_java::KafkaJavaAdapter;
pub use kafka_python::KafkaPythonAdapter;
pub use ldap_php::LdapPhpAdapter;
@ -221,10 +221,7 @@ fn any_callee_matches(
summary: &crate::summary::FuncSummary,
predicate: impl Fn(&str) -> bool,
) -> bool {
summary
.callees
.iter()
.any(|c| predicate(c.name.as_str()))
summary.callees.iter().any(|c| predicate(c.name.as_str()))
}
/// True when any callee in `summary.callees` matches `name_pred` AND
@ -270,10 +267,7 @@ fn any_callee_matches_with_receiver(
/// Per-language sigil stripping covers PHP (`$x`), Ruby (`@x`), and
/// Java/Python/JS (no sigil). Leading whitespace is also trimmed so
/// adapters can pass the raw `utf8_text` of the argument node.
pub(super) fn arg_is_tainted_param(
summary: &crate::summary::FuncSummary,
arg_text: &str,
) -> bool {
pub(super) fn arg_is_tainted_param(summary: &crate::summary::FuncSummary, arg_text: &str) -> bool {
fn strip(s: &str) -> &str {
s.trim()
.trim_start_matches('$')
@ -281,15 +275,10 @@ pub(super) fn arg_is_tainted_param(
.trim_start_matches('&')
}
let needle = strip(arg_text);
let Some(idx) = summary
.param_names
.iter()
.position(|p| strip(p) == needle)
else {
let Some(idx) = summary.param_names.iter().position(|p| strip(p) == needle) else {
return false;
};
summary.tainted_sink_params.contains(&idx)
|| summary.propagating_params.contains(&idx)
summary.tainted_sink_params.contains(&idx) || summary.propagating_params.contains(&idx)
}
/// True when any descendant identifier in `node`'s subtree resolves to

View file

@ -18,11 +18,7 @@ fn callee_is_nats(name: &str) -> bool {
}
fn source_imports_nats(file_bytes: &[u8]) -> bool {
const NEEDLES: &[&[u8]] = &[
b"github.com/nats-io/nats.go",
b"nats.Connect",
b"nats.Msg",
];
const NEEDLES: &[&[u8]] = &[b"github.com/nats-io/nats.go", b"nats.Connect", b"nats.Msg"];
NEEDLES
.iter()
.any(|n| file_bytes.windows(n.len()).any(|w| w == *n))

View file

@ -11,9 +11,9 @@
//! inner name (after the `:`) for each so a `$id` formal whose name
//! matches the placeholder binds as [`super::super::ParamSource::PathSegment`].
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, RouteShape};
#[cfg(test)]
use crate::dynamic::framework::HttpMethod;
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, RouteShape};
use crate::evidence::EntryKind;
use crate::summary::FuncSummary;
use crate::symbol::Lang;
@ -49,8 +49,7 @@ impl FrameworkAdapter for PhpCodeIgniterAdapter {
let (func_node, class) = find_php_function(ast, file_bytes, &summary.name)?;
let controller = class.and_then(|c| php_class_name(c, file_bytes));
let (method, path) =
find_codeigniter_route(ast, file_bytes, &summary.name, controller)?;
let (method, path) = find_codeigniter_route(ast, file_bytes, &summary.name, controller)?;
let formals = php_formal_names(func_node, file_bytes);
let request_params = bind_php_path_params(&formals, &path);
@ -120,17 +119,21 @@ mod tests {
fn skips_when_codeigniter_not_imported() {
let src: &[u8] = b"<?php\n$routes->get('users/(:num)', 'UserController::show');\n";
let tree = parse(src);
assert!(PhpCodeIgniterAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none());
assert!(
PhpCodeIgniterAdapter
.detect(&summary("show"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_callable_does_not_reference_method() {
let src: &[u8] = b"<?php\nuse CodeIgniter\\Router\\RouteCollection;\n$routes->get('users/(:num)', 'UserController::show');\nclass UserController extends BaseController {\n public function helper($x) { return $x; }\n}\n";
let tree = parse(src);
assert!(PhpCodeIgniterAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
PhpCodeIgniterAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -12,9 +12,9 @@
//! a `class UserController { public function show($id) {…} }`
//! declaration in the same file.
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, RouteShape};
#[cfg(test)]
use crate::dynamic::framework::HttpMethod;
use crate::dynamic::framework::{FrameworkAdapter, FrameworkBinding, RouteShape};
use crate::evidence::EntryKind;
use crate::summary::FuncSummary;
use crate::symbol::Lang;
@ -50,8 +50,7 @@ impl FrameworkAdapter for PhpLaravelAdapter {
let (func_node, class) = find_php_function(ast, file_bytes, &summary.name)?;
let controller = class.and_then(|c| php_class_name(c, file_bytes));
let (method, path) =
find_laravel_static_route(ast, file_bytes, &summary.name, controller)?;
let (method, path) = find_laravel_static_route(ast, file_bytes, &summary.name, controller)?;
let formals = php_formal_names(func_node, file_bytes);
let request_params = bind_php_path_params(&formals, &path);
@ -143,17 +142,21 @@ mod tests {
fn skips_when_laravel_not_imported() {
let src: &[u8] = b"<?php\nfunction f($x) { return $x; }\n";
let tree = parse(src);
assert!(PhpLaravelAdapter
.detect(&summary("f"), tree.root_node(), src)
.is_none());
assert!(
PhpLaravelAdapter
.detect(&summary("f"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_route_mapping_does_not_reference_function() {
let src: &[u8] = b"<?php\nuse Illuminate\\Support\\Facades\\Route;\nRoute::get('/users', 'UserController@show');\nfunction helper($x) { return $x; }\n";
let tree = parse(src);
assert!(PhpLaravelAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
PhpLaravelAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -122,15 +122,16 @@ fn walk<'a>(
&& let Some(name) = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(bytes).ok())
&& name == target {
let klass = if node.kind() == "method_declaration" {
here_class
} else {
None
};
*out = Some((node, klass));
return;
}
&& name == target
{
let klass = if node.kind() == "method_declaration" {
here_class
} else {
None
};
*out = Some((node, klass));
return;
}
let mut cur = node.walk();
for child in node.children(&mut cur) {
walk(child, bytes, target, here_class, out);
@ -511,10 +512,7 @@ fn laravel_callable_matches(
}
}
fn parse_array_callable<'a>(
array: Node<'a>,
bytes: &'a [u8],
) -> Option<(Option<String>, String)> {
fn parse_array_callable<'a>(array: Node<'a>, bytes: &'a [u8]) -> Option<(Option<String>, String)> {
let mut cur = array.walk();
let elements: Vec<Node<'a>> = array
.named_children(&mut cur)
@ -544,10 +542,7 @@ fn split_laravel_callable(literal: &str) -> (Option<String>, String) {
fn leaf(qualified: &str) -> &str {
let last_backslash = qualified.rsplit('\\').next().unwrap_or(qualified);
last_backslash
.rsplit("::")
.next()
.unwrap_or(last_backslash)
last_backslash.rsplit("::").next().unwrap_or(last_backslash)
}
fn verb_method(verb: &str) -> Option<HttpMethod> {
@ -711,18 +706,12 @@ mod tests {
extract_php_path_placeholders("/u/{id}/p/{slug?}"),
vec!["id", "slug"]
);
assert_eq!(
extract_php_path_placeholders("/u/{id:[0-9]+}"),
vec!["id"]
);
assert_eq!(extract_php_path_placeholders("/u/{id:[0-9]+}"), vec!["id"]);
}
#[test]
fn extracts_codeigniter_placeholders() {
assert_eq!(
extract_php_path_placeholders("users/(:num)"),
vec!["num"]
);
assert_eq!(extract_php_path_placeholders("users/(:num)"), vec!["num"]);
assert_eq!(
extract_php_path_placeholders("p/(:any)/c/(:segment)"),
vec!["any", "segment"]
@ -778,20 +767,16 @@ mod tests {
fn finds_laravel_static_route_with_string_callable() {
let src: &[u8] = b"<?php\nRoute::get('/users/{id}', 'UserController@show');\nclass UserController {\n public function show($id) { return $id; }\n}\n";
let tree = parse(src);
let hit = find_laravel_static_route(
tree.root_node(),
src,
"show",
Some("UserController"),
)
.unwrap();
let hit = find_laravel_static_route(tree.root_node(), src, "show", Some("UserController"))
.unwrap();
assert_eq!(hit.0, HttpMethod::GET);
assert_eq!(hit.1, "/users/{id}");
}
#[test]
fn finds_laravel_static_route_with_closure() {
let src: &[u8] = b"<?php\nRoute::post('/users', function ($payload) { return $payload; });\n";
let src: &[u8] =
b"<?php\nRoute::post('/users', function ($payload) { return $payload; });\n";
let tree = parse(src);
let hit = find_laravel_static_route(tree.root_node(), src, "anything", None).unwrap();
assert_eq!(hit.0, HttpMethod::POST);
@ -802,13 +787,8 @@ mod tests {
fn finds_codeigniter_member_route() {
let src: &[u8] = b"<?php\n$routes->get('users/(:num)', 'UserController::show');\n";
let tree = parse(src);
let hit = find_codeigniter_route(
tree.root_node(),
src,
"show",
Some("UserController"),
)
.unwrap();
let hit =
find_codeigniter_route(tree.root_node(), src, "show", Some("UserController")).unwrap();
assert_eq!(hit.0, HttpMethod::GET);
assert_eq!(hit.1, "users/(:num)");
}

View file

@ -165,17 +165,21 @@ mod tests {
fn skips_when_symfony_not_imported() {
let src: &[u8] = b"<?php\n#[Route('/x')]\nfunction f() { return 1; }\n";
let tree = parse(src);
assert!(PhpSymfonyAdapter
.detect(&summary("f"), tree.root_node(), src)
.is_none());
assert!(
PhpSymfonyAdapter
.detect(&summary("f"), tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_when_method_has_no_route_attribute() {
let src: &[u8] = b"<?php\nuse Symfony\\Component\\Routing\\Annotation\\Route;\nclass C {\n public function helper($x) { return $x; }\n}\n";
let tree = parse(src);
assert!(PhpSymfonyAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none());
assert!(
PhpSymfonyAdapter
.detect(&summary("helper"), tree.root_node(), src)
.is_none()
);
}
}

View file

@ -145,9 +145,11 @@ mod tests {
let src: &[u8] = b"<?php\nuse Twig\\Environment;\nfunction render($body, $twig) {\n $tpl = $twig->createTemplate($body);\n return $tpl->render([]);\n}\n";
let tree = parse_php(src);
let summary = summary_for("render", &["body", "twig"], &[0]);
assert!(PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -158,9 +160,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -170,9 +174,11 @@ mod tests {
let src: &[u8] = b"<?php\n// Twig\\Environment is great\nfunction render($body, $twig) {\n $tpl = $twig->createTemplate('static');\n return $tpl->render([]);\n}\n";
let tree = parse_php(src);
let summary = summary_for("render", &["body", "twig"], &[0]);
assert!(PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -180,8 +186,10 @@ mod tests {
let src: &[u8] = b"<?php\nuse Twig\\Environment;\nfunction render($body, $twig) {\n $tpl = $twig->createTemplate($body);\n return $tpl->render([]);\n}\n";
let tree = parse_php(src);
let summary = summary_for("render", &["body", "twig"], &[]);
assert!(PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PhpTwigAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -68,9 +68,11 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(PhpUnserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PhpUnserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -81,8 +83,10 @@ mod tests {
name: "run".into(),
..Default::default()
};
assert!(PhpUnserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PhpUnserializeAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -141,9 +141,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("JSON.parse")],
..Default::default()
};
assert!(PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -155,9 +157,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("JSON.parse")],
..Default::default()
};
assert!(PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -176,8 +180,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("JSON.parse")],
..Default::default()
};
assert!(PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpJsonDeepAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -13,7 +13,10 @@ use crate::symbol::Lang;
fn callee_is_lodash_merge(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(last, "merge" | "mergeWith" | "defaultsDeep" | "set" | "setWith")
matches!(
last,
"merge" | "mergeWith" | "defaultsDeep" | "set" | "setWith"
)
}
/// True when `receiver` looks like a lodash module handle (`_`, `lodash`,
@ -152,9 +155,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("merge")],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -165,9 +170,11 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -193,9 +200,11 @@ mod tests {
}],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -213,9 +222,11 @@ mod tests {
}],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -233,9 +244,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("merge")],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -249,8 +262,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("merge")],
..Default::default()
};
assert!(PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpLodashMergeJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -117,9 +117,11 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("Object.assign")],
..Default::default()
};
assert!(PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some());
assert!(
PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_some()
);
}
#[test]
@ -130,24 +132,27 @@ mod tests {
name: "add".into(),
..Default::default()
};
assert!(PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
fn skips_object_create_null_mitigation() {
let src: &[u8] =
b"function run(payload) { return Object.create(null); }\n";
let src: &[u8] = b"function run(payload) { return Object.create(null); }\n";
let tree = parse_js(src);
let summary = FuncSummary {
name: "run".into(),
callees: vec![crate::summary::CalleeSite::bare("Object.create")],
..Default::default()
};
assert!(PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
#[test]
@ -164,8 +169,10 @@ mod tests {
callees: vec![crate::summary::CalleeSite::bare("Object.assign")],
..Default::default()
};
assert!(PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none());
assert!(
PpObjectAssignJsAdapter
.detect(&summary, tree.root_node(), src)
.is_none()
);
}
}

View file

@ -11,10 +11,7 @@ const ADAPTER_NAME: &str = "pubsub-python";
fn callee_is_pubsub(name: &str) -> bool {
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
matches!(
last,
"subscribe" | "pull" | "callback" | "process_message"
)
matches!(last, "subscribe" | "pull" | "callback" | "process_message")
}
fn source_imports_pubsub(file_bytes: &[u8]) -> bool {

Some files were not shown because too many files have changed in this diff Show more