Asphallea
A Genovo Technologies company
A security runtime that secures what your AI agent does, not what it says.
Asphallea sits between an agent and its tools (shell, filesystem, network, and MCP tool-calls) and blocks disallowed actions by declarative policy. Enforcement is deterministic: the same tool-call against the same policy always yields the same decision, with no model in the loop, and a full audit trail of everything the agent tried.
The problem
An AI agent that runs code, calls tools, browses, and touches APIs is a new kind of privileged process. It acts on its own, and it has none of the containment we built for normal processes over the last fifty years. When an agent is prompt-injected or its tools are poisoned, it can do anything its credentials allow. It can exfiltrate data, delete infrastructure, call APIs, and spend money.
Asphallea wraps an agent's tool-execution layer and enforces a least-privilege policy on every action, with a complete audit trail. A hijacked agent can only do what the policy allows, and you can see everything it did.
This is not guardrails
Asphallea does not judge or filter what the model says. It contains what the agent does. This is an operating-systems problem wearing an AI costume: process isolation, least privilege, syscall filtering, blast-radius containment. That framing is the whole point. A pure-ML approach cannot give you kernel-level containment. Asphallea does, on Linux, Windows, and macOS, where it counts.
Two tiers
Policy tier. Cross platform. Every tool call is intercepted, checked against a declarative policy, allowed or denied deterministically, and logged. Works on Linux, macOS, and Windows. This alone is useful.
Containment tier. For high-blast-radius tools that spawn processes, execute code, or run shell commands, Asphallea contains them at the OS level using each platform's own engine. Linux gets a Landlock filesystem allowlist, seccomp-bpf syscall and network filtering, resource limits, and network-namespace isolation. Windows gets an AppContainer filesystem allowlist and network deny inside a Job Object that bounds memory, CPU, and process count and guarantees the whole process tree is killed. macOS gets a Seatbelt profile that allowlists the filesystem and denies network. This is the part a pure-ML competitor cannot replicate.
Install
pip install asphallea
On a platform with a published wheel, that is the whole product: the policy tier
and the asphallea-run core binary that enforces OS containment, with nothing to
compile and no Rust toolchain. The wheels are platform specific and each bundles the
prebuilt core together with a _core/checksums.json manifest. Before the SDK runs
that binary it recomputes the SHA-256 and refuses one that does not match, so a
swapped or patched core is rejected and the run fails closed.
Wheels are published for:
| Platform | Wheel | Containment backend |
|---|---|---|
| Linux x86_64 (glibc and musl) | manylinux_2_17, musllinux_1_2 |
Landlock + seccomp-bpf |
| macOS 11+ (Apple Silicon and Intel) | macosx_11_0_universal2 |
Seatbelt (sandbox-exec) |
| Windows x86_64 | win_amd64 |
AppContainer + Job Object |
Anywhere else — Linux aarch64, or any platform without a wheel — pip falls back to
the source distribution. That is a pure-Python install: the full policy tier
(interception, deterministic allow/deny, rate and spend limits, the JSONL audit
trail) works identically, but there is no core binary, so the containment tier is
unavailable until you supply one. In that state sandbox.run fails closed: it
refuses the command and tells you what is missing, rather than running it
uncontained. asphallea.capabilities().explain() tells you which state you are in.
Supplying a core binary yourself
Every release also publishes the standalone binary. Download the one for your
platform and set ASPHALLEA_CORE_BIN:
curl -L -o asphallea-run \
https://github.com/Asphallea/Asphallea/releases/latest/download/asphallea-run-linux-x86_64
chmod +x asphallea-run
export ASPHALLEA_CORE_BIN="$PWD/asphallea-run"
A binary you download or build has no entry in a bundled manifest, so the SDK has
nothing to verify it against. It proceeds and reports the check as none rather
than implying it verified something. If that matters to you, check the binary's
SHA-256 against the digest published on the release page yourself. Release binaries
are code-signed on Windows and macOS when signing certificates are configured.
To build the core from source, see core/. The trust model is in
SECURITY.md.
Quickstart
Define a policy once and put it between the agent and its tools. This uses only the policy tier, so it runs the same on every platform.
from asphallea import Interceptor, Policy
# One declarative policy. It governs tools it did not author (an MCP server's) by
# declaring how each tool's arguments map to resources.
policy = (
Policy.builder("agent")
.tool("filesystem.read", reads="path")
.tool("filesystem.delete", writes="path")
.read_paths("./workspace")
.write_paths("./workspace/out")
.deny_network()
.build()
)
# The choke point: decide a tool-call by name and arguments. Deterministic.
gate = Interceptor(policy)
gate.decide("filesystem.delete", {"path": "/etc/passwd"}).allowed # -> False
gate.enforce("filesystem.delete", {"path": "/etc/passwd"}) # raises PolicyViolation
Wrap an MCP session so every tool-call is decided before it runs:
from asphallea.integrations.mcp import guard_mcp_session
session = guard_mcp_session(session, policy) # a denied call never reaches the server
Or guard a Python function tool directly:
from asphallea import guard
@guard(policy, tool="filesystem.read", reads="path")
def read_file(path: str) -> str:
with open(path) as fh:
return fh.read()
@guard, the MCP adapter, and Interceptor.decide all funnel through one decision
point, so a decorated function and an MCP tool-call are decided and logged by the
same code. The full quickstart is examples/quickstart.py:
python examples/quickstart.py
The containment tier
The policy tier gates whether a tool runs. For tools that run shell commands or execute code, the containment tier contains what they then do, at the OS level, on Linux, Windows, and macOS.
If you installed a release wheel the core is already bundled, so skip this. Otherwise download the standalone binary from the releases page, or build it:
cd core
cargo build --release
export ASPHALLEA_CORE_BIN="$PWD/target/release/asphallea-run"
Then run commands under OS enforcement:
from asphallea import Policy, sandbox
policy = (
Policy.builder("shell")
.allow_tools("run_shell")
.read_paths("./workspace")
.write_paths("./workspace/out")
.deny_network()
.limits(cpu_seconds=10, memory_mb=512, max_processes=64)
.build()
)
result = sandbox.run(["bash", "-c", "echo hello > ./workspace/out/ok.txt"],
policy=policy, tool="run_shell")
print(result.returncode, result.controls)
# Contained: the read lands outside the allowlist and the OS sandbox blocks it.
blocked = sandbox.run(["bash", "-c", "cat ~/.ssh/id_rsa"], policy=policy, tool="run_shell")
print(blocked.returncode, blocked.stderr) # non-zero, permission denied
By default sandbox.run fails closed. If OS containment is not available (not
Linux, no core binary, kernel too old), it refuses to run the command and tells you
exactly what is missing. Pass allow_degraded=True to run without containment; that
is logged loudly on every call so it can never pass silently.
Check what your environment can actually enforce:
from asphallea import capabilities
print(capabilities().explain())
The demo
examples/demo.py is the whole pitch in one file. An agent is
connected to a filesystem tool server over MCP and reads a page carrying an injected
instruction that tells it to steal a credential and delete the production database.
It runs twice: once unguarded, where the attack succeeds against throwaway temp
files, and once with the MCP session wrapped in one line, where both tool-calls are
BLOCKED by policy before they run, the database is intact, the credential is never
read, and the audit log is printed. If the OS containment core is present, it adds a
run showing a shell command contained at the OS level too.
python examples/demo.py
Policy model
A policy declares, per policy:
- which tools may be called (allowlist), and which are denied outright (a denial wins)
- how each tool's arguments map to resources, so a tool it did not author can be
governed:
.tool("filesystem.delete", writes="path") - filesystem paths that are readable and writable
- network hosts that are allowed or denied (exact host or parent domain)
- per-tool call-count and rate limits
- a wall-clock timeout per call
- a spend cap, modeled as a maximum number of invocations of a paid tool
- OS resource limits for the containment tier
Build it fluently or load it from YAML. See
policies/example.yaml.
from asphallea import Policy
policy = Policy.from_yaml("policies/example.yaml")
Audit log
Every decision is written as one JSON object per line (JSONL), append-only. Each record carries the timestamp, tool, arguments (by name, the shape a tool-call has), the allow or deny decision, the reason, and the exact policy rule that fired. A redaction hook scrubs likely secrets before anything is written.
{"timestamp": "2026-07-12T18:20:01Z", "tier": "policy", "tool": "filesystem.delete", "decision": "deny", "rule": "write_paths", "reason": "write path '/etc/passwd' is not under an allowed write prefix", "policy": "agent", "args": [], "kwargs": {"path": "/etc/passwd"}}
Swap in your own audit sink or redactor. See asphallea/audit.py.
MCP
An MCP tool-call is a tool name and an arguments dict, which is exactly what the decision point takes, so guarding a session is one line. A denied tool-call never reaches the server.
from asphallea.integrations.mcp import guard_mcp_session
session = guard_mcp_session(session, policy) # raises PolicyViolation on deny
# or, to let the agent loop continue on a normal tool error:
session = guard_mcp_session(session, policy, on_deny="error")
guard_call_tool(fn, policy) wraps a bare call_tool (client or server side, sync
or async), and namespace= keeps two servers exposing the same tool name apart. The
adapter is duck-typed, so it works whether or not the mcp package is installed.
LangChain and LangGraph
Wrap existing LangChain or LangGraph tools with a policy. The adapter is duck-typed,
so it works whether or not langchain is installed.
from asphallea import Policy, Engine, AuditLog
from asphallea.integrations.langchain import guard_tool
policy = Policy.builder("lc").allow_tools("read_file").read_paths("./workspace").build()
engine = Engine(policy)
safe_tool = guard_tool(read_file, engine, reads="path", audit=AuditLog("audit.jsonl"))
# hand `safe_tool` to your agent or graph in place of the original
OpenAI and Anthropic tool-calling adapters are fast-follow.
Honest platform support
Each OS has its own containment engine, and the coverage differs. Asphallea reports what it actually enforces per dimension and never claims more. When a policy needs a dimension the local backend cannot deliver, it fails closed rather than run partially contained.
| Capability | Linux 5.13+ | Windows | macOS |
|---|---|---|---|
| Policy tier: allow/deny, allowlists, rate, spend, timeout | yes | yes | yes |
| Audit trail (JSONL) | yes | yes | yes |
| Filesystem allowlist at the OS level | yes (Landlock) | yes (AppContainer) | yes (Seatbelt) |
| Network deny at the OS level | yes (seccomp + netns) | yes (AppContainer) | yes (Seatbelt) |
| Syscall filtering | yes (seccomp-bpf) | n/a | n/a |
| Resource limits (memory, CPU, processes) | yes (setrlimit) | yes (Job Objects) | planned |
| Guaranteed process termination | yes | yes (Job Objects) | yes (process group) |
| Containment engine | Landlock + seccomp | AppContainer + Job Objects | Seatbelt |
The policy tier enforces the tool allowlist, path allowlist, rate limits, spend caps, and timeouts identically on all three. The containment tier is where the OS matters:
- Linux contains with Landlock (filesystem allowlist), seccomp (syscall and network filter), network namespaces, and setrlimit, applied to the process and everything it spawns.
- Windows contains with an AppContainer (filesystem allowlist and network deny) inside a Job Object (memory, CPU, and process-count limits, and guaranteed termination of the whole process tree). A hijacked shell command cannot read the user's files, write outside the workspace, or reach the network.
- macOS contains with a Seatbelt profile: a deny-by-default sandbox that allows the system directories a program needs to run, allows the policy's read and write paths, and denies everything else including network. Resource limits are a follow-up.
Coverage is reported per dimension. A run proceeds contained only when the backend covers every dimension the policy requires; otherwise it fails closed rather than run partially contained.
Architecture
The design and the decisions behind it are in PLAN.md. The short
version: the Python SDK is the developer-facing surface, and the Rust
core/ crate is the OS enforcement, invoked as a launcher binary that
applies containment to itself and then execs the sandboxed command. The launch essay
is in docs/why-agent-security-is-an-os-problem.md.
What v0 is not
No observe mode, no baseline learning, no anomaly detection, no ML. No dashboard, no web UI, no SaaS backend. No real-time dollar metering. No content filtering or prompt-injection detection. Asphallea contains actions. It does not judge text. These are deliberate non-goals for v0.
License
Apache-2.0. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 asphallea-0.1.1.tar.gz.
File metadata
- Download URL: asphallea-0.1.1.tar.gz
- Upload date:
- Size: 302.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eee8d36a3b9cf29a6e2076571f0fa13a134028784fb208a2b34e51833320cdcf
|
|
| MD5 |
9edb673cb9a72854f1d09750b3bb6e50
|
|
| BLAKE2b-256 |
e53c8cdd3ae477ad40b64416daf266cefe7ed1b2d24616547b211a530fe49cc5
|
Provenance
The following attestation bundles were made for asphallea-0.1.1.tar.gz:
Publisher:
release.yml on Asphallea/Asphallea
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asphallea-0.1.1.tar.gz -
Subject digest:
eee8d36a3b9cf29a6e2076571f0fa13a134028784fb208a2b34e51833320cdcf - Sigstore transparency entry: 2500260771
- Sigstore integration time:
-
Permalink:
Asphallea/Asphallea@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Asphallea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file asphallea-0.1.1-py3-none-win_amd64.whl.
File metadata
- Download URL: asphallea-0.1.1-py3-none-win_amd64.whl
- Upload date:
- Size: 140.5 kB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eacdcb2aea78f66b961e34e0a594b9146cca43e66fe43e77afa573e953266b0e
|
|
| MD5 |
ed6a5c5ae0e410645a033930e377f8cf
|
|
| BLAKE2b-256 |
e8d9c1b249fc0af3a538c1782b4b213f14929853e95f2c5f563098503ba0a7b7
|
Provenance
The following attestation bundles were made for asphallea-0.1.1-py3-none-win_amd64.whl:
Publisher:
release.yml on Asphallea/Asphallea
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asphallea-0.1.1-py3-none-win_amd64.whl -
Subject digest:
eacdcb2aea78f66b961e34e0a594b9146cca43e66fe43e77afa573e953266b0e - Sigstore transparency entry: 2500260800
- Sigstore integration time:
-
Permalink:
Asphallea/Asphallea@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Asphallea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file asphallea-0.1.1-py3-none-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: asphallea-0.1.1-py3-none-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 293.0 kB
- Tags: Python 3, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e31e2ab5eb807120a93e7bbfff3bd785067ff6e4deccde9c68a4a834328b7d81
|
|
| MD5 |
d110609ee4915800f13c5df32c79b8b1
|
|
| BLAKE2b-256 |
c68a5e3e3ca89a5f686ad50626b19f79bca10d1da62a3d69b271b0224c26bd2b
|
Provenance
The following attestation bundles were made for asphallea-0.1.1-py3-none-musllinux_1_2_x86_64.whl:
Publisher:
release.yml on Asphallea/Asphallea
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asphallea-0.1.1-py3-none-musllinux_1_2_x86_64.whl -
Subject digest:
e31e2ab5eb807120a93e7bbfff3bd785067ff6e4deccde9c68a4a834328b7d81 - Sigstore transparency entry: 2500260828
- Sigstore integration time:
-
Permalink:
Asphallea/Asphallea@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Asphallea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file asphallea-0.1.1-py3-none-manylinux_2_17_x86_64.whl.
File metadata
- Download URL: asphallea-0.1.1-py3-none-manylinux_2_17_x86_64.whl
- Upload date:
- Size: 293.0 kB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b231bd8c23ab026ddc1065628fa7c05fe33bb0c2e1f80527c5a8e6fceab659bd
|
|
| MD5 |
45164213db9b974024319d9f1a3a7f2e
|
|
| BLAKE2b-256 |
161e14aeed5d295c9cca076b0e267a489ade30fd6165c28f3359f2110b903163
|
Provenance
The following attestation bundles were made for asphallea-0.1.1-py3-none-manylinux_2_17_x86_64.whl:
Publisher:
release.yml on Asphallea/Asphallea
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asphallea-0.1.1-py3-none-manylinux_2_17_x86_64.whl -
Subject digest:
b231bd8c23ab026ddc1065628fa7c05fe33bb0c2e1f80527c5a8e6fceab659bd - Sigstore transparency entry: 2500260786
- Sigstore integration time:
-
Permalink:
Asphallea/Asphallea@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Asphallea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file asphallea-0.1.1-py3-none-macosx_11_0_universal2.whl.
File metadata
- Download URL: asphallea-0.1.1-py3-none-macosx_11_0_universal2.whl
- Upload date:
- Size: 414.9 kB
- Tags: Python 3, macOS 11.0+ universal2 (ARM64, x86-64)
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29a5fb65b53e616182c343f75a8f6775d095da029aadc8cb0e4b41d057c2349c
|
|
| MD5 |
f80fe53ba0dc6c729d03e1f09ec71250
|
|
| BLAKE2b-256 |
b5f35eb1970a421b92bf74df2daff499100ca3ebcb811ea5ccbe604ea484c17a
|
Provenance
The following attestation bundles were made for asphallea-0.1.1-py3-none-macosx_11_0_universal2.whl:
Publisher:
release.yml on Asphallea/Asphallea
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asphallea-0.1.1-py3-none-macosx_11_0_universal2.whl -
Subject digest:
29a5fb65b53e616182c343f75a8f6775d095da029aadc8cb0e4b41d057c2349c - Sigstore transparency entry: 2500260808
- Sigstore integration time:
-
Permalink:
Asphallea/Asphallea@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Asphallea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3bd5e8ccbdcbed40062577ea65d8ae56ff324fc4 -
Trigger Event:
push
-
Statement type: