#![allow(unknown_lints)] #![allow(clippy::assert_is_empty)]
use guard_core_engine::detect;
use guard_core_engine::patterns::{self, ViewFilter};
fn scan(value: &str, context: &str) -> Vec<(String, String)> {
let normalized = detect::normalize_context(context);
let skip_filter = matches!(normalized, "unknown" | "request_body");
patterns::scan_view(value, ViewFilter::All, normalized, skip_filter, normalized)
.into_iter()
.map(|t| (t.category, t.pattern))
.collect()
}
const CASE_VARIANT_PROBES: &[(&str, &str)] = &[
("/sap", "/SAP"),
("/default.asp", "/DEFAULT.ASP"),
("/cgi-bin/a.cgi", "/CGI-BIN/a.cgi"),
("/geoserver/web", "/GEOSERVER/web"),
("/scadabr/x", "/SCADABR/x"),
("/magicinfo/x", "/MAGICINFO/x"),
("/hnap1", "/HNAP1"),
("/language", "/LANGUAGE"),
("/readme.txt", "/README.TXT"),
("/changelog", "/CHANGELOG"),
("/.openclaw/config", "/.OPENCLAW/config"),
("/inicio.html", "/INICIO.HTML"),
("/.cursor/config", "/.CURSOR/config"),
("/Dockerfile", "/DOCKERFILE"),
("/docker-compose.yml", "/DOCKER-COMPOSE.YML"),
("/Jenkinsfile", "/JENKINSFILE"),
("/secrets.json", "/SECRETS.JSON"),
("/credentials.yml", "/CREDENTIALS.YML"),
("/autodiscover/a.xml", "/AUTODISCOVER/a.xml"),
("/dns-query", "/DNS-QUERY"),
("/.git/HEAD", "/.GIT/HEAD"),
("/.env", "/.ENV"),
("/.env.local", "/.ENV.LOCAL"),
("/config.env", "/CONFIG.ENV"),
("/config.env", "/config.ENV"),
("/app.map", "/app.MAP"),
("/.git/config", "/.GIT/config"),
("/.svn/entries", "/.SVN/entries"),
("/wp-login.php", "/WP-LOGIN.php"),
("/wp-admin/setup-config.php", "/WP-ADMIN/setup-config.php"),
("/phpinfo.php", "/PHPINFO.PHP"),
("/config.php.bak", "/config.php.BAK"),
("/index.php.old", "/index.php.OLD"),
("/.htaccess", "/.HTACCESS"),
("/Thumbs.db", "/THUMBS.DB"),
("/.DS_Store", "/.DS_STORE"),
("/system/credentials", "/system/CREDENTIALS"),
("/actuator/env", "/ACTUATOR/env"),
];
#[test]
fn structural_probe_rows_fold_case_like_the_reference() {
for (lower, upper) in CASE_VARIANT_PROBES {
for context in ["url_path", "query_param"] {
let lower_hits = scan(lower, context);
let upper_hits = scan(upper, context);
assert!(
!lower_hits.is_empty(),
"baseline lowercase probe {lower:?} in {context} does not fire"
);
assert!(
!upper_hits.is_empty(),
"uppercase probe {upper:?} in {context} is under-detected; \
the reference row is case-insensitive (lower fires, upper does not)"
);
let lower_cats: Vec<&str> = lower_hits.iter().map(|(c, _)| c.as_str()).collect();
let upper_cats: Vec<&str> = upper_hits.iter().map(|(c, _)| c.as_str()).collect();
assert_eq!(
lower_cats, upper_cats,
"case variants disagree on categories in {context}"
);
}
}
}
#[test]
fn proto_pollution_assignment_folds_case() {
assert!(!scan("Object.prototype.x = 1", "request_body").is_empty());
assert!(!scan("object.prototype.x = 1", "request_body").is_empty());
assert!(!scan("OBJECT.PROTOTYPE.x = 1", "request_body").is_empty());
assert!(scan("object.prototype.x == 1", "request_body").is_empty());
}
#[test]
fn file_upload_marker_extensions_fold_case() {
assert!(!scan(r#"filename="shell.PHP.jpg""#, "request_body").is_empty());
assert!(!scan(r#"filename="x.PHTML""#, "request_body").is_empty());
assert!(!scan(r#"filename="a.JSP""#, "request_body").is_empty());
assert!(scan(r#"filename="report.pdf""#, "request_body").is_empty());
}
#[test]
fn deserialization_magic_prefixes_stay_case_sensitive() {
for (fires, silent) in [
(
"rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcA==",
"ro0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcA==",
),
(
"AAEAAAD/////AQAAAAAAAAAMAgAA",
"aaeaaad/////AQAAAAAAAAAMAgAA",
),
(
"gASVAAAAAAAAACMAAAAAAAAAAw==",
"gAsVAAAAAAAAACMAAAAAAAAAAw==",
),
("BAhJGlNpZ25hdHVyZQ==\n", "bahJGlNpZ25hdHVyZQ==\n"),
] {
assert!(
!scan(fires, "request_body").is_empty(),
"exact-case magic {fires:?} must fire"
);
assert!(
scan(silent, "request_body").is_empty(),
"wrong-case magic {silent:?} must not fire (reference (?-i:) row)"
);
}
}