Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions src/rules/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,45 +28,63 @@ fn strip_comments(source: &str) -> String {

fn nginx_protocols_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"(?i)ssl_protocols\s+[^;]+;").unwrap())
RE.get_or_init(|| {
Regex::new(r"(?i)ssl_protocols\s+[^;]+;")
.expect("static nginx protocols regex should compile")
})
}

fn nginx_ciphers_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"(?i)ssl_ciphers\s+[^;]+;").unwrap())
RE.get_or_init(|| {
Regex::new(r"(?i)ssl_ciphers\s+[^;]+;").expect("static nginx ciphers regex should compile")
})
}

fn apache_protocol_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"(?i)SSLProtocol\s+.+").unwrap())
RE.get_or_init(|| {
Regex::new(r"(?i)SSLProtocol\s+.+").expect("static Apache protocol regex should compile")
})
}

fn apache_cipher_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"(?i)SSLCipherSuite\s+.+").unwrap())
RE.get_or_init(|| {
Regex::new(r"(?i)SSLCipherSuite\s+.+").expect("static Apache cipher regex should compile")
})
}

fn haproxy_options_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"(?i)ssl-default-bind-options\s+.+").unwrap())
RE.get_or_init(|| {
Regex::new(r"(?i)ssl-default-bind-options\s+.+")
.expect("static HAProxy options regex should compile")
})
}

fn haproxy_ciphers_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"(?i)ssl-default-bind-ciphers\s+.+").unwrap())
RE.get_or_init(|| {
Regex::new(r"(?i)ssl-default-bind-ciphers\s+.+")
.expect("static HAProxy ciphers regex should compile")
})
}

fn haproxy_ciphersuites_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"(?i)ssl-default-bind-ciphersuites\s+.+").unwrap())
RE.get_or_init(|| {
Regex::new(r"(?i)ssl-default-bind-ciphersuites\s+.+")
.expect("static HAProxy ciphersuites regex should compile")
})
}

fn dockerfile_insecure_env_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(
r#"(?im)^(?:ENV|ARG)\s+.*(?:NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*0|PYTHONHTTPSVERIFY\s*=\s*0|GIT_SSL_NO_VERIFY\s*=\s*(?:true|1)|CURL_CA_BUNDLE\s*=\s*(?:''|""|$)|REQUESTS_CA_BUNDLE\s*=\s*(?:''|""|$)|SSL_CERT_FILE\s*=\s*/dev/null)"#
).unwrap()
).expect("static Dockerfile insecure env regex should compile")
})
}

Expand All @@ -75,7 +93,7 @@ fn dockerfile_run_insecure_re() -> &'static Regex {
RE.get_or_init(|| {
Regex::new(
r#"(?im)^RUN\s+.*(?:NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*0|PYTHONHTTPSVERIFY\s*=\s*0|GIT_SSL_NO_VERIFY\s*=\s*(?:true|1)|curl\s+.*--insecure|curl\s+.*-k[\s|&;]|wget\s+.*--no-check-certificate)"#
).unwrap()
).expect("static Dockerfile insecure RUN regex should compile")
})
}

