
MCP Output Firewall
io.github.tangfei7777-cell由 GitHub 账号 tangfei7777-cell 发布v0.3.0更新于 Oct 24, 2026
Three-layer firewall for MCP: content, egress and action policy. Proxy or server.
概览
本地运行的 MCP 三层防火墙,扫描工具返回内容中的注入指令、检查调用目标,并对有后果的操作进行把关。
- 可以做什么
- 扫描并评估工具返回内容,识别提示注入、凭据泄露、外传与混淆模式,支持 monitor、warn、block 三种判定,并可返回净化后的内容交给模型。检查拟执行的工具调用是否符合出站与操作策略,返回 allow、confirm 或 block,并给出判定规则和该调用将访问的目标。说明策略本身:规则目录、基准测试数字以及该架构无法拦截的攻击类型。以代理方式包裹启动的 MCP 服务器,让整个会话经过同样的检查,或作为服务器由智能体主动查询。可在命令行运行基准测试并扫描已捕获的载荷。
- 适用场景
- 当智能体需要读取不可信的工具输出、抓取的网页、文件或数据库记录,并可能被其中植入的文字操纵时适用。若你能控制服务器的启动方式,希望在传输路径中加入内容、出站与操作检查,或希望智能体在依据结果行动前先请求判定,也值得使用。
- 运行要求
- 通过 npx 以 stdio 在本地运行,需要 Node.js;未声明账号、API 密钥或环境变量。README 称其零依赖且数据不离开本机。出站层与操作层默认关闭,需用 egress-strict、egress-allow、confirm-actions、trust-tools 等参数开启。
安装
在 SourceWeft 中
- 打开 控制台中的 MCP Output Firewall,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
mcp-output-firewall
A three-layer firewall for MCP servers.
An MCP-enabled agent reads the output of every tool it calls, and it reads that output as data. Sometimes it isn't data. Anyone with write access to a file, an issue, a database row, or a web page the agent fetches can plant text that looks like data and reads like instructions — and the model does what the text says.
Existing MCP gateways solve authorisation: who is allowed to call this tool. They do not solve content: whether what came back can be trusted. A perfectly legitimate tool call can return poisoned data, and an authorisation layer has no opinion about that at all.
mcp-output-firewall sits in that gap.
Three layers, because one is not enough
Content filtering is defeatable. That is not a defect of this implementation; it is the state of the field. The honest response is not a better regex list, it is a posture with more than one failure mode.
Layer 1 is the one everyone builds. Layers 2 and 3 exist because layer 1 loses.
Why layer 2 matters most
The content scanner never sees the tool call. That is where the damage
happens. A tool named fetch_url with the description "Retrieve the contents of
a URL" is honest, well-behaved, and completely safe to install — right up until a
poisoned tool result tells the agent to call it with
http://169.254.169.254/latest/meta-data/iam/security-credentials/.
No amount of scanning the response can catch that, because the attack isn't in the response. It's in the request.
Blocked by default: cloud instance metadata endpoints, private ranges, loopback, DNS-tunnelling hostnames, and known drop-host patterns. Everything else that is unrecognised is permitted but logged — this is not a web filter and does not pretend to be one.
Why layer 3 exists
Meta's "Rule of Two" for agent design: an agent may combine at most two of (a) processing untrusted input, (b) access to sensitive systems, and (c) changing state externally. An agent reading tool results permanently has (a). So any call that is both sensitive and state-changing is asking for all three at once — and that is the shape of every real agent incident.
Read-only tools are never interrupted. Destructive arguments (rm -rf /,
DROP TABLE, pipe-to-shell) are refused outright. Consequential but legitimate
operations (file writes, git push, payments) require an explicit yes.
Two shapes, because the check has two possible locations
The proxy is the stronger shape when it applies, because nothing has to remember to ask. But it only applies when you control how a server is launched, and the interesting failure often happens somewhere no proxy sits: the agent fetches a page directly, reads a file, or follows up on a result from a server you don't own — and then decides for itself what to do next. At that moment a check that lives in a transport path the traffic never entered is not a control, it is a configuration file.
serve is that missing shape. It is the same three layers, exposed as MCP
tools, so the decision can be requested rather than merely imposed.
The two compose. Run serve for the agent's own decisions; wrap the servers
whose launch you control.
Install
Zero dependencies, no build step, no account, nothing leaves your machine.
Use
Modes for layer 1: monitor (log only, the default), warn (sanitise in band,
then deliver), block (refuse, and fail loudly with a JSON-RPC error).
Running as a server
It speaks both protocol eras: modern revisions (2026-07-28 and later, which
carry the version, client identity and capabilities per request in _meta and
have no handshake) and legacy ones (2025-11-25 and earlier, which open with
initialize). It implements server/discover, which modern clients probe with,
so a dual-era client resolves the era from the probe rather than from a guess.
Client configuration:
The four tools
check_tool_call returns allow, confirm or block, names the rule that
decided it, and lists every destination the call would reach — an operator
reading "allowed" needs to see what was allowed, not merely that something
was. It never echoes the arguments back, because a call carrying a live
credential must not have that credential written into the audit record of the
call.
evaluate_tool_result is the decision form of scan_content. It takes the same
monitor / warn / block modes, and when the verdict is sanitize it returns
the rewritten payload ready to hand on.
describe_policy exists so a caller can learn what a clean verdict does and does
not mean before trusting one. It returns the rule catalogue, the benchmark
numbers, and the attack classes this architecture cannot catch — each with the
layer expected to handle it instead.
What it refuses to do to itself
This server's replies are read by a language model, which makes its output a delivery channel. Two rules follow, and both are asserted in the test suite:
- Evidence is neutralised, not quoted. A finding's
evidencegoes through the same sanitizer the proxy uses and comes back prefixed with an untrusted marker, so a report about a payload cannot become the payload. Raw evidence is one flag away (include_raw_evidence), off by default, on the record. - A firewall that repeats what it just blocked is an injection amplifier with
extra steps. That was a real defect during development, not a hypothetical:
maskSecretsmasks credentials but has no opinion about imperatives, so the first version of this mode returned attack text verbatim inside a security report. The suite now asserts against the response bytes rather than against the shape we intended to produce.
The benchmark
Anyone can claim detection rates. mcp-output-firewall bench scores the engine against a
published corpus of attack vectors — including the classes known to defeat
content-based detection — and reports the misses.
Three numbers, reported separately, because collapsing them into one "detection rate" is how security tools mislead their users.
The gaps are the point. Four vectors in the corpus are marked as not catchable by content inspection, with the layer that is supposed to handle them instead. If you see a tool claim 100% coverage of indirect prompt injection with no listed gaps, it is either not measuring honestly or not measuring.
The attack that walks through every content filter
This one is worth understanding, because it explains why this tool has three layers instead of one:
There is no imperative here. No "ignore", no "you must", no known-bad signature. A signature list matches known malice and there is none on the surface. An LLM classifier asks "instruction or data?" and correctly answers "data".
It does not attack the model. It soothes it — by impersonating the pipeline that is supposed to be protecting it.
We catch the variants that assert a verdict about the content they arrive in
(rule INJ-006), because a genuine verdict is delivered out of band by the
client and never written into the payload it just cleared. The bare-header form
remains a documented gap. That is why layers 2 and 3 exist.
Source: the maintainer of a comparable tool published this attack against their own detector and concluded it is a wall the whole field is standing in front of. We agree, and we build accordingly rather than pretending otherwise.
What it catches
Layer 1 — 17 rules across 6 categories
Layer 2 — destination policy. Metadata endpoints, private ranges, DNS tunnelling shapes, known drop hosts. Allow-list for everything else.
Layer 3 — 13 action rules. Destructive operations blocked; consequential operations confirmed; read-only operations untouched.
All three are callable at runtime through serve, so the same policy that guards
a wrapped session can also be consulted by an agent choosing its own next step.
Precision
A filter that screams at documentation gets uninstalled on day one, which is a worse outcome than missing an attack. The corpus includes eight benign controls and the test suite asserts zero findings on them:
- security documentation that discusses prompt injection
- changelogs that use the words "ignores instructions"
- shell commands in legitimate runbooks
- signed CDN URLs with long query strings
- Dockerfiles, CI configs, JSON schemas with an enum called
system - genuine Cyrillic text (not homoglyph substitution)
Tests
The serve suite drives a real child process over real stdio, because the two
things most likely to be wrong there cannot be tested as a library: a handshake
answered in the wrong shape is a server no client can talk to, and a reply that
repeats the payload it just flagged is an attack delivered.
Limitations
Stated plainly, because a security tool that oversells itself is worse than none.
- Content detection is defeatable. Semantic evasion — a reworded imperative with no lexical overlap with any signature — gets through. Documented as a gap, not hidden.
- This is not a sandbox. It inspects traffic. A tool whose code does something other than its description says cannot be caught by inspecting strings.
- Layer 2 is a policy, not a packet filter. Unrecognised public hosts pass. If you want a hard boundary, put the agent in a container with no direct egress and route it through the proxy.
- Layers 2 and 3 are off by default. Turning them on changes what your agent can do. Read the flags first.
- Regex is fast, not smart. No machine learning, no remote classification, no telemetry. That is a deliberate trade: it runs in-process at zero cost and sends nothing anywhere.
- Not audited. Run it alongside other controls, not instead of them.
Design principles
- Precision over recall. Every rule has a concrete attack it stops.
- Report gaps honestly. A benchmark with no misses is a benchmark that isn't measuring.
- Layer, don't chase. When content inspection loses, add a layer that doesn't depend on inspecting content.
- Zero dependencies. A tool on your trust boundary should not bring a dependency tree with it.
- Explain every hit. Span, reason, and a fix hint on every finding.
Licence
MIT
来源:README.md,提交 a70ca98
工具
0版本历史
1- v0.3.0最新Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分