NullWire-MCP について
NullWire-MCP は、API 通信を調査する開発者向けの Go 製 HTTP デバッグプロキシ兼 MCP サーバーです。通信はメモリ内に保持し、秘密情報を安定したソルト付きタグに置き換えるため、エージェントは値を見ずに同一性を追跡できます。
主な機能
- 01ループバック HTTP プロキシとリバースゲートウェイ
- 02最近の通信を保存するメモリ内リングバッファー
- 03秘密情報の同一性を保つソルト付きタグ
- 04フィルターによる通信の確認
- 05再実行可能なサニタイズ済み curl コマンドの出力
活用シーン
- 01リクエスト間の認証ヘッダーを比較
- 02トークンが再利用・ローテーションされたか確認
- 03失敗したリクエストを再現するコマンドを生成
MCP 対応
stdio 経由で MCP クライアントと通信し、最近のサニタイズ済み HTTP 通信の確認と、サニタイズ済み curl コマンドの出力を行うツールを提供します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
⚡ NullWire-MCP
AI エージェントが、実際の HTTP トラフィックをデバッグできます。
秘密情報を一切見ることはありません。
すべてのトークン、Cookie、IP アドレスは安定したソルト付きタグに置き換えられるため、エージェントは
*"同じトークンが再利用された"のか、"トークンがローテーションされた"*のかを見分けられます。これは、多くの認証バグの核心となる問いです。
クイックスタート · 仕組み · インストール · ツール · セキュリティ · [制限事項]#-honest-scope-&-known-limitations) · FAQ
👀 違いを確認
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標準ライブラリのみ | RAM上の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"
あとは、エージェントに尋ねるだけです。
「
/v1/ordersが401を返すのはなぜですか?直近5件のリクエストの認証ヘッダーを比較してください。」 「リクエスト3と4では同じトークンが使われましたか?それともローテーションされましたか?」 「失敗した呼び出しを再現するcurlコマンドを作成してください。」
アプリには常に、実際の変更されていないレスポンスが返されます。スクラブされるのはエージェントから見える内容だけです。
🔥 開発の背景
AI エージェントをネットワークトラフィックに接続すると、一般的なキャプチャプロキシはすべてを渡します。実際に使われているベアラートークン、セッションCookie、内部IPアドレスなどです。それらすべてがクラウドLLMのコンテキストウィンドウに入り、多くの場合、ディスク上に残されたキャプチャファイルにも保存されます。
単純な[REDACTED]では情報漏えいを防げますが、デバッグもできなくなります。NullWire-MCPはすべての秘密情報をRAM上で秘匿し、プロセスごとに決定論的なソルト付きタグに置き換えます。これにより、エージェントは各秘密情報の識別情報を保持したまま、値を見ることはありません。
タグは
SHA-256(nonce ‖ secret)[:6]です。nonceは起動時にcrypto/randから取得される32バイトの乱数で、RAM上だけに保持され、プロセスの終了時に消去されます。これが重要な理由については、セキュリティの詳細解説をご覧ください。
🧭 仕組み
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"]
2つの経路に、1つのルールがあります。**実際のバイト列はアプリに渡し、スクラブ済みのコピーだけを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エージェントから見える内容だけです。
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。RAM のみを使用する 50 スロットのリング。コードにファイル I/O はありません | ディスク上のフローファイル/SQLite データベース |
| シークレットのマスキング | ソルト付き SHA-256 相関タグにより、シークレットを見せずに再利用を証明できます | なし、または相関関係を失わせる盲目的な [REDACTED] |
| 起動 | ネイティブの静的バイナリ。インタープリター、VM、パッケージ解決は不要です | インタープリター/VM のコールドスタート |
| MCP プロトコル | 2026-07-28 ステートレス版 + 2025-11-25/2024-11-05 レガシー版 | 通常はレガシー版のみ |
| HTTPS アップストリーム | リバースゲートウェイ。ルート CA もキーチェーンの変更も不要です | ルート CA をインストールして TLS を傍受 |
| 監査対象のサプライチェーン | 1 つの main.go | 依存関係ツリー全体 |
右側の列は、MITM プロキシでトラフィックをキャプチャして LLM に渡すツール全般について説明したものです。特定の製品について主張するものではありません。起動ベンチマークは公開していません。起動に関する記述はアーキテクチャに基づくものです。
🚀 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 を省略してください。
🔀 2 つのキャプチャモード
モード 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 証明書は不要)
NullWire にはプレーン HTTP で接続し、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 ツールのリファレンス
3 つのツールはいずれも {"resultType":"complete","content":[{"type":"text","text":"…"}],"isError":false} を返します。不明なフロー ID などのツールレベルの問題が発生した場合、RPC が失敗する代わりに isError が true になります。
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 フラグはありません。 | トークンを扱うプロキシに、LAN 上の何者もアクセスできなくなります。 |
| DNS リバインディング対策 | リバースモードでは、Host ヘッダーが 127.0.0.1、localhost、::1 のいずれでもないリクエストは 403 Forbidden になります。 | ドメインを 127.0.0.1 にリバインドする悪意のある Web ページが、ゲートウェイを認証済みリレーとして利用できないようにします。 |
| 自己ループ防止 | プロキシ自身の host:port に解決されるターゲットは 508 Loop Detected になります。 | 誤って設定された -target によるリクエストの殺到を防ぎます。 |
| 切り詰め前のスクラブ | 各本文の最大 64 KB を取得して正規表現で完全にスクラブし、その後で初めて 8 KB(リクエスト)/16 KB(レスポンス)に切り詰めます。 | 8 KB の境界をまたぐシークレットも、切り詰める時点ですでにタグになっています。先に切り詰めると、トークンの半分が残ってしまいます。 |
| プロセスごとの一時ノンス | crypto/rand から取得した 32 バイトを RAM のみに保持します。タグは SHA-256(nonce ‖ secret) の先頭 6 桁の 16 進数です。 | ソルトがなければ、エントロピーの低いシークレット(短い PIN や推測しやすいパスワード)のハッシュは、漏えいしたトランスクリプトから総当たり攻撃やレインボーテーブル攻撃を受ける可能性があります。シークレットノンスがあれば事前計算はできず、再起動をまたいでタグを関連付けることもできません。 |
厳密な stdout の扱い | stdout には、ミューテックスのロック下で書き込まれる、末尾が \n の単一行 JSON-RPC フレームのみが出力されます。バナー、エラー、ステータスバーは stderr に出力されます。 | stdout に余計な 1 バイトが混入するだけで、MCP ストリームが破損します。 |
| 通知への無応答 | id のないメッセージ(または notifications/* メソッドで id: null のメッセージ。例:notifications/initialized)には 0 バイトを出力します。 | JSON-RPC では通知への応答が禁止されており、厳格なクライアントは応答すると切断します。 |
| 元のトラフィックを変更しない | アプリはストリーミングとフラッシュを行うコピー経由で実際のレスポンスを受け取ります。スクラブはバックグラウンド goroutine で別のコピーに対して行われます。 | プロキシがデバッグ対象の動作を妨げてはなりません。 |
| 分離されたトランスポート | プロキシを使わず、圧縮もリダイレクト追従もしない専用の http.Transport を使用します。 | 意図しないプロキシチェーンを防ぎ、リダイレクトを変更せずにアプリへ渡します。 |
| ホップ単位ヘッダーの除去 | Connection、Proxy-*、TE、Upgrade、…を双方向で削除します。 | RFC 9110 に準拠した正しいプロキシ動作を実現します。 |
| パニックの封じ込め | キャプチャと RPC ディスパッチではパニックを回復し、プロセスの終了を防ぎます。 | 想定外のペイロードがセッションを終了させないようにします。 |
スクラブされる対象
| パターン | タグ |
|---|---|
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 を生成し、OS またはブラウザーの信頼ストアに登録しなければなりません。これは鍵素材を扱い、キーチェーンを汚すことになり、フットプリントゼロのツールとは正反対です。リバースゲートウェイなら、そのようなことを一切せずに同じ可視性を得られます。アプリの接続先を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 ビットです。 16 進数 6 文字あれば、50 件のバッファー内にある少数のシークレットを区別するには十分ですが、理論上は衝突する可能性があります。タグは照合用であり、識別用ではありません。
- ボディのサイズには上限があります。 各ボディから取得されるのは先頭の 64 KB のみで、保存されるボディは 8 KB/16 KB で切り詰められます。
Content-Encoding(gzip など)付きで届いたレスポンスは、デコードされず、短いプレースホルダーとして保存されます。NullWire は転送するリクエストからAccept-Encodingを削除するため、ほとんどのサーバーは非圧縮で応答しますが、それを強制することはできません。 - 「RAM のみ」とは、プログラムがファイルに書き込まないという意味です。 ステータスバーの
Disk: 0Bは、計測されたカウンターではなく設計上の不変条件です(ソースコードにはファイル I/O がありません)。OS がメモリーページをスワップしたり、クラッシュダンプを書き込んだりする可能性はありますが、NullWire では制御できません。 - リングバッファーは一時的なものです。 プロセスの終了時に、直近 50 件のフローは消去されます。それが本来の設計です。
- アプリには生のトラフィックが見えます。 NullWire が保護するのは AI エージェント に見える情報であり、ローカルアプリに見える情報ではありません。アプリ自体に対するセキュリティ境界ではありません。
- 対応プロトコルの範囲は、ドキュメントに記載されたとおりです。 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、エラーコード、および通知時の標準出力が0バイトであること。
CI(.github/workflows/release.yml)では go vet と go test -race を実行し、9つのターゲット(Linux amd64/arm64/armv7/riscv64、macOS amd64/arm64、Windows amd64/arm64、FreeBSD amd64)向けに静的な CGO_ENABLED=0 バイナリをクロスコンパイルして、v* タグで SHA256SUMS.txt とともに公開します。
❓ よくある質問
AI が実際のトークンを見ることはありますか?
NullWire のツール経由ではありません。エージェントが受け取るのは [REDACTED_TOKEN#a1b2c3] のようなタグのみです。秘匿化はパターンベースのため、スクラバーが認識しない形式のシークレットは通過する可能性があります。正直な範囲をご覧ください。
アプリが受け取る内容は変わりますか?
アプリはストリーミングコピーを通じて実際のレスポンスを受け取り、スクラブはバックグラウンドのゴルーチンで別のコピーに対して実行されます。ひとつだけ注意点があります。NullWire は上流リクエストの Accept-Encoding を削除するため、通常、レスポンスは圧縮されずに届きます。正直な範囲をご覧ください。
HTTPS API をデバッグするにはどうすればよいですか?
モード2、リバースゲートウェイを使用してください。アプリは 127.0.0.1 に平文HTTPで接続し、NullWire は実際のサーバーに検証済みTLSで接続します。ルートCAは不要で、信頼ストアにも何も追加しません。HTTPS_PROXY(CONNECT)への応答は仕様上 501 です。
エージェントはシークレットを知らずにリクエストを再生できますか?
はい。export_sanitized_curl は、各タグを $NULLWIRE_AUTH_TOKEN のような環境変数に置き換えた、実行可能な curl を再構築します。実際の値を設定するのはユーザーであり、エージェントがそれらを知ることはありません。
トラフィックはどこに保存されますか?
RAM上の50スロットのリングに保存されます。プロセスの終了時に消去され、clear_ram_buffer を使えば必要に応じて消去できます。プログラムにはファイルI/Oがありません。
ネットワーク上に公開できますか?
いいえ。リスナーは 127.0.0.1 にハードコードされ、IsLoopback() でチェックされます。別のアドレスにバインドするフラグはありません。
🤝 貢献
Issue や PR を歓迎します。基本ルール、テストコマンド、PR チェックリストについては、CONTRIBUTING.md をお読みください。セキュリティに関する報告は、公開 Issue ではなく SECURITY.md を通じてお寄せください。
📄 ライセンス
MIT © 2026 VoxNode-ux
NullWire がチャットウィンドウに有効なトークンを貼り付けずに済む助けになったなら、⭐ を付けて、ほかのエンジニアにも見つけてもらいましょう。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。