Skip to content

feat(rules/java): heuristic detection of custom authz service calls#13

Merged
boorad merged 1 commit into
mainfrom
feat/java-custom-authz-call
Apr 28, 2026
Merged

feat(rules/java): heuristic detection of custom authz service calls#13
boorad merged 1 commit into
mainfrom
feat/java-custom-authz-call

Conversation

@boorad
Copy link
Copy Markdown
Contributor

@boorad boorad commented Apr 28, 2026

Summary

Application-specific authorization layers (e.g. PrivilegeService, AccessChecker) expose boolean predicate methods like isOrgAdmin, isCandidateManager, isAdminForAccount, hasFullOrganizationAccess. These don't match Spring/Shiro/Jakarta names so they were silently missed — even when they form the entire authorization layer of an app, which was the user's main complaint in #6.

Approach

Add a heuristic rule (java-custom-authz-call, confidence=medium, category=custom) that fires on is* / has* / can* method invocations whose names contain a role/access keyword as a complete camelCase sub-word:

Admin, Manager, Member, Recruiter, Viewer, Editor, Owner,
Access, Permission, Privilege, Role, Authority, Authorized

The two-predicate trick: name: (identifier) is captured twice under different aliases so we can run an inclusive match regex and an exclusive not_match regex on the same node — needed because the current TOML schema is HashMap<capture_name, predicate> and regex doesn't support negative lookahead.

Exclusion list filters out methods already handled by dedicated rules to prevent duplicate findings:

  • hasRole, hasAnyRole, hasAuthority, hasAnyAuthority (handled by java-has-role-call)
  • isUserInRole (handled by java-is-user-in-role)

Why a sub-word boundary

A naive substring match would over-fire: isIncludeAdmins() (a DTO getter) contains "Admin" as a substring of "Admins". The regex requires the keyword to be followed by a capital letter ([A-Z]…, i.e. a new camelCase word) or end-of-name, so:

method matches? note
isOrgAdmin Admin at end
isAdminForAccount Admin → For (camelCase boundary)
isVBenchAdmin acronym prefix tolerated by lazy \w*?
hasFullOrganizationAccess Access at end
isIncludeAdmins "Admin" → "s" (lowercase) — substring, not sub-word
isArchived, isValid, isEmpty, hasLength, hasRelationship no keyword

Verification on the issue tarballs

