Railproof
The short version
AI agents can now send emails, move money, call APIs, modify tickets, and touch production systems.
The problem is not whether the model can choose a tool. The problem is whether that exact action should be allowed.
Railproof puts a deterministic security contract in front of every tool call. It checks the tool, complete arguments, user, tenant, roles, data labels, provenance, approvals, and session budgets before application code runs.
If the action is not explicitly allowed, it does not execute.
Why product teams need this
Most guardrails answer one of these questions:
- Is the request safe to discuss?
- Does the tool call match a JSON shape?
- Is this tool on an allowlist?
Those checks are useful, but they do not answer the business question:
“Should this agent be allowed to perform this exact operation for this user, with this data, in this session, right now?”
A request can be perfectly valid JSON and still be wrong. It can send data to the wrong host, use retrieved content as an email recipient, exceed a spending budget, or reuse an approval after the action changes.
Railproof is the enforcement layer for that decision.
What this repository covers
This repository contains the complete v0.1.0 alpha implementation:
| Area | What is included |
|---|---|
| Policy | A strict, versioned YAML policy language with deny-by-default behavior |
| Authorization | Deterministic rules over actions, users, tenants, roles, tools, sinks, labels, provenance, and session state |
| Execution | A wrapped tool registry that cannot run a tool call before the decision is made |
| Approvals | One-time HMAC approval tokens bound to the exact action, policy, principal, expiry, and nonce |
| Budgets | Atomic per-session call-count and numeric-sum limits |
| Evidence | Bounded redacted records with policy, action, and decision hashes |
| Integrations | Normalizers for OpenAI Chat Completions function calls and MCP tools/call requests |
| Testing | Adversarial, concurrency, property-based, replay, CLI, and integration tests |
| Operations | CLI validation, single-event checks, JSONL replay, locked builds, dependency audits, and CI |
| Benchmarking | A reproducible comparison against NeMo Guardrails 0.24.0’s built-in tool-call validator |
What this means for a product owner
You define the rules once. Every connected agent and tool call goes through the same decision contract.
1. Stop actions that are technically valid but business-invalid
JSON Schema can confirm that an email has a to field and a body. Railproof can additionally say:
- never send sensitive data to an external sink
- never let retrieved content choose an email recipient
- only call a transfer tool within a session budget
- allow weather lookups but require approval for external email
- deny unknown tools and unmatched actions
The policy does not ask the model whether the action is safe. The model has no authority to override it.
2. Require approval for high-impact actions
A rule can stop an action and return an approval challenge. The approval is cryptographically bound to the exact action.
Change the recipient, amount, tool, user, or policy and the old approval no longer works. Reuse the same approval and it is rejected.
The v0.1.0 broker is the security primitive, not a finished approval dashboard or human workflow. Your product can connect it to the approval system you already use.
3. Enforce budgets across a session
Policies can limit:
- how many times a tool is called
- how much numeric value accumulates across calls
- per-session budgets that can be applied alongside user and tenant rules
The reservation happens before execution and is atomic for concurrent calls. A burst of parallel requests cannot spend the same remaining budget twice.
4. Keep a useful decision trail
Railproof records whether a call was allowed, denied, waiting for approval, started, completed, or failed.
Built-in evidence includes identifiers, hashes, matched rules, reasons, outcomes, and a hashed principal. It does not store raw arguments, approval tokens, prompts, or tool results.
Use the in-memory sink for local work or the append-only JSONL sink for a file-based audit trail.
5. Give different agent stacks the same security contract
The policy engine is protocol-independent. The repository includes adapters that normalize:
- OpenAI Chat Completions function calls
- MCP JSON-RPC 2.0
tools/callrequests
Both become the same canonical event before policy evaluation.
6. Test policy behavior before production
The CLI can validate policies, evaluate one event, and replay JSONL decision fixtures. That gives product and security teams a way to review expected outcomes without making real tool calls.
The repository also includes a benchmark and its raw result. Railproof scored 10/10 against the benchmark’s declared requirements. NeMo Guardrails 0.24.0’s built-in tool-call validator scored 5/10.
That is a narrow comparison against one NeMo rail. It is not a claim that Railproof replaces NeMo’s conversational rails, model-backed detectors, server, or observability.
How a decision works
Agent proposes a tool call
|
v
Adapter or GuardedTools creates one canonical action snapshot
|
v
Policy validates tool, arguments, identity, labels, provenance, and session limits
|
+--+-------------------+
| |
deny allow or approval
| |
no execution reserve limits and verify approval
|
v
wrapped executor runs exact snapshot
|
v
evidence records the outcome
The important boundary is simple: application code does not receive a tool result until the action has passed the contract.
Feature detail
Strict policy compiler
Policies use the railproof.dev/v1alpha1 format. Compilation fails closed when the policy contains:
- unknown fields or duplicate keys
- an unsafe default
- undeclared tools or limits
- unsupported stages, effects, operators, or fields
- invalid JSON Schema
- schema references not supported by this alpha
- oversized policy input or excessive YAML aliases
Every compiled policy receives a deterministic SHA-256 policy hash.
Deterministic authorization
The decision engine evaluates locally with no model call and no network call. It supports:
- equality and inequality
- membership and exclusion
- numeric upper and lower bounds
- field labels such as
sensitive - provenance such as
retrieved.email - principal ID, tenant, and roles
- tool risk and sink metadata
- per-session call counts and numeric sums
Decision precedence is fixed:
deny > require approval > allow > unmatched deny
There is no implicit allow.
Exact-action binding
Railproof canonicalizes the caller’s arguments once into isolated event and execution snapshots. The hash covers:
- policy
- session
- principal identity, tenant, and roles
- tool name
- complete arguments
- labels
- provenance
This prevents a mutable or stateful caller mapping from being authorized as one action and executed as another.
Approval tokens
The built-in approval broker uses HMAC-SHA-256 and requires a signing key of at least 32 bytes. Tokens bind the action hash, policy hash, principal, approver, expiry, and random nonce.
It rejects forged, modified, malformed, expired, cross-principal, and reused tokens. Token replay protection is process-local in v0.1.0.
Safe execution wrapper
GuardedTools combines authorization and execution so application code does not need to remember a separate “check first, execute later” rule.
It supports synchronous and asynchronous executors, prevents denied executor entry, reserves budgets atomically, bounds active session state, and refuses session teardown while an execution is in flight.
Evidence sinks
Two sinks are included:
InMemoryEvidenceSinkwith bounded retention, defaulting to 10,000 recordsJsonlEvidenceSinkfor append-only newline-delimited records
The built-in record contains event/session/tool identifiers, a principal hash, action/policy/decision hashes, outcome, matched rules, and reason codes.
CLI and replay
railproof validate policy.yaml
railproof check policy.yaml event.json
railproof replay policy.yaml fixtures.jsonl
validate compiles a policy and prints its hash. check evaluates one event. replay compares actual outcomes with expected outcomes and returns a failing exit code when fixtures disagree.
Install
Once the first release is published, install Railproof from PyPI:
python -m pip install railproof
Or with uv:
uv add railproof
The package includes the runtime library and the railproof command. No repository clone is needed to use it.
Quick start from source
Requires Python 3.11+ and uv.
git clone https://github.com/ppradyoth/railproof.git
cd railproof
uv sync --group dev --group test
uv run railproof validate examples/policy.yaml
uv run python examples/demo.py
The demo shows a benign weather call, a denied sensitive email, an approval challenge, and an approved email.
Add it to an application
Register the real executor once, then route calls through GuardedTools:
from railproof.executor import GuardedTools, ToolRegistry
from railproof.models import Principal
from railproof.policy import load_policy
policy = load_policy("policy.yaml")
registry = ToolRegistry()
registry.register("get_weather", get_weather)
tools = GuardedTools(policy, registry)
result = await tools.call(
"get_weather",
{"city": "Bengaluru"},
principal=Principal(id="user-123", tenant="tenant-a"),
session_id="session-123",
)
For high-impact actions, configure an ApprovalBroker, catch ApprovalRequired, obtain approval through your application workflow, and call the same action with the returned token.
See the complete example policy and runnable demo.
What Railproof is not
This alpha is an enforcement runtime, not a complete AI platform. It does not provide:
- a model or content-moderation system
- a sandbox against code that bypasses the wrapper
- automatic taint tracking or provenance attestation
- tool-result validation after execution
- argument rewriting or policy-driven mutation
- persistent or distributed session budgets and nonce storage
- a hosted control plane, dashboard, or approval UI
- a TypeScript SDK
These are explicit boundaries. Production teams should keep the underlying executors private, default policies to deny, protect signing keys, and treat labels and provenance as application-attested inputs.
Evidence for the v0.1.0 alpha
| Gate | Result |
|---|---|
| Tests | 110 passed |
| Branch coverage | 94.18% |
| NeMo comparison | Railproof 10/10, NeMo 5/10 |
| Railproof benchmark latency | 44 µs p50, 48 µs p95, 57 µs p99 |
| NeMo benchmark latency | 492 µs p50, 517 µs p95, 575 µs p99 |
| Dependency audit | No known vulnerabilities found |
| GitHub Actions workflow audit | Zizmor: no findings |
The benchmark uses NeMo Guardrails 0.24.0’s real deterministic ToolCallRailAction._validate implementation with no model or network calls. Read the benchmark method before interpreting the comparison.
Repository map
| Path | Purpose |
|---|---|
src/railproof/ |
Runtime, policy compiler, engine, approvals, evidence, adapters, CLI, and replay |
tests/ |
Security and correctness contract tests |
examples/ |
Copy-pasteable policy and runnable demo |
benchmarks/ |
NeMo comparison harness and benchmark policy |
docs/ |
Architecture, product specification, threat model, competitive analysis, and benchmark evidence |
PROJECT_PLAN.md |
Product direction and staged roadmap |
.github/workflows/ci.yml |
Python quality matrix, dependency audit, and benchmark gate |
.github/workflows/publish.yml |
Tag-triggered, OIDC-based PyPI publishing |
Development
uv run ruff format --check .
uv run ruff check .
uv run ty check src tests examples
uv run pytest -q
uv run pip-audit
uv build
Read SECURITY.md before production use. Contributions should include an adversarial fixture, a benign control, an expected decision, and proof of whether the executor was reached. See CONTRIBUTING.md.
Documentation
- Architecture
- Product specification
- Threat model
- NeMo comparison
- Benchmark method
- Project plan
- Roadmap
Apache-2.0 licensed.
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 railproof-0.1.0.tar.gz.
File metadata
- Download URL: railproof-0.1.0.tar.gz
- Upload date:
- Size: 23.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
672c690d2eb8182048130c257d51af256d5f3a5e3ce34796cbce7acf1f826046
|
|
| MD5 |
17f7fbe92796c59b35eb914c7cfd556c
|
|
| BLAKE2b-256 |
3328c4b33c9338fcdef5fddadd140b73a3c60b3ba61df81a8e90fb78a399d85d
|
Provenance
The following attestation bundles were made for railproof-0.1.0.tar.gz:
Publisher:
publish.yml on ppradyoth/railproof
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
railproof-0.1.0.tar.gz -
Subject digest:
672c690d2eb8182048130c257d51af256d5f3a5e3ce34796cbce7acf1f826046 - Sigstore transparency entry: 2879904450
- Sigstore integration time:
-
Permalink:
ppradyoth/railproof@0447f15906f858369f1d68d67eed757658815606 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ppradyoth
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0447f15906f858369f1d68d67eed757658815606 -
Trigger Event:
push
-
Statement type:
File details
Details for the file railproof-0.1.0-py3-none-any.whl.
File metadata
- Download URL: railproof-0.1.0-py3-none-any.whl
- Upload date:
- Size: 29.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71088b49f8d2427edc69a6d9f63490adb399b307cda3912b39a005c8a2abb2bc
|
|
| MD5 |
49f1a20fdb404798b47a4d82dad3a3fc
|
|
| BLAKE2b-256 |
a292bba347fd2ea476d8071aa0978cb68bc83017ac571c10ccb37a6317c85dd7
|
Provenance
The following attestation bundles were made for railproof-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on ppradyoth/railproof
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
railproof-0.1.0-py3-none-any.whl -
Subject digest:
71088b49f8d2427edc69a6d9f63490adb399b307cda3912b39a005c8a2abb2bc - Sigstore transparency entry: 2879904462
- Sigstore integration time:
-
Permalink:
ppradyoth/railproof@0447f15906f858369f1d68d67eed757658815606 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ppradyoth
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0447f15906f858369f1d68d67eed757658815606 -
Trigger Event:
push
-
Statement type: