AgentFence is a local tool-call firewall. It evaluates the exact MCP tool and arguments, decides allow / deny / ask before forwarding, and writes a redacted audit receipt you can verify offline.

flowchart LR

A["Agent tool call"] --> B{"AgentFence policy"}

B -->|ALLOW| C["MCP server"]

B -->|DENY / ASK| D["Stop before side effect"]

B --> E["Redacted receipt"]

The maintained, offline demo runs AgentFence in front of a real MCP stdio

session. An allowed read returns prompt-injected text; the resulting .env

write is denied before the upstream server sees it.

git clone https://github.com/dgenio/agentfence.git

cd agentfence

./examples/demo-blocked-call.shAgentFence flagship MCP demo

ALLOW filesystem.read path=project-notes.txt -> upstream

DENY filesystem.write path=.env -> BlockedByPolicy before upstream

PROOF upstream received tools: ["filesystem.read"]

PASS safe read executed; injected .env write blocked before side effect.

The demo's tiny policy, exact MCP requests, and expected audit receipt are committed and exercised in CI. The full command also prints the normalized receipt and verifies its hash chain. The scenario explanation shows how the proof works.

Security boundary: AgentFence governs tool calls that pass through its configured CLI/proxy boundary. It is not a sandbox and does not prevent prompt injection; it limits what a successful injection can cause at mediated tool boundaries. Calls that bypass AgentFence are outside its control, and

askrequires a trustworthy approval path. See CLAIMS and when not to use AgentFence.

AgentFence is a single, local Go binary with no account or built-in telemetry. It can wrap an MCP stdio server, gate a streamable-HTTP server, or evaluate recorded tool calls in CI. Policies are deny-by-default; audit logs can be hash-chained and signed.

Who it's for: security and platform operators who need to gate agents they did not write, with a policy and audit trail they control.

New here? Follow the 10-minute Quickstart — from install to a policy-gated MCP setup with an observed allow and deny. Then see the Daily Driver guide for day-to-day operation and CLAIMS for what AgentFence does and does not promise.

Generate inspectable authorization evidence for the exact calls and policy

AgentFence evaluated. VeriCordon turns a policy, representative call JSONL,

and optional policy fixtures into a human-readable report.md plus a versioned

report.json.

- uses: dgenio/agentfence/evidence-action@v0.10.0

with:

policy: agentfence.yaml

calls: testdata/tool-calls.jsonl

tests: testdata/policy-tests.yamlThe safe downloadable artifact excludes raw audit.jsonl by default. Missing

evidence stays partial / not_evaluated; exact-action + effective-policy

binding is reported only for the calls whose supplied audit evidence supports

it. No account or hosted service is required.

See the VeriCordon authorization-evidence guide for the copy/paste workflow, minimal inputs, a real missing-evidence example, the fresh-consumer 3/3 binding result, and the explicit non-claims.

AgentFence is in active development. The table below distinguishes what works today from what is planned. Do not assume planned features are usable yet.

Tool-capable agents are useful, but they can also be risky:

- Prompt injection can trigger unsafe calls.

- Agents may take destructive actions too quickly.

- Sensitive values can leak into logs.

- Teams need an audit trail of what was allowed, denied, or sent for approval.

AgentFence is a practical local control point before execution.

curl -fsSL https://raw.githubusercontent.com/dgenio/agentfence/main/scripts/install.sh | shThe script detects your OS/arch, downloads the matching release archive, and

verifies it against checksums.txt (failing closed on a mismatch) before

installing to ~/.local/bin. Pin a version or change the directory with

AGENTFENCE_VERSION / AGENTFENCE_INSTALL_DIR.

brew install dgenio/tap/agentfenceInstalls the binary plus shell completions and the man page. The tap is updated automatically on each release.

scoop bucket add dgenio https://github.com/dgenio/scoop-bucket

scoop install agentfence

# or

winget install dgenio.agentfenceA minimal, non-root, multi-arch (amd64/arm64) image is published to GHCR:

docker run --rm ghcr.io/dgenio/agentfence:latest versionSee docs/distribution.md for

running the HTTP proxy in a container with a mounted policy and audit-log

volume.

Pre-built binaries for Linux, macOS, and Windows (amd64 and arm64) are

published on the GitHub Releases

page for each tagged release. Each release includes a checksums.txt, a cosign

signature, and an SBOM for verification (see

docs/distribution.md).

Download the archive matching your platform, extract it, and put the

agentfence binary on your PATH. Archives also bundle shell completions

(completions/) and the man page (manpages/agentfence.1).

go build -o agentfence ./cmd/agentfenceTo embed a release version at build time (compatible with goreleaser):

