Catalog PyPI
PACKAGE EXPLORERView in registry

ai-agent-guardrails

PyPI

A tool-agnostic policy layer that intercepts and blocks destructive commands proposed by autonomous AI coding agents, with full audit logging.

ai-safetyautonomous-agentsclaude-codeguardrailspolicy-engine
Versions collected1
Direct dependencies2From release 0.1.1
Last observed21 Oct, 09:51 UTC

README

Version 0.1.1 documentation

ai-agent-guardrails

CI PyPI Python Versions License: MIT

A small, tool-agnostic policy layer that sits between an autonomous AI coding agent and the shell: it intercepts a proposed command, checks it against a declarative threat model, and blocks the genuinely destructive ones — terraform destroy with no reviewed plan, a namespace wipe, an unscoped rm -rf, a force-push to main, an IAM policy that grants * on * — before they execute, with every decision written to a structured audit log.

As coding agents (Claude Code, Cursor, and others) move from suggesting commands to actually running them, they need the same change-management discipline a human on-call engineer operates under: nothing destructive ships without a second set of eyes, and there's a record of what ran and why. Going into 2026, agent observability and guardrails have stopped being an afterthought bolted on after an incident — they're treated as a baseline requirement of AI-infrastructure design, on the same footing as auth or logging. This project is a small, concrete, working example of that layer: not a framework, not a platform, just the policy engine, an audit trail, a CLI, and one real integration (Claude Code), built to be read end-to-end in one sitting.

guard CLI blocking a terraform destroy and a force-push, allowing npm install

Threat model

Every rule lives in guardrails/policies/default.yaml as a regex or a small Python check function (see guardrails/checks.py for the ones that need more than a regex — e.g. "was a plan reviewed earlier in this session?").

PatternWhy it's dangerousAction
terraform destroy with no -target and no evidence of a reviewed terraform plan -out=Can tear down an entire state file's worth of infrastructure with nobody having seen what would actually be deletedBLOCK
kubectl delete namespace <ns>Deletes every resource in the namespace — pods, services, secrets, PVCs, config — with no per-resource confirmationBLOCK
kubectl delete ... --allRemoves every object of that kind in the target scope in one shot; equivalent blast radius to a namespace wipeBLOCK
rm -rf /, rm -rf ~, rm -rf ., or rm -rf with no path at allRecursive+force delete at a filesystem root/home/cwd, or with no path scoping, can destroy a machine irreversibly and near-instantlyBLOCK
git push --force / -f to main/master/productionRewrites shared history other engineers and CI/CD depend on; can silently drop merged commitsBLOCK
git push --force / -f to any other branchStill rewrites history, but the blast radius is smaller if the branch isn't sharedWARN
IAM policy statement with "Resource": "*" and a broad action (iam:*, *:*)Grants effectively unrestricted access to the whole account — one of the most common causes of cloud account takeoverBLOCK
Disabling MFA / deleting an IAM user or access keyRemoves an identity's ability to authenticate, or a control protecting it — accidental or malicious, this can lock out real operators or open a holeBLOCK
DROP TABLE / DROP DATABASE / TRUNCATE TABLE outside a clearly-named migration/rollback fileIrreversible without a backup; expected and reviewable inside a migration, much easier to hit the wrong table when run ad hocWARN
curl/wget piped directly into sh/bashExecutes unreviewed, unpinned, potentially-mutable remote code with no chance to inspect it first — a common supply-chain vectorWARN

Anything that doesn't match a rule is ALLOWed — this is a blocklist for known-dangerous patterns, not a default-deny sandbox.

