Skip to content

v2.0.0

Latest

Choose a tag to compare

@cn0xroot cn0xroot released this 16 Sep 14:50
· 29 commits to master since this release

新增

  • 跨工作目录行为检测(新模块 cc_monitor/workdir.py + policy.py 新增
    match: "workdir" 规则类型):已有规则全是"对某个字段做正则",看不见 hook 输入里的
    cwd,判断不了"这个路径相对当前项目在哪"。新模块把一次工具调用要碰的所有路径抠出来
    (Read/Write/Edit/Glob/Grep 直接看 file_path/path;Bash 复用 split_shell_segments
    按子命令拆、shlex 认 token、~/$HOME/$PWD 展开、同一条命令里的 cd 会改后续
    相对路径的基准、>/>> 重定向和 rm/mv/cp/tee/sed -i/tar -xC/git clone
    等识别为写、bash -c "..." 递归拆),解析成绝对路径跟 cwd 比对,项目外的按位置分四档
    (homeDotfile 家目录隐藏文件、otherUserHome 别的用户家目录、system 系统目录、
    otherProject 其它)× 读/写。临时目录、/dev、系统目录里的解释器本身
    (/usr/bin/python3 x.py)、Claude Code permissions.additionalDirectories 里用户
    授权过的目录一律不报。evaluate() 新增 cwd 参数,hook 和 rematch 都会传。

  • 四条新默认规则(排在所有具体规则之后兜底,读 ~/.ssh 这类被更具体规则先命中的
    仍算在原规则里):workdir_escape_write_sensitive(high/confirm,往家目录隐藏
    文件/别人家目录/系统目录写)、workdir_escape_write_other(medium/log,往
    其它项目目录写——真实历史里一个会话跨到旁边项目改几百个文件很常见,默认只记录,
    想拦的话把 action 改成 confirm)、workdir_escape_read_sensitive(medium/log)、
    workdir_escape_read_other(low/log)。每条都带 scopes/access/ignore_paths
    字段,用户可以在 rules.json 里自己调档位、加白名单。规则数从 70 增至 74。
    claude 用 root 跑、项目却放在 /home/<user> 下的场景专门处理过:cwd 所在的那个
    用户家目录也算"自己家",旁边的项目是 otherProject,不会误判成"别的用户的家"。

  • 跨工作目录检测的降噪(用 2500 多条真实历史事件做的调优):把"cd 进子目录后误报
    整个项目"这一类彻底消掉(实测占误报的约七成),加上系统目录只读、Claude 自身状态目录
    等豁免;剩下的基本都是真的跨出去了(比如在 A 项目目录里读写 B 项目的文件——这种即便
    两个目录是同一份工作的不同副本,工具也无从判断,需要的话用规则的 ignore_paths 把
    某个目录并进来)。

    • 按项目根算边界,不按 hook 给的 cwd:Claude Code 的 Bash 工具 cd 进子目录之后,
      hook 输入里的 cwd 也跟着变成子目录(比如 proj/webui),这时候改 proj/README.md
      会被当成跨目录——占了原来七成的误报。现在从 cwd 往上找 .git/.hg/.svn/
      CLAUDE.md/.claude 标记,取家目录之下最靠上的那一层当项目根。
    • 只读系统目录里的读不报(/usr、/lib*、/bin、/opt、/snap、/nix、
      /System、/Library、/Applications,以及 ~/.cache):查共享库、看头文件、跑程序
      都是读这些地方;往这些地方写照样按 system 档报。/etc、/proc、/var、/root 这类
      放配置/状态的目录读仍然报。
    • ~/.claude/projects 整体忽略:Claude Code 自己的会话记录/自动记忆/todo,读写它们是
      它正常工作的一部分;~/.claude 下别的东西(settings、凭证)照常报。
    • 忽略目录/项目内的路径按字面判定、不追符号链接:ln -sf /usr/lib/x.so /tmp/build/x.so
      之前会被 realpath 解析成"往 /usr/lib 写"。
    • / 本身归到 system 档(find /、ls /)。
  • CC-Monitor workdir 子命令:按规则小计 + 逐条列出最近的跨工作目录文件操作。

  • 审批弹窗用通俗易懂的内容概述需要审批的操作指令:74 条默认规则每条新增 title /
    desc(及 title_en / desc_en),用一句通俗的话说明这条规则拦的是什么操作、为什么
    要确认(比如 git_force_push →
    "git 强制推送:会覆盖远程分支历史,别人已拉取的提交可能丢失")。AI 审批台的每张卡
    先显示"需要确认:<title>",下面一行解释,规则 id 和工具名退到最后一行小字;Claude Code
    原生权限确认(没命中规则)按工具名给一句说明;审批历史表的"命中规则"列也显示通俗标题
    (悬停看 id)。终端里的确认提示和桌面通知同样带上 title/desc。新增 /api/rules/meta
    (webui/lib/rules.js 读 ~/.cc-monitor/rules.json,用户改过的文案也能跟上)。
    老用户的 rules.json 会通过默认规则同步机制自动补上这些字段;用户自定义的规则没写
    title 就退回显示 id。

  • 首页"跨工作目录操作"卡片 + /api/drilldown/workdir-escape:跟"高级威胁检测"
    同一套 matched_rule 归类/下钻模板,四个分类对应上面四条规则。

  • tests/test_workdir.py:分档、文件类工具、Bash 路径抽取(含 heredoc 正文/字符串
    里的路径不误报、cd 跟踪、同一路径写覆盖读)、规则映射与优先级的回归测试。

  • 截图检测大幅扩充:原来只认 scrot/gnome-screenshot 那一撮桌面截图工具,实测
    394 条记录里 391 条其实是"读取图片文件",真正的截图命令只识别出 3 条。现在按三类覆盖:
    宿主机屏幕(X11 / Wayland / macOS / Windows 截图工具、屏幕录制 ffmpeg x11grab 等、
    framebuffer 直读、脚本里的 ImageGrab.grab()/pyautogui.screenshot()/mss 等抓屏库)、
    网页(headless 浏览器 --screenshot、CDP captureScreenshot、Playwright/Puppeteer/
    Selenium 的截图 API、shot-scraper/wkhtmltoimage/gowitness 等工具)、虚拟机与远程设备
    (QEMU screendump(HMP 与 QMP 两种形式)、virsh screenshot、VBoxManage、VMware、
    VNC、adb shell screencap、idevicescreenshot、simctl)。识别数从 3 → 209,其中
    QEMU 虚拟机截屏 174 条此前完全没有被检测到。误报为 0:命中前会剥掉
    sudo/env/timeout/路径前缀等包装,grep/rm/ls 等纯检索命令即使提到关键字也
    不算,ImageMagick 的 import 必须带 -window/-screen 或图片文件名才算(避免撞上
    Python 的 import)。详情页新增按方式分类的小计与彩色徽章,真截屏不再被图片读取淹没。

  • 账号信息一键打码:「Anthropic 账号 & 额度」分组新增"隐藏敏感信息"按钮,把姓名 /
    邮箱 / 组织 / 组织角色 / 套餐 / 额度档位 / 计费方式 / 账号创建时间 / 订阅开始时间
    全部显示成 ***,方便截图和演示。状态存本浏览器,只改显示、不动数据,额度百分比不打码。

  • 额度进度条配色方案:10 种可选(默认 / 分段色阶 / 红黄绿三档 / 蓝青 / 紫粉霓虹 /
    日落暖 / 森林绿 / 光谱 / 灰度 / 主题强调色),同时作用于额度卡、"已使用百分比"和
    "重置时间"三种进度条;选默认时后两者保持原来的 severity 配色。

  • claude 进程明细增加信息:运行时间、模型 ID、事件数、内存占用(ps 的 etime/rss,
    POSIX 字段,Linux 与 macOS 通用;模型按 cwd 关联审计会话的 transcript 解析)。

  • 被拦截的高危操作详情增加统计:累计 / 最近 24 小时 / 最近 7 天三张卡,外加按命中规则、
    按工具、按工作目录三个维度的分类小计。

