5. Governance: Control Tokens and Tools¶
Delivery stage: Feature development Starting point: OpenClaw coordinates FORMAT-482, but kars decides which inference and workspace capabilities that workflow may use. Executable lab:
code/04
Start from the OpenClaw workflow¶
The Forge example does not begin with a generic shell. OpenClaw receives the FORMAT-482 issue, plans the work, and delegates bounded operations through the kars Router and Workspace MCP:
FORMAT-482
|
v
OpenClaw coordinator (KarsSandbox/forge)
|-- inference --> 127.0.0.1:8443 kars Router
|-- MCP --------> forge-workspace-mcp:8931/mcp
`-- specialists -> inference and mesh only
The Workspace MCP owns a disposable /workspace; OpenClaw does not directly
mount the repository. This separation lets policy answer four different
questions:
- Which MCP server may be registered for Forge?
- Which exact tool capabilities may the coordinator use?
- Which inputs will the Workspace MCP implementation accept?
- How many inference tokens may one request or one day consume?
The runnable definitions are in
policies.yaml
and
workspace-mcp.yaml.
Three layers, not one allow-list¶
| Layer | Forge configuration | What it prevents |
|---|---|---|
McpServer |
Seven allowedTools and allowedSandboxes label forge |
Registering an unexpected tool surface or exposing the server to unrelated Sandboxes |
ToolPolicy |
Explicit AGT capabilities, rps: 2, burst: 20, window: 1m |
A caller using capabilities absent from its loaded profile |
| Workspace MCP implementation | Normalized paths, exact replacement, fixed test IDs, CI and size restrictions | Valid tool names being abused with dangerous arguments |
These controls are complementary. An MCP tool appearing in tools/list does
not prove that every agent is authorized to call it. Conversely, a
ToolPolicy capability does not make arbitrary paths or commands safe.
Coarse MCP registration¶
Forge registers exactly seven tools:
spec:
allowedTools:
- workspace_get_task
- workspace_read_file
- workspace_search
- workspace_apply_patch
- workspace_run_test
- workspace_get_diff
- workspace_reset
allowedSandboxes:
matchLabels:
kars.azure.com/sandbox: forge
There is no shell, upload, arbitrary network request, environment dump, or free-form command tool.
Coordinator and Specialist capabilities¶
The coordinator profile names every allowed action:
allowed_actions:
- "inference:chat_completions:*"
- "inference:responses:*"
- "inference:content_safety:*"
- "spawn:*"
- "mesh:*"
- "tool:workspace_get_task:*"
- "tool:workspace_read_file:*"
- "tool:workspace_search:*"
- "tool:workspace_apply_patch:*"
- "tool:workspace_run_test:*"
- "tool:workspace_get_diff:*"
- "tool:workspace_reset:*"
Specialists receive inference and mesh capabilities only. A Specialist Pod may
still contain KARS_MCP_SERVERS=forge-workspace; hiding server registration is
not the security boundary. Its AGT profile deliberately contains no
tool:workspace_* capability.
Verify policy is loaded, not merely submitted¶
kars compiles the inline AGT profile into a ConfigMap. The Router echoes the loaded digest, and the controller reports:
ToolPolicy/forge-workspace-tools
phase: Ready
condition: Ready=True
reason: RouterEnforcing
code/04
requires the status.agtProfileDigest to equal the compiled
ConfigMap annotation. It also verifies the selector, rate limit, approval mode,
and all seven capability strings. A successful kubectl apply alone is not
sufficient evidence.
The rate limit is proven through this compiled-and-loaded contract. The lab does not flood the live Router merely to exhaust its token bucket.
Enforce inference budget at request time¶
Forge uses both per-request and daily limits:
spec:
tokenBudget:
perRequestTokens: 20000
dailyTokens: 100000
The lab first checks that the InferencePolicy compiled and loaded digests
match. It then sends a request with max_completion_tokens: 20001. The Router
returns HTTP 429 because the request exceeds the 20,000-token limit.
This is stronger evidence than inspecting YAML: it proves enforcement on the request path. Azure quota, cost alerts, and task-level loop limits remain additional safeguards.
Treat repository instructions as untrusted data¶
The disposable fixture contains a hostile README instruction referencing
collect.example. workspace_read_file can read that text, but reading text
does not grant a capability.
The direct MCP experiment sends Streamable HTTP/SSE JSON-RPC calls and verifies:
| Attempt | Verified result |
|---|---|
Read ../../etc/passwd |
Tool result has isError: true; traversal denied |
Call does_not_exist |
JSON-RPC error -32602; unknown tool rejected |
Run test ID npm-test |
Tool result has isError: true; test not approved |
Patch .github/workflows/ci.yml |
Tool result has isError: true; CI modification prohibited |
Patch src/formatUser.js with the exact expected text |
Patch succeeds |
Run named test format-user |
Fails before the patch and passes afterward |
| Request the diff | Exact unified diff contains the approved UNKNOWN fallback |
The named test maps to a fixed argument vector in the MCP implementation.
OpenClaw cannot convert repository prose into npm test, curl, or an
arbitrary shell command.
Make dependency failure explicit¶
The experiment stops its local port-forward, scales
Deployment/forge-workspace-mcp to zero, and checks that the Service has no
ready endpoint. It then restores one replica and waits until the Deployment is
Ready.
The expected behavior is explicit unavailability, not a fabricated patch or test result. The script installs an exit trap so an interrupted run also attempts to restore the MCP replica.
Run the lab¶
Keep the code/01
Forge deployment running, then execute:
cd code/04
make test
The validated host is macOS arm64. The inherited platform setup also supports macOS amd64, Linux amd64, and Windows amd64 through Ubuntu WSL2.
The lab configures and verifies Microsoft Package Feed Proxy:
npm https://packagefeedproxy.microsoft.io/npm/
PyPI https://packagefeedproxy.microsoft.io/pypi/simple/
NuGet https://packagefeedproxy.microsoft.io/nuget/v3/index.json
Each run stores policy objects, compiled profiles, JSON-RPC responses, the
budget denial, controller and Router logs, and the exact patch diff under
code/04/.evidence/<UTC timestamp>/.
Verified acceptance matrix¶
| Scenario | Decision | Evidence |
|---|---|---|
| Listed MCP surface | Allow only seven named tools | Runtime list equals McpServer.allowedTools |
| Approved patch and test | Allow | Patch response, passing format-user, unified diff |
| Path traversal, CI patch, unknown tool, unknown test | Deny | MCP and JSON-RPC error evidence |
| Specialist workspace capability | Deny by omission | Specialist AGT profile has no tool:workspace_* |
| Request above 20,000 tokens | Deny | Router HTTP 429 |
| Configured tool rate | Enforced profile | Ready=True/RouterEnforcing plus matching digest |
| Workspace MCP outage | Fail explicitly and recover | Empty endpoint set followed by Ready Deployment |
Governance is complete only when the declared policy, Router-loaded digest, and runtime behavior agree.
Official references¶
Sandbox-escape checkpoint: allowlist arguments, not only tool names¶
An edit tool is not safe merely because its name is allowlisted. The
code/04 live MCP experiment attempts to write editor settings, Agent/MCP
configuration, Git hooks, CI, and package metadata. The tool implementation
rejects every target while preserving the approved src/ patch path.
KARS keeps this authorization outside the Agent runtime. The same Router and
ToolPolicy decision remains effective when the model, prompt, or Agent
framework changes.
cd code/04
make test
This closes the self-configuration and trust-handoff paths that have appeared in public coding-agent sandbox escapes.