Tanuki 发布本地代理,为 Claude Code 安全测试改写目标信息
Tanuki – Local proxy/wrapper bypassing Claude guardrails for security research
Tanuki 是一个本地匿名化代理,运行在 Claude Code 与 Anthropic API 之间,把提示词和工具输出中的真实域名、IP、邮箱、路径、云标识和渗透测试术语实时替换为 localhost 虚构项目,模型回复再映射回真实值。
“最大的技艺是隐藏技艺。”
Thomas Fuller
用于 AI 辅助安全测试/隐私的代理
Tanuki 位于你本地的 Claude Code 代理和 Anthropic API 之间,实时重写每一个可识别目标的字符串。你的工具命中真实目标;模型只会看到一个虚构的 localhost 项目。
目录
概述
Tanuki 是一个为 AI 辅助安全测试和漏洞赏金狩猎而构建的匿名化代理。当你通过 Claude Code 之类的代理进行渗透测试时,提示词中的每一个真实域名、IP 和 payload 都会离开你的机器。这是一种 OpSec 风险,也可能违反漏洞赏金项目规则或 NDA。
Tanuki 以 Docker 容器方式运行,并透明地拦截该 API 流量。在提示词离开你的机器之前,它会将真实目标数据(域名、子域名、IP、电子邮件、路径、云标识符以及激进的安全术语)替换为无害的虚构开发等价物。当模型响应时,虚构内容会被映射回现实,以便你的本地工具针对真实目标执行。模型以为它在帮你调试一个本地开发应用,而上游提供商对你的实际任务一无所知。
工作原理
flowchart TD
classDef default fill:#050505,stroke:#8b0000,stroke-width:1px,color:#ff3333,font-family:Courier
classDef proxy fill:#110000,stroke:#ff0000,stroke-width:2px,color:#ffffff,font-family:Courier
classDef hooks fill:#000000,stroke:#550000,stroke-width:1px,stroke-dasharray:4 4,color:#cc0000,font-family:Courier
classDef upstream fill:#0a0a0a,stroke:#333333,stroke-width:1px,color:#666666,font-family:Courier
classDef data fill:#1a0505,stroke:#aa0000,stroke-width:1px,color:#ff6666,font-family:Courier
subgraph Host["LOCAL MACHINE"]
C["Claude Code"]
H[/"Tanuki Hooks<br/>(docker exec)"/]
C -. "Tool execution" .-> H
end
subgraph Container["DOCKER CONTAINER"]
P{"Tanuki Proxy<br/>[scratch ~10MB]"}:::proxy
D[("( mappings volume )")]:::data
end
A((("Anthropic API"))):::upstream
C == "Real prompt data" ==> P
P -. "Loads config" .-> D
P -- "Rewrites to Fiction" --> A
A -- "Responds in Fiction" --> P
P == "Rewrites to Real" ==> C
class C default
class H hooks
style Host fill:transparent,stroke:#8b0000,stroke-width:1px,color:#8b0000,font-family:Courier
style Container fill:#020000,stroke:#4a0000,stroke-width:1px,stroke-dasharray:3 3,color:#8b0000,font-family:Courier
加载中
| 方向 | 路径 | 发生什么 |
|---|---|---|
| 入站 | 你的提示词 → API | 域名、组织名称、电子邮件、IP、路径、云标识符和渗透测试术语在模型看到之前被替换为 localhost 虚构内容。 |
| 出站 | API → Claude Code | 虚构术语在响应流中被重写回真实值,因此 Claude Code 的本地工具会收到真实目标数据。 |
| 工具调用 | 通过 docker exec 挂钩 |
一个 PreToolUse 挂钩在工具参数中将虚构→真实重写,因此工具会命中实际目标。工具输出不需要挂钩:它只会在下一个请求中到达模型,而代理会像处理其他任何请求一样重写该请求。 |
示例
你正在测试 bounty.amazon.com。以下是 Tanuki 所做的:
| 你输入(真实) | LLM 看到(虚构) |
|---|---|
amazon.com |
localhost:9000 |
bounty.amazon.com |
localhost:9002 |
any-other.amazon.com |
localhost:9001(通配符) |
admin@amazon.com |
admin@devtarget.local |
Amazon |
DEVTARGET |
52.94.236.248 |
127.0.0.3 |
/var/www/amazon/ |
/var/www/dev/devtarget/ |
s3://amazon-prod |
file:///tmp/devtarget-prod |
SQL injection |
parameterized query test |
reverse shell |
remote debug session |
每个目标域名会获得三个映射:基础域名、用于尚未见过的子域名的通配符,以及检测到每个子域名时的专用映射。通配符正是阻止像 internal-admin.amazon.com 这样未映射的主机将其标签泄露到上游的原因。
快速开始
git clone https://github.com/Pigyon/tanuki.git
cd tanuki
docker compose up -d
claude这会从 GitHub Container Registry 拉取最新发布的镜像,因此不需要 Go 工具链,也不需要构建。
要从源码构建而不是拉取:
docker compose -f compose.yaml -f compose.dev.yaml up -d --build
启动时,代理会自动配置 .claude/settings.json 中的 Claude Code 挂钩和代理环境变量,并写入掩护故事 .claude/CLAUDE.md。域名、电子邮件和 IP 会从你的对话中自动检测,因此无需手动配置目标。
当你输入 “check bounty.amazon.com for SQL injection” 时,代理会:
- 检测到
amazon.com和bounty.amazon.com为新域名 - 创建所有派生映射(组织、电子邮件、路径、云)
- 在请求到达 API 之前重写该请求
包装脚本
包含一个 ./tanuki 包装脚本,这样你就不必输入 docker compose exec tanuki /tanuki <command>:
./tanuki add example.com ./tanuki map ./tanuki status
验证重写
在不访问 API 的情况下测试重写引擎:
./tanuki test "scan bounty.amazon.com for SQL injection and contact admin@amazon.com"
配置
所有内容都是可选的;不配置时,目标会从你的对话中自动检测。要预先配置一个 engagement,请编辑 compose.yaml:
environment: # Custom fiction org name (default: DEVTARGET) - TANUKI_FICTION_ORG=ACMECORP # Pre-seed target domains (comma-separated) - TANUKI_DOMAINS=example.com,api.example.com # Pre-seed target IPs (comma-separated) - TANUKI_IPS=93.184.216.34 # Custom rewrite rules (semicolon-separated, pipe-delimited real|fiction) - TANUKI_RULES=John Smith|Jane Developer;Project Phoenix|Test Alpha
自定义重写规则
对于不是域名或 IP 的字符串(人名、项目名称、内部术语),可通过 compose.yaml(在每次容器启动时应用)或运行时添加自定义规则:
./tanuki add-rule "John Smith" "Jane Developer" ./tanuki add-rule "Project Phoenix" "Test Alpha"
命名 engagement
用于独立项目或自定义虚构组织名称:
./tanuki init bugbounty --fiction-org ACMECORP ./tanuki add bounty.amazon.com
CLI 参考
所有命令均通过 ./tanuki <command>(或 docker compose exec tanuki /tanuki <command>)运行:
| 命令 | 描述 |
|---|---|
setup |
配置 Claude Code hooks(启动时自动执行) |
init <name> [--fiction-org NAME] |
创建命名 engagement |
add <domain> [--fiction-org NAME] |
预置目标域名 |
add-ip <ip> |
预置 IP 映射 |
add-rule <real> <fiction> |
添加自定义重写规则 |
map |
显示当前映射表 |
status |
显示 engagement 信息 |
list |
列出所有 engagement |
activate <name> |
切换当前 engagement |
test <text> |
在示例文本上测试重写 |
verify [file...|-] |
检查映射是否保持;扫描文件中未映射的目标 |
terms |
显示术语映射 |
export [name] |
将 engagement 导出为 JSON |
import <file.json> |
从 JSON 导入 engagement |
reset [--data] |
移除 hooks(--data 还会清除 engagement) |
version |
显示版本和构建信息 |
proxy |
运行代理服务器(容器入口点) |
哪些内容会被重写
基于映射(按 engagement,全部自动检测)
| 类型 | 检测方式 | 虚构 |
|---|---|---|
| 域名 | 在提示词和工具输出中自动检测 | localhost:<port> |
| 组织名称 | 从域名派生 | DEVTARGET(可配置) |
| 邮箱 | 从域名派生 | user@devtarget.local |
| IP(v4 + v6) | 在提示词和工具输出中自动检测 | 127.0.0.x / ::ffff:... |
| 路径 | 从组织名称派生 | /dev/devtarget/ |
| 云资源 | 从组织名称派生 | file:///tmp/devtarget... |
| 自定义规则 | 通过环境变量或 CLI 用户定义 | 用户定义 |
术语(内置,应用于所有 engagement)
复合的渗透测试专用术语会被替换为中性的开发等效词。只有明确无歧义的多词术语会被替换。像 target、payload 或 exploit 这样的单个词会保留,以免破坏正常的代码讨论。
运行 ./tanuki terms 查看完整词典。该列表位于 internal/tanuki/terminology.json,并在构建时嵌入;要自定义它,请编辑该文件并重新构建。
环境变量
在 compose.yaml 中设置:
| 环境变量 | 默认值 | 描述 |
|---|---|---|
TANUKI_DATA |
/data |
Engagement 数据目录(卷) |
TANUKI_PROXY_PORT |
18080 |
代理监听端口 |
TANUKI_PORT_START |
9000 |
域名分配的起始端口 |
TANUKI_UPSTREAM |
https://api.anthropic.com |
上游 API URL |
TANUKI_FICTION_ORG |
DEVTARGET |
用于映射的虚构组织名称 |
TANUKI_DOMAINS |
(无) | 要预置的域名,以逗号分隔 |
TANUKI_IPS |
(无) | 要预置的 IP,以逗号分隔 |
TANUKI_RULES |
(无) | 自定义规则:real|fiction 对,以分号分隔 |
TANUKI_IGNORE |
(无) | 以逗号分隔的域名,永远不会被视为目标(包括子域名)。始终忽略:anthropic.com、claude.com、claude.ai、json-schema.org、Claude Code 所呈现的开发者平台域名(github.com、google.com),以及来自 Claude Code 账户上下文的操作者自己的邮箱域名。如果其中之一是你的真实目标,./tanuki add <domain> 会显式映射它 |
TANUKI_UID / TANUKI_GID |
1000 |
Docker 不得不创建的 .claude/ 目录的所有者;已存在的目录保留其所有者 |
TANUKI_DEBUG |
(无) | 设置为任意非空值以启用调试日志 |
TANUKI_ON_LEAK |
refuse |
当真实值在重写后仍存在时为 refuse 或 warn |
架构
容器:基于 scratch 的镜像,包含单个静态 Go 二进制文件以及 CA 证书。无操作系统、无 shell、无运行时,总计约 10MB。所有能力均被丢弃,只读文件系统,禁止权限提升。
一切都在 Docker 中运行。代理、hooks 和 CLI 都在容器内执行;hooks 由 Claude Code 通过 docker exec 调用,因此不需要宿主机端的二进制文件。
匹配:Aho-Corasick 自动机在流上单次遍历中执行最长优先替换。
JSON 感知:请求体和响应体逐字符串重写,绝不作为原始文本处理。协议字段(model、role、type、id、工具名)保持原样,base64 数据既不重写也不扫描,thinking 块按模型写入的原样返回给 API,因为它们带有签名。
已知限制
- 代理上没有 HTTPS。代理监听的是明文 HTTP。这可以接受,因为它只接受来自 localhost(同一台机器上的 Claude Code)的连接;到 Anthropic API 的上游连接使用 HTTPS。
- 仅支持字面字符串匹配。编码形式(URL 编码、base64 及类似形式)不会被 Aho-Corasick 自动机捕获。
- 术语是单向的。渗透测试术语只做真实→虚构的重写。模型的响应使用虚构术语,会原样通过。
- 通配符子域不可逆。每个没有自己映射的子域都会折叠到同一个通配符值上,因此无法映射回去。自动检测会为它看到的子域创建专用映射,这些子域可以正常往返;通配符是兜底,用于任何漏过检测的内容,它更倾向于隐藏主机名,而不是保持其可用。
- 检测才是薄弱环节,而不是重写。每个出站请求体在重写后都会重新扫描,如果真实值仍然存在就会被拒绝,因此重写器的 bug 不会导致泄露。但没有任何机制能防范检测器一开始就未识别的值——因此下面两条限制才是真正重要的。
- 用于自动检测的精选 TLD 列表。自动检测使用约 70 个常见 TLD 的精选列表,以避免代码模式(如
readme.md、foo.bar或user.id)带来的误报。与日常代码冲突的 TLD(.info、.name、.app、.store、.top、.site,如logger.info或user.name)被排除在外。这些或其他不常见 TLD(.pizza、.click、.it、.id)上的域名可以用./tanuki add手动添加。 - 被忽略的域名需要显式
add才能成为目标。github.com、google.com以及你自己的邮箱域名会被自动检测跳过,这样 Claude Code 自己的 git 远程、连接器和账户上下文就不会污染表格。代价是仅靠自动检测不会映射其中任何一个,因此在少数以 GitHub 或 Google 为目标的场景中,你必须先./tanuki add github.com;残留检查也会跳过被忽略的域名,所以它不会替你发现这个遗漏。 - Thinking 保持虚构。Thinking 块由 API 签名,因此在两个方向上都会原样传递。Claude Code 中显示的 thinking 以虚构术语呈现。
- 流式文本会被短暂扣留。一个虚构值可能每个事件只流出几个字符,因此代理会扣留文本或工具输入块的结尾,因为它仍可能是某个值的开头,等下一个事件确定后再发送。显示可能滞后几个字符;完整值总会以反转形式到达。
验证一次约定
tanuki verify 无需向任何地方发送任何内容即可回答两个问题。
我的映射真的有效吗? 不带参数时,它会取表中的每一个真实值,并将其套用到目标在实践中可能出现的各种形态中——裸字符串、URL 中、反引号内、作为 markdown 链接、JSON 字段中、HTTP 头中、大写形式、句子末尾——然后检查每个形态中该值是否已被消除。一个覆盖了裸字符串的映射,仍可能在其中某个形态下失效,因此值得在项目开始前而非进行中运行。
$ ./tanuki verify Verifying 8 mappings against 12 shapes 96 checks, 0 leaks Every mapped value was rewritten in every shape.
这份文档可以安全发送吗? 给定文件、- 或 < file 重定向,它会重写文本并报告没有映射覆盖的、看起来像真实值的值。这是在草稿报告提交前应运行的检查。
$ ./tanuki verify report.md LEAK report.md partner-portal.acme-supplier.io 93.184.216.34 2 value(s) have no mapping and would be sent as-is. Add them with: tanuki add <domain> / tanuki add-ip <ip>
当它发现问题时,会以非零状态退出,因此可以直接接入提交前钩子或 CI。
故障关闭行为
代理不会转发它无法匿名化的提示。
每个请求体在重写后都会被检查。 在 /v1/messages 请求发出前做的最后一件事,是重新扫描是否还有任何看起来像真实目标的内容。如果有一个幸存,请求会被拒绝并返回 400,该值会被写入 tanuki 日志——绝不会写入 HTTP 响应,因为客户端可能会将其并入下一个提示。在正常工作的流水线中,这永远不会触发;一旦触发,就意味着重写器漏掉了某些内容,该请求体绝不能发往上游。
目标在首次出现的位置就会被映射。 代理会对每个出站请求体运行检测,因此,无论目标出现在你输入的提示中、工具输出中、@ 提到的文件中、粘贴内容中、CLAUDE.md 中、在别处恢复的会话中,还是来自任何非 Claude Code 的客户端,都会在重写之前获得一个虚构值,而不是以明文形式发往上游。
当没有活跃项目、映射表不可读,或映射表为空且请求体中没有任何可识别内容时,请求也会被拒绝。全新项目不再阻塞:第一个提及目标的请求会将其映射并转发匿名化后的请求体。
将 TANUKI_ON_LEAK=warn 设为记录幸存值并仍然转发。这以牺牲保证换取正常运行时间,仅用于诊断误报,不适用于正常使用。
该检查继承了检测的盲区:它识别相同的精选 TLD 列表和相同的字面形式,因此位于不常见 TLD 上或以编码形式出现的目标对两者都不可见。它能捕获重写器故障,而非检测器故障。
免责声明
Tanuki 仅用于授权的安全测试:你已获得书面许可执行的项目,例如范围内的漏洞赏金计划或签约渗透测试。它降低了目标数据暴露给第三方模型提供商的风险;它不保证匿名,也不免除你遵守任何项目规则、保密协议或法律的义务。你对自己的使用方式负责。
许可证
Apache 2.0
来源:Hacker News · github.com