Skip to main content

warrantgen

tests

Record what your AI agent actually does, then get the narrowest Tenuo warrant that still allows it: signed, per step, per sub-agent, with tests that keep it honest in CI.

Writing least-privilege warrants by hand is the slow part of adopting Tenuo. A real Temporal agent needed ~290 lines of hand-written warrant, key and audit code. warrantgen infers the same constraints from recorded runs, and on that agent's held-out runs it allows every legitimate call and denies every injected secret read.

Try it in five commands, no network or account: examples/demo records a small agent, infers its warrants, and stops a prompt injection the agent fell for.

For actions that can't be undone, examples/refund-agent caps refunds to recorded behavior, gates each one behind a signed human approval, and runs 16 failure scenarios: injection, approval bypass, expired approvals, malformed arguments, timeouts.

How it works

flowchart TD
    A["Your agent<br/>LangGraph or Temporal"] -- "Recorder<br/>(no warrant needed)" --> T[("traces.jsonl")]
    T --> Q{"Quarantine runs that touched<br/>.env · .. · keys · metadata IPs"}
    Q -- "clean runs" --> I["infer<br/>rules per step"]
    I --> W["Signed warrant<br/>warrantgen.mint"]
    I --> Y["tenuo-cli template<br/>warrantgen template"]
    I --> C["Per-hop delegation chain<br/>warrantgen chain"]
    I --> P["pytest tests<br/>warrantgen test"]
  1. Record tool calls with a LangChain callback or Tenuo's Temporal audit_callback. Each call is tagged with its run and its step (a LangGraph node path, or the warrant that checked it on Temporal).
  2. Quarantine any run that touched a secret file, a .. path or a private host. It is never learned from.
  3. Infer one rule per argument from several runs: what's fixed, what changes per run, what's free text.
  4. Use the rules: mint a warrant, write a tenuo-cli template, mint a delegation chain, or generate tests.

What inference looks like

Rules inferred from 15 recorded runs of a real agent on 3 sites, next to the warrant its authors wrote by hand (the fetch.url row comes from the LangGraph example):

Argument Recorded values Inferred rule Hand-written warrant
repo one per run: acme/site, northside/bakery-web, ... per-run value → Exact Exact(repo)
path this run's files per-run list → OneOf OneOf(paths)
branch fixagent/agent-20261018-014809, ... per-run value (or Pattern("fixagent/*")) Pattern("fixagent/*")
size 1 to 503 Range(0, 1000) (rounded headroom) Range(max=20000)
text, message, body free text Wildcard Wildcard
read_file.path files under public/, web/ under those folders, minus secrets and .. the same All([...]) shape
fetch.url pages on docs.python.org/3/library/ UrlPattern(".../3/library/*") n/a

Reads generalize to their folders or URL prefix (several hosts become AnyOf of one UrlPattern per host); writes keep their exact files. Money-like arguments (amount, price, cents, ...) never get headroom.

Per-hop delegation chains

When a LangGraph supervisor hands work to sub-agents, warrantgen chain builds one warrant per hop from the recorded node paths. Every child is minted from its parent through Tenuo's own delegation API, so Tenuo checks that each hop only narrows.

flowchart TD
    R["root, signed by the issuer<br/>search · fetch · write_file"]
    R --> T["team<br/>search · fetch"]
    T --> S["team/researcher<br/>search · fetch under docs.python.org/3/library/"]
    R --> W["writer<br/>write_file: this run's report only"]
    S -.-x SD["write_file: denied"]
    W -.-x WD["fetch: denied"]

Tested under real TenuoTool enforcement with full chains: each sub-agent runs its recorded calls, and none can use a sibling's tools. If Tenuo refuses to narrow an argument, it keeps its parent's constraint and chain says which. Since tenuo 0.3.6 that includes a URL rule listing several hosts: every child keeps the parent's full host list.

Results on a real agent

The test agent is a Temporal workflow that opens pull requests to fix a website, minting one warrant per phase. 15 recorded runs: 3 sites × 5 behaviors, including a prompt injection that tries to read .env and ../payments-api/.env. Details in examples/seo-agent.

