关于 NullWire-MCP
NullWire-MCP 是面向开发者的 Go HTTP 调试代理和 MCP 服务,用于排查 API 流量。流量仅保存在内存中,秘密值会替换为稳定的加盐标签,Agent 可据此关联值而看不到原始秘密。
主要功能
- 01本地 HTTP 代理与反向网关
- 02仅存于内存的近期流量环形缓冲区
- 03加盐标签保留秘密值身份
- 04筛选并检查脱敏流量
- 05导出可重放的脱敏 curl 命令
适用场景
- 01比较不同请求的认证标头
- 02确认令牌是否复用或轮换
- 03生成命令以重放失败请求
MCP 相关
服务通过 stdio 与 MCP 客户端通信,并提供检查近期脱敏 HTTP 流量及导出脱敏 curl 命令的工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
⚡ NullWire-MCP
你的 AI Agent 可以调试实时 HTTP 流量。
它看不到任何秘密。
每个令牌、Cookie 和 IP 都会替换为稳定的加盐标签,因此 Agent 仍能分辨
“同一令牌被重复使用”和“令牌已轮换”:这是大多数身份验证错误背后的问题。
快速开始 · 工作原理 · 安装 · 工具 · 安全性 · [局限性]#-honest-scope-&-known-limitations) · 常见问题
👀 看看效果有何不同
You send Authorization: Bearer sk-live-51Hk3...
Your AI sees Authorization: Bearer [REDACTED_TOKEN#a1b2c3]
Request #1 Bearer [REDACTED_TOKEN#a1b2c3]
Request #2 Bearer [REDACTED_TOKEN#a1b2c3] ◄── same token, reused ✔
Request #3 Bearer [REDACTED_TOKEN#7f09de] ◄── different token, it rotated ✔
盲目地用 [REDACTED] 替换内容能保护你,却会让 AI 无法调试身份验证错误。NullWire 保留每个秘密的身份,并丢弃它的值。
| 📦 0 项依赖 | 💾 0 字节写入磁盘 | 🔍 1 个待审查的源文件 | 🖥️ 9 个发布目标 |
|---|---|---|---|
| 纯 Go 标准库 | 仅驻留内存的 50 流环形缓冲区 | main.go | Linux · macOS · Windows · FreeBSD |
⚡ 60 秒快速开始
# 1. install (Go 1.23+), or grab a prebuilt binary from Releases
go install github.com/VoxNode-ux/nullwire-mcp@latest
# 2. register it with your AI client (Claude Code shown; Cursor/Windsurf/Desktop configs below)
claude mcp add nullwire -- nullwire-mcp -port 8080 -target https://api.example.com
# 3. point your app at the proxy instead of the real API
curl http://127.0.0.1:8080/v1/orders -H "Authorization: Bearer $MY_TOKEN"
然后直接向你的 Agent 提问:
“为什么
/v1/orders返回 401?比较最近 5 个请求的身份验证标头。” “请求 3 和 4 使用了相同的令牌,还是令牌轮换了?” “给我一条 curl 命令,用于重放失败的调用。”
你的应用始终会收到真实、未经修改的响应。只有 Agent 看到的内容会经过脱敏。
🔥 为什么要做这个项目
让 AI Agent 读取你的网络流量时,典型的流量捕获代理会把所有内容交给它:实时 Bearer 令牌、会话 Cookie、内部 IP。所有内容都会进入云端大模型的上下文窗口,而且往往还会写入留在磁盘上的捕获文件。
盲目地用 [REDACTED] 替换内容可以阻止泄露,却也会让调试变得不可能。NullWire-MCP 会在内存中脱敏所有秘密,并将其替换为每个进程独有的确定性加盐标签:Agent 能保留每个秘密的身份,却永远看不到它的值。
标签的计算方式为
SHA-256(nonce ‖ secret)[:6]。nonce是启动时从crypto/rand生成的 32 个随机字节,仅保存在内存中,进程退出后便会消失。请参阅安全性深入解析,了解这为何重要。
🧭 工作原理
flowchart LR
APP["Your app / curl / SDK"] -->|"plain HTTP to 127.0.0.1"| PROXY["NullWire proxy<br/>loopback only"]
PROXY -->|"original request, untouched"| UP["Upstream API<br/>http or https"]
UP -->|"original response"| PROXY
PROXY -->|"original response, untouched"| APP
PROXY -.->|"async copy"| DLP["DLP scrubber<br/>regex, then SHA-256 tag"]
DLP --> RING[("RAM ring<br/>50 flows")]
RING -->|"MCP over stdio<br/>scrubbed data only"| AGENT["AI agent<br/>Claude · Cursor · Windsurf"]
两条路径,一条规则:**真实字节发送给你的应用,只有经过脱敏的副本才会发送给 AI。**任何内容都不会写入磁盘。
图表无法显示(手机端或 Mermaid 已禁用)?打开 ASCII 版本
╔══ [1] LOCAL APP / CLI ══════════════════════════════════════╗
║ curl · SDK · browser · your service ║▒
║ speaks plain HTTP to 127.0.0.1:8080 ║▒
╚═════════════════════════════════════════════════════════════╝▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│
▼ plain HTTP, loopback only
╔══ [2] NULLWIRE PROXY 127.0.0.1:8080 ═══════════════════════╗
║ bound with net.Listen(tcp4) + IsLoopback() check ║▒
║ forward mode : absolute URL (curl -x) ║▒
║ reverse mode : -target flag / X-Target-URL header ║▒
║ guards : DNS-rebind 403 · self-loop 508 · CONNECT 501 ║▒
╚═════════════════════════════════════════════════════════════╝▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│
▼ io.TeeReader split
┌───────────────┴────────────────┐
▼ ▼
╔══ [3a] ROUNDTRIP ══════════╗ ╔══ [3b] DLP SCRUBBER ═══════╗
║ http.Transport (isolated) ║▒ ║ async copy, capped at 64KB ║▒
║ Accept-Encoding stripped ║▒ ║ regex scrub, THEN truncate ║▒
║ Host = upstream host ║▒ ║ SHA-256(nonce|secret)[:6] ║▒
║ bytes forwarded untouched ║▒ ║ 32-byte crypto/rand nonce ║▒
╚════════════════════════════╝▒ ╚════════════════════════════╝▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│ │
▼ ▼
┌── UPSTREAM API ────────────┐ ╔══ [4] RAM RING ════════════╗
│ http:// or https:// │▒ ║ 50 slots · sync.RWMutex ║▒
│ TLS handled by Go, no MITM │▒ ║ req 8KB / resp 16KB max ║▒
└────────────────────────────┘▒ ║ newest-first reads ║▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ║ disk writes: none ║▒
│ ╚════════════════════════════╝▒
◄──┘ original response ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
returns to the app │
▼
╔══ [5] JSON-RPC 2.0 MCP (2026-07-28 + legacy) ═══════════════╗
║ initialize · discover · ping · tools/list · call ║▒
║ inspect_traffic · export_sanitized_curl · clear_ram_buffer ║▒
║ notifications → zero bytes written ║▒
╚═════════════════════════════════════════════════════════════╝▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│ │
▼ ▼
╔══ STDOUT ▓▓▓ ══════════════╗ ┌── STDERR ░░░ ──────────────┐
║ one compact JSON line ║▒ │ ANSI status bar, │▒
║ per frame, ends in \n ║▒ │ banners, errors │▒
║ NOTHING else, ever ║▒ │ (human telemetry) │▒
╚════════════════════════════╝▒ └────────────────────────────┘▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│ │
▼ ▼
╔══ AI AGENT (MCP) ══════════╗ ┌── HUMAN ───────────────────┐
║ Claude Code · Desktop ║▒ │ you, in a terminal │▒
║ Cursor · Windsurf ║▒ └────────────────────────────┘▒
╚════════════════════════════╝▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
**你的应用始终会收到真实、未经修改的响应。**只有 AI Agent 看到的内容会经过脱敏。
stderr 上的实时遥测栏(通过 \r\033[K 原地重绘):
⚡ [NullWire-MCP] Proxy 127.0.0.1:8080 | RAM Ring: 14/50 | Scrubbed: 9 Tokens, 4 Cookies, 6 IPs | Disk: 0B
| ⚡ NullWire-MCP | 典型的基于 MITM 的 MCP 包装器 | |
|---|---|---|
| 第三方依赖 | 0(仅使用 Go 标准库;go.mod 中没有 require) | 数十个传递依赖的 Python / Node / JVM 软件包 |
| 磁盘占用 | 0 B,仅驻留内存的 50 槽环形缓冲区;代码中没有文件 I/O | 磁盘上的流量文件 / SQLite 数据库 |
| 密钥脱敏 | 加盐 SHA-256 关联标签,可证明重复使用,且不会暴露密钥 | 无,或使用无法关联的盲目 [REDACTED] |
| 启动 | 原生静态二进制文件:无需解释器、虚拟机或软件包解析 | 解释器 / 虚拟机冷启动 |
| MCP 协议 | 2026-07-28 无状态 + 2025-11-25 / 2024-11-05 旧版 | 通常仅支持旧版 |
| HTTPS 上游 | 反向网关:无需根 CA,也无需更改钥匙串 | 安装根 CA 并拦截 TLS |
| 需审计的供应链 | 一个 main.go | 整个依赖树 |
右侧列描述的是一类通用工具:通过 MITM 代理捕获流量并将其交给大模型。这并非针对任何特定产品的说法。我们尚未发布启动基准测试;启动这一行描述的是架构。
🚀 30 秒安装
选项 A:go install(Go 1.23+)
go install github.com/VoxNode-ux/nullwire-mcp@latest
选项 B:预编译静态二进制文件,通过 GitHub Releases 中的 SHA256SUMS.txt 校验:
| 平台 | 文件 |
|---|---|
| Linux x86-64 / ARM64 | nullwire-linux-amd64 / nullwire-linux-arm64 |
| Linux ARMv7(Raspberry Pi 等)/ RISC-V 64 | nullwire-linux-armv7 / nullwire-linux-riscv64 |
| macOS Intel / Apple Silicon | nullwire-darwin-amd64 / nullwire-darwin-arm64 |
| Windows x86-64 / ARM64 | nullwire-windows-amd64.exe / nullwire-windows-arm64.exe |
| FreeBSD x86-64 | nullwire-freebsd-amd64 |
# Linux example (swap the file name for your platform)
BASE=https://github.com/VoxNode-ux/nullwire-mcp/releases/latest/download
curl -LO "$BASE/nullwire-linux-amd64"
curl -LO "$BASE/SHA256SUMS.txt"
grep ' nullwire-linux-amd64$' SHA256SUMS.txt | sha256sum -c - # macOS: shasum -a 256 -c -
chmod +x nullwire-linux-amd64
sudo mv nullwire-linux-amd64 /usr/local/bin/nullwire-mcp
# Windows (PowerShell)
Get-FileHash .\nullwire-windows-amd64.exe -Algorithm SHA256 # compare with SHA256SUMS.txt
选项 C:从源代码构建
git clone https://github.com/VoxNode-ux/nullwire-mcp && cd nullwire-mcp
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o nullwire-mcp .
标志
| 标志 | 默认值 | 含义 |
|---|---|---|
-port | 8080 | 环回端口。主机固定为 127.0.0.1;无法绑定到其他地址。0 会选择一个空闲端口。 |
-target | (无) | 反向网关模式的默认上游基础 URL,例如 https://api.example.com |
不使用 AI 客户端,直接试用
只要标准输入保持打开,MCP 端就会持续运行。因此,快速演示时,可以定时向它发送请求:
( sleep 2
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"inspect_traffic","arguments":{"limit":5}}}'
sleep 1 ) | nullwire-mcp -port 8080 &
sleep 1
curl -s -x http://127.0.0.1:8080 http://example.com/ \
-H 'Authorization: Bearer my_secret_token_123' \
-H 'Cookie: sid=my_secret_cookie' \
-H 'X-Forwarded-For: 198.51.100.1' > /dev/null
wait
你会在标准错误输出中看到实时状态栏,在标准输出中看到流量,其中的标头会显示为 Bearer [REDACTED_TOKEN#…]、[REDACTED_COOKIE#…] 和 [REDACTED_IP#…]。
接入 AI 客户端
客户端会将 nullwire-mcp 作为子进程启动,并通过标准输入输出进行 JSON-RPC 通信。然后,你的应用将流量发送到 127.0.0.1:8080。(配置文件的位置会随客户端版本变化;如果下方路径已过时,请查阅客户端文档。)
Claude Code CLI
claude mcp add nullwire -- nullwire-mcp -port 8080 -target https://api.example.com
Claude Desktop(claude_desktop_config.json)
{
"mcpServers": {
"nullwire": {
"command": "nullwire-mcp",
"args": ["-port", "8080", "-target", "https://api.example.com"]
}
}
}
Cursor(~/.cursor/mcp.json,或项目中的 .cursor/mcp.json)
{
"mcpServers": {
"nullwire": {
"command": "nullwire-mcp",
"args": ["-port", "8080", "-target", "https://api.example.com"]
}
}
}
Windsurf(~/.codeium/windsurf/mcp_config.json)
{
"mcpServers": {
"nullwire": {
"command": "nullwire-mcp",
"args": ["-port", "8080", "-target", "https://api.example.com"]
}
}
}
如果只使用正向代理模式或每个请求的 X-Target-URL 标头,请省略 -target。
🔀 两种捕获模式
模式 1:正向代理(纯 HTTP)
通过代理发送完整 URL。请求会原样转发,经过清理的副本则会进入环形缓冲区。
curl -x http://127.0.0.1:8080 http://httpbin.org/headers
# or for any app that honours the standard variable
HTTP_PROXY=http://127.0.0.1:8080 ./my-service
HTTPS_PROXY会使客户端发送CONNECT请求;按设计,此类请求会收到501响应。请参阅明确的适用范围。对于 HTTPS,请使用模式 2。
模式 2:反向网关(HTTPS 上游,无 MITM 证书)
通过纯 HTTP 与 NullWire 通信;NullWire 使用 Go 的常规证书验证,通过 TLS 与真实服务端通信。无需在信任存储中安装任何内容。
# Fixed upstream for the whole session
nullwire-mcp -target https://api.github.com
curl http://127.0.0.1:8080/users/octocat
# Or choose the upstream per request (the header is removed before forwarding)
curl -H 'X-Target-URL: https://httpbin.org' http://127.0.0.1:8080/anything?api_key=hunter2hunter2
只需更改基础 URL,即可将 SDK 指向该网关:
export OPENAI_BASE_URL=http://127.0.0.1:8080/v1 # with: nullwire-mcp -target https://api.openai.com
上游 Host 标头会设为目标主机,因此虚拟主机和 TLS SNI 均可正常工作。
🧰 MCP 工具参考
这三个工具都返回 {"resultType":"complete","content":[{"type":"text","text":"…"}],"isError":false}。工具级问题(例如未知的流 ID)会将 isError 设为 true,而不会导致 RPC 失败。
inspect_traffic
最近的脱敏流量,按时间倒序排列。
{
"name": "inspect_traffic",
"inputSchema": {
"type": "object",
"properties": {
"limit": { "type": "integer", "default": 10, "minimum": 1, "maximum": 50 },
"method": { "type": "string", "description": "HTTP method filter, case-insensitive" },
"url_filter": { "type": "string", "description": "Substring of the scrubbed URL, case-insensitive" }
},
"additionalProperties": false
}
}
结果文本示例:
{
"ring_size": 3,
"ring_capacity": 50,
"returned": 1,
"scrubbed_totals": { "tokens": 5, "cookies": 1, "ips": 1 },
"flows": [
{
"id": 3,
"timestamp": "2026-10-25T14:03:11Z",
"method": "POST",
"url": "http://[REDACTED_IP#98f1a2]:9000/v1/orders?api_key=[REDACTED_TOKEN#3c9a1e]",
"request_headers": {
"Authorization": "Bearer [REDACTED_TOKEN#a1b2c3]",
"Content-Type": "application/json",
"Cookie": "[REDACTED_COOKIE#d4e5f6]"
},
"request_body": "{\"item\":\"widget\",\"client_secret\":\"[REDACTED_TOKEN#5b7e20]\"}",
"status_code": 401,
"response_headers": { "Content-Type": "application/json" },
"response_body": "{\"error\":\"token expired\"}",
"latency_ms": 42,
"redaction_count": 5
}
]
}
export_sanitized_curl
为单个流量重建可运行的 POSIX curl 命令。每个脱敏标签都会变为环境变量占位符,并附带注释图例,将每个占位符映射回其替换的标签,方便你了解每个位置对应哪个真实值。
{
"name": "export_sanitized_curl",
"inputSchema": {
"type": "object",
"properties": {
"flow_id": { "type": "integer", "minimum": 1, "description": "An id returned by inspect_traffic" }
},
"required": ["flow_id"],
"additionalProperties": false
}
}
上方流量 3 的结果文本示例:
# Replay of flow 3 (2026-10-25T14:03:11Z)
# Set these environment variables first (distinct tags share one placeholder):
# export NULLWIRE_IP=... # was [REDACTED_IP#98f1a2]
# export NULLWIRE_AUTH_TOKEN=... # was [REDACTED_TOKEN#3c9a1e]
# export NULLWIRE_AUTH_TOKEN=... # was [REDACTED_TOKEN#5b7e20]
# export NULLWIRE_AUTH_TOKEN=... # was [REDACTED_TOKEN#a1b2c3]
# export NULLWIRE_COOKIE=... # was [REDACTED_COOKIE#d4e5f6]
curl -X 'POST' 'http://'"$NULLWIRE_IP"':9000/v1/orders?api_key='"$NULLWIRE_AUTH_TOKEN" \
-H 'Authorization: Bearer '"$NULLWIRE_AUTH_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Cookie: '"$NULLWIRE_COOKIE" \
--data-raw '{"item":"widget","client_secret":"'"$NULLWIRE_AUTH_TOKEN"'"}'
逐跳标头、Host、Content-Length 和 Accept-Encoding 会被省略,以便由 curl 自行设置。对于已截断或经过编码的请求正文,会显示 # WARNING: 行,而不会静默重放不完整的内容。
clear_ram_buffer
将全部 50 个槽位清零并重置脱敏计数器。流 ID 会持续递增,因此旧 ID 永远不会指向新流量。不接受任何参数({"type":"object","properties":{},"additionalProperties":false})。
🛡️ 安全与协议工程深入解析
以下各项均在 main.go 中实现,并由 main_test.go 测试。
| 措施 | 作用 | 原因 |
|---|---|---|
| 严格绑定回环地址 | 监听器地址硬编码为 127.0.0.1,使用 net.Listen("tcp4", …) 打开,然后通过 TCPAddr.IP.IsLoopback() 再次检查。如果检查失败,进程将退出。没有 -host 标志。 | 局域网中的任何设备都无法访问会接触你令牌的代理。 |
| DNS 重绑定防护 | 在反向模式下,任何 Host 标头不是 127.0.0.1、localhost 或 ::1 的请求都会收到 403 Forbidden。 | 恶意网页即使将其域名重绑定到 127.0.0.1,也无法将你的网关用作经过身份验证的中继。 |
| 自环保护 | 如果目标解析到代理自身的 host:port,就会返回 508 Loop Detected。 | 防止配置错误的 -target 导致请求风暴。 |
| 先清理,再截断 | 每个请求正文最多捕获 64 KB,完整进行正则清理,之后才截断为 8 KB(请求)/16 KB(响应)。 | 跨越 8 KB 边界的密钥在截断前就已被替换为标签。若先截断,就会保留半个令牌。 |
| 每个进程独有的临时随机数 | 从 crypto/rand 获取 32 字节,仅保存在 RAM 中。标签 = SHA-256(nonce ‖ secret) 的前 6 个十六进制字符。 | 如果没有盐,低熵密钥(短 PIN、容易猜中的密码)的哈希可能会根据泄露的记录被暴力破解或通过彩虹表破解。使用秘密随机数后,任何人都无法预先计算,而且重启后标签也无法关联。 |
严格遵守 stdout 规范 | stdout 只传输以 \n 结尾的单行 JSON-RPC 帧,并在互斥锁保护下写入。横幅、错误和状态栏写入 stderr。 | stdout 中任何多余字节都会破坏 MCP 流。 |
| 静默通知 | 不带 id 的消息(或方法为 notifications/* 且 id: null 的消息),例如 notifications/initialized,会写入 0 字节。 | JSON-RPC 禁止回复通知;如果回复,严格的客户端会断开连接。 |
| 原始流量不受影响 | 应用通过流式、可刷新的副本接收真实响应。清理操作在后台 goroutine 中对单独的副本进行。 | 代理绝不能影响你正在调试的对象。 |
| 隔离传输 | 使用专用的 http.Transport,不使用代理、不启用压缩,也不跟随重定向。 | 避免意外的代理链;重定向会原样传递给应用。 |
| 剥除逐跳标头 | 双向移除 Connection、Proxy-*、TE、Upgrade 等标头。 | 符合 RFC 9110 的代理行为规范。 |
| 崩溃隔离 | 捕获和 RPC 分发会从 panic 中恢复,而不会终止进程。 | 异常载荷不会导致你的会话中断。 |
会清理的内容
| 模式 | 标签 |
|---|---|
Authorization、Proxy-Authorization、X-API-Key、API-Key、X-Auth-Token 标头值;任何位置的 Bearer / Basic / Token 凭据 | TOKEN |
Cookie、Set-Cookie 标头值(整个标头作为一个整体进行哈希) | COOKIE |
JWT(eyJ….….…) | TOKEN |
API 密钥:sk-…、ghp_…、github_pat_…、AIza…、xox[baprs]-…、AKIA… | TOKEN |
JSON 字段:access_token、refresh_token、id_token、api_key、apikey、secret、client_secret、password、passwd、session_id、auth_token | TOKEN |
查询/表单参数:api_key、apikey、token、access_token、refresh_token、id_token、secret、client_secret、password、passwd、key | TOKEN |
URL 中的 user:password@host | TOKEN |
IPv4 地址(127.0.0.1 和 0.0.0.0 保留不变) | IP |
⚖️ 诚实说明范围与已知限制
我们宁愿由我们来告诉你,也不希望你在生产环境中才发现。
HTTP CONNECT会返回501 Not Implemented,这是有意为之。 通过CONNECT检查 HTTPS 意味着要对 TLS 实施中间人攻击,这就需要生成根 CA,并将其加入操作系统或浏览器的信任存储。这会涉及密钥材料并污染密钥链,与零足迹工具的目标背道而驰。反向网关无需这些操作就能提供同样的可见性:将应用指向http://127.0.0.1:8080,让 NullWire 处理到上游的 TLS。- 不支持 WebSocket /
Upgrade透传。Upgrade和Connection会被视为逐跳标头并移除,因此升级后的协议无法通过代理运行。支持普通请求/响应和流式响应(例如 SSE)。 - Android / Termux: Android 上的静态 Go 二进制文件没有 DNS 解析器配置,因此上游主机名解析会失败,并出现
502 … lookup … connection refused。请在termux-chroot中运行(pkg install proot),它会提供解析器。Linux 桌面、macOS、Windows 和 FreeBSD 预计不受影响。 - 脱敏基于模式,并非万能。 它可以识别上表中的格式。如果密钥采用我们无法识别的格式、出现在我们不了解的字段中,或跨越正文边界被拆分,就可能漏过。请把脱敏器视为可靠的安全带,而非保证,并使用你自己的流量进行测试。目前尚未对 IPv6 地址进行脱敏。看起来像 IPv4 地址、实际并非地址的字符串(某些版本号)可能会被误报并脱敏。
- 标签为 24 位。 六个十六进制字符足以区分 50 槽缓冲区中的少量密钥,但理论上仍可能发生冲突。标签用于关联,而非识别。
- 正文长度有限。 每个正文只会捕获前 64 KB,存储的正文会截断至 8 KB / 16 KB。带有
Content-Encoding(例如 gzip)的响应会以简短占位内容存储,而不会解码。NullWire 会从转发的请求中移除Accept-Encoding,因此大多数服务会返回未压缩内容,但它无法强制服务这么做。 - “仅驻留内存”意味着程序永远不会写入文件。 状态栏中的
Disk: 0B是设计约束(源代码不执行文件 I/O),而非测量所得的计数。操作系统仍可能将内存页换出到磁盘或写入崩溃转储;NullWire 无法控制这些行为。 - 环形缓冲区是临时的。 进程退出时,最近的 50 个流量记录会消失。这正是设计目的。
- 你的应用会看到原始流量。 NullWire 保护的是 AI Agent 看到的内容,而不是你的本地应用看到的内容。它并不是应用本身的安全边界。
- 协议支持范围以文档所述为准。 支持 2026-07-28 / 2025-11-25 / 2024-11-05 规范中的初始化、发现、ping、
tools/list和tools/call。资源、提示词和采样尚未实现,调用时会返回-32601。 - 发布的二进制文件未签名。 请使用
SHA256SUMS.txt验证。在 macOS 上,你可能需要运行xattr -d com.apple.quarantine ./nullwire-darwin-arm64。
🧪 测试平台
| 平台 | 状态 | 运行内容 |
|---|---|---|
| Linux x86-64(Ubuntu 24.04) | ✅ 已验证 | go vet、使用 -race 的单元测试、冒烟测试、端到端脱敏 |
| macOS 15(Apple Silicon) | ✅ 已验证 | 冒烟测试、静默通知、端到端脱敏 |
| macOS 15(Intel) | ✅ 已验证 | 冒烟测试、静默通知、端到端脱敏 |
| Windows x86-64 | ✅ 已验证 | 冒烟测试、静默通知、端到端脱敏 |
| FreeBSD amd64(VM) | ✅ 已验证 | 冒烟测试、静默通知、端到端脱敏 |
| Linux ARMv7(QEMU) | 🟡 模拟运行 | 冒烟测试、静默通知、端到端脱敏 |
| Linux RISC-V 64(QEMU) | 🟡 模拟运行 | 冒烟测试、静默通知、端到端脱敏 |
| Linux ARM64(Android / Termux) | ✅ 已在设备上验证 | 实时上游代理、脱敏标签、export_sanitized_curl、反向网关、防护措施(需要 termux-chroot) |
| Windows ARM64 | ✅ 已验证 | 冒烟测试、静默通知、端到端脱敏 |
🟡 模拟运行表示在 QEMU 下运行,而非在真实硬件上运行。每次运行的完整日志可通过 docs/test-results.md 和 Actions 选项卡查看。
✅ 验证与测试
go vet ./...
go test -v ./...
go test -v -race ./... # the race detector needs cgo (gcc/clang) on most platforms
测试套件仅使用标准库(net/http/httptest),涵盖:
- 确定性标记:相同的密钥共用一个标签,不同的密钥使用不同标签,不会泄漏任何内容,
127.0.0.1会保留。 - 环形缓冲区绕回淘汰,以及使用单调递增 ID 的
Clear()。 - 完整的反向网关往返流程:移除
Accept-Encoding、将令牌清理后存入环形缓冲区,并导出有效的脱敏curl。 - MCP 分发:
server/discover、initialize、ping、tools/list、tools/call、错误代码,以及通知的标准输出字节数为零。
CI(.github/workflows/release.yml)运行 go vet 和 go test -race,为 9 个目标交叉编译静态 CGO_ENABLED=0 二进制文件(Linux amd64/arm64/armv7/riscv64、macOS amd64/arm64、Windows amd64/arm64、FreeBSD amd64),并在 v* 标签下发布这些文件及 SHA256SUMS.txt。
❓ 常见问题
AI 会看到我的真实令牌吗?
不会通过 NullWire 的工具看到。Agent 只会收到类似 [REDACTED_TOKEN#a1b2c3] 的标签。脱敏基于模式,因此,若秘密的格式不被清理器识别,就可能通过。参见坦诚说明。
它会改变我的应用收到的内容吗?
你的应用会通过流式副本收到真实响应,而清理操作会在后台 goroutine 中对另一个副本单独运行。有一点需要注意:NullWire 会在上游请求中移除 Accept-Encoding,因此响应通常以未压缩的形式到达。参见坦诚说明。
如何调试 HTTPS API?
使用模式 2:反向网关(HTTPS 上游,无 MITM 证书):你的应用通过普通 HTTP 与 127.0.0.1 通信,NullWire 则通过经过验证的 TLS 与真实服务器通信。无需根 CA,也无需修改信任库。按设计,HTTPS_PROXY(CONNECT)请求会收到 501 响应。
Agent 能否在不知道秘密的情况下重放请求?
可以。export_sanitized_curl 会生成可运行的 curl 命令,将每个标签替换为环境变量,例如 $NULLWIRE_AUTH_TOKEN。你设置真实值;Agent 无法获取这些值。
我的流量存储在哪里?
存储在一个有 50 个槽位的 RAM 环形缓冲区中。进程退出后,数据便会消失;你也可以按需使用 clear_ram_buffer 清除数据。该程序不包含任何文件 I/O。
我可以在网络上公开它吗?
不可以。监听器被硬编码为 127.0.0.1,并通过 IsLoopback() 进行检查;没有可用于绑定到其他地址的标志。
🤝 参与贡献
欢迎提交问题和 PR。请阅读 CONTRIBUTING.md,了解基本规则、测试命令和 PR 检查清单。安全问题请通过 SECURITY.md 报告,不要提交到公开问题区。
📄 许可证
MIT © 2026 VoxNode-ux
如果 NullWire 帮你避免将有效令牌粘贴到聊天窗口中,欢迎点个 ⭐,让更多工程师发现它。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。