变更

  • 首页改成紧凑仪表盘(先用 Claude Design 出稿确认方向再落地;配色/圆角/字号/卡片
    边框全部沿用原来的主题变量,没有新颜色):

    • 顶部一条态势摘要:审计状态 + claude 进程数、已监测会话(附进行中的 Web UI 终端
      会话数)、待审批(点击直达审批台)、已拦截 + 疑似绕过、单次额度剩余——原来这几个
      "现在怎么样"的数字散在页面四处。
    • 原来四条各带一段说明文字的控制条(审计开关/归档清空/同步更新/远程访问)收成一行
      工具栏
      ,说明文字进 ⓘ 悬停气泡(新增 data-i18n-tip,中英文照常切换)。
    • 每个分组改成短标题 + ⓘ,不再每组占一整行长说明;安全类六张红卡并排,卡片下多一行
      小字提示覆盖范围(跨工作目录那张实时显示读/写/敏感位置计数,/api/overview 新增
      workdirEscapeBreakdown);活动/命令类用紧凑卡(1.5rem 数字),MCP/Glob·Grep/TodoWrite
      三个低频计数合并成一张卡(每个数字仍各自可点开下钻);文件操作/软件安装收进两个盒子。
    • 账号信息 + 额度卡 + 限额明细并成一行两栏。页面高度约减半,所有下钻入口和元素 id 都
      保留(app.js 的绑定不受影响)。
  • 其它七个标签页统一成同一套页头(.page-head:短标题 + ⓘ 说明 + 行内控件):
    状态信息页改成跟首页一样的两栏账号/额度布局,模型使用统计和会话状态用分组标题;
    网络流量页的 GeoIP 精度徽章进页头、汇总卡改紧凑卡、地图图例移到分组标题右侧;
    AI 审批台的桌面通知按钮进页头,审批历史表格时间/结果/处理方式列不再折行、长文本列
    限宽省略(悬停看全文);Log 审计 / Claude Tap 的开关和会话下拉框收进页头,Claude Tap
    没选会话时给一句提示而不是整页空白;终端会话侧栏标题同款;历史数据页头同款。
    所有页面的长段说明文字都从正文移进 ⓘ。

  • 下钻弹窗加载失败时给出说明:之前接口 404/无响应会一直停在"加载中…"(典型场景:
    刚更新过代码但正在运行的 Web UI 服务还是旧版本、没有新路由);现在换成一句提示,
    告诉用户重启 Web UI。

  • 下钻弹窗放宽到 1400px:会话列表有 8 列(完整工作路径、36 位 UUID、时间范围…),
    原来跟"选择文件夹"那种窄弹窗共用 760px,列头被压成一列一个字、UUID 折成四行,一屏
    只看得到 10 行。现在单独放宽(一屏 24 行),窄屏改为横向滚动而不是继续挤;
    "选择文件夹"弹窗保持 760px 不变。

  • 事件列表分段上色:时间戳、文件夹名、session id、完整路径、命中规则各自配色(原来
    整行灰字),并抽出共用函数统一了 5 处手拼代码;下钻列表的操作标签复用 Log 审计页已有的
    按操作类型配色(读/写/编辑/Bash/删除),不再是没有颜色的裸标签。

  • 生命体征指示器放大:心形 14→20px、心电图 40×14→54×20,颜色深浅(也就是"有多活跃")
    在表格里终于看得出层次。

  • "已监测的 Claude Code 会话"后面加了 Claude 星芒标记(内联 SVG,无外部资源)。

  • 规则文案回退:用户改过的规则不会被默认规则合并覆盖,于是拿不到新版加的
    title/desc,审批台只能显示规则 id。展示时按 id 回退到内置默认文案(Python 与
    Node 两侧),不写回用户的 rules.json。

