尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

面向 RubyDoc.info 的审计:TaoToken 给文档 Agent 供 Key

面向 RubyDoc.info 的审计:TaoToken 给文档 Agent 供 Key 1. 从一个 .yardopts 文件说起为什么文档审计要先管住 Key最近 Ruby 生态里被反复讨论的一件事是有恶意 gem 被指与某个自动化机器人相关它在仓库里塞了一个看起来人畜无害的.yardopts通过--load参数让 YARD 在生成文档时去加载外部脚本。这条链路真正麻烦的地方在于触发点你本地bundle install会触发CI 会触发文档站点在后台解析并渲染 YARD 产物时同样会触发。而很多场景下承载 YARD 处理的容器是带网络出口的脚本一旦跑起来就能对外发起请求、抓取环境信息、把结果带出去。我这一轮的工作是站在 RubyDoc.info 审计员的位置上把「文档处理链路」当成一个需要被审计的攻击面来处理不是去读新闻而是产出一份可复现的文档站审计清单、一套给文档 Agent 用的参数、以及一组能落地复跑的检测结果。要让文档 Agent 帮我读 YARD 解析产物、比对上游 gem 源码、输出结构化结论我必须先给它一把独立的、可撤销的、能限流的 Key。获取入口在 TaoToken 官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentyard_audit_intro Base URL 统一用https://taotoken.net/api。注意这里的顺序——先拿到凭证、先固定 Base URL、先把调用边界写进配置再让 Agent 去碰任何 gem 目录。本文不复述事件本身只写怎么搭一条可复现的审计流水线本地复现 YARD 触发点、用 Agent 做静态比对、把 Claude Code 和 Codex 的配置写对、用 CC Switch 做供应商隔离最后给出一份可以直接抄走的审计清单和检测结果模板。命令和 SQL 都由你在本地执行不要把审计用的 Agent 直接接到任何生产库或线上文档服务上。2. 审计靶场把 YARD 的触发点在本地钉死要做审计第一步不是写提示词而是先知道「什么时候会执行代码」。YARD 在执行阶段会读取项目根目录的.yardopts把里面的参数当成命令行选项处理。除了--load--plugin和--scripts这类同样指向外部代码的参数也要一起盯。下面这个脚本只做一件事扫出所有可能引入外部执行的位置输出机器可读的 JSON供后面的 Agent 消费。# audit_yardopts.rb # 用途在只读挂载的 gem 目录里静态提取 YARD 相关的高风险参数 require json require pathname DANGEROUS { --load 在 YARD 进程内加载 Ruby 文件等价于任意代码执行, --plugin 加载 YARD 插件插件本身是 Ruby 代码, --scripts 指定脚本目录可能被用于间接加载 }.freeze FILES %w[.yardopts .yardopts.rb yardopts Gemfile Rakefile].freeze def scan(root) findings [] FILES.each do |name| path Pathname.new(root).join(name) next unless path.file? path.each_line.with_index(1) do |line, lineno| stripped line.strip DANGEROUS.each do |flag, risk| next unless stripped.include?(flag) findings { file: path.to_s, line: lineno, flag: flag, raw: stripped, risk: risk } end end end findings end root ARGV[0] || . result { root: File.expand_path(root), findings: scan(root) } puts JSON.pretty_generate(result)跑法固定成一条命令方便在不同 gem 上横向比较git clone --depth 1 待审 gem 的仓库地址 /tmp/audit-target ruby audit_yardopts.rb /tmp/audit-target /tmp/audit-target.yardopts.json接下来是容器层的证据。很多同学只检查代码忽略了运行时权限结果「没有网」这个假设从来没被验证过。用下面两条命令对比能直接看出默认网络策略到底放开了什么# 默认网络观察是否存在外部 DNS 与 HTTP 出口 docker run --rm -v /tmp/audit-target:/src:ro ruby:3.3 \ bash -lc getent hosts example.com; (command -v curl /dev/null curl -s -m 5 -o /dev/null -w %{http_code}\n https://example.com) || echo no-curl # 关掉网络审计 YARD 解析本身是否能在无出口环境完成 docker run --rm --network none -v /tmp/audit-target:/src:ro ruby:3.3 \ bash -lc cd /src gem install yard --no-document /dev/null 21 yard doc --dry-run 21 | tail -n 20如果第二条在--network none下依然能完成解析说明你的文档流水线完全可以收敛到无出口环境这是一个低成本、高收益的加固动作。把这两条的输出一起存进证据目录后面写结论时就有据可依。3. TaoToken 接入Base URL、Key 与文档 Agent 的最小调用骨架审计 Agent 不需要很复杂它需要的是稳定的模型入口和可控的成本边界。先去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentkey_for_doc_agent 拿到 Key然后在本地环境里固定三个变量避免把凭证硬编码进任何脚本export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODELYOUR_MODEL_ID # 在控制台的模型列表里选一个适合长文分析的连通性自检用一条 curl 就够确认网络、凭证、路径三者都对curl -sS $TAOTOKEN_BASE_URL/v1/messages \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { \model\: \$TAOTOKEN_MODEL\, \max_tokens\: 128, \messages\: [{\role\: \user\, \content\: \只回复 ok\}] } | head -c 400返回正常之后把审计脚本里那坨 JSON 喂给 Agent。这里有个关键设计不要让 Agent 自由发挥总结而是用固定的输出契约让它只做「分类 定位 建议」。结构化输出能直接进你的审计报告也方便回归对比。# audit_agent.py —— 把静态扫描结果交给文档 Agent 做归类 import json import os import urllib.request BASE_URL os.environ[TAOTOKEN_BASE_URL] API_KEY os.environ[TAOTOKEN_API_KEY] MODEL os.environ[TAOTOKEN_MODEL] SYSTEM_PROMPT 你是 RubyDoc.info 的文档链路审计助手。 输入是 YARD 相关高风险的静态扫描结果。 你只能依据输入内容作答不得补充未在输入中出现的事实。 输出必须是严格的 JSON结构如下 { severity: high|medium|low, trigger_stage: [install|ci|doc_build|doc_site], evidence: [{file: , line: 0, flag: }], needs_network: yes|no|unknown, remediation: [不超过 5 条每条一行] } def ask(scan_json: dict) - dict: payload { model: MODEL, max_tokens: 1024, system: SYSTEM_PROMPT, messages: [{ role: user, content: 扫描结果如下\n json.dumps(scan_json, ensure_asciiFalse) }] } req urllib.request.Request( f{BASE_URL}/v1/messages, datajson.dumps(payload).encode(utf-8), headers{ x-api-key: API_KEY, anthropic-version: 2023-06-01, content-type: application/json, }, methodPOST, ) with urllib.request.urlopen(req, timeout90) as resp: body json.loads(resp.read().decode(utf-8)) text .join(b.get(text, ) for b in body.get(content, [])) return json.loads(text) if __name__ __main__: with open(/tmp/audit-target.yardopts.json, encodingutf-8) as f: print(json.dumps(ask(json.load(f)), ensure_asciiFalse, indent2))这段代码有三个刻意为之的地方。第一模型和入口全部走环境变量方便在 CI 里换 Key 而不改代码第二system prompt 明确禁止模型脑补审计结论最怕「听起来合理但没证据」第三输出强约束为 JSON方便你在多份 gem 之间做横向 diff。如果你更习惯 OpenAI 风格的 SDK把路径换成$TAOTOKEN_BASE_URL/v1/chat/completions、把认证头换成Authorization: Bearer $TAOTOKEN_API_KEY即可Base URL 本身不变。4. Claude Code 侧settings.json 与 ANTHROPIC_* 的写法审计工作里我大量使用 Claude Code 做「读仓库、定位文件、比对参数」这类脏活。它的配置有两个位置容易混用户级的~/.claude/settings.json和项目级的.claude/settings.json。审计场景建议用项目级避免污染你日常的默认供应商而供应商相关内容全部通过env字段注入。{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_MODEL_ID, ANTHROPIC_SMALL_FAST_MODEL: YOUR_FAST_MODEL_ID, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1 }, permissions: { allow: [ Read(//tmp/audit-target/**), Bash(ruby audit_yardopts.rb:*), Bash(docker run --rm --network none:*) ], deny: [ Read(./.env), Bash(curl:*example.com*) ] } }几个落到实处的点ANTHROPIC_BASE_URL只写到https://taotoken.net/api不要再手工拼/v1路径由客户端自己拼拼错会得到 404 而不是 401容易误判成 Key 问题。ANTHROPIC_AUTH_TOKEN放的是你的 Key不要把YOUR_API_KEY原样提交进 Git本地用 direnv 或 shell 注入变量后用${TAOTOKEN_API_KEY}这类引用更稳。ANTHROPIC_SMALL_FAST_MODEL用于标题生成、会话摘要这类轻量任务审计场景建议指向一个更便宜的模型主模型留给真正的分析。permissions.deny里显式挡住.env和外部连通测试防止 Agent 在排查过程中把环境变量打印到日志。写完配置后用一条命令验证 Claude Code 实际读到的入口claude --version env | grep -E ^ANTHROPIC_ | sed s/\(TOKEN\).*/\1***/确认ANTHROPIC_BASE_URL输出的是 TaoToken 的地址而不是上一家公司内网的代理。这一步看似多余实际上它能挡掉大部分「配置看着对、行为不对」的诡异问题。5. Codex 侧config.toml 而不是 ANTHROPIC_*Codex 的配置体系跟 Claude Code 完全不同最大的错误就是把ANTHROPIC_*那套变量照搬过去。Codex 读的是~/.codex/config.toml供应商通过model_providers段声明凭证通过环境变量名间接引用。正确形态是这样# ~/.codex/config.toml model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api/v1 env_key TAOTOKEN_API_KEY wire_api chat配套的 shell 环境只保留一个变量export TAOTOKEN_API_KEYYOUR_API_KEY这样拆的好处是Claude Code 用ANTHROPIC_*系列Codex 用TAOTOKEN_API_KEYconfig.toml两套配置互不干扰同一个 Key 可以复用在两个工具上但切换供应商时不会互相污染。审计任务里我通常让 Codex 负责「大范围检索 生成补丁建议」让 Claude Code 负责「精读单个文件 解释解析链路」两边跑在同一个 Base URL 上账单和配额都能在一个地方看。如果你在config.toml里写了base_url https://taotoken.net/api却发现请求 404八成是 Codex 的 chat 兼容层期望路径带/v1把base_url补成https://taotoken.net/api/v1再试反过来Claude Code 那边如果多写了/v1则会出现路径重复。记住一句话客户端负责拼版本前缀你只提供基址。6. CC Switch 三件套把审计 Agent 的供应商切换做成可回滚动作审计工作经常需要在多个供应商、多个 Key 之间切换一个用于大批量静态分析一个用于精读一个留给 CI。手工改配置文件迟早会改错我用 CC Switch 把这件事收敛成三个东西——Provider 列表、激活写入、环境校验。第一件Provider 列表。把每个供应商的base_url、env_key、目标工具claude / codex写进配置作为唯一事实来源{ providers: [ { id: taotoken-audit, label: TaoToken / 文档审计, tools: [claude, codex], base_url: https://taotoken.net/api, env_key: TAOTOKEN_API_KEY }, { id: taotoken-ci, label: TaoToken / CI 只读, tools: [codex], base_url: https://taotoken.net/api, env_key: TAOTOKEN_CI_KEY } ] }第二件激活写入。切换动作要把选中的 Provider 落到工具自己的配置文件里对 Claude Code 写settings.json的env段对 Codex 写config.toml的model_providers与model_provider。切换完成后不要让脚本「静默成功」一定要打印出实际写入的路径和base_url这样你在审计报告里才能引用「当时用的是哪个入口」。第三件环境校验。切换后立刻做一次最小连通测试并把结果记进审计日志set -euo pipefail : ${TAOTOKEN_API_KEY:?TAOTOKEN_API_KEY 未设置} curl -sS -o /tmp/probe.json -w http%{http_code}\n \ https://taotoken.net/api/v1/messages \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d {model:YOUR_MODEL_ID,max_tokens:16,messages:[{role:user,content:ping}]}三件套跑顺之后一个典型审计流程就变成切到taotoken-audit→ 跑静态扫描 → 让 Agent 产出结构化结论 → 切回默认 Provider。整个过程中没有任何一步需要手工编辑配置文件也就没有「审计时用的是生产 Key」这类事故。顺带一提审计时用的 Key 建议单独建一个只给它必要的额度用完就撤创建入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentaudit_key_isolate 。7. 文档站审计清单12 项、可勾选、可回归把上面的动作整理成清单每次审一个新 gem 或新文档站时逐条过。清单本身不依赖任何模型模型只负责帮你解释结果。# doc_site_audit_checklist.yaml target: /tmp/audit-target items: - id: YD-01 check: 是否存在 .yardopts / .yardopts.rb evidence: 文件路径 内容哈希 - id: YD-02 check: .yardopts 中是否含 --load severity: high - id: YD-03 check: .yardopts 中是否含 --plugin / --scripts severity: high - id: YD-04 check: gemspec 的 extensions 是否有编译期脚本 severity: medium - id: YD-05 check: Rakefile 是否在默认任务中触发 yard severity: medium - id: YD-06 check: 仓库内是否存在 base64 / eval / system 组合 severity: high - id: YD-07 check: 是否引用外部 URL 或 IP 字面量 severity: high - id: YD-08 check: CI 配置中文档任务的网络策略 severity: medium - id: YD-09 check: 文档构建容器是否以非 root 身份运行 severity: medium - id: YD-10 check: --network none 下文档任务能否完成 severity: low - id: YD-11 check: /tmp 与工作目录是否可写最小权限 severity: low - id: YD-12 check: 审计 Agent 是否只挂载只读目录 severity: high跑完之后你应该拿到一份检测结果形态大致如下这是我实际跑一份公开仓库时得到的样子字段名沿用上面的 Agent 输出契约{ target: /tmp/audit-target, yardopts_present: true, findings: [ {file: .yardopts, line: 3, flag: --load, raw: --load ./tasks/pre.rb} ], agent_conclusion: { severity: high, trigger_stage: [install, ci, doc_build, doc_site], needs_network: yes, remediation: [ 在 CI 中固定使用锁定后的依赖版本文档任务与安装任务分离, 文档构建容器统一使用 --network none, 对 .yardopts 增加仓库级静态检查命中高风险参数直接失败 ] } }清单的价值在于「可回归」同一个 gem 的下一个版本用同样的命令再跑一次diff 两份 JSON就能看出这次改动有没有把风险面放大。8. 常见报错与排障顺序接入阶段最容易踩的坑集中在四类按这个顺序排查可以少走弯路。第一类401 / 403。先确认ANTHROPIC_AUTH_TOKEN或x-api-key里放的是完整 Key没有多余空格或换行再确认 Key 没有被误撤。用第 3 节的 curl 单独验证不要通过任何工具链验证。第二类404。几乎是路径问题。Claude Code 保留https://taotoken.net/apiCodex 的config.toml用https://taotoken.net/api/v1手写 curl 时再拼/v1/messages。三种写法混用必出错。第三类模型不存在。把YOUR_MODEL_ID换成你账户下真实可用的模型标识别照抄教程里的字符串。模型列表在控制台里看选一个长上下文、适合读代码的即可。第四类Agent 输出不是合法 JSON。通常是max_tokens给小了导致截断或者 system prompt 里的结构被模型自由发挥。把max_tokens提到 1024 以上并在 prompt 里加一句「只输出 JSON不要任何解释文字」。排障时把每次请求的http_code和响应体前 200 字符打到日志里比反复猜配置有效得多。这些日志同时也是审计证据的一部分说明你的结论是在什么条件下得出的。9. 把结论交出去之前边界与收尾回到审计员的本职工作。文档链路的特殊性在于它的执行发生在「你并不盯着」的时候——CI 跑、文档站后台跑、别人 clone 之后跑。所以审计结论必须包含两件事一是触发条件的精确定位二是加固后能否复跑验证。有三条边界我会写进每一份报告。第一审计 Agent 只读挂载目标目录不给写权限也不挂载宿主机的凭证目录第二审计用 Key 单独创建、单独限额、随时可撤不和线上服务共用第三所有命令、SQL、检测脚本都由人在本地执行Agent 的职责是解释和归类不是执行。Agent 的结论必须能回溯到具体文件与行号任何「凭感觉」的判断都不进最终报告。如果你也想把这条流水线跑起来建议的路径是先用模型对话把审计提示词和输出契约调通确认结构化结果可用再根据任务量选择合适的能力档位把它固化进 CI然后单独创建一个只用在这条流水线上的 Key最后照着 Claude Code 的配置文档把settings.json落到项目里确保团队里每个人跑出来的结果一致。模型对话先验证提示词与输出契约https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentdoc_audit_chatCoding Plan评估批量审计的额度与档位https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentdoc_audit_plan创建 API Key审计专用独立限额https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentdoc_audit_keyClaude Code 配置文档settings.json 与 ANTHROPIC_* 细节https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentdoc_audit_claudecode审计做的不是「找出一个坏人」而是让下一次处理 YARD 文档时风险面是已知的、可枚举的、可验证的。把 Key 管好、把网络关小、把结论钉在证据上这条链路就稳了。
返回列表