agent-sandbox

agent-sandbox

Enables AI coding agents to run Kubernetes inspection and Terraform plan/apply operations inside ephemeral gVisor-sandboxed jobs with short-lived, narrowly-scoped credentials, while routing destructive changes through a human approval gate.

Category
访问服务器

README

agent-sandbox

I built this to let an AI coding agent run real infrastructure commands against a real Kubernetes cluster — without ever holding a standing credential, and without being able to destroy anything unsupervised.

Three MCP tools. Every call runs inside a gVisor-sandboxed Kubernetes Job with a short-lived, narrowly-scoped credential I have Vault mint for that single action. Anything destructive stops at a human approval gate.

AI Agent (Claude Desktop / Cursor)
        │  MCP protocol (stdio)
        ▼
┌──────────────────────────────────────────┐
│  MCP Server            src/agent_sandbox │
│    3 tools -> guardrails -> broker ->    │
│    sandbox -> audit                      │
└───────┬──────────────────────┬───────────┘
        │                      │
        ▼                      ▼
┌────────────────┐   ┌──────────────────────────┐
│ Credential     │   │ Sandbox Runner           │
│ Broker (Vault) │   │ K8s Job + gVisor         │
│ 10-min leases  │   │ restricted PSS           │
│ per-action     │   │ default-deny NetworkPolicy│
│ scope          │   │ cpu/mem limits, deadline │
└────────────────┘   └──────────────────────────┘
        │                      │
        └──────────┬───────────┘
                   ▼
        ┌──────────────────────┐
        │ Guardrails + Approval│
        │ policy.yaml, SQLite, │
        │ agent-sandbox CLI    │
        └──────────────────────┘

Why I built this

An AI tool I was using once proposed a Terraform change against production infrastructure that would have forced replacement of a live resource. The plan looked routine. The failure mode wasn't the model being wrong — it was that nothing sat between a plausible-looking plan and a destructive apply.

I built this project as the missing layer, in working code:

  • the agent never holds a credential it could reuse
  • everything runs somewhere it can't hurt the host
  • destructive changes stop and wait for a person
  • every action is on the record

make demo reproduces the exact scenario I ran into. A one-line label edit forces replacement of a running Deployment, and the gate catches it.

Quick start

Requires Docker, kind, kubectl, vault, terraform, and Python 3.11+.

brew install kind kubectl hashicorp/tap/vault terraform
make up      # ~5 minutes from cold: cluster, CNI, gVisor, Vault, image, verify
make demo    # the forces-replacement guardrail demo
make down    # tear it all down

make up is idempotent. It finishes by running make verify, which proves the isolation claims rather than asserting them (see below).

Point an agent at it

cp examples/claude_desktop_config.json \
   ~/Library/Application\ Support/Claude/claude_desktop_config.json

Cursor: copy examples/cursor_mcp.json into .cursor/mcp.json. Then ask the agent to "check pod status in demo-app" or "plan the k8s-demo terraform".

The three tools

Tool Risk Behaviour
k8s_get_pod_status(namespace) low Runs immediately. Credential scoped to get/list/watch pods in one namespace.
terraform_plan(working_dir) low Runs immediately. Saves the plan so a later apply executes exactly the reviewed diff.
terraform_apply(working_dir, approval_id?) high Without approval_id: computes the plan, records a pending approval, applies nothing. With one: spends the approval and applies the saved plan.

The four components

1. Sandboxed execution — src/agent_sandbox/sandbox.py

One throwaway Job per tool call. Every control is there for a specific reason:

Control Prevents
runtimeClassName: gvisor Syscalls hit the gVisor sentry, not the host kernel
PSS restricted, enforced by the API server root, privilege escalation, capabilities, writable rootfs
automountServiceAccountToken: false Any ambient cluster identity inside the sandbox
default-deny NetworkPolicy + API-server allowlist Internet egress, lateral movement, metadata endpoints
resources.limits, activeDeadlineSeconds A runaway job starving the node or hanging forever
backoffLimit: 0 A failed destructive action being silently retried

The credential is mounted as a file, never an env var — env vars leak through kubectl describe, /proc, and crash dumps.

2. Credential broker — src/agent_sandbox/broker.py

cred = broker.issue_scoped_credential("k8s_get_pod_status")
# -> Vault mints a ServiceAccount + Role + RoleBinding, 10-minute lease
# -> revoked immediately after the Job finishes
  • The agent never picks its own scope. Scope is derived from the action.
  • Deny by default. An action with no mapped scope gets no credential.
  • Blast radius is enforced. Requesting any namespace but the target is refused.
  • The token never leaves the module. Credential.__repr__ prints token=<redacted>, so even an accidental log can't leak it.

