Skip to main content

Why

AI agents are shipping fast and getting real permissions — deleting records, sending emails, moving money, running shell commands. Most teams have zero enforcement between "the model decided to call a tool" and "the tool ran." bastion sits in that gap:

 agent decides to call a tool
          │
          ▼
    ┌──────────┐      allow    ──▶  tool runs
    │ bastion  │      block    ──▶  denied, agent sees why
    │  policy  │      approve  ──▶  human reviews (Slack / dashboard / console)
    └──────────┘
          │
          ▼
   every decision logged

Not another prompt-injection classifier — enforcement on what agents are actually allowed to do.

What you get

🛡️ Policy engine Allow / block / require-approval rules, glob-matched tool names, conditions on args (amount > 500, regex match, contains-PII, ...)
✅ Human-in-the-loop Console prompt, Slack (reaction-based, no webhook server), or the hosted dashboard — pluggable
📝 Audit log, for free Every decision recorded automatically — JSONL locally or shipped to the dashboard
🔍 PII/secrets scanning Regex-based (no ML model to download) — emails, SSNs, Luhn-validated credit cards, cloud credentials, private keys
📄 Declarative policy Rules in YAML, editable by a non-engineer reviewer — by hand or from the dashboard's /policy page
🔌 3 framework integrations LangGraph, OpenAI Agents SDK, Claude Agent SDK — one line to wrap your tools
📊 Dashboard Audit log, approval queue, and a policy editor — a real Next.js app

Quickstart

pip install bastionguard — the PyPI distribution is named bastionguard (plain bastion was already taken — an old Python 2 stdlib module, of all things). The import name is unaffected: still import bastion.

from bastion import PolicyEngine, Rule, Action, guard
from bastion.policy import arg_exceeds

policy = PolicyEngine()
policy.add_rule(Rule(tool_pattern="delete_*", action=Action.BLOCK, reason="irreversible"))
policy.add_rule(Rule(
    tool_pattern="transfer_funds",
    action=Action.APPROVE,
    condition=arg_exceeds("amount", 500),
    reason="large transfers need a human",
))

def transfer_funds(account: str, amount: float) -> str:
    return f"sent ${amount} to {account}"

guarded = guard(transfer_funds, policy=policy)
guarded(account="acct_1", amount=600)  # prompts for approval in the terminal
python examples/basic_example.py   # runs the full demo above

Rules can also live in YAML instead of hand-written Python:

from bastion import load_policy_from_yaml
policy = load_policy_from_yaml("policy.yaml")

See examples/policy.yaml for the schema.

Framework integrations

LangGraph
from bastion.integrations.langgraph import guarded_tool_node

tool_node = guarded_tool_node([my_tool_a, my_tool_b], policy=policy)
# use tool_node exactly where you'd use langgraph.prebuilt.ToolNode(tools)

A blocked or approval-denied call comes back as a normal error ToolMessage (handle_tool_errors=BlockedByPolicy), so the agent can react to it instead of the graph run crashing. See examples/langgraph_demo.py.

pip install "bastionguard[langgraph]"
OpenAI Agents SDK
from bastion.integrations.openai_agents import guarded_tools

agent = Agent(name="...", tools=guarded_tools([my_tool_a, my_tool_b], policy=policy))

A blocked or approval-denied call returns a descriptive string as the tool's result ("Tool call blocked by policy: <reason>") rather than raising — the same idiom the SDK itself uses when a tool raises an exception, so the model sees why and can react. See examples/openai_agents_demo.py.

pip install "bastionguard[openai-agents]"
Claude Agent SDK
from claude_agent_sdk import ClaudeAgentOptions
from bastion.integrations.claude_agent_sdk import guarded_can_use_tool

options = ClaudeAgentOptions(can_use_tool=guarded_can_use_tool(policy=policy))

