Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

actionguard-java

CI license

Policy-as-code (deny-overrides, fail-closed) for AI agent tool calls, in Java. The Java port of actionguard (Rust) — the pattern security teams already use for cloud infrastructure (OPA/Rego, AWS Cedar: a policy decision point in front of every action, deny-overrides, fail-closed by default) applied to agent tool calls instead of API requests.

guardflow-java validates what an agent says; actionguard-java validates what it's about to do, before it does it.

Install

Not published to Maven Central — pull it straight from GitHub via JitPack:

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.github.thaicn1712</groupId>
    <artifactId>actionguard-java</artifactId>
    <version>main-SNAPSHOT</version> <!-- or a tagged release -->
</dependency>

Usage

import io.github.thaicn1712.actionguard.*;
import io.github.thaicn1712.actionguard.policies.*;

PolicySet policies = PolicySet.create()
    .with(new AllowList("read_file", "search", "send_email"))
    .with(new DenyList("rm_rf", "drop_table", "shell_exec"))
    .with(new ArgMatchesRegex("read_file", "path", "^/workspace/.*"));

ToolCall call = new ToolCall("read_file", Map.of("path", "/workspace/notes.txt"));

switch (policies.check(call)) {
    case Decision.Allow ignored -> runTheTool(call);
    case Decision.Deny deny -> System.out.println("blocked: " + deny.reason());
}

Fail-closed by default: if no policy explicitly allows a call, it's denied — the same default OPA and every serious authorization system ships with, and the opposite of what most hand-rolled if (command.contains("rm")) checks do.

Deny-overrides: any policy voting Deny blocks the call outright, even if another policy voted Allow — you can't accidentally allowlist your way past an explicit deny rule.

Built-in policies

AllowList, DenyList (deny-overrides an allow list), ArgMatchesRegex (scopes a string argument of one named tool to a pattern, abstaining for every other tool), CustomAsyncPolicy (wraps a function as an AsyncPolicy).

Async policies

For checks that need a model call — "does this action match what the user actually asked for" — AsyncPolicy wraps a blocking check, evaluated only after every sync policy has already voted:

AsyncPolicySet policies = AsyncPolicySet.fromSync(syncPolicies)
    .withAsync(new CustomAsyncPolicy("matches_intent", call -> {
        return callIsConsistentWithUserRequest(userRequest, call)
            ? new Vote.Allow()
            : new Vote.Deny("not consistent with the user's request");
    }));

Decision decision = policies.check(call);

AsyncPolicy.vote is a plain blocking call rather than CompletableFuture chaining — on modern Java, run it on a virtual thread and blocking costs nothing, the same idiom as AsyncValidator in guardflow-java. Sync policies always run first: an explicit sync Deny short-circuits before any async policy — and therefore any network call — runs at all.

Examples

mvn -q compile exec:java -Dexec.mainClass=io.github.thaicn1712.actionguard.examples.AgentDispatcherExample

License

MIT

About

Policy-as-code (deny-overrides, fail-closed) for AI agent tool calls, in Java

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages