Repository navigation
v1.6.0
新增
- npm 安装统计新增本地安装识别,不再只认全局:
default_rules.json新增
npm_local_install规则(risk: low,action: log),匹配不带-g/--global的
npm install/npm i,排在已有的npm_global_install(risk: medium,
action: confirm)后面,全局规则先命中的命令不会重复计到本地这边。以前本地
npm install完全没有任何规则覆盖——不拦截、不记录、不统计,首页"软件安装
统计"里的"npm 全局安装"卡片对本地安装是彻底的盲区,即使npm install的
preinstall/postinstall生命周期脚本跟全局安装是同一个执行权限、同样是真实
存在的供应链投毒入口(event-stream、ua-parser-js这些真实事件都是本地安装
阶段就已经中招,不需要等到全局安装那一步)。首页卡片文案改成"npm 安装"(原来是
"npm 全局安装"),数字是本地+全局的合计;点开详情不会把两种混在一起平铺,而是
分成"全局安装"/"本地安装"两组分别列出——风险等级不一样的东西不该在界面上看起来
一样重。INSTALL_RULE_GROUPS.npm同步更新为两条规则的并集。用真实
WebSocket/无头浏览器验证过:首页卡片数字正确合并,点开详情两组分类完全正确
(sudo npm install -g pm2、npm install -g ccstatusline归到"全局",
npm install、npm install lodash --save、npm i react归到"本地")。已有用户
的~/.cc-monitor/rules.json是首次运行时拷的副本,不会自动更新——想要这条新
规则生效,删掉它让它重新生成,或者手动加进去。 - "终端会话"新增"新建窗口"按钮:跟"新建会话"共用同一个选工作目录的弹窗
(弹窗标题/说明文字跟着按钮动态换),唯一的行为区别是不会自动往新建的 PTY 里
敲claude\r——原来"新建会话"打开的窗口固定会自动进 Claude Code 会话,单纯
想要个终端跑跑脚本、看看文件的场景之前没有对应的入口。SessionManager. create()新增launchClaude(默认true,不传就是原来的行为)参数,
POST /api/sessions透传launchClaude: false即可跳过。语言切换时弹窗的
动态标题/说明文字也会跟着重新套用当前语言(复用syncGridToggleBtnText()
这些"状态相关文案"函数同一个模式,不能只靠data-i18n静态属性)。用真实
WebSocket 连接分别抓了两种模式下的终端输出对比:launchClaude:false只有
裸 shell 提示符,launchClaude:true能看到claude\r被写入终端。 - 世界地图新增"本机 ↔ 目的地"连线弧光点动画:参考
BeeEye(同一个作者的另一个项目)的
WorldMap.jsx直接移植——每条连接画一条从示意起点到目的地的二次贝塞尔弧线
(arc2d(),往极点方向鼓一点,有大圆航线的弧度感),头部带一个跑动的光点:
方向跟着这条连接上传/下载哪个字节数更多走(下载为主时光点从目的地往回跑,
推断出来的命令文本目标没有真实字节数,默认往外跑),2.2 秒一个周期,光点
用独立一次drawArrays批量画(复用目的地发光点同一套pointProg/
FRAG_POINT着色器),不是只给弧线本身调透明度——第一版只调透明度做的效果,
实测截图确认过肉眼基本看不出哪段更亮,加了这个独立光点才是真的"看起来在跑"。
"本机"在这张图上没有真实地理位置可言(这是运行 Claude Code 的这台机器,不是
流量真正经过的公网出口),固定钉在 (0, 0)(南大西洋几内亚湾外海,"Null
Island")——不会为了这个另外发请求去问"我的公网 IP 在哪",那样等于把用户的
真实位置发给了第三方,图例里明确标注这不是真实位置。WebGL2 不可用时自动退化
成 Canvas 2D 画同一套内容(海岸线/弧线/光点/发光点全都有,用
globalCompositeOperation='lighter'模拟 GL 那边的加法混合),地图不会因为
拿不到 WebGL2 就整个消失。用无头浏览器实测截图验证过两条渲染路径:GL 模式和
2D 模式下光点都正确沿弧线移动、方向都正确(上传为主的连接往外跑,下载为主的
从目的地往回跑)。 - 首页新增"SSH 操作统计"和"下载行为统计":SSH 操作拆成 ssh(远程登录/执行)/
scp(文件复制)/ sftp(文件传输)/ 密钥管理(ssh-keygen/ssh-copy-id/ssh-add/
ssh-agent)/ 其它(autossh/sshpass)五张卡片;下载行为拆成 wget / curl(只在
带-o/-O/--output落盘参数时才算,裸 curl 调 API 不算)/ aria2 / 其它
(axel/lftp/ftp/http)四张卡片。识别方式跟已有的 GitHub 操作统计
(classifyGithubOp)完全一致:按;/&/|/换行拆成子命令,只看子命令开头,
不对整条命令文本做子串匹配。新增webui/lib/audit.js的cc_ssh_op/
cc_download_opSQL 自定义函数,/api/drilldown/ssh-op/:type、
/api/drilldown/download-op/:type接口。 - "AI 轨迹"卡片和世界地图新增命令文本推断的网络目标:之前"AI 轨迹"完全依赖
系统层探针(eBPF/nettop)的实测数据,很多人从没手动启动过探针(需要额外
sudo ./bin/CC-Monitor-probe),即使 Claude 明明执行过一堆 wget/curl/git clone/
ssh/scp 这类联网命令,这张卡片和世界地图也一直是空的。现在会从这些命令的文本里
提取目标主机名(extractCommandHosts():URL 里的 host、user@host形式、
host:path形式,特意不认裸主机名——第一版曾经把curl -o out.tar.gz ...的
输出文件名、scp file.txt user@host:/path的本地源文件名都当成了"主机名",因为
这些字符串本身也是带点的、后面跟着空白,形状上没法用纯正则跟真主机名区分开,
收紧成"必须有@前缀或紧跟冒号"两种明确写法后才不再误判),惰性 DNS 解析
(dnscache.js,带缓存和超时,不在 hook 里做——避免拖慢每次 Bash 调用)+ 查
GeoIP,跟探针实测数据合并进同一套network.js的listTraffic()/summary()/
geoPairs()管线,用inferred:true标记,前端渲染成"推断"徽章(不会冒充成
探针确认过的真实流量——命令有没有真的连通、字节数多少都无法得知)。已实测验证
完整链路:真实 DNS 解析 github.com/example.com 到正确 IP、GeoIP 查到 Toronto/
Singapore、"AI 轨迹"下钻里 Session/文件夹信息正确关联到发起命令的会话。 - 首页新增"截屏审计"卡片:Claude Code 没有内置"截图"工具,识别靠三条独立信号——
① Bash 命令调用截图类 CLI 工具(scrot/gnome-screenshot/import/spectacle/
flameshot/maim/grim/xwd、macOS 的screencapture,以及 Wayland 下常见的
gdbus/dbus-send调用org.freedesktop.portal.Screenshot,判断方式跟已有的
commandDeletesFiles()一个思路,按;/&/|/换行拆成子命令分别看开头,不对
整条命令文本做子串匹配,避免echo输出内容被误判);②Read工具打开的文件
本身是图片(.png/.jpg/.gif/.webp/.bmp,范围比纯截图宽——用户明确要求
把"查看已有图片内容"也算进来);③ MCP/"computer use" 类工具的截图动作(工具名
带screenshot字样,比如 Playwright/Puppeteer 这类浏览器自动化 MCP server 暴露
出来的工具,或者 Anthropic Computer Use 的computer工具、action字段等于
"screenshot")。新增webui/lib/audit.js的cc_is_screenshotSQL 自定义函数、
/api/drilldown/screenshot接口。点开详情只显示命令/文件路径等基本信息,不
读取、不展示截图本身的图像内容——这是用户明确要的尺度,截图很可能带敏感桌面
信息,网页详情页不该把它 serve 出来。用隔离测试数据库 + 无头浏览器验证过端到端
链路:首页卡片计数正确、点击详情正确列出匹配事件、不匹配的普通命令(ls -la、
纯文本文件读取)正确排除在外。 - 新增 kill/pkill 监控进程检测规则:
default_rules.json新增两条规则——
kill_monitoring_process(risk: high,action: confirm)专门匹配
kill/pkill/killall后面跟着 CC-Monitor 自身进程名(探针二进制、
probe_linux.bt/probe_darwin.py、cc_monitor.probe、bpftrace)的命令——
这是"正在试图关掉安全监控本身"这个场景,用confirm而不是block,因为
正常维护/重启探针本来就是合理操作,不该被硬拦截,只是需要人确认一下;通用的
kill/pkill/killall(process_kill,risk: low,action: log)单独放
在后面兜底,只做记录不打扰,kill/pkill在日常开发里太常见,全部要求确认
会造成警报疲劳。两条规则插在disable_security_controls和sudo_pip_install
之间,规则顺序保证专门针对 CC-Monitor 自身进程的这条先匹配。已有用户的
~/.cc-monitor/rules.json是首次运行时拷的副本,不会自动更新,想要这两条新
规则生效,删掉它让它重新生成,或者手动加进去。 - 首页新增"Docker 操作统计":跟 SSH/下载行为统计同一套思路,按
;/&/
|/换行拆成子命令只看开头,拆成 run(启动容器)/ build(构建镜像)/ exec
(进入容器执行)/ compose(docker compose/docker-compose)/ 其它
(ps/logs/images等只读查看类)五张卡片。run/build/exec 单独拆出来是
因为这三个是"会执行任意外部镜像/Dockerfile/容器内命令",风险跟纯只读查看不是
一个量级。新增webui/lib/audit.js的classifyDockerOp/dockerOpsStats/
dockerOpsDetails、cc_docker_opSQL 自定义函数,/api/drilldown/docker-op/:type
接口。用隔离测试数据库验证过 9 条真实命令(含一条echo "docker run..."的
字符串输出,确认不会被误判成真的执行了 docker run)分类结果全部正确,用无头
浏览器截图验证过首页卡片计数和点击详情弹窗都正确。 - 敏感文件读取检测扩展到 Bash 命令:之前
sensitive_file_read规则只覆盖
Read工具直接打开.ssh//.aws/credentials/.env/私钥等敏感路径的场景,
用cat/less/head等 Bash 命令读同样的文件完全是盲区。新增
sensitive_file_read_bash(risk: medium,action: log)覆盖
cat/less/more/head/tail/strings/xxd/hexdump/od后面跟着这些
敏感路径的命令;另外新增env_dump(risk: low,action: log)记录
env/printenv/export -p这类会把当前进程全部环境变量(可能包含 API key/
token)打印到终端的命令。两条都插在已有的sensitive_file_read和
system_config_write之间。这几条规则复用了policy.py现有的re.search
纯正则匹配,没有像 webui 那边的splitShellSegments()一样做引号/heredoc
感知——这是 Python 策略引擎全部 30+ 条规则共有的既有特点,不是这几条新规则
引入的新问题,之后如果要修,得是对整个策略引擎的单独改造,不在这次范围内。
同样地,已有用户的~/.cc-monitor/rules.json不会自动更新,想要这两条新
规则生效需要手动同步。 - 新增
su/pkexec提权检测:之前只有sudo_usage这一条规则覆盖提权场景,
su/pkexec这两个跟sudo效果等价(切换/以其它用户身份执行命令,通常是
root)的提权方式完全漏检。新增su_pkexec_privilege_escalation
(risk: medium,action: confirm),只认命令开头或;/&&/||之后紧跟的
su/pkexec(写法跟已有sudo_usage的(^|;|&&|\|\|)\s*sudo\b完全一致),
不会把echo su这类字符串输出、或subprocess.run(...)里当成普通标识符出现的
"su" 误判成真的在提权。插在sudo_usage后面。 - 新增单文件
chmod 777(非递归)检测:原来chmod_world_writable_recursive
只认目标路径以/或~开头的写法,chmod_recursive_generic只认带-R的
调用——chmod 777 file.txt(相对路径、单文件、不递归)这种真实存在的场景恰好
两条都没覆盖到。新增chmod_777_single_file(risk: medium,action: confirm),
插在chmod_recursive_generic之后,靠规则顺序保证只在前两条都没命中时才轮到它
(带-R的、目标是//~开头的都已经在更早的规则里被更高级别处理过,不会
重复触发或降级)。 - 新增数据库直连破坏性命令检测:
mysql/psql/redis-cli/mongo/mongosh/
sqlite3这类数据库命令行客户端接DROP/DELETE/TRUNCATE(SQL)或
FLUSHALL/FLUSHDB(Redis)之前完全没有任何规则覆盖——能直接读写生产数据,
风险性质跟"删文件"是一回事,但完全在雷达外。新增db_destructive_command
(risk: high,action: confirm),插在history_tampering和
docker_privileged_or_host_mount之间。用 23 个正负测试用例验证过匹配和跟
既有规则的优先级交互(比如chmod -R 777 subdir正确命中更早的
chmod_recursive_generic而不是新规则,chmod 777 ~/.ssh/id_rsa正确命中
chmod_world_writable_recursive),node --test5/5 全部通过,不是回归。
这三条同样复用policy.py现有的纯正则匹配,没有 webui 那边的引号/heredoc
感知,理由跟上面sensitive_file_read_bash/env_dump一致;已有用户的
~/.cc-monitor/rules.json同样不会自动更新。 - 新增历史指令读取检测:
cat ~/.bash_history/cat .history这类读取 shell
历史文件的命令、以及直接执行裸history命令(会把当前会话的历史命令原样打印
到 stdout),之前完全没有规则覆盖——命令历史里经常留着过去输入过的密码/token
等明文凭据,是真实存在的信息泄露路径。新增history_read(risk: low,
action: log),覆盖cat/less/more/head/tail/strings读取
.bash_history/.zsh_history/.python_history/.mysql_history/
.psql_history/.node_repl_history/任意*.history文件、以及命令开头或
;/&&/||之后紧跟的裸history(history -c这种清空历史的破坏性用法
已经由更早的history_tampering规则单独覆盖,靠规则顺序 + 显式排除
(?!\s*-c\b)避免重复触发)。插在history_tampering之后。用 13 个正负
测试用例验证过匹配和优先级,不是回归。 - 加强反弹 shell / 后门执行检测:原来的
reverse_shell_pattern只认
nc ... -e /bin/sh这一种写法,覆盖面偏窄——真实攻击/红队工具箱里还有好几种
常见变体完全漏检:nc/ncat/netcat换用-c而不是-e(部分 nc 变体的
执行命令写法)、ncat/netcat这两个 nc 的别名工具本身没被认到、socat用
exec:目标做反弹 shell(很多加固过的系统没有带-e支持的 nc,socat是
最常见的替代品)、以及不带-e/-c参数、靠mkfifo建一个命名管道配合
nc+shell 手动拼出来的反弹 shell(更隐蔽,规避了对-e/-c参数的检测)。
扩展了reverse_shell_pattern的正则覆盖这四种新变体,risk/action维持
原来的high/block不变。用 17 个正负测试用例验证过匹配(含原有nc -e/
/dev/tcp/sh -i写法必须继续命中,不能因为改动引入回归;以及nc -zv/
nmap/ncat --ssl这类正常网络诊断用途不能被误伤)。范围上刻意不做的:
Python/Perl/PHP/Ruby 这类脚本语言的一行反弹 shell(python3 -c "import socket,subprocess...")——这类命令的"危险性"完全取决于脚本内容的
语义,纯正则匹配要么漏掉绝大多数变体、要么把大量合法用到socket模块的
脚本也拦下来,误报代价太高,不在这次范围内。 - 首页新增"压缩/归档操作统计":跟 SSH/下载/Docker 操作统计同一套思路,拆成
tar / zip(含 unzip)/ 7z / gzip(含 gunzip/zcat)/ 其它
(bzip2/xz/zstd/rar 等)五张卡片,按 Bash 命令文本识别。新增
webui/lib/audit.js的classifyArchiveOp/archiveOpsStats/
archiveOpsDetails、cc_archive_opSQL 自定义函数,
/api/drilldown/archive-op/:type接口。 - 首页新增"网络诊断工具统计":nc(含 ncat/netcat 别名)/ nmap / telnet /
其它(socat)四张卡片,纯粹是"用过这些工具没有"的可见性统计,跟上面的反弹
shell 风险判断是两回事——nc -zv example.com 443这种正常端口探测也会被计入
这张卡片,不代表危险。新增classifyNetdiagOp/netdiagOpsStats/
netdiagOpsDetails、cc_netdiag_opSQL 自定义函数,
/api/drilldown/netdiag-op/:type接口。 - 首页新增"进程管理/后台驻留统计":nohup / disown / 后台任务(命令末尾裸
&)/ 其它(setsid)四张卡片。前两个按子命令开头识别,跟其它分类器一个思路;
"后台任务"不一样——裸&不是某个命令的名字,是整条命令末尾的 shell 语法
标记,splitShellSegments()本身会把单个&当分隔符切开,没法通过"看子命令
开头"识别,改成直接在原始命令文本上找"独立的&":前面不能紧跟&/>(排除
&&、2>&1、&>这些不是真正后台标记的写法),后面不能紧跟数字/&/>
(同样排除文件描述符重定向),且这个&后面(跳过空白)直接是命令末尾或
;——故意收窄换取不误伤curl 'http://x.com/a&b=c'这类 URL 查询字符串里的
&,代价是task1 & task2(后台之后紧接着写下一条命令、中间没有;)这种
少见写法会漏检。新增classifyProcessBackground/procbgOpsStats/
procbgOpsDetails、cc_procbg_opSQL 自定义函数,
/api/drilldown/procbg-op/:type接口。 - 首页新增"子代理派生"统计卡片:跟 MCP/Skill 调用统计同一个思路,只是分组
字段换成tool_input里的subagent_type(general-purpose/Explore/
Plan/fork,或者用户自定义的子代理名字)。Claude Code 不同版本这个工具名
叫 "Task" 还是 "Agent" 不完全一致,两个都认。子代理会消耗独立资源、有自己的
一整套操作轨迹,之前完全混在笼统的"工具调用"计数里,没有单独可见性。新增
subagentCallStats/subagentCallBreakdown/subagentCallEvents,
/api/drilldown/subagent-calls接口,复用mcpCallBreakdown/
skillCallBreakdown已有的 SQLjson_extract分组手法。
以上四个统计卡片 + 反弹 shell 检测扩展 + 历史指令读取检测都用隔离测试数据库
(压缩/网络诊断/后台驻留三个分类器还额外验证了curlURL 查询字符串里的&、
cmd1 && cmd2、echo '...'这几种已知误判来源正确排除在外)+ 无头浏览器截图
验证过端到端链路:首页卡片计数正确、点击详情正确列出匹配事件。node --test
5/5 全部通过,不是回归。 - 首页折叠 GitHub/SSH/下载/Docker/压缩/网络诊断/进程管理这七组统计卡片:
这七组原来每组都是一整排细分类卡片(合计 6+5+4+5+5+4+4 = 33 张),叠在一起
刷屏太厉害。改成每组只放一张汇总卡片(数字是这组所有分类的合计),点开才展示
分类小计表 + 完整命令明细(每条前面挂一个分类徽章),交互模式跟已有的
MCP/Skill/子代理调用卡片保持一致。webui/lib/audit.js把七组各自的
xxxOpsStats()/xxxOpsDetails(type)换成两个通用函数
opsBreakdown(sqlFn)/opsEvents(sqlFn)(七组背后本来就是同一个"按
cc_xxx_op()自定义 SQL 函数分类、GROUP BY kind"的查询形状,只是传的函数名
不同,抽出来减少了约 190 行重复代码);server.js对应把 14 个/api/drilldown/ xxx-op/:type单分类接口换成 7 个/api/drilldown/xxx-ops(不带:type)
接口,一次性返回{breakdown, events};首页卡片总数改成读
sumN(audit.xxxOpsBreakdown())。原来这七组专用的GITHUB_OP_TYPES等
仅用于校验 URL:type参数的常量、drilldown.githubOp.suffix这个不再被
引用的 i18n key 一并删除,没有留背景兼容代码。用隔离测试数据库 + 无头浏览器
截图验证过:首页从 7 排 33 张卡片变成 1 排 7 张卡片,点开任意一张(截图验证的
是 GitHub 操作)正确显示分类小计表和带语法高亮的命令明细列表,node --test
5/5 全部通过,不是回归。
修复
- 命令分类器误报:heredoc/引号内多行字符串被当成多条独立子命令:截屏审计、
GitHub/SSH/下载操作统计、AI 轨迹的命令主机名提取等好几个分类器,共用同一套
"按;/&/|/换行拆成子命令再看开头"的判断方式,天真的换行切分有个漏洞——
双引号参数、heredoc(<<'EOF' ... EOF)正文里的换行是内容本身的一部分,不是
shell 语法意义上的命令分隔符。线上这台机器自己的审计数据抓到两个真实案例:
python3 -c "\nimport json,sys\n..."这种命令里,双引号参数中单独成一行的
import json,sys被当成了 ImageMagick 截图命令import的调用;
git commit -m "$(cat <<'EOF' ... EOF)"里,heredoc 正文中 word-wrap 过的一行
(刚好是本项目上一条 commit 描述截图功能时提到的 "spectacle" 截图工具名)被
当成了真的在调用spectacle截图——"误报太多"的用户反馈根源就在这里。新增
splitShellSegments(),用一个简化版的 shell 分词器(跟踪当前在不在单/双
引号、在不在 heredoc 正文里)替换所有分类器里原来的cmd.split(/[;&|\n]+/),
只有真正在"顶层"的分隔符才切分。顺手把import从截屏 CLI 识别列表里删掉
(ImageMagick 的 import 命令在现代 Linux 桌面上已经边缘化,"import" 又是
Python 极常用的关键字,即使分词修好了也不值得为了这一个命令保留这个碰撞
风险)。用两个真实复现案例验证过:改之前两条误报都会命中,改之后都正确排除,
真的scrot/gnome-screenshot调用依然正确识别;其它分类器(GitHub/SSH/
下载操作统计)跑现有的单元测试全部结果不变,不是回归。
English
Added
- npm install stats now recognize local installs, not just global ones: added an
npm_local_installrule todefault_rules.json(risk: low,action: log) matching
npm install/npm iwithout-g/--global, placed after the existing
npm_global_installrule (risk: medium,action: confirm) so a command the global rule
already claimed never double-counts on the local side. Previously a localnpm installhad
zero rule coverage at all — never blocked, never logged, never counted — and the Home page's
"npm global installs" card was a complete blind spot for it, even thoughnpm install's
preinstall/postinstalllifecycle scripts run with the exact same privileges as a global
install and are just as real a supply-chain attack surface (theevent-streamand
ua-parser-jsincidents both compromised machines at the local-install stage, not global).
The Home card's label changed to "npm installs" (was "npm global installs"), showing the
combined local+global total; clicking through no longer flattens both kinds into one list —
the drilldown splits into separate "Global installs"/"Local installs" groups, since risk
levels that aren't the same shouldn't look the same on screen.INSTALL_RULE_GROUPS.npmwas
updated to the union of both rules. Verified over a real WebSocket connection and a headless
browser: the Home card correctly sums both, and the drilldown correctly sorts
sudo npm install -g pm2/npm install -g ccstatuslineinto "Global" and
npm install/npm install lodash --save/npm i reactinto "Local". An existing
~/.cc-monitor/rules.jsonis a copy made on first run and won't pick up the new rule
automatically — delete it to regenerate, or add it by hand. - New "New Window" button on Terminal Sessions: shares the same working-directory picker
modal as "New Session" (the modal's title/hint text swap dynamically based on which button
opened it), the only behavioral difference being it never typesclaude\rinto the freshly
spawned PTY — "New Session" always drops you straight into a Claude Code session, and there
was previously no entry point for just wanting a plain terminal to run a script or poke
around files.SessionManager.create()gained alaunchClaudeparameter (defaults to
true, so passing nothing keeps the old behavior);POST /api/sessionspasses through
launchClaude: falseto skip it. The modal's dynamic title/hint also get re-applied on a
language switch (reusing the same pattern assyncGridToggleBtnText()and friends for
"state-dependent" text — a plaindata-i18nstatic attribute alone isn't enough here).
Verified by capturing terminal output over a real WebSocket connection for both modes:
launchClaude:falseshows only a bare shell prompt,launchClaude:trueshowsclaude\r
actually typed into the terminal. - World map gained an animated "this machine ↔ destination" arc with a travelling light
dot: a direct port of BeeEye (another project by the
same author)'sWorldMap.jsx— each connection draws a quadratic-bezier arc from a
schematic anchor to the destination (arc2d(), bowed toward the pole for a great-circle
feel), with a travelling dot at the head: direction follows whichever side of the connection
moved more bytes (download-heavy animates back toward the anchor; inferred command-text
targets have no real byte counts, so they default outward), a 2.2s cycle, the dot itself
drawn as a single batcheddrawArrayscall reusing the samepointProg/FRAG_POINTshader
already used for destination glows — not just alpha-modulating the arc line itself, which
the first version did and which a real screenshot showed was barely perceptible; the
separate dot is what actually reads as "something travelling." The anchor has no real
geographic meaning here (this is the machine running Claude Code, not the public egress the
traffic actually passes through), so it's pinned at (0, 0) (open ocean off the Gulf of
Guinea, "Null Island") — no extra request is made to ask a third party for the public IP
just for this, and the legend explicitly says it isn't a real location. Falls back to Canvas
2D (coastlines/arcs/dots/glows all present,globalCompositeOperation='lighter'standing in
for the GL side's additive blending) when WebGL2 isn't available, so the map never
disappears entirely just because of that. Verified both rendering paths with real
screenshots from a headless browser: the dot correctly travels along the arc and the
direction is correct in both GL and 2D mode (upload-heavy animates outward, download-heavy
animates back toward the anchor). - New "SSH Operations" and "Downloads" cards on the Home page: SSH operations split into
five cards — ssh (remote login/exec) / scp (file copy) / sftp (file transfer) / key
management (ssh-keygen/ssh-copy-id/ssh-add/ssh-agent) / other
(autossh/sshpass); downloads split into four — wget / curl (only counted when it writes
to a file via-o/-O/--output— a bare curl call to an API isn't a "download") / aria2 /
other (axel/lftp/ftp/http). Classified exactly like the existing GitHub operation
stats (classifyGithubOp): split on;/&/|/newlines, check each sub-command's start,
never a substring match against the whole text. Added thecc_ssh_op/cc_download_opSQL
custom functions inwebui/lib/audit.js, and/api/drilldown/ssh-op/:type,
/api/drilldown/download-op/:typeendpoints. - The "AI Trajectory" card and world map now also include command-text-inferred network
targets: "AI Trajectory" used to depend entirely on the system-layer probe's (eBPF/nettop)
observed data, and many people never start the probe by hand (it needs a separate
sudo ./bin/CC-Monitor-probe), so the card and the world map stayed empty even when Claude
had clearly run a pile of networked commands like wget/curl/git clone/ssh/scp. Now the
target hostname is extracted from these commands' text (extractCommandHosts(): the host in
a URL,user@hostform,host:pathform — deliberately never a bare hostname with no@
and no trailing colon; an earlier version misread the output filename in
curl -o out.tar.gz ...and the local source path inscp file.txt user@host:/pathas
"hostnames," since those strings are also dotted and followed by whitespace, structurally
indistinguishable from a real host by regex alone — tightening the rule to require an@
prefix or an immediately-following colon fixed it), lazily resolved via DNS
(dnscache.js, cached with a timeout — deliberately not done inside the hook, to avoid
slowing down every Bash call) and geo-located, then merged into the samenetwork.js
pipeline (listTraffic()/summary()/geoPairs()) used for probe data, tagged
inferred: trueand rendered as an "inferred" badge on the frontend — never passed off as
confirmed probe traffic, since there's no way to know whether the command actually connected
or how many bytes moved. Verified end-to-end: real DNS resolution of github.com/example.com
to correct IPs, GeoIP lookups landing in Toronto/Singapore, and the AI Trajectory drilldown
correctly attributing Session/folder info back to the session that ran the command. - New "Screenshot Audit" card on the Home page: Claude Code has no built-in "screenshot"
tool, so this is identified from three independent signals — ① a Bash command invoking a
screenshot CLI (scrot,gnome-screenshot,import,spectacle,flameshot,maim,
grim,xwd, macOS'sscreencapture, or the Wayland-typicalgdbus/dbus-sendcall to
org.freedesktop.portal.Screenshot— matched the same way as the existing
commandDeletesFiles(): split on;/&/|/newlines and check each sub-command's start,
never a substring match against the whole text, so anecho'd string can't be misread as a
real invocation); ② theReadtool opening a file that's itself an image
(.png/.jpg/.gif/.webp/.bmp— deliberately broader than "screenshot," since the user
explicitly wanted viewing an existing image counted too); ③ an MCP/"computer use" tool's
screenshot action (a tool name containing "screenshot" — e.g. what browser-automation MCP
servers like Playwright/Puppeteer expose — or acomputertool whoseactionfield is
"screenshot"). Added thecc_is_screenshotSQL custom function inwebui/lib/audit.jsand
a new/api/drilldown/screenshotendpoint. The drilldown only shows basic info (the command
or file path), never reads or renders the screenshot's own image content — an explicit scope
call from the user, since a screenshot can easily contain sensitive desktop content that a web
page shouldn't be serving. Verified end-to-end with an isolated test database and a headless
browser: the home card count is correct, the drilldown lists exactly the matching events, and
non-matching commands (ls -la, opening a plain text file) are correctly excluded. - New kill/pkill monitoring-tamper detection rules:
default_rules.jsongains two rules —
kill_monitoring_process(risk: high,action: confirm) matcheskill/pkill/killall
followed by one of CC-Monitor's own process names (the probe binary,probe_linux.bt/
probe_darwin.py,cc_monitor.probe,bpftrace) — this is the "someone is trying to shut
down the monitoring itself" scenario, set toconfirmrather thanblocksince restarting
the probe for legitimate maintenance is a normal action that shouldn't be hard-blocked, just
surfaced for a human to confirm; a generickill/pkill/killall(process_kill,risk: low,
action: log) sits after it as a catch-all, log-only, since killing processes is extremely
common in day-to-day dev work and flagging every instance for confirmation would cause alert
fatigue. Both rules are inserted betweendisable_security_controlsandsudo_pip_install,
with ordering ensuring the CC-Monitor-specific rule matches first. An existing user's
~/.cc-monitor/rules.jsonis a copy made on first run and does not auto-update — delete it to
regenerate, or add these rules manually, to pick them up. - New "Docker Operations" home card: same approach as the SSH/Download operation stats —
split on;/&/|/newlines and check only each sub-command's start, into five cards: run
(start a container) / build (build an image) / exec (run inside a running container) / compose
(docker composeor the standalonedocker-compose) / other (read-only inspection commands
likeps/logs/images). run/build/exec are broken out separately because they can execute
arbitrary code from an external image, Dockerfile, or a running container — not the same risk
tier as pure read-only inspection. AddedclassifyDockerOp/dockerOpsStats/dockerOpsDetails
and thecc_docker_opSQL custom function inwebui/lib/audit.js, plus a new
/api/drilldown/docker-op/:typeendpoint. Verified against 9 real commands in an isolated test
database (including anecho "docker run..."string, confirmed not to be misread as an actual
docker run) — all classified correctly; also verified the home card counts and the drilldown
modal via a headless-browser screenshot. - Sensitive-file-read detection extended to Bash commands: previously the
sensitive_file_readrule only covered theReadtool opening sensitive paths
(.ssh/,.aws/credentials,.env, private keys, etc.) directly — reading the same files via
a Bash command likecat/less/headwas a complete blind spot. Added
sensitive_file_read_bash(risk: medium,action: log) covering
cat/less/more/head/tail/strings/xxd/hexdump/odfollowed by one of those
sensitive paths, plusenv_dump(risk: low,action: log) logging
env/printenv/export -p— commands that print the entire current-process environment
(which can include API keys/tokens) to the terminal. Both rules are inserted between the
existingsensitive_file_readandsystem_config_write. These rules reusepolicy.py's
existing plainre.searchmatching and do not get the same quote/heredoc awareness as the
webui-sidesplitShellSegments()— that's a pre-existing characteristic shared by all 30+
rules in the Python policy engine, not something newly introduced by these rules; fixing it
properly would mean reworking the whole policy engine, which is out of scope here. As with the
rules above, an existing user's~/.cc-monitor/rules.jsonwon't auto-update to pick these up. - New
su/pkexecprivilege-escalation detection: previously onlysudo_usagecovered
privilege escalation —su/pkexec, which achieve the same thing (switch to, or run as,
another user — typically root), were a complete blind spot. Added
su_pkexec_privilege_escalation(risk: medium,action: confirm), matchingsu/pkexec
only at the start of a command or right after;/&&/||(the same shape as the existing
sudo_usagepattern,(^|;|&&|\|\|)\s*sudo\b), so anecho su-style string or the token "su"
appearing inside something likesubprocess.run(...)isn't misread as an actual escalation.
Inserted right aftersudo_usage. - New single-file, non-recursive
chmod 777detection:chmod_world_writable_recursiveonly
matched when the target path started with/or~, andchmod_recursive_genericonly
matched when-Rwas present —chmod 777 file.txt(a relative path, single file, no
recursion) fell through both. Addedchmod_777_single_file(risk: medium,action: confirm),
inserted right afterchmod_recursive_genericso rule ordering guarantees it only fires when
neither of the earlier two already matched (a-Rcall or a/- or~-rooted target is
already handled, at its appropriate severity, by an earlier rule — no double-firing or
downgrade). - New destructive direct-database-command detection:
mysql/psql/redis-cli/mongo/
mongosh/sqlite3followed byDROP/DELETE/TRUNCATE(SQL) orFLUSHALL/FLUSHDB(Redis)
previously had zero rule coverage — these can write directly to production data, the same risk
category as "deleting files," but were completely off the radar. Addeddb_destructive_command
(risk: high,action: confirm), inserted betweenhistory_tamperingand
docker_privileged_or_host_mount. Verified with 23 positive/negative test cases covering both
matching and the interaction with existing rules' precedence (e.g.chmod -R 777 subdir
correctly hits the earlierchmod_recursive_genericrather than the new one, and
chmod 777 ~/.ssh/id_rsacorrectly hitschmod_world_writable_recursive);node --teststill
passes 5/5 — not a regression. These three rules reusepolicy.py's existing plain regex
matching without the webui-side quote/heredoc awareness, for the same reason noted above for
sensitive_file_read_bash/env_dump; an existing user's~/.cc-monitor/rules.jsonlikewise
won't auto-update. - New shell-history-read detection: commands reading shell history files (
cat ~/.bash_history,cat .history, etc.) and running the barehistorybuiltin (which dumps the
current session's command history straight to stdout) had zero rule coverage — command history
frequently retains plaintext passwords/tokens typed as CLI arguments in the past, a real
information-leak path. Addedhistory_read(risk: low,action: log), covering
cat/less/more/head/tail/stringsreading.bash_history/.zsh_history/
.python_history/.mysql_history/.psql_history/.node_repl_history/any*.historyfile, and
a barehistoryat the start of a command or right after;/&&/||(the destructive
history -ccase is already covered by the earlierhistory_tamperingrule; ordering plus an
explicit(?!\s*-c\b)exclusion avoids double-firing). Inserted right afterhistory_tampering.
Verified with 13 positive/negative test cases covering matching and precedence — not a
regression. - Strengthened reverse-shell / backdoor-execution detection: the previous
reverse_shell_patternonly recognizednc ... -e /bin/sh— real attacker/red-team toolkits
have several common variants that were a complete blind spot:nc/ncat/netcatusing-c
instead of-e(how some nc variants invoke a command),ncat/netcat— nc's own aliases —
weren't recognized at all,socatdoing a reverse shell via anexec:target (many hardened
systems ship an nc without-esupport, makingsocatthe most common substitute), and a
reverse shell hand-assembled from amkfifonamed pipe plusnc+ a shell without any-e/-c
flag at all (stealthier, since it evades any check keyed on those flags). Expanded
reverse_shell_pattern's regex to cover all four new variants, keepingrisk/actionat the
existinghigh/block. Verified with 17 positive/negative test cases (the originalnc -e,
/dev/tcp, andsh -iforms had to keep matching — no regression from the change — while
ordinary network-diagnostic usage likenc -zv,nmap,ncat --sslhad to stay unflagged).
Deliberately out of scope: one-liner reverse shells in scripting languages (Python/Perl/PHP/Ruby,
e.g.python3 -c "import socket,subprocess...") — their "danger" depends entirely on the
script's semantics, and a plain regex would either miss most variants or flag a large amount of
legitimate code that happens to use thesocketmodule; the false-positive cost was judged too
high for this pass. - New "Archive/Compression Operations" home card: same approach as the SSH/Download/Docker
operation stats — split into tar / zip (including unzip) / 7z / gzip (including gunzip/zcat) /
other (bzip2/xz/zstd/rar, etc.), five cards, classified from the Bash command text. Added
classifyArchiveOp/archiveOpsStats/archiveOpsDetailsand thecc_archive_opSQL custom
function inwebui/lib/audit.js, plus a new/api/drilldown/archive-op/:typeendpoint. - New "Network Diagnostic Tools" home card: nc (including the ncat/netcat aliases) / nmap /
telnet / other (socat), four cards — purely a "was this tool used" visibility stat, a separate
concern from the reverse-shell risk judgment above:nc -zv example.com 443, an ordinary port
probe, is still counted on this card without implying danger. Added
classifyNetdiagOp/netdiagOpsStats/netdiagOpsDetailsand thecc_netdiag_opSQL custom
function, plus a new/api/drilldown/netdiag-op/:typeendpoint. - New "Process Management / Backgrounding" home card: nohup / disown / background job (a bare
trailing&) / other (setsid), four cards. The first two are classified by sub-command start,
the same approach as the other classifiers; "background job" is different — a bare&isn't a
command name, it's a shell-syntax marker at the end of the whole command, and
splitShellSegments()itself treats a lone&as a separator, so it can't be identified by
"look at the sub-command's start." Instead it's detected by scanning the raw command text for an
"isolated&": not immediately preceded by&/>(excludes&&,2>&1,&>— none of which
are real background markers), not immediately followed by a digit/&/>(excludes file
descriptor redirection), and, after that&(skipping whitespace), immediately the end of the
command or a;. This is deliberately narrow to avoid false-positiving on the&inside a URL
query string likecurl 'http://x.com/a&b=c', at the cost of missing a rarer form like
task1 & task2(backgrounding immediately followed by another command with no;in between).
AddedclassifyProcessBackground/procbgOpsStats/procbgOpsDetailsand thecc_procbg_opSQL
custom function, plus a new/api/drilldown/procbg-op/:typeendpoint. - New "Subagent spawns" home card: same approach as the MCP/Skill call stats, just grouped by
thesubagent_typefield intool_input(general-purpose/Explore/Plan/fork, or a
user-defined subagent name). Different Claude Code versions call this tool "Task" or "Agent" —
both are recognized. Subagents consume independent resources and have their own full trail of
operations, previously buried inside the generic "tool calls" count with no dedicated
visibility. AddedsubagentCallStats/subagentCallBreakdown/subagentCallEventsand a new
/api/drilldown/subagent-callsendpoint, reusing the same SQLjson_extractgrouping technique
already used bymcpCallBreakdown/skillCallBreakdown.
All four of the above stat cards, plus the reverse-shell detection expansion and the
history-read detection, were verified against isolated test databases (the archive/netdiag/
procbg classifiers additionally verified that a&inside a curl URL query string,cmd1 && cmd2, andecho '...'— three known false-positive sources — are correctly excluded) plus a
headless-browser screenshot of the end-to-end flow: home card counts are correct, and the
drilldown lists exactly the matching events.node --teststill passes 5/5 — not a regression. - Collapsed the GitHub/SSH/Download/Docker/Archive/Network-Diagnostics/Process-Management home
cards into one card per group: these seven groups previously each rendered a full row of
sub-category cards (6+5+4+5+5+4+4 = 33 cards total), which stacked up into a wall of cards. Each
group now shows a single summary card (the number is the sum across that group's categories);
clicking it reveals a category breakdown table plus the full command list (each entry tagged
with a category badge) — the same interaction pattern already used by the MCP/Skill/Subagent
call cards. Inwebui/lib/audit.js, each group's separatexxxOpsStats()/xxxOpsDetails(type)
pair was replaced with two generic functions,opsBreakdown(sqlFn)/opsEvents(sqlFn)(all
seven groups were already the same "classify via acc_xxx_op()SQL custom function,GROUP BY kind" query shape, just with a different function name — extracting it removed about 190 lines
of duplication).server.jscorrespondingly replaced the 14 single-category
/api/drilldown/xxx-op/:typeendpoints with 7/api/drilldown/xxx-opsendpoints (no:type),
each returning{breakdown, events}in one call; the home card totals now read
sumN(audit.xxxOpsBreakdown()). The now-unused per-group constants that existed only to
validate the old:typeURL parameter (e.g.GITHUB_OP_TYPES) and the orphaned
drilldown.githubOp.suffixi18n key were removed along with them — no backwards-compat
leftovers. Verified against an isolated test database plus a headless-browser screenshot: the
home page went from 7 rows of 33 cards down to 1 row of 7 cards, and opening one (GitHub
operations was the one screenshotted) correctly shows the category breakdown table and the
syntax-highlighted command list.node --teststill passes 5/5 — not a regression.
Fixed
- Command classifiers false-positiving: heredoc/quoted multi-line strings misread as
several independent sub-commands: Screenshot Audit, GitHub/SSH/Download operation stats,
and the AI Trajectory command-hostname extraction all share the same "split on
;/&/|/newlines, check each sub-command's start" approach — but the naive newline split
has a hole: a newline inside a double-quoted argument or a heredoc body (<<'EOF' ... EOF)
is part of the content, not a shell-syntax command separator. This machine's own audit data
caught two real cases: inpython3 -c "\nimport json,sys\n...", the lineimport json,sys
inside the quoted argument got read as an invocation of ImageMagick'simportscreenshot
command; ingit commit -m "$(cat <<'EOF' ... EOF)", a word-wrapped line inside the heredoc
body (which happened to be this project's own previous commit describing the screenshot
feature, mentioning the "spectacle" screenshot tool by name) got read as an actual call to
spectacle— this is exactly what the "too many false positives" report was catching. Added
splitShellSegments(), a small shell tokenizer (tracks whether the cursor is currently
inside a single/double quote or a heredoc body) that replaces every classifier's old
cmd.split(/[;&|\n]+/)— only separators at the true "top level" now split. Also dropped
importfrom the screenshot CLI list entirely (ImageMagick'simportis already marginal
on modern Linux desktops, and "import" is too common a Python keyword to be worth the
collision risk even with the tokenizer fixed). Verified against the two real reproduction
cases: both false-matched before the fix and are correctly excluded after, while genuine
scrot/gnome-screenshotinvocations still match; the other classifiers (GitHub/SSH/
Download operation stats) were re-run through their existing unit tests with identical
results — not a regression.
完整更新日志见 CHANGELOG.md (English)。