Skip to content

v1.6.0

Choose a tag to compare

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

新增

  • 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_op SQL 自定义函数,/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_screenshot SQL 自定义函数、
    /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_op SQL 自定义函数,/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 --test 5/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_op SQL 自定义函数,
    /api/drilldown/archive-op/:type 接口。
  • 首页新增"网络诊断工具统计":nc(含 ncat/netcat 别名)/ nmap / telnet /
    其它(socat)四张卡片,纯粹是"用过这些工具没有"的可见性统计,跟上面的反弹
    shell 风险判断是两回事——nc -zv example.com 443 这种正常端口探测也会被计入
    这张卡片,不代表危险。新增 classifyNetdiagOp/netdiagOpsStats/
    netdiagOpsDetails、cc_netdiag_op SQL 自定义函数,
    /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_op SQL 自定义函数,
    /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 已有的 SQL json_extract 分组手法。
    以上四个统计卡片 + 反弹 shell 检测扩展 + 历史指令读取检测都用隔离测试数据库
    (压缩/网络诊断/后台驻留三个分类器还额外验证了 curl URL 查询字符串里的 &、
    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_install rule to default_rules.json (risk: low, action: log) matching
    npm install/npm i without -g/--global, placed after the existing
    npm_global_install rule (risk: medium, action: confirm) so a command the global rule
    already claimed never double-counts on the local side. Previously a local npm install had
    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 though npm install's
    preinstall/postinstall lifecycle scripts run with the exact same privileges as a global
    install and are just as real a supply-chain attack surface (the event-stream and
    ua-parser-js incidents 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.npm was
    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 ccstatusline into "Global" and
    npm install / npm install lodash --save / npm i react into "Local". An existing
    ~/.cc-monitor/rules.json is 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 types claude\r into 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 a launchClaude parameter (defaults to
    true, so passing nothing keeps the old behavior); POST /api/sessions passes through
    launchClaude: false to skip it. The modal's dynamic title/hint also get re-applied on a
    language switch (reusing the same pattern as syncGridToggleBtnText() and friends for
    "state-dependent" text — a plain data-i18n static attribute alone isn't enough here).
    Verified by capturing terminal output over a real WebSocket connection for both modes:
    launchClaude:false shows only a bare shell prompt, launchClaude:true shows claude\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)'s WorldMap.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 batched drawArrays call reusing the same pointProg/FRAG_POINT shader
    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 the cc_ssh_op/cc_download_op SQL
    custom functions in webui/lib/audit.js, and /api/drilldown/ssh-op/:type,
    /api/drilldown/download-op/:type endpoints.
  • 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@host form, host:path form — 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 in scp file.txt user@host:/path as
    "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 same network.js
    pipeline (listTraffic()/summary()/geoPairs()) used for probe data, tagged
    inferred: true and 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's screencapture, or the Wayland-typical gdbus/dbus-send call 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 an echo'd string can't be misread as a
    real invocation); ② the Read tool 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 a computer tool whose action field is
    "screenshot"). Added the cc_is_screenshot SQL custom function in webui/lib/audit.js and
    a new /api/drilldown/screenshot endpoint. 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.json gains two rules —
    kill_monitoring_process (risk: high, action: confirm) matches kill/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 to confirm rather than block since restarting
    the probe for legitimate maintenance is a normal action that shouldn't be hard-blocked, just
    surfaced for a human to confirm; a generic kill/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 between disable_security_controls and sudo_pip_install,
    with ordering ensuring the CC-Monitor-specific rule matches first. An existing user's
    ~/.cc-monitor/rules.json is 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 compose or the standalone docker-compose) / other (read-only inspection commands
    like ps/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. Added classifyDockerOp/dockerOpsStats/dockerOpsDetails
    and the cc_docker_op SQL custom function in webui/lib/audit.js, plus a new
    /api/drilldown/docker-op/:type endpoint. Verified against 9 real commands in an isolated test
    database (including an echo "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_read rule only covered the Read tool opening sensitive paths
    (.ssh/, .aws/credentials, .env, private keys, etc.) directly — reading the same files via
    a Bash command like cat/less/head was a complete blind spot. Added
    sensitive_file_read_bash (risk: medium, action: log) covering
    cat/less/more/head/tail/strings/xxd/hexdump/od followed by one of those
    sensitive paths, plus env_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
    existing sensitive_file_read and system_config_write. These rules reuse policy.py's
    existing plain re.search matching and do not get the same quote/heredoc awareness as the
    webui-side splitShellSegments() — 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.json won't auto-update to pick these up.
  • New su/pkexec privilege-escalation detection: previously only sudo_usage covered
    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), matching su/pkexec
    only at the start of a command or right after ;/&&/|| (the same shape as the existing
    sudo_usage pattern, (^|;|&&|\|\|)\s*sudo\b), so an echo su-style string or the token "su"
    appearing inside something like subprocess.run(...) isn't misread as an actual escalation.
    Inserted right after sudo_usage.
  • New single-file, non-recursive chmod 777 detection: chmod_world_writable_recursive only
    matched when the target path started with / or ~, and chmod_recursive_generic only
    matched when -R was present — chmod 777 file.txt (a relative path, single file, no
    recursion) fell through both. Added chmod_777_single_file (risk: medium, action: confirm),
    inserted right after chmod_recursive_generic so rule ordering guarantees it only fires when
    neither of the earlier two already matched (a -R call 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/sqlite3 followed by DROP/DELETE/TRUNCATE (SQL) or FLUSHALL/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. Added db_destructive_command
    (risk: high, action: confirm), inserted between history_tampering and
    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 earlier chmod_recursive_generic rather than the new one, and
    chmod 777 ~/.ssh/id_rsa correctly hits chmod_world_writable_recursive); node --test still
    passes 5/5 — not a regression. These three rules reuse policy.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.json likewise
    won't auto-update.
  • New shell-history-read detection: commands reading shell history files (cat ~/.bash_history, cat .history, etc.) and running the bare history builtin (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. Added history_read (risk: low, action: log), covering
    cat/less/more/head/tail/strings reading .bash_history/.zsh_history/
    .python_history/.mysql_history/.psql_history/.node_repl_history/any *.history file, and
    a bare history at the start of a command or right after ;/&&/|| (the destructive
    history -c case is already covered by the earlier history_tampering rule; ordering plus an
    explicit (?!\s*-c\b) exclusion avoids double-firing). Inserted right after history_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_pattern only recognized nc ... -e /bin/sh — real attacker/red-team toolkits
    have several common variants that were a complete blind spot: nc/ncat/netcat using -c
    instead of -e (how some nc variants invoke a command), ncat/netcat — nc's own aliases —
    weren't recognized at all, socat doing a reverse shell via an exec: target (many hardened
    systems ship an nc without -e support, making socat the most common substitute), and a
    reverse shell hand-assembled from a mkfifo named pipe plus nc + 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, keeping risk/action at the
    existing high/block. Verified with 17 positive/negative test cases (the original nc -e,
    /dev/tcp, and sh -i forms had to keep matching — no regression from the change — while
    ordinary network-diagnostic usage like nc -zv, nmap, ncat --ssl had 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 the socket module; 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/archiveOpsDetails and the cc_archive_op SQL custom
    function in webui/lib/audit.js, plus a new /api/drilldown/archive-op/:type endpoint.
  • 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/netdiagOpsDetails and the cc_netdiag_op SQL custom
    function, plus a new /api/drilldown/netdiag-op/:type endpoint.
  • 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 like curl '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).
    Added classifyProcessBackground/procbgOpsStats/procbgOpsDetails and the cc_procbg_op SQL
    custom function, plus a new /api/drilldown/procbg-op/:type endpoint.
  • New "Subagent spawns" home card: same approach as the MCP/Skill call stats, just grouped by
    the subagent_type field in tool_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. Added subagentCallStats/subagentCallBreakdown/subagentCallEvents and a new
    /api/drilldown/subagent-calls endpoint, reusing the same SQL json_extract grouping technique
    already used by mcpCallBreakdown/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, and echo '...' — 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 --test still 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. In webui/lib/audit.js, each group's separate xxxOpsStats()/xxxOpsDetails(type)
    pair was replaced with two generic functions, opsBreakdown(sqlFn)/opsEvents(sqlFn) (all
    seven groups were already the same "classify via a cc_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.js correspondingly replaced the 14 single-category
    /api/drilldown/xxx-op/:type endpoints with 7 /api/drilldown/xxx-ops endpoints (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 :type URL parameter (e.g. GITHUB_OP_TYPES) and the orphaned
    drilldown.githubOp.suffix i18n 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 --test still 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: in python3 -c "\nimport json,sys\n...", the line import json,sys
    inside the quoted argument got read as an invocation of ImageMagick's import screenshot
    command; in git 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
    import from the screenshot CLI list entirely (ImageMagick's import is 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-screenshot invocations 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)。