修复

  • Web UI 终端会话的健康状态永远是灰色:/api/sessions 从来没返回 statusAgoMs,
    而 vitalHeat() 拿到 undefined 一律返回 0,导致颜色插值退化成纯灰、心电图拉直线,
    不管会话多活跃都一样。现在按"PTY 最近有输出"和"该目录最近有审计事件"取更近的那个返回。
  • 账号额度卡片"剩余 · 单次额度"在窗口刚到期时显示 0%:接口在 5 小时窗口过期、
    还没有新请求刷新数字的那段时间里,仍会把上一个窗口末尾的用量(用满就是 100%)连同
    已经过去的 resets_at 一起原样返回,前端按它算"剩余"就是 0%。现在 resets_at 已
    过就按 0 已用处理,并把用量夹在 0~100 之间。

本版本还包含以下未单独发版的改动(1.8.0 / 1.7.2 / 1.7.1)

1.8.0

新增

  • "高级威胁检测"首页卡片:复用 matched_rule 架构(不重复写正则),把此前几批新加的
    高危规则统一归类展示——cryptoMining(挖矿)、dbFileWrite(数据库任意文件写入)、
    webshell(webshell 代码特征)、reverseEscapeShell(反弹/逃逸 shell)、
    downloadExec(下载后执行)、c2Framework(C2 框架工具)、postExploitation
    (后渗透/内网横向工具)、suspiciousMcp(可疑 MCP 工具名)、pentestRecon
    (扫描/爆破工具)、covertTunnel(隐蔽隧道工具)十个分类,新增
    /api/drilldown/advanced-threat。
  • policy.py 的 evaluate() 新增 field: "tool_name" 支持:规则可以直接匹配
    tool_name 本身,不再局限于 tool_input 里的字段——MCP 工具调用的 tool_name
    是运行时才知道的动态字符串(mcp__<server>__<tool>),没法像 Bash/Write 那样枚举进
    tools 列表。
  • 新增 mcp_suspicious_tool_name 规则(high/confirm):MCP 工具名里出现
    reverse-shell/c2/beacon/backdoor 字样就标出来——直接对应分析
    AIPentest/CyberStrikeAI 时发现的反向 Shell MCP Server(官方文档明确支持接入 Claude
    Code 的 .mcp.json)。
  • 新增 post_exploitation_tool_execution 规则(high/confirm):
    linpeas/netexec/bloodhound/sharphound/smbmap/rpcclient/enum4linux-ng
    这几个后渗透/内网横向阶段的标准工具,c2_framework_execution 加了 pacu(AWS 云
    攻击框架),pentest_recon_tool_execution 加了 18 个子域名枚举/Web 模糊测试工具
    (rustscan/amass/subfinder/ffuf/feroxbuster/dirsearch 等)。
  • 新增"逆向分析工具调用"首页卡片:识别 IDA/Ghidra/radare2/rizin/GDB 以及
    Binary Ninja/Hopper/x64dbg/WinDbg/dnSpy/JADX/apktool/Frida/binwalk/checksec 等
    一批逆向分析工具的调用,纯可见性统计(这些是专业逆向工程师/CTF/合规安全测试里的日常
    工具,不代表风险,不参与 confirm/block)。
  • 新增 shell_escape_via_utility 规则(high/block,参考
    GTFOBins):find -exec、
    awk system()、perl exec、Python 的 pty.spawn/os.system、
    tar --checkpoint-action=exec、vim -c ':!sh'、zip --unzip-command、
    script -c、ssh 的 ProxyCommand 这几种"用一个看起来无害的日常工具逃逸出
    shell"的经典手法,"高级威胁检测"同步加了 reverseEscapeShell 分类(把这条和已有的
    reverse_shell_pattern 归到一起)。规则数从 58 增至 70。
  • 首页新增"同步更新"按钮:POST /api/sync-update 用 execFile 固定参数数组在
    项目源码目录跑 git pull(不经过 shell,不接受任何请求参数拼进命令行),把
    GitHub 上的新代码/新规则同步下来;二次确认弹窗,把 git 自己的输出/报错原样展示,
    不做任何自动冲突处理。
  • 下钻详情统一视觉强化:GitHub/SSH/下载/Docker/压缩归档/网络诊断/逆向分析/
    进程管理/敏感操作/敏感数据/高级威胁检测这十个共用同一套下钻模板的分组,事件明细
    里的分类徽章和"行为操作"标签统一标红加粗。

