A paranoid Claude Code wrapper for macOS machines. 三层 fail-closed 防护——wrapper + Proxy + PF——让 Claude Code 的流量物理上只能从一个固定 IP 出去。
1.这是一个笨重、overthinking、作者严重封号焦虑下产生的且不一定有意义的极端产物,仅作为我自己的技术/思路分享,并非正式project; 2.代码部分完全由claude,GPT接力完成,不保证没有bug完全能跑; 3.A\封号受多方面因素影响(例如ip本身,用户使用习惯等等),这个项目只是为cc的运行提供一个稳定的网络环境,减少ip泄漏的风险,不保证能完全规避封号的可能性; 4.这个项目在proxy配置,pf kill switch上的策略非常极端,仅供参考;推荐落地的只有wrapper部分,且在落地wrapper前,也强烈建议和你的AI先行讨论并且修改/删除部分条件; 5.请保证你在正式实施前阅读完所有前置条件;
ccroach 是一套围绕 macOS 上 Claude Code的 fail-closed 网络隔离方案。它的设计前提是:
- 我肉身在国内,因此有网络环境的需求
- 我的被封号焦虑大于对工作效率的追求,我不是重度coding用户,对我来说我的账号比项目本身更加重要
- 我有一台单独的mac用于我的AI agent工作,我希望这台机器上的
claude等开发流量从一个固定的公网 IP 出去 - 我不介意任何网络异常、代理链崩溃、IP 漂移、IPv6 leak、DNS泄漏国内ip都导致拒绝启动 / 立刻杀掉这个进程,而不是 fallback 到真实 IP,我不在乎当前操作进度中断造成的后果
┌─────────────────────────────────────────────────────────────┐
│ Claude Code / Codex / npm / pip / git / curl ... │
└───────────────────────────┬─────────────────────────────────┘
│ Wrapped & gated by
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 1 · ccroach wrapper(应用层) │
│ • 完全接管CC启动,启动前 8 项 precheck,未通过 → 拒绝启动 │
│ • 后台 daemon 每 5s 监控 IP / PF / DNS │
│ • 异常时 kill cc 进程 + macOS 通知 │
└───────────────────────────┬─────────────────────────────────┘
│ HTTPS_PROXY=127.0.0.1:6152
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 2 · Proxy Enhanced Mode(策略层) │
│ • Proxy Chain: 机场入口 → 美国家宽 SOCKS5 │
│ • FINAL → strict policy (no DIRECT, no fallback) │
│ • DoH 跟随出站策略 │
└───────────────────────────┬─────────────────────────────────┘
│ TCP only to airport ingress
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 3 · PF kill switch(物理网卡层) │
│ • en0 只放行 → <airport_ip>:<airport_port> │
│ • IPv6 全封 / DNS 53/853 全封 / 私有段全封 │
│ • 兜底 block out(fail-closed) │
└───────────────────────────┬─────────────────────────────────┘
│
▼
Internet
(only via your egress IP)
三层互相独立——任何一层失效,其他两层仍然能拦住绝大多数泄露路径。这就是为什么 wrapper 检查 PF 状态、PF 不信任 wrapper、Surge 不信任 PF:三方互不背书。
| 原则 | 含义 |
|---|---|
| 断网 = 正确失败 | 网络异常时拒绝启动 / kill cc,不允许 fallback 到任何其他出口 |
| 三层独立证据 | wrapper / Surge / PF 不互相信任,每层独立验证 |
| 物理边界优先 | 可信边界是 PF 对物理网卡的控制优先 |
| 不做大锤式断网 | wrapper 只 kill cc 进程;不会 down 网卡、不会 pkill Surge、不会 route delete default |
如果符合下面任何一条,请谨慎考虑:
- ❌ 电脑是日常工作 / 上网机,AI 只是偶尔用一下的工具
- ❌ 没有固定公网出口 IP(住宅 ISP 动态分配 / 经常切换网络),或网络条件非常不稳定、极易出现断联or超时现象(会增加cc被误杀的概率)
- ❌ 需要这台机器同时跑别的需要其他出口的服务
✅ 可能适合:封号/网络环境焦虑大于对工作效率的追求的人、有 dedicated Mac 给 AI / 工作 agent 的人、对 IP 隐私有刚性需求的开发者、想要 fail-closed 而不是 best-effort 的 paranoid 用户。
- 一台 专门 给 AI / Claude Code 使用的 Mac(如果电脑有日常使用需要的话,必须对Proxy和PF策略进行删除或修改,并且对wrapper进行对应调整)
- 物理网卡接口名(一般 Wi-Fi 是
en0,跑ifconfig确认)
- 一个靠谱的proxy软件,可以自定义编写调整策略(这里采用surge)
- 一个固定出口公网 IPv4——可以是:
- 美国家宽 SOCKS5(推荐)
- 任何你能确定 IP 稳定的代理
- 你的机场入口 IP + 端口(用来在 PF 里放行唯一公网出口)
- 已安装的 Claude Code(
npm install -g @anthropic-ai/claude-code或类似) - bash 4+ / coreutils(macOS 自带的够用)
git clone git@github.com:<your-username>/ccroach.git ~/.ccroach
cd ~/.ccroachcp etc/config.example.toml etc/config.toml
# 编辑 etc/config.toml,填入你的:
# expected_ipv4 = "<YOUR_EXPECTED_EGRESS_IPV4>"
# airport_ip = "<YOUR_AIRPORT_INGRESS_IPV4>"
# airport_port = <YOUR_AIRPORT_PORT>
# physical_interface = "en0" # 你的物理网卡
⚠️ 仅供参考:本节描述的是我本机 Surge profile 的关键约束,并非通用配置模板。每个人的机场订阅、节点格式、策略偏好都不一样,请根据自己的情况进行重写or修改。 我自己的 Surge profile 的设计条件是:
- Enhanced Mode 开启(系统全局代理)
- 唯一允许出口指向你的
airport_ip:airport_port - FINAL 走你的 strict policy(禁用 DIRECT / fallback / url-test / load-balance / 自动选择)
- DoH / DoT 跟随出站策略,不走系统 DNS
- IPv6 关闭
- 因为不是日常用机,这里我不保留日常分流、Domestic、Debug、DIRECT
⚠️ 仅供参考:pf/surge-killswitch.example是我本机 PF anchor 的脱敏样本,反映我的具体网络环境(单一物理网卡、固定家宽出口、无 IPv6 需求等)。你的硬件 / 网卡数 / 链路结构如果不同,请根据自己的情况进行修改,不能直接照搬。 装 PF 之前请多次检查——‼️ 错误的 PF 规则可能导致你完全连不上网且无法 ssh 回来修‼️
# 编辑 pf/surge-killswitch.example,替换占位符 + 按你的环境调整规则
vim pf/surge-killswitch.example
# 然后装到系统:
sudo cp pf/surge-killswitch.example /etc/pf.anchors/surge-killswitch
# 在 /etc/pf.conf 里加上(如果还没加过):
# anchor "surge-killswitch"
# load anchor "surge-killswitch" from "/etc/pf.anchors/surge-killswitch"
sudo pfctl -f /etc/pf.conf
sudo pfctl -e我这里另写了一个 LaunchDaemon 让 PF 开机自启。
# 让 claude 命令默认走 ccroach
mkdir -p ~/bin
ln -sf ~/.ccroach/bin/claude ~/bin/claude
# 在你的 ~/.zshrc 或 ~/.bashrc 里添加:
# export PATH="$HOME/bin:$PATH"# 编辑 launchd 模板把 REPLACE_ME_USERNAME 换成你的用户名
sed -i '' "s|REPLACE_ME_USERNAME|$USER|g" launchd/ccroach.daemon.plist.example
cp launchd/ccroach.daemon.plist.example ~/Library/LaunchAgents/ccroach.daemon.plist
launchctl load ~/Library/LaunchAgents/ccroach.daemon.plist# 跑一次 precheck,看 8 项是否全绿
~/.ccroach/ccroach check
# 全绿了再正式启动 Claude Code
claude # 走的是 shim,会被 wrapper 接管
# 看到 "Type START to continue" → 输入 START 启动| 命令 | 说明 |
|---|---|
ccroach run [args...] |
跑 precheck → 等 START 提示 → 启动 daemon → 启动真实 claude |
ccroach check |
只跑 precheck,不启动 cc |
ccroach daemon |
跑后台监控 daemon(一般 LaunchAgent 调用,不手跑) |
ccroach status |
当前状态(缓存值,非实时) |
ccroach logs |
调取实时日志(相当于 tail -f daemon 日志) |
ccroach stop |
停止 cc 和 daemon |
启动前 wrapper 会跑这 8 项:
- Surge — 进程在跑 + 6152 端口监听
- PF — ICMP 直连被阻 + TCP 直连被阻(不采用pf策略的话这里需要删掉)
- Surge 链路 — 经 Surge 出口 IP 等于
expected_ipv4 - IPv4 三源 — ipify / ifconfig.me / icanhazip 至少 2/3 都返回
expected_ipv4 - IPv6 — 四层独立验证:
- 接口层(无全局 v6 地址)
- 系统层(macOS Wi-Fi 服务 v6 = Off)
- PF 运行层(v6 literal 探针被秒拒)
- PF 配置层(surge-killswitch 文件里有
block drop quick inet6 all)
- DoH 出口 — 经 Surge 的 DNS-over-HTTPS 正常工作
- 直连 DNS TCP/53 — 被 PF 封死
- 直连 DNS UDP/53 — 被 PF 封死
任何一项 fail → 拒绝启动。
后台 daemon 每 5 秒跑一次轻量监控(不跑完整 8 项 precheck,避免对网络造成持续负担):
- 每次:当前出口 IP 是否仍等于
expected_ipv4 - 每 30 秒一次:PF ICMP / TCP 直连探针
- 每 60 秒一次:DoH 工作正常 + 直连 UDP/53 被封
返回码:
0 = OK1 = HARD FAIL(确诊 IP 错误 / PF 漏洞)→ 立即 kill cc2 = SOFT FAIL(网络抖动 / 临时无响应)→ 累计 3 次才 kill,避免误杀
重要保护:daemon kill 之前会检查 cc 是否真的在跑——cc 没在跑就只写 log,不弹通知,不退出。避免你不开 cc 时网络抖动也跳通知。
这些是开发过程中真实踩过的坑,仅供参考。
settings.json 里的 Read deny 规则用 glob 时,如果路径含空格(比如 Library/Application Support/Surge/),匹配会失效。解决:用 ** 通配符替代(Library/**/Surge/**)。
chflags uchg 只阻止写 / 删 / 改名,不阻止读。如果你想完全阻止 cc 读某个敏感文件,需要 chmod 改权限或挪到 root-owned 路径。
127.0.0.1:6170/6171 是 Surge 的本地 API 端口。如果不显式 deny,理论上 cc 可以通过它修改 Surge profile。我们的方案是 settings.json 里 deny 整个 127.0.0.1 和 localhost 的 WebFetch + deny curl 127.0.0.1:6170 类 bash 命令。
如果用 curl -6 https://api64.ipify.org 测 IPv6 leak,Surge MitM DNS 会把域名解析成 fake IPv4 ::ffff:198.18.x.x,curl 看到 v6 socket 就以为通了。正确做法是用 IPv6 literal 直连(比如 nc -6 -z 2606:4700:4700::1111 443)绕开 DNS。
utun0-9 不一定是 Surge 创建的——其他 VPN、Tailscale、WireGuard、系统隧道都可能创建。PF 规则只盯物理网卡(en0),不显式信任任何 utun。
曾经讨论过 route delete default / ifconfig en0 down / pkill Surge 这类"全锁死"方案。最终拒绝——PF 已经提供 fail-closed,wrapper 只需要 kill cc。乱关网卡会增加恢复复杂度。
ccroach/
├── README.md ← 你正在看的这个
├── LICENSE ← MIT
├── .gitignore ← 排除 var/、log、用户真实 config.toml
├── ccroach ← 主脚本(~500 行 bash)
├── bin/
│ └── claude ← PATH shim,让 `claude` 命令走 wrapper
├── etc/
│ └── config.example.toml ← 配置模板(填占位符)
├── launchd/
│ └── ccroach.daemon.plist.example ← LaunchAgent 模板
└── pf/
└── surge-killswitch.example ← PF anchor 模板
---
## License
MIT。
---
## 使用感受
Mac air跑cc时后台挂着daemon并没有感受到明显负担or卡顿的情况,也基本上没有误杀的情况,姑且算是使用体验良好,但不确定哪里会不会有bug,readme大部分是CC和GPT写的,怨气有点大有些地方可能语气不太友好hhh
🪳 *Like a cockroach: small, persistent, lives quietly in the corner, almost impossible to dislodge once installed.*