Hotdry.
security

LLM 流量审计代理架构:Sherlock 的工程实现解析

深入剖析用于审计 LLM 工具流量的 HTTP 代理架构:环境变量劫持、实时仪表盘与零证书配置的设计取舍。

在 LLM 应用开发过程中,流量审计长期处于黑箱状态。开发者难以直观看到每次 API 调用消耗了多少 tokens、上下文窗口的实际使用情况,以及提示词在不同 provider 之间的流转路径。传统方案往往依赖复杂的证书配置或侵入式代码埋点,实施成本居高不下。Sherlock 项目提供了一种轻量级的替代思路:通过环境变量劫持配合本地 HTTP 代理,实现对主流 LLM CLI 工具的无侵入式流量捕获与可视化监控。本文将聚焦其架构设计中的关键工程决策,剖析环境变量路由机制、仪表盘渲染策略以及多 provider 适配的边界条件,为类似审计工具的实现提供可复用的参数模板。

环境变量劫持的核心机制

Sherlock 的流量拦截建立在 LLM CLI 工具普遍支持的环境变量配置机制之上。以 Anthropic 的 Claude Code 为例,官方文档明确指出客户端会优先读取 ANTHROPIC_BASE_URL 环境变量来决定 API 端点地址。当开发者执行 sherlock claude 时,启动脚本首先将 ANTHROPIC_BASE_URL 设置为本地代理地址(默认为 http://localhost:8080),随后启动实际的 CLI 进程。这一步骤的本质是修改子进程的环境上下文,使得所有出站请求在用户无感知的情况下流向本地代理,而非直接发往云端 API。

代理进程在本地监听指定端口(默认 8080,可通过 -p/--port 参数覆盖),接收来自 CLI 工具的 HTTP 请求。收到请求后,代理保留原始请求体的完整副本用于后续分析,同时将请求转发至目标 provider 的真实 API 地址。响应回传时采用同样的镜像逻辑:代理在将响应内容返回给 CLI 工具的同时,完成 token 计数、上下文窗口更新和持久化存储等副作用。这种设计避免了传统 MITM 方案中必须的证书注入和 TLS 解密步骤,因为整个流量拦截发生在客户端进程的出口节点,代理与 CLI 工具之间的通信始终是明文 HTTP。

值得注意的是,这种架构依赖于 provider 端对环境变量配置的原生支持。不同厂商的实现存在差异:Anthropic 和 OpenAI 的 CLI 工具均遵循这一约定,但 Google 的 Gemini CLI 在 OAuth 认证流程中存在已知问题,会忽略自定义 base URL 导致代理失效。这一边界条件要求开发者在选择 provider 时进行前置检查,或在文档中明确标注兼容性问题。

实时仪表盘的渲染架构

Sherlock 的终端仪表盘采用分栏布局设计,核心信息包括上下文使用率燃料条、各请求的时间戳与 provider 标识、以及累计 token 消耗统计。燃料条的可视化逻辑以百分比形式呈现累积 tokens 与设定阈值的比值,并通过颜色编码(绿色 <50%、黄色 50%-80%、红色> 80%)提供即时的容量预警。这一设计借鉴了物理仪表的视觉隐喻,使得开发者能够在密集的调试会话中快速感知预算消耗趋势。

仪表盘的刷新机制基于事件驱动而非轮询。每当代理完成一次请求 - 响应周期,即触发状态更新事件,通知渲染组件重绘受影响的字段。这种事件驱动架构的优势在于最小化 CPU 占用 —— 终端 UI 库(如 curses 或 textual)在等待用户输入时本就处于阻塞状态,仅在状态变化时进行局部刷新即可保持流畅的交互体验。相比之下,基于 time.sleep() 的轮询方案在高频 API 调用场景下会造成不必要的资源浪费。

会话结束时,仪表盘会输出汇总统计,包括累计请求次数和总 token 消耗。这一数据来源于代理在会话期间维护的内存状态,会话结束后即丢弃,不涉及持久化存储。如果需要长期追踪,代理的另一个组件负责将每次请求的原始数据写入指定目录,格式包括人类可读的 Markdown 和机器可解析的 JSON,前者便于快速浏览历史会话,后者支持后续的数据分析脚本。

多 provider 适配的抽象层

为了支持不同的 LLM provider,Sherlock 在代理层与 CLI 启动脚本之间抽象了一层 provider 适配逻辑。每种 provider 对应一个注册表项,包含厂商标识、默认 API 端点、环境变量名称以及特定的启动参数模板。以 Anthropic 为例,其注册表项记录了 ANTHROPIC_BASE_URL 环境变量名和 api.anthropic.com 的默认 host;当用户执行 sherlock claude 时,适配层自动填充这些预配置值,并调用 os.environ.update() 将其注入子进程环境。

Provider 注册表的扩展遵循插件化原则:新增支持只需在代码库中声明一个新的配置对象,无需修改代理核心逻辑。这种设计降低了社区贡献的门槛,也使得 Sherlock 能够快速跟进新出现的 CLI 工具。然而,实践中发现部分 provider 的认证流程与代理架构存在冲突 —— 例如 Gemini CLI 在 OAuth 2.0 流程中会跳过环境变量配置,直接使用硬编码的 token 端点。对于这类情况,Sherlock 选择在文档中标注已知问题而非强行兼容,因为绕过 OAuth 流程可能引入安全风险。

工程落地的关键参数

在生产环境中部署类似的流量审计代理时,以下参数值得特别关注。代理端口的选择应避免与本地现有服务冲突,默认为 8080 但可通过 -p 参数覆盖,建议在多代理共存场景下使用高位端口(如 18808)。Token 限制阈值通过 -l/--limit 参数设定,默认 200,000 对应 Claude 3.5 Sonnet 的上下文窗口上限,实际使用时应根据目标模型调整以确保燃料条准确性。

对于需要长期审计的场景,建议配置独立的持久化目录并设置日志轮转策略。Sherlock 每次请求生成的文件包含请求体 JSON 和带元数据的 Markdown 两个副本,高频调用下可能产生大量小文件,此时应考虑按日期子目录归档或压缩存储。安全方面,虽然当前架构不涉及 TLS 解密,但代理进程本身暴露了明文流量接口,在共享开发机上部署时应限制监听地址为 127.0.0.1 而非 0.0.0.0,避免同一网络的其他用户访问审计数据。

边界条件与已知局限

Sherlock 的设计在轻量级与功能性之间做出了明确取舍。最主要的局限是不支持 HTTPS 流量解密 —— 由于跳过了 MITM 证书注入步骤,代理只能看到明文请求体和响应体,但无法捕获 TLS 握手中的证书链和密钥交换信息。对于需要深度检查加密流量的安全审计场景,当前架构不适用,必须回退到传统的 Burp Suite 或 mitmproxy 方案。

另一个边界条件是流式响应(streaming response)的处理。LLM API 在返回生成内容时通常采用 Server-Sent Events(SSE)协议,数据以 \ndata: {...}\n\n 的增量帧形式传输。Sherlock 的当前实现在捕获流式响应时只能记录完整响应体,无法按帧切分统计 token 消耗。这一局限源于 SSE 解析的额外复杂度,以及不同 provider 在流式响应格式上的差异。如果需要细粒度的流式审计,代理层需要引入专门的 SSE 解析器并维护帧级别的状态机。

资料来源

查看归档