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
29 changes: 27 additions & 2 deletions rules/java/feature-gate-check.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ languages = ["java"]
category = "feature_gate"
confidence = "medium"
description = "Feature flag or plan-based gating in Java"
# Accept string literals as well as field accesses and bare identifiers
# as the feature key; non-literal stubs need manual rego review.
query = """
(method_invocation
name: (identifier) @method_name
arguments: (argument_list
(string_literal
(string_fragment) @feature))
[
(string_literal (string_fragment) @feature)
(field_access) @feature
(identifier) @feature
])
) @match
"""

Expand Down Expand Up @@ -46,6 +51,26 @@ public class Service {
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
public void check(String featureKey) {
if (featureFlags.hasFeature(featureKey)) { enable(); }
}
}
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
public void check() {
if (featureFlags.isFeatureEnabled(Features.BETA_DASHBOARD)) { enable(); }
}
}
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
Expand Down
31 changes: 29 additions & 2 deletions rules/java/has-role-call.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ languages = ["java"]
category = "rbac"
confidence = "high"
description = "Spring Security hasRole/hasAuthority method call"
# Accept string literals as well as field accesses (e.g. `Role.ADMIN`) and
# bare identifiers (e.g. `role`) as the role argument. The rego stub treats
# whatever text is captured as the role name; for non-literal expressions
# the stub will need manual review, but the finding is still recorded.
query = """
(method_invocation
name: (identifier) @method_name
arguments: (argument_list
(string_literal
(string_fragment) @role_value))
[
(string_literal (string_fragment) @role_value)
(field_access) @role_value
(identifier) @role_value
])
) @match
"""

