Skip to main content

attenu-guard

OpenSSF Scorecard attenu.io · Docs · Attenu Derive — what each agent may do, read from your app · Internet-Draft · Changelog

Works with LangGraph · LangChain create_agent / deepagents · OpenAI Agents SDK · Google ADK · Pydantic AI · CrewAI · AutoGen · Claude Agent SDK · smolagents · AWS Strands · LlamaIndex · Semantic Kernel · Agno · Haystack · CAMEL-AI · Microsoft Agent Framework · AG2 — each integrated unmodified, each with an offline demo and tests (matrix). Enforced live on real applications with Google ADK, CrewAI and LangGraph.

Attenu Guard checks what an AI agent may do — and keeps it narrowing at every handoff. When one agent hands work to another, the child gets only the permissions its task needs, never the parent's full set. Chains have hard ceilings. Any subtree can be revoked in one call. Every decision lands on a tamper-evident log you can verify offline. The alternative most teams live with is handing an agent a person's credentials and reading the logs afterwards.

An open enforcement layer for OWASP ASI07 (insecure inter-agent communication) and ASI08 (cascading failures): delegated authority stays inside the parent's limits, and every decision remains verifiable offline.

attenu-guard demo — the poisoned summariser: one legitimate read allowed, the exfiltration blocked, the subtree revoked, the audit chain verified

pip install attenu-guard                 # zero runtime deps; gives you the `attenu-guard` command too
pip install 'attenu-guard[langgraph]'    # or crewai, google-adk, openai-agents, … — one extra per framework

Have a bundle to check? pipx run attenu-guard verify bundle.json — integrity, child ⊆ parent and containment from the file alone, no account, no network; the auditor's walkthrough has three sample bundles (clean, tampered, widened) and takes a minute.

Just want to see it run? From the repo root, no install needed: python examples/poisoned_summarizer.py — the examples bootstrap the src/ path themselves. python tests/run_properties.py proves the invariants the same way.

from attenu_guard import Authority, Guard, RowLimit, EgressRank

# The orchestrator holds broad authority.
orchestrator = Guard.issue("orchestrator", Authority(
    scopes={"crm.*", "mail.send"},
    ceilings=[RowLimit(100_000), EgressRank("any")], ttl=3600))

# It delegates a narrow task. The child gets the *meet* of what the parent
# held and what the task needs — computed and enforced, not suggested.
summarizer = orchestrator.delegate("summarizer", Authority(
    scopes={"crm.read"},
    ceilings=[RowLimit(5_000), EgressRank("none")], ttl=900),
    task="summarize Q3 pipeline")

decision = summarizer.check("crm.read", context={"rows": 4_200})   # Decision(allowed=True)
if not decision:
    print(decision.explain())

summarizer.enforce("crm.export", context={"egress": "any"})        # raises AuthorityDenied

check() returns a rich Decision (with machine-readable reason codes for your audit trail); enforce() is the hard-stop gate that raises; would_allow() is a dry-run that writes nothing. The crm.export call is refused whatever the agent was talked into trying — the sub-agent never held that permission, so an injected instruction has nothing to widen. That is the default once permissions narrow at the handoff.

What happens at a handoff today

In the frameworks we audited, the handoff itself is not something the system can see: identity tokens describe two parties — user and agent — so "child ⊆ parent" cannot be written down, and policy checks fire when a tool is invoked rather than at the moment permissions are passed down. Verified against released code, and pinned by tests that fail the day the behaviour changes:

System What it does at a handoff (verified against the released code — see docs/INTEGRATIONS.md)
OpenAI Agents SDK 0.21 passes the entire conversation to the sub-agent (Handoff.input_filter=None by default: "the new agent sees the entire conversation history"); no parent/child relation exists, so nothing checks child ⊆ parent
LangChain deepagents 0.7 a sub-agent's permissions replace the parent's rules entirely (graph.py) — a child can be granted what its parent is denied
Google ADK 2.7.1 disallow_transfer_to_peers is enforced on the legacy llm_flows path since 2.7.1 (#3850, fix fa18d26a) — but the 2.x default workflow path (workflow/utils/_transfer_utils.py, sibling case) still carries no check: on 2.7.1 the peer transfer goes through (pinned by tests/integrations/test_google_adk.py, which fails the day it stops). Either way ADK checks who may transfer; it does not check what authority passes, and no record exists to verify afterwards
CrewAI 1.15 a delegated coworker runs with its own full tool list; the tool-hook dispatcher swallows exceptions and runs the tool (fail-open) unless you raise its one blessed exception
AutoGen 0.7 Handoff carries target/description/message only; the receiver offers the model its own full tool list
Microsoft Entra child agent inherits the parent's scopes
MCP scope flow is accumulation-biased (step-up unions)
A2A authenticates the hop, carries no delegated authority

attenu-guard makes child ⊆ parent a computed, enforced, offline-verifiable invariant — in your framework, in your process — no proxy, and no network call in the deny path.

What you get

  • Authority — an immutable capability (scopes + a list of typed, extensible Ceiling bounds + TTL) with meet, the lattice operation that can only ever shrink, and is_narrower_than, the provable subsumption relation.
  • Guardissue() a root, delegate() a sub-agent with attenuated authority, check()Decision, enforce() → raises, would_allow() → dry-run, revoke() a whole subtree.
  • Typed ceilingsRowLimit, SpendCap, CallLimit, EgressRank, Allow, Deny, Prefix, or your own via register_ceiling. Unknown ceiling types fail closed, never silently unbounded.
  • Chain invariants — depth, fanout, and aggregate budget ceilings; cascade revocation (revoke any node, every descendant denies immediately).
  • Hash-chained audit log — an open, versioned schema; attenu-guard view log.jsonl renders the tree and verifies it; tampering is provable offline. Every deny says why (disposition: held_pending_grant — waiting on a human · withheld_tier2 · unresolved — no authority known for the tool · out_of_authority — real over-reach), so "held" never reads as "denied"; evidence.export_bundle / verify_bundle / delegation_graph / denials give an auditor an offline-verifiable bundle and the folds a console renders. AuditLog(sinks=…) copies entries to local sinks after the write (never the network) — sinks.SpoolSink is a bounded, fsync'd, resumable write-ahead spool carrying the ingest idempotency key (boot_id, chain_id, seq, hash); attenu_guard.identity gives a product an identity before it has a key (.attenu/product.json, per-process boot_id, assigned chain ids).
  • Wire format (attenu_guard.wire) — serialize/load the delegation chain as signed Delegation Tokens and verify child ⊆ parent offline, across services, with no authorization server in the path. This is the reference implementation of the Internet-Draft in docs/; interop test vectors live in tests/vectors/.
  • Scenario harness — declarative JSON/YAML authorization tests (attenu-guard scenarios file.json); see scenarios/.
  • Adapters — shipped, tested integrations for the major agent frameworks as attenu_guard.adapters.<name>: LangGraph, LangChain create_agent / deepagents, OpenAI Agents SDK, Google ADK, Pydantic AI, CrewAI, AutoGen, Claude Agent SDK, smolagents, AWS Strands, LlamaIndex, Semantic Kernel, Agno — each with an offline demo under examples/integrations/; install one with pip install 'attenu-guard[<extra>]'. Hooks, versions and what each framework enforces itself: docs/INTEGRATIONS.md.

Prove the safety claims yourself

python tests/run_properties.py      # 4,000 random delegation trees per invariant, zero deps
python tests/red_team.py            # 17 adversarial attacks, black- & white-box; 0 must break
python examples/poisoned_summarizer.py
attenu-guard demo

The property suite asserts — over thousands of random chains — that attenuation never widens, holds transitively down a chain, that a revoked subtree authorizes nothing, and that audit tampering is detected. The red-team harness (see docs/RED-TEAM.md) additionally tries to break the protocol — privilege escalation, chain splicing, expired-grant reuse — and every genuine finding is fixed and pinned as a regression. If you can break one, the core claim is false; please tell us.

Standards

The protocol is designed to be IETF-acceptable: it reuses the OAuth/JOSE stack (JWT, RFC 9396 authorization_details, DPoP, Token Status List) and invents only the one missing piece — cryptographically-linked, subsumption-enforced, offline multi-hop attenuation. See docs/STANDARDS-ALIGNMENT.md and the Internet-Draft draft-asor-wimse-agent-delegation-chain (individual submission, WIMSE; source in docs/).

What this is not

This library does not decide authority for you. You write the Authority for each delegation — or you let attenu-derive, the open engine, compute it from your app's declared structure (agents, roster, tools, what each task calls) and approve it before it is enforced. The library is the enforcement shim and the open schema; it is useful entirely on its own, forever, with no account and no network. The Attenu console is optional: a place to see denials, decide, and verify — never in the deny path.

License

Apache-2.0. Contributions under the DCO. Security policy in SECURITY.md.

Download files

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

Source Distribution

attenu_guard-0.5.0.tar.gz (1.8 MB view details)

Uploaded Source

Built Distribution

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

attenu_guard-0.5.0-py3-none-any.whl (174.1 kB view details)

Uploaded Python 3

File details

Details for the file attenu_guard-0.5.0.tar.gz.

File metadata

  • Download URL: attenu_guard-0.5.0.tar.gz
  • Upload date:
  • Size: 1.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for attenu_guard-0.5.0.tar.gz
Algorithm Hash digest
SHA256 038c98f4d68febf9471d4c667867ad81cdae91e784027eefdeff8d590a1c1400
MD5 95a89e60290a17bc48f04e156fb7b782
BLAKE2b-256 cd50e1a8b7deaa8142ac7b72cde50cde7db713f8ee04ad6a603b3b596ed4d4ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for attenu_guard-0.5.0.tar.gz:

Publisher: release.yml on attenu-io/attenu-guard

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

File details

Details for the file attenu_guard-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: attenu_guard-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 174.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for attenu_guard-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f277f3fcf6e8f152cd61df464e64ea4c0f15fb60377cbbcd0ab3c6494e8583e5
MD5 d1a3b5f5858b87fc9b182d9cd4494cb0
BLAKE2b-256 481c8ceb16ef4748af164500c5331eaa28b157350511583460f9a04ae2fcbe85

See more details on using hashes here.

Provenance

The following attestation bundles were made for attenu_guard-0.5.0-py3-none-any.whl:

Publisher: release.yml on attenu-io/attenu-guard

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

Release history Release notifications | RSS feed

0.10.0

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.7.1

2 files

0.7.0

2 files

0.6.1

2 files

0.6.0

2 files

This release

0.5.0 This release

2 files

0.4.1

2 files

0.4.0

2 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