go build -ldflags "-X main.Version=0.1.0" -o agentfence ./cmd/agentfenceOr via the project Makefile:

make build VERSION=0.1.0Run the built-in demo:

./agentfence demoRun policy checks against example tool calls:

./agentfence check --policy examples/policy.yaml --call examples/tool-calls.jsonlWrite audit events to an append-only, owner-readable log:

./agentfence check --policy examples/policy.yaml --call examples/tool-calls.jsonl --audit-log audit.jsonlSign each event (writer authentication), rotate the log, and ship a copy to an external sink — then verify the chain and the signatures offline:

./agentfence audit keygen --private audit.key --public audit.pub

./agentfence check --policy examples/policy.yaml --call examples/tool-calls.jsonl \

--audit-log audit.jsonl --tamper-evident --sign-key audit.key \

--audit-max-size 10485760 --audit-keep 5 --audit-sink syslog://127.0.0.1:514

./agentfence audit verify --log audit.jsonl --pubkey audit.pubPublish an anchor so a third party can later detect silent deletion or truncation, then check the log against it:

./agentfence audit anchor --log audit.jsonl --out audit.anchor.json # commit this somewhere you don't control

./agentfence audit verify --log audit.jsonl --anchor audit.anchor.jsonSign the anchor so a verifier can confirm it was not itself swapped for one naming an earlier event:

./agentfence audit anchor --log audit.jsonl --out audit.anchor.json --sign-key audit.key

./agentfence audit verify --log audit.jsonl --anchor audit.anchor.json --anchor-pubkey audit.pubGet machine-readable output for CI pipelines:

./agentfence check --policy examples/policy.yaml --call examples/tool-calls.jsonl --output json

./agentfence check --policy examples/policy.yaml --call examples/tool-calls.jsonl --output jsonl | jq '.decision'check --summary <file> writes a compact JSON gate summary (per-decision

counts, top denied tools/reasons, and whether --fail-on matched) alongside the

decision stream, so CI can surface "what was denied" without recomputing it with

jq. Write it to a file for a clean machine-readable artifact — it is

produced even when --fail-on fails the run. (--summary - writes to stderr

instead, which is convenient for logs but, on a gate failure, also carries

diagnostic lines and so is not pure JSON.)

./agentfence check --policy examples/policy.yaml --call examples/tool-calls.jsonl \

--no-interactive --fail-on deny --output json --summary gate-summary.jsonpolicy test and audit verify share the same --output text|json convention

as check, explain, and audit summarize, so every gate the pipeline runs

can be consumed structurally while preserving each command's exit code:

./agentfence policy test --policy examples/policy.yaml --tests examples/policy-tests.yaml --output json

./agentfence audit verify --log audit.jsonl --output jsonValidate a policy file before use (catches typos and unknown fields):

./agentfence validate --policy examples/policy.yamlcheck exposes three operator controls for the ask decision and for

"evaluate without enforcing" workflows:

- --no-interactive— never prompt; auto-deny any- askdecision. The audit reason is- non-interactive: ask auto-denied. Use this in CI.

- --approval-timeout <duration>— bound the wait for a y/N response (e.g.- 30s,- 2m). On expiry the call is denied with reason- approval timeout.- 0(the default) waits forever.

- --dry-run— evaluate policy and write audit records but never invoke the approver and never propagate a non-zero exit from- --fail-on. Each audit record carries- "mode": "dry_run"so downstream readers can distinguish simulated decisions from enforced ones. Text output is suffixed with- [dry-run].

Typical CI invocation:

./agentfence check \

--policy policy.yaml --call calls.jsonl \

--no-interactive --approval-timeout 30s --fail-on deny,askRun the same input through --dry-run first to see what would happen without

failing the pipeline.

Initialize a starter policy in your current directory:

./agentfence initCheck the installed version:

./agentfence versionList commands and flags:

./agentfence --helpRun AgentFence as an MCP stdio proxy in front of any MCP server:

./agentfence proxy \

--policy examples/policy.yaml \

--audit-log audit.jsonl \

-- \

npx -y @modelcontextprotocol/server-filesystem /path/to/workspaceOr gate a remote MCP server reached over streamable HTTP / SSE:

./agentfence proxy-http \

--policy examples/policy.yaml \

--upstream https://mcp.example.com/mcp \

--listen 127.0.0.1:8787 \

--audit-log audit.jsonlPoint your MCP client at http://127.0.0.1:8787; AgentFence evaluates each

tools/call with the same decision, redaction, approval, and audit semantics

as the stdio proxy, and relays everything else (including SSE streams)

