Claude 会在响应里偷偷打水印?我用代码验证了一下
Claude 会在响应里偷偷打水印?我用代码验证了一下
最近 Hacker News 上有个帖子炸了——有人发现 Claude Code 在发出的 HTTP 请求里做了隐写术(steganography)标记。
这件事本身挺有意思:一个 AI 工具在你不知情的情况下,在请求里嵌入了可识别信息。不管出于什么目的,这让很多人开始思考一个问题:我调用的 API,到底在传输什么?
今天不讨论阴谋论,而是做一件更有价值的事——自己动手,把 Claude 的每一个请求都抓出来审一遍,包括 headers、body、响应延迟,看看里面究竟有什么。
先说原理
隐写术在 HTTP 里的常见做法:
- Header 顺序:HTTP/2 里 header 的传输顺序本身可以编码信息
- 空白字符:JSON body 里不影响解析的空格、换行
- 大小写变体:某些 key 的大小写
- 时间间隔:请求的发送时序
我们要做的是:写一个透明代理(transparent proxy),拦截所有发往 Claude API 的请求,打印原始字节,分析 header 结构,记录时序。
环境准备
pip install fastapi uvicorn httpx rich
核心代码:30 行搭一个 API 透明审计代理
# audit_proxy.py
import httpx
import json
import time
from fastapi import FastAPI, Request, Response
from rich.console import Console
from rich.panel import Panel
from rich.syntax import Syntax
app = FastAPI()
console = Console()
# 你想审计哪个上游就填哪个
# 用无量Api,价格比官方便宜 60%,格式完全兼容
UPSTREAM_BASE = "https://api2everything.xyz/v1"
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def proxy(path: str, request: Request):
body = await request.body()
headers = dict(request.headers)
# ---- 打印请求详情 ----
console.print(Panel(f"[bold cyan]→ {request.method} /{path}[/bold cyan]"))
# 分析 header 顺序(隐写术关注点)
console.print("[yellow]Header 顺序:[/yellow]")
for i, (k, v) in enumerate(request.headers.items()):
# 隐藏 Authorization 的实际值
display_v = v[:8] + "..." if k.lower() == "authorization" else v
console.print(f" [{i:02d}] {k}: {display_v}")
# 分析 body 里的空白字符
if body:
try:
parsed = json.loads(body)
raw_str = body.decode()
spaces = raw_str.count(' ')
newlines = raw_str.count('\n')
console.print(f"[yellow]Body 空白字符: 空格={spaces}, 换行={newlines}[/yellow]")
syntax = Syntax(
json.dumps(parsed, indent=2, ensure_ascii=False),
"json", theme="monokai", line_numbers=True
)
console.print(syntax)
except Exception:
console.print(f"[red]Raw body: {body[:200]}[/red]")
# ---- 转发到上游 ----
t0 = time.time()
async with httpx.AsyncClient() as client:
upstream_url = f"{UPSTREAM_BASE}/{path}"
resp = await client.request(
method=request.method,
url=upstream_url,
headers={k: v for k, v in headers.items()
if k.lower() not in ("host", "content-length")},
content=body,
params=dict(request.query_params),
timeout=60.0
)
elapsed = time.time() - t0
# ---- 打印响应详情 ----
console.print(f"[green]← 响应 {resp.status_code},耗时 {elapsed*1000:.1f}ms[/green]")
console.print("[yellow]响应 Header 顺序:[/yellow]")
for i, (k, v) in enumerate(resp.headers.items()):
console.print(f" [{i:02d}] {k}: {v}")
try:
resp_json = resp.json()
console.print(Syntax(
json.dumps(resp_json, indent=2, ensure_ascii=False),
"json", theme="monokai", line_numbers=True
))
except Exception:
pass
return Response(
content=resp.content,
status_code=resp.status_code,
headers=dict(resp.headers),
media_type=resp.headers.get("content-type")
)
跑起来
# 启动代理,监听本地 8080
uvicorn audit_proxy:app --host 0.0.0.0 --port 8080
# 另开一个终端,用代理发一条消息
curl http://localhost:8080/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-你的key" \
-d '{
"model": "claude-sonnet-4-5",
"messages": [{"role": "user", "content": "hi"}]
}'
你会看到终端里完整打印出请求的每一个 header、顺序、body 空白分布,以及响应的全部细节。
实测发现了什么
用这个代理跑了几十个请求后,几个观察:
1. 标准 curl 发出的请求 header 顺序是固定的,和工具无关
2. **Claude Code 的请求里确实有一个 x-stainless-* 系列 header**,这是 Stainless SDK 自动注入的——不是隐写,是公开的 SDK 指纹
3. JSON body 的空白分布在不同客户端之间有差异,但这更多是序列化库的行为,不是有意编码
4. 响应里没有发现任何异常 header,至少在这个转发链路上没有
HN 那个帖子说的"隐写标记",大概率指的就是 SDK 指纹这类东西——它不是藏起来的秘密,是 Anthropic 用来分析 API 调用来源的正常工程实践。
把代理改成批量 repo 审计工具
既然代理能拦截所有请求,稍微改一下就能把它变成一个自动化代码审计工具:
# batch_audit.py
import asyncio
import httpx
from pathlib import Path
BASE_URL = "https://api2everything.xyz/v1" # 无量Api,兼容 OpenAI 格式
async def audit_file(client: httpx.AsyncClient, file_path: str, api_key: str) -> dict:
code = Path(file_path).read_text(encoding="utf-8", errors="ignore")
if len(code) > 8000:
code = code[:8000] + "\n... (truncated)"
resp = await client.post(
f"{BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": f"审计以下代码,找出安全漏洞和明显 bug,用中文输出,每条一行:\n\n```\n{code}\n```"
}]
},
timeout=60.0
)
result = resp.json()
return {
"file": file_path,
"issues": result["choices"][0]["message"]["content"]
}
async def audit_repo(repo_path: str, api_key: str, extensions=(".py", ".js", ".ts")):
files = [
str(p) for p in Path(repo_path).rglob("*")
if p.suffix in extensions and ".git" not in str(p)
]
print(f"找到 {len(files)} 个文件,开始审计...")
async with httpx.AsyncClient() as client:
# 并发控制,避免超限
semaphore = asyncio.Semaphore(5)
async def bounded(f):
async with semaphore:
return await audit_file(client, f, api_key)
results = await asyncio.gather(*[bounded(f) for f in files])
for r in results:
if r["issues"].strip():
print(f"\n{'='*50}")
print(f"📁 {r['file']}")
print(r["issues"])
if __name__ == "__main__":
import os
asyncio.run(audit_repo(
repo_path="./your-project",
api_key=os.environ["API_KEY"]
))
一条命令,把整个项目扫一遍。
关于费用
Claude Sonnet 4.5 官方价格是 $3/M input tokens。如果你在跑这类批量任务,成本会很快累积。
我现在用的是无量Api,同样的模型便宜约 60%,国内直连不需要代理,接入方式就是改一行 base_url,其他代码完全不动。注册还送 ¥1 余额,测试够用了。
小结
- HTTP 请求里确实可以做隐写,但 Claude Code 那个更可能是 SDK 指纹,不是恶意行为
- 自己搭透明代理是验证这类问题最直接的方式,不用猜
- 顺手把代理改成批量审计工具,对 CI/CD 流程很有用
有问题或者跑出了有趣的结果,评论区见。觉得有用的话点个赞,我会继续写这类可以直接跑的实战内容。