The Zero-Trust Execution Governance Platform for AI Agents
An intelligent security gateway that sits between AI reasoning models and real-world tools, databases, and environments.
- Why Runwall?
- High-Level Architecture
- Core Features
- Key Workflows (Diagrams)
- Quick Start
- Client Integration
- Custom Agent Integration (Python)
- REST API & Dashboard
- License & Support
Everyone is wiring AI agents into production systems β Jira, Salesforce, internal databases, cloud providers, shell terminals β usually via protocols like the Model Context Protocol (MCP). Unprotected, these connections behave like unlocked servers:
| Risk | Description |
|---|---|
| Infinite Loop Risk | A minor agent logic bug can trigger thousands of API calls in minutes, burning your budget. |
| Prompt Injection Threat | A malicious instruction hidden in a webpage or document can hijack your agent into destructive actions (e.g. "delete all users"). |
| The Trust Gap | Binary tool permissions can't express nuance β "read one record" and "bulk export the database" look identical to a naive ACL. |
Runwall closes this gap with intent-aware, risk-scored, policy-driven execution control.
Runwall is deployed as a governance layer between your AI client (Claude Desktop, Cursor, VS Code/Cline, or a custom agent) and your actual tools/connectors.
flowchart TB
subgraph Clients["π€ AI Clients"]
A1[Claude Desktop]
A2[Cursor / VS Code / Cline]
A3[Custom Agent<br/>LangChain / CrewAI / AutoGPT]
end
subgraph Aegis["π‘οΈ Runwall Gateway"]
direction TB
Auth[Identity & Session Mgmt<br/>JWT + API Keys]
Policy[Intent-Aware Policy Engine]
Rate[Distributed Quotas &<br/>Rate Limiting]
Taint[Taint Tracking Engine]
Trust[Tool Trust & Provenance]
Approval[Async Approval Workflow]
Contracts[Task Contracts]
OPA[OPA / Rego<br/>Policy-as-Code]
Admin[Admin & Governance<br/>Controls]
REST[REST API Control Plane]
end
subgraph Tools["π Connectors & Tools"]
T1[REST API Connector]
T2[Database Connector<br/>Postgres / MySQL]
T3[Shell Connector<br/>sandboxed bash/powershell]
T4[Reversible Execution<br/>Compensation Handlers]
end
A1 & A2 & A3 -->|MCP tool call| Auth
Auth --> Policy
Policy <--> OPA
Policy --> Taint
Policy --> Rate
Policy --> Trust
Policy -->|high risk| Approval
Policy -->|multi-step job| Contracts
Policy -->|approved| Tools
Admin -.manages.-> Policy
Admin -.reviews.-> Approval
REST -.exposes.-> Admin
Tools --> T4
Reads the semantic intent of an agent's action, not just whether it has raw tool access. Actions are classified (read, write, delete, export) and scored 0.0 (safe) β 1.0 (dangerous). A single record read is auto-allowed; a bulk export triggers human approval.
Database-backed identity tracking per organization and user. Uses a dual-token architecture: short-lived JWT access tokens + long-lived refresh tokens, secured with bcrypt hashing, plus a global JTI blacklist for instant session revocation.
Authorization: Bearer <JWT_access_token>
Machine-friendly credentials for service accounts, cron jobs, and event-driven agents. Keys are 32-byte tokens prefixed mcp_, shown once, and stored only as a SHA-256 hash. Keys can be scoped per environment and locked to CIDR IP allowlists.
Authorization: Bearer mcp_abc123...[secret_hash_key]
Multi-dimensional limiter tracking tenant, user, and tool-level quotas simultaneously. Adaptive throttling halves the rate limit automatically when a session's risk score exceeds 0.7. Backed by Redis for distributed clusters.
Exposed as secure Admin MCP Tools / REST APIs:
manage_policyβ create, update, soft-delete rulesexplore_audit_logsβ query historical tool usageget_decision_logsβ inspect the exact evaluation chain behind an allow/deny decision
The core defense against prompt injection. Reading tools (e.g. fetch_webpage) are taint sources β running them labels the session (e.g. EXTERNAL_WEB). Writing tools (e.g. execute_sql) are taint sinks. Any sink call is blocked while an untrusted taint label is active on the session.
An automated "undo" registry pairing mutating tools with rollback handlers.
@tool(is_reversible=True, compensation_handler="rollback_create_user")
async def create_user(username: str):
...Admins can later call rollback_action(execution_id="rev-abc123") to invoke the compensation handler with the original arguments.
Cryptographically hashes tool source code and descriptions at boot. Unauthorized code edits flip a tool's state to QUARANTINED, blocking execution until an admin calls approve_tool_trust_state(tool_name).
High-risk actions don't fail β they pause. The agent receives an approval_id, a human reviews it (dashboard or REST), and once approved the agent calls execute_approved_action(approval_id) to complete execution.
Upfront sandboxed boundaries for multi-step jobs, avoiding "approval fatigue." An agent declares a goal, expected tools, write limits, and a spend cap once; subsequent calls within those bounds skip individual policy checks.
{
"goal": "Refactor authentication module",
"expected_tools": ["read_file", "write_file", "git_commit"],
"max_writes": 12,
"max_spend": 25.0
}Point Runwall at a database URL or OpenAPI spec and it auto-generates fully governed MCP tools:
- RestAPIConnector β turns HTTP endpoints into tools
- DatabaseConnector β generates
sql_query/sql_executefor Postgres/MySQL - ShellConnector β sandboxed bash/powershell execution
All generated tools automatically inherit taint tracking, risk scoring, and rate limits.
Version-controlled, GitOps-friendly policy files written in Rego, evaluated via the OPA binary or an in-memory fallback. Supports Simulation Mode β dry-run new rules against live traffic without affecting agent behavior.
package execution.governance
deny[msg] {
input.intent.intent_category == "delete"
input.user_context.role != "admin"
msg := "Only administrators can perform delete operations."
}A FastAPI server exposing full CRUD endpoints and Swagger docs, powering dashboards and monitoring integrations at http://localhost:8000/docs.
Every tool call β regardless of client β passes through the same governance pipeline.
sequenceDiagram
participant Agent as AI Agent
participant Aegis as Runwall Gateway
participant Policy as Policy Engine (+ OPA)
participant Tool as Target Tool/Connector
Agent->>Aegis: tools/call (name, arguments)
Aegis->>Aegis: Authenticate (JWT / API Key)
Aegis->>Aegis: Check quotas & rate limits
Aegis->>Policy: Evaluate intent + risk score
Policy->>Policy: Check taint labels on session
alt Risk low & session clean
Policy-->>Aegis: ALLOW
Aegis->>Tool: Execute
Tool-->>Agent: Result
else Risk high or destructive
Policy-->>Aegis: REQUIRE_APPROVAL
Aegis-->>Agent: approval_id (paused)
else Session tainted + sink tool
Policy-->>Aegis: DENY (taint block)
Aegis-->>Agent: Blocked - untrusted data in session
end
flowchart LR
A[Agent calls fetch_webpage] -->|Taint Source| B[Session labeled: EXTERNAL_WEB]
B --> C{Agent attempts<br/>execute_sql / send_slack_msg?}
C -->|Yes - Taint Sink| D[π« BLOCKED by Policy Engine]
C -->|No sink tool called| E[β
Continues normally]
sequenceDiagram
participant Agent as AI Agent
participant Aegis as Runwall
participant Admin as Human Admin
Agent->>Aegis: Attempt high-risk action
Aegis-->>Agent: REQUIRE_APPROVAL (approval_id)
Aegis->>Admin: Slack/dashboard notification
Admin->>Aegis: POST /api/v1/approvals/{id}/review (APPROVED)
Aegis-->>Agent: Approval granted
Agent->>Aegis: execute_approved_action(approval_id)
Aegis-->>Agent: β
Execution completes
flowchart TB
A[Agent proposes contract:<br/>goal, tools, max_writes, max_spend] --> B{Admin/Policy<br/>approves contract?}
B -->|No| C[π« Rejected - fallback to per-call checks]
B -->|Yes| D[contract_id issued]
D --> E[Agent runs many tool calls<br/>tagged with contract_id]
E --> F{Within declared tools,<br/>write limit & budget?}
F -->|Yes| G[β
Auto-allowed, no repeated prompts]
F -->|No - exceeds bounds| H[π« Falls back to individual policy check]
flowchart LR
A[Agent calls mutating tool<br/>e.g. create_user] --> B[Execution archived<br/>with execution_id + args]
B --> C[Tool runs normally]
C --> D{Mistake or<br/>unauthorized action?}
D -->|Yes| E[Admin calls<br/>rollback_action execution_id]
E --> F[Compensation handler invoked<br/>rollback_create_user]
F --> G[β
State restored]
D -->|No| H[No action needed]
No complex setup required. Connect your AI client directly to the hosted Runwall endpoint.
Runwall Public Gateway
URL: https://mcp.runwall.in/mcp
Quickstart: https://mcp.runwall.in/
Transport: Streamable HTTP (MCP Specification)
Auth: API Key (Authorization: Bearer <api_key>) β strictly required
Important
API Key Required: An API key is required for every request to https://mcp.runwall.in/mcp. There is no unauthenticated or anonymous access. Unauthenticated requests are immediately rejected with HTTP 401.
Claude Desktop / Cursor / VS Code β Remote URL
{
"mcpServers": {
"runwall": {
"url": "https://mcp.runwall.in/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}Claude Desktop / Cursor / VS Code β Local stdio wrapper
{
"mcpServers": {
"runwall": {
"command": "npx",
"args": ["-y", "@runwall/mcp"],
"env": {
"RUNWALL_API_KEY": "YOUR_API_KEY",
"RUNWALL_URL": "https://mcp.runwall.in/mcp"
}
}
}
}Claude API / Hosted Agent Platforms
{
"mcp_servers": [
{
"type": "url",
"name": "runwall",
"url": "https://mcp.runwall.in/mcp",
"authorization_token": "YOUR_API_KEY"
}
],
"tools": [
{
"type": "mcp_toolset",
"mcp_server_name": "runwall"
}
]
}Run Runwall on your own infrastructure:
docker run -d -p 8000:8000 \
-e SECRET_KEY=your-production-secret-key \
-e DATABASE_URL=your-database-url \
dushyantzz/secure-mcp-server:latestThen connect your client to http://localhost:8000/mcp (or put it behind HTTPS with a reverse proxy).
import httpx
async def run_governed_tool(api_key, tool_name, arguments):
headers = {"Authorization": f"Bearer {api_key}"}
payload = {
"method": "tools/call",
"params": {"name": tool_name, "arguments": arguments}
}
response = await httpx.post(
"https://mcp.runwall.in/mcp",
json=payload,
headers=headers,
)
return response.json()That's it β your agent is now authenticated, rate-limited, taint-tracked, audited, and protected against prompt injection and runaway costs.
| Client | Recommended Method | Config Key / Reference |
|---|---|---|
| Claude Desktop / Claude Code | Stdio wrapper or Remote URL | url: https://mcp.runwall.in/mcp or npx -y @runwall/mcp |
| Cursor | Stdio wrapper or Remote URL | url: https://mcp.runwall.in/mcp or npx -y @runwall/mcp |
| VS Code (Cline) | Stdio wrapper or Remote URL | url: https://mcp.runwall.in/mcp or npx -y @runwall/mcp |
| Windsurf | Stdio wrapper or Remote URL | url: https://mcp.runwall.in/mcp or npx -y @runwall/mcp |
| Trae / Qoder / Copilot | Settings panel configuration | Configure via the built-in MCP panel (Stdio connection) |
| KIRO / Codex / Custom Agents | HTTP POST to /mcp gateway |
Authorization: Bearer YOUR_KEY (Streamable HTTP) |
Transport Support:
- Primary: Streamable HTTP at
https://mcp.runwall.in/mcp(recommended) - Legacy: SSE at
https://mcp.runwall.in/sse(backward compatible)
Register a connector so Runwall auto-generates governed tools instead of hand-writing them:
# Example: register a database connector
connector_config = {
"type": "DatabaseConnector",
"connection_string": "postgresql://user:pass@host:5432/db"
}
# Runwall auto-generates sql_query / sql_execute tools,
# each inheriting taint tracking, risk scoring, and rate limits.Runwall ships a FastAPI control plane with full Swagger documentation:
http://localhost:8000/docs
Common endpoints:
POST /api/v1/approvals/{id}/reviewβ approve/deny a pending high-risk actionGET /api/v1/audit-logsβ query historical tool executionGET /api/v1/policiesβ inspect/manage active policy rulesPOST /api/v1/tools/{name}/approve-trustβ re-baseline a quarantined tool
Runwall is distributed as a Docker image (dushyantzz/secure-mcp-server:latest). For issues, feature requests, or policy authoring help, consult the in-app Swagger docs at /docs or your internal platform team.
Integrate the Runwall remote gateway into your LangChain workflow using the standard MCP client bridge:
from langchain_openai import ChatOpenAI
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
# Define connection parameters
server_params = StdioServerParameters(
command="npx",
args=["-y", "@runwall/mcp"],
env={
"RUNWALL_API_KEY": "YOUR_API_KEY",
"RUNWALL_URL": "https://mcp.runwall.in/mcp"
}
)
# Establish connection
async with stdio_client(server_params) as (read_stream, write_stream):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
tools = await session.list_tools()
# Bind governance-wrapped tools to LLM
llm = ChatOpenAI(model="gpt-4o").bind_tools(tools)Expose Runwall tools to your CrewAI agents. CrewAI automatically leverages tool descriptions and schemas to invoke them safely:
from crewai import Agent
from crewai.tools import tool
import httpx
@tool("Runwall Tool Runner")
def runwall_tool(tool_name: str, arguments: dict) -> str:
"""Executes a tool securely routed through the Runwall governance gateway."""
headers = {"Authorization": "Bearer YOUR_API_KEY"}
payload = {
"method": "tools/call",
"params": {"name": tool_name, "arguments": arguments}
}
res = httpx.post("https://mcp.runwall.in/mcp", json=payload, headers=headers)
return res.text
# Define Governed DBA Agent
dba_agent = Agent(
role="Database Administrator",
goal="Safely query and mutate database records",
backstory="An automated DBA operating strictly under enterprise security rules.",
tools=[runwall_tool],
verbose=True
)Register Runwall's remote MCP tools to an AutoGen Conversational Agent:
import autogen
import httpx
config_list = [{"model": "gpt-4", "api_key": "YOUR_OPENAI_KEY"}]
assistant = autogen.AssistantAgent(name="governed_assistant", llm_config={"config_list": config_list})
user_proxy = autogen.UserProxyAgent(name="user_proxy", code_execution_config=False)
@user_proxy.register_for_execution()
@assistant.register_for_llm(description="Secure math calculator governed by Runwall policies")
def calculator(expression: str) -> str:
headers = {"Authorization": "Bearer YOUR_API_KEY"}
payload = {"method": "tools/call", "params": {"name": "calculator", "arguments": {"expression": expression}}}
res = httpx.post("https://mcp.runwall.in/mcp", json=payload, headers=headers)
return res.json().get("result", {}).get("content", [{}])[0].get("text", "Error")Built for the era of autonomous AI agents, because "can it access the tool" is no longer the right question.
