Skip to main content

Drop-in permission middleware for AI agents.

Project description

Custos

Drop-in permission middleware for AI agents.

Python License Version


What is Custos?

Modern AI agents make tool calls — they read your files, send emails, query databases, run shell commands, and call APIs. A single misdirected prompt can cause real damage: data exfiltration, unintended writes, or privilege escalation.

Custos sits between your agent and the tools it calls, intercepting every invocation and deciding whether to allow, deny, ask you, or quarantine the call when indirect prompt injection is detected — based on a declarative policy you control.

Think of it as OAuth-style consent, but for autonomous LLM agents.

  ┌───────┐      ┌────────────────────┐      ┌──────────┐
  │ Agent │─────>│      Custos        │─────>│  Tools   │
  └───────┘      │                    │      └──────────┘
                 │  policy evaluator  │
                 │  + IPI defence     │
                 │  + AI assistant    │
                 │  + your approval   │
                 │  + audit trail     │
                 └────────────────────┘

Key principles:

  • Default-deny. Anything not explicitly allowed in policy is blocked.
  • Policy is the floor. An AI assistant or context inspector can escalate strictness, but can never override a deny.
  • Transparent wrapping. The agent sees the same tool signatures it always did. Gating is invisible to the agent code.
  • Zero runtime deps beyond a JSON-schema validator. Every framework adapter, LLM backend, and eval suite is an optional extra.

How it works

Every tool call flows through a single decision pipeline:

    Tool call
       │
  ┌────▼─────┐
  │ 1. Policy│  Deterministic. Evaluates your YAML rules.
  │   engine │  First-match-wins. Returns allow/deny/assist/prompt/inspect.
  └────┬─────┘
       │
  ┌────▼─────┐
  │ 2. Context│ (optional) If policy says "inspect:<name>", an inspector
  │   insp.  │  scans full agent context for indirect prompt injection.
  └────┬─────┘
       │
  ┌────▼─────┐
  │ 3. AI    │  (optional) If policy says "assist:<name>", an LLM-driven
  │   asst.  │  assistant scores risk and returns a recommendation.
  └────┬─────┘
       │
  ┌────▼─────┐
  │ 4. User  │  (optional) If still unresolved, prompts you via CLI,
  │   prompt │  Slack, web widget, or webhook. You decide.
  └────┬─────┘
       │
  ┌────▼─────┐
  │ 5. Fatigue│  Deduplicates repeated prompts, batches similar calls,
  │   layer  │  enforces rate limits, supports "ask me later".
  └────┬─────┘
       │
  ┌────▼─────┐
  │ 6. Audit │  Every decision is logged — what, who, why, how long.
  │   + return│  Hash-chained, tamper-evident, verifiable.
  └────┬─────┘
       │
  Decision: allow / allow_once / allow_and_persist / deny / prompt / defer
            + quarantine (injection detected — block + memory wipe)

Decision types

Decision Meaning
allow Matched a standing policy rule. Execute immediately.
allow_once Execute this one time. Future identical calls will be re-evaluated.
allow_and_persist Execute and save a new policy rule for future calls.
deny Blocked. Final — no assistant or responder can override.
prompt Hand to the responder. Ask the user what to do.
defer Rate-limited or batched. Try again later.
quarantine Injection detected. Block call + trigger SDK memory wipe.

Quick example

from custos import Gateway, Policy
from custos.assistants import RulePolicy
from custos.responders import CLIResponder
from custos.audit import FileAuditSink

# 1. Load your rules
policy = Policy.from_yaml("policy.yaml")

# 2. Assemble the gateway
gw = Gateway(
    policy=policy,
    assistant=RulePolicy,                # deterministic fast path (A7)
    responder=CLIResponder(timeout=30),  # ask you in the terminal
    audit_sink=FileAuditSink("audit.jsonl"),
)

# 3. Wrap your tools — they keep the same signatures
gated_read, gated_write, gated_send = gw.wrap([read_file, write_file, send_email])

# 4. Agent code calls tools normally; Custos gates transparently
gated_read("/etc/hosts")   # policy match -> allowed, silently audited
gated_send(  "/hi")        # no policy match -> prompt: y/N/a/A/l/d

A minimal policy.yaml:

version: 1
default: deny                    # everything blocked by default

overlays:
  - id: base
    rules:
      - match: { tool: "fs.read*" }
        action: allow_and_audit

      - match: { tool: "fs.write*", side_effects: [write] }
        action: assist:risk-assessment    # hand to AI risk scorer

      - match: { tool: "shell.*", risk_tier: [4, 5] }
        action: prompt                    # ask the user

      - match: { tool: "payment.*" }
        action: prompt
        options: [allow_once, deny]       # no standing allows

      - match: { tool: "email.send" }
        action: inspect:ipi-defender      # scan for prompt injection first

On a deny or defer, the wrapper raises custos.exceptions.PermissionDenied. For async frameworks (OpenAI Agents, Anthropic, MCP, AutoGen), use AsyncGateway — same pipeline, native-async.


Custos vs. what exists

Approach Autonomy Safety User fatigue How Custos compares
No guardrails Full None Zero Custos adds a safety floor without breaking tool signatures
Manual review None Maximum Severe Custos automates the routine; prompts only on high-risk calls
System prompts only Full Brittle Zero Prompts are advice, not enforcement. Custos is a hard gate
Janus Configurable Configurable Configurable Academic reference. Custos is a production-grade reimplementation
Custos Configurable Configurable Tunable (fatigue layer) Policy floor + AI assistant + user prompt + audit, all in one

Install

The runtime has zero hard dependencies beyond jsonschema. Pick the extra that matches what you need:

# Start here — runtime-only, policy via Python dicts or YAML
pip install "custos-middleware[yaml]"       # + PyYAML (recommended)
pip install custos-middleware               # programmatic policies only

# LLM-backed AI assistants (A3-A6, A9)
pip install "custos-middleware[llm]"        # + LiteLLM

# Framework adapters — pick your agent framework
pip install "custos-middleware[langchain]"   # LangChain
pip install "custos-middleware[mcp]"         # MCP in-process
pip install "custos-middleware[openai-agents]"   # OpenAI Agents SDK
pip install "custos-middleware[anthropic]"   # Anthropic messages API

# Eval harness + CI test suites
pip install "custos-middleware[eval]"        # google-adk, litellm (dev/test)

# Local development
pip install "custos-middleware[dev]"         # pytest, ruff, mypy

All extras are additive: pip install "custos-middleware[yaml,llm,langchain]".

Which one do I need?

You want to... Install
Try it out, read the docs custos-middleware[yaml]
Add LLM-powered risk scoring add [llm]
Protect a LangChain agent add [langchain]
Run the adversarial eval suite in CI add [eval]
Develop Custos itself add [dev]

Components

Custos is assembled from interchangeable pieces. Wire what you need, skip what you don't.

Policy engine

A deterministic, pure-function rules engine. Write rules in YAML or construct them programmatically. First-match-wins, default-deny. Hot-reloadable. Supports tool-name globs, arg predicates, risk tiers, delegation depth, and side-effect matching. Docs →

Permission assistants (A1–A12)

Pluggable AI assistants that handle policy escalations. A1–A6 reproduce the Janus reference designs; A7–A12 are Custos extensions:

ID Name Strategy Needs LLM?
A1 Auto-approve Always yes. Baseline. No
A2 User confirmation Always ask. Maximum safety. No
A3 Constitution Check against a written constitution doc. Yes
A4 Policy suggestion Draft generalized ABAC rules for you. Yes
A5 Risk assessment Score risk against task goals; escalate to user. Yes
A6 Risk assessment autonomous Same as A5, but deny instead of prompting. Yes
A7 Rule policy Pure deterministic rules. Fast path. No
A8 Summarize batch Batch similar calls into one prompt. No
A9 Context adaptive Choose prompt granularity by sensitivity. Yes
A10 Learned policy Learn from your past decisions. No
A11 Delegation aware Stricter rules for deeper delegation chains. No
A12 IPI defender Context inspector. Detects indirect prompt injection via pattern matching + leave-one-out causal attribution. configurable

Docs →

Responders

User-facing prompt backends. One gateway can route to different responders per tool or risk tier.

  • CLI — yn/A/a/L/d prompt in the terminal
  • Noop — silent deny (for CI/headless)
  • Web — SSE-backed web widget, embeddable
  • Webhook — HMAC-signed HTTP callbacks with nonce replay protection
  • Slack — Block Kit messages with signed interactions
  • Multi-approver — Quorum: requires N distinct approvers from disjoint roles

Docs →

Fatigue layer

Prevents prompt storms. Deduplicates repeated tool calls (SHA-256 args hash), batches similar calls into a single prompt, enforces per-minute rate limits, and supports "ask me later" via defer. Docs →

Audit

Structured, append-only JSONL decision log. Every event records: what tool was called, by whom, what the policy said, what the assistant recommended, what you decided, and how long it took. Hash-chained mode adds tamper evidence with custos audit verify. Secret/PII fields in tool args are redacted before they reach the audit log or any responder. Docs →

Framework adapters

