agent-sandbox
agent-sandbox
저는 AI 코딩 에이전트가 실제 Kubernetes 클러스터에 대해 실제 인프라 명령을 실행할 수 있도록 이 프로젝트를 만들었습니다. 상시 자격 증명(standing credential)을 보유하지 않고, 감독 없이 아무것도 파괴할 수 없도록 말이죠.
세 가지 MCP 도구가 있습니다. 모든 호출은 gVisor 샌드박스 Kubernetes Job 안에서 실행되며, Vault가 해당 단일 작업을 위해 발급한 수명이 짧고 범위가 좁은 자격 증명을 사용합니다. 파괴적인 작업은 모두 사람의 승인 게이트에서 멈춥니다.
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 │
└──────────────────────┘왜 만들었나
제가 사용하던 AI 도구가 한 번 프로덕션 인프라에 대해 Terraform 변경을 제안했는데, 그 변경은 실행 중인 리소스를 강제로 교체하게 만들 수 있었습니다. 계획은 평범해 보였습니다. 실패 모드는 모델이 틀린 것이 아니라, 그럴듯한 계획과 파괴적인 apply 사이에 아무것도 없었다는 점이었습니다.
저는 이 프로젝트를 누락된 계층으로, 실제 작동하는 코드로 만들었습니다:
에이전트는 재사용할 수 있는 자격 증명을 절대 보유하지 않습니다.
모든 것은 호스트를 해칠 수 없는 곳에서 실행됩니다.
파괴적인 변경은 멈추고 사람을 기다립니다.
모든 작업은 기록에 남습니다.
make demo는 제가 겪었던 정확한 시나리오를 재현합니다. 한 줄의 레이블 편집이 실행 중인 Deployment의 교체를 강제하고, 게이트가 이를 잡아냅니다.
Related MCP server: safe-runbook-mcp
빠른 시작
Docker, kind, kubectl, vault, terraform, 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 downmake up은 멱등적입니다. 마지막에 make verify를 실행하여 격리 주장을 단순히 주장하는 대신 증명합니다(아래 참조).
에이전트를 연결하기
cp examples/claude_desktop_config.json \
~/Library/Application\ Support/Claude/claude_desktop_config.jsonCursor: examples/cursor_mcp.json을 .cursor/mcp.json에 복사하세요. 그런 다음 에이전트에게 "demo-app에서 pod 상태 확인" 또는 *"k8s-demo terraform 계획"*을 요청하세요.
세 가지 도구
도구 | 위험 | 동작 |
| 낮음 | 즉시 실행됩니다. 자격 증명은 한 네임스페이스의 |
| 낮음 | 즉시 실행됩니다. 나중에 apply가 검토된 diff를 정확히 실행할 수 있도록 계획을 저장합니다. |
| 높음 |
|
네 가지 구성 요소
1. 샌드박스 실행 — src/agent_sandbox/sandbox.py
도구 호출당 일회용 Job 하나. 모든 제어는 특정 이유로 존재합니다:
제어 | 방지하는 것 |
| 시스템 호출이 호스트 커널이 아닌 gVisor 센트리에 도달합니다. |
PSS | root, 권한 상승, capabilities, 쓰기 가능한 rootfs |
| 샌드박스 내부의 모든 주변 클러스터 ID |
기본 거부 | 인터넷 이그레스, 측면 이동, 메타데이터 엔드포인트 |
| 노드를 굶기거나 영원히 멈추는 폭주 작업 |
| 실패한 파괴적 작업이 조용히 재시도되는 것 |
자격 증명은 파일로 마운트되며, 환경 변수로는 절대 마운트되지 않습니다. 환경 변수는 kubectl describe, /proc, 크래시 덤프를 통해 누출됩니다.
2. 자격 증명 브로커 — 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에이전트는 자신의 범위를 선택할 수 없습니다. 범위는 작업에서 파생됩니다.
기본적으로 거부합니다. 매핑된 범위가 없는 작업은 자격 증명을 받지 못합니다.
폭발 반경이 강제됩니다. 대상이 아닌 네임스페이스를 요청하면 거부됩니다.
토큰은 모듈을 벗어나지 않습니다.
Credential.__repr__는token=<redacted>를 출력하므로, 우발적인 로그에도 누출될 수 없습니다.
제가 직접 확인했습니다: pod-reader 토큰은 demo-app에서 pod를 나열하고, kube-system에서는 거부되며, secrets에서는 거부되고, 임대가 해지되는 순간 작동을 멈춥니다. ServiceAccount는 남기지 않습니다.
3. 가드레일 — policy/policy.yaml, src/agent_sandbox/guardrails.py
기본적으로 거부: MCP 도구를 등록한다고 해서 호출 가능한 것은 아닙니다. 정책에 없는 도구는 거부되므로, 기능을 추가하려면 의도적인 위험 등급 결정이 필요합니다.
승인은 명백한 공격에 대해 강화되어 있습니다:
일회용 — 단일 SQLite 트랜잭션 내에서 소비되므로, 두 개의 동시 apply가 동일한 승인을 사용할 수 없습니다.
매개변수 바인딩 — 정확한 도구 + 매개변수의 해시에 바인딩되므로,
k8s-demo에 대한 승인은prod-cluster에 재생될 수 없습니다.만료 — 기본 30분.
대역 외 — 별도의 CLI 프로세스를 통해 부여됩니다. 승인할 MCP 도구가 없으므로, 에이전트는 자신의 요청을 승인할 코드 경로가 없습니다.
4. MCP 서버 — src/agent_sandbox/server.py
공식 Python SDK(mcp 2.0, MCPServer)를 기반으로 구축되었습니다. 전송 계층은 의도적으로 얇고 자체적으로 권한을 부여하지 않습니다. 그곳의 버그는 에이전트가 할 수 있는 것을 넓힐 수 없습니다. 실제 제어는 정책과 API 서버의 Pod Security admission이기 때문입니다.
감사 로그
모든 호출은 상관된 이벤트 추적을 var/audit.jsonl에 기록합니다:
tool.request -> guardrail.decision -> credential.issued -> sandbox.started
-> sandbox.completed -> credential.revoked -> tool.resultmake audit
./.venv/bin/agent-sandbox audit --request-id req-4239b8bb5459 --json자격 증명 값은 쓰기 전에 재귀적으로 삭제됩니다. 범위, 임대 ID, TTL은 유지됩니다. 테스트는 JWT 형태의 문자열이 로그에 도달하지 않음을 단언합니다.
검증됨, 가정되지 않음
이 프로젝트에는 주장하기 쉽고 조용히 없는 두 가지가 있으므로, 저는 그것들을 믿지 않았습니다. make verify는 라이브 클러스터에서 둘 다 테스트합니다:
== 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)이것은 제가 만드는 동안 실제 문제를 잡아냈습니다. kind의 기본 CNI(kindnet)는 NetworkPolicy 객체를 수락하고 조용히 무시합니다. 기본 거부 이그레스 정책을 적용했는데도 pod 간 트래픽이 여전히 통과했습니다. 샌드박스는 완전한 네트워크 액세스 권한을 가지면서 잠긴 것처럼 보였을 것입니다. kindnet을 비활성화하고 실제로 강제하는 Calico를 설치하여 수정했습니다. scripts/install-calico.sh를 참조하세요.
API 서버 허용 목록에서 관련 함정을 발견했습니다: ClusterIP는 작동하지 않습니다. kube-proxy가 Calico가 이그레스를 평가하기 전에 실제 엔드포인트로 DNAT하기 때문입니다. 증상은 정책 거부 이벤트 없이 샌드박스가 그냥 멈추는 것이었습니다. scripts/apply-sandbox-policy.sh에 문서화되어 있습니다.
솔직한 한계
gVisor는 실행되지만 여전히 kind입니다. kind 노드(Docker Desktop의 Linux VM 안의 컨테이너)에
runsc를 설치하고 활성화되었는지 확인했습니다. 그것은 실제 gVisor 샌드박스이지만 프로덕션에 강화된 노드는 아닙니다.AWS/STS 경로는 조건부입니다.
scripts/vault-setup.sh는 실제 AWS 자격 증명이 있을 때만 Vault의 AWS secrets 엔진을 구성합니다. 없으면 건너뛰고 그렇게 말합니다. 데모를 완성해 보이기 위해 그 경로를 가짜로 만들고 싶지 않았습니다. 라이브, 입증 가능한 자격 증명 경로는 Kubernetes 경로이며, 완전히 실제입니다: 동적 ServiceAccount, 실제 RBAC, 실제 임대, 실제 해지.Vault는 개발 모드로 실행됩니다 — 메모리 내, 루트 토큰
root, 봉인 없음. 로컬 프로젝트에는 괜찮지만, 그대로 배포할 것은 아닙니다.파괴적 신호 감지는 계획 출력의 문자열 일치입니다. 인간을 위한 표면화 보조 도구이지 보안 경계가 아닙니다.
terraform_apply는 이미 높은 등급이며 스캔 결과와 관계없이 게이트됩니다.단일 노드 클러스터이므로 Terraform 상태를 보유하는 PVC는 한 노드에서
ReadWriteOnce입니다.
레이아웃
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테스트
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 clusterAvailable Tools
3 toolsk8s_get_pod_statusGet pod statusA
Read-only. Lists pods and their phase in the target namespace, executed inside a gVisor sandbox with a credential scoped to get/list/watch pods in that one namespace. Runs immediately; no approval needed.
| Name | Required | Description | Default |
|---|---|---|---|
| namespace | No | demo-app |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full disclosure burden and does it well: it states read-only semantics, gVisor sandbox isolation, the exact credential scope (get/list/watch on one namespace), and that no approval gate exists. It stops short of pagination or error behavior, but the safety and permission profile is unusually well covered for an annotation-free tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with the safety-critical 'Read-only.' qualifier followed by scope, environment, and approval status. Every sentence adds information; no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-format explanation is unnecessary, and the description covers read-only status, execution environment, credential scope, and approval behavior for a single-parameter tool. The one remaining gap is that the namespace default and format are never mentioned in either the description or the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for the lone parameter. It only gestures at 'the target namespace' without naming the parameter, giving the default value ('demo-app'), or indicating accepted namespace formats. The agent must open the schema to learn anything actionable about the input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb and resource ('Lists pods and their phase') plus an explicit scope ('in the target namespace'), so the agent knows exactly what is returned. It does not differentiate from siblings, but the siblings (terraform_plan/terraform_apply) are an unrelated domain, so no routing confusion exists. Minor tension: the name says 'pod status' (singular) while the description lists pods (plural).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Read-only' and 'Runs immediately; no approval needed' imply when this is safe to call, and the namespace scoping implies the context. There is no explicit when-to-use/when-not statement or named alternative, so usage is only implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
terraform_applyTerraform applyA
HIGH RISK -- mutates real infrastructure. Calling without approval_id does NOT apply anything: it computes the plan, records a pending approval, and returns an approval id for a human to review out-of-band. Once a human has approved, call again with that approval_id to execute the reviewed plan. Approvals are single-use, expiring, and bound to these exact parameters.
| Name | Required | Description | Default |
|---|---|---|---|
| approval_id | No | ||
| working_dir | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden and does so well: it flags HIGH RISK mutation, explains that the no-approval path is non-destructive (computes plan, records pending approval), and discloses approval lifecycle traits (single-use, expiring, parameter-bound). This is far beyond what a bare 'apply' would convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four tight sentences with the risk warning and the two-phase contract front-loaded; no filler. Dense but readable, and every clause adds operational information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Output schema exists so return-value documentation is unnecessary, and the description still names the key return (approval id). Combined with the risk warning and approval lifecycle, an agent has everything needed to call this correctly and safely.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate; it fully explains approval_id semantics (optional, single-use, expiring, must match exact parameters, absence means no apply). working_dir is never explained, which is the one remaining gap, but the high-risk parameter is covered.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('mutates real infrastructure', Terraform apply) and immediately clarifies its two-phase nature, which separates it from terraform_plan's pure-preview role. An agent can tell what this does and when it would fire without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit conditional workflow: call without approval_id to compute a plan and register a pending approval, then call again with the returned approval_id to execute. It also states the human review happens out-of-band, so the agent knows not to wait on this call for the mutation to occur.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
terraform_planTerraform planA
Computes a Terraform diff for a module under the terraform/ root. Runs immediately in the sandbox and persists the plan so a subsequent apply can execute exactly the reviewed diff. Output is annotated when the plan contains destructive changes such as forced replacement.
| Name | Required | Description | Default |
|---|---|---|---|
| working_dir | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses immediate sandbox execution (no confirmation gate), that the plan is persisted as durable state, and that output is annotated on destructive changes like forced replacement. It omits auth/permission requirements and failure behavior, which keeps it out of 5 territory.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences with no filler, front-loaded with what the tool does before the sandbox/persistence and destructive-change details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the description covers execution model, persistence, and destructive-change signaling. The main residual gap is parameter semantics for working_dir, which neither schema nor description pins down.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for the single working_dir parameter, so the schema adds nothing. The description hints that the module lives 'under the terraform/ root', which weakly constrains the path, but it never states whether working_dir is absolute, relative, or relative to that root.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Computes a Terraform diff for a module'), and scopes it to the terraform/ root. It also implicitly distinguishes itself from the sibling terraform_apply by framing itself as the review step that a 'subsequent apply' consumes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for use: it runs immediately and produces a persisted plan that a later apply executes, which tells the agent this belongs before terraform_apply. It stops short of explicit when-not-to-use guidance or naming the alternative tool directly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
k8s_get_pod_status - First observed
terraform_apply - First observed
terraform_plan
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: read-only Kubernetes pod status, a Terraform plan diff, and a gated Terraform apply. The plan/apply pair is explicitly differentiated by the approval workflow, so an agent can reliably choose the right tool.
Names use consistent snake_case with a domain prefix and a verb (k8s_get_pod_status, terraform_plan, terraform_apply). The k8s tool adds an object noun while the Terraform tools do not, a minor deviation but still predictable.
Three tools across two distinct domains (Kubernetes and Terraform) is thin for an agent sandbox. Each tool earns its place, but the surface feels minimal relative to the apparent infrastructure-management scope.
Kubernetes coverage is limited to reading pod phase with no logs, events, describe, or any mutating operation, and Terraform lacks init/validate, destroy, and state inspection. The core plan-then-apply lifecycle is well covered, but notable gaps remain for a sandbox meant to work with real infrastructure.
Maintenance
Related MCP Connectors
Fail-closed policy guardrails for AI agents running kubectl, terraform, helm, and argocd.
Security gateway for AI agents: policy, approval, and audited execution, no secrets shared.
Security reviews for coding agents: diffs checked against your org policy and live infrastructure.
- emisarOAuthdev.emisar
Let AI operate servers without SSH. Choose actions, approve risky changes, and audit every step.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceGive AI agents Zero-Trust access to production infrastructure without the risks of granting them shell access. Actions are bounded by policy and an on-host runner.337-
- AlicenseAqualityBmaintenanceEnables AI agents to safely inspect and execute version-controlled operational runbooks with policy checks, dry-run planning, and out-of-band approvals.3MIT
- AlicenseAqualityBmaintenanceEnables AI coding agents to plan, policy-check, and cost-estimate Terraform changes while requiring human approval through Slack or CLI before any terraform apply can execute.98 npmMIT
- AlicenseAqualityBmaintenanceEnables AI coding agents to safely execute commands, run tests, and modify project files inside disposable, policy-enforced Docker sandboxes that are isolated from the host machine and its credentials.15MIT