资讯详情

资讯详情

基于 Claude Code Hooks 的 IP 地理位置检测达到账号防封方案记录

1. 为什么要在 Claude Code 里做 IP 地理位置检测先说清楚这套东西是什么、能做什么、适合谁。Claude Code Hooks 是 Claude Code 提供的事件钩子机制允许你在会话启动、用户提交 prompt 等时机自动执行自定义脚本IP 地理位置检测则是通过查询当前出口公网 IP 的归属地判断网络环境是否发生异常漂移。把这两者结合起来就能在账号风控场景下做一层本地化的前置校验适合团队协作、远程办公、经常切换网络环境的开发者。我所在的团队前段时间推广 Claude Code陆续有成员的账号被限制访问排查下来大多和 IP 地理位置异常有关。有的同事上午在家、下午到公司出口 IP 跨了城市有的出差途中用酒店网络归属地直接跳到另一个省份。这些行为在服务端的风控模型里都属于可疑信号。问题在于用户自己往往毫无感知等到账号被限制才反应过来。所以核心诉求很明确在本地就把异常网络环境识别出来提前告警甚至拦截而不是等账号出问题。Claude Code Hooks 正好提供了这个切入点——它不需要你改任何业务代码配置写进.claude/settings.json提交到仓库团队所有成员自动生效。这里要区分两个钩子的职责。SessionStart 在每次会话启动时触发适合做一次性的环境快照和缓存预热UserPromptSubmit 在每次用户发送消息前触发适合做高频的轻量校验。两者配合既能覆盖会话生命周期又不会因为频繁调用外部接口拖慢交互。需要提前说明的是这套方案的本质是自我约束工具帮你发现网络环境的异常变化而不是绕过任何地区限制。检测到受限地区时拦截是为了避免账号在异常状态下继续使用而触发更严重的风控。理解这一点后面的配置和脚本逻辑就顺理成章了。整个链路涉及三个部分Hooks 配置、IP 查询脚本、缓存与告警逻辑。下面我会按可复制的顺序拆开讲每一步都给出完整代码和参数说明你可以直接照着搭。2. TaoToken 前置准备与 Claude Code 接入配置在写 Hooks 之前得先把 Claude Code 本身跑通。如果你还没配置好模型接入Hooks 再完善也没意义。这一步我用 TaoToken 作为接入层来演示它的 Base URL 和 Key 管理比较清晰适合团队统一配置。先说清楚三件套Base URL、API Key、Model ID。这三样缺一不可很多接入失败都是因为只填了 Key 没改 Base URL或者 Model ID 写错。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数。API Key 需要到控制台创建路径是 console 里的 api-keys 页面。Model ID 根据你实际使用的模型填写比如claude-sonnet-4-5这类标识。如果你用的是 Claude Code 原生命令行配置通常写在~/.claude/settings.json或者项目级的.claude/settings.json。环境变量方式也可以ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY两个变量设好即可。我倾向于项目级配置因为可以提交到仓库让团队共享。{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的实际Key, ANTHROPIC_MODEL: claude-sonnet-4-5 } }这里有个容易踩的坑Base URL 结尾不要多加/v1之类的路径TaoToken 的 API 入口就是https://taotoken.net/api多写反而会 404。另外 Key 不要硬编码提交到公开仓库团队场景建议用环境变量注入或者本地覆盖文件。配置完成后先用一个最简单的请求验证连通性。可以直接跑claude命令进入交互发一句你好看是否正常返回。如果报 401说明 Key 无效或没生效如果报连接超时检查 Base URL 是否写错。对于需要长期编码和 Agent 场景的团队可以考虑 Coding Plan 这类方案配额和并发更稳定。但不管用哪种Base URL 和 Key 的配置逻辑是一样的。验证通过后你的.claude/目录结构应该已经存在。接下来我们在这个目录下新增 Hooks 配置和脚本不会影响已有的模型接入设置。两者是正交的env 管模型连接hooks 管事件触发。顺便提一句如果你之前配过 Codex 的auth.json或者 Cline 的 MCP那些是不同工具的配置体系不要混在一起。Claude Code 认的是settings.json里的hooks字段格式后面会给全。3. 可复制的 Hooks 配置与 IP 查询脚本这一节是核心给出能直接落地的配置片段和脚本。目录结构先定好.claude/ ├── settings.json └── scripts/ ├── ip-guard-lib.sh ├── check-ip-on-start.sh └── check-ip-on-prompt.shsettings.json里的 hooks 配置如下注意 matcher 和 timeout 参数{ 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 } ] } ] } }timeout 设 15 秒是留足外部接口的响应时间正常情况几百毫秒就返回了。matcher 为startup表示只在会话启动时触发避免重复执行。共享库ip-guard-lib.sh负责缓存读写和接口查询#!/usr/bin/env bash CACHE_DIR$HOME/.cache/claude-ip-guard CACHE_FILE$CACHE_DIR/ip_cache HISTORY_FILE$CACHE_DIR/ip_history.jsonl mkdir -p $CACHE_DIR query_current_ip() { curl -s --max-time 5 https://api.ipify.org } query_geo() { local ip$1 curl -s --max-time 8 https://ipinfo.io/${ip}/json } is_blocked() { local country$1 case $country in CN|IR|KP|SY|CU) return 0 ;; *) return 1 ;; esac }check-ip-on-prompt.sh的主逻辑重点是双层查询策略——每次 prompt 只做轻量 IP 查询IP 没变且缓存未过期就复用#!/usr/bin/env bash source $(dirname $0)/ip-guard-lib.sh now$(date %s) current_ip$(query_current_ip) if [ -z $current_ip ]; then exit 0 fi if [ -f $CACHE_FILE ]; then IFS| read -r cached_ts cached_country cached_city cached_ip $CACHE_FILE if ! [[ $cached_ts ~ ^[0-9]$ ]]; then cached_ts fi elapsed$((now - cached_ts)) if [ $current_ip $cached_ip ] [ $elapsed -lt 600 ]; then if is_blocked $cached_country; then echo [访问受限] 当前 IP 位于受限地区$cached_country 2 exit 2 fi exit 0 fi fi geo_result$(query_geo $current_ip) if [ -z $geo_result ]; then exit 0 fi country$(echo $geo_result | python3 -c import sys,json;print(json.load(sys.stdin).get(country,))) city$(echo $geo_result | python3 -c import sys,json;print(json.load(sys.stdin).get(city,))) echo ${now}|${country}|${city}|${current_ip} $CACHE_FILE echo {\ts\:${now},\ip\:\${current_ip}\,\country\:\${country}\,\city\:\${city}\} $HISTORY_FILE if is_blocked $country; then echo [访问受限] 检测到当前网络 IP${current_ip}位于受限地区${country}请切换网络后重试。 2 exit 2 fi exit 0check-ip-on-start.sh更简单只负责预热缓存#!/usr/bin/env bash source $(dirname $0)/ip-guard-lib.sh current_ip$(query_current_ip) [ -z $current_ip ] exit 0 geo_result$(query_geo $current_ip) [ -z $geo_result ] exit 0 country$(echo $geo_result | python3 -c import sys,json;print(json.load(sys.stdin).get(country,))) city$(echo $geo_result | python3 -c import sys,json;print(json.load(sys.stdin).get(city,))) echo $(date %s)|${country}|${city}|${current_ip} $CACHE_FILE exit 0关键设计点所有外部接口失败一律exit 0放行这是 fail-safe避免网络抖动导致误拦截。缓存有效期 600 秒正常使用每次 prompt 只有一次轻量请求延迟极低。4. 验证请求与成功结果演示配置写完后必须验证否则你不知道钩子到底有没有生效。验证分三步先确认脚本可执行再模拟一次正常请求最后模拟异地 IP 触发告警。第一步给脚本加执行权限并手动跑一次chmod x .claude/scripts/*.sh bash .claude/scripts/check-ip-on-prompt.sh echo exit code: $?正常情况退出码是 0缓存文件~/.cache/claude-ip-guard/ip_cache会被写入格式是时间戳|国家码|城市|IP。你可以cat一下确认内容。第二步进入 Claude Code 发一条消息观察是否正常响应。如果钩子配置有语法错误Claude Code 启动时会提示 hooks 解析失败。这一步能过说明配置格式没问题。第三步是重点模拟异地 IP。最直接的办法是临时改脚本里的query_current_ip返回一个固定 IP比如把函数改成echo 1.2.3.4然后手动触发bash .claude/scripts/check-ip-on-prompt.sh如果这个 IP 归属受限地区你会看到 stderr 输出拦截提示退出码为 2。在 Claude Code 里UserPromptSubmit 的 exit 2 会把 stderr 内容展示给用户并阻止当前 prompt 执行效果如下[访问受限] 检测到当前网络 IP1.2.3.4位于受限地区CN请切换网络后重试。城市切换的告警则是分级输出。统计口径是ip_history.jsonl里近 30 天的条目数文件本身只保留 30 天。分级逻辑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 }实测下来正常办公场景一天内城市不会变缓存命中率高几乎无感。出差或切换网络时才会触发完整查询和告警。这里有个值得注意的坑SessionStart 的 exit 2 不会把 stderr 展示给用户也不会阻断会话启动。这是 Claude Code 当前的架构限制。所以真正的可见拦截必须交给 UserPromptSubmit——用户发第一条消息时触发检测缓存命中后直接判断 country 是否受限。SessionStart 只负责更新缓存不做拦截。验证通过后把.claude/目录提交到仓库团队成员拉取后自动生效无需各自配置。这就是 Hooks 机制在团队场景下的价值。5. 本篇常见错误排查搭这套链路时我遇到过几类典型报错逐个说清楚排查方向。第一类是 401 未授权。这通常不是 Hooks 的问题而是模型接入配置错了。检查ANTHROPIC_API_KEY是否有效、ANTHROPIC_BASE_URL是否写成https://taotoken.net/api。如果 Key 是从控制台新建的确认没有多余空格。401 和 Hooks 无关但很多人会误以为是脚本拦截先排除这一层。第二类是local proxy failed或连接超时。这多半是curl请求外部 IP 接口时网络不通。检查api.ipify.org和ipinfo.io是否可达公司内网可能有出站限制。这种情况下脚本会走 fail-safe 放行不会拦截但你也拿不到检测结果。解决办法是换用可达的接口或者在内网放行这两个域名。第三类是reading choices相关报错。这通常出现在模型返回格式异常时和 Hooks 无直接关系但如果你在钩子里调用了模型接口就会遇到。检查请求体是否符合 API 规范Model ID 是否拼写正确。第四类是 OAuth 相关报错。如果你用的是 OAuth 方式登录而非 API Key配置路径不同。OAuth 场景下 Base URL 和 Key 的注入方式要参考对应文档不要直接套用 API Key 的写法。第五类是脚本权限问题。报Permission denied时执行chmod x .claude/scripts/*.sh。另外确认settings.json里的 command 路径是相对项目根目录的路径写错会导致钩子静默失败。第六类是缓存文件格式损坏。如果ip_cache里的时间戳不是纯数字脚本里的正则校验会把它置空导致每次都走完整查询。手动删掉缓存文件重新生成即可。排查时建议打开日志在脚本里加set -x或者把关键变量 echo 到 stderr观察实际执行路径。Claude Code 的 hooks 执行日志可以在启动时加--debug查看。对照真实报错时记住一个原则先分清是接入层问题还是 Hooks 层问题。401、OAuth、reading choices 属于接入层Permission denied、缓存格式、接口超时属于 Hooks 层。分层排查能省很多时间。6. 长期使用与团队落地建议这套方案跑通后日常维护成本很低但有几个细节值得长期注意。缓存有效期 600 秒是个平衡点。设太短会导致频繁调用外部接口设太长则网络切换后不能及时感知。如果你的团队网络环境稳定可以适当调长如果经常移动办公调短到 300 秒更灵敏。历史记录文件ip_history.jsonl只保留 30 天需要配合定期清理。可以在 SessionStart 脚本里加一段按时间戳过滤的逻辑删掉超过 30 天的条目避免文件无限增长。团队落地时.claude/settings.json提交到仓库前要确认不含个人 Key。env 部分建议用占位符或者环境变量引用Key 通过本地.env或 CI 注入。这样既共享了 Hooks 配置又不泄露凭证。对于需要长期编码和 Agent 协作的团队接入层的稳定性同样重要。Coding Plan 这类方案在配额和并发上更适合持续使用配合 Hooks 的本地校验能形成接入稳定 风控前置的双层保障。最后提醒一点这套工具的目的是帮你发现网络异常、避免账号在异常状态下继续使用而不是对抗任何平台规则。检测到受限地区时老老实实切换网络才是保护账号的正确姿势。把检测链路建起来让异常可见剩下的交给规范使用习惯。如果你还没配好接入层先去 API Keys 页面创建 Key再对照接入文档把 Base URL 和 Model ID 填对然后回来搭 Hooks。顺序别反否则排查起来会互相干扰。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →