基于 Claude Code Hooks 的 IP 地理位置检测达到账号防封方案记录
背景
最近在团队中推广 Claude Code 的过程中,遇到了一个让人头疼的问题:部分成员的账号被 Anthropic 封禁,原因是 IP 地理位置异常。
Anthropic 对 Claude 的访问有明确的地区限制,同时对账号的 IP 行为也有监控------短时间内城市切换频繁,会被判定为异常使用。尤其是远程办公、出差、使用 VPN 等场景,封号风险极高。
这篇文章记录利用 Claude Code Hooks 机制实现 IP 检测与拦截的思路和关键细节。
Claude Code Hooks 简介
Claude Code 提供了 Hooks 机制,允许在特定事件触发时执行自定义脚本:
| Hook 类型 | 触发时机 |
|---|---|
SessionStart |
每次会话启动时 |
UserPromptSubmit |
每次用户发送消息前 |
Hook 脚本通过退出码控制行为:
exit 0:放行,Claude 正常处理exit 2:拦截,将 stderr 内容展示给用户,阻止当前 prompt 执行
配置写在 .claude/settings.json 中,提交到仓库后对所有团队成员生效。
整体设计
检测目标
- IP 来自受限国家 → 直接拦截,提示切换网络
- 城市发生切换 → 发出分级警告,提示用户注意
双层查询策略
每次 prompt 都做完整地理查询开销太大(需要调外部接口)。一种可行的思路是两层查询:
- 轻量查询(每次 prompt):仅获取当前公网 IP,与缓存比对
- 完整地理查询(IP 变化或超 10 分钟):调用地理接口,获取 country/city 等字段
这样正常使用时每次 prompt 只有一次轻量请求,延迟极低。
接口选型
经测试,以下免费接口可用:
| 用途 | 接口 | 协议 |
|---|---|---|
| 轻量查询(仅 IP) | api.ipify.org |
HTTPS |
| 完整地理(主) | ipinfo.io |
HTTPS |
| 完整地理(备) | ip-api.com |
HTTP |
核心实现
目录结构
.claude/
├── settings.json
└── scripts/
├── ip-guard-lib.sh # 共享库
├── check-ip-on-start.sh # SessionStart hook
└── check-ip-on-prompt.sh # UserPromptSubmit hook
缓存格式
# ~/.cache/claude-ip-guard/ip_cache
1742380000|US|Salt Lake City|1.2.3.4
[时间戳]|[国家码]|[城市]|[IP]
关键逻辑:check-ip-on-prompt.sh
bash
main() {
# 1. 轻量查询当前 IP
current_ip=$(query_current_ip)
# 2. 读取缓存,验证时间戳格式
IFS='|' read -r cached_ts cached_country cached_city cached_ip < "$CACHE_FILE"
if ! [[ "$cached_ts" =~ ^[0-9]+$ ]]; then cached_ts=""; fi
# 3. IP 未变 且 缓存未过期 → 复用缓存
elapsed=$((now - cached_ts))
if [ "$current_ip" = "$cached_ip" ] && [ "$elapsed" -lt 600 ]; then
is_blocked "$cached_country" && exit 2
exit 0
fi
# 4. 否则完整地理查询
geo_result=$(query_geo "$current_ip")
IFS='|' read -r ip country region city org <<< "$geo_result"
# 5. 更新缓存,执行拦截检测
echo "${now}|${country}|${city}|${ip}" > "$CACHE_FILE"
process_geo_result "$ip" "$country" "$region" "$city" "$org" "$cached_city"
}
城市切换分级告警
bash
build_city_change_warning() {
local count="$3"
if [ "$count" -ge 7 ]; then
header="[严重警告] 近 30 天城市切换次数过高(${count} 次)..."
elif [ "$count" -ge 4 ]; then
header="[警告] 近 30 天城市切换次数异常(${count} 次)..."
elif [ "$count" -ge 2 ]; then
header="[注意] 近 30 天已发生 ${count} 次城市切换..."
else
header="[提示] 检测到网络城市发生变化..."
fi
}
统计口径:ip_history.jsonl 中近 30 天的全部条目数(文件本身只保留 30 天)。
Fail-safe 设计
所有外部接口不可用时一律 exit 0 放行,避免网络故障导致误拦截:
bash
if [ $? -ne 0 ] || [ -z "$geo_result" ]; then
log "接口不可用,放行(fail-safe)"
exit 0
fi
Hook 配置
json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [{ "type": "command", "command": "bash .claude/scripts/check-ip-on-start.sh", "timeout": 15 }]
}
],
"UserPromptSubmit": [
{
"hooks": [{ "type": "command", "command": "bash .claude/scripts/check-ip-on-prompt.sh", "timeout": 15 }]
}
]
}
}
一个值得注意的坑
SessionStart 的 exit 2 不会将 stderr 展示给用户,也不会阻断会话启动。
这是 Claude Code 目前的架构限制。解决方案是:SessionStart 负责更新缓存,真正的可见拦截交给 UserPromptSubmit------用户发第一条消息时就会触发检测,缓存命中后直接判断 country 是否受限。
运行效果
检测到受限地区 IP 时:
[访问受限] 检测到您当前的网络 IP(1.2.3.4)位于受限地区(CN),
无法使用 Claude。请切换网络后重试。
检测到城市切换时:
[提示] 检测到网络城市发生变化(北京 → 新加坡),请确认网络环境正常。
最近 30 天 IP 使用记录:
时间 IP 完整地址
--------------------------------------------------------------------------------
2026-03-20 10:00:01 1.2.3.4 SG · Central Singapore · Singapore (...)
总结
通过 Claude Code Hooks 机制,用 bash + python3 可以实现:
- IP 地理位置检测与受限地区拦截
- 城市切换异常分级告警
- 30 天 IP 历史记录
- 智能缓存,最小化外部接口调用
整个实现无需修改任何业务代码,.claude/settings.json 提交到仓库后对团队所有成员透明生效。
本文对应的实现代码参考:https://github.com/cso1z/claude-ip-guard