Expand Down Expand Up @@ -68,6 +75,26 @@ public class SecurityConfig {
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
public void check(Account acct) {
if (!acct.hasRole(Role.ADMIN)) { throw new ForbiddenException(); }
}
}
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
public void check(Account acct, String role) {
if (acct.hasRole(role)) { allow(); }
}
}
"""
expect_match = true

[[rule.tests]]
input = """
public class SecurityConfig {
Expand Down
29 changes: 27 additions & 2 deletions rules/java/is-user-in-role.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ languages = ["java"]
category = "rbac"
confidence = "high"
description = "Servlet API isUserInRole() call"
# Accept string literals as well as field accesses and bare identifiers
# as the role argument; non-literal stubs need manual rego review.
query = """
(method_invocation
name: (identifier) @method_name
arguments: (argument_list
(string_literal
(string_fragment) @role_value))
[
(string_literal (string_fragment) @role_value)
(field_access) @role_value
(identifier) @role_value
])
) @match
"""

Expand All @@ -35,6 +40,26 @@ public class MyServlet {
"""
expect_match = true

[[rule.tests]]
input = """
public class MyServlet {
public void doGet() {
if (request.isUserInRole(Role.ADMIN)) { doSomething(); }
}
}
"""
expect_match = true

Comment thread
coderabbitai[bot] marked this conversation as resolved.
[[rule.tests]]
input = """
public class MyServlet {
public void doGet(String roleVar) {
if (request.isUserInRole(roleVar)) { doSomething(); }
}
}
"""
expect_match = true

[[rule.tests]]
input = """
public class MyServlet {
Expand Down
29 changes: 27 additions & 2 deletions rules/java/role-equals-check.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ languages = ["java"]
category = "rbac"
confidence = "medium"
description = "Role comparison via .equals() or string equality"
# Accept string literals as well as field accesses (e.g. `Role.ADMIN`) and
# bare identifiers (e.g. `roleVar`) as the comparison argument.
query = """
(method_invocation
object: (method_invocation
name: (identifier) @getter)
name: (identifier) @method_name
arguments: (argument_list
(string_literal
(string_fragment) @role_value))
[
(string_literal (string_fragment) @role_value)
(field_access) @role_value
(identifier) @role_value
])
) @match
"""

Expand Down Expand Up @@ -50,6 +55,26 @@ public class Service {
"""
expect_match = false

[[rule.tests]]
input = """
public class Service {
public void check() {
if (user.getRole().equals(Role.ADMIN)) { doSomething(); }
}
}
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
public void check(String roleName) {
if (user.getRole().equals(roleName)) { doSomething(); }
}
}
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
Expand Down
29 changes: 27 additions & 2 deletions rules/java/shiro-is-permitted.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ languages = ["java"]
category = "rbac"
confidence = "high"
description = "Apache Shiro Subject.isPermitted() call"
# Accept string literals as well as field accesses and bare identifiers
# as the permission argument; non-literal stubs need manual rego review.
query = """
(method_invocation
name: (identifier) @method_name
arguments: (argument_list
(string_literal
(string_fragment) @perm_value))
[
(string_literal (string_fragment) @perm_value)
(field_access) @perm_value
(identifier) @perm_value
])
) @match
"""

Expand Down Expand Up @@ -45,6 +50,26 @@ public class Service {
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
public void delete() {
if (subject.isPermitted(Permissions.USER_DELETE)) { doIt(); }
}
}
"""
expect_match = true

Comment thread
coderabbitai[bot] marked this conversation as resolved.
[[rule.tests]]
input = """
public class Service {
public void delete(String permVar) {
if (subject.isPermitted(permVar)) { doIt(); }
}
}
"""
expect_match = true

[[rule.tests]]
input = """
public class Service {
Expand Down
120 changes: 120 additions & 0 deletions src/scanner/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,30 @@ public class Ctrl {
assert!(!findings.is_empty(), "should match isUserInRole");
}

#[test]
fn java_is_user_in_role_non_literal_arg_matches() {
let findings = parse_and_match_java(
r#"request.isUserInRole(Role.ADMIN);"#,
include_str!("../../rules/java/is-user-in-role.toml"),
);
assert!(
!findings.is_empty(),
"should match isUserInRole with field-access arg"
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#[test]
fn java_is_user_in_role_identifier_arg_matches() {
let findings = parse_and_match_java(
r#"request.isUserInRole(roleVar);"#,
include_str!("../../rules/java/is-user-in-role.toml"),
);
assert!(
!findings.is_empty(),
"should match isUserInRole with identifier arg (variable role name)"
);
}

#[test]
fn java_has_role_call_matches() {
let findings = parse_and_match_java(
Expand All @@ -387,6 +411,30 @@ public class Ctrl {
assert!(!findings.is_empty(), "should match hasRole");
}

#[test]
fn java_has_role_call_field_access_arg_matches() {
let findings = parse_and_match_java(
r#"if (!acct.hasRole(Role.ADMIN)) { throw new ForbiddenException(); }"#,
include_str!("../../rules/java/has-role-call.toml"),
);
assert!(
!findings.is_empty(),
"should match hasRole with field-access arg (e.g., Role.ADMIN)"
);
}

#[test]
fn java_has_role_call_identifier_arg_matches() {
let findings = parse_and_match_java(
r#"if (acct.hasRole(roleName)) { allow(); }"#,
include_str!("../../rules/java/has-role-call.toml"),
);
assert!(
!findings.is_empty(),
"should match hasRole with identifier arg (variable role name)"
);
}

#[test]
fn java_shiro_requires_permissions_matches() {
let findings = parse_and_match_java(
Expand All @@ -410,6 +458,30 @@ public class Ctrl {
assert!(!findings.is_empty(), "should match isPermitted");
}

#[test]
fn java_shiro_is_permitted_non_literal_arg_matches() {
let findings = parse_and_match_java(
r#"subject.isPermitted(Permissions.USER_DELETE);"#,
include_str!("../../rules/java/shiro-is-permitted.toml"),
);
assert!(
!findings.is_empty(),
"should match isPermitted with field-access arg"
);
}

#[test]
fn java_shiro_is_permitted_identifier_arg_matches() {
let findings = parse_and_match_java(
r#"subject.isPermitted(permVar);"#,
include_str!("../../rules/java/shiro-is-permitted.toml"),
);
assert!(
!findings.is_empty(),
"should match isPermitted with identifier arg (variable permission)"
);
}

#[test]
fn java_marker_annotation_matches() {
let findings = parse_and_match_java(
Expand Down Expand Up @@ -450,6 +522,30 @@ public class Ctrl {
assert!(!findings.is_empty(), "should match getRole().equals()");
}

#[test]
fn java_role_equals_check_field_access_arg_matches() {
let findings = parse_and_match_java(
r#"user.getRole().equals(Role.ADMIN);"#,
include_str!("../../rules/java/role-equals-check.toml"),
);
assert!(
!findings.is_empty(),
"should match getRole().equals(field-access)"
);
}

#[test]
fn java_role_equals_check_identifier_arg_matches() {
let findings = parse_and_match_java(
r#"user.getRole().equals(roleVar);"#,
include_str!("../../rules/java/role-equals-check.toml"),
);
assert!(
!findings.is_empty(),
"should match getRole().equals(identifier)"
);
}

#[test]
fn java_role_equals_check_no_false_positive() {
let findings = parse_and_match_java(
Expand Down Expand Up @@ -486,6 +582,30 @@ public class Ctrl {
assert!(!findings.is_empty(), "should match hasFeature()");
}

#[test]
fn java_feature_gate_non_literal_arg_matches() {
let findings = parse_and_match_java(
r#"featureFlags.hasFeature(Features.BETA_DASHBOARD);"#,
include_str!("../../rules/java/feature-gate-check.toml"),
);
assert!(
!findings.is_empty(),
"should match hasFeature with field-access arg"
);
}

#[test]
fn java_feature_gate_identifier_arg_matches() {
let findings = parse_and_match_java(
r#"featureFlags.hasFeature(featureKey);"#,
include_str!("../../rules/java/feature-gate-check.toml"),
);
assert!(
!findings.is_empty(),
"should match hasFeature with identifier arg (variable feature key)"
);
}

#[test]
fn dedup_removes_duplicates() {
let f = Finding {
Expand Down
Loading