Hooks into the SDK's own permission system (can_use_tool), which it calls for every tool invocation — built-in tools (Bash, Read, Write, ...) and custom tools registered via create_sdk_mcp_server alike. Unlike the other two integrations, nothing needs wrapping per-tool — one callback covers everything. See examples/claude_agent_sdk_demo.py.

pip install "bastionguard[claude-agent-sdk]"

PII / secrets scanning

from bastion import Rule, Action, contains_pii, enforce_text_policy

policy.add_rule(Rule(
    tool_pattern="*",
    action=Action.BLOCK,
    condition=contains_pii(["ssn_us", "credit_card"]),
    reason="tool call args contain PII/secrets",
))

# directly on an LLM response, outside the tool-call path:
enforce_text_policy(llm_output, categories=["email"], on_detect="redact")

Also available as a YAML condition type (type: contains_pii). See examples/pii_scanning_example.py.

Slack approval

from bastion.integrations.slack import SlackApprovalHandler

approval_handler = SlackApprovalHandler(
    token="xoxb-...",       # needs chat:write + reactions:read scopes
    channel="#agent-approvals",
)

Posts a message and polls for a ✅/❌ reaction — no webhook server required. Fails closed (denies) if nobody responds within timeout seconds.

Dashboard

A real Next.js app (dashboard/) — audit log, approval queue, and a policy editor, all wired to the SDK over HTTP:

from bastion import AuditLogger, guard
from bastion.integrations.dashboard import (
    DashboardApprovalHandler,
    dashboard_audit_sink,
    load_policy_from_dashboard,
)

api_key = "..."  # must match DASHBOARD_API_KEY in the dashboard's env
policy = load_policy_from_dashboard("http://localhost:3000", api_key)
audit = AuditLogger(sink=dashboard_audit_sink("http://localhost:3000", api_key))
approval_handler = DashboardApprovalHandler("http://localhost:3000", api_key)

The dashboard UI is behind a separate session login (not this API key) — see dashboard/README.md for the full auth model and self-hosting setup.

Install

pip install bastionguard
# with a framework integration:
pip install "bastionguard[langgraph]"   # or [openai-agents] / [claude-agent-sdk]
# with Slack approval:
pip install "bastionguard[slack]"

For local development (editable install, running the test suite), see CONTRIBUTING.md.

Tests

ruff check . && mypy src && pytest -q

Framework integration tests run against the real installed package for each framework, never a mock — see AGENTS.md for why that's a hard rule here.

Project status

Pre-1.0. Core SDK, all three framework integrations, and the dashboard are built and tested — see CHANGELOG.md for what's shipped and PLAN.md for what's next. Not yet on PyPI; install from source for now (see CONTRIBUTING.md).

Contributing

Issues and PRs welcome — see CONTRIBUTING.md. Found a security issue? See SECURITY.md instead of opening a public issue for it.

License

Apache 2.0

Metadata

Release files for bastionguard 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bastionguard 0.1.0
File Size Uploaded
bastionguard-0.1.0.tar.gz 30.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bastionguard 0.1.0
File Interpreter ABI Platform
bastionguard-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 56.8 kB

Release files / bastionguard-0.1.0.tar.gz

Download URL bastionguard-0.1.0.tar.gz
Size 30.9 kB
Tags Source
SHA-256 checksum
How to use checksums
0219262a8a4affc45cf6a9771a11487bcc49fb00de82528d9f53a1782f804c46
BLAKE2b-256 checksum
How to use checksums
e459fe345c006688349bfd1fbe859d2264e7bce119644b35a33e46f0409e690e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release files / bastionguard-0.1.0-py3-none-any.whl

Download URL bastionguard-0.1.0-py3-none-any.whl
Size 25.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0f8414638dadbd5d1a911898a9eb0d4f496b5846cf5c427846e5915b04c8b32b
BLAKE2b-256 checksum
How to use checksums
4eff77719c8eda7046650cf5eeae25670aea1adab89a99e9912e4c89ad3a7d91
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page