修复

  • decision.submitted 缺中英文翻译:UserPromptSubmit 生命周期事件的
    decision 固定是 "submitted",之前漏加了对应的 i18n key,中文界面下显示成了
    英文单词。
  • 逆向分析工具调用分类器的几个精度问题(用真实历史数据发现的):真实场景里 IDA
    几乎都是绝对路径调用(比如 /opt/idapro-9.0/idat64),原来只认裸文件名开头会
    全部漏检,补了路径前缀剥离;LD_LIBRARY_PATH=... gdb ... 这种环境变量赋值前缀
    也会把剥离逻辑带偏,补了环境变量赋值剥离;rizin 家族只认了 rz-bin/rz-asm
    两个子工具,扩到 rz-\w+;frida-ls-devices 这类多段连字符的 frida 子命令
    漏匹配;新增 macOS open -a "IDA Pro" 等 GUI 启动方式识别。
  • env_dump 规则过度匹配:env VAR=val VAR2=val2 command 这种设置环境变量
    启动子进程的常见写法(调试/构建脚本里很常见)被误判成"在 dump 环境变量",加了
    负向前瞻排除掉赋值形式。用真实数据验证:某台机器上的历史误判从 134 条降到
    14 条,v1.7.1 加的自动重判机制自动生效,未手动干预。
  • 归属地信息中文界面下显示英文(比如 Tseung Kwan O, HK):geoip.js 之前
    写死取 MaxMind 数据的英文名,改成跟着页面语言选(中文界面优先取
    .names["zh-CN"]);另外新增一张 250 个国家/地区代码到中文名的静态映射表,
    给 DB-IP Lite 这类没有多语言字段的扁平格式数据源兜底(国家/地区一级能翻,城市名
    因为数据源限制没有中文版本,如实保留英文)。
  • shell_escape_via_utility 规则设计过程中修了两个正则精度 bug:
    script -qc /bin/sh 这种合并短选项写法一开始没匹配上;
    ssh -o ProxyCommand='ssh -W %h:%p jump' target(合法跳板机用法)被误判成
    shell 逃逸——懒惰匹配的通配符吃到了 "ssh" 这个词自己的尾巴 "sh",加了词边界
    卡死。

1.7.2