Architecture

                  ┌──────────────────────────────┐
 proposed         │                               │      ALLOW  ──▶ command runs
 shell command ──▶│        PolicyEngine           │
 + context        │  (guardrails/policies/*.yaml  │      WARN   ──▶ command runs,
                   │   + guardrails/checks.py)     │                 reason logged
                   └──────────────┬────────────────┘
                                  │                     BLOCK  ──▶ command refused,
                                  ▼                                reason surfaced
                  ┌─────────────────────┐
                  │     AuditLogger      │  ──▶  ~/.guardrails/audit.jsonl
                  │ (JSON lines, one     │        (tail into Loki/ELK/
                  │  record per call)    │         Langfuse/etc.)
                  └─────────────────────┘

Two callers drive this today: the standalone guard CLI, and the Claude Code PreToolUse hook. Both go through the same PolicyEngine.evaluate() — the policy logic doesn't know or care which one called it, which is the point: adding a Cursor, CI-bot, or LangGraph-tool integration means writing a thin adapter that extracts a command string and calls evaluate(), not touching the rules.

Quickstart

git clone <this-repo> ai-agent-guardrails
cd ai-agent-guardrails
python3 -m venv .venv && source .venv/bin/activate
pip install -e .

guard check "terraform destroy"
# [BLOCK] 'terraform destroy'
#   rule:   terraform_destroy_unreviewed
#   reason: 'terraform destroy' with no -target and no evidence a plan was
#           generated and reviewed first ...
# (exit code 1)

guard exec -- ls
# [ALLOW] 'ls'
#   reason: No policy violation detected.
# <actual `ls` output follows>

guard exec -- rm -rf /
# [BLOCK] ... refusing to execute: command blocked by policy. (exit code 1, `rm` never runs)

Every call above appends a JSON record to ~/.guardrails/audit.jsonl. Global flags (--json, --audit-log, --policy-dir, --context, --no-audit) go before the subcommand, e.g. guard --json check "...".

Claude Code integration

integrations/claude_code/pretooluse_hook.py implements Claude Code's PreToolUse hook contract: it reads the tool-call JSON from stdin, pulls the Bash command out of it, runs it through PolicyEngine.evaluate(), and on BLOCK prints a JSON decision to stdout plus exits non-zero so Claude Code refuses to run the command. See the docstring at the top of that file for the exact assumptions this makes about Claude Code's hook I/O — the hook schema has changed across Claude Code releases, and this is a best-effort reference implementation, not a guarantee it matches whatever version you're running. Verify against your installed version's hook docs before trusting it in anything that matters.

integrations/claude_code/CLAUDE.md.snippet has the .claude/settings.json hook registration and a sample CLAUDE.md section to paste into a project that wires this in.

This project is deliberately not Claude-Code-specific at its core — Claude Code is one integration among several a policy engine like this should support (Cursor, a CI approval bot, a custom agent loop) and the guardrails/ package has no dependency on it.

Design decisions

Why a rule-based engine and not an LLM classifier? A BLOCK decision here is safety-critical: it has to be deterministic, explainable, and auditable — the same command should get the same decision every time, and the reason needs to be traceable to a specific rule, not a model's momentary judgment. An LLM classifier is a genuinely good complement for the fuzzy cases — "does this diff look like it's quietly widening an IAM policy," "does this commit message match what the diff actually does" — where the answer is a judgment call, not a fact. But mixing that into the hard BLOCK path means a prompt-injected or just-wrong model call could either wave through something that should never run, or refuse something completely benign for no reproducible reason. So: deterministic pattern-matching for the tier where being wrong is expensive (BLOCK), with room to layer a classifier on top for the WARN tier where a human is going to look at the output anyway. This mirrors how the rest of this author's tooling treats AI — as a risk-checker that flags things for a human, never as the thing making the final call.

Why regex + small Python checks instead of a full AST/shell parser? Most of these patterns (branch names, path literals, JSON key/value pairs) are easy to catch reliably with a regex or a couple of shlex.split() calls, and keeping the rule format readable in YAML is worth more here than parsing correctness for adversarial shell syntax. This is explicitly a reference project, not a hardened sandbox — see "Status" below.

Status

This is a portfolio / reference project demonstrating the pattern, not a hardened production security boundary. In particular:

  • Pattern matching can be evaded by determined obfuscation (rm -rf $(echo /), base64-encoded commands, etc.) — it's built to catch the common, honest case of an agent about to run something destructive, not to resist an adversary deliberately trying to get around it.
  • It has one real integration (Claude Code). A Cursor or generic-CI adapter would follow the same shape: extract a command string, call PolicyEngine.evaluate(), act on the Decision.
  • The audit log is local JSONL by design (zero-dependency); wiring it into an actual Loki/ELK/Langfuse pipeline is a matter of tailing the file, not code changes here.

Extending the policy set

Add a rule to guardrails/policies/default.yaml:

- id: my_new_rule
  action: WARN   # or BLOCK
  pattern: '(?i)some regex'   # or: check: my_check_function
  reason: >
    Explain *why* this is dangerous, not just what matched.

For anything that needs more context than a regex over the raw command string (session history, current branch, file path, etc.), add a check(command: str, context: dict) -> bool function to guardrails/checks.py and reference it by name via check: instead of pattern:. Run pytest -q after any change — every rule above has both a BLOCK/WARN test and at least one matching ALLOW test to guard against regressions in either direction.


This is part of a small set of AI-infrastructure portfolio projects; sibling repos (rag-mlops-pipeline, llm-observability-stack, ai-infra-terraform) cover the rest of the stack — no links yet since they may not all be pushed.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.1.115 Aug 20262

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
goshipra/ai-agent-guardrails

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 09:51 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:cafa84949d7c1e337f2715f6570bf805d19bc028dbcd4b3f7ed6b3ead44df030