Expand Down
6 changes: 4 additions & 2 deletions src/rules/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,8 @@ impl_rule! {
fn check(_self, source, tree) {

let mut findings = Vec::new();
let secret_pattern = Regex::new(CSHARP_HARDCODED_SECRET_PATTERN).unwrap();
let secret_pattern = Regex::new(CSHARP_HARDCODED_SECRET_PATTERN)
.expect("static C# hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// variable_declarator: string password = "hardcoded"
Expand Down Expand Up @@ -656,7 +657,8 @@ impl_rule! {
fn check(_self, source, tree) {

let mut findings = Vec::new();
let cors_star = Regex::new(r#"WithOrigins\s*\(\s*"\*"\s*\)"#).unwrap();
let cors_star = Regex::new(r#"WithOrigins\s*\(\s*"\*"\s*\)"#)
.expect("static C# CORS regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
if node.kind() == "invocation_expression" {
Expand Down
10 changes: 6 additions & 4 deletions src/rules/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl_rule! {

let mut findings = Vec::new();
let sql_pattern =
Regex::new(r"(?i)(SELECT\s+.{0,40}\s+FROM|INSERT\s+INTO|UPDATE\s+.{0,40}\s+SET|DELETE\s+FROM|DROP\s+TABLE|ALTER\s+TABLE|CREATE\s+TABLE|EXEC\s+)").unwrap();
Regex::new(r"(?i)(SELECT\s+.{0,40}\s+FROM|INSERT\s+INTO|UPDATE\s+.{0,40}\s+SET|DELETE\s+FROM|DROP\s+TABLE|ALTER\s+TABLE|CREATE\s+TABLE|EXEC\s+)").expect("static Go SQL regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// Detect: "SELECT ... WHERE id = " + userId (binary_expression with +)
Expand Down Expand Up @@ -150,7 +150,7 @@ impl_rule! {
let mut findings = Vec::new();
let secret_pattern =
Regex::new(HARDCODED_SECRET_PATTERN)
.unwrap();
.expect("static hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// Short variable declaration: password := "hardcoded"
Expand Down Expand Up @@ -591,7 +591,8 @@ impl_rule! {
fn check(_self, source, _tree) {

let mut findings = Vec::new();
let pattern = Regex::new(r"InsecureSkipVerify\s*:\s*true").unwrap();
let pattern = Regex::new(r"InsecureSkipVerify\s*:\s*true")
.expect("static Go TLS regex should compile");

for matched in pattern.find_iter(source) {
findings.push(make_finding_from_offsets(
Expand Down Expand Up @@ -753,7 +754,8 @@ impl_rule! {
fn check(_self, source, tree) {

let mut findings = Vec::new();
let hardcoded_byte_re = Regex::new(r#"\[\]byte\(\s*"[^"]{4,}"\s*\)"#).unwrap();
let hardcoded_byte_re = Regex::new(r#"\[\]byte\(\s*"[^"]{4,}"\s*\)"#)
.expect("static Go JWT secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
if node.kind() != "call_expression" {
Expand Down
18 changes: 11 additions & 7 deletions src/rules/java.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ impl_rule! {

let mut findings = Vec::new();
let sql_methods =
Regex::new(r"^(executeQuery|execute|createQuery|createNativeQuery)$").unwrap();
Regex::new(r"^(executeQuery|execute|createQuery|createNativeQuery)$")
.expect("static Java SQL method regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
if node.kind() == "method_invocation" {
Expand Down Expand Up @@ -415,7 +416,8 @@ impl_rule! {

let mut findings = Vec::new();
let weak_algo =
Regex::new(r#"(?i)"(DES|DESede|RC2|RC4|Blowfish|MD5|SHA-?1|.*ECB.*)"#).unwrap();
Regex::new(r#"(?i)"(DES|DESede|RC2|RC4|Blowfish|MD5|SHA-?1|.*ECB.*)"#)
.expect("static Java weak crypto regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
if node.kind() == "method_invocation" {
Expand Down Expand Up @@ -606,7 +608,7 @@ impl_rule! {
let mut findings = Vec::new();
let secret_pattern =
Regex::new(HARDCODED_SECRET_PATTERN)
.unwrap();
.expect("static hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// variable_declarator: String password = "hardcoded";
Expand Down Expand Up @@ -687,9 +689,10 @@ impl_rule! {
let factory_pattern = Regex::new(
r"(DocumentBuilderFactory|SAXParserFactory|XMLInputFactory)\.newInstance\(\)",
)
.unwrap();
.expect("static Java XXE factory regex should compile");
let secure_pattern =
Regex::new(r"setFeature\s*\(|setProperty\s*\(|setAttribute\s*\(").unwrap();
Regex::new(r"setFeature\s*\(|setProperty\s*\(|setAttribute\s*\(")
.expect("static Java XXE hardening regex should compile");

// Simple heuristic: if a factory is created but no setFeature is called
// in the same file, flag it.
Expand Down Expand Up @@ -729,7 +732,7 @@ impl_rule! {
let csrf_pattern = Regex::new(
r"\.csrf\(\s*\)\s*\.\s*disable\(\s*\)|csrf\s*\([^)]*\.\s*disable\(\s*\)\s*\)",
)
.unwrap();
.expect("static Java CSRF regex should compile");

for matched in csrf_pattern.find_iter(source) {
findings.push(make_finding_from_offsets(
Expand Down Expand Up @@ -905,7 +908,8 @@ impl_rule! {
let mut findings = Vec::new();

// allowedOrigins("*")
let wildcard_pattern = Regex::new(r#"allowedOrigins\s*\(\s*"\*"\s*\)"#).unwrap();
let wildcard_pattern = Regex::new(r#"allowedOrigins\s*\(\s*"\*"\s*\)"#)
.expect("static Java CORS regex should compile");
for matched in wildcard_pattern.find_iter(source) {
findings.push(make_finding_from_offsets(
_self.id(),
Expand Down
12 changes: 7 additions & 5 deletions src/rules/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl_rule! {
let mut findings = Vec::new();
let secret_pattern =
Regex::new(HARDCODED_SECRET_PATTERN)
.unwrap();
.expect("static hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// variable_declarator: const password = "hardcoded"
Expand Down Expand Up @@ -153,7 +153,7 @@ impl_rule! {
// This avoids matching plain English like res.send('delete ' + name)
let sql_pattern = Regex::new(
r"(?i)(SELECT\s+.{0,40}\s+FROM|INSERT\s+INTO|UPDATE\s+.{0,40}\s+SET|DELETE\s+FROM|DROP\s+TABLE|ALTER\s+TABLE|CREATE\s+TABLE|EXEC\s+)"
).unwrap();
).expect("static JavaScript SQL pattern should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// Detect: query("SELECT * FROM users WHERE id = " + userId)
Expand Down Expand Up @@ -897,7 +897,8 @@ impl_rule! {
let mut findings = Vec::new();
// Patterns known to cause catastrophic backtracking: nested quantifiers
let dangerous_pattern =
Regex::new(r"(\([^)]*[+*][^)]*\)[+*]|\([^)]*\|[^)]*\)[+*])").unwrap();
Regex::new(r"(\([^)]*[+*][^)]*\)[+*]|\([^)]*\|[^)]*\)[+*])")
.expect("static ReDoS regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// Detect regex literals: /pattern/
Expand Down Expand Up @@ -1243,11 +1244,12 @@ impl_rule! {

let mut findings = Vec::new();
// Match user-controlled input objects
let user_input_re = Regex::new(r"^req\.(params|query|body|headers)(\b|\[|\.)").unwrap();
let user_input_re = Regex::new(r"^req\.(params|query|body|headers)(\b|\[|\.)")
.expect("static Express user-input regex should compile");
// Sanitization wrappers that neutralise XSS risk
let sanitize_re = Regex::new(
r"(?i)(escapeHtml|escape|sanitize|encode|encodeURIComponent|encodeURI|htmlEncode|xss|purify|DOMPurify|validator|parseInt|parseFloat|Number|String)\s*\("
).unwrap();
).expect("static JavaScript sanitizer regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// Detect: res.send(req.query.foo), res.write(req.body.bar)
Expand Down
3 changes: 1 addition & 2 deletions src/rules/javascript_taint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -762,8 +762,7 @@ fn scan_module_exports<'tree, F>(
}

// `module.exports.foo = function(...)` or `module.exports.foo = someFunc`
if left_text.starts_with("module.exports.") {
let export_name = left_text.strip_prefix("module.exports.").unwrap();
if let Some(export_name) = left_text.strip_prefix("module.exports.") {
if matches!(right.kind(), "arrow_function" | "function_expression") {
visit(export_name.to_string(), right);
} else if right.kind() == "identifier" {
Expand Down
15 changes: 9 additions & 6 deletions src/rules/kotlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl_rule! {
let mut findings = Vec::new();
let sql_methods =
Regex::new(r"^(executeQuery|execute|createQuery|createNativeQuery|rawQuery|execSQL)$")
.unwrap();
.expect("static Kotlin SQL method regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
if node.kind() == "call_expression" {
Expand Down Expand Up @@ -466,7 +466,8 @@ impl_rule! {

let mut findings = Vec::new();
let weak_algo =
Regex::new(r#"(?i)"(DES|DESede|RC2|RC4|Blowfish|MD5|SHA-?1|.*ECB.*)"#).unwrap();
Regex::new(r#"(?i)"(DES|DESede|RC2|RC4|Blowfish|MD5|SHA-?1|.*ECB.*)"#)
.expect("static Kotlin weak crypto regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
if node.kind() == "call_expression" {
Expand Down Expand Up @@ -523,7 +524,7 @@ impl_rule! {
let mut findings = Vec::new();
let secret_pattern =
Regex::new(HARDCODED_SECRET_PATTERN)
.unwrap();
.expect("static hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// property_declaration: val password = "hardcoded"
Expand Down Expand Up @@ -624,9 +625,10 @@ impl_rule! {
let factory_pattern = Regex::new(
r"(DocumentBuilderFactory|SAXParserFactory|XMLInputFactory)\.newInstance\(\)",
)
.unwrap();
.expect("static Kotlin XXE factory regex should compile");
let secure_pattern =
Regex::new(r"setFeature\s*\(|setProperty\s*\(|setAttribute\s*\(").unwrap();
Regex::new(r"setFeature\s*\(|setProperty\s*\(|setAttribute\s*\(")
.expect("static Kotlin XXE hardening regex should compile");

if factory_pattern.is_match(source) && !secure_pattern.is_match(source) {
for matched in factory_pattern.find_iter(source) {
Expand Down Expand Up @@ -663,7 +665,8 @@ impl_rule! {

// allowedOrigins("*") / addAllowedOrigin("*")
let wildcard_pattern =
Regex::new(r#"(allowedOrigins|addAllowedOrigin)\s*\(\s*"\*"\s*\)"#).unwrap();
Regex::new(r#"(allowedOrigins|addAllowedOrigin)\s*\(\s*"\*"\s*\)"#)
.expect("static Kotlin CORS regex should compile");
for matched in wildcard_pattern.find_iter(source) {
findings.push(make_finding_from_offsets(
_self.id(),
Expand Down
6 changes: 4 additions & 2 deletions src/rules/php.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ impl_rule! {
fn check(_self, source, tree) {

let mut findings = Vec::new();
let secret_pattern = Regex::new(HARDCODED_SECRET_PATTERN).unwrap();
let secret_pattern = Regex::new(HARDCODED_SECRET_PATTERN)
.expect("static hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// Detect: $password = "hardcoded";
Expand Down Expand Up @@ -498,7 +499,8 @@ impl_rule! {
fn check(_self, source, tree) {

let mut findings = Vec::new();
let e_modifier = Regex::new(r#"['"][^'"]*/.*/[a-z]*e[a-z]*['"]"#).unwrap();
let e_modifier = Regex::new(r#"['"][^'"]*/.*/[a-z]*e[a-z]*['"]"#)
.expect("static PHP preg_replace regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
if node.kind() == "function_call_expression" {
Expand Down
4 changes: 2 additions & 2 deletions src/rules/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl_rule! {
let mut findings = Vec::new();
let secret_pattern =
Regex::new(HARDCODED_SECRET_PATTERN)
.unwrap();
.expect("static hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// assignment: password = "hardcoded"
Expand Down Expand Up @@ -136,7 +136,7 @@ impl_rule! {

let mut findings = Vec::new();
let sql_pattern =
Regex::new(r"(?i)(SELECT\s+.{0,40}\s+FROM|INSERT\s+INTO|UPDATE\s+.{0,40}\s+SET|DELETE\s+FROM|DROP\s+TABLE|ALTER\s+TABLE|CREATE\s+TABLE|EXEC\s+)").unwrap();
Regex::new(r"(?i)(SELECT\s+.{0,40}\s+FROM|INSERT\s+INTO|UPDATE\s+.{0,40}\s+SET|DELETE\s+FROM|DROP\s+TABLE|ALTER\s+TABLE|CREATE\s+TABLE|EXEC\s+)").expect("static Python SQL regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// Detect f-strings with SQL: f"SELECT * FROM users WHERE id = {user_id}"
Expand Down
2 changes: 1 addition & 1 deletion src/rules/ruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ impl_rule! {
let mut findings = Vec::new();
let secret_pattern =
Regex::new(HARDCODED_SECRET_PATTERN)
.unwrap();
.expect("static hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// assignment: variable = "hardcoded"
Expand Down
8 changes: 5 additions & 3 deletions src/rules/rust_lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ impl_rule! {
fn check(_self, source, tree) {

let mut findings = Vec::new();
let sql_methods = Regex::new(r"(?i)\b(query|sql_query|execute|raw_sql)\b").unwrap();
let sql_methods = Regex::new(r"(?i)\b(query|sql_query|execute|raw_sql)\b")
.expect("static Rust SQL method regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
if node.kind() == "call_expression" {
Expand Down Expand Up @@ -186,7 +187,8 @@ impl_rule! {
fn check(_self, source, tree) {

let mut findings = Vec::new();
let weak_hash = Regex::new(r"\b(md5|sha1|Md5|Sha1|MD5|SHA1)\b").unwrap();
let weak_hash = Regex::new(r"\b(md5|sha1|Md5|Sha1|MD5|SHA1)\b")
.expect("static Rust weak hash regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// Detect use declarations: use md5::..., use sha1::...
Expand Down Expand Up @@ -338,7 +340,7 @@ impl_rule! {
let mut findings = Vec::new();
let secret_pattern =
Regex::new(HARDCODED_SECRET_PATTERN)
.unwrap();
.expect("static hardcoded secret regex should compile");

walk_tree(tree.root_node(), source, &mut |node, src| {
// let password = "hardcoded";
Expand Down
Loading