新增

  • 参考 suricata-rules 分类思路新增 9 条规则:这是一份网络层面的
    Suricata IDS 规则集,跟 CC-Monitor 不是同一层检测(那边看网络包内容,这边看 Claude
    Code 自己发起的工具调用),规则文本本身不能照搬,但按它的目录分类找出了几个此前
    完全没覆盖的攻击技术类别:
    • crypto_miner_pool_domain_command/_write(medium/confirm):命令行或写入内容
      里出现已知挖矿矿池域名(pool.minexmr.com/monerohash.com/xmrpool.eu 等,从该仓库
      Crypto_miner_pool 目录提取)或 stratum:// 协议 URI。
    • db_arbitrary_file_write(high/confirm,tools: ["Bash"])+
      db_arbitrary_file_write_content(medium/confirm,tools: ["Write","Edit", "NotebookEdit"]):MySQL/MariaDB 的 INTO OUTFILE/INTO DUMPFILE/general_log_file
      这几个合法功能被滥用成任意文件写入、借此在 Web 目录落地 webshell 的经典手法(该仓库
      Mysql 目录的日志写文件规则思路),之前的 db_destructive_command
      只覆盖 DROP/DELETE/TRUNCATE,完全没管这种"写文件"用法。
    • webshell_pattern_in_write(high/block,插在 dynamic_exec_in_write 之前,
      优先命中):菜刀/冰蝎/Weevely 这几类一句话马的经典代码形状(PHP 的
      eval/assert 直接调用 $_POST/$_REQUEST 数组、ASP 的 eval request(、JSP 的
      Runtime.getRuntime().exec(request.getParameter),比现有的泛化
      dynamic_exec_in_write(只认字面上的 eval(,噪音太大只能 log)精确得多,误判率低
      到可以直接 block——正常代码几乎不会写出这种形状。
    • curl_download_then_exec(high/confirm):curl_pipe_shell 只抓
      curl|sh 管道形式,"先 curl -o x.sh 下载、再 chmod +x && ./x.sh 分两步执行"这种
      功能等价但没有管道符的变体之前完全漏检。
    • c2_framework_execution(high/confirm)/pentest_recon_tool_execution
      (medium/log):渗透测试/C2 框架工具的命令行调用本身(msfconsole/msfvenom/
      teamserver/impacket-*/mimikatz/cobaltstrike/sliver/havoc 等 vs.
      nmap/sqlmap/hydra/nikto/gobuster 等扫描类工具,前者风险明显更高单独分级)。
    • covert_tunnel_tool_execution(medium/confirm):dnscat2/iodine/dns2tcp/
      ptunnel/icmptunnel/hans/pingtunnel 这类 DNS/ICMP 隐蔽隧道工具的调用,跟
      1.7.1 加的 ssh_tunnel_reverse_proxy 是同一个"隐蔽出网通道"主题下的姊妹规则。
      default_rules.json 从 58 条增至 67 条。用真实 policy.evaluate() 跑了 25 个正负测试
      用例全部通过(含"webshell 规则不应该误伤普通代码里出现的动态执行调用"这类边界情况),
      python3 -m unittest tests/test_rules.py 和 webui 的 node --test 均无回归。已有
      用户的 ~/.cc-monitor/rules.json 会在下次 hook 调用时经 1.7.1 加入的自动合并机制
      自动补上这 9 条,不需要手动同步。

1.7.1

修复

  • macOS 上读取 shell 历史指令没被"敏感操作统计"识别到:根因有两层。
    1. ~/.cc-monitor/rules.json 是首次运行时拷的副本,之后新版本加进
      default_rules.json 的规则(包括 history_read 本身,1.6.0 才加的)根本不在老用户
      的文件里——实测一份 1.x 早期安装的 rules.json 只有 33 条规则,1.7.0 的默认表有 57 条。
      这类"改了默认规则但用户侧不生效"的问题在 CHANGELOG 里已经提醒过五六次"需要手动
      同步",这次直接在 policy.ensure_config() 里做自动合并:本地缺失的默认规则按 id 补上
      (插在它在默认表里前一条规则后面,保持首个命中即返回的顺序语义);新增
      ~/.cc-monitor/rules.defaults_snapshot.json 记录上次同步时的默认表,本地某条规则跟快照
      一模一样(用户没改过)而默认表里这条变了(比如修正正则)就直接换成新的;用户改过的
      (跟快照不一样)一律不动;本地缺失但快照里有的 id 视为用户主动删除,不再补回。老版本
      升上来第一次没有快照,只补缺失的、不碰已有的。合并结果直接返回给 load_rules(),
      配置目录只读时也能用内存里合并好的规则跑,hook 本身不受影响。
    2. 即使规则在,原 history_read 正则也只认 cat/less/more/head/tail/strings 开头
      • 历史文件名这一种形态。Claude Code 在 Mac 上实际的读法几乎全在盲区:python3 -c "open('~/.zsh_history')"、wc -l < ~/.zsh_history、for f in ~/.zsh_history ...、
        grep token ~/.zsh_history、zsh 原生的 fc -l、$HISTFILE、macOS Terminal 按会话
        存的 ~/.zsh_sessions/*.history、Claude Code 自己的 ~/.claude/history.jsonl,以及
        最常见的——直接用 Read 工具读 ~/.zsh_history(sensitive_file_read 规则根本没
        包含历史文件)。重写 history_read:只要命令文本里出现历史文件路径(不再要求特定
        读取命令前缀,python/grep/重定向都算)、$HISTFILE、.zsh_sessions/、
        .claude/history.jsonl,或子命令开头是 history/fc -l 就命中;新增
        history_file_read(tools: ["Read", "Grep"], field: "file_path", log 级别)覆盖
        Read/Grep 工具直接读历史文件。history_tampering 顺带补上 .zsh_history 和
        rm .zsh_sessions/ 两种 Mac 上的清历史写法(原来只认 .bash_history)。
    3. WebUI 的敏感操作统计(webui/lib/audit.js)原来把四条规则 id 硬编码在 JS 常量和
      两处 SQL IN (...) 里三个地方,加规则要同步改三处;现在 SQL 从 SENSITIVE_READ_RULES
      常量生成,只改一处。history_file_read 归到"其它(历史指令读取等)";取文本时
      Bash 看 command,其它工具看 file_path/path(原来只认 Read 一个工具名)。
      注意:之前没被识别到的历史读取事件当时 matched_rule 就是空的,不会追溯补算,
      统计只对修复之后的新事件生效。
  • 排查"MacPorts port install 没被识别":system_package_install 从 1d762af 起就
    覆盖了 port install/uninstall/upgrade/activate/deactivate/selfupdate,规则引擎对
    sudo port install、port -N install、/opt/local/bin/port install、xargs sudo port install 等写法全部命中,而且审计库(含归档)里从来没有一条 port 命令经过 hook 的
    记录——说明这次"没识别到"不是规则漏了,而是那条命令压根没走 Claude Code 的 Bash 工具
    (在自己终端里敲的、用 ! 前缀跑的、或者 Claude 因为 sudo 要密码而让用户自己去
    执行的,hook 都看不到;这是 PreToolUse 机制的边界,不是 bug)。顺手加固了 port
    这段正则:-D /path、--debug 这类带参数/长格式的全局选项夹在中间也能命中,动作
    列表补上 sync。
  • 系统包管理器(apt/yum/dnf/pacman/brew/port)识别不准确:10 条"命中"全是误报:
    审计库里归到 system_package_install 的 10 条事件,没有一条是真的在装软件——全是
    grep "port install" default_rules.json、python heredoc 里写着 "apt install" 字样的
    命令。根因是规则引擎对整条命令文本做 re.search,引号里的字符串、heredoc 正文、grep
    的搜索词一视同仁;sudo_usage/sudo_pip_install 也是同样的问题(修这个 bug 的过程中
    两条编辑命令就因为 heredoc 里出现 sudo apt-get install、sudo pip install 字样被拦了)。
    规则新增可选字段 match:"search"(默认,行为不变)或 "segment"——把 Bash 命令按顶层
    ; & | 换行 切成子命令(引号/heredoc 内部不切),每段剥掉 sudo/env/xargs/time/
    nice/nohup 包装、环境变量赋值前缀、可执行文件路径前缀(/opt/local/bin/port →
    port),再用 re.match 从开头匹配;bash -c "..."/osascript ... do shell script "..."
    的字符串体递归展开,藏在里面的真实安装不会漏。每段同时给"原样"和"剥包装"两个版本,
    sudo_usage 要看到 sudo 本身、apt/brew/port 规则要看到真正的命令名。切到 segment
    模式的规则:system_package_install、package_install_other、npm_global_install、
    npm_local_install、sudo_usage、su_pkexec_privilege_escalation、sudo_pip_install、
    pip_install_no_venv。pip_install_venv_context 依赖整条命令里的 venv 上下文
    (source .venv/bin/activate 在另一个子命令里),保持 search 模式。package_install_other
    里的 brew install 分支删掉(system_package_install 在前面已经接管,永远到不了)。
    segment 模式下 matched_value 是命中的那个子命令而不是整条命令,审批台/拦截原因里
    更直观。policy.split_shell_segments/segment_heads 跟 webui/lib/audit.js 的
    splitShellSegments 是同一个思路的 Python 版。
  • 规则更新后历史事件自动按新规则重判:首页各统计卡片都是按 events.matched_rule
    聚合的,而这个字段是事件发生那一刻按"当时的规则"算出来写死的——规则修好了,历史
    上的误报/漏报也不会自己消失。新增 cc_monitor/rematch.py:hook 每次调用算当前规则表
    的内容指纹(policy.rules_fingerprint,跟文件 mtime 无关,自动合并重写/touch 不会
    触发),跟 meta 表里记的"上次重判用的指纹"不一样就通过一条原子 upsert 认领,起一个
    独立后台进程跑 CC-Monitor rematch --apply --quiet(几千条事件逐条过正则要几秒,
    PreToolUse 不能卡这么久;多个 hook 同时发现也只有一个会起进程)。只改 risk/
    matched_rule 两列,decision 永远不动,归档库不碰。也可以手动:CC-Monitor rematch
    预览、--apply 写库。本机跑完之后"系统包管理器"从 10 变成 0,之前没识别到的 4 条
    历史指令读取事件也补上了 history_read。
  • README"已知限制"补充监测边界说明:排查"brew/port 安装没被统计"时发现那几条
    命令是用户在自己终端里敲的(~/.zsh_history 里有、审计库里没有),hook 只能看到 Claude
    Code 发起的工具调用。这个边界之前文档里没写明,现在两种语言的"已知限制"都加了一条。
  • tests/test_rules.py 补充 segment 模式和 rematch 的用例:12 种"提到了但没执行"的
    误报样例、23 种真实执行(含 bash -c、xargs、环境变量前缀、子 shell 括号、绝对路径)
    的命中样例,以及 rematch 改 matched_rule 不改 decision 的断言。
  • 新增规则回归测试 tests/test_rules.py:python3 -m unittest tests/test_rules.py,
    用隔离的 CC_MONITOR_HOME 跑,不碰用户自己的 rules.json。目前覆盖系统包管理器
    (含 MacPorts 各种写法)和历史指令读取两组"必须命中/不能误报"的样例,以后再报
    "某某没检测到"先往这里加一行样例就能复现。

English

Added

  • Cross-workdir behavior detection (new module cc_monitor/workdir.py + a new
    match: "workdir" rule type in policy.py): every existing rule is "a regex over one
    field", which can't see the hook input's cwd and therefore can't tell where a path sits
    relative to the current project. The new module extracts every path a tool call is about to
    touch (Read/Write/Edit/Glob/Grep: file_path/path directly; Bash: reuses
    split_shell_segments to split into sub-commands, shlex to tokenize, expands
    ~/$HOME/$PWD, lets a cd inside the same command rebase later relative paths,
    recognizes >/>> redirects and rm/mv/cp/tee/sed -i/tar -xC/git clone
    etc. as writes, and recurses into bash -c "..."), resolves them to absolute paths and
    compares against cwd. Anything outside is tiered by location (homeDotfile hidden files
    under home, otherUserHome, system, otherProject) × read/write. Temp directories,
    /dev, interpreters living in system directories (/usr/bin/python3 x.py) and the
    directories the user already authorized via Claude Code's
    permissions.additionalDirectories are never reported. evaluate() gained a cwd
    parameter; the hook and rematch both pass it.

  • Four new default rules (placed after every specific rule as a fallback — reads of
    ~/.ssh and the like that a more specific rule catches first still count under that rule):
    workdir_escape_write_sensitive (high/confirm: writes into hidden home files, other
    users' homes or system directories), workdir_escape_write_other (medium/log:
    writes into other project directories — in real history one session editing hundreds of
    files in a sibling project is common, so this only logs by default; flip its action to
    confirm to be prompted), workdir_escape_read_sensitive (medium/log),
    workdir_escape_read_other (low/log). Each carries scopes/access/ignore_paths
    so users can retune tiers or whitelist paths in rules.json. Rule count goes from 70 to 74.
    The "claude runs as root but the project lives under /home/<user>" case is handled: the
    user home containing cwd also counts as "own home", so a sibling project there is
    otherProject rather than being mistaken for another user's home.

  • Noise reduction for cross-workdir detection (tuned against 2,500+ real historical
    events): eliminates the "cd into a subdirectory then the whole project looks external" class
    (about 70% of the false positives in practice), plus exemptions for read-only system
    directories and Claude's own state dirs; what remains is genuinely outside the project (e.g.
    reading/writing project B's files from within project A's directory — which the tool cannot
    tell apart even when the two are copies of the same work; use a rule's ignore_paths to fold
    one directory into another when needed):

    • The boundary is the project root, not the hook's cwd: after Claude Code's Bash tool
      cds into a subdirectory, the hook input's cwd follows it (e.g. proj/webui), so editing
      proj/README.md looked like a cross-directory write — about 70% of the old false
      positives. The root is now found by walking up from cwd for .git/.hg/.svn/
      CLAUDE.md/.claude markers, taking the highest marked directory below the home dir.
    • Reads inside read-only system directories are not reported (/usr, /lib*, /bin,
      /opt, /snap, /nix, /System, /Library, /Applications, plus ~/.cache): looking up
      shared libraries, headers and running programs all read there; writes there are still
      reported at the system tier. Reads of config/state directories (/etc, /proc, /var,
      /root) are still reported.
    • ~/.claude/projects is ignored entirely: Claude Code's own transcripts / auto-memory /
      todos — reading and writing them is part of its normal operation; everything else under
      ~/.claude (settings, credentials) is still reported.
    • Paths inside the project or an ignored directory are judged literally, without following
      symlinks
      : ln -sf /usr/lib/x.so /tmp/build/x.so used to realpath into "a write to
      /usr/lib".
    • / itself is classified as system (find /, ls /).
  • CC-Monitor workdir subcommand: per-rule totals plus a list of recent cross-workdir
    file operations.

  • Approval prompts in plain language: every one of the 74 default rules gains title /
    desc (plus title_en / desc_en) — one plain sentence on what the rule catches and why
    it needs a decision (e.g. git_force_push → "git force push: overwrites remote branch
    history; commits others already pulled may be lost"). Each Approval Desk card now leads
    with "Confirm: <title>", an explanation line below, and the rule id + tool name demoted to
    a small last line; Claude Code's native permission prompts (no rule matched) get a
    per-tool sentence; the approval-history table's rule column shows the title (hover for
    the id). The terminal confirm prompt and desktop notification carry title/desc too. New
    /api/rules/meta (webui/lib/rules.js reads ~/.cc-monitor/rules.json, so user-edited
    wording is honored). Existing users' rules.json picks the fields up through the default-
    rule sync; a user-defined rule without a title falls back to its id.

  • "Cross-workdir operations" home-page card + /api/drilldown/workdir-escape: same
    matched_rule grouping/drilldown template as "Advanced Threat Detection", four categories
    mapping to the four rules above.

  • tests/test_workdir.py: regression tests for tiering, file tools, Bash path extraction
    (paths inside heredoc bodies/strings don't false-positive, cd tracking, write wins over
    read for the same path), and rule mapping/precedence.

  • Screenshot detection greatly expanded: it used to recognise only desktop tools like
    scrot/gnome-screenshot — of 394 records, 391 were actually "opened an image file" and
    only 3 real screenshot commands were detected. Coverage is now three-way: host screen (X11 /
    Wayland / macOS / Windows tools, screen recording via ffmpeg x11grab and friends, framebuffer
    reads, and in-script capture libraries such as ImageGrab.grab(), pyautogui.screenshot(),
    mss), web (headless browser --screenshot, CDP captureScreenshot, Playwright/Puppeteer/
    Selenium screenshot APIs, shot-scraper/wkhtmltoimage/gowitness…), and VMs / remote devices
    (QEMU screendump in both HMP and QMP form, virsh screenshot, VBoxManage, VMware, VNC,
    adb shell screencap, idevicescreenshot, simctl). Detections went from 3 to 209 — of
    which 174 QEMU VM captures had never been detected at all. Zero false positives: wrappers
    (sudo/env/timeout/path prefixes) are stripped first, pure search commands
    (grep/rm/ls) don't count even when they mention the keywords, and ImageMagick's
    import needs -window/-screen or an image filename (so Python's import never matches).
    The drilldown now shows a per-method breakdown with colour-coded badges.

  • One-click account masking: a "hide sensitive info" button in the Anthropic account section
    renders name / email / org / role / plan / rate-limit tier / billing / account-created /
    subscription-started as ***. Stored per browser; display only, quota percentages stay visible.

  • Quota bar colour schemes: ten options (default / stepped / traffic-light / ocean / neon /
    sunset / forest / spectrum / mono / accent), applied to the quota cards plus the
    "used percentage" and "reset window" bars; the default keeps the original severity colours
    for the latter two.

  • More detail for claude processes: uptime, model ID, event count and memory (ps etime/
    rss, POSIX fields that work on Linux and macOS; the model comes from the audit session's
    transcript matched by cwd).

  • Statistics on the blocked-operations drilldown: total / last 24h / last 7d cards plus
    breakdowns by matched rule, by tool and by working directory.

Changed

  • Home page redesigned as a compact dashboard (direction settled on a Claude Design
    canvas first, then implemented; colors/radii/type/card borders all reuse the existing theme
    variables — no new colors):

    • A status strip at the top: audit state + claude process count, sessions monitored
      (with active Web UI terminal sessions), pending approvals (click jumps to the approval
      desk), blocked + suspected bypasses, session quota remaining — numbers that used to be
      scattered across the page.
    • The four control bars that each carried a paragraph of explanation (audit toggle /
      archive & clear / sync update / remote access) collapse into one toolbar; the
      explanations move into ⓘ hover bubbles (new data-i18n-tip, switches with the language).
    • Every group becomes a short title + ⓘ instead of a full-width line of description;
      the six security cards sit in one row with a one-line coverage hint under each (the
      cross-workdir card shows live read/write/sensitive counts — /api/overview gained
      workdirEscapeBreakdown); activity/command groups use dense cards (1.5rem numbers), the
      three low-frequency counters MCP / Glob·Grep / TodoWrite merge into one card (each number
      still opens its own drilldown); file operations / software installs move into two boxes.
    • Account info + quota cards + limit details share one two-column row. The page is roughly
      half as tall; every drilldown entry and element id is preserved (app.js bindings untouched).
  • The other seven tabs share one page header (.page-head: short title + ⓘ explanation +
    inline controls): the Status tab uses the same two-column account/quota layout as home, with
    section headers for model usage and session status; on Network the GeoIP accuracy badge moves
    into the header, summary cards become dense cards and the map legend sits on the section
    header; on the Approval Desk the desktop-notification button moves into the header and the
    history table stops wrapping the time/result/via columns and clips long text columns (hover
    for the full value); Audit Log / Claude Tap fold their toggles and session dropdown into the
    header, and Claude Tap shows a hint instead of a blank page when no session is selected; the
    terminal sidebar title and the Archives header follow suit. Every long explanatory paragraph
    moved from the body into ⓘ.

  • Drilldown dialog reports load failures: a 404/unresponsive endpoint used to leave the
    dialog stuck on "Loading…" (typically: code just updated but the running Web UI server is
    still the old version without the new route); it now shows a message telling the user to
    restart the Web UI.

  • Drilldown dialog widened to 1400px: the session list has 8 columns (full working path,
    36-char UUID, time range…) and used to share the 760px width of the folder picker, which
    squeezed headers to one character per line and wrapped UUIDs over four lines, fitting only
    ~10 rows. It now fits 24, and narrow viewports scroll horizontally instead of squeezing
    further; the folder picker keeps its 760px.

  • Colour-coded event lines: timestamp, folder name, session id, full path and matched rule
    each get their own colour (they used to be one grey line), via a shared helper that replaced
    five hand-built copies; drilldown operation labels reuse the Log Audit page's per-operation
    colours instead of rendering unstyled.

  • Larger vital-sign indicator: heart 14→20px, ECG 40×14→54×20, so the colour depth (how
    recently active) is actually legible inside tables.

  • A Claude starburst mark after "Claude Code sessions monitored" (inline SVG, no assets).

  • Rule-text fallback: rules a user has edited are deliberately not overwritten by the
    default-rule merge, so they never received the new title/desc and the approval desk could
    only show a rule id. Display now falls back to the shipped default text by id (both the Python
    and Node sides) without writing back to the user's rules.json.

Fixed

  • Web UI terminal sessions always showed a grey health indicator: /api/sessions never
    returned statusAgoMs, and vitalHeat() returns 0 for undefined, so the colour collapsed
    to plain grey with a flat ECG no matter how active the session was. It now returns the more
    recent of "the PTY produced output" and "that directory had a new audit event".
  • Account-quota card "Remaining · Session quota" showing 0% right after the window
    expires
    : while the 5-hour window has rolled over but no new request has refreshed the
    numbers yet, the API keeps returning the previous window's final utilization (100% if it was
    used up) together with an already-past resets_at; computing "remaining" from that gives
    0%. An expired resets_at is now treated as 0% used, and utilization is clamped to 0–100.

完整更新日志见 CHANGELOG.md (English)。