feat(rules/java): heuristic detection of custom authz service calls#13
Conversation
📝 WalkthroughWalkthroughIntroduces a new embedded Java heuristic rule Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Comment |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
rules/java/custom-authz-call.toml (1)
1-35: Add fixtures forcan*andhasAny*variants to lock declared behavior.The rule definition supports
can*matches and excludeshasAnyRole/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 = falseAlso 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
📒 Files selected for processing (3)
rules/java/custom-authz-call.tomlsrc/rules/embedded.rssrc/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.
7905b99 to
d05f063
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
rules/java/custom-authz-call.toml (1)
34-35: Add explicit negative fixtures forhasAnyRoleandhasAnyAuthority.Line 34 excludes both variants, but current fixtures don’t pin them directly. Adding
expect_match = falsecases 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
📒 Files selected for processing (3)
rules/java/custom-authz-call.tomlsrc/rules/embedded.rssrc/scanner/matcher.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/rules/embedded.rs
Summary
Application-specific authorization layers (e.g.
PrivilegeService,AccessChecker) expose boolean predicate methods likeisOrgAdmin,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 onis*/has*/can*method invocations whose names contain a role/access keyword as a complete camelCase sub-word:The two-predicate trick:
name: (identifier)is captured twice under different aliases so we can run an inclusivematchregex and an exclusivenot_matchregex on the same node — needed because the current TOML schema isHashMap<capture_name, predicate>andregexdoesn't support negative lookahead.Exclusion list filters out methods already handled by dedicated rules to prevent duplicate findings:
hasRole,hasAnyRole,hasAuthority,hasAnyAuthority(handled byjava-has-role-call)isUserInRole(handled byjava-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:isOrgAdminisAdminForAccountisVBenchAdmin\w*?hasFullOrganizationAccessisIncludeAdminsisArchived,isValid,isEmpty,hasLength,hasRelationshipVerification on the issue tarballs
vbench-server-src.tar.gz(#6)endorsed_resume_src.tar.gz(#7)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 allisOwner(accountId, …). Zero false positives surfaced in either codebase.Tests
cargo testcases insrc/scanner/matcher.rs:…matches_role_suffix…matches_keyword_in_middle…matches_has_access…no_substring_false_positive(theisIncludeAdminscase)…no_state_check_false_positive…excludes_known_framework_methods(assertshasRole,hasAuthority,isUserInRoleare silently filtered)[[rule.tests]]toml fixtures (run viazift rules test)Closes #6.
Out of scope (follow-ups)
*PrivilegeService/*PermissionManagerstyle classes themselves (the definition, not the call sites). Useful as a "this is your custom authz module" signal.Role.ADMINconstant to its string value when generating Rego stubs (would benefit from--deepLLM-assisted mode).Test plan
cargo fmt -- --checkcargo clippy --tests -- -D warningscargo testcargo run -- rules validate— 33 rules passisOwner(...)checks detected, all realSummary by CodeRabbit