In-process wrappers that make Custos transparent to the agent framework. The agent sees normal tool signatures; Custos sits inside as a gating layer.

  • LangChainwrap_langchain_tools(gw, agent.tools)
  • MCPgated_tool decorator and wrap_mcp_tools for FastMCP servers
  • OpenAI Agents SDKgated_function_tool decorator
  • Anthropicgated_anthropic_tool for the messages-API dispatch loop
  • AutoGen, Google ADK, LlamaIndex — v1.0 carry-forward adapters

Docs →

Eval harness

custos eval CLI for CI. Two suites: (1) a Janus v1 parity matrix reproducing the published 72-cell evaluation, and (2) a Custos-authored adversarial suite (53+ cells covering prompt injection, IPI detection, confused deputy, tool spoofing, delegation abuse, policy poisoning, and quorum bypass). Default backend is local Ollama — no API spend. Docs →


Relationship to Janus

Janus (arXiv:2607.01510, Brigham et al., U. Washington) is the academic reference implementation that established the permission-assistant design space. It introduces the concept of plug-and-play permission assistants and the six-assistant catalog (Auto-Approve, User Confirmation, Constitution, Policy Suggestion, Risk Assessment, Risk Assessment Autonomous).

Custos is an independent, production-grade reimplementation of the Janus concept. It is not affiliated with the Janus authors and does not vendor any Janus code (Apache-2.0). Key differences:

  • Production runtime — Zero hard deps beyond jsonschema. Janus depends on Google ADK and LiteLLM.
  • 6 additional assistants — A7 (rule-policy), A8 (summarize-batch), A9 (context-adaptive), A10 (learned-policy), A11 (delegation-aware), A12 (ipi-defender).
  • 7 decision types — Janus has 3 (approve_once / create_policy / reject). Custos adds allow, prompt, defer, and quarantine.
  • Cross-language — Python SDK, TypeScript SDK (@taqiy/custos-core), and a gRPC sidecar for out-of-process deployment.
  • Audit tamper evidence — Hash-chained JSONL with HMAC signing and custos audit verify.
  • Deterministic policy engine — Pure, no side effects, hot-reloadable. Janus policy is runtime-coupled to the ADK session.

License

Apache-2.0. See LICENSE.


Documentation

Document What it covers
Quickstart 5-line integration, policy YAML, audit log
Tutorial 20-30 minute walk from zero to gated agent
Policy schema Full YAML reference: match criteria, actions, overlays
Policy cookbook 5 runnable recipes for common patterns
Assistant catalog A1-A12 with exfiltrates_args flags
Context inspectors A12 IPI defence + leave-one-out attribution
Responder reference CLI, web, Slack, webhook, multi-approver
Audit reference Sinks, hash-chaining, custos audit verify
Eval harness Janus-v1 parity + adversarial CI suites
Adapters Per-framework integration details
Sidecar (gRPC) Cross-language deployment surface
Threat model Normative: every mapped to a STRIDE threat
IR contract Cross-language pinning: Python-TS byte parity
Changelog Phased release history
Contributing Code style, runtime-dep discipline, test policy
Security Vulnerability disclosure policy

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

custos_middleware-1.1.1.tar.gz (428.6 kB view details)

Uploaded Source

Built Distribution

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

custos_middleware-1.1.1-py3-none-any.whl (270.0 kB view details)

Uploaded Python 3

File details

Details for the file custos_middleware-1.1.1.tar.gz.

File metadata

  • Download URL: custos_middleware-1.1.1.tar.gz
  • Upload date:
  • Size: 428.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for custos_middleware-1.1.1.tar.gz
Algorithm Hash digest
SHA256 4eef4c2a4540bd4a5aa8551f8ccd557f4d53622946fe5519b5863b4b61c06d7e
MD5 751c7603dd6a184de3de0895a40ae433
BLAKE2b-256 1bf15205a8f8f6cecce9287f34a80bcc74f6373e58aa8d5e279f0903752d4763

See more details on using hashes here.

Provenance

The following attestation bundles were made for custos_middleware-1.1.1.tar.gz:

Publisher: release.yml on Taki-chiasf/Custos

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file custos_middleware-1.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for custos_middleware-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 916e798f57f062c6825d17b9b74c773a8f2ae97075396afd5df2e0246fc1470f
MD5 a8816555c01a80ece80d01b34ccb9513
BLAKE2b-256 7ad1b78a51543924b98523bb652fab408d94fa42c9d73342cce13d40992d6bc8

See more details on using hashes here.

Provenance

The following attestation bundles were made for custos_middleware-1.1.1-py3-none-any.whl:

Publisher: release.yml on Taki-chiasf/Custos

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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