NockGuard
MCP firewall for AI agent fleets. Guard your agent's tools.
NockGuard is an MCP proxy that sits between AI agents and their MCP servers. Every tool call passes through NockGuard's policy engine before reaching the server. If the call violates policy, NockGuard blocks it and returns an error to the agent.
Agent (Claude Code, Codex, Cursor, etc.)
|
v
NockGuard (MCP Proxy)
| <- Policy check: allowlist, deny, wildcard match
v
MCP Server (NockCC, GitHub, filesystem, etc.)
Install
brew install nocktechnologies/tap/nockguard
Or build from source:
go install github.com/nocktechnologies/nockguard/cmd/nockguard@latest
Quick Start
The fastest path — scaffold a default-deny starter policy, then run the firewall:
nockguard init # writes ~/.nockguard/policy.yaml (won't clobber an existing one)
nockguard proxy --upstream "npx mcp-server-nockcc" --agent my-agent
init generates a commented, default-deny policy you edit in place — no hand-writing YAML to get started. Or write one yourself:
- Create a policy file:
# policy.yaml
agents:
kit:
allow:
- "nockcc_nock_*"
- "nockcc_pipeline_*"
- "github_*"
deny:
- "nockcc_kill_switch_set"
beck:
allow:
- "nockcc_nock_*"
- "nockcc_ops_log_*"
deny:
- "nockcc_spend_*"
default:
mode: allow
deny:
- "nockcc_kill_switch_set"
- "nockcc_private_*"
- Run NockGuard as a proxy:
nockguard proxy \
--upstream "npx mcp-server-nockcc" \
--agent kit \
--policy policy.yaml
- Point your agent at NockGuard instead of the MCP server:
{
"mcpServers": {
"nockcc": {
"command": "nockguard",
"args": ["proxy", "--upstream", "npx mcp-server-nockcc", "--agent", "kit", "--policy", "policy.yaml"]
}
}
}
Policy Rules
- allow: Tool patterns the agent can use. Supports
* wildcards.
- deny: Tool patterns blocked regardless of allow rules. Deny takes precedence.
- shadow: Dry-run allowlist for observe-to-enforce staging. A tool outside
shadow records would-deny in the audit trail but is not blocked.
- ask: Tool patterns that return the
Ask verdict. NockGuard holds the call for a human approval decision before forwarding it. Denial or timeout drops the call.
- fail_mode:
deny (default) rejects malformed or ambiguous tools/call messages that NockGuard cannot safely interpret; ask parks those failures behind approval instead.
- mode:
allow (default) permits unlisted tools, deny blocks them.
- default: Fallback policy for agents not listed by name.
- validate_input (Phase 2): built-in input-validation categories applied to tool-call arguments —
sqli, path_traversal, secrets. Opt-in per agent.
- block_params (Phase 2): custom regex patterns; a tool call is blocked if any argument matches.
- rate_limit (Phase 3): at most
max_calls tool calls within a sliding window (a Go duration — 30s, 1m, 1h). The allowance refills as the window slides — this bounds bursts and runaway tool-call loops without blocking normal traffic.
- spend_cap (Phase 3): a hard cumulative ceiling of
max_calls tool calls for the whole proxy session. It never refills — once hit, every further call is blocked. This is the kill-before-runaway stop that bounds total cost for an agent left running unattended.
- trust: opt-in behavioral trust scoring. When
enabled: true, NockGuard persists a per-agent score at ~/.nockguard/trust/<agent>.json and scales the configured rate-limit cap from 0.1x to 2.0x based on recent allow/warn/deny outcomes. See docs/TRUST.md.
agents:
kit:
allow: ["read_*", "search_*"]
shadow: ["read_*", "search_*"]
ask: ["deploy_*"]
fail_mode: ask
validate_input: ["sqli", "path_traversal", "secrets"]
block_params:
- "(?i)rm\\s+-rf\\s+/"
rate_limit:
max_calls: 60
window: 1m
trust:
enabled: true
spend_cap:
max_calls: 5000
Policy verdict precedence is deny first, then ask, then normal allow/mode logic. An Ask verdict is not a warning or "flag but pass" result: the call is withheld until approval. Any state-write intents attached to the policy decision are also withheld; NockGuard applies them only after approval, and drops them on denial or timeout. The legacy require_approval key remains accepted for existing policies, but new policies should use ask.
Rate limiting and spend caps are opt-in and independent — set one, both, or neither. NockGuard sits at the MCP layer and sees tool calls, not upstream API token spend, so the spend cap is denominated in tool calls (a proxy for cost), enforced before the call reaches the server. When a call clears the allowlist and input validation but exceeds a limit, the agent receives a JSON-RPC error (rate limit exceeded / spend cap exceeded); policy-denied or input-blocked calls never consume budget. Ask calls are metered before the approval prompt, preserving the existing Phase 5 gate order.
Verify a trail in one command
Any signed audit trail can be proven intact and non-repudiable offline — no daemon, no signing key, only the public key:
nockguard verify --agent kit # one agent's trail (exit 0 = intact + authentic, 2 = tampered)
nockguard verify --all # every per-agent trail in one shot — prove the whole fleet
--all scans the audit dir for every <agent>.audit.jsonl, verifies each with that agent's own public key, and prints a per-agent summary (exit 0 = all intact, 2 = any tampered, 1 = any it could not verify). That single command replays the whole hash chain and the per-entry signatures: it proves the trail was not edited, reordered, truncated, or signed by anyone but the holder of the agent's private key. verify is the first-class form of audit verify (below), which documents HMAC vs Ed25519 signing, per-agent keys, and the compliance evidence packs.
Audit Trail (Phase 4)
NockGuard can write a structured, append-only record of every policy decision — turning the firewall from a gate into an accountable trail of what each agent attempted and what the policy did about it. Enable it with a top-level audit block:
audit:
enabled: true
path: ~/.nockguard/logs/audit.jsonl # optional; this is the default
agents:
kit:
allow: ["nockcc_nock_*"]
Each decision is one JSON object per line (JSON Lines):
{"ts":"2026-06-03T18:30:00Z","agent":"kit","tool":"nockcc_kill_switch_set","decision":"deny","reason":"deny-rule \"nockcc_kill_switch_set\""}
{"ts":"2026-06-03T18:30:01Z","agent":"kit","tool":"nockcc_nock_list","decision":"allow","reason":"allow-rule \"nockcc_nock_*\""}
decision is one of allow, deny, would-deny (shadow dry-run miss), block (input validation), ratelimit, approval-granted, approval-denied, state-write, or hide (filtered from tools/list). Auditing is opt-in (absent or enabled: false keeps Phase 1–3 behavior) and fail-open — an audit write error is logged but never blocks or fails a tool call.
reason names the specific policy rule behind each decision — deny-rule "…", allow-rule "…", no allow-rule matched, default-allow (no allow list), or no policy for agent (fail-closed) — so the trail is explainable rather than an opaque policy. The matched rule is recorded operator-side only; the error returned to the agent stays minimal (denied by policy), so a hostile agent cannot map the policy surface from rejections.
By design, NockGuard does not write raw tool-call parameters to the audit trail. Logging arguments would persist exactly the secrets and injection payloads that Phase 2 exists to keep out, so the trail records the decision (agent, tool, outcome, reason), not the payload.
Forwarding to the NockCC ops-log
NockGuard can stream enforcement decisions (deny, block, ratelimit, approval-granted, approval-denied) to the NockCC ops-log, so what the firewall blocks or parks across the fleet shows up in one Command Center feed. Allowed calls, state-write records, and tool-list hides are not forwarded — the centralized feed stays high-signal, while the local JSONL keeps the complete record.
audit:
enabled: true
forward:
enabled: true
url: https://cc.nocktechnologies.io
api_key_env: NOCKCC_API_KEY # the key is read from this env var, never stored in the policy file
Forwarding is asynchronous and fail-open: events post on a background worker, Enqueue never blocks (it drops if the buffer saturates), and any HTTP/transport error is swallowed — a slow or unreachable NockCC can never stall or fail a tool call. Severity maps as block → high (an injection / secret-exfil attempt), would-deny → info, and deny / ratelimit → warn. Misconfiguration (forwarding enabled with no url, or an api_key_env that doesn't resolve) fails loud at startup rather than silently dropping every event.
Observe to Enforce
Use the signed audit trail to derive enforcement from the tools an agent actually
used:
nockguard policy propose --agent kit
The command reads ~/.nockguard/logs/kit.audit.jsonl, extracts distinct allowed
tools, and prints a proposed shadow: allowlist. After adding that block to the
agent policy, run in observe mode and preview false positives:
nockguard policy shadow-report --agent kit
0 would-deny entries means the shadow window is clean enough for human review
before flipping mode: allow to mode: deny.
Tamper-evidence (HMAC hash-chain)
Audit entries can be signed with an HMAC hash-chain — each entry's signature covers its own content plus the previous entry's signature, so any insertion, deletion, or edit anywhere in the trail breaks the chain from that point forward. Signing is opt-in (provide a key); without one, the trail behaves exactly as the unsigned JSONL above. This closes Phase 4.
nockguard audit verify --key-env NOCKGUARD_AUDIT_KEY # exit 0 = intact, 2 = tampered
Non-repudiation (Ed25519)
HMAC is tamper-evident but symmetric: whoever verifies the trail holds the same key that produced the signatures, and could therefore forge them. Ed25519 signing closes that gap — it is asymmetric. A private key signs; the trail is verified with the corresponding public key, which cannot produce signatures. So a passing verification is proof that the holder of the private key signed every entry — the verifier never has the power to forge one. That is the difference between "this trail wasn't edited" and "this trail is non-repudiable": court-credible, and aligned with the emerging IETF agent-audit-trail direction (hash-chain + asymmetric signatures).
Generate a keypair, then point the policy at the private seed via an env var:
nockguard keygen
# NOCKGUARD_AUDIT_ED25519_KEY=<private seed — secret, never commit>
# NOCKGUARD_AUDIT_ED25519_PUB=<public key — share with verifiers>
audit:
enabled: true
sign_ed25519_key_env: NOCKGUARD_AUDIT_ED25519_KEY # hex 32-byte seed or 64-byte key; read from env, never stored in the policy file
sign_ed25519_key_env takes precedence over sign_key_env when both are set. Verify the trail with only the public key — the signer's secret is never needed to audit it:
nockguard audit verify --ed25519-pub-env NOCKGUARD_AUDIT_ED25519_PUB # exit 0 = intact + authentic, 2 = tampered or wrong signer
Live Wall
The audit trail is a file; the Live Wall makes it something you watch. nockguard-wall tails the audit JSONL and streams each policy decision to a local browser dashboard in real time, color-coded by outcome — every tool call an agent attempted and exactly what the firewall did about it. It is the visible layer over NockGuard's invisible enforcement: visceral proof of the accountability moat.
go run ./cmd/nockguard-wall # serves http://127.0.0.1:8787 (loopback = private)
go run ./cmd/nockguard-wall --demo # synthesize a sample stream when there's no live traffic
go run ./cmd/nockguard-wall --audit <path> # point at a specific audit JSONL
It binds to loopback by default (private), embeds its own page (single binary, no assets to ship), and replays the existing audit record on open so the wall is populated immediately, then streams new decisions as they land. The wall reads only the recorded decision (agent, tool, outcome, reason) — never raw tool-call parameters, consistent with the audit trail's no-payload rule.
How It Works
NockGuard intercepts two MCP methods:
- tools/list: Filters the tool list response, hiding denied tools from the agent.
- tools/call: Checks the tool name against policy before forwarding.
Deny rejects immediately. Ask holds the call for the configured approver and forwards only on approval, applying any withheld state writes after that approval. With Phase 2 validation enabled, NockGuard also scans the call's arguments (recursively, keys and values) against the configured rule categories and custom patterns, blocking injection attempts and outbound sensitive-data leaks. With Phase 3 limits enabled, a call that clears policy and validation is then metered against the agent's rate limit and spend cap. Denied, ask-denied, blocked, or over-limit calls get a JSON-RPC error response.
All other MCP traffic passes through unmodified. NockGuard is version-transparent — it works with any MCP protocol version. Input validation is opt-in: an allowlist-only policy behaves exactly as in Phase 1.
Coverage Scope
NockGuard operates at the MCP transport layer. It intercepts messages on the stdio pipe between the agent and an MCP server. That is its coverage boundary.
What NockGuard covers:
- Every
tools/list and tools/call MCP message the agent sends through the proxy.
- All policy decisions (allow, deny, ask, rate-limit, spend-cap) on those calls.
- The full audit trail and Live Wall feed for those decisions.
What NockGuard does not cover:
- Direct HTTP/REST calls an agent makes to a backend service (e.g., NockCC's REST API via
curl or an HTTP client) that bypass the MCP proxy entirely.
- Network egress to arbitrary URLs — NockGuard is not an HTTP forward proxy.
- Calls routed to MCP servers that are not wired through NockGuard (an agent can have multiple MCP servers; only the ones using
nockguard proxy as their command are gated).
This is a deliberate design constraint, not a defect. An agent that calls the backing REST API directly — rather than through its MCP server — operates outside the gated channel. The accountability guarantee is: every MCP tool call through NockGuard is policy-checked, audited, and optionally non-repudiably signed. It does not extend to direct HTTP traffic that never touches the proxy.
If your threat model includes agents switching to curl/HTTP as a bypass, enforce that at the network layer (egress firewall, outbound proxy, NockLock seccomp policy) rather than at the MCP layer. HTTP forward-proxy coverage for NockGuard is a planned feature.
Relationship to NockLock
- NockLock = secret isolation ("Fence your environment")
- NockGuard = MCP firewall ("Guard your agent's tools")
Same brand, same tap, independent products. Use one or both.
Roadmap
- Phase 1: Tool allowlists (per-agent, wildcard, default-deny)
- Phase 2: Input validation (SQLi / path-traversal / secrets rule sets + custom regex on tool-call arguments)
- Phase 3: Rate limiting and spend caps (per-agent sliding-window rate limit + hard cumulative session cap)
- Phase 4: Audit trail — structured JSONL trail, NockCC ops-log forwarding, and tamper-evident HMAC hash-chain signing
- Phase 4+: Non-repudiable audit — Ed25519 asymmetric signing (private-key signs, public-key verifies),
keygen, standards-aligned
- Phase 5: Interactive approval gates — human-in-the-loop hold on consequential tools (dedicated Telegram bot, fail-safe deny)
- Live Wall: real-time local dashboard streaming every policy decision from the audit trail
License
MIT