pyintents
Explicit capabilities and conservative call-policy analysis for Python.
Explore the docs »Overview
PyIntents offers two complementary tools:
IntentDispatcherauthorizes access to a fixed registry of trusted handlers, checks arguments, and accepts only bounded, plain JSON-like data.IntentNamespacechecks statically visible function calls before execution. It helps detect accidental policy violations in trusted application code.
Neither API is a Python sandbox. Do not run hostile Python in the same process as your secrets, policy objects, or privileged handlers. Static analysis cannot prove the complete behavior of arbitrary Python: descriptors, operators, callbacks, mutable globals, decorators, native extensions, and concurrency all matter. A decorator can also be bypassed by directly accessing the original function. Use an OS-isolated worker and a trusted broker for hostile code.
Installation
pip install pyintents
Python 3.12+ is required. The library has no runtime dependencies.
Explicit capability dispatch
Use this API when controlling AI tools, plugin requests, or privileged actions. Construct the registry and grants in trusted code, not from a client's claims.
from pyintents import Capability, IntentDispatcher, IntentViolationError
def validate_greeting(payload):
return (
set(payload) == {"name"}
and type(payload["name"]) is str
and 0 < len(payload["name"]) <= 80
)
def greet(payload):
return {"message": f"Hello, {payload['name']}!"}
dispatcher = IntentDispatcher(
{"greeting.create": Capability(greet, validate_greeting)},
grants=["greeting.create"],
)
print(dispatcher.dispatch("greeting.create", {"name": "Alice"}))
try:
dispatcher.dispatch("shell.run", {"command": "anything"})
except IntentViolationError as exc:
print(f"Blocked: {exc}")
Dispatcher guarantees and responsibilities
- Registry and grants are copied at construction; deny takes precedence.
- No dynamic import, name-to-attribute lookup,
eval, orpickleis used. - Unknown/ungranted IDs and invalid payloads are rejected before handler execution.
- Handlers and validators must be trusted synchronous functions. Generators and coroutine functions are rejected at registration.
- Validators must return exactly
True. Exceptions propagate without invoking the handler. Validators should be pure and must enforce resource-level policy: allowed file paths, network destinations, operation types, and argument ranges. - Data is copied into plain
dict,list,str,int,float,bool, andNone. Custom objects, subclasses, non-string keys, non-finite floats, and integers larger than 4096 bits are rejected. Callable payloads are never accepted. - Default limits:
max_nodes=10_000,max_depth=32,max_bytes=1_000_000. Configuredmax_depthcannot exceed 128. The string budget uses a conservative four bytes per character. Limits apply separately to each copied request/result, not to handler runtime or transport allocation. Decode size-limited requests before dispatching. - The validator receives a separate copy so it cannot rewrite handler arguments. Results are checked and copied too. A rejected result does not roll back a completed side effect. Handler exceptions propagate; dispatch never retries.
The dispatcher is an authorization component, not authentication, scheduling, an IPC server, rate limiting, persistence, or OS isolation. Keep it in a trusted process. A child process with the same unrestricted OS privileges is not enough.
Static call policies
from pyintents import IntentNamespace, IntentViolationError
namespace = IntentNamespace(uses=[print, len])
@namespace.intent()
def allowed():
print("Allowed")
return len([1, 2])
def other():
return 3
@namespace.intent()
def denied():
return other()
allowed()
try:
denied()
except IntentViolationError as exc:
print(f"Blocked: {exc}")
Rules
| Rule | Meaning |
|---|---|
uses=[helper] |
Allow precisely this function object, not another object with matching metadata |
uses=["package:helper"] |
Match diagnostic module/qualified-name metadata; trusted-code convenience only |
uses=["package.helper"] |
Match a dotted call name or normalized metadata |
uses=["helper"] |
Match a short call name; deliberately broad |
deny=[helper] |
Deny this exact object, including aliases |
without=[helper] |
Exempt this call from allowlist checks, but still traverse its body and enforce deny |
Callable instances are not accepted as rules. String rules are not unforgeable
capabilities. Unresolved/dynamic targets cannot be authorized solely by a name;
they require the explicitly unsafe allow_unknown=True setting. Default dynamic
primitive denial matches both names and builtin objects, so aliases of exec,
eval, getattr, etc. remain denied even when included in uses.
Settings
IntentNamespace(
uses=None,
recursive=True,
without=None,
uselocals=False,
usemodule=False,
deny=None,
allow_unknown=False,
deny_dynamic_primitives=True,
only_warnings=False,
strict=True,
max_nodes=10_000,
analysis_depth=100,
)
recursive=True: traverse statically resolvable child functions.uselocals=True: allow stable nested definitions; their bodies are still checked.usemodule=True: allow source-available functions with matching module metadata; requiresrecursive=Trueand is a convenience for trusted modules only.strict=True: reject unsupported implicit-execution syntax. This includes imports, classes, attribute reads outside call targets, subscripts, operators, truth tests, loops/comprehensions, context managers, formatting,await, yields, pattern matching, nested decorators/annotations, and argument unpacking. This is intentionally restrictive, not a sound safe-Python subset.strict=False: audit more ordinary Python syntax without claiming that implicit calls were checked. Use only with trusted code.allow_unknown=True,recursive=False, andonly_warnings=Trueweaken checks. Warning mode continues on policy violations, not on parse/budget failures.max_nodesandanalysis_depth: bounded graph expansion, with controlledIntentParseErroron exhaustion. Each extracted source is limited to 1,000,000 characters. No graph cache is used because bindings are mutable.
@namespace.intent(...) can add uses, deny, and without and override
recursive, uselocals, usemodule, and allow_unknown. Syntax profile and
analysis budgets belong to the namespace and cannot be weakened per decorator.
Important limitations
Even strict mode does not control behavior inside explicitly allowed opaque
functions. len(obj) can invoke obj.__len__, print(obj) can invoke
obj.__str__, and sorted(..., key=callback) invokes callbacks. A permitted
function is a trust decision, not a proof that its arguments are safe.
Source is retrieved from the actual code object without following __wrapped__.
Source files, linecache, code objects, globals, and policy configuration must all
be trusted. This does not cryptographically bind source text to executable code.
Changing bindings during/after validation is still a time-of-check/time-of-use
problem. Threads, signals, callbacks, and mutations can invalidate a decision.
Async functions preserve coroutine identity and are checked when the coroutine
starts executing, not when it is created. Strict mode rejects await in analyzed
bodies. Audit mode cannot protect across suspension points. Generators and async
generators are explicitly unsupported by the decorator.
There is no ACID/rollback guarantee. A static precheck does not make filesystem, network, or database effects transactional. Implement database transactions, idempotency, and compensation in trusted handlers where applicable.
Exceptions
All inherit from IntentError and are exported from pyintents:
IntentViolationError: a forbidden call, capability, or payload.IntentParseError: unavailable source, unsupported strict syntax, or analysis budget.IntentConfigurationError: invalid rules, handlers, settings, or function kinds.IntentShadowingError: assignment/import conflicts with a protected name.
CallTree, CallNode, get_calls, and SafeResolver are introspection utilities,
not execution guards. CallTree defaults to strict=False for inspection.
Examples and development
Run uv run python example1.py for basic policies or
uv run python example_dispatcher.py for explicit capabilities. Examples use only
harmless operations and can be run without privileged commands.
uv sync --all-groups
uv run pytest
uv run ruff check src tests
uv run mypy src/pyintents
uv run sphinx-build -W -b html docs /tmp/opencode/pyintents-docs
See the security model and migration notes for the hardening-related compatibility changes.
Metadata
Release files for pyintents 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pyintents-0.4.0.tar.gz | 15.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyintents-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.2 kB
Release files / pyintents-0.4.0.tar.gz
| Download URL | pyintents-0.4.0.tar.gz |
|---|---|
| Size | 15.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fcedae0423726093d965386332dabf0c1ad34e247f70b87b5b9ae31341f086d6
|
|
BLAKE2b-256 checksum How to use checksums |
69f8df02065ceb1c784c15f50a5c1c4014b1887267f6edca453d66a7ac55459e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / pyintents-0.4.0-py3-none-any.whl
| Download URL | pyintents-0.4.0-py3-none-any.whl |
|---|---|
| Size | 17.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ff28a986c89c8901b8e70161e6bbf04522f06888ce0ed3e97a9b70391682067c
|
|
BLAKE2b-256 checksum How to use checksums |
2b5f74eda9095f4589f6f76831136a67b8e68ec2c84e00bf61b683d2690deb47
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for pyintents 0.4.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.