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

Asphallea is not on PyPI yet. Install from the repository:

pip install git+https://github.com/Asphallea/Asphallea.git

That gives you the whole policy tier: interception, deterministic allow/deny, rate and spend limits, and the JSONL audit trail, behaving identically on Linux, macOS, and Windows. It is a pure-Python install with no Rust toolchain and nothing to compile.

It does not include the asphallea-run 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.

Adding the containment tier

Two ways, and they differ in who verifies the binary.

Install a release wheel (the core is verified for you). The wheels attached to each release are platform specific and bundle a prebuilt asphallea-run together with a _core/checksums.json manifest. Before the SDK runs that binary it recomputes the SHA-256 and refuses a binary that does not match, so a swapped or patched core is rejected and the run fails closed. This is the only path where that check has something to check against.

# Linux x86_64
pip install https://github.com/Asphallea/Asphallea/releases/download/v0.0.1/asphallea-0.0.1-py3-none-linux_x86_64.whl

# macOS (universal2)
pip install https://github.com/Asphallea/Asphallea/releases/download/v0.0.1/asphallea-0.0.1-py3-none-macosx_10_9_universal2.whl

# Windows x86_64
pip install https://github.com/Asphallea/Asphallea/releases/download/v0.0.1/asphallea-0.0.1-py3-none-win_amd64.whl

Or point the SDK at 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/download/v0.0.1/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.0.1.tar.gz (296.8 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.0.1-py3-none-musllinux_1_2_x86_64.whl (292.8 kB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

asphallea-0.0.1-py3-none-manylinux_2_17_x86_64.whl (292.8 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

File details

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

File metadata

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

File hashes

Hashes for asphallea-0.0.1.tar.gz
Algorithm Hash digest
SHA256 2ba41988dfe1ad25adc239c4fdf16b621d8c9d9daa38555096c51be81b9413da
MD5 b7c509d8b8435d934233fb175a5df41a
BLAKE2b-256 4351f7e2b92a62d081ce538a4ce9bef2f4b662ef694d64202124902ceceaabfb

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for asphallea-0.0.1-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 ef98cf586ed87a7ed7a5431fe83d0cf7ddebabdba57d71c0ace92f32c6c2f84b
MD5 29f555e41ae0f016f76f54747df9887c
BLAKE2b-256 2bdd2b61641d295a40ece3986a6305bc0c386741f41f76a4549a7aa0814b5838

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for asphallea-0.0.1-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 3c94d3006467be6607ddbc540c1a3b574c8104bdcb21a6e22ad4ad18efe9ddb4
MD5 3b2acb9e96edf583d70daa9fdf9ea729
BLAKE2b-256 5afcc0ea5d4d9d8d15f6333f2721d22ad5ab5df8c4e4aad0ccca085602e4dbdb

See more details on using hashes here.

Supported by

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