每会话审计日志:限定 AI 编程代理的影响半径
一个持续滚动的审计日志记录 AI 编程代理执行的每条 shell 命令是好事。每会话一份日志更好。本文说明为什么作用域比体量更重要、本周 Coograph 改了什么,以及如何把同样的模式套用到你自己的环境。
两天前我们发了一篇关于 TanStack npm 投毒事件的文章,主张每个运行 AI 编程代理的团队都应该把代理执行的每条 shell 命令都写入一份本地、只追加的日志。我们是认真的。每个 Coograph 安装里都带一份。
然后一位读者问出了击穿我们自己设计的问题:“那个文件是所有会话共用一份,还是每会话一份?”
是所有会话共用一份。比没有强 — 但也只强一点点。本文讲我们改了什么、为什么在事件响应中作用域比体量更重要,以及不论你用不用 Coograph 都能套用同一种模式。
“够用”的审计日志到底要做什么
shell 命令审计日志的工作不是记账。工作是 在压力下快速回答带作用域的问题。三周后你读到一份安全公告。你依赖的某个包在四十分钟的窗口内是恶意的。你要回答一个问题:
“我的 AI 代理在那四十分钟内有没有运行过任何拉取了那个包的命令?”
要回答这个,日志必须同时支持三件事:
- 时间作用域。 公告给你一个窗口。你得 grep 那个窗口。
- 会话作用域。 “代理在我上周调试那个 PR 的对话里做了什么” — 这个问题代理自己答不了,因为它的对话历史是摘要、是片段,而且存在别人的服务器上。
- 命令作用域。 “把每条
npm install都列出来。” 只要文件格式一致就轻而易举。
单一只追加日志能搞定时间作用域(按时间戳排序、切片窗口)。能搞定命令作用域(grep 模式)。但基本搞不定会话作用域,因为会话会交错 — 两个并行的代理会话在不同终端里写进同一个文件,中间没有分隔。如果你知道开始时间还可以重建会话,但凌晨 2 点处理事件时,你不想做任何重建。
每会话日志三件全占,因为会话边界是一等公民。
本周 Coograph 改了什么
本周之前,Coograph 钩子写一个文件:.claude/session.log。每个 Claude Code 会话的每条 Bash 命令,按顺序、带时间戳,仅以换行分隔。一个代理。一个桶。没有作用域。
新钩子一次性带来三个改动:
1. 每会话拆分。 单一全局文件变成两层:
| 文件 | 每行内容 | 何时读 |
|---|---|---|
.coograph/session.log | [2026-05-17 09:42:18] [claude-code] [4f1c8b3e] npm install | 跨每个代理 + 每个会话的单一时序尾部。每行前缀工具名和短会话 id。最适合 跨会话 和 跨工具 grep。 |
.coograph/sessions/<session_id>.log | [2026-05-17 09:42:18] [claude-code] npm install | 每个代理会话一个文件。无会话 id 前缀,因为文件名已经带了。最适合 单会话 问题。 |
2. 统一路径。 日志从 .claude/session.log 搬到 .coograph/session.log。原因是第三个改动。
3. 多工具支持。 同一份审计轨迹现在覆盖 Claude Code、Codex CLI 和 OpenCode。三者都写入相同的 .coograph/ 文件,每行以代理名前缀(claude-code、codex-cli、opencode)。一次 grep、一个真相、三个代理。
Coograph 支持的另外五个工具 — VS Code Copilot、Cursor、Windsurf、Aider、Cline — 截至 2026 年 5 月在任何公开 API 中都没有暴露 pre-tool-use 钩子面,所以我们无法用同样的方式拦截它们的 shell 命令。README 为每个工具记录了回退方案(shell 层 trap DEBUG、VS Code 命令历史等)。当上游工具发布钩子 API 时,我们会接上。
所有 .coograph/ 文件都被 gitignore、按项目、只追加、本地优先。同一个钩子一次写两个文件 — 没有额外成本。会话 id 来自代理负载(Claude Code 每个对话会话传一个 UUID;Codex CLI 传同样的字段;OpenCode 暴露 sessionID)。如果某个代理的负载没带会话 id,相应行会落到名为 unknown.log 的文件 — 仍然记录,只是没分开。
Python 钩子六十行。OpenCode 插件用 TypeScript 写也差不多。三者都放在 github.com/paullukic/coograph 的 .claude/hooks/log-bash.py、.codex/hooks/log-bash.py 和 .opencode/plugin/log-bash.ts。MIT 许可。
为什么会话边界比文件体量更重要
我们本可以靠写一份更密的全局日志来解决会话作用域问题 — 在大文件的每行里嵌入会话 id,按会话 id grep 来限定作用域。多数单体日志系统都是这么干的,而且能跑通。
我们选了两个文件,因为事件响应是人来做的,不是脚本化的。凌晨 2 点你正读一份新公告,你不会想跑一条 grep | awk | sort 管道来抽取一段对话。你想敲的是:
cat .coograph/sessions/<that-one-session>.log
…然后屏幕上出现一份自包含的记录,只含那段对话的命令,按顺序,没有多余噪音。这种人体工学的代价是多一个目录、每条 Bash 命令多一次 open()。我们认这笔账。
全局日志依然存在,因为有些问题天然是跨会话的:
- “本季度全部工作里,代理曾在哪些时刻碰过
/etc/?” - “这台笔记本上所有 Coograph 项目里,曾经有过
curl | sh吗?” - “过去一个月里,代理跑得最频繁的命令是什么?”
这些问题靠带会话 id 前缀的单一尾部就对了。两个文件回答两种形态的问题。两个都留下是最便宜的保险。
你应该能用一条终端命令做的四种操作
我们做个小断言。如果你的审计日志不能让你用一条终端命令完成下面四件事,它就没有干好它的活:
# 1. 限定到一个会话 — 该对话的完整历史(任何代理)
cat .coograph/sessions/4f1c8b3e-a2d1-4f9c-8e7a-2b3c5d6e7f8a.log
# 2. 限定到一个时间窗口 — 已知事件窗口内跑过什么
awk -F'] ' '$1 >= "[2026-05-11 19:20:00" && $1 <= "[2026-05-11 19:26:00"' .coograph/session.log
# 3. 限定到一个命令模式 — 所有代理 + 所有会话中的每次安装
grep -E '^\[.*\] \[(claude-code|codex-cli|opencode)\] \[.*\] (npm|pnpm|yarn) install' .coograph/session.log
# 4. 交叉引用 — 哪些会话跑过可疑命令,按频次排序
grep "curl.*| *sh" .coograph/session.log | grep -oE '\[[a-f0-9]{8}\]' | sort | uniq -c | sort -rn
如果你当前的方案要三个工具加一个数据库才能回答这些问题,那是过度设计。如果它根本答不了,那是设计不足。正确答案是纯文本、两个文件、标准 Unix 工具集。
自己动手 — 哪怕你不用 Coograph
我们没说你必须用 Coograph 才能做这件事。我们说的是你需要 某个 东西来做。如果你已经在用 Claude Code 或 Codex CLI,钩子就二十行 Python:
#!/usr/bin/env python3
"""log-bash: append every Bash command to a global tail and a per-session file."""
import json, os, sys
from datetime import datetime
from pathlib import Path
AGENT = "claude-code" # or "codex-cli" — set this per hook file
payload = json.loads(sys.stdin.read() or "{}")
if payload.get("tool_name") == "Bash":
cmd = (payload.get("tool_input") or {}).get("command", "")
if cmd:
sid = "".join(c for c in str(payload.get("session_id") or "unknown")
if c.isalnum() or c in "-_")[:64] or "unknown"
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
root = Path(payload.get("cwd") or os.getcwd()) / ".coograph"
(root / "sessions").mkdir(parents=True, exist_ok=True)
(root / "session.log").open("a", encoding="utf-8").write(
f"[{ts}] [{AGENT}] [{sid[:8]}] {cmd}\n")
(root / "sessions" / f"{sid}.log").open("a", encoding="utf-8").write(
f"[{ts}] [{AGENT}] {cmd}\n")
sys.exit(0)
Claude Code 的接法:丢到 .claude/hooks/log-bash.py,标记可执行,在 .claude/settings.json 里按 Bash 工具过滤接成 PreToolUse 钩子。把 .coograph/ 加进 .gitignore。搞定。
Codex CLI 的接法:丢到 .codex/hooks/log-bash.py,然后在 ~/.codex/config.toml 里加一次以下内容 — $(git rev-parse --show-toplevel) 这层间接让一条全局配置在每个 Coograph 初始化过的项目里都生效:
[[hooks.PreToolUse]]
matcher = "^Bash$"
[[hooks.PreToolUse.hooks]]
type = "command"
command = '/usr/bin/python3 "$(git rev-parse --show-toplevel)/.codex/hooks/log-bash.py"'
timeout = 5
OpenCode 的接法:同样的思路,用 TypeScript 借 tool.execute.before 插件事件。完整插件在 Coograph 仓库的 .opencode/plugin/log-bash.ts — 不到五十行。
VS Code Copilot、Cursor、Windsurf、Aider、Cline — 截至 2026 年 5 月没有一个在公开 API 里暴露 pre-tool-use 钩子面。没有干净的拦截点。回退方案:VS Code 内置命令历史(Copilot)、集成终端回滚(Cursor / Windsurf / Cline),或 shell 层埋点(trap DEBUG、PROMPT_COMMAND、Linux 上的 auditd)。Coograph 的 README 为每个工具记录了取舍。上游发布钩子 API 时,我们会接上。
这套依然给不了你什么
审计日志不是检测。不是防御。不是实时拦截。
它是为安全事件准备的最便宜的 事后作用域物证。“我们被搞了,不知道代理干了啥,所有凭据全部轮换” 和 “我们被搞了,这是那个会话,这是关键的四条命令,轮换这六个凭据然后翻篇” — 这之间的差距,是一周事件响应和一个下午的差距。
下一份供应链公告砸下来时 — 一定会 — 你不想成为那个跑 grep -r 翻 ~/.claude_logs/ 然后祈祷自己在某处设过全局保留策略的团队。你想把文件路径背下来。每台机器都有。每个项目都有。
每会话。只追加。Gitignored。本地优先。搞定。
如何获得新行为
如果你的项目里已经有 Coograph,在 AI 工具的对话里重新运行初始化器同步最新钩子(Claude Code、Cursor、Copilot、OpenCode、Windsurf、Aider、Cline 用 /coograph-init;Codex CLI 用 $coograph-init)。初始化器会覆盖钩子脚本,在已有 Claude 变体旁边放下 .codex/hooks/log-bash.py 和 .opencode/plugin/log-bash.ts,并更新 .gitignore 以覆盖 .coograph/。已有的 .claude/session.log 历史会保留 — mv .claude/session.log .coograph/session.log.legacy 让它继续可扫描。
如果你还没有 Coograph:
# from your project root
git clone https://github.com/paullukic/coograph.git ../coograph
…然后在你的 AI 工具里调用初始化器。两分钟。约四十行 Python 落到你的仓库。下一条 Bash 命令开始日志就开始捕获。
完整步骤见 coograph.com/docs/getting-started/。
如果再给我们两周会做什么
如果你在意,两个后续我们会做:
- 一个
coograph auditCLI,接受一个会话 id 或时间窗口,打印干净的报告 — 把上面四种操作做成命名子命令,这样你不用记 awk 语法。可能四十行 shell。 - 写入时的选择性脱敏。 目前日志会捕获完整命令,包括可能敏感的环境变量或参数(例如
AWS_SECRET=... aws s3 cp ...)。在钩子时点加一道小脱敏,按常见密钥模式在写入前打码。日志可能要分享给第三方时有用。
两个都还没装在盒子里。想要哪个,开一个 issue。
更简单的版本现在就发。这才是关键版本。
削减你的 AI 编程账单 30–80%。Coograph 采用 MIT 许可、永久免费。Pro 提供定制服务。