Skip to main content

Asphallea

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.

License Mode Policy tier Containment MCP

A prompt-injected agent, contained

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

asphallea-0.1.0.tar.gz (298.9 kB view details)

Uploaded Source

Built Distributions

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

asphallea-0.1.0-py3-none-win_amd64.whl (140.6 kB view details)

Uploaded Python 3Windows x86-64

asphallea-0.1.0-py3-none-musllinux_1_2_x86_64.whl (292.9 kB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

asphallea-0.1.0-py3-none-manylinux_2_17_x86_64.whl (292.9 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

asphallea-0.1.0-py3-none-macosx_11_0_universal2.whl (414.9 kB view details)

Uploaded Python 3macOS 11.0+ universal2 (ARM64, x86-64)

File details

Details for the file asphallea-0.1.0.tar.gz.

File metadata

  • Download URL: asphallea-0.1.0.tar.gz
  • Upload date:
  • Size: 298.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for asphallea-0.1.0.tar.gz
Algorithm Hash digest
SHA256 74274dcaad9d016cafd61b1ed31ca71715ca1506899b0e71f02ef63a4648ecb0
MD5 7d88e429204009104b5f2b474b835e4f
BLAKE2b-256 6ed6f6b6ac95361b8b3880bd40213a3af05b96a0be2cfa60bf3fbf0ed99d24fe

See more details on using hashes here.

File details

Details for the file asphallea-0.1.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: asphallea-0.1.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 140.6 kB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for asphallea-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 c6c3dacf481eaf9e35b2d914521a28bff429181fbd1a059592ba863efb2022fa
MD5 e3233c7590f54f7156c8791214e42f4e
BLAKE2b-256 70b2b9ff61d643eeba0126b7387db6ddc5eb7317a0a11788edfbe5fe4ac2647d

See more details on using hashes here.

Provenance

The following attestation bundles were made for asphallea-0.1.0-py3-none-win_amd64.whl:

Publisher: release.yml on Asphallea/Asphallea

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

File details

Details for the file asphallea-0.1.0-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for asphallea-0.1.0-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 61514c5285192066280398f5a7fcbbbfeab79832d0029bf2192a73e35019c244
MD5 4796df928a091895c9f9b5f8b1bf6c00
BLAKE2b-256 d9a510983817a61e95afa8326f84d1bc9611f973edf5d209f6dab6a0980dd4b4

See more details on using hashes here.

File details

Details for the file asphallea-0.1.0-py3-none-manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for asphallea-0.1.0-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 e8953380842d26d470eefd469fc2ea0454c54e9f1a4072674845a9de4850a641
MD5 8acc4f5ad3c713e7c4d96e623191b3b3
BLAKE2b-256 eb231d23be2c7050d73af64c45d532ac94b3c57e0171b2d2608f6f53b22f8cd2

See more details on using hashes here.

File details

Details for the file asphallea-0.1.0-py3-none-macosx_11_0_universal2.whl.

File metadata

File hashes

Hashes for asphallea-0.1.0-py3-none-macosx_11_0_universal2.whl
Algorithm Hash digest
SHA256 79e840fd02d5a241c9b2e68e01e677235803520e64b7ccf679d529ff277e3e5e
MD5 38b0908c88e13cd63b4365e639fcce89
BLAKE2b-256 9fdd618448c1e9793a33a1ec017c137cc42eb10e06990ca38ba778849fe97786

See more details on using hashes here.

Provenance

The following attestation bundles were made for asphallea-0.1.0-py3-none-macosx_11_0_universal2.whl:

Publisher: release.yml on Asphallea/Asphallea

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page