transparently. See docs/integration-guide.md

for Claude Code and VS Code configuration, audit-log inspection, and

troubleshooting.

Scaffold a policy from curated policy packs instead of starting from a blank file:

./agentfence init --pack filesystem,github,shellThis writes one pack file per surface plus an agentfence.yaml that imports

them; redeclare any tool key in agentfence.yaml to override a pack rule.

Summarise an existing audit log to see which tools are most active and which rules dominate the deny pile:

./agentfence audit summarize --log audit.jsonl

./agentfence audit summarize --log audit.jsonl --output json --top 20The text output reports totals, decision counts, schema versions, top tools

(overall, denied, allowed), and top reasons. The JSON output has the same

fields under stable snake_case keys for automation. Malformed JSONL lines are

counted as malformed and never abort the run.

Running the built-in demo against the bundled example tool calls produces the output below. The first line of each pair is the human-readable decision; the second line is the JSONL audit event with secret-looking values redacted.

$ ./agentfence demo

AgentFence demo:

call_001 filesystem.read -> allow (tool filesystem.read matched explicit policy rule)

{"schema_version":"4","session_id":"<session_id>","seq":1,"timestamp":"<rfc3339>","call_id":"call_001","tool":"filesystem.read","decision":"allow","reason":"tool filesystem.read matched explicit policy rule","reason_code":"rule_match","arguments":{"path":"README.md"}}

call_002 filesystem.write -> deny (path ".env" denied by pattern ".env")

{"schema_version":"4","session_id":"<session_id>","seq":2,"timestamp":"<rfc3339>","call_id":"call_002","tool":"filesystem.write","decision":"deny","reason":"path \".env\" denied by pattern \".env\"","reason_code":"path_denied","arguments":{"content":"OPENAI_[REDACTED:generic_secret_assignment]","path":".env"}}

call_003 github.create_issue -> ask (tool github.create_issue matched explicit policy rule)

{"schema_version":"4","session_id":"<session_id>","seq":3,"timestamp":"<rfc3339>","call_id":"call_003","tool":"github.create_issue","decision":"ask","reason":"tool github.create_issue matched explicit policy rule","reason_code":"rule_match","arguments":{"body":"Created by an agent","repo":"dgenio/agentfence","title":"Demo issue"}}

call_004 github.delete_repo -> deny (tool github.delete_repo matched explicit policy rule)

{"schema_version":"4","session_id":"<session_id>","seq":4,"timestamp":"<rfc3339>","call_id":"call_004","tool":"github.delete_repo","decision":"deny","reason":"tool github.delete_repo matched explicit policy rule","reason_code":"rule_match","arguments":{"repo":"dgenio/agentfence"}}

./agentfence check against the example policy and tool calls produces the

same decisions plus a one-line summary:

$ ./agentfence check --policy examples/policy.yaml --call examples/tool-calls.jsonl

call_001 filesystem.read -> allow (tool filesystem.read matched explicit policy rule)

{"schema_version":"4","session_id":"<session_id>","seq":1,"timestamp":"<rfc3339>","call_id":"call_001","tool":"filesystem.read","decision":"allow","reason":"tool filesystem.read matched explicit policy rule","reason_code":"rule_match","arguments":{"path":"README.md"}}

call_002 filesystem.write -> deny (path ".env" denied by pattern ".env")

{"schema_version":"4","session_id":"<session_id>","seq":2,"timestamp":"<rfc3339>","call_id":"call_002","tool":"filesystem.write","decision":"deny","reason":"path \".env\" denied by pattern \".env\"","reason_code":"path_denied","arguments":{"content":"OPENAI_[REDACTED:generic_secret_assignment]","path":".env"}}

call_003 github.create_issue -> ask (tool github.create_issue matched explicit policy rule)

{"schema_version":"4","session_id":"<session_id>","seq":3,"timestamp":"<rfc3339>","call_id":"call_003","tool":"github.create_issue","decision":"ask","reason":"tool github.create_issue matched explicit policy rule","reason_code":"rule_match","arguments":{"body":"Created by an agent","repo":"dgenio/agentfence","title":"Demo issue"}}

call_004 github.delete_repo -> deny (tool github.delete_repo matched explicit policy rule)

{"schema_version":"4","session_id":"<session_id>","seq":4,"timestamp":"<rfc3339>","call_id":"call_004","tool":"github.delete_repo","decision":"deny","reason":"tool github.delete_repo matched explicit policy rule","reason_code":"rule_match","arguments":{"repo":"dgenio/agentfence"}}

4 call(s) processed, 0 parse error(s): allow=1 deny=2 ask=1

Timestamps are real time.RFC3339Nano values at runtime, and session IDs are

UUIDs generated per run. They are shown as placeholders here so this section

does not need to be updated on every build.

version: "0.1"

defaults:

decision: deny

tools:

filesystem.read:

decision: allow

filesystem.write:

decision: ask

github.create_issue:

decision: ask

github.delete_repo:

decision: denySee examples/policy.yaml for the full policy including constraints and redaction patterns.

AgentFence is built to reduce practical risks from agent tool calls:

- prompt injection

- confused deputy behavior

- accidental destructive actions

- secret leakage through logs

- excessive default permissions

See docs/threat-model.md for the full threat model

(including the MCP proxy threat surface, confused-deputy-via-MCP-proxy, and

audit-log integrity), docs/architecture.md for the

evaluation flow, and docs/modes.md for the

detection/prevention/audit-only/dry-run mode taxonomy.

See the confused-deputy guard in action with the runnable, hermetic

examples/taint-scenario/: an allowed read returns

untrusted text, and a later write reusing that text is blocked because its

argument was derived from untrusted output.

AgentFence is not a sandbox. It enforces policy before a tool call executes; it does not contain a tool call that has already been forwarded. Pair it with OS-level isolation for defense in depth.

AgentFence is the external policy edge of the Weaver Stack — a set of small, composable tools for building and operating safer AI agents. Each works standalone; together they cover an agent's lifecycle:

author & test build & run operate the edge learn

┌──────────────┐ ┌──────────────────┐ ┌────────────────────┐ ┌──────────────┐

│ vibeguard │ │ agent-kernel │ │ AgentFence │ │ lessonweaver │

│ (dev-time) │ │ (in-process │ │ (external policy │ │ (learning │

│ │ │ firewall) │ │ edge) │ │ loop) │

└──────────────┘ └──────────────────┘ └────────────────────┘ └──────────────┘

agent ──tool calls──▶ AgentFence ──allow / deny / ask──▶ MCP servers / tools

(+ tamper-evident audit) ──deny/ask traces──▶ lessonweaver

- AgentFence (this repo) — the external gate: an operator-controlled policy boundary in front of any MCP server or tool-call pipeline.

- agent-kernel — the in-process firewall compiled into an agent application.

- vibeguard — dev-time guardrails for agent code.

- lessonweaver — turns recurring deny/ask traces into reviewed policy lessons.

All of these are optional. AgentFence works standalone with any agent and has no hard dependency on any sibling.

AgentFence is the external gate in a layered approach to agent safety:

- AgentFence (this project): a standalone CLI and MCP stdio proxy that sits outside the agent process. It is configured by an operator and decides allow / deny / ask for each tool call before it reaches the tool server. Use AgentFence when you want a policy layer that does not require modifying the agent or the application embedding it.

- agent-kernel: an embeddable runtime/library layer for applications that build their own agent on top. It runs inside the agent process and is configured by the application author.

The two are complementary. An application can embed agent-kernel for

in-process safety and also run behind agentfence for an operator-controlled

policy boundary. If you only need one, pick AgentFence when the policy author

is not the application author, and pick agent-kernel when you are building

an agent application and want safety guarantees compiled in.

For a worked, side-by-side comparison of the same policy intent at each

integration point, see

docs/edge-proxy-vs-kernel.md. For a supervisor

gating many workers through AgentFence, see

docs/puppetmaster-integration.md.

The initial phased roadmap (harden the MVP → policy language → MCP proxy →

trustworthy audit logs) is complete, along with signed audit logs and a signed

multi-channel release. Current direction — response-side policy, more policy

packs, and broader client-config recipes — lives in

ROADMAP.md, which tracks the

roadmap-labeled issues

so this list can't drift.

- Detecting or preventing prompt injection inside the model.

- Sandboxing an MCP server or containing code after a call is forwarded.

- Governing tool paths that bypass the configured AgentFence proxy or gate.

- Proving that an operator-authored policy is correct or sufficiently strict.

- Replacing host, container, or network isolation.

See CONTRIBUTING.md for local-development setup, test

conventions, and PR guidelines. The short version:

make cibefore opening a pull request. CI runs the same command.

Most contributions here come from AI coding agents. If that's you, read

AGENTS.md first (a thin pointer to the make ci gate,

high-churn files, and the conventions in CONTRIBUTING.md).

By participating you agree to abide by our

CODE_OF_CONDUCT.md. Release history is tracked in

CHANGELOG.md.

To report a vulnerability, please follow the coordinated-disclosure process in

SECURITY.md — do not open a public issue for security reports.

Apache-2.0. See LICENSE.