I verified this by hand: a pod-reader token lists pods in demo-app, is denied in kube-system, is denied on secrets, and stops working the moment its lease is revoked — leaving no ServiceAccount behind.

3. Guardrails — policy/policy.yaml, src/agent_sandbox/guardrails.py

Deny by default: registering an MCP tool is not enough to make it callable. A tool absent from the policy is refused, so adding capability requires a deliberate risk-tier decision.

Approvals are hardened against the obvious attacks:

  • single-use — consumed inside one SQLite transaction, so two concurrent applies can't spend the same approval
  • parameter-bound — bound to a hash of the exact tool + parameters, so an approval for k8s-demo can't be replayed against prod-cluster
  • expiring — 30 minutes by default
  • out-of-band — granted via a separate CLI process. There is no MCP tool to approve anything; the agent has no code path to approve its own request.

4. MCP server — src/agent_sandbox/server.py

Built on the official Python SDK (mcp 2.0, MCPServer). The transport layer is deliberately thin and grants no authority of its own — a bug there cannot widen what the agent can do, because the policy and the API server's Pod Security admission are the actual controls.

Audit log

Every call emits a correlated event trail to var/audit.jsonl:

tool.request -> guardrail.decision -> credential.issued -> sandbox.started
   -> sandbox.completed -> credential.revoked -> tool.result
make audit
./.venv/bin/agent-sandbox audit --request-id req-4239b8bb5459 --json

Credential values are scrubbed recursively before write; scope, lease id and TTL are kept. A test asserts no JWT-shaped string ever reaches the log.

Verified, not assumed

Two things in this project are easy to claim and quietly not have, so I didn't take them on faith. make verify tests both against the live cluster:

== 1. gVisor kernel check ==
     kernel reported: Linux version 4.19.0-gvisor
  PASS: sandbox runs on the gVisor sentry kernel
== 2. NetworkPolicy egress enforcement check ==
  PASS: baseline connectivity works (got PONG)
  PASS: default-deny egress enforced (traffic blocked)

This caught a real problem while I was building it. kind's default CNI (kindnet) accepts NetworkPolicy objects and silently ignores them — I applied a default-deny egress policy and pod-to-pod traffic still got through. The sandbox would have looked locked down while having full network access. I fixed it by disabling kindnet and installing Calico, which enforces for real. See scripts/install-calico.sh.

I hit a related trap allowlisting the API server: the ClusterIP doesn't work, because kube-proxy DNATs to the real endpoint before Calico evaluates egress. The symptom was a sandbox that just hung with no policy-denied event to explain it. Documented in scripts/apply-sandbox-policy.sh.

Honest limitations

  • gVisor runs, but this is still kind. I installed runsc inside the kind node (a container in Docker Desktop's Linux VM) and verified it's active. That's a real gVisor sandbox, not a production-hardened node.
  • The AWS/STS path is conditional. scripts/vault-setup.sh only configures Vault's AWS secrets engine when real AWS credentials are present; without them it's skipped and says so. I didn't want to fake that path just to make the demo look complete. The live, demonstrable credential path is the Kubernetes one, which is fully real: dynamic ServiceAccounts, real RBAC, real leases, real revocation.
  • Vault runs in dev mode — in-memory, root token root, no seal. Fine for a local project, not something I'd deploy as-is.
  • Destructive-signal detection is string matching on plan output. It's a surfacing aid for the human, not a security boundary — terraform_apply is already high-tier and gated regardless of what the scan finds.
  • Single-node cluster, so the PVC holding Terraform state is ReadWriteOnce on one node.

Layout

cluster/      kind config, RuntimeClass, namespaces, RBAC, network policy
images/       sandbox runner image (terraform + kubectl, providers vendored)
policy/       guardrail policy: risk tiers and destructive signals
scripts/      up/down, gVisor + Calico install, verification, demo
src/          the package: broker, sandbox, guardrails, approvals, audit, MCP
terraform/    demo module managed by the agent
tests/        56 unit tests + a real-stdio MCP integration check

Testing

make test       # 56 unit tests, no cluster required
make test-mcp   # drives the server over real MCP stdio (needs the stack up)
make verify     # proves gVisor + NetworkPolicy enforcement on the live cluster

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选