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
21 changes: 16 additions & 5 deletions .agents/commands/commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,24 @@ When activated, commit the current working tree changes:
1. **Sync with remote**:
- Run `git fetch origin main` to get latest upstream
- Run `git log HEAD..origin/main --oneline` to check if main has moved ahead
- If it has, warn the user but don't rebase automatically
- If it has, pull main into the working state before committing — either
`git merge origin/main` on a feature branch, or use the new-branch path
in step 2 if the current branch is no longer the right home for this work
(e.g. its name references a different issue/PR than what's being committed).

2. **Ensure we're not on main**:
2. **Ensure we're on a branch dedicated to this work**:
- Run `git branch --show-current`
- If on `main`, create a new feature branch:
- Look at the staged/unstaged changes to infer a branch name
- Run `git checkout -b feat/<descriptive-name>`
- Create a fresh feature branch off `origin/main` if **any** of the following hold:
- Current branch is `main`
- Current branch's name references a different issue/PR than the work being
committed (e.g. branch is `refactor/71-…` but the diff implements #58)
- Current branch already merged upstream (its work is in `origin/main`)
- To create the new branch:
- Stash uncommitted work (`git stash push -m "wip: <issue>"`)
- `git checkout -b <type>/<issue-number>-<descriptive-name> origin/main`
- If a prior commit on the old branch belongs to this work, cherry-pick it:
`git cherry-pick <hash>`
- `git stash pop`
- Inform the user of the new branch name

3. **Review changes**:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ jobs:

- name: Tests
run: cargo test --all-features

- name: Validate rule queries and policy templates
run: cargo run --all-features -- rules validate

- name: Run rule fixtures
run: cargo run --all-features -- rules test
25 changes: 23 additions & 2 deletions rules/java/authorized-annotation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,29 @@ description = "OpenMRS-style @Authorized({\"PRIV\"}) annotation"
query = """
[
(annotation
name: (identifier) @anno_name
name: [
(identifier) @anno_name
(scoped_identifier
scope: (_) @anno_scope
name: (identifier) @anno_name)
]
arguments: (annotation_argument_list
(string_literal (string_fragment) @role_value)))
(annotation
name: (identifier) @anno_name
name: [
(identifier) @anno_name
(scoped_identifier
scope: (_) @anno_scope
name: (identifier) @anno_name)
]
arguments: (annotation_argument_list
(element_value_array_initializer
(string_literal (string_fragment) @role_value))))
] @match
"""

provenance_capture = "anno_scope"

[rule.predicates.anno_name]
eq = "Authorized"

Expand Down Expand Up @@ -78,6 +90,15 @@ public class UserService {
"""
expect_match = false

[[rule.tests]]
input = """
public class UserService {
@org.openmrs.annotation.Authorized("Manage Users")
public void delete(User u) { }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class UserService {
Expand Down
113 changes: 113 additions & 0 deletions rules/java/jaxrs-endpoint.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
[rule]
id = "java-jaxrs-endpoint"
languages = ["java"]
category = "route"
confidence = "medium"
description = "JAX-RS / Jakarta REST resource method (@GET, @POST, @Path, ...)"
# JAX-RS resource methods are tagged with one of `@Path`, `@GET`, `@POST`,
# `@PUT`, `@DELETE`, `@PATCH`, `@HEAD`, or `@OPTIONS` from `javax.ws.rs.*`
# (Java EE 8 and earlier) or `jakarta.ws.rs.*` (Jakarta EE 9+, Jersey 3+,
# RESTEasy 6+). The annotation set is identical across the migration boundary
# — only the package prefix changes — so the rule needs to accept bare and
# fully-qualified forms for both.
#
# Without this rule a bare `@GET` REST endpoint is invisible to the
# structural pass; the existing annotation rules cover Spring Security, JSR-250,
# and Shiro but say nothing about REST surface area, so unprotected JAX-RS
# methods (a `@GET` with no auth annotation) wouldn't surface at all.
query = """
[
(marker_annotation
name: [
(identifier) @anno_name
(scoped_identifier
scope: (_) @anno_scope
name: (identifier) @anno_name)
])
(annotation
name: [
(identifier) @anno_name
(scoped_identifier
scope: (_) @anno_scope
name: (identifier) @anno_name)
])
] @match
"""

provenance_capture = "anno_scope"

[rule.predicates.anno_name]
match = "^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|Path)$"

[[rule.tests]]
input = """
public class UserResource {
@GET
public List<User> list() { return null; }
}
"""
expect_match = true

[[rule.tests]]
input = """
@Path("/users")
public class UserResource {
public List<User> list() { return null; }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class UserResource {
@POST
@Path("/users")
public Response create(User u) { return null; }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class UserResource {
@jakarta.ws.rs.GET
public List<User> list() { return null; }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class UserResource {
@javax.ws.rs.GET
public List<User> list() { return null; }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class UserResource {
@jakarta.ws.rs.Path("/users")
public Response find() { return null; }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class UserController {
@Override
public void delete() { }
}
"""
expect_match = false

[[rule.tests]]
input = """
public class UserController {
@Transactional
public void save() { }
}
"""
expect_match = false
36 changes: 35 additions & 1 deletion rules/java/shiro-requires-authentication.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ confidence = "high"
description = "Apache Shiro @RequiresAuthentication, @RequiresGuest, or @RequiresUser annotation"
query = """
(marker_annotation
name: (identifier) @anno_name
name: [
(identifier) @anno_name
(scoped_identifier
scope: (_) @anno_scope
name: (identifier) @anno_name)
]
) @match
"""

provenance_capture = "anno_scope"

[rule.predicates.anno_name]
match = "^(RequiresAuthentication|RequiresGuest|RequiresUser)$"

Expand Down Expand Up @@ -40,6 +47,33 @@ public class SecureController {
"""
expect_match = true

[[rule.tests]]
input = """
public class SecureController {
@org.apache.shiro.authz.annotation.RequiresAuthentication
public void secureAction() { }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class SecureController {
@org.apache.shiro.authz.annotation.RequiresGuest
public void guestAction() { }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class SecureController {
@org.apache.shiro.authz.annotation.RequiresUser
public void userAction() { }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class Controller {
Expand Down
18 changes: 17 additions & 1 deletion rules/java/shiro-requires-permissions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ confidence = "high"
description = "Apache Shiro @RequiresPermissions annotation"
query = """
(annotation
name: (identifier) @anno_name
name: [
(identifier) @anno_name
(scoped_identifier
scope: (_) @anno_scope
name: (identifier) @anno_name)
]
arguments: (annotation_argument_list
(string_literal
(string_fragment) @perm_value))
) @match
"""

provenance_capture = "anno_scope"

[rule.predicates.anno_name]
eq = "RequiresPermissions"

Expand Down Expand Up @@ -46,6 +53,15 @@ public class UserController {
"""
expect_match = true

[[rule.tests]]
input = """
public class UserController {
@org.apache.shiro.authz.annotation.RequiresPermissions("user:delete")
public void deleteUser(Long id) { }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class UserController {
Expand Down
18 changes: 17 additions & 1 deletion rules/java/shiro-requires-roles-array.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ confidence = "high"
description = "Apache Shiro @RequiresRoles annotation with array of roles"
query = """
(annotation
name: (identifier) @anno_name
name: [
(identifier) @anno_name
(scoped_identifier
scope: (_) @anno_scope
name: (identifier) @anno_name)
]
arguments: (annotation_argument_list
(element_value_array_initializer
(string_literal) @role_value))
) @match
"""

provenance_capture = "anno_scope"

[rule.predicates.anno_name]
eq = "RequiresRoles"

Expand Down Expand Up @@ -46,6 +53,15 @@ public class AdminController {
"""
expect_match = true

[[rule.tests]]
input = """
public class AdminController {
@org.apache.shiro.authz.annotation.RequiresRoles({"admin", "manager"})
public void adminAction() { }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class AdminController {
Expand Down
18 changes: 17 additions & 1 deletion rules/java/shiro-requires-roles.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ confidence = "high"
description = "Apache Shiro @RequiresRoles annotation"
query = """
(annotation
name: (identifier) @anno_name
name: [
(identifier) @anno_name
(scoped_identifier
scope: (_) @anno_scope
name: (identifier) @anno_name)
]
arguments: (annotation_argument_list
(string_literal
(string_fragment) @role_value))
) @match
"""

provenance_capture = "anno_scope"

[rule.predicates.anno_name]
eq = "RequiresRoles"

Expand Down Expand Up @@ -46,6 +53,15 @@ public class AdminController {
"""
expect_match = true

[[rule.tests]]
input = """
public class AdminController {
@org.apache.shiro.authz.annotation.RequiresRoles("admin")
public void adminAction() { }
}
"""
expect_match = true

[[rule.tests]]
input = """
public class AdminController {
Expand Down
Loading