Check Result
Held-out replay (infer from 14 runs, replay the 15th through Tenuo's authorizer) 186/186 legitimate calls allowed, 6/6 injected reads denied
Same replay with read paths deliberately unconstrained 0/6 denied (the replay can fail)
Through tenuo-cli (template validate + warrant issue) 41 templates issued, 87/87 calls allowed; 2 of 5 steps need template features proposed in tenuo#959
Generated tests against the agent's hand-written warrants 40/44 at first: 4 places looser than recorded behavior. After pinning the branch: 43/44, size kept loose on purpose

Quickstart

pip install "warrantgen[langchain]"

Record (several runs with different inputs: one run can't tell a fixed value from a per-run one):

# LangChain or LangGraph
from warrantgen.record_langchain import Recorder
graph.invoke(state, {"callbacks": [Recorder("traces.jsonl")]})

# Temporal, through Tenuo's plugin (dry_run=True with a broad warrant to observe)
from warrantgen.record_temporal import TemporalRecorder
TenuoPluginConfig(..., audit_callback=TemporalRecorder("traces.jsonl"))

Then:

warrantgen infer traces.jsonl                          # rules per step, plus quarantined runs
warrantgen template traces.jsonl --step write --set repo=acme/site --set paths=a.txt,b.txt --out write.yaml
tenuo-cli warrant issue --template write.yaml --holder agent
warrantgen chain traces.jsonl --from-run RUN           # per-hop tree, minted and checked by Tenuo
warrantgen test traces.jsonl --out test_warrants.py    # recorded calls allowed, near-misses denied

In Python: warrantgen.infer, warrantgen.mint, warrantgen.chain.mint_chains. Pass gates=["issue_refund"] and approvers=[key.public_key] to mint to require a signed approval on every call to those tools.

Test the warrants you ship

warrantgen test traces.jsonl --warrant-for my_warrants:warrant_for --known-looser size --out tests/test_recorded_calls.py

The generated file imports your warrant_for(step, values, issuer, holder_public_key) instead of warrantgen, so you can commit it with only tenuo and pytest as dependencies. Each step's recorded calls must be allowed, and near-misses must be denied: one argument just outside its rule, or a tool the step never used. --known-looser marks arguments you loosen on purpose as strict expected failures, so tightening one later flags the stale mark.

Rules

Rule Inferred when Tenuo constraint Generated near-misses
exact one value everywhere Exact (numbers: Range(n, n)) another value
per-run value one value per run, different across runs Exact, filled at mint time another value
per-run list a list that changes per run OneOf, filled at mint time a value not in the list
range numbers Range, rounded headroom max + 1
folders path argument of a read tool All([folders, Not(secret), Not(..)]) .env, ../, another folder
URL prefix URL argument of a read tool UrlPattern, or AnyOf per host another host, .. climb
prefix strings sharing a /-delimited prefix Pattern outside the prefix
subset lists of items Subset an item outside the set
any free text (whitespace or long) Wildcard none

Override a guess with --var ARG (treat as per-run) or --exact ARG (pin it).

Status

Early and opinionated. Tested against tenuo 0.3.6 and 0.3.7 (the versions the pin allows) and the local-cli stack (tenuo#942 to #952). Limits and the Tenuo issues found along the way are tracked in UPSTREAM.md.

License

Apache-2.0

Metadata

Release files for warrantgen 0.2.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 warrantgen 0.2.0
File Size Uploaded Provenance Actions
warrantgen-0.2.0.tar.gz 192.1 kB Without provenance Details for warrantgen-0.2.0.tar.gz

Built distribution (wheel)

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

Total release size: 219.6 kB

Release files / warrantgen-0.2.0.tar.gz

Download URL warrantgen-0.2.0.tar.gz
Size 192.1 kB
Tags Source
SHA-256 checksum
How to use checksums
79ad35d17f9e18dc34953bd4d934685f80fea917b358695628d4d1e329cb22c4
BLAKE2b-256 checksum
How to use checksums
0db169a1508acb1dbc7a4de06350e6c4f9af62c2aa1726da614d0b31b5ee8705
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.8 {"installer":{"name":"uv","version":"0.13.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / warrantgen-0.2.0-py3-none-any.whl

Download URL warrantgen-0.2.0-py3-none-any.whl
Size 27.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
44233fc36179bd4651e66feaf776269b2c98ac2b7594e44eb0fe14125b58839b
BLAKE2b-256 checksum
How to use checksums
aa47ba47ddb9430b12f6c0e2ea22877ed933fd4137465e05c93c21615f9c2e1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.8 {"installer":{"name":"uv","version":"0.13.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for warrantgen 0.2.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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