Tarball Before After New findings
vbench-server-src.tar.gz (#6) 3 115 112 PrivilegeService call sites
endorsed_resume_src.tar.gz (#7) 11* 16 12 isOwner(...) ownership checks

* with #12 (PR-2) merged

I hand-audited a sample of the new vbench findings — they're all PRIV_SERVICE.isOrgAdmin/isOrgManager/isCandidateViewer/isAdminForAccount, hasFullOrganizationAccess, hasOrgRole, etc. The endorsed findings are all isOwner(accountId, …). Zero false positives surfaced in either codebase.

Tests

  • 6 new cargo test cases in src/scanner/matcher.rs:
    • …matches_role_suffix
    • …matches_keyword_in_middle
    • …matches_has_access
    • …no_substring_false_positive (the isIncludeAdmins case)
    • …no_state_check_false_positive
    • …excludes_known_framework_methods (asserts hasRole, hasAuthority, isUserInRole are silently filtered)
  • 9 new [[rule.tests]] toml fixtures (run via zift rules test)
$ cargo test
test result: ok. 95 passed; 0 failed; 0 ignored
$ cargo run --quiet -- rules validate
All 33 rules validated successfully.

Closes #6.

Out of scope (follow-ups)

  • A class-level rule that flags *PrivilegeService / *PermissionManager style classes themselves (the definition, not the call sites). Useful as a "this is your custom authz module" signal.
  • Surfacing the same heuristic in TypeScript/JavaScript and Python.
  • Resolving the Role.ADMIN constant to its string value when generating Rego stubs (would benefit from --deep LLM-assisted mode).

Test plan

Summary by CodeRabbit

  • New Features
    • Added a Java analysis rule that detects custom authorization-style method calls (methods starting with "is", "has", or "can" containing role/access/permission keywords) while excluding common framework authorization patterns.
  • Tests
    • Added unit tests verifying positive detections for app-specific patterns and negative cases for false positives and framework APIs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

Introduces a new embedded Java heuristic rule java-custom-authz-call that detects custom authorization method invocations by name patterns (is|has|can with role/access keywords) while excluding common framework/security authorization APIs; includes rule TOML and unit tests, and registers the rule in the embedded rules list.

Changes

Cohort / File(s) Summary
Rule Definition
rules/java/custom-authz-call.toml
Adds a new TOML rule java-custom-authz-call with predicates to match authorization-like method names and exclude known framework patterns; includes multiple positive and negative test cases.
Embedded Rules Registry
src/rules/embedded.rs
Registers the new TOML rule in EMBEDDED_RULES so it is parsed and loaded as an embedded rule.
Unit Tests / Matcher
src/scanner/matcher.rs
Adds tests exercising the new rule: positive matches for app-specific auth method names, negative tests for substring/state checks and framework APIs, and asserts finding category where applicable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through code to sniff and find,

is/has/can with roles combined.
I skip the framework, chase the bespoke,
a tiny rule that gives a poke.
🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: addition of a new heuristic rule for detecting custom authorization service calls in Java code.
Linked Issues check ✅ Passed The PR successfully implements detection of custom authorization method invocations, directly addressing issue #6's objective to surface application-specific authorization logic like PrivilegeService calls that were previously undetected.
Out of Scope Changes check ✅ Passed All changes are within scope: new rule definition, embedded rules registry update, and corresponding unit tests. No unrelated modifications to existing functionality.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • PR-2: Request failed with status code 401

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@amazon-q-developer amazon-q-developer Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation successfully adds heuristic detection for custom authorization service calls in Java codebases. The regex pattern correctly implements camelCase boundary detection to avoid false positives, the exclusion list prevents duplicate findings with framework-specific rules, and the comprehensive test coverage validates all scenarios described in the PR.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
rules/java/custom-authz-call.toml (1)

1-35: Add fixtures for can* and hasAny* variants to lock declared behavior.

The rule definition supports can* matches and excludes hasAnyRole/hasAnyAuthority, but those specific variants are not fixture-tested yet. Adding them would reduce regression risk.

Suggested fixture additions
 [[rule.tests]]
 input = """
 public class ReportController {
@@
 """
 expect_match = true
 
+[[rule.tests]]
+input = """
+public class UserController {
+    public void update(Account actor, Org org) {
+        if (!privService.canManageUsers(actor, org)) {
+            throw new ForbiddenException();
+        }
+    }
+}
+"""
+expect_match = true
+
 [[rule.tests]]
 input = """
 public class SecurityConfig {
@@
 """
 expect_match = false
 
+[[rule.tests]]
+input = """
+public class SecurityConfig {
+    public void configure(HttpSecurity http) {
+        http.authorizeRequests().antMatchers("/admin/**").hasAnyRole("ADMIN", "MANAGER");
+    }
+}
+"""
+expect_match = false
+
+[[rule.tests]]
+input = """
+public class SecurityConfig {
+    public void configure(HttpSecurity http) {
+        http.authorizeRequests().antMatchers("/api/**").hasAnyAuthority("SCOPE_read", "SCOPE_write");
+    }
+}
+"""
+expect_match = false

Also applies to: 36-136

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rules/java/custom-authz-call.toml` around lines 1 - 35, Add unit fixtures
that cover the missing "can*" matches and "hasAny*" exclusions to lock the
declared behavior for the rule id "java-custom-authz-call": create positive
fixtures with method names starting with "can" that include role/access keywords
to ensure predicates.method_name matches them, and add negative fixtures with
"hasAnyRole"/"hasAnyAuthority" (and other hasAny* variants) to ensure
method_name_excl's not_match continues to exclude them; verify tests exercise
both predicates.method_name and method_name_excl to prevent regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@rules/java/custom-authz-call.toml`:
- Around line 1-35: Add unit fixtures that cover the missing "can*" matches and
"hasAny*" exclusions to lock the declared behavior for the rule id
"java-custom-authz-call": create positive fixtures with method names starting
with "can" that include role/access keywords to ensure predicates.method_name
matches them, and add negative fixtures with "hasAnyRole"/"hasAnyAuthority" (and
other hasAny* variants) to ensure method_name_excl's not_match continues to
exclude them; verify tests exercise both predicates.method_name and
method_name_excl to prevent regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d6a8bcac-8727-4205-a8ae-74b2e582f339

📥 Commits

Reviewing files that changed from the base of the PR and between 9b0134c and 7905b99.

📒 Files selected for processing (3)
  • rules/java/custom-authz-call.toml
  • src/rules/embedded.rs
  • src/scanner/matcher.rs

Application-specific authorization layers (e.g. `PrivilegeService`,
`AccessChecker`) expose boolean predicate methods like `isOrgAdmin`,
`isCandidateManager`, `isAdminForAccount`, or `hasFullOrganizationAccess`.
These don't match Spring/Shiro/Jakarta names so they were silently
missed — even when they form the entire authorization layer of an app.

Add a heuristic rule (`java-custom-authz-call`, confidence=medium,
category=custom) that fires on `is*`/`has*`/`can*` method invocations
whose names contain a role/access keyword as a complete camelCase
sub-word:

  Admin, Manager, Member, Recruiter, Viewer, Editor, Owner,
  Access, Permission, Privilege, Role, Authority, Authorized

Two predicates on the same `name: (identifier)` capture (using two
capture aliases) implement an inclusive `match` plus an exclusive
`not_match` so we don't double-report calls already handled by the
framework rules (`hasRole`, `hasAnyRole`, `hasAuthority`,
`hasAnyAuthority`, `isUserInRole`).

The keyword regex requires a complete sub-word — a capital letter or
end-of-name must follow the keyword — so `isIncludeAdmins` (where
"Admin" is a substring of "Admins") is correctly rejected. Methods like
`isArchived`, `isEmpty`, `hasLength`, `isValid` don't match either.

Verified against the issue tarballs:

  vbench-server  3 → 115 findings  (all 112 PrivilegeService call sites)
  endorsed_resume 11 → 16 findings  (all 12 isOwner ownership checks)

Hand-audited the new findings; no false positives surfaced.

Adds 6 cargo-test cases and 9 toml `rule.tests` fixtures.

Fixes #6.
@boorad boorad force-pushed the feat/java-custom-authz-call branch from 7905b99 to d05f063 Compare April 28, 2026 17:18
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
rules/java/custom-authz-call.toml (1)

34-35: Add explicit negative fixtures for hasAnyRole and hasAnyAuthority.

Line 34 excludes both variants, but current fixtures don’t pin them directly. Adding expect_match = false cases will harden regression coverage.

Suggested fixture additions
 [[rule.tests]]
 input = """
 public class SecurityConfig {
     public void configure(HttpSecurity http) {
         http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN");
     }
 }
 """
 expect_match = false

+[[rule.tests]]
+input = """
+public class SecurityConfig {
+    public void configure(HttpSecurity http) {
+        http.authorizeRequests().antMatchers("/admin/**").hasAnyRole("ADMIN","MANAGER");
+    }
+}
+"""
+expect_match = false
+
+[[rule.tests]]
+input = """
+public class SecurityConfig {
+    public void configure(HttpSecurity http) {
+        http.authorizeRequests().antMatchers("/api/**").hasAnyAuthority("SCOPE_read","SCOPE_write");
+    }
+}
+"""
+expect_match = false
+
 [[rule.tests]]
 input = """
 public class Servlet {
     public void doGet() {
         if (request.isUserInRole("admin")) { allow(); }

Also applies to: 107-136

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rules/java/custom-authz-call.toml` around lines 34 - 35, The current rule's
not_match pattern excludes hasAnyRole and hasAnyAuthority but there are no
explicit negative fixtures; add explicit fixtures that assert expect_match =
false for inputs invoking hasAnyRole and hasAnyAuthority to prevent regressions.
Locate the two fixtures blocks associated with the rule (the one around the
existing not_match =
"^(hasRole|hasAnyRole|hasAuthority|hasAnyAuthority|isUserInRole)$" and the
similar block later covering lines ~107-136) and add concise test cases named or
described to include calls to hasAnyRole(...) and hasAnyAuthority(...) with
expect_match = false so the rule’s negation is pinned by tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@rules/java/custom-authz-call.toml`:
- Around line 34-35: The current rule's not_match pattern excludes hasAnyRole
and hasAnyAuthority but there are no explicit negative fixtures; add explicit
fixtures that assert expect_match = false for inputs invoking hasAnyRole and
hasAnyAuthority to prevent regressions. Locate the two fixtures blocks
associated with the rule (the one around the existing not_match =
"^(hasRole|hasAnyRole|hasAuthority|hasAnyAuthority|isUserInRole)$" and the
similar block later covering lines ~107-136) and add concise test cases named or
described to include calls to hasAnyRole(...) and hasAnyAuthority(...) with
expect_match = false so the rule’s negation is pinned by tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4843699f-266f-4604-a0d1-64d40db59f3c

📥 Commits

Reviewing files that changed from the base of the PR and between 7905b99 and d05f063.

📒 Files selected for processing (3)
  • rules/java/custom-authz-call.toml
  • src/rules/embedded.rs
  • src/scanner/matcher.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/rules/embedded.rs

@boorad boorad merged commit 59125ce into main Apr 28, 2026
2 checks passed
@boorad boorad deleted the feat/java-custom-authz-call branch April 28, 2026 17:22
@boorad boorad self-assigned this May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Zift did not find the major authorization logic in this app

1 participant