mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-30 20:39:39 +02:00
cargo fmt
This commit is contained in:
parent
bec7bbf96c
commit
3a35cd6c8f
294 changed files with 6809 additions and 3911 deletions
|
|
@ -129,7 +129,10 @@ fn bench_sandbox_run_payload(c: &mut Criterion) {
|
||||||
let spec = make_sqli_spec();
|
let spec = make_sqli_spec();
|
||||||
let harness = harness::build(&spec).expect("harness build");
|
let harness = harness::build(&spec).expect("harness build");
|
||||||
let payloads = payloads_for(Cap::SQL_QUERY);
|
let payloads = payloads_for(Cap::SQL_QUERY);
|
||||||
let payload = payloads.iter().find(|p| !p.is_benign).expect("sqli payload");
|
let payload = payloads
|
||||||
|
.iter()
|
||||||
|
.find(|p| !p.is_benign)
|
||||||
|
.expect("sqli payload");
|
||||||
let opts = SandboxOptions {
|
let opts = SandboxOptions {
|
||||||
timeout: std::time::Duration::from_secs(10),
|
timeout: std::time::Duration::from_secs(10),
|
||||||
..SandboxOptions::default()
|
..SandboxOptions::default()
|
||||||
|
|
@ -192,10 +195,19 @@ fn bench_docker_exec_warm(c: &mut Criterion) {
|
||||||
let container = "nyx-bench-exec-warm";
|
let container = "nyx-bench-exec-warm";
|
||||||
let _ = std::process::Command::new("docker")
|
let _ = std::process::Command::new("docker")
|
||||||
.args([
|
.args([
|
||||||
"run", "-d", "--rm", "--name", container,
|
"run",
|
||||||
"--cap-drop=ALL", "--security-opt", "no-new-privileges:true",
|
"-d",
|
||||||
"--network", "none",
|
"--rm",
|
||||||
"python:3-slim", "sleep", "300",
|
"--name",
|
||||||
|
container,
|
||||||
|
"--cap-drop=ALL",
|
||||||
|
"--security-opt",
|
||||||
|
"no-new-privileges:true",
|
||||||
|
"--network",
|
||||||
|
"none",
|
||||||
|
"python:3-slim",
|
||||||
|
"sleep",
|
||||||
|
"300",
|
||||||
])
|
])
|
||||||
.stdout(std::process::Stdio::null())
|
.stdout(std::process::Stdio::null())
|
||||||
.stderr(std::process::Stdio::null())
|
.stderr(std::process::Stdio::null())
|
||||||
|
|
@ -239,7 +251,10 @@ fn bench_docker_payload_cost(c: &mut Criterion) {
|
||||||
let spec = make_sqli_spec();
|
let spec = make_sqli_spec();
|
||||||
let built = harness::build(&spec).expect("harness build");
|
let built = harness::build(&spec).expect("harness build");
|
||||||
let payloads = payloads_for(Cap::SQL_QUERY);
|
let payloads = payloads_for(Cap::SQL_QUERY);
|
||||||
let payload = payloads.iter().find(|p| !p.is_benign).expect("sqli payload");
|
let payload = payloads
|
||||||
|
.iter()
|
||||||
|
.find(|p| !p.is_benign)
|
||||||
|
.expect("sqli payload");
|
||||||
let opts = SandboxOptions {
|
let opts = SandboxOptions {
|
||||||
timeout: std::time::Duration::from_secs(30),
|
timeout: std::time::Duration::from_secs(30),
|
||||||
backend: SandboxBackend::Docker,
|
backend: SandboxBackend::Docker,
|
||||||
|
|
|
||||||
24
build.rs
24
build.rs
|
|
@ -154,10 +154,12 @@ fn emit_seccomp_policy() {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(n, _)| *n == cap_name.as_str())
|
.find(|(n, _)| *n == cap_name.as_str())
|
||||||
.map(|(_, b)| *b)
|
.map(|(_, b)| *b)
|
||||||
.unwrap_or_else(|| panic!(
|
.unwrap_or_else(|| {
|
||||||
"seccomp_policy.toml references unknown Cap '{cap_name}' — \
|
panic!(
|
||||||
|
"seccomp_policy.toml references unknown Cap '{cap_name}' — \
|
||||||
add it to CAP_BIT_FOR_NAME in build.rs first"
|
add it to CAP_BIT_FOR_NAME in build.rs first"
|
||||||
));
|
)
|
||||||
|
});
|
||||||
out.push_str(&format!(" (0x{bit:08x}_u32, &[\n"));
|
out.push_str(&format!(" (0x{bit:08x}_u32, &[\n"));
|
||||||
for name in allow {
|
for name in allow {
|
||||||
out.push_str(&format!(" \"{}\",\n", escape(name)));
|
out.push_str(&format!(" \"{}\",\n", escape(name)));
|
||||||
|
|
@ -335,7 +337,9 @@ fn emit_image_digests() {
|
||||||
out.push_str("// generated by build.rs from tools/image-builder/images.toml — do not edit\n\n");
|
out.push_str("// generated by build.rs from tools/image-builder/images.toml — do not edit\n\n");
|
||||||
|
|
||||||
// IMAGE_DIGESTS: only entries with a non-empty digest survive.
|
// IMAGE_DIGESTS: only entries with a non-empty digest survive.
|
||||||
out.push_str("pub static IMAGE_DIGESTS: phf::Map<&'static str, &'static str> = phf::phf_map! {\n");
|
out.push_str(
|
||||||
|
"pub static IMAGE_DIGESTS: phf::Map<&'static str, &'static str> = phf::phf_map! {\n",
|
||||||
|
);
|
||||||
for e in &entries {
|
for e in &entries {
|
||||||
if e.digest.is_empty() {
|
if e.digest.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -351,7 +355,9 @@ fn emit_image_digests() {
|
||||||
|
|
||||||
// IMAGE_BASES: every entry, digest stripped. Used by docker.rs when no
|
// IMAGE_BASES: every entry, digest stripped. Used by docker.rs when no
|
||||||
// digest is pinned yet so a `docker pull <base>` is still possible.
|
// digest is pinned yet so a `docker pull <base>` is still possible.
|
||||||
out.push_str("pub static IMAGE_BASES: phf::Map<&'static str, &'static str> = phf::phf_map! {\n");
|
out.push_str(
|
||||||
|
"pub static IMAGE_BASES: phf::Map<&'static str, &'static str> = phf::phf_map! {\n",
|
||||||
|
);
|
||||||
for e in &entries {
|
for e in &entries {
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
" \"{}\" => \"{}\",\n",
|
" \"{}\" => \"{}\",\n",
|
||||||
|
|
@ -404,8 +410,12 @@ fn parse_image_catalogue(src: &str) -> Vec<ImageEntry> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(slot) = current.as_mut() else { continue };
|
let Some(slot) = current.as_mut() else {
|
||||||
let Some((key, value)) = line.split_once('=') else { continue };
|
continue;
|
||||||
|
};
|
||||||
|
let Some((key, value)) = line.split_once('=') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let key = key.trim();
|
let key = key.trim();
|
||||||
let value = value.trim().trim_matches('"').trim_matches('\'');
|
let value = value.trim().trim_matches('"').trim_matches('\'');
|
||||||
match key {
|
match key {
|
||||||
|
|
|
||||||
|
|
@ -236,9 +236,18 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flask_login_required_resolves_case_insensitively() {
|
fn flask_login_required_resolves_case_insensitively() {
|
||||||
assert!(is_router_auth_marker(AuthFramework::Flask, "login_required"));
|
assert!(is_router_auth_marker(
|
||||||
assert!(is_router_auth_marker(AuthFramework::Flask, "Login_Required"));
|
AuthFramework::Flask,
|
||||||
assert!(!is_router_auth_marker(AuthFramework::Flask, "something_else"));
|
"login_required"
|
||||||
|
));
|
||||||
|
assert!(is_router_auth_marker(
|
||||||
|
AuthFramework::Flask,
|
||||||
|
"Login_Required"
|
||||||
|
));
|
||||||
|
assert!(!is_router_auth_marker(
|
||||||
|
AuthFramework::Flask,
|
||||||
|
"something_else"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -147,23 +147,20 @@ pub fn diags_to_baseline_entries(diags: &[Diag]) -> Vec<BaselineEntry> {
|
||||||
/// `path`, and `rule_id` — no source code snippets or flow steps.
|
/// `path`, and `rule_id` — no source code snippets or flow steps.
|
||||||
pub fn write_baseline(path: &Path, diags: &[Diag]) -> crate::errors::NyxResult<()> {
|
pub fn write_baseline(path: &Path, diags: &[Diag]) -> crate::errors::NyxResult<()> {
|
||||||
let entries = diags_to_baseline_entries(diags);
|
let entries = diags_to_baseline_entries(diags);
|
||||||
let json = serde_json::to_string_pretty(&entries).map_err(|e| {
|
let json = serde_json::to_string_pretty(&entries)
|
||||||
crate::errors::NyxError::Msg(format!("baseline serialize error: {e}"))
|
.map_err(|e| crate::errors::NyxError::Msg(format!("baseline serialize error: {e}")))?;
|
||||||
})?;
|
|
||||||
if let Some(parent) = path.parent()
|
if let Some(parent) = path.parent()
|
||||||
&& !parent.as_os_str().is_empty() {
|
&& !parent.as_os_str().is_empty()
|
||||||
std::fs::create_dir_all(parent).map_err(|e| {
|
{
|
||||||
crate::errors::NyxError::Msg(format!(
|
std::fs::create_dir_all(parent).map_err(|e| {
|
||||||
"cannot create baseline dir {}: {e}",
|
crate::errors::NyxError::Msg(format!(
|
||||||
parent.display()
|
"cannot create baseline dir {}: {e}",
|
||||||
))
|
parent.display()
|
||||||
})?;
|
))
|
||||||
}
|
})?;
|
||||||
|
}
|
||||||
std::fs::write(path, json).map_err(|e| {
|
std::fs::write(path, json).map_err(|e| {
|
||||||
crate::errors::NyxError::Msg(format!(
|
crate::errors::NyxError::Msg(format!("cannot write baseline {}: {e}", path.display()))
|
||||||
"cannot write baseline {}: {e}",
|
|
||||||
path.display()
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,9 +180,7 @@ fn classify_transition(
|
||||||
Transition::FlippedNotConfirmed
|
Transition::FlippedNotConfirmed
|
||||||
}
|
}
|
||||||
// NotConfirmed → Confirmed: regression
|
// NotConfirmed → Confirmed: regression
|
||||||
(Some(VerifyStatus::NotConfirmed), Some(VerifyStatus::Confirmed)) => {
|
(Some(VerifyStatus::NotConfirmed), Some(VerifyStatus::Confirmed)) => Transition::Regressed,
|
||||||
Transition::Regressed
|
|
||||||
}
|
|
||||||
// None / Inconclusive / Unsupported → Confirmed
|
// None / Inconclusive / Unsupported → Confirmed
|
||||||
(_, Some(VerifyStatus::Confirmed)) => Transition::FlippedConfirmed,
|
(_, Some(VerifyStatus::Confirmed)) => Transition::FlippedConfirmed,
|
||||||
// Everything else: treat as unchanged (e.g. Confirmed → Inconclusive
|
// Everything else: treat as unchanged (e.g. Confirmed → Inconclusive
|
||||||
|
|
@ -380,9 +375,7 @@ pub fn format_diff_console(diff: &VerdictDiff) -> String {
|
||||||
}
|
}
|
||||||
Transition::FlippedConfirmed => {
|
Transition::FlippedConfirmed => {
|
||||||
non_unchanged += 1;
|
non_unchanged += 1;
|
||||||
lines.push(format!(
|
lines.push(format!(" + {hash_str}: new Confirmed at {loc}"));
|
||||||
" + {hash_str}: new Confirmed at {loc}"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
Transition::Unchanged => {}
|
Transition::Unchanged => {}
|
||||||
}
|
}
|
||||||
|
|
@ -402,7 +395,7 @@ pub fn format_diff_console(diff: &VerdictDiff) -> String {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::commands::scan::{compute_stable_hash, Diag};
|
use crate::commands::scan::{Diag, compute_stable_hash};
|
||||||
use crate::evidence::{Evidence, VerifyResult, VerifyStatus};
|
use crate::evidence::{Evidence, VerifyResult, VerifyStatus};
|
||||||
use crate::patterns::{FindingCategory, Severity};
|
use crate::patterns::{FindingCategory, Severity};
|
||||||
|
|
||||||
|
|
@ -471,7 +464,10 @@ mod tests {
|
||||||
)];
|
)];
|
||||||
let diff = compute_verdict_diff(&[], ¤t);
|
let diff = compute_verdict_diff(&[], ¤t);
|
||||||
assert_eq!(diff.entries[0].transition, Transition::New);
|
assert_eq!(diff.entries[0].transition, Transition::New);
|
||||||
assert_eq!(diff.entries[0].current_status, Some(VerifyStatus::Confirmed));
|
assert_eq!(
|
||||||
|
diff.entries[0].current_status,
|
||||||
|
Some(VerifyStatus::Confirmed)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -620,7 +616,10 @@ mod tests {
|
||||||
let tmp = tempfile::NamedTempFile::new().unwrap();
|
let tmp = tempfile::NamedTempFile::new().unwrap();
|
||||||
write_baseline(tmp.path(), &[d]).unwrap();
|
write_baseline(tmp.path(), &[d]).unwrap();
|
||||||
let content = std::fs::read_to_string(tmp.path()).unwrap();
|
let content = std::fs::read_to_string(tmp.path()).unwrap();
|
||||||
assert!(!content.contains("SECRET CODE"), "baseline must not contain source code");
|
assert!(
|
||||||
|
!content.contains("SECRET CODE"),
|
||||||
|
"baseline must not contain source code"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -259,7 +259,6 @@ impl ClassMethodIndex {
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Type hierarchy index ────────────────────────────────────────────────
|
// ── Type hierarchy index ────────────────────────────────────────────────
|
||||||
|
|
@ -955,10 +954,9 @@ impl FileReachMap {
|
||||||
|
|
||||||
fn normalize<'a>(&self, path: &'a str) -> std::borrow::Cow<'a, str> {
|
fn normalize<'a>(&self, path: &'a str) -> std::borrow::Cow<'a, str> {
|
||||||
match self.scan_root.as_deref() {
|
match self.scan_root.as_deref() {
|
||||||
Some(root) => std::borrow::Cow::Owned(crate::symbol::normalize_namespace(
|
Some(root) => {
|
||||||
path,
|
std::borrow::Cow::Owned(crate::symbol::normalize_namespace(path, Some(root)))
|
||||||
Some(root),
|
}
|
||||||
)),
|
|
||||||
None => std::borrow::Cow::Borrowed(path),
|
None => std::borrow::Cow::Borrowed(path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2926,7 +2924,10 @@ mod tests {
|
||||||
let transitive = callers_transitive(&cg, &sink_key);
|
let transitive = callers_transitive(&cg, &sink_key);
|
||||||
let caller_names: std::collections::HashSet<String> =
|
let caller_names: std::collections::HashSet<String> =
|
||||||
transitive.iter().map(|k| k.name.clone()).collect();
|
transitive.iter().map(|k| k.name.clone()).collect();
|
||||||
assert!(caller_names.contains("process"), "process should reach sink");
|
assert!(
|
||||||
|
caller_names.contains("process"),
|
||||||
|
"process should reach sink"
|
||||||
|
);
|
||||||
assert!(caller_names.contains("handle"), "handle should reach sink");
|
assert!(caller_names.contains("handle"), "handle should reach sink");
|
||||||
assert_eq!(transitive.len(), 2, "sink itself must be excluded");
|
assert_eq!(transitive.len(), 2, "sink itself must be excluded");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,30 +182,28 @@ pub fn pick_chain_cap(bits: u32) -> Option<Cap> {
|
||||||
while remaining != 0 {
|
while remaining != 0 {
|
||||||
let bit = 1u32 << remaining.trailing_zeros();
|
let bit = 1u32 << remaining.trailing_zeros();
|
||||||
if let Some(cap) = Cap::from_bits(bit)
|
if let Some(cap) = Cap::from_bits(bit)
|
||||||
&& lookup_impact(cap, None).is_some() {
|
&& lookup_impact(cap, None).is_some()
|
||||||
return Some(cap);
|
{
|
||||||
}
|
return Some(cap);
|
||||||
|
}
|
||||||
remaining &= !bit;
|
remaining &= !bit;
|
||||||
}
|
}
|
||||||
lowest_cap(bits)
|
lowest_cap(bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn locate_reach(
|
fn locate_reach(loc: &SourceLocation, surface: &SurfaceMap, reach: Option<&FileReachMap>) -> Reach {
|
||||||
loc: &SourceLocation,
|
|
||||||
surface: &SurfaceMap,
|
|
||||||
reach: Option<&FileReachMap>,
|
|
||||||
) -> Reach {
|
|
||||||
// Pass 1: file-local match (legacy behaviour, always applies).
|
// Pass 1: file-local match (legacy behaviour, always applies).
|
||||||
for node in &surface.nodes {
|
for node in &surface.nodes {
|
||||||
if let SurfaceNode::EntryPoint(ep) = node
|
if let SurfaceNode::EntryPoint(ep) = node
|
||||||
&& ep.handler_location.file == loc.file {
|
&& ep.handler_location.file == loc.file
|
||||||
return Reach::Reachable {
|
{
|
||||||
location: ep.location.clone(),
|
return Reach::Reachable {
|
||||||
method: ep.method,
|
location: ep.location.clone(),
|
||||||
route: ep.route.clone(),
|
method: ep.method,
|
||||||
auth_required: ep.auth_required,
|
route: ep.route.clone(),
|
||||||
};
|
auth_required: ep.auth_required,
|
||||||
}
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Pass 2: transitive caller match via the call graph. Only fires
|
// Pass 2: transitive caller match via the call graph. Only fires
|
||||||
// when `reach` is supplied — keeps the legacy file-local behaviour
|
// when `reach` is supplied — keeps the legacy file-local behaviour
|
||||||
|
|
@ -213,14 +211,15 @@ fn locate_reach(
|
||||||
if let Some(reach) = reach {
|
if let Some(reach) = reach {
|
||||||
for node in &surface.nodes {
|
for node in &surface.nodes {
|
||||||
if let SurfaceNode::EntryPoint(ep) = node
|
if let SurfaceNode::EntryPoint(ep) = node
|
||||||
&& reach.reaches(&ep.handler_location.file, &loc.file) {
|
&& reach.reaches(&ep.handler_location.file, &loc.file)
|
||||||
return Reach::Reachable {
|
{
|
||||||
location: ep.location.clone(),
|
return Reach::Reachable {
|
||||||
method: ep.method,
|
location: ep.location.clone(),
|
||||||
route: ep.route.clone(),
|
method: ep.method,
|
||||||
auth_required: ep.auth_required,
|
route: ep.route.clone(),
|
||||||
};
|
auth_required: ep.auth_required,
|
||||||
}
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reach::Unreachable
|
Reach::Unreachable
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,10 @@ impl Feasibility {
|
||||||
/// in the doc's table can fire. Phase 25's scoring pass uses this
|
/// in the doc's table can fire. Phase 25's scoring pass uses this
|
||||||
/// flavour.
|
/// flavour.
|
||||||
pub fn for_finding(diag: &Diag) -> Feasibility {
|
pub fn for_finding(diag: &Diag) -> Feasibility {
|
||||||
let verdict = diag.evidence.as_ref().and_then(|e| e.dynamic_verdict.as_ref());
|
let verdict = diag
|
||||||
|
.evidence
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|e| e.dynamic_verdict.as_ref());
|
||||||
Self::bucket_from_verdict(verdict, diag.confidence)
|
Self::bucket_from_verdict(verdict, diag.confidence)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,9 +85,7 @@ impl Feasibility {
|
||||||
) -> Feasibility {
|
) -> Feasibility {
|
||||||
match verdict.map(|v| v.status) {
|
match verdict.map(|v| v.status) {
|
||||||
Some(VerifyStatus::Confirmed) => Feasibility::Confirmed,
|
Some(VerifyStatus::Confirmed) => Feasibility::Confirmed,
|
||||||
Some(VerifyStatus::Inconclusive)
|
Some(VerifyStatus::Inconclusive) if static_confidence == Some(Confidence::High) => {
|
||||||
if static_confidence == Some(Confidence::High) =>
|
|
||||||
{
|
|
||||||
Feasibility::InconclusiveHighConf
|
Feasibility::InconclusiveHighConf
|
||||||
}
|
}
|
||||||
_ => Feasibility::Unverified,
|
_ => Feasibility::Unverified,
|
||||||
|
|
|
||||||
|
|
@ -210,23 +210,14 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stable_hash_changes_with_member_order() {
|
fn stable_hash_changes_with_member_order() {
|
||||||
let a = ChainFinding::compute_stable_hash(
|
let a = ChainFinding::compute_stable_hash(&[member(1), member(2)], ImpactCategory::Rce);
|
||||||
&[member(1), member(2)],
|
let b = ChainFinding::compute_stable_hash(&[member(2), member(1)], ImpactCategory::Rce);
|
||||||
ImpactCategory::Rce,
|
|
||||||
);
|
|
||||||
let b = ChainFinding::compute_stable_hash(
|
|
||||||
&[member(2), member(1)],
|
|
||||||
ImpactCategory::Rce,
|
|
||||||
);
|
|
||||||
assert_ne!(a, b);
|
assert_ne!(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stable_hash_changes_with_impact() {
|
fn stable_hash_changes_with_impact() {
|
||||||
let a = ChainFinding::compute_stable_hash(
|
let a = ChainFinding::compute_stable_hash(&[member(1), member(2)], ImpactCategory::Rce);
|
||||||
&[member(1), member(2)],
|
|
||||||
ImpactCategory::Rce,
|
|
||||||
);
|
|
||||||
let b = ChainFinding::compute_stable_hash(
|
let b = ChainFinding::compute_stable_hash(
|
||||||
&[member(1), member(2)],
|
&[member(1), member(2)],
|
||||||
ImpactCategory::BrowserToLocalRce,
|
ImpactCategory::BrowserToLocalRce,
|
||||||
|
|
|
||||||
|
|
@ -250,9 +250,10 @@ pub fn lookup_impact(source: Cap, adjacent: Option<Cap>) -> Option<ImpactCategor
|
||||||
// try the standalone rule on adjacent_cap so a CODE_EXEC + UNRELATED
|
// try the standalone rule on adjacent_cap so a CODE_EXEC + UNRELATED
|
||||||
// pair still reaches `Rce`.
|
// pair still reaches `Rce`.
|
||||||
if let Some(adj) = adjacent
|
if let Some(adj) = adjacent
|
||||||
&& let Some(cat) = standalone_lookup(adj) {
|
&& let Some(cat) = standalone_lookup(adj)
|
||||||
return Some(cat);
|
{
|
||||||
}
|
return Some(cat);
|
||||||
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -264,8 +264,8 @@ impl CompositeReverifier for DefaultCompositeReverifier {
|
||||||
if result.cache_hit {
|
if result.cache_hit {
|
||||||
cache_hits += 1;
|
cache_hits += 1;
|
||||||
}
|
}
|
||||||
total_build_ms = total_build_ms
|
total_build_ms =
|
||||||
.saturating_add(result.duration.as_millis());
|
total_build_ms.saturating_add(result.duration.as_millis());
|
||||||
built_steps.push((built_harness.workdir, spec));
|
built_steps.push((built_harness.workdir, spec));
|
||||||
}
|
}
|
||||||
Err(_) => build_errors += 1,
|
Err(_) => build_errors += 1,
|
||||||
|
|
@ -400,7 +400,11 @@ fn run_chain_steps(
|
||||||
let mut prev_output: Option<Vec<u8>> = None;
|
let mut prev_output: Option<Vec<u8>> = None;
|
||||||
let last_idx = built_steps.len().saturating_sub(1);
|
let last_idx = built_steps.len().saturating_sub(1);
|
||||||
for (idx, (workdir, spec)) in built_steps.iter().enumerate() {
|
for (idx, (workdir, spec)) in built_steps.iter().enumerate() {
|
||||||
let step_terminal = if idx == last_idx { Some(terminal) } else { None };
|
let step_terminal = if idx == last_idx {
|
||||||
|
Some(terminal)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let step = lang::compose_chain_step(spec.lang, prev_output.as_deref(), step_terminal);
|
let step = lang::compose_chain_step(spec.lang, prev_output.as_deref(), step_terminal);
|
||||||
|
|
||||||
let step_path = workdir.join(&step.filename);
|
let step_path = workdir.join(&step.filename);
|
||||||
|
|
@ -459,7 +463,13 @@ fn run_chain_steps(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(steps_run, sandbox_errors, steps_timeout, nonzero_exits, final_sink_hit)
|
(
|
||||||
|
steps_run,
|
||||||
|
sandbox_errors,
|
||||||
|
steps_timeout,
|
||||||
|
nonzero_exits,
|
||||||
|
final_sink_hit,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 26 — Track G.3: drive composite dynamic re-verification for
|
/// Phase 26 — Track G.3: drive composite dynamic re-verification for
|
||||||
|
|
@ -472,7 +482,13 @@ pub fn reverify_chain(
|
||||||
surface: &SurfaceMap,
|
surface: &SurfaceMap,
|
||||||
opts: &VerifyOptions,
|
opts: &VerifyOptions,
|
||||||
) -> ChainReverifyResult {
|
) -> ChainReverifyResult {
|
||||||
reverify_chain_with(chain, member_diags, surface, opts, &DefaultCompositeReverifier)
|
reverify_chain_with(
|
||||||
|
chain,
|
||||||
|
member_diags,
|
||||||
|
surface,
|
||||||
|
opts,
|
||||||
|
&DefaultCompositeReverifier,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inject-the-reverifier flavour of [`reverify_chain`].
|
/// Inject-the-reverifier flavour of [`reverify_chain`].
|
||||||
|
|
@ -630,7 +646,10 @@ mod tests {
|
||||||
assert!(!result.was_downgraded());
|
assert!(!result.was_downgraded());
|
||||||
assert_eq!(result.severity_after, ChainSeverity::Critical);
|
assert_eq!(result.severity_after, ChainSeverity::Critical);
|
||||||
assert_eq!(chain.severity, ChainSeverity::Critical);
|
assert_eq!(chain.severity, ChainSeverity::Critical);
|
||||||
assert_eq!(chain.dynamic_verdict.as_ref().unwrap().status, VerifyStatus::Confirmed);
|
assert_eq!(
|
||||||
|
chain.dynamic_verdict.as_ref().unwrap().status,
|
||||||
|
VerifyStatus::Confirmed
|
||||||
|
);
|
||||||
assert!(chain.reverify_reason.is_none());
|
assert!(chain.reverify_reason.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -690,7 +709,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert!(results.is_empty());
|
assert!(results.is_empty());
|
||||||
for c in &chains {
|
for c in &chains {
|
||||||
assert!(c.dynamic_verdict.is_none(), "no verdict attached when top_n=0");
|
assert!(
|
||||||
|
c.dynamic_verdict.is_none(),
|
||||||
|
"no verdict attached when top_n=0"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -178,8 +178,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn category_weights_strictly_ordered() {
|
fn category_weights_strictly_ordered() {
|
||||||
assert!(category_weight(ImpactCategory::BrowserToLocalRce) > category_weight(ImpactCategory::Rce));
|
assert!(
|
||||||
assert!(category_weight(ImpactCategory::Rce) > category_weight(ImpactCategory::SessionHijack));
|
category_weight(ImpactCategory::BrowserToLocalRce)
|
||||||
|
> category_weight(ImpactCategory::Rce)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
category_weight(ImpactCategory::Rce) > category_weight(ImpactCategory::SessionHijack)
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
category_weight(ImpactCategory::SessionHijack)
|
category_weight(ImpactCategory::SessionHijack)
|
||||||
> category_weight(ImpactCategory::InternalNetworkAccess)
|
> category_weight(ImpactCategory::InternalNetworkAccess)
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,16 @@ pub fn find_chains_with_reach(
|
||||||
.filter(|e| edge_reaches_entry(e, entry, reach))
|
.filter(|e| edge_reaches_entry(e, entry, reach))
|
||||||
.collect();
|
.collect();
|
||||||
candidates.sort_by(|a, b| {
|
candidates.sort_by(|a, b| {
|
||||||
(a.finding.stable_hash, &a.finding.rule_id, &a.finding.location)
|
(
|
||||||
.cmp(&(b.finding.stable_hash, &b.finding.rule_id, &b.finding.location))
|
a.finding.stable_hash,
|
||||||
|
&a.finding.rule_id,
|
||||||
|
&a.finding.location,
|
||||||
|
)
|
||||||
|
.cmp(&(
|
||||||
|
b.finding.stable_hash,
|
||||||
|
&b.finding.rule_id,
|
||||||
|
&b.finding.location,
|
||||||
|
))
|
||||||
});
|
});
|
||||||
for sink in &sinks {
|
for sink in &sinks {
|
||||||
// Scope candidates to the sink: same-file match (legacy),
|
// Scope candidates to the sink: same-file match (legacy),
|
||||||
|
|
@ -139,13 +147,9 @@ pub fn find_chains_with_reach(
|
||||||
})
|
})
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
if let Some(chain) = compose_chain(
|
if let Some(chain) =
|
||||||
entry,
|
compose_chain(entry, sink, &scoped, cfg.max_depth, local_listener_present)
|
||||||
sink,
|
&& chain.score >= cfg.min_score
|
||||||
&scoped,
|
|
||||||
cfg.max_depth,
|
|
||||||
local_listener_present,
|
|
||||||
) && chain.score >= cfg.min_score
|
|
||||||
{
|
{
|
||||||
chains.push(chain);
|
chains.push(chain);
|
||||||
}
|
}
|
||||||
|
|
@ -201,15 +205,9 @@ fn is_loopback_label(s: &str) -> bool {
|
||||||
|| lower.contains("://localhost")
|
|| lower.contains("://localhost")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edge_reaches_entry(
|
fn edge_reaches_entry(edge: &ChainEdge, entry: &EntryPoint, reach: Option<&FileReachMap>) -> bool {
|
||||||
edge: &ChainEdge,
|
|
||||||
entry: &EntryPoint,
|
|
||||||
reach: Option<&FileReachMap>,
|
|
||||||
) -> bool {
|
|
||||||
let route_method_match = match &edge.reach {
|
let route_method_match = match &edge.reach {
|
||||||
Reach::Reachable { route, method, .. } => {
|
Reach::Reachable { route, method, .. } => *route == entry.route && *method == entry.method,
|
||||||
*route == entry.route && *method == entry.method
|
|
||||||
}
|
|
||||||
Reach::Unreachable => return false,
|
Reach::Unreachable => return false,
|
||||||
};
|
};
|
||||||
if !route_method_match {
|
if !route_method_match {
|
||||||
|
|
@ -265,8 +263,7 @@ fn compose_chain(
|
||||||
let bound = scoped.len().min(max_depth);
|
let bound = scoped.len().min(max_depth);
|
||||||
let path: Vec<&ChainEdge> = scoped[..bound].to_vec();
|
let path: Vec<&ChainEdge> = scoped[..bound].to_vec();
|
||||||
let sink_cap = sole_cap(sink.cap_bits)?;
|
let sink_cap = sole_cap(sink.cap_bits)?;
|
||||||
let (impact, member_impacts) =
|
let (impact, member_impacts) = resolve_impact(&path, sink_cap, entry, local_listener_present)?;
|
||||||
resolve_impact(&path, sink_cap, entry, local_listener_present)?;
|
|
||||||
let mut chain = build_chain(entry, sink, &path, impact, &member_impacts);
|
let mut chain = build_chain(entry, sink, &path, impact, &member_impacts);
|
||||||
// SSRF + LocalListener refinement (Phase 24 deferred close): when
|
// SSRF + LocalListener refinement (Phase 24 deferred close): when
|
||||||
// the implied impact is `InternalNetworkAccess` AND the SurfaceMap
|
// the implied impact is `InternalNetworkAccess` AND the SurfaceMap
|
||||||
|
|
@ -394,9 +391,7 @@ fn build_chain(
|
||||||
/// member edge has `Feasibility::Confirmed` the composite verdict
|
/// member edge has `Feasibility::Confirmed` the composite verdict
|
||||||
/// inherits that confirmation; otherwise `None` (Phase 26 will run a
|
/// inherits that confirmation; otherwise `None` (Phase 26 will run a
|
||||||
/// real composite re-verification pass).
|
/// real composite re-verification pass).
|
||||||
fn composite_dynamic_verdict(
|
fn composite_dynamic_verdict(_path: &[ChainEdge]) -> Option<crate::evidence::VerifyResult> {
|
||||||
_path: &[ChainEdge],
|
|
||||||
) -> Option<crate::evidence::VerifyResult> {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -649,7 +644,9 @@ mod tests {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let mut surface_no_listener = SurfaceMap::new();
|
let mut surface_no_listener = SurfaceMap::new();
|
||||||
surface_no_listener.nodes.push(entry("app.py", "/fetch", false));
|
surface_no_listener
|
||||||
|
.nodes
|
||||||
|
.push(entry("app.py", "/fetch", false));
|
||||||
surface_no_listener
|
surface_no_listener
|
||||||
.nodes
|
.nodes
|
||||||
.push(sink("app.py", 20, "requests.get", Cap::SSRF));
|
.push(sink("app.py", 20, "requests.get", Cap::SSRF));
|
||||||
|
|
@ -662,7 +659,10 @@ mod tests {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(baseline.len(), 1);
|
assert_eq!(baseline.len(), 1);
|
||||||
assert_eq!(baseline[0].implied_impact, ImpactCategory::InternalNetworkAccess);
|
assert_eq!(
|
||||||
|
baseline[0].implied_impact,
|
||||||
|
ImpactCategory::InternalNetworkAccess
|
||||||
|
);
|
||||||
|
|
||||||
let mut surface_with_listener = surface_no_listener.clone();
|
let mut surface_with_listener = surface_no_listener.clone();
|
||||||
surface_with_listener
|
surface_with_listener
|
||||||
|
|
@ -681,7 +681,10 @@ mod tests {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert_eq!(boosted.len(), 1);
|
assert_eq!(boosted.len(), 1);
|
||||||
assert_eq!(boosted[0].implied_impact, ImpactCategory::InternalNetworkAccess);
|
assert_eq!(
|
||||||
|
boosted[0].implied_impact,
|
||||||
|
ImpactCategory::InternalNetworkAccess
|
||||||
|
);
|
||||||
let ratio = boosted[0].score / baseline[0].score;
|
let ratio = boosted[0].score / baseline[0].score;
|
||||||
assert!(
|
assert!(
|
||||||
(ratio - LOCAL_LISTENER_BOOST).abs() < 1e-9,
|
(ratio - LOCAL_LISTENER_BOOST).abs() < 1e-9,
|
||||||
|
|
@ -693,9 +696,7 @@ mod tests {
|
||||||
fn score_threshold_drops_low_score_chains() {
|
fn score_threshold_drops_low_score_chains() {
|
||||||
let mut surface = SurfaceMap::new();
|
let mut surface = SurfaceMap::new();
|
||||||
surface.nodes.push(entry("app.py", "/r", false));
|
surface.nodes.push(entry("app.py", "/r", false));
|
||||||
surface
|
surface.nodes.push(sink("app.py", 20, "open", Cap::FILE_IO));
|
||||||
.nodes
|
|
||||||
.push(sink("app.py", 20, "open", Cap::FILE_IO));
|
|
||||||
let e = edge_with(
|
let e = edge_with(
|
||||||
"app.py",
|
"app.py",
|
||||||
10,
|
10,
|
||||||
|
|
@ -724,12 +725,9 @@ mod tests {
|
||||||
surface.nodes.push(entry("routes.py", "/exec", false));
|
surface.nodes.push(entry("routes.py", "/exec", false));
|
||||||
// Sink lives in a helper file the entry handler transitively
|
// Sink lives in a helper file the entry handler transitively
|
||||||
// reaches, not the entry file itself.
|
// reaches, not the entry file itself.
|
||||||
surface.nodes.push(sink(
|
surface
|
||||||
"helper.py",
|
.nodes
|
||||||
20,
|
.push(sink("helper.py", 20, "os.system", Cap::CODE_EXEC));
|
||||||
"os.system",
|
|
||||||
Cap::CODE_EXEC,
|
|
||||||
));
|
|
||||||
let e = edge_with(
|
let e = edge_with(
|
||||||
"routes.py",
|
"routes.py",
|
||||||
10,
|
10,
|
||||||
|
|
@ -798,15 +796,9 @@ mod tests {
|
||||||
surface.nodes.push(entry("a.js", "/run", false));
|
surface.nodes.push(entry("a.js", "/run", false));
|
||||||
surface.nodes.push(entry("b.js", "/run", false));
|
surface.nodes.push(entry("b.js", "/run", false));
|
||||||
surface.nodes.push(entry("c.py", "/run", false));
|
surface.nodes.push(entry("c.py", "/run", false));
|
||||||
surface
|
surface.nodes.push(sink("a.js", 7, "eval", Cap::CODE_EXEC));
|
||||||
.nodes
|
surface.nodes.push(sink("b.js", 7, "eval", Cap::CODE_EXEC));
|
||||||
.push(sink("a.js", 7, "eval", Cap::CODE_EXEC));
|
surface.nodes.push(sink("c.py", 7, "eval", Cap::CODE_EXEC));
|
||||||
surface
|
|
||||||
.nodes
|
|
||||||
.push(sink("b.js", 7, "eval", Cap::CODE_EXEC));
|
|
||||||
surface
|
|
||||||
.nodes
|
|
||||||
.push(sink("c.py", 7, "eval", Cap::CODE_EXEC));
|
|
||||||
let edges = vec![
|
let edges = vec![
|
||||||
edge_with(
|
edge_with(
|
||||||
"a.js",
|
"a.js",
|
||||||
|
|
@ -845,7 +837,11 @@ mod tests {
|
||||||
let mut hashes: Vec<u64> = chains.iter().map(|c| c.stable_hash).collect();
|
let mut hashes: Vec<u64> = chains.iter().map(|c| c.stable_hash).collect();
|
||||||
hashes.sort();
|
hashes.sort();
|
||||||
hashes.dedup();
|
hashes.dedup();
|
||||||
assert_eq!(hashes.len(), 3, "surviving chains must have distinct hashes");
|
assert_eq!(
|
||||||
|
hashes.len(),
|
||||||
|
3,
|
||||||
|
"surviving chains must have distinct hashes"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// File-affinity gate on `edge_reaches_entry`: an entry only
|
/// File-affinity gate on `edge_reaches_entry`: an entry only
|
||||||
|
|
@ -858,12 +854,8 @@ mod tests {
|
||||||
let mut surface = SurfaceMap::new();
|
let mut surface = SurfaceMap::new();
|
||||||
surface.nodes.push(entry("a.js", "/run", false));
|
surface.nodes.push(entry("a.js", "/run", false));
|
||||||
surface.nodes.push(entry("b.js", "/run", false));
|
surface.nodes.push(entry("b.js", "/run", false));
|
||||||
surface
|
surface.nodes.push(sink("a.js", 7, "eval", Cap::CODE_EXEC));
|
||||||
.nodes
|
surface.nodes.push(sink("b.js", 7, "eval", Cap::CODE_EXEC));
|
||||||
.push(sink("a.js", 7, "eval", Cap::CODE_EXEC));
|
|
||||||
surface
|
|
||||||
.nodes
|
|
||||||
.push(sink("b.js", 7, "eval", Cap::CODE_EXEC));
|
|
||||||
// Single finding lives in a.js only. Both entries match
|
// Single finding lives in a.js only. Both entries match
|
||||||
// route+method but only entry@a.js shares the file.
|
// route+method but only entry@a.js shares the file.
|
||||||
let edges = vec![edge_with(
|
let edges = vec![edge_with(
|
||||||
|
|
|
||||||
|
|
@ -389,7 +389,12 @@ pub fn handle_command(
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
#[cfg(feature = "dynamic")]
|
#[cfg(feature = "dynamic")]
|
||||||
Commands::VerifyFeedback { finding_id, wrong, right, upload } => {
|
Commands::VerifyFeedback {
|
||||||
|
finding_id,
|
||||||
|
wrong,
|
||||||
|
right,
|
||||||
|
upload,
|
||||||
|
} => {
|
||||||
handle_verify_feedback(&finding_id, wrong.as_deref(), right, upload)?;
|
handle_verify_feedback(&finding_id, wrong.as_deref(), right, upload)?;
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "dynamic"))]
|
#[cfg(not(feature = "dynamic"))]
|
||||||
|
|
@ -477,8 +482,8 @@ fn handle_verify_feedback(
|
||||||
right: bool,
|
right: bool,
|
||||||
upload: bool,
|
upload: bool,
|
||||||
) -> crate::errors::NyxResult<()> {
|
) -> crate::errors::NyxResult<()> {
|
||||||
use std::io::Write;
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
let _ = upload; // Upload not yet implemented (reserved).
|
let _ = upload; // Upload not yet implemented (reserved).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,10 @@ fn load_verify_summaries(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let root_str = scan_root.to_string_lossy().into_owned();
|
let root_str = scan_root.to_string_lossy().into_owned();
|
||||||
Some(Arc::new(crate::summary::merge_summaries(all, Some(&root_str))))
|
Some(Arc::new(crate::summary::merge_summaries(
|
||||||
|
all,
|
||||||
|
Some(&root_str),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the whole-program [`crate::callgraph::CallGraph`] from a
|
/// Build the whole-program [`crate::callgraph::CallGraph`] from a
|
||||||
|
|
@ -446,60 +449,59 @@ pub fn handle(
|
||||||
let chain_reach_slot: std::sync::OnceLock<crate::callgraph::FileReachMap> =
|
let chain_reach_slot: std::sync::OnceLock<crate::callgraph::FileReachMap> =
|
||||||
std::sync::OnceLock::new();
|
std::sync::OnceLock::new();
|
||||||
|
|
||||||
let (mut diags, surface_map): (Vec<Diag>, crate::surface::SurfaceMap) = if index_mode
|
let (mut diags, surface_map): (Vec<Diag>, crate::surface::SurfaceMap) =
|
||||||
== IndexMode::Off
|
if index_mode == IndexMode::Off {
|
||||||
{
|
scan_filesystem_with_observer(
|
||||||
scan_filesystem_with_observer(
|
|
||||||
&scan_path,
|
|
||||||
config,
|
|
||||||
show_progress,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
Some(&preview_tier_seen),
|
|
||||||
Some(&chain_reach_slot),
|
|
||||||
)?
|
|
||||||
} else {
|
|
||||||
if index_mode == IndexMode::Rebuild || !db_path.exists() {
|
|
||||||
tracing::debug!("Scanning filesystem index filesystem");
|
|
||||||
crate::commands::index::build_index(
|
|
||||||
&project_name,
|
|
||||||
&scan_path,
|
&scan_path,
|
||||||
&db_path,
|
|
||||||
config,
|
config,
|
||||||
show_progress,
|
show_progress,
|
||||||
)?;
|
None,
|
||||||
}
|
None,
|
||||||
|
None,
|
||||||
|
Some(&preview_tier_seen),
|
||||||
|
Some(&chain_reach_slot),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
if index_mode == IndexMode::Rebuild || !db_path.exists() {
|
||||||
|
tracing::debug!("Scanning filesystem index filesystem");
|
||||||
|
crate::commands::index::build_index(
|
||||||
|
&project_name,
|
||||||
|
&scan_path,
|
||||||
|
&db_path,
|
||||||
|
config,
|
||||||
|
show_progress,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
let pool = Indexer::init(&db_path)?;
|
let pool = Indexer::init(&db_path)?;
|
||||||
if config.database.vacuum_on_startup {
|
if config.database.vacuum_on_startup {
|
||||||
let idx = Indexer::from_pool(&project_name, &pool)?;
|
let idx = Indexer::from_pool(&project_name, &pool)?;
|
||||||
idx.vacuum()?;
|
idx.vacuum()?;
|
||||||
}
|
}
|
||||||
// Indexed scan path: persist + return the SurfaceMap so the
|
// Indexed scan path: persist + return the SurfaceMap so the
|
||||||
// Phase 25 chain composer can walk it. `scan_with_index_parallel_observer`
|
// Phase 25 chain composer can walk it. `scan_with_index_parallel_observer`
|
||||||
// already builds and persists the map into the `surface_map`
|
// already builds and persists the map into the `surface_map`
|
||||||
// SQLite table; reload it through the same pool so the indexed
|
// SQLite table; reload it through the same pool so the indexed
|
||||||
// chain emission matches the non-indexed branch.
|
// chain emission matches the non-indexed branch.
|
||||||
let scan_pool = Arc::clone(&pool);
|
let scan_pool = Arc::clone(&pool);
|
||||||
let diags = scan_with_index_parallel_observer(
|
let diags = scan_with_index_parallel_observer(
|
||||||
&project_name,
|
&project_name,
|
||||||
scan_pool,
|
scan_pool,
|
||||||
config,
|
config,
|
||||||
show_progress,
|
show_progress,
|
||||||
&scan_path,
|
&scan_path,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Some(&preview_tier_seen),
|
Some(&preview_tier_seen),
|
||||||
Some(&chain_reach_slot),
|
Some(&chain_reach_slot),
|
||||||
)?;
|
)?;
|
||||||
let surface_map = {
|
let surface_map = {
|
||||||
let idx = Indexer::from_pool(&project_name, &pool)?;
|
let idx = Indexer::from_pool(&project_name, &pool)?;
|
||||||
idx.load_surface_map()?.unwrap_or_default()
|
idx.load_surface_map()?.unwrap_or_default()
|
||||||
|
};
|
||||||
|
(diags, surface_map)
|
||||||
};
|
};
|
||||||
(diags, surface_map)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Print the Preview-tier banner to stderr once, after file enumeration
|
// Print the Preview-tier banner to stderr once, after file enumeration
|
||||||
// completes and before the console output. Suppressed under --quiet and
|
// completes and before the console output. Suppressed under --quiet and
|
||||||
|
|
@ -646,8 +648,7 @@ pub fn handle(
|
||||||
// empty (legacy / AST-only paths that never built a call graph),
|
// empty (legacy / AST-only paths that never built a call graph),
|
||||||
// the chain layer falls back to file-local reach.
|
// the chain layer falls back to file-local reach.
|
||||||
let chain_reach = chain_reach_slot.get();
|
let chain_reach = chain_reach_slot.get();
|
||||||
let chain_edges =
|
let chain_edges = crate::chain::findings_to_edges_with_reach(&diags, &surface_map, chain_reach);
|
||||||
crate::chain::findings_to_edges_with_reach(&diags, &surface_map, chain_reach);
|
|
||||||
let chain_search_cfg = crate::chain::ChainSearchConfig {
|
let chain_search_cfg = crate::chain::ChainSearchConfig {
|
||||||
max_depth: config.chain.max_depth,
|
max_depth: config.chain.max_depth,
|
||||||
min_score: config.chain.min_score,
|
min_score: config.chain.min_score,
|
||||||
|
|
@ -697,21 +698,15 @@ pub fn handle(
|
||||||
let diff_value = verdict_diff
|
let diff_value = verdict_diff
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|d| serde_json::to_value(d).unwrap_or(serde_json::Value::Null));
|
.map(|d| serde_json::to_value(d).unwrap_or(serde_json::Value::Null));
|
||||||
let out = crate::output::build_findings_json(
|
let out =
|
||||||
&diags_for_output,
|
crate::output::build_findings_json(&diags_for_output, &chains, diff_value.as_ref());
|
||||||
&chains,
|
|
||||||
diff_value.as_ref(),
|
|
||||||
);
|
|
||||||
let json = serde_json::to_string(&out)
|
let json = serde_json::to_string(&out)
|
||||||
.map_err(|e| crate::errors::NyxError::Msg(e.to_string()))?;
|
.map_err(|e| crate::errors::NyxError::Msg(e.to_string()))?;
|
||||||
println!("{json}");
|
println!("{json}");
|
||||||
}
|
}
|
||||||
OutputFormat::Sarif => {
|
OutputFormat::Sarif => {
|
||||||
let sarif = crate::output::build_sarif_with_chains(
|
let sarif =
|
||||||
&diags_for_output,
|
crate::output::build_sarif_with_chains(&diags_for_output, &chains, &scan_path);
|
||||||
&chains,
|
|
||||||
&scan_path,
|
|
||||||
);
|
|
||||||
let json = serde_json::to_string_pretty(&sarif)
|
let json = serde_json::to_string_pretty(&sarif)
|
||||||
.map_err(|e| crate::errors::NyxError::Msg(e.to_string()))?;
|
.map_err(|e| crate::errors::NyxError::Msg(e.to_string()))?;
|
||||||
println!("{json}");
|
println!("{json}");
|
||||||
|
|
@ -725,12 +720,7 @@ pub fn handle(
|
||||||
tracing::debug!("Printing to console");
|
tracing::debug!("Printing to console");
|
||||||
print!(
|
print!(
|
||||||
"{}",
|
"{}",
|
||||||
crate::fmt::render_console(
|
crate::fmt::render_console(&diags_for_output, &project_name, Some(&stats), &chains,)
|
||||||
&diags_for_output,
|
|
||||||
&project_name,
|
|
||||||
Some(&stats),
|
|
||||||
&chains,
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
if let Some(ref diff) = verdict_diff {
|
if let Some(ref diff) = verdict_diff {
|
||||||
println!("\nBaseline comparison:");
|
println!("\nBaseline comparison:");
|
||||||
|
|
@ -769,10 +759,7 @@ pub fn handle(
|
||||||
if let (Some(diff), Some(gate_name)) = (&verdict_diff, gate) {
|
if let (Some(diff), Some(gate_name)) = (&verdict_diff, gate) {
|
||||||
if !crate::baseline::check_gate(diff, gate_name) {
|
if !crate::baseline::check_gate(diff, gate_name) {
|
||||||
if !suppress_status {
|
if !suppress_status {
|
||||||
eprintln!(
|
eprintln!("Gate '{}' violated. Exit code 2.", gate_name);
|
||||||
"Gate '{}' violated. Exit code 2.",
|
|
||||||
gate_name
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
std::process::exit(2);
|
std::process::exit(2);
|
||||||
}
|
}
|
||||||
|
|
@ -2235,9 +2222,8 @@ pub(crate) fn scan_filesystem_with_observer(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(out) = chain_reach_out {
|
if let Some(out) = chain_reach_out {
|
||||||
let _ = out.set(
|
let _ =
|
||||||
crate::callgraph::FileReachMap::build(&call_graph).with_scan_root(Some(root)),
|
out.set(crate::callgraph::FileReachMap::build(&call_graph).with_scan_root(Some(root)));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Pass 2: re-run with cross-file global summaries ──────────────────
|
// ── Pass 2: re-run with cross-file global summaries ──────────────────
|
||||||
|
|
@ -2311,15 +2297,14 @@ pub(crate) fn scan_filesystem_with_observer(
|
||||||
// `surface_map` SQLite table. The map is returned alongside the
|
// `surface_map` SQLite table. The map is returned alongside the
|
||||||
// diagnostics so consumers (e.g. `nyx surface`) can avoid scanning
|
// diagnostics so consumers (e.g. `nyx surface`) can avoid scanning
|
||||||
// twice.
|
// twice.
|
||||||
let surface_map = crate::surface::build::build_surface_map(
|
let surface_map =
|
||||||
&crate::surface::build::SurfaceBuildInputs {
|
crate::surface::build::build_surface_map(&crate::surface::build::SurfaceBuildInputs {
|
||||||
files: &all_paths,
|
files: &all_paths,
|
||||||
scan_root: Some(root),
|
scan_root: Some(root),
|
||||||
global_summaries: &gs,
|
global_summaries: &gs,
|
||||||
call_graph: &call_graph,
|
call_graph: &call_graph,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
if let Some(p) = progress {
|
if let Some(p) = progress {
|
||||||
p.record_pass2_ms(pass2_start.elapsed().as_millis() as u64);
|
p.record_pass2_ms(pass2_start.elapsed().as_millis() as u64);
|
||||||
}
|
}
|
||||||
|
|
@ -3142,15 +3127,14 @@ pub fn scan_with_index_parallel_observer(
|
||||||
// view. Errors here are logged but not propagated — the surface
|
// view. Errors here are logged but not propagated — the surface
|
||||||
// map is an additive Phase F deliverable, not a scan gate.
|
// map is an additive Phase F deliverable, not a scan gate.
|
||||||
{
|
{
|
||||||
let surface_map = crate::surface::build::build_surface_map(
|
let surface_map =
|
||||||
&crate::surface::build::SurfaceBuildInputs {
|
crate::surface::build::build_surface_map(&crate::surface::build::SurfaceBuildInputs {
|
||||||
files: &files,
|
files: &files,
|
||||||
scan_root: Some(scan_root),
|
scan_root: Some(scan_root),
|
||||||
global_summaries: &global_summaries,
|
global_summaries: &global_summaries,
|
||||||
call_graph: &call_graph,
|
call_graph: &call_graph,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
let mut idx = Indexer::from_pool(project, &pool)?;
|
let mut idx = Indexer::from_pool(project, &pool)?;
|
||||||
if let Err(e) = idx.replace_surface_map(&surface_map) {
|
if let Err(e) = idx.replace_surface_map(&surface_map) {
|
||||||
tracing::warn!("failed to persist surface_map: {e}");
|
tracing::warn!("failed to persist surface_map: {e}");
|
||||||
|
|
|
||||||
|
|
@ -100,12 +100,13 @@ pub fn load_or_build(
|
||||||
) -> NyxResult<SurfaceMap> {
|
) -> NyxResult<SurfaceMap> {
|
||||||
if let Ok((project, db_path)) = get_project_info(scan_root, database_dir)
|
if let Ok((project, db_path)) = get_project_info(scan_root, database_dir)
|
||||||
&& db_path.exists()
|
&& db_path.exists()
|
||||||
&& let Ok(pool) = Indexer::init(&db_path)
|
&& let Ok(pool) = Indexer::init(&db_path)
|
||||||
&& let Ok(idx) = Indexer::from_pool(&project, &pool)
|
&& let Ok(idx) = Indexer::from_pool(&project, &pool)
|
||||||
&& let Ok(Some(map)) = idx.load_surface_map()
|
&& let Ok(Some(map)) = idx.load_surface_map()
|
||||||
&& !map.nodes.is_empty() {
|
&& !map.nodes.is_empty()
|
||||||
return Ok(map);
|
{
|
||||||
}
|
return Ok(map);
|
||||||
|
}
|
||||||
build_from_filesystem(scan_root, config)
|
build_from_filesystem(scan_root, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,11 +152,7 @@ fn build_full_from_filesystem(scan_root: &Path, config: &Config) -> NyxResult<Su
|
||||||
///
|
///
|
||||||
/// Per-file errors are swallowed so a single bad file does not kill
|
/// Per-file errors are swallowed so a single bad file does not kill
|
||||||
/// the whole map.
|
/// the whole map.
|
||||||
fn build_summaries_inline(
|
fn build_summaries_inline(files: &[PathBuf], scan_root: &Path, config: &Config) -> GlobalSummaries {
|
||||||
files: &[PathBuf],
|
|
||||||
scan_root: &Path,
|
|
||||||
config: &Config,
|
|
||||||
) -> GlobalSummaries {
|
|
||||||
let root_str = scan_root.to_string_lossy().into_owned();
|
let root_str = scan_root.to_string_lossy().into_owned();
|
||||||
let mg = config.module_graph.as_deref();
|
let mg = config.module_graph.as_deref();
|
||||||
files
|
files
|
||||||
|
|
@ -279,7 +276,8 @@ pub fn render_text(map: &SurfaceMap, scan_root: Option<&Path>) -> String {
|
||||||
}
|
}
|
||||||
for &i in indices {
|
for &i in indices {
|
||||||
match &map.nodes[i] {
|
match &map.nodes[i] {
|
||||||
SurfaceNode::DataStore(_) | SurfaceNode::ExternalService(_)
|
SurfaceNode::DataStore(_)
|
||||||
|
| SurfaceNode::ExternalService(_)
|
||||||
| SurfaceNode::DangerousLocal(_) => {
|
| SurfaceNode::DangerousLocal(_) => {
|
||||||
if !entry_indices.is_empty() {
|
if !entry_indices.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -456,10 +454,18 @@ pub fn render_dot(map: &SurfaceMap) -> String {
|
||||||
escape_dot(&ep.handler_name),
|
escape_dot(&ep.handler_name),
|
||||||
),
|
),
|
||||||
"box",
|
"box",
|
||||||
if ep.auth_required { "#3aa57c" } else { "#3072c4" },
|
if ep.auth_required {
|
||||||
|
"#3aa57c"
|
||||||
|
} else {
|
||||||
|
"#3072c4"
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SurfaceNode::DataStore(ds) => (
|
SurfaceNode::DataStore(ds) => (
|
||||||
format!("DataStore ({})\\n{}", ds_kind_str(ds.kind), escape_dot(&ds.label)),
|
format!(
|
||||||
|
"DataStore ({})\\n{}",
|
||||||
|
ds_kind_str(ds.kind),
|
||||||
|
escape_dot(&ds.label)
|
||||||
|
),
|
||||||
"cylinder",
|
"cylinder",
|
||||||
"#b07a18",
|
"#b07a18",
|
||||||
),
|
),
|
||||||
|
|
@ -543,9 +549,7 @@ fn render_svg(map: &SurfaceMap) -> NyxResult<Vec<u8>> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::entry_points::HttpMethod;
|
use crate::entry_points::HttpMethod;
|
||||||
use crate::surface::{
|
use crate::surface::{EntryPoint, Framework, SourceLocation, SurfaceEdge, SurfaceNode};
|
||||||
EntryPoint, Framework, SourceLocation, SurfaceEdge, SurfaceNode,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn flask_fixture_map() -> SurfaceMap {
|
fn flask_fixture_map() -> SurfaceMap {
|
||||||
let mut map = SurfaceMap::new();
|
let mut map = SurfaceMap::new();
|
||||||
|
|
@ -598,12 +602,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn text_render_groups_reaches_under_entry() {
|
fn text_render_groups_reaches_under_entry() {
|
||||||
let mut m = flask_fixture_map();
|
let mut m = flask_fixture_map();
|
||||||
m.nodes
|
m.nodes.push(SurfaceNode::DangerousLocal(
|
||||||
.push(SurfaceNode::DangerousLocal(crate::surface::DangerousLocal {
|
crate::surface::DangerousLocal {
|
||||||
location: SourceLocation::new("app.py", 12, 1),
|
location: SourceLocation::new("app.py", 12, 1),
|
||||||
function_name: "eval".into(),
|
function_name: "eval".into(),
|
||||||
cap_bits: crate::labels::Cap::CODE_EXEC.bits(),
|
cap_bits: crate::labels::Cap::CODE_EXEC.bits(),
|
||||||
}));
|
},
|
||||||
|
));
|
||||||
// Build edge after canonicalize so indices are stable.
|
// Build edge after canonicalize so indices are stable.
|
||||||
m.canonicalize();
|
m.canonicalize();
|
||||||
let ep_idx = m
|
let ep_idx = m
|
||||||
|
|
@ -657,10 +662,7 @@ mod tests {
|
||||||
let canon = project_dir.canonicalize().unwrap();
|
let canon = project_dir.canonicalize().unwrap();
|
||||||
let files = collect_files(&canon, &cfg).unwrap();
|
let files = collect_files(&canon, &cfg).unwrap();
|
||||||
let summaries = build_summaries_inline(&files, &canon, &cfg);
|
let summaries = build_summaries_inline(&files, &canon, &cfg);
|
||||||
let names: Vec<String> = summaries
|
let names: Vec<String> = summaries.iter().map(|(k, _)| k.qualified_name()).collect();
|
||||||
.iter()
|
|
||||||
.map(|(k, _)| k.qualified_name())
|
|
||||||
.collect();
|
|
||||||
assert!(
|
assert!(
|
||||||
names.iter().any(|n| n.ends_with("run")),
|
names.iter().any(|n| n.ends_with("run")),
|
||||||
"summaries should contain `run`, got {names:?}"
|
"summaries should contain `run`, got {names:?}"
|
||||||
|
|
|
||||||
|
|
@ -1913,10 +1913,7 @@ pub mod index {
|
||||||
/// per project. The map is canonicalised before serialisation so
|
/// per project. The map is canonicalised before serialisation so
|
||||||
/// `replace_surface_map` + `load_surface_map` round-trip is
|
/// `replace_surface_map` + `load_surface_map` round-trip is
|
||||||
/// byte-identical for structurally identical maps.
|
/// byte-identical for structurally identical maps.
|
||||||
pub fn replace_surface_map(
|
pub fn replace_surface_map(&mut self, map: &crate::surface::SurfaceMap) -> NyxResult<()> {
|
||||||
&mut self,
|
|
||||||
map: &crate::surface::SurfaceMap,
|
|
||||||
) -> NyxResult<()> {
|
|
||||||
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
|
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
|
||||||
let mut canon = map.clone();
|
let mut canon = map.clone();
|
||||||
let bytes = canon
|
let bytes = canon
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,11 @@ pub fn prepare_rust(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
|
||||||
// Cache hit: binary already compiled and stored.
|
// Cache hit: binary already compiled and stored.
|
||||||
let binary = cache_path.join("nyx_harness");
|
let binary = cache_path.join("nyx_harness");
|
||||||
if binary.exists() {
|
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();
|
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> {
|
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("PATH", std::env::var("PATH").unwrap_or_default())
|
||||||
.env("HOME", std::env::var("HOME").unwrap_or_default())
|
.env("HOME", std::env::var("HOME").unwrap_or_default())
|
||||||
// Inherit CARGO_HOME so the local registry cache is reused.
|
// Inherit CARGO_HOME so the local registry cache is reused.
|
||||||
.env("CARGO_HOME", std::env::var("CARGO_HOME").unwrap_or_else(|_| {
|
.env(
|
||||||
dirs_next_cargo_home()
|
"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(
|
||||||
|
"RUSTUP_HOME",
|
||||||
|
std::env::var("RUSTUP_HOME").unwrap_or_default(),
|
||||||
|
)
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| format!("cargo build: {e}"))?;
|
.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.
|
// Copy binary to cache location.
|
||||||
let compiled = workdir.join("target").join("release").join("nyx_harness");
|
let compiled = workdir.join("target").join("release").join("nyx_harness");
|
||||||
if compiled.exists() {
|
if compiled.exists() {
|
||||||
std::fs::copy(&compiled, binary_dest)
|
std::fs::copy(&compiled, binary_dest).map_err(|e| format!("copy binary: {e}"))?;
|
||||||
.map_err(|e| format!("copy binary: {e}"))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -137,7 +147,10 @@ fn compute_rust_lockfile_hash(workdir: &Path) -> String {
|
||||||
h.update(&content);
|
h.update(&content);
|
||||||
}
|
}
|
||||||
let out = h.finalize();
|
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.
|
/// 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
|
/// If a compatible cache entry exists, returns it immediately. Otherwise
|
||||||
/// builds in isolation and caches the result.
|
/// builds in isolation and caches the result.
|
||||||
pub fn prepare_python(
|
pub fn prepare_python(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, BuildError> {
|
||||||
spec: &HarnessSpec,
|
|
||||||
workdir: &Path,
|
|
||||||
) -> Result<BuildResult, BuildError> {
|
|
||||||
let lockfile_hash = compute_lockfile_hash(workdir);
|
let lockfile_hash = compute_lockfile_hash(workdir);
|
||||||
let cache_path = build_cache_path(&lockfile_hash, "python", &spec.toolchain_id)?;
|
let cache_path = build_cache_path(&lockfile_hash, "python", &spec.toolchain_id)?;
|
||||||
|
|
||||||
|
|
@ -217,11 +227,7 @@ pub fn prepare_python(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_build_venv(
|
fn try_build_venv(venv_path: &Path, workdir: &Path, spec: &HarnessSpec) -> Result<(), String> {
|
||||||
venv_path: &Path,
|
|
||||||
workdir: &Path,
|
|
||||||
spec: &HarnessSpec,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
// Find python binary.
|
// Find python binary.
|
||||||
let python = python_binary(spec);
|
let python = python_binary(spec);
|
||||||
|
|
||||||
|
|
@ -262,10 +268,7 @@ fn try_build_venv(
|
||||||
|
|
||||||
fn python_binary(spec: &HarnessSpec) -> String {
|
fn python_binary(spec: &HarnessSpec) -> String {
|
||||||
// Try the pinned version first; fall back to python3.
|
// Try the pinned version first; fall back to python3.
|
||||||
let ver = spec
|
let ver = spec.toolchain_id.strip_prefix("python-").unwrap_or("3");
|
||||||
.toolchain_id
|
|
||||||
.strip_prefix("python-")
|
|
||||||
.unwrap_or("3");
|
|
||||||
let candidate = format!("python{ver}");
|
let candidate = format!("python{ver}");
|
||||||
if which_exists(&candidate) {
|
if which_exists(&candidate) {
|
||||||
return candidate;
|
return candidate;
|
||||||
|
|
@ -290,7 +293,10 @@ fn compute_lockfile_hash(workdir: &Path) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let out = h.finalize();
|
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(
|
fn build_cache_path(
|
||||||
|
|
@ -308,9 +314,7 @@ fn build_cache_path(
|
||||||
"cannot determine cache dir",
|
"cannot determine cache dir",
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
dirs.cache_dir()
|
dirs.cache_dir().join("dynamic").join("build-cache")
|
||||||
.join("dynamic")
|
|
||||||
.join("build-cache")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = format!("{lockfile_hash}-{language}-{toolchain_id}");
|
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 {
|
for attempt in 0..MAX_ATTEMPTS {
|
||||||
if attempt > 0 {
|
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) {
|
match try_npm_install(workdir) {
|
||||||
Ok(()) => {
|
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> {
|
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 {
|
fn compute_node_lockfile_hash(workdir: &Path) -> String {
|
||||||
let mut h = Hasher::new();
|
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)) {
|
if let Ok(content) = std::fs::read(workdir.join(fname)) {
|
||||||
h.update(fname.as_bytes());
|
h.update(fname.as_bytes());
|
||||||
h.update(&content);
|
h.update(&content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let out = h.finalize();
|
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 ──────────────────────────────────────────────────────────
|
// ── Go build sandbox ──────────────────────────────────────────────────────────
|
||||||
|
|
@ -470,7 +487,9 @@ pub fn prepare_go(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bui
|
||||||
|
|
||||||
for attempt in 0..MAX_ATTEMPTS {
|
for attempt in 0..MAX_ATTEMPTS {
|
||||||
if attempt > 0 {
|
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);
|
let _ = std::fs::remove_dir_all(&cache_path);
|
||||||
std::fs::create_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> {
|
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 go_bin = std::env::var("NYX_GO_BIN").unwrap_or_else(|_| "go".to_owned());
|
||||||
let output = Command::new(&go_bin)
|
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)
|
.current_dir(workdir)
|
||||||
.env_clear()
|
.env_clear()
|
||||||
.env("PATH", std::env::var("PATH").unwrap_or_default())
|
.env("PATH", std::env::var("PATH").unwrap_or_default())
|
||||||
.env("HOME", std::env::var("HOME").unwrap_or_default())
|
.env("HOME", std::env::var("HOME").unwrap_or_default())
|
||||||
.env("GOPATH", std::env::var("GOPATH").unwrap_or_else(|_| {
|
.env(
|
||||||
std::env::var("HOME").map(|h| format!("{h}/go")).unwrap_or_else(|_| "/tmp/go".to_owned())
|
"GOPATH",
|
||||||
}))
|
std::env::var("GOPATH").unwrap_or_else(|_| {
|
||||||
.env("GOMODCACHE", std::env::var("GOMODCACHE").unwrap_or_else(|_| {
|
std::env::var("HOME")
|
||||||
std::env::var("HOME").map(|h| format!("{h}/go/pkg/mod")).unwrap_or_else(|_| "/tmp/gomod".to_owned())
|
.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()
|
.output()
|
||||||
.map_err(|e| format!("go build: {e}"))?;
|
.map_err(|e| format!("go build: {e}"))?;
|
||||||
|
|
||||||
|
|
@ -529,7 +566,10 @@ fn compute_go_source_hash(workdir: &Path) -> String {
|
||||||
h.update(&content);
|
h.update(&content);
|
||||||
}
|
}
|
||||||
let out = h.finalize();
|
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 ────────────────────────────────────────────────────────
|
// ── Java build sandbox ────────────────────────────────────────────────────────
|
||||||
|
|
@ -592,7 +632,9 @@ pub fn prepare_java(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
|
||||||
|
|
||||||
for attempt in 0..MAX_ATTEMPTS {
|
for attempt in 0..MAX_ATTEMPTS {
|
||||||
if attempt > 0 {
|
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) {
|
match try_compile_java(workdir, &cache_path, target_release) {
|
||||||
Ok(()) => {
|
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.
|
/// 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());
|
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
|
// 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() {
|
if path.is_dir() {
|
||||||
stack.push(path);
|
stack.push(path);
|
||||||
} else if path.extension().map(|e| e == "class").unwrap_or(false)
|
} else if path.extension().map(|e| e == "class").unwrap_or(false)
|
||||||
&& let Ok(rel) = path.strip_prefix(root) {
|
&& let Ok(rel) = path.strip_prefix(root)
|
||||||
out.push(rel.to_path_buf());
|
{
|
||||||
}
|
out.push(rel.to_path_buf());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.sort();
|
out.sort();
|
||||||
|
|
@ -826,7 +876,10 @@ fn compute_java_source_hash(workdir: &Path, target_release: Option<u32>) -> Stri
|
||||||
h.update(b":release=host");
|
h.update(b":release=host");
|
||||||
}
|
}
|
||||||
let out = h.finalize();
|
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 ─────────────────────────────────────────────────────────
|
// ── PHP build sandbox ─────────────────────────────────────────────────────────
|
||||||
|
|
@ -869,7 +922,9 @@ pub fn prepare_php(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bu
|
||||||
|
|
||||||
for attempt in 0..MAX_ATTEMPTS {
|
for attempt in 0..MAX_ATTEMPTS {
|
||||||
if attempt > 0 {
|
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) {
|
match try_composer_install(workdir) {
|
||||||
Ok(()) => {
|
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> {
|
fn try_composer_install(workdir: &Path) -> Result<(), String> {
|
||||||
|
|
@ -922,7 +980,10 @@ fn compute_php_lockfile_hash(workdir: &Path) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let out = h.finalize();
|
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 ───────────────────────────────────────────────────────────
|
// ── C build sandbox ───────────────────────────────────────────────────────────
|
||||||
|
|
@ -959,7 +1020,9 @@ pub fn prepare_c(
|
||||||
|
|
||||||
for attempt in 0..MAX_ATTEMPTS {
|
for attempt in 0..MAX_ATTEMPTS {
|
||||||
if attempt > 0 {
|
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);
|
let _ = std::fs::remove_dir_all(&cache_path);
|
||||||
std::fs::create_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> {
|
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 binary_str = binary_dest.to_str().unwrap_or("nyx_harness");
|
||||||
let mut args: Vec<&str> = leading_flags.to_vec();
|
let mut args: Vec<&str> = leading_flags.to_vec();
|
||||||
args.extend(["-o", binary_str, "main.c"]);
|
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");
|
h.update(b"static");
|
||||||
}
|
}
|
||||||
let out = h.finalize();
|
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 ─────────────────────────────────────────────────────────
|
// ── C++ build sandbox ─────────────────────────────────────────────────────────
|
||||||
|
|
@ -1093,7 +1167,9 @@ pub fn prepare_cpp(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, Bu
|
||||||
|
|
||||||
for attempt in 0..MAX_ATTEMPTS {
|
for attempt in 0..MAX_ATTEMPTS {
|
||||||
if attempt > 0 {
|
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);
|
let _ = std::fs::remove_dir_all(&cache_path);
|
||||||
std::fs::create_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> {
|
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()
|
"c++".to_owned()
|
||||||
});
|
});
|
||||||
let output = Command::new(&cxx_bin)
|
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)
|
.current_dir(workdir)
|
||||||
.env_clear()
|
.env_clear()
|
||||||
.env("PATH", std::env::var("PATH").unwrap_or_default())
|
.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();
|
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) ────────
|
// ── Uniform per-language build dispatch (Phase 26 — composite chains) ────────
|
||||||
|
|
@ -1251,10 +1340,14 @@ fn start_isolated_build_container(
|
||||||
network_none: bool,
|
network_none: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut args: Vec<&str> = vec![
|
let mut args: Vec<&str> = vec![
|
||||||
"run", "-d", "--rm",
|
"run",
|
||||||
"--name", name,
|
"-d",
|
||||||
|
"--rm",
|
||||||
|
"--name",
|
||||||
|
name,
|
||||||
"--cap-drop=ALL",
|
"--cap-drop=ALL",
|
||||||
"--security-opt", "no-new-privileges:true",
|
"--security-opt",
|
||||||
|
"no-new-privileges:true",
|
||||||
];
|
];
|
||||||
if network_none {
|
if network_none {
|
||||||
args.extend_from_slice(&["--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());
|
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");
|
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
|
||||||
|
|
||||||
// CARGO_NET_OFFLINE prevents any registry contact; std lib is pre-built in the image.
|
// CARGO_NET_OFFLINE prevents any registry contact; std lib is pre-built in the image.
|
||||||
let _ = std::process::Command::new(&docker)
|
let _ = std::process::Command::new(&docker)
|
||||||
.args([
|
.args([
|
||||||
"exec",
|
"exec",
|
||||||
"-e", "CARGO_NET_OFFLINE=true",
|
"-e",
|
||||||
|
"CARGO_NET_OFFLINE=true",
|
||||||
&container,
|
&container,
|
||||||
"sh", "-c", "cd /build && cargo build --release 2>&1",
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"cd /build && cargo build --release 2>&1",
|
||||||
])
|
])
|
||||||
.output();
|
.output();
|
||||||
|
|
||||||
|
|
@ -1347,10 +1446,15 @@ pub fn prepare_node_in_docker(workdir: &Path) -> Result<(), String> {
|
||||||
let container = build_container_id("nodebuild", workdir);
|
let container = build_container_id("nodebuild", workdir);
|
||||||
|
|
||||||
if !start_isolated_build_container(&docker, &container, "node:20-slim", true) {
|
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");
|
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
|
||||||
|
|
||||||
// npm install may fail if the registry is unreachable (--network none), but the
|
// 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([
|
.args([
|
||||||
"exec",
|
"exec",
|
||||||
&container,
|
&container,
|
||||||
"sh", "-c",
|
"sh",
|
||||||
|
"-c",
|
||||||
"cd /build && npm install --no-save --no-audit --no-fund 2>&1",
|
"cd /build && npm install --no-save --no-audit --no-fund 2>&1",
|
||||||
])
|
])
|
||||||
.output();
|
.output();
|
||||||
|
|
@ -1379,20 +1484,29 @@ pub fn prepare_go_in_docker(workdir: &Path) -> Result<(), String> {
|
||||||
let container = build_container_id("gobuild", workdir);
|
let container = build_container_id("gobuild", workdir);
|
||||||
|
|
||||||
if !start_isolated_build_container(&docker, &container, "golang:1.21-slim", true) {
|
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");
|
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
|
||||||
|
|
||||||
// GOPROXY=off prevents module downloads; std library is pre-compiled in the image.
|
// GOPROXY=off prevents module downloads; std library is pre-compiled in the image.
|
||||||
let _ = std::process::Command::new(&docker)
|
let _ = std::process::Command::new(&docker)
|
||||||
.args([
|
.args([
|
||||||
"exec",
|
"exec",
|
||||||
"-e", "GOPROXY=off",
|
"-e",
|
||||||
"-e", "GONOSUMDB=*",
|
"GOPROXY=off",
|
||||||
|
"-e",
|
||||||
|
"GONOSUMDB=*",
|
||||||
&container,
|
&container,
|
||||||
"sh", "-c", "cd /build && go build ./... 2>&1",
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"cd /build && go build ./... 2>&1",
|
||||||
])
|
])
|
||||||
.output();
|
.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.
|
// Bridge network: Maven must download exec-maven-plugin from Maven Central.
|
||||||
// Filesystem isolation still holds: /tmp inside the container is private.
|
// Filesystem isolation still holds: /tmp inside the container is private.
|
||||||
if !start_isolated_build_container(
|
if !start_isolated_build_container(&docker, &container, "maven:3.9-eclipse-temurin-21", false) {
|
||||||
&docker,
|
|
||||||
&container,
|
|
||||||
"maven:3.9-eclipse-temurin-21",
|
|
||||||
false,
|
|
||||||
) {
|
|
||||||
return Err(
|
return Err(
|
||||||
"failed to start maven:3.9-eclipse-temurin-21 build container; image may not be available"
|
"failed to start maven:3.9-eclipse-temurin-21 build container; image may not be available"
|
||||||
.into(),
|
.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");
|
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
|
||||||
|
|
||||||
let _ = std::process::Command::new(&docker)
|
let _ = std::process::Command::new(&docker)
|
||||||
.args([
|
.args([
|
||||||
"exec",
|
"exec",
|
||||||
&container,
|
&container,
|
||||||
"sh", "-c", "cd /build && mvn --no-transfer-progress validate 2>&1",
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"cd /build && mvn --no-transfer-progress validate 2>&1",
|
||||||
])
|
])
|
||||||
.output();
|
.output();
|
||||||
|
|
||||||
|
|
@ -1451,10 +1565,15 @@ pub fn prepare_php_in_docker(workdir: &Path) -> Result<(), String> {
|
||||||
let container = build_container_id("phpbuild", workdir);
|
let container = build_container_id("phpbuild", workdir);
|
||||||
|
|
||||||
if !start_isolated_build_container(&docker, &container, "composer:2", true) {
|
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");
|
copy_workdir_to_build_container(&docker, workdir, &container, "/build");
|
||||||
|
|
||||||
// Empty require{} means no packages to fetch; post-install-cmd still fires.
|
// 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([
|
.args([
|
||||||
"exec",
|
"exec",
|
||||||
&container,
|
&container,
|
||||||
"sh", "-c",
|
"sh",
|
||||||
|
"-c",
|
||||||
"cd /build && composer install --no-dev --no-interaction --prefer-dist 2>&1",
|
"cd /build && composer install --no-dev --no-interaction --prefer-dist 2>&1",
|
||||||
])
|
])
|
||||||
.output();
|
.output();
|
||||||
|
|
@ -1519,11 +1639,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn java_source_hash_differs_across_target_release() {
|
fn java_source_hash_differs_across_target_release() {
|
||||||
let dir = tempfile::TempDir::new().unwrap();
|
let dir = tempfile::TempDir::new().unwrap();
|
||||||
std::fs::write(
|
std::fs::write(dir.path().join("Vuln.java"), "public class Vuln {}\n").unwrap();
|
||||||
dir.path().join("Vuln.java"),
|
|
||||||
"public class Vuln {}\n",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let h_none = compute_java_source_hash(dir.path(), None);
|
let h_none = compute_java_source_hash(dir.path(), None);
|
||||||
let h17 = compute_java_source_hash(dir.path(), Some(17));
|
let h17 = compute_java_source_hash(dir.path(), Some(17));
|
||||||
let h21 = compute_java_source_hash(dir.path(), Some(21));
|
let h21 = compute_java_source_hash(dir.path(), Some(21));
|
||||||
|
|
@ -1568,7 +1684,10 @@ mod tests {
|
||||||
copy_dir_all(src.path(), dst.path()).unwrap();
|
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("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]
|
#[test]
|
||||||
|
|
@ -1760,7 +1879,11 @@ mod tests {
|
||||||
|
|
||||||
let result = dispatch_prepare(&spec, dir.path(), ProcessHardeningProfile::Standard)
|
let result = dispatch_prepare(&spec, dir.path(), ProcessHardeningProfile::Standard)
|
||||||
.expect("TypeScript dispatch must succeed on a workdir with no package.json");
|
.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!(
|
assert!(
|
||||||
!result.cache_hit,
|
!result.cache_hit,
|
||||||
"first dispatch on a fresh cache must be a cache miss; got {result:?}",
|
"first dispatch on a fresh cache must be a cache miss; got {result:?}",
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,9 @@ mod xss;
|
||||||
mod xxe;
|
mod xxe;
|
||||||
|
|
||||||
pub use registry::{
|
pub use registry::{
|
||||||
audit_marker_collisions, benign_payload_for, benign_payload_for_lang, materialise_bytes,
|
CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL, audit_marker_collisions, benign_payload_for,
|
||||||
payloads_for, payloads_for_lang, resolve_benign_control, resolve_benign_control_lang,
|
benign_payload_for_lang, materialise_bytes, payloads_for, payloads_for_lang,
|
||||||
CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL,
|
resolve_benign_control, resolve_benign_control_lang,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Re-exported canonical [`Oracle`] type.
|
/// Re-exported canonical [`Oracle`] type.
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@
|
||||||
//! The runtime `corpus_registry::audit` test mirrors both checks so
|
//! The runtime `corpus_registry::audit` test mirrors both checks so
|
||||||
//! failure surfaces in `cargo test` output, not just `cargo build`.
|
//! failure surfaces in `cargo test` output, not just `cargo build`.
|
||||||
|
|
||||||
use super::registry::{CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL};
|
|
||||||
use super::CuratedPayload;
|
use super::CuratedPayload;
|
||||||
|
use super::registry::{CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL};
|
||||||
use crate::labels::Cap;
|
use crate::labels::Cap;
|
||||||
|
|
||||||
/// Byte-level equality for `&'static str` usable in const eval.
|
/// 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 {
|
match p.benign_control {
|
||||||
Some(r) => {
|
Some(r) => {
|
||||||
let found = slice
|
let found = slice.iter().any(|q| q.is_benign && q.label == r.label);
|
||||||
.iter()
|
|
||||||
.any(|q| q.is_benign && q.label == r.label);
|
|
||||||
if !found {
|
if !found {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"({:?}, {:?}) vuln payload {:?} references missing \
|
"({:?}, {:?}) vuln payload {:?} references missing \
|
||||||
|
|
@ -180,17 +178,18 @@ pub fn audit_benign_label_uniqueness_runtime() -> Result<(), String> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(prev_lang) = bucket.insert(p.label, lang)
|
if let Some(prev_lang) = bucket.insert(p.label, lang)
|
||||||
&& prev_lang != lang {
|
&& prev_lang != lang
|
||||||
return Err(format!(
|
{
|
||||||
"benign label {:?} for cap {:#x} is registered in both \
|
return Err(format!(
|
||||||
|
"benign label {:?} for cap {:#x} is registered in both \
|
||||||
{:?} and {:?} — lang-agnostic resolve_benign_control \
|
{:?} and {:?} — lang-agnostic resolve_benign_control \
|
||||||
could match the wrong language",
|
could match the wrong language",
|
||||||
p.label,
|
p.label,
|
||||||
cap.bits(),
|
cap.bits(),
|
||||||
prev_lang,
|
prev_lang,
|
||||||
lang,
|
lang,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -206,7 +205,6 @@ mod corpus_registry {
|
||||||
fn audit() {
|
fn audit() {
|
||||||
audit_benign_controls_runtime().expect("benign_control audit failed");
|
audit_benign_controls_runtime().expect("benign_control audit failed");
|
||||||
audit_cap_coverage_runtime().expect("cap coverage audit failed");
|
audit_cap_coverage_runtime().expect("cap coverage audit failed");
|
||||||
audit_benign_label_uniqueness_runtime()
|
audit_benign_label_uniqueness_runtime().expect("benign label uniqueness audit failed");
|
||||||
.expect("benign label uniqueness audit failed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-c" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-c",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-cpp" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-cpp",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-go" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-go",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-java" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-java",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-javascript" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-javascript",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-php" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-php",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-python" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-python",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-ruby" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-ruby",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
// Benign control: plain text that should never produce the cmdi marker.
|
// Benign control: plain text that should never produce the cmdi marker.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "cmdi-benign-typescript" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "cmdi-benign-typescript",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_WEAK",
|
bytes: b"NYX_CRYPTO_WEAK",
|
||||||
label: "crypto-go-weak-random",
|
label: "crypto-go-weak-random",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -19,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/crypto/go/vuln.go"],
|
fixture_paths: &["tests/dynamic_fixtures/crypto/go/vuln.go"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "crypto-go-benign",
|
label: "crypto-go-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -29,7 +33,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_STRONG",
|
bytes: b"NYX_CRYPTO_STRONG",
|
||||||
label: "crypto-go-benign",
|
label: "crypto-go-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_WEAK",
|
bytes: b"NYX_CRYPTO_WEAK",
|
||||||
label: "crypto-java-weak-random",
|
label: "crypto-java-weak-random",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -30,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/crypto/java/vuln.java"],
|
fixture_paths: &["tests/dynamic_fixtures/crypto/java/vuln.java"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "crypto-java-benign",
|
label: "crypto-java-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -40,7 +44,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_STRONG",
|
bytes: b"NYX_CRYPTO_STRONG",
|
||||||
label: "crypto-java-benign",
|
label: "crypto-java-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_WEAK",
|
bytes: b"NYX_CRYPTO_WEAK",
|
||||||
label: "crypto-php-weak-random",
|
label: "crypto-php-weak-random",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/crypto/php/vuln.php"],
|
fixture_paths: &["tests/dynamic_fixtures/crypto/php/vuln.php"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "crypto-php-benign",
|
label: "crypto-php-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_STRONG",
|
bytes: b"NYX_CRYPTO_STRONG",
|
||||||
label: "crypto-php-benign",
|
label: "crypto-php-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_WEAK",
|
bytes: b"NYX_CRYPTO_WEAK",
|
||||||
label: "crypto-python-weak-random",
|
label: "crypto-python-weak-random",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -28,7 +30,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/crypto/python/vuln.py"],
|
fixture_paths: &["tests/dynamic_fixtures/crypto/python/vuln.py"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "crypto-python-benign",
|
label: "crypto-python-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -38,7 +42,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_STRONG",
|
bytes: b"NYX_CRYPTO_STRONG",
|
||||||
label: "crypto-python-benign",
|
label: "crypto-python-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_WEAK",
|
bytes: b"NYX_CRYPTO_WEAK",
|
||||||
label: "crypto-rust-weak-random",
|
label: "crypto-rust-weak-random",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -19,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/crypto/rust/vuln.rs"],
|
fixture_paths: &["tests/dynamic_fixtures/crypto/rust/vuln.rs"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "crypto-rust-benign",
|
label: "crypto-rust-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -29,7 +33,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"NYX_CRYPTO_STRONG",
|
bytes: b"NYX_CRYPTO_STRONG",
|
||||||
label: "crypto-rust-benign",
|
label: "crypto-rust-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::WeakKeyEntropy { max_bits: WEAK_BITS }],
|
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||||
|
max_bits: WEAK_BITS,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"attacker.test",
|
bytes: b"attacker.test",
|
||||||
label: "data-exfil-go-external",
|
label: "data-exfil-go-external",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/go/vuln.go"],
|
fixture_paths: &["tests/dynamic_fixtures/data_exfil/go/vuln.go"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "data-exfil-go-benign",
|
label: "data-exfil-go-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"127.0.0.1",
|
bytes: b"127.0.0.1",
|
||||||
label: "data-exfil-go-benign",
|
label: "data-exfil-go-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"attacker.test",
|
bytes: b"attacker.test",
|
||||||
label: "data-exfil-java-external",
|
label: "data-exfil-java-external",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/java/vuln.java"],
|
fixture_paths: &["tests/dynamic_fixtures/data_exfil/java/vuln.java"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "data-exfil-java-benign",
|
label: "data-exfil-java-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"127.0.0.1",
|
bytes: b"127.0.0.1",
|
||||||
label: "data-exfil-java-benign",
|
label: "data-exfil-java-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"attacker.test",
|
bytes: b"attacker.test",
|
||||||
label: "data-exfil-js-external",
|
label: "data-exfil-js-external",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/js/vuln.js"],
|
fixture_paths: &["tests/dynamic_fixtures/data_exfil/js/vuln.js"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "data-exfil-js-benign",
|
label: "data-exfil-js-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"127.0.0.1",
|
bytes: b"127.0.0.1",
|
||||||
label: "data-exfil-js-benign",
|
label: "data-exfil-js-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"attacker.test",
|
bytes: b"attacker.test",
|
||||||
label: "data-exfil-php-external",
|
label: "data-exfil-php-external",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/php/vuln.php"],
|
fixture_paths: &["tests/dynamic_fixtures/data_exfil/php/vuln.php"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "data-exfil-php-benign",
|
label: "data-exfil-php-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"127.0.0.1",
|
bytes: b"127.0.0.1",
|
||||||
label: "data-exfil-php-benign",
|
label: "data-exfil-php-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"attacker.test",
|
bytes: b"attacker.test",
|
||||||
label: "data-exfil-python-external",
|
label: "data-exfil-python-external",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/python/vuln.py"],
|
fixture_paths: &["tests/dynamic_fixtures/data_exfil/python/vuln.py"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "data-exfil-python-benign",
|
label: "data-exfil-python-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"127.0.0.1",
|
bytes: b"127.0.0.1",
|
||||||
label: "data-exfil-python-benign",
|
label: "data-exfil-python-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"attacker.test",
|
bytes: b"attacker.test",
|
||||||
label: "data-exfil-ruby-external",
|
label: "data-exfil-ruby-external",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/ruby/vuln.rb"],
|
fixture_paths: &["tests/dynamic_fixtures/data_exfil/ruby/vuln.rb"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "data-exfil-ruby-benign",
|
label: "data-exfil-ruby-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"127.0.0.1",
|
bytes: b"127.0.0.1",
|
||||||
label: "data-exfil-ruby-benign",
|
label: "data-exfil-ruby-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"attacker.test",
|
bytes: b"attacker.test",
|
||||||
label: "data-exfil-rust-external",
|
label: "data-exfil-rust-external",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -18,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/rust/vuln.rs"],
|
fixture_paths: &["tests/dynamic_fixtures/data_exfil/rust/vuln.rs"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "data-exfil-rust-benign",
|
label: "data-exfil-rust-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -28,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"127.0.0.1",
|
bytes: b"127.0.0.1",
|
||||||
label: "data-exfil-rust-benign",
|
label: "data-exfil-rust-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::OutboundHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 7,
|
since_corpus_version: 7,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/deserialize/java/Vuln.java"],
|
||||||
"tests/dynamic_fixtures/deserialize/java/Vuln.java",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||||
require_invoked: true,
|
require_invoked: true,
|
||||||
|
|
@ -55,9 +53,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 7,
|
since_corpus_version: 7,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/deserialize/java/Benign.java"],
|
||||||
"tests/dynamic_fixtures/deserialize/java/Benign.java",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 7,
|
since_corpus_version: 7,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/deserialize/php/vuln.php"],
|
||||||
"tests/dynamic_fixtures/deserialize/php/vuln.php",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||||
require_invoked: true,
|
require_invoked: true,
|
||||||
|
|
@ -53,9 +51,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 7,
|
since_corpus_version: 7,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/deserialize/php/benign.php"],
|
||||||
"tests/dynamic_fixtures/deserialize/php/benign.php",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 7,
|
since_corpus_version: 7,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/deserialize/python/vuln.py"],
|
||||||
"tests/dynamic_fixtures/deserialize/python/vuln.py",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||||
require_invoked: true,
|
require_invoked: true,
|
||||||
|
|
@ -49,9 +47,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 7,
|
since_corpus_version: 7,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/deserialize/python/benign.py"],
|
||||||
"tests/dynamic_fixtures/deserialize/python/benign.py",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 7,
|
since_corpus_version: 7,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/deserialize/ruby/vuln.rb"],
|
||||||
"tests/dynamic_fixtures/deserialize/ruby/vuln.rb",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||||
require_invoked: true,
|
require_invoked: true,
|
||||||
|
|
@ -50,9 +48,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 7,
|
since_corpus_version: 7,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/deserialize/ruby/benign.rb"],
|
||||||
"tests/dynamic_fixtures/deserialize/ruby/benign.rb",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "fmt-string-benign" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "fmt-string-benign",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"https://attacker.test/",
|
bytes: b"https://attacker.test/",
|
||||||
label: "open-redirect-go-absolute",
|
label: "open-redirect-go-absolute",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -29,7 +31,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/go/vuln.go"],
|
fixture_paths: &["tests/dynamic_fixtures/open_redirect/go/vuln.go"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "open-redirect-go-benign",
|
label: "open-redirect-go-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -39,7 +43,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"/dashboard",
|
bytes: b"/dashboard",
|
||||||
label: "open-redirect-go-benign",
|
label: "open-redirect-go-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"https://attacker.test/",
|
bytes: b"https://attacker.test/",
|
||||||
label: "open-redirect-java-absolute",
|
label: "open-redirect-java-absolute",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -34,7 +36,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/java/Vuln.java"],
|
fixture_paths: &["tests/dynamic_fixtures/open_redirect/java/Vuln.java"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "open-redirect-java-benign",
|
label: "open-redirect-java-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -44,7 +48,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"/dashboard",
|
bytes: b"/dashboard",
|
||||||
label: "open-redirect-java-benign",
|
label: "open-redirect-java-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"https://attacker.test/",
|
bytes: b"https://attacker.test/",
|
||||||
label: "open-redirect-js-absolute",
|
label: "open-redirect-js-absolute",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -28,7 +30,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/js/vuln.js"],
|
fixture_paths: &["tests/dynamic_fixtures/open_redirect/js/vuln.js"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "open-redirect-js-benign",
|
label: "open-redirect-js-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -38,7 +42,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"/dashboard",
|
bytes: b"/dashboard",
|
||||||
label: "open-redirect-js-benign",
|
label: "open-redirect-js-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"https://attacker.test/",
|
bytes: b"https://attacker.test/",
|
||||||
label: "open-redirect-php-absolute",
|
label: "open-redirect-php-absolute",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -30,7 +32,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/php/vuln.php"],
|
fixture_paths: &["tests/dynamic_fixtures/open_redirect/php/vuln.php"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "open-redirect-php-benign",
|
label: "open-redirect-php-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -40,7 +44,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"/dashboard",
|
bytes: b"/dashboard",
|
||||||
label: "open-redirect-php-benign",
|
label: "open-redirect-php-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"https://attacker.test/",
|
bytes: b"https://attacker.test/",
|
||||||
label: "open-redirect-python-absolute",
|
label: "open-redirect-python-absolute",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -29,7 +31,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/python/vuln.py"],
|
fixture_paths: &["tests/dynamic_fixtures/open_redirect/python/vuln.py"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "open-redirect-python-benign",
|
label: "open-redirect-python-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -39,7 +43,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"/dashboard",
|
bytes: b"/dashboard",
|
||||||
label: "open-redirect-python-benign",
|
label: "open-redirect-python-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"https://attacker.test/",
|
bytes: b"https://attacker.test/",
|
||||||
label: "open-redirect-ruby-absolute",
|
label: "open-redirect-ruby-absolute",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -28,7 +30,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/ruby/vuln.rb"],
|
fixture_paths: &["tests/dynamic_fixtures/open_redirect/ruby/vuln.rb"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "open-redirect-ruby-benign",
|
label: "open-redirect-ruby-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -38,7 +42,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"/dashboard",
|
bytes: b"/dashboard",
|
||||||
label: "open-redirect-ruby-benign",
|
label: "open-redirect-ruby-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"https://attacker.test/",
|
bytes: b"https://attacker.test/",
|
||||||
label: "open-redirect-rust-absolute",
|
label: "open-redirect-rust-absolute",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: false,
|
is_benign: false,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
@ -28,7 +30,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/rust/vuln.rs"],
|
fixture_paths: &["tests/dynamic_fixtures/open_redirect/rust/vuln.rs"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
label: "open-redirect-rust-benign",
|
label: "open-redirect-rust-benign",
|
||||||
}),
|
}),
|
||||||
|
|
@ -38,7 +42,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
bytes: b"/dashboard",
|
bytes: b"/dashboard",
|
||||||
label: "open-redirect-rust-benign",
|
label: "open-redirect-rust-benign",
|
||||||
oracle: Oracle::SinkProbe {
|
oracle: Oracle::SinkProbe {
|
||||||
predicates: &[ProbePredicate::RedirectHostNotIn { allowlist: ALLOWLIST }],
|
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||||
|
allowlist: ALLOWLIST,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
is_benign: true,
|
is_benign: true,
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
],
|
],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "path-traversal-benign" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "path-traversal-benign",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,12 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use super::{CapCorpus, CuratedPayload, Oracle};
|
||||||
use super::{
|
use super::{
|
||||||
cmdi, crypto, data_exfil, deserialize, fmt_string, header_injection, json_parse, ldap,
|
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,
|
open_redirect, path_trav, prototype_pollution, sqli, ssrf, ssti, unauthorized_id, xpath, xss,
|
||||||
xxe,
|
xxe,
|
||||||
};
|
};
|
||||||
use super::{CapCorpus, CuratedPayload, Oracle};
|
|
||||||
use crate::dynamic::oracle::ProbePredicate;
|
use crate::dynamic::oracle::ProbePredicate;
|
||||||
use crate::labels::Cap;
|
use crate::labels::Cap;
|
||||||
use crate::symbol::Lang;
|
use crate::symbol::Lang;
|
||||||
|
|
@ -93,7 +93,11 @@ const ENTRIES: &[(Cap, Lang, &[CuratedPayload])] = &[
|
||||||
(Cap::HTML_ESCAPE, Lang::Rust, xss::rust::PAYLOADS),
|
(Cap::HTML_ESCAPE, Lang::Rust, xss::rust::PAYLOADS),
|
||||||
(Cap::FMT_STRING, Lang::C, fmt_string::c::PAYLOADS),
|
(Cap::FMT_STRING, Lang::C, fmt_string::c::PAYLOADS),
|
||||||
(Cap::DESERIALIZE, Lang::Java, deserialize::java::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::Php, deserialize::php::PAYLOADS),
|
||||||
(Cap::DESERIALIZE, Lang::Ruby, deserialize::ruby::PAYLOADS),
|
(Cap::DESERIALIZE, Lang::Ruby, deserialize::ruby::PAYLOADS),
|
||||||
(Cap::SSTI, Lang::Python, ssti::python_jinja2::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::Python, xpath::python::PAYLOADS),
|
||||||
(Cap::XPATH_INJECTION, Lang::Php, xpath::php::PAYLOADS),
|
(Cap::XPATH_INJECTION, Lang::Php, xpath::php::PAYLOADS),
|
||||||
(Cap::XPATH_INJECTION, Lang::JavaScript, xpath::js::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,
|
||||||
(Cap::HEADER_INJECTION, Lang::Php, header_injection::php::PAYLOADS),
|
Lang::Java,
|
||||||
(Cap::HEADER_INJECTION, Lang::Ruby, header_injection::ruby::PAYLOADS),
|
header_injection::java::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::HEADER_INJECTION,
|
||||||
(Cap::OPEN_REDIRECT, Lang::Java, open_redirect::java::PAYLOADS),
|
Lang::Python,
|
||||||
(Cap::OPEN_REDIRECT, Lang::Python, open_redirect::python::PAYLOADS),
|
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::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::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,
|
Cap::PROTOTYPE_POLLUTION,
|
||||||
Lang::JavaScript,
|
Lang::JavaScript,
|
||||||
|
|
@ -142,16 +194,48 @@ const ENTRIES: &[(Cap, Lang, &[CuratedPayload])] = &[
|
||||||
(Cap::CRYPTO, Lang::Php, crypto::php::PAYLOADS),
|
(Cap::CRYPTO, Lang::Php, crypto::php::PAYLOADS),
|
||||||
(Cap::CRYPTO, Lang::Go, crypto::go::PAYLOADS),
|
(Cap::CRYPTO, Lang::Go, crypto::go::PAYLOADS),
|
||||||
(Cap::CRYPTO, Lang::Rust, crypto::rust::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::Python, json_parse::python::PAYLOADS),
|
||||||
(Cap::JSON_PARSE, Lang::Ruby, json_parse::ruby::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,
|
||||||
(Cap::UNAUTHORIZED_ID, Lang::Java, unauthorized_id::java::PAYLOADS),
|
Lang::Python,
|
||||||
(Cap::UNAUTHORIZED_ID, Lang::Php, unauthorized_id::php::PAYLOADS),
|
unauthorized_id::python::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::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::Python, data_exfil::python::PAYLOADS),
|
||||||
(Cap::DATA_EXFIL, Lang::Ruby, data_exfil::ruby::PAYLOADS),
|
(Cap::DATA_EXFIL, Lang::Ruby, data_exfil::ruby::PAYLOADS),
|
||||||
(Cap::DATA_EXFIL, Lang::Java, data_exfil::java::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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dynamic::corpus::{benign_payload_for, CORPUS_VERSION};
|
use crate::dynamic::corpus::{CORPUS_VERSION, benign_payload_for};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn supported_caps_have_payloads() {
|
fn supported_caps_have_payloads() {
|
||||||
|
|
@ -404,8 +488,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn phase_11_caps_pair_benign_controls_per_lang() {
|
fn phase_11_caps_pair_benign_controls_per_lang() {
|
||||||
let cases: &[(Cap, &[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,
|
Cap::UNAUTHORIZED_ID,
|
||||||
&[
|
&[
|
||||||
|
|
@ -434,10 +524,7 @@ mod tests {
|
||||||
for (cap, langs) in cases {
|
for (cap, langs) in cases {
|
||||||
for lang in *langs {
|
for lang in *langs {
|
||||||
let slice = payloads_for_lang(*cap, *lang);
|
let slice = payloads_for_lang(*cap, *lang);
|
||||||
assert!(
|
assert!(!slice.is_empty(), "({cap:?}, {lang:?}) must have payloads",);
|
||||||
!slice.is_empty(),
|
|
||||||
"({cap:?}, {lang:?}) must have payloads",
|
|
||||||
);
|
|
||||||
let vuln = slice
|
let vuln = slice
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| !p.is_benign)
|
.find(|p| !p.is_benign)
|
||||||
|
|
@ -596,7 +683,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn ssrf_has_oob_nonce_slot() {
|
fn ssrf_has_oob_nonce_slot() {
|
||||||
let has_oob = payloads_for(Cap::SSRF).iter().any(|p| p.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]
|
#[test]
|
||||||
|
|
@ -617,8 +707,7 @@ mod tests {
|
||||||
.find(|p| p.oob_nonce_slot)
|
.find(|p| p.oob_nonce_slot)
|
||||||
.expect("must have OOB payload");
|
.expect("must have OOB payload");
|
||||||
let url = "http://127.0.0.1:54321/mynonce";
|
let url = "http://127.0.0.1:54321/mynonce";
|
||||||
let bytes =
|
let bytes = materialise_bytes(p, Some(url)).expect("OOB payload materialises with URL");
|
||||||
materialise_bytes(p, Some(url)).expect("OOB payload materialises with URL");
|
|
||||||
assert_eq!(&*bytes, url.as_bytes());
|
assert_eq!(&*bytes, url.as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -637,7 +726,11 @@ mod tests {
|
||||||
(Cap::SQL_QUERY, "sqli-tautology", "sqli-benign"),
|
(Cap::SQL_QUERY, "sqli-tautology", "sqli-benign"),
|
||||||
(Cap::SQL_QUERY, "sqli-union-nyx", "sqli-benign"),
|
(Cap::SQL_QUERY, "sqli-union-nyx", "sqli-benign"),
|
||||||
(Cap::CODE_EXEC, "cmdi-echo-marker", "cmdi-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::SSRF, "ssrf-file-scheme", "ssrf-benign"),
|
||||||
(Cap::HTML_ESCAPE, "xss-script-marker", "xss-benign-text"),
|
(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])>> =
|
let mut entries_by_cap: HashMap<u32, Vec<(Lang, &'static [CuratedPayload])>> =
|
||||||
HashMap::new();
|
HashMap::new();
|
||||||
for &(cap, lang, slice) in CORPUS.entries {
|
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 {
|
for (cap_bits, langs) in &entries_by_cap {
|
||||||
if langs.len() != 1 {
|
if langs.len() != 1 {
|
||||||
|
|
@ -899,9 +995,8 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| !p.is_benign)
|
.find(|p| !p.is_benign)
|
||||||
.expect("each lang must have an LDAP vuln payload");
|
.expect("each lang must have an LDAP vuln payload");
|
||||||
let resolved =
|
let resolved = super::resolve_benign_control_lang(vuln, Cap::LDAP_INJECTION, lang)
|
||||||
super::resolve_benign_control_lang(vuln, Cap::LDAP_INJECTION, lang)
|
.expect("lang-aware benign control must resolve");
|
||||||
.expect("lang-aware benign control must resolve");
|
|
||||||
assert!(resolved.is_benign);
|
assert!(resolved.is_benign);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -941,9 +1036,8 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| !p.is_benign)
|
.find(|p| !p.is_benign)
|
||||||
.expect("each lang must have an XPath vuln payload");
|
.expect("each lang must have an XPath vuln payload");
|
||||||
let resolved =
|
let resolved = super::resolve_benign_control_lang(vuln, Cap::XPATH_INJECTION, lang)
|
||||||
super::resolve_benign_control_lang(vuln, Cap::XPATH_INJECTION, lang)
|
.expect("lang-aware benign control must resolve");
|
||||||
.expect("lang-aware benign control must resolve");
|
|
||||||
assert!(resolved.is_benign);
|
assert!(resolved.is_benign);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -992,9 +1086,8 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| !p.is_benign)
|
.find(|p| !p.is_benign)
|
||||||
.expect("each lang must have a HEADER_INJECTION vuln payload");
|
.expect("each lang must have a HEADER_INJECTION vuln payload");
|
||||||
let resolved =
|
let resolved = super::resolve_benign_control_lang(vuln, Cap::HEADER_INJECTION, lang)
|
||||||
super::resolve_benign_control_lang(vuln, Cap::HEADER_INJECTION, lang)
|
.expect("lang-aware benign control must resolve");
|
||||||
.expect("lang-aware benign control must resolve");
|
|
||||||
assert!(resolved.is_benign);
|
assert!(resolved.is_benign);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1036,9 +1129,8 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| !p.is_benign)
|
.find(|p| !p.is_benign)
|
||||||
.expect("each lang must have a PROTOTYPE_POLLUTION vuln payload");
|
.expect("each lang must have a PROTOTYPE_POLLUTION vuln payload");
|
||||||
let resolved =
|
let resolved = super::resolve_benign_control_lang(vuln, Cap::PROTOTYPE_POLLUTION, lang)
|
||||||
super::resolve_benign_control_lang(vuln, Cap::PROTOTYPE_POLLUTION, lang)
|
.expect("lang-aware benign control must resolve");
|
||||||
.expect("lang-aware benign control must resolve");
|
|
||||||
assert!(resolved.is_benign);
|
assert!(resolved.is_benign);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
|
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "sqli-benign" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "sqli-benign",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
@ -32,7 +34,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
|
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "sqli-benign" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "sqli-benign",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
// Benign control: ordinary value that should never produce the SQL marker.
|
// Benign control: ordinary value that should never produce the SQL marker.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
fixture_paths: &["tests/benchmark/corpus/rust/ssrf/ssrf_reqwest.rs"],
|
fixture_paths: &["tests/benchmark/corpus/rust/ssrf/ssrf_reqwest.rs"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "ssrf-benign" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "ssrf-benign",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/java_thymeleaf/vuln.java"],
|
||||||
"tests/dynamic_fixtures/ssti/java_thymeleaf/vuln.java",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
|
|
@ -39,9 +37,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/java_thymeleaf/benign.java"],
|
||||||
"tests/dynamic_fixtures/ssti/java_thymeleaf/benign.java",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/js_handlebars/vuln.js"],
|
||||||
"tests/dynamic_fixtures/ssti/js_handlebars/vuln.js",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
|
|
@ -45,9 +43,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/js_handlebars/benign.js"],
|
||||||
"tests/dynamic_fixtures/ssti/js_handlebars/benign.js",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/php_twig/vuln.php"],
|
||||||
"tests/dynamic_fixtures/ssti/php_twig/vuln.php",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
|
|
@ -39,9 +37,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/php_twig/benign.php"],
|
||||||
"tests/dynamic_fixtures/ssti/php_twig/benign.php",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/python_jinja2/vuln.py"],
|
||||||
"tests/dynamic_fixtures/ssti/python_jinja2/vuln.py",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
|
|
@ -46,9 +44,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/python_jinja2/benign.py"],
|
||||||
"tests/dynamic_fixtures/ssti/python_jinja2/benign.py",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/ruby_erb/vuln.rb"],
|
||||||
"tests/dynamic_fixtures/ssti/ruby_erb/vuln.rb",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||||
benign_control: Some(PayloadRef {
|
benign_control: Some(PayloadRef {
|
||||||
|
|
@ -39,9 +37,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 8,
|
since_corpus_version: 8,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/ssti/ruby_erb/benign.rb"],
|
||||||
"tests/dynamic_fixtures/ssti/ruby_erb/benign.rb",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
fixture_paths: &["tests/benchmark/corpus/rust/xss/axum_html/main.rs"],
|
fixture_paths: &["tests/benchmark/corpus/rust/xss/axum_html/main.rs"],
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: Some(PayloadRef { label: "xss-benign-text" }),
|
benign_control: Some(PayloadRef {
|
||||||
|
label: "xss-benign-text",
|
||||||
|
}),
|
||||||
no_benign_control_rationale: None,
|
no_benign_control_rationale: None,
|
||||||
},
|
},
|
||||||
CuratedPayload {
|
CuratedPayload {
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 15,
|
since_corpus_version: 15,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/go/vuln.go"],
|
||||||
"tests/dynamic_fixtures/xxe/go/vuln.go",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: true,
|
oob_nonce_slot: true,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
@ -57,9 +55,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/go/vuln.go"],
|
||||||
"tests/dynamic_fixtures/xxe/go/vuln.go",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||||
require_expanded: true,
|
require_expanded: true,
|
||||||
|
|
@ -82,9 +78,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/go/benign.go"],
|
||||||
"tests/dynamic_fixtures/xxe/go/benign.go",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 15,
|
since_corpus_version: 15,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Vuln.java"],
|
||||||
"tests/dynamic_fixtures/xxe/java/Vuln.java",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: true,
|
oob_nonce_slot: true,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
@ -59,9 +57,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Vuln.java"],
|
||||||
"tests/dynamic_fixtures/xxe/java/Vuln.java",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||||
require_expanded: true,
|
require_expanded: true,
|
||||||
|
|
@ -84,9 +80,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Benign.java"],
|
||||||
"tests/dynamic_fixtures/xxe/java/Benign.java",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 15,
|
since_corpus_version: 15,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/php/vuln.php"],
|
||||||
"tests/dynamic_fixtures/xxe/php/vuln.php",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: true,
|
oob_nonce_slot: true,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
@ -57,9 +55,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/php/vuln.php"],
|
||||||
"tests/dynamic_fixtures/xxe/php/vuln.php",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||||
require_expanded: true,
|
require_expanded: true,
|
||||||
|
|
@ -82,9 +78,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/php/benign.php"],
|
||||||
"tests/dynamic_fixtures/xxe/php/benign.php",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 15,
|
since_corpus_version: 15,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/python/vuln.py"],
|
||||||
"tests/dynamic_fixtures/xxe/python/vuln.py",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: true,
|
oob_nonce_slot: true,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
@ -68,9 +66,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/python/vuln.py"],
|
||||||
"tests/dynamic_fixtures/xxe/python/vuln.py",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||||
require_expanded: true,
|
require_expanded: true,
|
||||||
|
|
@ -93,9 +89,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/python/benign.py"],
|
||||||
"tests/dynamic_fixtures/xxe/python/benign.py",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 15,
|
since_corpus_version: 15,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/vuln.rb"],
|
||||||
"tests/dynamic_fixtures/xxe/ruby/vuln.rb",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: true,
|
oob_nonce_slot: true,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
@ -56,9 +54,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/vuln.rb"],
|
||||||
"tests/dynamic_fixtures/xxe/ruby/vuln.rb",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||||
require_expanded: true,
|
require_expanded: true,
|
||||||
|
|
@ -81,9 +77,7 @@ pub const PAYLOADS: &[CuratedPayload] = &[
|
||||||
provenance: PayloadProvenance::Curated,
|
provenance: PayloadProvenance::Curated,
|
||||||
since_corpus_version: 9,
|
since_corpus_version: 9,
|
||||||
deprecated_at_corpus_version: None,
|
deprecated_at_corpus_version: None,
|
||||||
fixture_paths: &[
|
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/benign.rb"],
|
||||||
"tests/dynamic_fixtures/xxe/ruby/benign.rb",
|
|
||||||
],
|
|
||||||
oob_nonce_slot: false,
|
oob_nonce_slot: false,
|
||||||
probe_predicates: &[],
|
probe_predicates: &[],
|
||||||
benign_control: None,
|
benign_control: None,
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rule_a_both_fire_is_collision() {
|
fn rule_a_both_fire_is_collision() {
|
||||||
assert_eq!(evaluate(true, true), DifferentialVerdict::OracleCollisionSuspected);
|
assert_eq!(
|
||||||
|
evaluate(true, true),
|
||||||
|
DifferentialVerdict::OracleCollisionSuspected
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -128,7 +131,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rule_d_only_benign_fires_is_reversed() {
|
fn rule_d_only_benign_fires_is_reversed() {
|
||||||
assert_eq!(evaluate(false, true), DifferentialVerdict::ReversedDifferential);
|
assert_eq!(
|
||||||
|
evaluate(false, true),
|
||||||
|
DifferentialVerdict::ReversedDifferential
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@
|
||||||
//! source file. The 10 MiB ceiling protects against runaway full-tree
|
//! source file. The 10 MiB ceiling protects against runaway full-tree
|
||||||
//! copy regressions called out in the Phase 09 acceptance.
|
//! 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::spec::HarnessSpec;
|
||||||
use crate::dynamic::toolchain::{self, ToolchainResolution};
|
use crate::dynamic::toolchain::{self, ToolchainResolution};
|
||||||
use crate::summary::GlobalSummaries;
|
use crate::summary::GlobalSummaries;
|
||||||
use crate::symbol::{FuncKey, Lang};
|
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::collections::HashSet;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
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::JavaScript | Lang::TypeScript => &["process.env.", "process.env["],
|
||||||
Lang::Java => &["System.getenv(", "getenv("],
|
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::Go => &["os.Getenv(", "os.LookupEnv("],
|
||||||
Lang::Php => &["getenv(", "$_ENV[", "$_SERVER["],
|
Lang::Php => &["getenv(", "$_ENV[", "$_SERVER["],
|
||||||
Lang::Ruby => &["ENV[", "ENV.fetch(", "ENV.fetch "],
|
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),
|
_ => extract_quoted_arg(tail),
|
||||||
};
|
};
|
||||||
if let Some(name) = name
|
if let Some(name) = name
|
||||||
&& !name.is_empty() && is_env_var_name(&name) && seen.insert(name.clone()) {
|
&& !name.is_empty()
|
||||||
out.push(name);
|
&& is_env_var_name(&name)
|
||||||
}
|
&& seen.insert(name.clone())
|
||||||
|
{
|
||||||
|
out.push(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
|
@ -199,7 +207,9 @@ fn extract_quoted_arg(s: &str) -> Option<String> {
|
||||||
if i >= bytes.len() {
|
if i >= bytes.len() {
|
||||||
return None;
|
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
|
/// 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
|
/// Returned in deterministic source-order so two runs against the same
|
||||||
/// inputs produce byte-identical env layouts.
|
/// inputs produce byte-identical env layouts.
|
||||||
pub fn build_secret_bag(
|
pub fn build_secret_bag(entry_file: &Path, lang: Lang, spec_hash: &str) -> Vec<(String, String)> {
|
||||||
entry_file: &Path,
|
|
||||||
lang: Lang,
|
|
||||||
spec_hash: &str,
|
|
||||||
) -> Vec<(String, String)> {
|
|
||||||
let mut out: Vec<(String, String)> = Vec::new();
|
let mut out: Vec<(String, String)> = Vec::new();
|
||||||
for name in extract_env_var_references(entry_file, lang) {
|
for name in extract_env_var_references(entry_file, lang) {
|
||||||
let val = derive_secret(spec_hash, &name);
|
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
|
/// user's pinned dependency set. Order is significant only insofar as
|
||||||
/// the first match wins for [`CapturedDeps::lockfile_origin`].
|
/// the first match wins for [`CapturedDeps::lockfile_origin`].
|
||||||
const MANIFEST_FILES_BY_LANG: &[(Lang, &[&str])] = &[
|
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::Python,
|
||||||
(Lang::TypeScript, &["package.json", "package-lock.json", "yarn.lock", "tsconfig.json"]),
|
&[
|
||||||
|
"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::Rust, &["Cargo.toml", "Cargo.lock"]),
|
||||||
(Lang::Go, &["go.mod", "go.sum"]),
|
(Lang::Go, &["go.mod", "go.sum"]),
|
||||||
(Lang::Java, &["pom.xml", "build.gradle", "build.gradle.kts"]),
|
(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 manifests = collect_manifest_files(spec.lang, project_root);
|
||||||
let lockfile = manifests.first().cloned();
|
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 {
|
CapturedDeps {
|
||||||
project_root: project_root.to_path_buf(),
|
project_root: project_root.to_path_buf(),
|
||||||
|
|
@ -575,13 +606,8 @@ pub fn stage_workdir_full(
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
running_bytes = copy_into_workdir(
|
running_bytes =
|
||||||
manifest,
|
copy_into_workdir(manifest, workdir, &rel, running_bytes, &mut staged_sources)?;
|
||||||
workdir,
|
|
||||||
&rel,
|
|
||||||
running_bytes,
|
|
||||||
&mut staged_sources,
|
|
||||||
)?;
|
|
||||||
if lockfile_in_workdir.is_none() {
|
if lockfile_in_workdir.is_none() {
|
||||||
lockfile_in_workdir = Some(workdir.join(&rel));
|
lockfile_in_workdir = Some(workdir.join(&rel));
|
||||||
}
|
}
|
||||||
|
|
@ -596,8 +622,7 @@ pub fn stage_workdir_full(
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => PathBuf::from(cfg.file_name().unwrap_or_default()),
|
None => PathBuf::from(cfg.file_name().unwrap_or_default()),
|
||||||
};
|
};
|
||||||
running_bytes =
|
running_bytes = copy_into_workdir(cfg, workdir, &rel, running_bytes, &mut staged_sources)?;
|
||||||
copy_into_workdir(cfg, workdir, &rel, running_bytes, &mut staged_sources)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 11 — Track D.4: populate the per-spec secret bag for every
|
// 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();
|
let size = metadata.len();
|
||||||
if running_bytes.saturating_add(size) > MAX_WORKDIR_BYTES {
|
if running_bytes.saturating_add(size) > MAX_WORKDIR_BYTES {
|
||||||
return Err(io::Error::other(
|
return Err(io::Error::other(format!(
|
||||||
format!(
|
"staged workdir would exceed {} bytes (next file `{}` = {} bytes)",
|
||||||
"staged workdir would exceed {} bytes (next file `{}` = {} bytes)",
|
MAX_WORKDIR_BYTES,
|
||||||
MAX_WORKDIR_BYTES,
|
rel.display(),
|
||||||
rel.display(),
|
size
|
||||||
size
|
)));
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let dest = workdir.join(rel);
|
let dest = workdir.join(rel);
|
||||||
if let Some(parent) = dest.parent() {
|
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> {
|
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_path = path
|
||||||
let abs_root = root.canonicalize().ok().unwrap_or_else(|| root.to_path_buf());
|
.canonicalize()
|
||||||
|
.ok()
|
||||||
|
.unwrap_or_else(|| path.to_path_buf());
|
||||||
|
let abs_root = root
|
||||||
|
.canonicalize()
|
||||||
|
.ok()
|
||||||
|
.unwrap_or_else(|| root.to_path_buf());
|
||||||
abs_path
|
abs_path
|
||||||
.strip_prefix(&abs_root)
|
.strip_prefix(&abs_root)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -729,9 +758,11 @@ fn collect_config_files(entry_file: &Path, project_root: &Path) -> Vec<PathBuf>
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
v.push(project_root.to_path_buf());
|
v.push(project_root.to_path_buf());
|
||||||
if let Some(parent) = entry_file.parent()
|
if let Some(parent) = entry_file.parent()
|
||||||
&& parent != project_root && parent.starts_with(project_root) {
|
&& parent != project_root
|
||||||
v.push(parent.to_path_buf());
|
&& parent.starts_with(project_root)
|
||||||
}
|
{
|
||||||
|
v.push(parent.to_path_buf());
|
||||||
|
}
|
||||||
v
|
v
|
||||||
};
|
};
|
||||||
for dir in &dirs {
|
for dir in &dirs {
|
||||||
|
|
@ -1253,7 +1284,11 @@ import './local-thing';
|
||||||
"from flask import Flask, request\nimport os\nimport requests\n",
|
"from flask import Flask, request\nimport os\nimport requests\n",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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 spec = fake_spec("app.py", Lang::Python);
|
||||||
let captured = capture_project_dependencies(root, &spec);
|
let captured = capture_project_dependencies(root, &spec);
|
||||||
assert!(captured.direct_deps.contains(&"flask".to_owned()));
|
assert!(captured.direct_deps.contains(&"flask".to_owned()));
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,10 @@ mod tests {
|
||||||
fn skips_when_chi_not_imported() {
|
fn skips_when_chi_not_imported() {
|
||||||
let src: &[u8] = b"package main\nfunc Show() {}\n";
|
let src: &[u8] = b"package main\nfunc Show() {}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(GoChiAdapter
|
assert!(
|
||||||
.detect(&summary("Show"), tree.root_node(), src)
|
GoChiAdapter
|
||||||
.is_none());
|
.detect(&summary("Show"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,10 @@ mod tests {
|
||||||
fn skips_when_echo_not_imported() {
|
fn skips_when_echo_not_imported() {
|
||||||
let src: &[u8] = b"package main\nfunc Show() {}\n";
|
let src: &[u8] = b"package main\nfunc Show() {}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(GoEchoAdapter
|
assert!(
|
||||||
.detect(&summary("Show"), tree.root_node(), src)
|
GoEchoAdapter
|
||||||
.is_none());
|
.detect(&summary("Show"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,10 @@ mod tests {
|
||||||
fn skips_when_fiber_not_imported() {
|
fn skips_when_fiber_not_imported() {
|
||||||
let src: &[u8] = b"package main\nfunc Show() {}\n";
|
let src: &[u8] = b"package main\nfunc Show() {}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(GoFiberAdapter
|
assert!(
|
||||||
.detect(&summary("Show"), tree.root_node(), src)
|
GoFiberAdapter
|
||||||
.is_none());
|
.detect(&summary("Show"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,9 +124,11 @@ mod tests {
|
||||||
fn skips_when_gin_not_imported() {
|
fn skips_when_gin_not_imported() {
|
||||||
let src: &[u8] = b"package main\nfunc Show(id string) {}\n";
|
let src: &[u8] = b"package main\nfunc Show(id string) {}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(GoGinAdapter
|
assert!(
|
||||||
.detect(&summary("Show"), tree.root_node(), src)
|
GoGinAdapter
|
||||||
.is_none());
|
.detect(&summary("Show"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -134,9 +136,11 @@ mod tests {
|
||||||
let src: &[u8] =
|
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";
|
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);
|
let tree = parse(src);
|
||||||
assert!(GoGinAdapter
|
assert!(
|
||||||
.detect(&summary("Helper"), tree.root_node(), src)
|
GoGinAdapter
|
||||||
.is_none());
|
.detect(&summary("Helper"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -83,22 +83,13 @@ fn contains_any(haystack: &[u8], needles: &[&[u8]]) -> bool {
|
||||||
|
|
||||||
/// Find a top-level `function_declaration` or a `method_declaration`
|
/// Find a top-level `function_declaration` or a `method_declaration`
|
||||||
/// whose name equals `target`. Returns the matching node.
|
/// whose name equals `target`. Returns the matching node.
|
||||||
pub fn find_go_function<'a>(
|
pub fn find_go_function<'a>(root: Node<'a>, bytes: &'a [u8], target: &str) -> Option<Node<'a>> {
|
||||||
root: Node<'a>,
|
|
||||||
bytes: &'a [u8],
|
|
||||||
target: &str,
|
|
||||||
) -> Option<Node<'a>> {
|
|
||||||
let mut hit: Option<Node<'a>> = None;
|
let mut hit: Option<Node<'a>> = None;
|
||||||
walk_go(root, bytes, target, &mut hit);
|
walk_go(root, bytes, target, &mut hit);
|
||||||
hit
|
hit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_go<'a>(
|
fn walk_go<'a>(node: Node<'a>, bytes: &'a [u8], target: &str, out: &mut Option<Node<'a>>) {
|
||||||
node: Node<'a>,
|
|
||||||
bytes: &'a [u8],
|
|
||||||
target: &str,
|
|
||||||
out: &mut Option<Node<'a>>,
|
|
||||||
) {
|
|
||||||
if out.is_some() {
|
if out.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -136,9 +127,10 @@ pub fn go_formal_names(func: Node<'_>, bytes: &[u8]) -> Vec<String> {
|
||||||
let mut pc = p.walk();
|
let mut pc = p.walk();
|
||||||
for c in p.named_children(&mut pc) {
|
for c in p.named_children(&mut pc) {
|
||||||
if c.kind() == "identifier"
|
if c.kind() == "identifier"
|
||||||
&& let Ok(text) = c.utf8_text(bytes) {
|
&& let Ok(text) = c.utf8_text(bytes)
|
||||||
out.push(text.to_owned());
|
{
|
||||||
}
|
out.push(text.to_owned());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
|
@ -428,8 +420,7 @@ mod tests {
|
||||||
let src: &[u8] =
|
let src: &[u8] =
|
||||||
b"package main\nfunc init() { r := gin.New(); r.GET(\"/u/:id\", Show) }\nfunc Show(c interface{}) {}\n";
|
b"package main\nfunc init() { r := gin.New(); r.GET(\"/u/:id\", Show) }\nfunc Show(c interface{}) {}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
let (method, path) =
|
let (method, path) = find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
|
||||||
find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
|
|
||||||
assert_eq!(method, HttpMethod::GET);
|
assert_eq!(method, HttpMethod::GET);
|
||||||
assert_eq!(path, "/u/:id");
|
assert_eq!(path, "/u/:id");
|
||||||
}
|
}
|
||||||
|
|
@ -439,8 +430,7 @@ mod tests {
|
||||||
let src: &[u8] =
|
let src: &[u8] =
|
||||||
b"package main\nfunc init() { r := chi.NewRouter(); r.Get(\"/x\", controllers.Show) }\n";
|
b"package main\nfunc init() { r := chi.NewRouter(); r.Get(\"/x\", controllers.Show) }\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
let (method, path) =
|
let (method, path) = find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
|
||||||
find_route_for_callee(tree.root_node(), src, "Show").expect("hit");
|
|
||||||
assert_eq!(method, HttpMethod::GET);
|
assert_eq!(method, HttpMethod::GET);
|
||||||
assert_eq!(path, "/x");
|
assert_eq!(path, "/x");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("Set")],
|
callees: vec![crate::summary::CalleeSite::bare("Set")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderGoAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderGoAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -146,9 +148,11 @@ mod tests {
|
||||||
name: "Add".into(),
|
name: "Add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderGoAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderGoAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -174,9 +178,11 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderGoAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderGoAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -195,9 +201,11 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderGoAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderGoAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -213,8 +221,10 @@ mod tests {
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderGoAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderGoAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,15 @@ const ADAPTER_NAME: &str = "header-java";
|
||||||
|
|
||||||
fn callee_is_header_setter(name: &str) -> bool {
|
fn callee_is_header_setter(name: &str) -> bool {
|
||||||
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
|
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 {
|
fn source_imports_servlet(file_bytes: &[u8]) -> bool {
|
||||||
|
|
@ -110,9 +118,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("setHeader")],
|
callees: vec![crate::summary::CalleeSite::bare("setHeader")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderJavaAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderJavaAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -123,9 +133,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderJavaAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderJavaAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -143,8 +155,10 @@ mod tests {
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderJavaAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderJavaAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,10 @@ const ADAPTER_NAME: &str = "header-js";
|
||||||
|
|
||||||
fn callee_is_header_setter(name: &str) -> bool {
|
fn callee_is_header_setter(name: &str) -> bool {
|
||||||
let last = name.rsplit_once('.').map(|(_, s)| s).unwrap_or(name);
|
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 {
|
fn source_uses_node_http(file_bytes: &[u8]) -> bool {
|
||||||
|
|
@ -115,9 +118,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("setHeader")],
|
callees: vec![crate::summary::CalleeSite::bare("setHeader")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderJsAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderJsAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -128,9 +133,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderJsAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderJsAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -146,8 +153,10 @@ mod tests {
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderJsAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderJsAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("header")],
|
callees: vec![crate::summary::CalleeSite::bare("header")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderPhpAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderPhpAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -119,15 +121,16 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderPhpAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderPhpAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skips_when_value_url_encoded() {
|
fn skips_when_value_url_encoded() {
|
||||||
let src: &[u8] =
|
let src: &[u8] = b"<?php\nfunction run($v) { header('Set-Cookie: ' . urlencode($v)); }\n";
|
||||||
b"<?php\nfunction run($v) { header('Set-Cookie: ' . urlencode($v)); }\n";
|
|
||||||
let tree = parse_php(src);
|
let tree = parse_php(src);
|
||||||
let summary = FuncSummary {
|
let summary = FuncSummary {
|
||||||
name: "run".into(),
|
name: "run".into(),
|
||||||
|
|
@ -137,8 +140,10 @@ mod tests {
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderPhpAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderPhpAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,10 @@ fn callee_is_header_setter(name: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
last,
|
last,
|
||||||
"__setitem__" | "set_header" | "setdefault" | "add_header" | "append"
|
"__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 {
|
fn source_imports_python_web(file_bytes: &[u8]) -> bool {
|
||||||
|
|
@ -116,9 +119,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("__setitem__")],
|
callees: vec![crate::summary::CalleeSite::bare("__setitem__")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderPythonAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderPythonAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -129,9 +134,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderPythonAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderPythonAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -149,8 +156,10 @@ mod tests {
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderPythonAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderPythonAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("set_header")],
|
callees: vec![crate::summary::CalleeSite::bare("set_header")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRubyAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRubyAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -145,9 +147,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRubyAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRubyAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -168,9 +172,11 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRubyAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRubyAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -188,9 +194,11 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRubyAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRubyAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -207,8 +215,10 @@ mod tests {
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRubyAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRubyAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("insert")],
|
callees: vec![crate::summary::CalleeSite::bare("insert")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRustAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRustAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -145,9 +147,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRustAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRustAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -173,9 +177,11 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRustAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRustAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -193,9 +199,11 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRustAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRustAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -215,8 +223,10 @@ mod tests {
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(HeaderRustAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
HeaderRustAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,10 @@ mod tests {
|
||||||
name: "run".into(),
|
name: "run".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(JavaDeserializeAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JavaDeserializeAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,7 @@ fn class_path_prefix(class: Node<'_>, bytes: &[u8]) -> Option<String> {
|
||||||
hit
|
hit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn method_verb_and_path(
|
fn method_verb_and_path(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
|
||||||
method: Node<'_>,
|
|
||||||
bytes: &[u8],
|
|
||||||
) -> Option<(HttpMethod, String)> {
|
|
||||||
let mut hit: Option<(HttpMethod, String)> = None;
|
let mut hit: Option<(HttpMethod, String)> = None;
|
||||||
iter_annotations(method, bytes, |ann, name| {
|
iter_annotations(method, bytes, |ann, name| {
|
||||||
if hit.is_some() {
|
if hit.is_some() {
|
||||||
|
|
@ -155,17 +152,21 @@ mod tests {
|
||||||
fn skips_non_micronaut_file() {
|
fn skips_non_micronaut_file() {
|
||||||
let src: &[u8] = b"@Controller\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
let src: &[u8] = b"@Controller\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(JavaMicronautAdapter
|
assert!(
|
||||||
.detect(&summary("x"), tree.root_node(), src)
|
JavaMicronautAdapter
|
||||||
.is_none());
|
.detect(&summary("x"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skips_method_without_micronaut_verb() {
|
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 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);
|
let tree = parse(src);
|
||||||
assert!(JavaMicronautAdapter
|
assert!(
|
||||||
.detect(&summary("helper"), tree.root_node(), src)
|
JavaMicronautAdapter
|
||||||
.is_none());
|
.detect(&summary("helper"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,17 +39,15 @@ fn class_path_prefix(class: Node<'_>, bytes: &[u8]) -> String {
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
iter_annotations(class, bytes, |ann, name| {
|
iter_annotations(class, bytes, |ann, name| {
|
||||||
if name == "Path"
|
if name == "Path"
|
||||||
&& let Some(p) = annotation_string_arg(ann, bytes) {
|
&& let Some(p) = annotation_string_arg(ann, bytes)
|
||||||
prefix = p;
|
{
|
||||||
}
|
prefix = p;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
fn method_verb_and_path(
|
fn method_verb_and_path(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
|
||||||
method: Node<'_>,
|
|
||||||
bytes: &[u8],
|
|
||||||
) -> Option<(HttpMethod, String)> {
|
|
||||||
let mut verb: Option<HttpMethod> = None;
|
let mut verb: Option<HttpMethod> = None;
|
||||||
let mut path = String::new();
|
let mut path = String::new();
|
||||||
iter_annotations(method, bytes, |ann, name| {
|
iter_annotations(method, bytes, |ann, name| {
|
||||||
|
|
@ -57,9 +55,10 @@ fn method_verb_and_path(
|
||||||
verb = Some(v);
|
verb = Some(v);
|
||||||
}
|
}
|
||||||
if name == "Path"
|
if name == "Path"
|
||||||
&& let Some(p) = annotation_string_arg(ann, bytes) {
|
&& let Some(p) = annotation_string_arg(ann, bytes)
|
||||||
path = p;
|
{
|
||||||
}
|
path = p;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Some((verb?, path))
|
Some((verb?, path))
|
||||||
}
|
}
|
||||||
|
|
@ -157,17 +156,21 @@ mod tests {
|
||||||
fn skips_non_quarkus_file() {
|
fn skips_non_quarkus_file() {
|
||||||
let src: &[u8] = b"@RestController\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
let src: &[u8] = b"@RestController\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(JavaQuarkusAdapter
|
assert!(
|
||||||
.detect(&summary("x"), tree.root_node(), src)
|
JavaQuarkusAdapter
|
||||||
.is_none());
|
.detect(&summary("x"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skips_method_without_verb_annotation() {
|
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 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);
|
let tree = parse(src);
|
||||||
assert!(JavaQuarkusAdapter
|
assert!(
|
||||||
.detect(&summary("helper"), tree.root_node(), src)
|
JavaQuarkusAdapter
|
||||||
.is_none());
|
.detect(&summary("helper"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,7 @@ pub fn source_imports_micronaut(bytes: &[u8]) -> bool {
|
||||||
pub fn source_imports_servlet(bytes: &[u8]) -> bool {
|
pub fn source_imports_servlet(bytes: &[u8]) -> bool {
|
||||||
let has_canonical = contains_any(
|
let has_canonical = contains_any(
|
||||||
bytes,
|
bytes,
|
||||||
&[
|
&[b"javax.servlet", b"jakarta.servlet", b"extends HttpServlet"],
|
||||||
b"javax.servlet",
|
|
||||||
b"jakarta.servlet",
|
|
||||||
b"extends HttpServlet",
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
if has_canonical {
|
if has_canonical {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -113,12 +109,7 @@ pub fn find_class_with_method<'a>(
|
||||||
hit
|
hit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk<'a>(
|
fn walk<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<(Node<'a>, Node<'a>)>) {
|
||||||
node: Node<'a>,
|
|
||||||
bytes: &[u8],
|
|
||||||
target: &str,
|
|
||||||
out: &mut Option<(Node<'a>, Node<'a>)>,
|
|
||||||
) {
|
|
||||||
if out.is_some() {
|
if out.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -126,21 +117,22 @@ fn walk<'a>(
|
||||||
&& let Some(body) = node
|
&& let Some(body) = node
|
||||||
.child_by_field_name("body")
|
.child_by_field_name("body")
|
||||||
.or_else(|| named_child_of_kind(node, "class_body"))
|
.or_else(|| named_child_of_kind(node, "class_body"))
|
||||||
{
|
{
|
||||||
let mut cur = body.walk();
|
let mut cur = body.walk();
|
||||||
for member in body.children(&mut cur) {
|
for member in body.children(&mut cur) {
|
||||||
if member.kind() != "method_declaration" {
|
if member.kind() != "method_declaration" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(name) = member
|
if let Some(name) = member
|
||||||
.child_by_field_name("name")
|
.child_by_field_name("name")
|
||||||
.and_then(|n| n.utf8_text(bytes).ok())
|
.and_then(|n| n.utf8_text(bytes).ok())
|
||||||
&& name == target {
|
&& name == target
|
||||||
*out = Some((node, member));
|
{
|
||||||
return;
|
*out = Some((node, member));
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let mut cur = node.walk();
|
let mut cur = node.walk();
|
||||||
for child in node.children(&mut cur) {
|
for child in node.children(&mut cur) {
|
||||||
walk(child, bytes, target, out);
|
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
|
// Try `value = "…"` / `path = "…"` first so the keyword form is
|
||||||
// not accidentally captured by the bare-string scan.
|
// not accidentally captured by the bare-string scan.
|
||||||
for key in ["value", "path"] {
|
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..];
|
let after = &raw[start..];
|
||||||
if let Some(open) = after.find('"') {
|
if let Some(open) = after.find('"') {
|
||||||
let rest = &after[open + 1..];
|
let rest = &after[open + 1..];
|
||||||
|
|
@ -300,16 +295,17 @@ pub fn extract_path_placeholders(path: &str) -> Vec<String> {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < bytes.len() {
|
while i < bytes.len() {
|
||||||
if bytes[i] == b'{'
|
if bytes[i] == b'{'
|
||||||
&& let Some(end) = bytes[i + 1..].iter().position(|&b| b == 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 inner = &path[i + 1..i + 1 + end];
|
||||||
let name = inner_name.strip_prefix('*').unwrap_or(inner_name);
|
let inner_name = inner.split(':').next().unwrap_or(inner).trim();
|
||||||
if !name.is_empty() && !out.iter().any(|n| n == name) {
|
let name = inner_name.strip_prefix('*').unwrap_or(inner_name);
|
||||||
out.push(name.to_owned());
|
if !name.is_empty() && !out.iter().any(|n| n == name) {
|
||||||
}
|
out.push(name.to_owned());
|
||||||
i += end + 2;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
i += end + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
|
@ -469,8 +465,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn class_extends_detects_servlet() {
|
fn class_extends_detects_servlet() {
|
||||||
let src: &[u8] =
|
let src: &[u8] = b"public class V extends HttpServlet { public void doGet() {} }\n";
|
||||||
b"public class V extends HttpServlet { public void doGet() {} }\n";
|
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
let (class, _) = find_class_with_method(tree.root_node(), src, "doGet").unwrap();
|
let (class, _) = find_class_with_method(tree.root_node(), src, "doGet").unwrap();
|
||||||
assert!(class_extends(class, src, "HttpServlet"));
|
assert!(class_extends(class, src, "HttpServlet"));
|
||||||
|
|
|
||||||
|
|
@ -126,10 +126,12 @@ mod tests {
|
||||||
let route = binding.route.unwrap();
|
let route = binding.route.unwrap();
|
||||||
assert_eq!(route.method, HttpMethod::GET);
|
assert_eq!(route.method, HttpMethod::GET);
|
||||||
assert_eq!(route.path, "/admin");
|
assert_eq!(route.path, "/admin");
|
||||||
assert!(binding
|
assert!(
|
||||||
.request_params
|
binding
|
||||||
.iter()
|
.request_params
|
||||||
.all(|p| matches!(p.source, ParamSource::Implicit)));
|
.iter()
|
||||||
|
.all(|p| matches!(p.source, ParamSource::Implicit))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -157,19 +159,24 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skips_when_method_name_is_not_a_servlet_verb() {
|
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);
|
let tree = parse(src);
|
||||||
assert!(JavaServletAdapter
|
assert!(
|
||||||
.detect(&summary("run"), tree.root_node(), src)
|
JavaServletAdapter
|
||||||
.is_none());
|
.detect(&summary("run"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skips_when_no_servlet_signature_markers() {
|
fn skips_when_no_servlet_signature_markers() {
|
||||||
let src: &[u8] = b"public class V {\n public void doGet(String x) {}\n}\n";
|
let src: &[u8] = b"public class V {\n public void doGet(String x) {}\n}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(JavaServletAdapter
|
assert!(
|
||||||
.detect(&summary("doGet"), tree.root_node(), src)
|
JavaServletAdapter
|
||||||
.is_none());
|
.detect(&summary("doGet"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,17 +49,15 @@ fn class_route_prefix(class: Node<'_>, bytes: &[u8]) -> String {
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
iter_annotations(class, bytes, |ann, name| {
|
iter_annotations(class, bytes, |ann, name| {
|
||||||
if name == "RequestMapping"
|
if name == "RequestMapping"
|
||||||
&& let Some(p) = annotation_string_arg(ann, bytes) {
|
&& let Some(p) = annotation_string_arg(ann, bytes)
|
||||||
prefix = p;
|
{
|
||||||
}
|
prefix = p;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
fn method_route(
|
fn method_route(method: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
|
||||||
method: Node<'_>,
|
|
||||||
bytes: &[u8],
|
|
||||||
) -> Option<(HttpMethod, String)> {
|
|
||||||
let mut hit: Option<(HttpMethod, String)> = None;
|
let mut hit: Option<(HttpMethod, String)> = None;
|
||||||
iter_annotations(method, bytes, |ann, name| {
|
iter_annotations(method, bytes, |ann, name| {
|
||||||
if hit.is_some() {
|
if hit.is_some() {
|
||||||
|
|
@ -100,7 +98,10 @@ impl FrameworkAdapter for JavaSpringAdapter {
|
||||||
// Quarkus / JAX-RS files often re-use `@Path` but the brief
|
// Quarkus / JAX-RS files often re-use `@Path` but the brief
|
||||||
// routes those through `java-quarkus`; skip when the file
|
// routes those through `java-quarkus`; skip when the file
|
||||||
// looks like Quarkus and is not also a Spring controller.
|
// 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;
|
return None;
|
||||||
}
|
}
|
||||||
let (class, method) = find_class_with_method(ast, file_bytes, &summary.name)?;
|
let (class, method) = find_class_with_method(ast, file_bytes, &summary.name)?;
|
||||||
|
|
@ -210,26 +211,32 @@ mod tests {
|
||||||
let src: &[u8] =
|
let src: &[u8] =
|
||||||
b"@RequestMapping(\"/api\")\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
b"@RequestMapping(\"/api\")\npublic class C {\n @GetMapping(\"/x\")\n public String x() { return \"\"; }\n}\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(JavaSpringAdapter
|
assert!(
|
||||||
.detect(&summary("x"), tree.root_node(), src)
|
JavaSpringAdapter
|
||||||
.is_none());
|
.detect(&summary("x"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skips_quarkus_file() {
|
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 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);
|
let tree = parse(src);
|
||||||
assert!(JavaSpringAdapter
|
assert!(
|
||||||
.detect(&summary("run"), tree.root_node(), src)
|
JavaSpringAdapter
|
||||||
.is_none());
|
.detect(&summary("run"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skips_plain_function() {
|
fn skips_plain_function() {
|
||||||
let src: &[u8] = b"public class C { public int add(int a, int b) { return a + b; } }\n";
|
let src: &[u8] = b"public class C { public int add(int a, int b) { return a + b; } }\n";
|
||||||
let tree = parse(src);
|
let tree = parse(src);
|
||||||
assert!(JavaSpringAdapter
|
assert!(
|
||||||
.detect(&summary("add"), tree.root_node(), src)
|
JavaSpringAdapter
|
||||||
.is_none());
|
.detect(&summary("add"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 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 tree = parse_java(src);
|
||||||
let summary = summary_for("run", &["body"], &[0]);
|
let summary = summary_for("run", &["body"], &[0]);
|
||||||
assert!(JavaThymeleafAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JavaThymeleafAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -137,9 +139,11 @@ mod tests {
|
||||||
name: "run".into(),
|
name: "run".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(JavaThymeleafAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JavaThymeleafAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 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 tree = parse_java(src);
|
||||||
let summary = summary_for("run", &["body"], &[0]);
|
let summary = summary_for("run", &["body"], &[0]);
|
||||||
assert!(JavaThymeleafAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JavaThymeleafAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 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 tree = parse_java(src);
|
||||||
let summary = summary_for("run", &["body"], &[]);
|
let summary = summary_for("run", &["body"], &[]);
|
||||||
assert!(JavaThymeleafAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JavaThymeleafAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,18 @@ mod tests {
|
||||||
let route = binding.route.as_ref().unwrap();
|
let route = binding.route.as_ref().unwrap();
|
||||||
assert_eq!(route.method, HttpMethod::GET);
|
assert_eq!(route.method, HttpMethod::GET);
|
||||||
assert_eq!(route.path, "/users/:id");
|
assert_eq!(route.path, "/users/:id");
|
||||||
assert!(binding.request_params.iter().any(|p| p.name == "req"
|
assert!(
|
||||||
&& matches!(p.source, ParamSource::Implicit)));
|
binding
|
||||||
assert!(binding.request_params.iter().any(|p| p.name == "res"
|
.request_params
|
||||||
&& matches!(p.source, ParamSource::Implicit)));
|
.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]
|
#[test]
|
||||||
|
|
@ -147,9 +155,11 @@ mod tests {
|
||||||
function handler(ctx) { ctx.body = 'ok'; }\n\
|
function handler(ctx) { ctx.body = 'ok'; }\n\
|
||||||
app.get('/x', handler);\n";
|
app.get('/x', handler);\n";
|
||||||
let tree = parse_js(src);
|
let tree = parse_js(src);
|
||||||
assert!(JsExpressAdapter
|
assert!(
|
||||||
.detect(&summary("handler"), tree.root_node(), src)
|
JsExpressAdapter
|
||||||
.is_none());
|
.detect(&summary("handler"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -159,8 +169,10 @@ mod tests {
|
||||||
function other(req, res) { res.send('x'); }\n\
|
function other(req, res) { res.send('x'); }\n\
|
||||||
app.get('/x', other);\n";
|
app.get('/x', other);\n";
|
||||||
let tree = parse_js(src);
|
let tree = parse_js(src);
|
||||||
assert!(JsExpressAdapter
|
assert!(
|
||||||
.detect(&summary("missing"), tree.root_node(), src)
|
JsExpressAdapter
|
||||||
.is_none());
|
.detect(&summary("missing"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,10 @@ mod tests {
|
||||||
function h(req, res) {}\n\
|
function h(req, res) {}\n\
|
||||||
app.get('/x', h);\n";
|
app.get('/x', h);\n";
|
||||||
let tree = parse_js(src);
|
let tree = parse_js(src);
|
||||||
assert!(JsFastifyAdapter
|
assert!(
|
||||||
.detect(&summary("h"), tree.root_node(), src)
|
JsFastifyAdapter
|
||||||
.is_none());
|
.detect(&summary("h"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 src: &[u8] = b"const Handlebars = require('handlebars');\nfunction render(body) {\n return Handlebars.compile(body)({});\n}\n";
|
||||||
let tree = parse_js(src);
|
let tree = parse_js(src);
|
||||||
let summary = summary_for("render", &["body"], &[0]);
|
let summary = summary_for("render", &["body"], &[0]);
|
||||||
assert!(JsHandlebarsAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JsHandlebarsAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -152,9 +154,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(JsHandlebarsAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JsHandlebarsAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -162,9 +166,11 @@ mod tests {
|
||||||
let src: &[u8] = b"// uses Handlebars\nfunction render(body) {\n return Handlebars.compile(\"static\")({});\n}\n";
|
let src: &[u8] = b"// uses Handlebars\nfunction render(body) {\n return Handlebars.compile(\"static\")({});\n}\n";
|
||||||
let tree = parse_js(src);
|
let tree = parse_js(src);
|
||||||
let summary = summary_for("render", &["body"], &[0]);
|
let summary = summary_for("render", &["body"], &[0]);
|
||||||
assert!(JsHandlebarsAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JsHandlebarsAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 src: &[u8] = b"const Handlebars = require('handlebars');\nfunction render(body) {\n return Handlebars.compile(body)({});\n}\n";
|
||||||
let tree = parse_js(src);
|
let tree = parse_js(src);
|
||||||
let summary = summary_for("render", &["body"], &[]);
|
let summary = summary_for("render", &["body"], &[]);
|
||||||
assert!(JsHandlebarsAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
JsHandlebarsAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,22 +39,13 @@ fn receiver_looks_like_koa(name: &str) -> bool {
|
||||||
/// that reference `target`. Returns the matched call node so callers
|
/// that reference `target`. Returns the matched call node so callers
|
||||||
/// can stamp a middleware-shape binding when the verb-based dispatch
|
/// can stamp a middleware-shape binding when the verb-based dispatch
|
||||||
/// fails to fire.
|
/// fails to fire.
|
||||||
fn find_use_middleware<'a>(
|
fn find_use_middleware<'a>(root: Node<'a>, bytes: &[u8], target: &str) -> Option<Node<'a>> {
|
||||||
root: Node<'a>,
|
|
||||||
bytes: &[u8],
|
|
||||||
target: &str,
|
|
||||||
) -> Option<Node<'a>> {
|
|
||||||
let mut hit: Option<Node<'a>> = None;
|
let mut hit: Option<Node<'a>> = None;
|
||||||
walk_for_use(root, bytes, target, &mut hit);
|
walk_for_use(root, bytes, target, &mut hit);
|
||||||
hit
|
hit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_for_use<'a>(
|
fn walk_for_use<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<Node<'a>>) {
|
||||||
node: Node<'a>,
|
|
||||||
bytes: &[u8],
|
|
||||||
target: &str,
|
|
||||||
out: &mut Option<Node<'a>>,
|
|
||||||
) {
|
|
||||||
if out.is_some() {
|
if out.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -108,8 +99,7 @@ impl FrameworkAdapter for JsKoaAdapter {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
bind_path_params(&formals, path)
|
bind_path_params(&formals, path)
|
||||||
};
|
};
|
||||||
if let Some((method, path)) =
|
if let Some((method, path)) = find_route_registration(ast, file_bytes, &summary.name, &recv)
|
||||||
find_route_registration(ast, file_bytes, &summary.name, &recv)
|
|
||||||
{
|
{
|
||||||
let request_params = formals_for(&path);
|
let request_params = formals_for(&path);
|
||||||
return Some(FrameworkBinding {
|
return Some(FrameworkBinding {
|
||||||
|
|
@ -180,8 +170,12 @@ mod tests {
|
||||||
let route = binding.route.as_ref().unwrap();
|
let route = binding.route.as_ref().unwrap();
|
||||||
assert_eq!(route.method, HttpMethod::GET);
|
assert_eq!(route.method, HttpMethod::GET);
|
||||||
assert_eq!(route.path, "/users/:id");
|
assert_eq!(route.path, "/users/:id");
|
||||||
assert!(binding.request_params.iter().any(|p| p.name == "ctx"
|
assert!(
|
||||||
&& matches!(p.source, ParamSource::Implicit)));
|
binding
|
||||||
|
.request_params
|
||||||
|
.iter()
|
||||||
|
.any(|p| p.name == "ctx" && matches!(p.source, ParamSource::Implicit))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -205,8 +199,10 @@ mod tests {
|
||||||
function h(req, res) {}\n\
|
function h(req, res) {}\n\
|
||||||
router.get('/x', h);\n";
|
router.get('/x', h);\n";
|
||||||
let tree = parse_js(src);
|
let tree = parse_js(src);
|
||||||
assert!(JsKoaAdapter
|
assert!(
|
||||||
.detect(&summary("h"), tree.root_node(), src)
|
JsKoaAdapter
|
||||||
.is_none());
|
.detect(&summary("h"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,7 @@ fn detect_nest(
|
||||||
if !source_imports_nest(file_bytes) {
|
if !source_imports_nest(file_bytes) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let (class_node, method_node) =
|
let (class_node, method_node) = find_class_method(ast, file_bytes, &summary.name)?;
|
||||||
find_class_method(ast, file_bytes, &summary.name)?;
|
|
||||||
let prefix = class_controller_prefix(class_node, file_bytes)?;
|
let prefix = class_controller_prefix(class_node, file_bytes)?;
|
||||||
let (method, sub_path) = method_verb_and_path(method_node, file_bytes)?;
|
let (method, sub_path) = method_verb_and_path(method_node, file_bytes)?;
|
||||||
let full_path = join_paths(&prefix, &sub_path);
|
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
|
/// with one of the Nest verb decorators (`@Get`, `@Post`, ...). The
|
||||||
/// `sub_path` is `""` when the decorator carries no argument
|
/// `sub_path` is `""` when the decorator carries no argument
|
||||||
/// (`@Get()` mounts at the controller prefix root).
|
/// (`@Get()` mounts at the controller prefix root).
|
||||||
fn method_verb_and_path(
|
fn method_verb_and_path(method_node: Node<'_>, bytes: &[u8]) -> Option<(HttpMethod, String)> {
|
||||||
method_node: Node<'_>,
|
|
||||||
bytes: &[u8],
|
|
||||||
) -> Option<(HttpMethod, String)> {
|
|
||||||
const VERBS: &[&str] = &[
|
const VERBS: &[&str] = &[
|
||||||
"Get", "Head", "Post", "Put", "Patch", "Delete", "Options", "All",
|
"Get", "Head", "Post", "Put", "Patch", "Delete", "Options", "All",
|
||||||
];
|
];
|
||||||
|
|
@ -461,8 +457,7 @@ mod tests {
|
||||||
|
|
||||||
fn parse_ts(src: &[u8]) -> tree_sitter::Tree {
|
fn parse_ts(src: &[u8]) -> tree_sitter::Tree {
|
||||||
let mut parser = tree_sitter::Parser::new();
|
let mut parser = tree_sitter::Parser::new();
|
||||||
let lang =
|
let lang = tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT);
|
||||||
tree_sitter::Language::from(tree_sitter_typescript::LANGUAGE_TYPESCRIPT);
|
|
||||||
parser.set_language(&lang).unwrap();
|
parser.set_language(&lang).unwrap();
|
||||||
parser.parse(src, None).unwrap()
|
parser.parse(src, None).unwrap()
|
||||||
}
|
}
|
||||||
|
|
@ -562,8 +557,10 @@ mod tests {
|
||||||
compute(x: number) { return x + 1; }\n\
|
compute(x: number) { return x + 1; }\n\
|
||||||
}\n";
|
}\n";
|
||||||
let tree = parse_ts(src);
|
let tree = parse_ts(src);
|
||||||
assert!(TsNestAdapter
|
assert!(
|
||||||
.detect(&summary("compute", "typescript"), tree.root_node(), src)
|
TsNestAdapter
|
||||||
.is_none());
|
.detect(&summary("compute", "typescript"), tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,22 +140,13 @@ pub fn strip_quotes(raw: &str) -> &str {
|
||||||
/// arrow function whose binding name equals `target`. Returns the
|
/// arrow function whose binding name equals `target`. Returns the
|
||||||
/// `formal_parameters` (or `formal_parameter` for shorthand arrows)
|
/// `formal_parameters` (or `formal_parameter` for shorthand arrows)
|
||||||
/// node so callers can enumerate parameter names.
|
/// node so callers can enumerate parameter names.
|
||||||
pub fn find_function_params<'a>(
|
pub fn find_function_params<'a>(root: Node<'a>, bytes: &[u8], target: &str) -> Option<Node<'a>> {
|
||||||
root: Node<'a>,
|
|
||||||
bytes: &[u8],
|
|
||||||
target: &str,
|
|
||||||
) -> Option<Node<'a>> {
|
|
||||||
let mut hit: Option<Node<'a>> = None;
|
let mut hit: Option<Node<'a>> = None;
|
||||||
walk_for_params(root, bytes, target, &mut hit);
|
walk_for_params(root, bytes, target, &mut hit);
|
||||||
hit
|
hit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_for_params<'a>(
|
fn walk_for_params<'a>(node: Node<'a>, bytes: &[u8], target: &str, out: &mut Option<Node<'a>>) {
|
||||||
node: Node<'a>,
|
|
||||||
bytes: &[u8],
|
|
||||||
target: &str,
|
|
||||||
out: &mut Option<Node<'a>>,
|
|
||||||
) {
|
|
||||||
if out.is_some() {
|
if out.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -311,15 +302,7 @@ pub fn bind_path_params(formals: &[String], path: &str) -> Vec<ParamBinding> {
|
||||||
fn is_implicit_formal(name: &str) -> bool {
|
fn is_implicit_formal(name: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
name,
|
name,
|
||||||
"req"
|
"req" | "request" | "res" | "response" | "reply" | "ctx" | "context" | "next" | "done"
|
||||||
| "request"
|
|
||||||
| "res"
|
|
||||||
| "response"
|
|
||||||
| "reply"
|
|
||||||
| "ctx"
|
|
||||||
| "context"
|
|
||||||
| "next"
|
|
||||||
| "done"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,9 +332,7 @@ pub fn extract_path_placeholders(path: &str) -> Vec<String> {
|
||||||
b':' => {
|
b':' => {
|
||||||
let start = i + 1;
|
let start = i + 1;
|
||||||
let mut j = start;
|
let mut j = start;
|
||||||
while j < bytes.len()
|
while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
|
||||||
&& (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_')
|
|
||||||
{
|
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
if j > start {
|
if j > start {
|
||||||
|
|
@ -456,10 +437,11 @@ fn walk_for_registration<'a>(
|
||||||
&& receiver_accepts(last_segment(object_text))
|
&& receiver_accepts(last_segment(object_text))
|
||||||
&& let Some(args) = node.child_by_field_name("arguments")
|
&& let Some(args) = node.child_by_field_name("arguments")
|
||||||
&& call_args_reference_target(args, bytes, target)
|
&& call_args_reference_target(args, bytes, target)
|
||||||
&& let Some(path) = first_string_arg(args, bytes) {
|
&& let Some(path) = first_string_arg(args, bytes)
|
||||||
*out = Some((method, path));
|
{
|
||||||
return;
|
*out = Some((method, path));
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
// Fastify options-object: `fastify.route({ method, url, handler })`.
|
// Fastify options-object: `fastify.route({ method, url, handler })`.
|
||||||
if prop_text == "route"
|
if prop_text == "route"
|
||||||
&& receiver_accepts(last_segment(object_text))
|
&& 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,
|
/// Parse a Fastify options-object call `fastify.route({ method, url,
|
||||||
/// handler })` returning the bound `(method, url)` when the
|
/// handler })` returning the bound `(method, url)` when the
|
||||||
/// `handler:` property references `target`.
|
/// `handler:` property references `target`.
|
||||||
fn parse_options_route(
|
fn parse_options_route(args: Node<'_>, bytes: &[u8], target: &str) -> Option<(HttpMethod, String)> {
|
||||||
args: Node<'_>,
|
|
||||||
bytes: &[u8],
|
|
||||||
target: &str,
|
|
||||||
) -> Option<(HttpMethod, String)> {
|
|
||||||
let mut cur = args.walk();
|
let mut cur = args.walk();
|
||||||
for c in args.named_children(&mut cur) {
|
for c in args.named_children(&mut cur) {
|
||||||
if c.kind() != "object" {
|
if c.kind() != "object" {
|
||||||
|
|
@ -525,7 +503,9 @@ fn parse_options_route(
|
||||||
if pair.kind() != "pair" {
|
if pair.kind() != "pair" {
|
||||||
continue;
|
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 {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,12 @@ fn source_imports_kafka(file_bytes: &[u8]) -> bool {
|
||||||
|
|
||||||
fn extract_topic(file_bytes: &[u8]) -> String {
|
fn extract_topic(file_bytes: &[u8]) -> String {
|
||||||
let text = std::str::from_utf8(file_bytes).unwrap_or("");
|
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) {
|
if let Some(idx) = text.find(needle) {
|
||||||
let after = &text[idx + needle.len()..];
|
let after = &text[idx + needle.len()..];
|
||||||
if let Some(end) = after.find('"') {
|
if let Some(end) = after.find('"') {
|
||||||
|
|
|
||||||
|
|
@ -129,8 +129,10 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(KafkaPythonAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
KafkaPythonAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,9 +173,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("ldap_search")],
|
callees: vec![crate::summary::CalleeSite::bare("ldap_search")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(LdapPhpAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
LdapPhpAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -186,9 +188,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(LdapPhpAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
LdapPhpAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -203,8 +207,10 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("ldap_search")],
|
callees: vec![crate::summary::CalleeSite::bare("ldap_search")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(LdapPhpAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
LdapPhpAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,9 +168,11 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("search_s")],
|
callees: vec![crate::summary::CalleeSite::bare("search_s")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(LdapPythonAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
LdapPythonAdapter
|
||||||
.is_some());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -181,9 +183,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(LdapPythonAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
LdapPythonAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -198,8 +202,10 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("search_s")],
|
callees: vec![crate::summary::CalleeSite::bare("search_s")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(LdapPythonAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
LdapPythonAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -205,9 +205,11 @@ mod tests {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(LdapSpringAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
LdapSpringAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -225,8 +227,10 @@ mod tests {
|
||||||
callees: vec![crate::summary::CalleeSite::bare("search")],
|
callees: vec![crate::summary::CalleeSite::bare("search")],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert!(LdapSpringAdapter
|
assert!(
|
||||||
.detect(&summary, tree.root_node(), src)
|
LdapSpringAdapter
|
||||||
.is_none());
|
.detect(&summary, tree.root_node(), src)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue