Skip to main content

Deny-by-default policy gate for AI agent tool calls: allowlists, argument validation, rate caps, human-approval hooks, dry-run mode, and an audit trail. Framework-agnostic, zero dependencies.

Project description

tool-call-guard (Python)

PyPI license

Deny-by-default policy gate for AI agent tool calls. The Python half of tool-call-guard — same JSON policy model and audit schema as the npm package, so one security review covers both stacks.

from tool_call_guard import Guard, ToolCallDenied

guard = Guard(
    {
        "defaultAction": "deny",              # anything unlisted is blocked
        "tools": {
            "search_*": {},                   # allowlist a group
            "send_email": {
                "validate": lambda a: a["to"].endswith("@mycompany.com")
                or "external recipients need approval",
                "maxCallsPerMinute": 5,
            },
            "deploy": {"action": "approve"},  # human-in-the-loop
            "shell_exec": {"action": "deny"},
        },
    },
    approve=lambda req: ask_operator(req),    # sync here; async via acheck()
)

@guard.protect("send_email")
def send_email(args):
    ...

send_email({"to": "attacker@evil.com"})       # raises ToolCallDenied

Install

pip install tool-call-guard

Zero dependencies, fully typed, Python 3.9+. Validators accept plain callables (return True/False/reason-string, or raise) or pydantic-style model classes (anything with model_validate).

What the policy gives you

  • Deny-by-default — unlisted tools are blocked; the allowlist is the policy.
  • Wildcard rules"fs_*" budgets and gates a whole group; exact names beat patterns.
  • Argument validation — runs before quota, so malformed calls never consume budget.
  • QuotasmaxCalls per guard lifetime, maxCallsPerMinute sliding window (injectable clock).
  • Approval hooksaction: "approve" calls your approver; no approver configured means deny, not allow.
  • Dry-run mode — everything proceeds, but the audit trail records what enforcement would have done. Observe a policy in production before turning it on. Approvers are never invoked during a rehearsal.
  • Audit trail — in-memory ring buffer plus optional sinks; jsonl_audit(path) writes one JSON line per decision, same schema as the JS package.

API sketch

guard = Guard(policy, mode="enforce"|"dry-run", approve=..., on_audit=...,
              audit_args=True, max_audit_events=1000, now=time.time)

guard.check(tool, args)   -> Decision      # sync; sync approvers only
await guard.acheck(tool, args)             # async; sync or async approvers
guard.wrap(name, fn)                       # sync fn -> sync wrapper, async -> async
@guard.protect(name)                       # decorator form
guard.wrap_tools({name: fn, ...})
guard.audit_log                            # ring buffer, newest last
guard.reset()

Decision: allowed, action, reason, tool, rule, and in dry-run would_allow + dry_run. Denied wrapped calls raise ToolCallDenied (with .decision).

Policy keys are camelCase (portable JSON, shared with the JS package); snake_case aliases (max_calls, …) are accepted in Python.

See the repository root for the full policy reference and the threat model this addresses.

License

MIT © Binaya Dhakal

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tool_call_guard-0.1.0.tar.gz (10.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tool_call_guard-0.1.0-py3-none-any.whl (8.3 kB view details)

Uploaded Python 3

File details

Details for the file tool_call_guard-0.1.0.tar.gz.

File metadata

  • Download URL: tool_call_guard-0.1.0.tar.gz
  • Upload date:
  • Size: 10.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.7

File hashes

Hashes for tool_call_guard-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ba60abe8427434a22d68da8b6f3c506a4c20ba8060aec787e78a526683e903be
MD5 91966fd45d9e857ccc1c91c9978546a8
BLAKE2b-256 dc9706537bbb827e10935f674e902176023d71e93ef6b2b33035ab7e7640208b

See more details on using hashes here.

File details

Details for the file tool_call_guard-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for tool_call_guard-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fb49471d50f19a8cab05f69967e4d4862788ffe40da88e919151cea7dbdda327
MD5 201bd026e1e39d44fa52e2546c1ae3eb
BLAKE2b-256 af009fbd3d3fd393ddf1e62307bf8d3594424d711dbdc7b00343b004473e5966

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page