pyintents
Declarative capability-based access control for Python functions.
Explore the docs »Getting Started · Basic Usage · Latest Documentation · License
📖 Overview
PyIntents brings capability-based security to Python.
Functions declare what they are allowed to do through an @intent decorator, and PyIntents enforces these permissions at call time. You define namespaces with allow and deny rules, recursively validate call chains, exempt trusted helpers when needed, and keep unknown or dynamic behavior blocked by default.
PyIntents is useful for:
- plugin sandboxes;
- AI agent tool control;
- environment-specific security policies;
- testing and dependency isolation;
- security audits and explicit capability boundaries.
Trust explicitly. Fail safely.
PyIntents is not a full operating-system-level sandbox. It is a declarative policy layer for Python functions. For strong isolation of untrusted code, combine it with process isolation, containers, WASM, or a dedicated sandboxing solution.
🚀 Getting Started
Installation
pip install pyintents
Python 3.12+ is recommended.
Quick Example
from pyintents import IntentNamespace
# Allow only print()
namespace = IntentNamespace(uses=[print])
@namespace.intent()
def safe_function():
print("This is allowed") # OK
@namespace.intent()
def unsafe_function():
import os
os.system("echo bad") # IntentViolationError
By default, PyIntents validates the function before execution.
If a forbidden or unknown call is found anywhere in the statically visible call chain, the decorated function is not executed.
🛡️ Basic Usage
1. Allow Specific Functions
from pyintents import IntentNamespace
namespace = IntentNamespace(uses=[print, len])
@namespace.intent()
def my_func():
print("Hello") # Allowed
return len([1]) # Allowed
2. Recursive Enforcement
Recursive validation is enabled by default.
namespace = IntentNamespace(uses=[print])
def helper():
print("Inside helper")
@namespace.intent(uses=[helper])
def main():
helper()
PyIntents checks not only main, but also what helper calls.
3. Allow Functions From the Same Module
If your module has many internal helper functions, allowing each one manually can be tedious.
Use usemodule=True:
namespace = IntentNamespace(
uses=[print],
recursive=True,
usemodule=True,
)
def inner():
print("Inner")
def outer():
print("Outer")
inner()
@namespace.intent()
def func():
outer()
With usemodule=True:
- functions defined in the same module as the decorated function are allowed;
- their calls are still recursively validated;
- functions from other modules are not automatically allowed.
usemodule requires recursive=True.
4. Allow Local Nested Functions
uselocals=True allows functions defined inside the decorated function.
namespace = IntentNamespace(
uses=[print],
uselocals=True,
)
@namespace.intent()
def main():
def local_helper():
print("Local helper")
local_helper()
Important:
uselocals=Trueallows nested local functions;- it does not automatically allow module-level global functions.
For module-level helpers, use usemodule=True or explicit uses=[...].
5. Exempt Trusted Functions From Allowlist Checks
without exempts a function from allowlist checks, but deny rules are still enforced.
def helper():
print("OK")
namespace = IntentNamespace(
uses=[print],
without=[helper],
)
@namespace.intent()
def main():
helper()
Important:
without does not mean “ignore everything inside this function forever”.
It means:
- this function does not need to be explicitly allowed by
uses; - but forbidden calls inside it can still be rejected.
6. Explicit Denial
import os
from pyintents import IntentNamespace
namespace = IntentNamespace(
uses=[print],
deny=[os.system],
)
@namespace.intent()
def restricted():
print("OK")
os.system("echo bad") # Explicitly denied
You can also use string rules:
namespace = IntentNamespace(
uses=[print],
deny=["os.system"],
)
7. Runtime Layering
Decorator-level rules extend or override namespace-level rules.
base = IntentNamespace(uses=[print])
@base.intent(uses=[len])
def layered_func():
print("Hi")
return len("world")
8. Unknown and Dynamic Calls Are Blocked by Default
PyIntents is fail-closed by default.
Unknown calls are denied unless explicitly allowed or unless allow_unknown=True is set.
Dynamic primitives such as:
eval
exec
compile
__import__
getattr
setattr
delattr
globals
locals
vars
breakpoint
are denied by default.
You can disable this behavior with:
IntentNamespace(deny_dynamic_primitives=False)
but this is discouraged.
📦 API Reference
IntentNamespace
IntentNamespace(
uses=None,
*,
recursive=True,
without=None,
uselocals=False,
usemodule=False,
deny=None,
allow_unknown=False,
deny_dynamic_primitives=True,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
uses |
Iterable[Callable or str] |
None |
Explicitly allowed functions or names |
recursive |
bool |
True |
Recursively validate called functions |
without |
Iterable[Callable or str] |
None |
Exempt from allowlist checks only |
uselocals |
bool |
False |
Allow nested functions defined inside the decorated function |
usemodule |
bool |
False |
Allow functions from the same module. Requires recursive=True |
deny |
Iterable[Callable or str] |
None |
Explicitly forbidden functions or names |
allow_unknown |
bool |
False |
Allow unresolved or opaque calls |
deny_dynamic_primitives |
bool |
True |
Deny dynamic primitives like eval, exec, getattr, etc. |
@namespace.intent()
Overrides or extends namespace settings per function.
@namespace.intent(
uses=[print],
recursive=True,
without=[helper],
uselocals=True,
usemodule=True,
deny=[os.system],
allow_unknown=False,
)
def custom_func():
pass
⚠️ Exceptions
IntentViolationError
Raised when a function violates declared permissions.
from pyintents import IntentNamespace, IntentViolationError
namespace = IntentNamespace(uses=[print])
@namespace.intent()
def bad():
import os
os.system("echo bad")
try:
bad()
except IntentViolationError as exc:
print(exc)
IntentParseError
Raised when function source code is unavailable or cannot be parsed.
from pyintents.exceptions import IntentParseError
IntentConfigurationError
Raised when namespace or decorator configuration is invalid.
from pyintents.exceptions import IntentConfigurationError
🔧 How It Works
PyIntents performs static policy validation before the decorated function is executed.
The pipeline is:
-
AST Parsing PyIntents parses the function source code using Python's
astmodule. -
Call Graph Construction It builds a tree of statically visible function calls.
-
Safe Resolution Call names are resolved to actual function objects when possible.
-
Rule Matching Each call is validated against:
uses;deny;without;uselocals;usemodule;- unknown-call policy;
- dynamic-primitive policy.
-
Pre-Execution Enforcement If a violation is found, the decorated function is not executed.
Example:
func -> outer -> inner -> os.system
If os.system is forbidden or unknown, PyIntents blocks the root call to func before any code inside func runs.
🧠 Security Model
PyIntents follows a fail-closed model.
By default:
- only explicitly allowed calls are permitted;
- recursive validation is enabled;
- unknown calls are denied;
- dynamic primitives are denied;
- functions without available source code are treated carefully;
- violations prevent execution.
Pre-Execution Validation
PyIntents validates the call chain before execution.
This means:
@namespace.intent()
def func():
print("Func")
outer()
If outer eventually calls something forbidden, func will not execute at all.
This is intentional.
It prevents partially executed functions from producing side effects before a violation is detected.
⚠️ Limitations
PyIntents is a static and runtime policy layer, not a complete sandbox.
Python is highly dynamic, so some behavior cannot be fully analyzed statically.
Examples:
getattr(os, "system")("echo bad")
eval("os.system('echo bad')")
globals()["os"].system("echo bad")
PyIntents mitigates many of these cases by denying dynamic primitives by default, but no AST-only solution can guarantee complete isolation.
For strong security boundaries, use:
- subprocesses;
- containers;
- seccomp;
- WASM;
- RestrictedPython;
- custom import hooks;
- runtime monitoring.
🎯 Use Cases
| Use Case | Description |
|---|---|
| Plugin Sandboxes | Restrict what third-party plugins can do |
| AI Agent Control | Limit tool access for LLM agents |
| Environment Policies | Enforce different rules per environment |
| Testing | Isolate unit tests from external dependencies |
| Security Audits | Document and enforce capability boundaries |
| Internal APIs | Prevent accidental access to dangerous helpers |
📚 Documentation
📄 License
Licensed under the GNU General Public License v3.0.
See LICENSE for details.
🤝 Contributing
Contributions are welcome.
Feel free to:
- open issues;
- submit pull requests;
- suggest features;
- improve documentation;
- report security concerns.
🌟 Support
If you find PyIntents useful, consider:
- ⭐ starring the repository on GitHub;
- 🐛 reporting issues;
- 💡 suggesting features;
- 📖 improving documentation.
Trust explicitly. Fail safely.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyintents-0.2.0.tar.gz.
File metadata
- Download URL: pyintents-0.2.0.tar.gz
- Upload date:
- Size: 11.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
026bfe26ad345e330a87b5a1f13242d04d9fa7c9e5bdd9de3c4ef866139f61fa
|
|
| MD5 |
bb93f2d30bb495018adabbc66b000e64
|
|
| BLAKE2b-256 |
ce854723d41760a83e0c5061b4642dc754a6b82b472b5432c57418c8c1d231a2
|
File details
Details for the file pyintents-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pyintents-0.2.0-py3-none-any.whl
- Upload date:
- Size: 13.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a7bb4efae40a393b8b2632d01fe77e672495f5631c4fe38c0c9a047f42bcaa4
|
|
| MD5 |
fc30ebf302496eeb14b0a297c769642f
|
|
| BLAKE2b-256 |
fea8ad31f14acc8761e197a59c7f25fe6972e3446529c97e67fb6e96bb24ed2a
|