mcpField
H
hookcheck

hookcheck

本地服务
github.com/CloudCorpRecords安全02 天前更新

关于 hookcheck

hookcheck 扫描本地仓库中的自动执行钩子,涵盖包、编辑器、Agent 和工作流配置。它通过确定性分类和证据帮助维护者检查持久化风险;提供的 README 在完整功能及 MCP 信息之前已截断。

主要功能

  1. 01盘点自动执行入口
  2. 02将钩子分类为安全、风险或未知
  3. 03显示文件、命令、规则和证据

适用场景

  1. 01打开仓库前进行检查
  2. 02安装依赖前审查钩子

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"cloudcorprecords-hookcheck": {
"command": "npx",
"args": [
"-y",
"@reneturcios/hookcheck"
]
}
}
}
启动命令npx -y @reneturcios/hookcheck

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@reneturcios/hookcheck0.1.18
ocighcr.io/cloudcorprecords/hookcheck:0.1.18

工具

2

收录的工具列表 · 更新于 6 天前

参数类型必填说明
pathstring是要扫描的本地目录,例如 /home/user/project

详细介绍

hookcheck

npm version npm downloads MCP Registry

找出在你操作前运行的钩子。一次扫描,覆盖所有自动执行入口,给出一个结论。

快速开始

npx @reneturcios/hookcheck scan ./some-repo

现代供应链攻击不需要你运行任何东西。攻击会通过自动运行的钩子持续存在:npm postinstall 脚本、.claude/settings.json 中的 Claude Code SessionStart 钩子,或设置为打开文件夹时运行的 VS Code 任务。Shai-Hulud npm 蠕虫正是利用了这种模式:植入一个 preinstall 下载器,然后通过 Agent 和编辑器钩子持续存在,从而在清理后仍能存活,并在下次打开时窃取凭据。

hookcheck 会扫描本地仓库中的所有自动执行钩子,对每个钩子进行分类,并输出一个结论:CLEAN、REVIEW 或 COMPROMISED-PATTERN,同时为每项发现列出文件、命令、规则和证据。仅使用确定性规则。核心扫描不使用大模型,也不访问网络。

为什么要开发它

2026 年 10 月的报告描述了 AI Agent 策划攻击,以及一种会自我传播、将数百个软件包植入后门的 npm 蠕虫。其持久化方式并非什么新奇恶意软件,而是每位开发者和每个编码 Agent 都已信任的普通配置文件:package.json 安装脚本、.claude/settings.json 钩子,以及 .vscode/tasks.json 中的 folderOpen 任务。防御者需要一种快速的本地检查方式,让 Agent 能在打开、安装或信任仓库前调用。这就是 hookcheck。

检查内容

类型检查内容
package.jsonpreinstall、install、postinstall、prepare 脚本。根目录中的 package.json 和嵌套工作区中的 package.json 文件(例如 packages/*/package.json、apps/*/package.json,以及根目录下最多 8 层的任意目录;不包括 node_modules)均按相同方式盘点
.claude/settings.json 和 settings.local.jsonSessionStart、PreToolUse 和其他钩子命令。也会发现 GNU stow 布局(claude/settings.json,不带点)以及配置目录下一层的 settings(例如 config/.claude/settings.json)。如果 settings 路径实际是目录,会报告警告,而不会静默跳过
Claude Code 插件钩子插件根目录中的 hooks/hooks.json:可以位于仓库根目录,也可以嵌套在插件目录中(例如 plugins/<name>/hooks/hooks.json),插件根目录距离扫描根目录最多 8 层。该文件包含与 .claude/settings.json 的 hooks 键相同的 events 映射(SessionStart、PreToolUse、PostToolUse、Stop、UserPromptSubmit,以及 Claude Code 的其余事件集),其命令也按完全相同的方式分类。同级的 hooks/hooks-cursor.json 变体在 Cursor 事件名称下包含相同结构的命令对象,也会以相同方式盘点
.vscode/tasks.jsonrunOn 为 folderOpen 的任务,以及任务清单
.husky/每个钩子文件及其完整命令内容
lefthook.yml运行命令
.pre-commit-config.yaml条目命令和远程仓库 URL
.cursor/ 和 .codex/配置命令和文件
MCP 配置.mcp.json、mcp.json、.vscode/mcp.json、.cursor/mcp.json、.windsurf/mcp.json、.codex/mcp.json 中自动启动的服务;每个服务只盘点一次
.github/workflows/run 步骤,作为清单列出,包括块标量正文(`run:
.githooks/钩子文件
上面提到的每种配置都通过一次有界递归遍历发现,而不仅限于扫描根目录:如果某种配置出现在子目录中(例如 packages/foo/.claude/settings.json、sub/.vscode/tasks.json 或 sub/.github/workflows/ci.yml),那么从父目录扫描时也会将其纳入清单,并在发现项中记录其真实相对路径,且不会超出“限制”中说明的发现范围。无论有多少条发现路径指向某个配置文件,该文件都只会被纳入清单一次。

每个钩子的分类:

  • SAFE:匹配已知的安全开发模式,例如 test、lint、build、typecheck、husky 或 lint-staged,且不匹配任何风险规则。

  • RISKY:至少匹配一条风险规则:网络获取、将 curl 的输出通过管道传给解释器、将 base64 解码结果通过管道传给解释器、凭据存储路径或凭据文件(依据内容和路径匹配,绝不会仅因脚本文件名中包含 credentials 一词而匹配)、形似凭据的环境变量名(token、secret、password、api key)或批量访问环境变量、base64 解码或 eval 式混淆、内联代码执行,或远程 URL。普通的单个具名环境变量读取(NODE_ENV、HOME、RUNNER_DEBUG)不会被标记。非读取操作也不会被标记:env -u NAME 会取消设置变量,os.environ.setdefault(...) 会为一个键写入默认值,而 [ -n "$NAME" ] 测试只检查变量是否存在。目标为 shell 脚本的钩子,会根据该脚本的内容分类,而不是因其是 shell 脚本而分类。分类前会剥离注释,因此注释中的文档 URL 不算网络获取;如果获取所用的所有 URL 都是环回地址(localhost 健康检查),则不会被视为网络行为。在脚本文件(JavaScript、Python)中,字符串和正则表达式字面量的内容属于数据,不属于行为:防御性保护代码中的模式列表即使包含以引号括起的 eval( 或 curl | sh,也不会执行这些操作,hookcheck 也不会将其归类为攻击者行为。传给 exec 式调用(execSync("...")、os.system("..."))的字面量按其性质属于代码,仍会被匹配。有两条规则关注的是值的流向,而非同时出现:仅当凭据值可明确追踪到流向外发请求(请求负载、查询字符串、传入网络工具的管道,或与发送操作相邻打印的输出)时,才触发 ENV-EXFIL;当凭据存储内容直接通过管道传入网络发送或直接随网络请求发出时,才触发 CRED-EXFIL。环境变量继承不属于发送:如果环境变量唯一的流动方式是继承,或作为子进程环境传入批量访问的环境变量(env=os.environ、{**os.environ, ...} 展开、dict(os.environ)、os.environ.copy() 及其 JavaScript 形式),则 ENV-EXFIL 还要求流向中存在非环回目标。因此,在 127.0.0.1 上启动自有服务器并将继承的环境变量传给它的测试工具,不会被判定为数据外泄;将相同形式序列化到负载中,或与远程目标搭配,仍会触发规则。ENV-BULK 证据会指出检测到的形式:继承和复制形式会描述为以继承或复制形式批量访问,而不会描述为序列化或转储。将令牌用于身份验证(Authorization 标头、推送到仓库自有主机的 URL 的 userinfo、--token 标记)属于身份验证,不属于数据外泄,绝不会触发这两条规则。身份验证位置按形式识别,而不只看 Authorization 字样:任何 curl -H 标头参数(无论标头名称为何)、单跳脚本文件中键名为身份验证标头名称(Authorization、x-api-key、*-api-key、*-auth-token 等)的任何字典或对象条目,以及标头构造中的任何标头形式条目(赋给 headers 变量的字典、headers= 参数、请求选项对象的 headers 属性)都算身份验证位置。按照同样的形式原则,还会识别其他身份验证位置:curl -u 或 --user 基本身份验证凭据对、git http.extraheader 键所配置的值(无论如何构造,Authorization 标头值都算)、::add-mask:: 注册(在此处打印值本身就是屏蔽惯用法)、凭据交接位置(git credential.helper 键所配置的值,包括该值所指 shell 函数的函数体:git 会将该 helper 回显的用户名和密码行作为身份验证协议读取,因此这种回显属于提交凭据,而非泄露;同一类还包括 core.askPass 值、GIT_ASKPASS 或 SSH_ASKPASS 赋值、传入 git credential 调用的管道输入,以及为其提供输入的 heredoc),以及发送操作本身 URL 中的目标位置:作为完整 URL 路径段、完整 URL 或 URL 源站的凭据(能力型 URL 本身就是凭据:构建钩子 URL、路径中的预览令牌、令牌查询参数)、身份验证类查询参数的值(token、api_key、access_token、auth、secret、signature、key)、curl URL 操数位置中的独立引用,以及 fetch 式调用的第一个目标参数。自 v0.1.18 起,同类位置还包括:将凭据通过重定向写入 ~/.npmrc(npm 客户端自己的身份验证文件),例如规范的 npm publish 步骤 echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc。这属于将凭据交给客户端自身,不会单独算作访问凭据存储;但读取 ~/.npmrc、复制它,或将其内容或令牌发送到其他位置,仍会按完整规则处理。具体由负载测试决定:如果凭据只出现在上述位置,且负载中没有凭据或批量数据,则属于身份验证;如果同一凭据出现在负载中、数据型查询参数中、不完整的 URL 路径段中,或打印输出中,则仍算数据外泄;若能力型 URL 形式同时伴有负载中的凭据数据,也仍会触发规则。污点不会通过本地定义的辅助函数返回值传递,除非该辅助函数本身返回污点数据(其函数体会作为相同文本的一部分进行分析);打印值的长度或计数(len(x)、x.length、${#NAME})属于元数据,不属于泄露值本身;名称若出现在对象键、属性名、赋值目标或参数声明中,则属于绑定,而非对该名称变量的引用。在同一文本中从不含凭据原子的值赋值而来的 shell 变量(例如名为 token 的变量绑定到字面文件路径,并用作 curl 的 --output 目标),或通过命令替换赋值且其中的凭据原子仅位于目标位置的 shell 变量(例如 HTTP 状态码变量),本身不算凭据值。检测网络获取要求命令处于命令位置:只有在 curl 或 wget 是被调用的命令(语句或管道段的开头、sudo 或 env 包装器之后,或传给解释器的命令字符串开头)时,才算网络获取;如果该词仅在安装命令中用作软件包名称(pacman -S curl、apt-get install curl),则不算。在脚本文件中,远程 URL 规则与其他行为规则采用相同的数据视角:仅出现在字符串字面量或注释中的 URL(错误消息、文档链接、API 基础常量)属于数据;传给网络调用(fetch、requests、http.get、urlopen、axios、sendBeacon)的 URL 则是获取目标,仍会触发规则。只有被求值内容本身包含下载或远程引用时,才会触发内联代码执行(DOWNLOAD-EXEC);不下载任何内容的常量内联代码(例如 node -e 版本比较)不会被标记。自 v0.1.17 起,当通过网络获取的内容到达 eval 式调用(eval、Function、exec、vm 的 runIn context 调用)时,即使中间经过变量传递(获取结果绑定到一个变量,其正文文本绑定到第二个变量,再将第二个变量传给 eval),文件级加载器形式也会触发同一规则:发现结果会同时触发 DOWNLOAD-EXEC 和 REMOTE-EVAL,并判定为 COMPROMISED-PATTERN。获取结果仅被记录或作为数据解析的情况,以及对常量或其推导中不存在获取操作的变量进行 eval,都不会触发该规则。

  • UNKNOWN:既不匹配安全模式,也不匹配风险模式。仅凭本地脚本名称无法判断,属于 UNKNOWN,需由人工阅读。钩子运行 node setup.mjs 等本地文件时,hookcheck 会读取该文件;如果文件本身匹配风险规则,就会提高发现的风险等级。对于直接执行脚本(./setup.sh)、node --require ./setup.mjs 和 package.json 脚本链(postinstall: npm run other-script),也会以同样方式跟踪一跳。如果被引用的文件本身又启动了另一个本地脚本,调用链就会超出一跳限制:该发现的等级最低为 UNKNOWN,绝不会是 SAFE;证据会指出第二跳文件,因此最终判定为 REVIEW,需人工阅读。 Verdict rules:

  • CLEAN:每项发现均为 SAFE,或未发现任何钩子。退出代码为 0。如果没有发现任何内容,报告会附注说明没有可供审计的内容,JSON 和面向用户的输出中都会显示此注释,因此空白项目不会被误读为一切正常。

  • REVIEW:至少有一项 UNKNOWN 发现,或某项 RISKY 发现的证据本身较弱(仅有远程 URL、单独下载、仅访问凭据而没有网络行为)。退出代码为 1。

  • COMPROMISED-PATTERN:至少有一项 RISKY 发现具有强证据:直接执行获取或解码的内容(将 curl 的输出通过管道传给解释器、将 base64 解码结果通过管道传给解释器、对获取的内容执行 eval);已验证的数据外泄流程(凭据值流向外发请求,或凭据存储内容通过管道传给网络工具);或在同一项发现中同时存在凭据存储访问和网络行为。这是 Shai-Hulud 持久化攻击模式。在证明安全之前,应将仓库视为不可信。退出代码为 1。按惯例,下载自身二进制文件的正规安装程序(esbuild、puppeteer)在此校准下判定为 REVIEW,而非 COMPROMISED-PATTERN;下载会作为一项带有相关证据的 RISKY 发现报告,但顶层判定不会因此夸大风险。仓库自身的发布自动化也适用同样规则:某一步读取令牌并推送到自己的仓库、发布自己的工作流调度请求,或更新自己的 homebrew tap 时,令牌用于身份验证,判定为 REVIEW,因为凭据值没有流入发往第三方的载荷。同一类别还有一项校准:CI 工作流步骤的唯一严重证据是通过 HTTPS 将 curl 管道传给某个已命名供应商的官方安装程序(安装程序形式的路径,例如 /install 或 /uv/install.sh,或已知安装程序主机,例如 astral.sh 或 sh.rustup.rs),且该项发现中没有凭据、持久化或第二个严重信号,则判定为 REVIEW,并保留该发现的 RISKY 评级。在自动执行钩子入口(安装脚本、Claude 设置或插件钩子、folderOpen 任务)上出现同样的管道、管道指向非安装程序来源,或管道伴有任何其他信号,仍会达到 COMPROMISED-PATTERN。安装程序形式可按确定规则泛化,而不依赖精确列表:路径段或文件名中包含以 token 边界分隔的 install、installer 或 setup(因此 cargo-hawk-installer.sh 和 setup_lts.x 符合,uninstall.sh 则不符合);作为主机名首个标签的安装程序子域名(get、install、installer、setup、sh,因此 get.maestro.mobile.dev 和 install.python-poetry.org 符合);有文档记录的安装程序主机(astral.sh、sh.rustup.rs、pyenv.run);或通过管道传入仓库自身在测试中的不可变提交版本脚本(github.com 或 raw.githubusercontent.com,仓库由 ${{ github.repository }} 指定,引用由 ${{ github.sha }} 指定):不可变引用上的同仓库内容不会增加代码本身以外的信任面,因此不计为严重管道规则中的远程下载。安装操作本身的行为也计入管道形式的伴随信号判定:管道自身的 NET-FETCH 和 REMOTE-URL,以及 WRITE-OUTSIDE(安装程序按定义会写入 $HOME);但如果同一项发现中存在真正独立的严重信号(凭据访问、数据外泄流程、持久化),则仍会打破此校准。此校准还会在判定层按单项发现组合:经过校准的管道不会推动顶层判定,也不能被仓库其他位置无关的严重发现重新触发;每项发现均按自身证据判定。还有两条内容规则可补全此逻辑。对于强证据组合的两部分,搜索模式中的内容均视为模式文本,而非行为:命令运行搜索工具(grep、egrep、fgrep、rg、ag、ack)时,模式参数和文件操作数是在搜索数据,因此其中写出的凭据路径字符串只是引用,不表示访问凭据;模式中的 URL 也绝不会被访问。lint 通过搜索 .npmrc 和 package-lock.json 查找软件包源模式以屏蔽该源,是防御性控制,而非凭据访问加网络行为。同样的引用原则也适用于输出文本:打印出凭据文件名称的消息(lint 自身的错误行 “Could not scan .npmrc and package-lock.json”)只是引用,不表示访问;因此 echo 和 printf 的参数文本对于凭据规则而言也属于数据;消息中的命令替换($(cat ~/.npmrc) 或反引号形式)则是在打印消息时会运行的代码,仍会完整纳入检测。实际读取凭据文件并发送其内容,或搜索 URL 后再获取该 URL,仍会触发完整规则。只有当解释器执行其标准输入时,传入解释器的管道才会触发严重管道规则:适用于裸解释器、单独的连字符或仅带模式标志的情况。如果程序通过其他方式提供(例如解释器模块 python3 -m json.tool、内联 -e 或 -c 程序,或脚本操作数),那么管道流只是被解析的数据,而非要执行的代码;因此,将本地 JSON 响应通过管道传给格式化工具的健康检查并不属于下载并执行的形式;但如果程序本身会执行其标准输入(内联程序调用 exec 或 eval 处理 stdin),则仍会触发规则。 每条发现都会报告文件、钩子 ID、命令文本、分类、规则 ID 和证据。格式错误的 JSON 会报告为警告,不会被悄然忽略。扫描不存在的路径,或扫描文件而非目录时,会清晰地报错,退出码为 2。报告通过具备刷新感知能力的 emit 写入:如果管道报告的读取方提前关闭管道,进程会以 141 退出(即 shell 可见的 SIGPIPE 终止等效状态),而不是报告一份从未送达的报告的判定代码。

限制

由 test/hardening.test.ts 中的测试明确界定:

  • 仅跟踪一跳。hookcheck 会沿着钩子检查它直接调用的本地文件或 package.json 脚本,并对该目标进行分类;不会继续跟踪第二跳。两项范围有限的值流检查会在单个文件或命令内执行:上文所述的数据外传流,以及(自 v0.1.17 起)会标明下载并执行加载器的 fetch 到 eval 流;不会追踪脚本内的一般数据流。如果某个负载由运行时拼接而成,且任何单个文件中都没有出现可识别的模式,就不会标明该负载;运行它的钩子通常仍会归为 UNKNOWN 或 REVIEW,这表示需要人工检查。如果第一跳文件又启动了另一个本地脚本,则这不是通常情况,而是强制规则:发现结果的最低等级为 UNKNOWN,证据会标明第二跳。
  • 基于模式,而非执行。hookcheck 不会运行它发现的钩子。RISKY 表示命令文本符合已知的恶意模式,并不表示观察到负载实施了危害。CLEAN 表示没有匹配已知模式,并不表示仓库已被证明安全。
  • 数据外传检测是在单条发现结果内进行的静态值流检查:凭据值必须明显流入同一命令或引用文件中的发送结构(包括经过一条变量赋值链)。如果某个值经过多个文件转手、在运行时拼接,或由参数中未指明该值的工具发送,就不会将其标记为数据外传;根据其他证据,该发现结果仍会归为 RISKY 或 UNKNOWN。反过来,如果凭据值放在请求正文、名为 data 的查询参数,或 URL 路径中的部分片段里,即使目标是第一方主机,也符合数据外传模式,因为凭据应放在身份验证位置,而不是负载中;如果凭据作为整个 URL 路径片段、整个 URL,或名为 authentication 的查询参数值提供,且正文不含凭据或批量数据,则属于能力 URL 形式的凭据呈现,会被视为身份验证。
  • 引号识别也有边界。在脚本文件中,字符串和正则表达式字面量的内容会被行为规则视为数据;如果负载藏在字符串中,随后被间接拼接并执行(而非通过可识别的 exec 调用),就可能漏检。在 shell 文本中,引号括起的字符串属于命令文本,仍会被检查;但 echo 和 printf 的参数属于输出数据。
  • 远程 pre-commit 仓库和远程 MCP 服务只会被列入清单,不会经过验证。hookcheck 无法查看远程 pre-commit 仓库的内容,因此这些条目会标为 UNKNOWN,并提示检查固定的 rev。远程 MCP 服务 URL 本身就属于 RISKY,因为该服务会自动启动且位于远程。
  • 重复的 JSON 键按后者覆盖前者处理,与 JSON.parse、Node 和 npm 的处理方式完全一致,因此扫描器看到的值与实际运行的值相同。
  • package.json 脚本中的非字符串值(例如数字、对象、null、布尔值,或 postinstall 等受监控生命周期字段中的数组)无法作为命令分类。自 v0.1.18 起,这些值不再被静默跳过:每个值都会按名称发出警告,并作为 UNKNOWN 发现结果列入清单;由于条目存在但内容无法验证,判定等级最低为 REVIEW。
  • 分类时最多读取钩子文件或引用的本地文件中的 200,000 个字符。超出此范围可能藏有负载;设置此上限是为了防止恶意文件导致扫描范围无限扩大。
  • GitHub 工作流中位于块标量内的运行步骤(run: |、run: > 及其裁剪变体)会被解析并按一行命令分类。不带块标量标头的普通多行标量不会合并,且不会解析可复用工作流的内部内容。同一文件中定义的锚点用于运行步骤时,会解析 YAML 锚点、别名和合并键;应用于其他步骤字段的锚点,以及在其他文件中定义的锚点,不会解析。采用 CRLF 换行符的工作流文件会与采用 LF 换行符的文件完全一致地解析。
  • 嵌套发现通过一次有界遍历覆盖所有列入清单的范围:package.json 文件、.claude/settings.json 和 settings.local.json(包括 stow 布局中的 claude/settings.json)、插件的 hooks/hooks.json 和 hooks/hooks-cursor.json 文件、.vscode/tasks.json、.github/workflows/*.yml 和 *.yaml、.husky/ 和 .githooks/ 钩子文件、lefthook 配置(lefthook.yml、lefthook.yaml、.lefthook.yml)、.pre-commit-config.yaml、.cursor/ 和 .codex/ 配置文件,以及 MCP 配置(.mcp.json、mcp.json、.cursor/mcp.json、.windsurf/mcp.json、.codex/mcp.json 和 .vscode/mcp.json)。当项目目录位于扫描根目录下至多 8 层时,该范围即纳入扫描(插件的 hooks 目录位于其插件根目录下一层);遍历顺序按名称排序。每类范围最多检查 200 个嵌套文件;扫描根目录中的文件始终计入。扫描器不会进入 node_modules、.git、点目录或符号链接目录,因此超过深度上限的范围,以及 node_modules 下的任何范围,都不会扫描。不会打开非普通文件(FIFO、套接字、设备文件):扫描器会先检查每个待读文件的 stat 类型,遇到非普通文件时会跳过并发出指明文件名的警告,因为打开 FIFO 会一直阻塞,直到写入端出现,从而令扫描挂起。达到上限时绝不会静默处理:遍历触及深度上限且仍有剩余目录时,报告会发出警告,指出深度上限,并统计紧邻上限以下包含钩子范围文件或配置目录的目录;某类范围达到文件上限时,警告会指出上限及已找到的数量。因任一深度上限、文件上限或不可读文件而中断的扫描都属于部分扫描,警告会明确说明;当已知有范围被遗漏时,绝不会仅凭此类扫描给出 CLEAN。插件架构中仅会将对象形式的 command 列入清单;以裸字符串而非命令对象书写的钩子条目不属于文档规定的架构,不会列入清单。
  • 扫描器尝试列入清单但无法读取的文件或目录(例如权限为 000 的工作流文件)绝不会被静默跳过:报告会发出指明名称的警告;只要项目中仍有任何部分未读取,判定等级最低为 REVIEW,因此部分扫描不会被呈现为完整无误。同样规则也适用于按禁止跟随策略跳过的范围:扫描器绝不会进入符号链接目录;自 v0.1.15 起,每个此类目录都会单独发出警告,指出链接和跳过原因;若扫描跳过了某个范围,判定等级最低为 REVIEW。自 v0.1.16 起,此规则还覆盖直接位于范围路径上的符号链接(.claude、.husky、.pre-commit-config.yaml 及其他列入清单的路径):如果无法解析链接目标,会按名称发出警告,而不会让该范围消失;如果链接可解析,则范围扫描器会像之前一样跟随链接。自 v0.1.18 起,最低判定等级适用于所有无法列入清单的已命名范围,而不只是不可以读取的范围:格式错误的设置文件或清单文件、作为目录的设置路径,以及范围路径上的非普通文件(FIFO、套接字或设备),都会按名称发出警告,并将判定等级最低设为 REVIEW;如果完全无法列出扫描根目录(例如非 root 用户读取权限为 chmod 000 的目录),也会发出警告并判定为 REVIEW,而不会将项目呈现为空且干净。
  • 重复的相同 SAFE 发现结果会合并(v0.1.15):当相同的 SAFE 发现结果(文件、来源、钩子、命令、规则和证据均相同)重复至少 3 次时,报告会以一条汇总记录替代逐条记录,其中包含计数以及首行和末行;JSON 和人类可读输出中都会附上汇总说明,指出合并了多少条发现结果。RISKY 和 UNKNOWN 发现结果绝不会合并。判定结果会在合并前基于完整清单计算,因此判定结果和退出代码不会改变;一个包含约 92,000 个相同无害步骤、大小为 5 MB 的工作流,会输出 1 条汇总记录和约 1 KB 的 JSON,而不是 91,980 条记录和 35 MB 的 JSON。
  • 不会扫描通过 core.hooksPath 设置的自定义 git 钩子目录:hookcheck 会检查标准的 .githooks/ 目录和 .husky/,但不会解析 git 配置或已提交的 .gitconfig 来发现自定义 core.hooksPath 目录。仅保存在自定义 hooksPath 目录中的钩子不属于扫描清单;请在其位于标准位置的目录树中执行扫描,以便审计这些钩子。
  • Agent 配置覆盖范围的边界:目前不会扫描 .gemini/settings.json。模板配置(例如 templates/settings.json.template)不会扫描:它们在复制到实际位置之前不会生效;复制后,扫描器会按其实际文件名扫描该文件。
  • 在大型真实仓库中,UNKNOWN 是最常见的分类(Round 1 验证中,langfuse 和 cli/cli 的发现结果约有 80% 属于此类)。这些分类用于清点,而非表达怀疑:大型项目判定为 REVIEW,意味着已映射自动执行范围,且需要人工分诊;每条发现结果的证据都会准确说明各个钩子运行的内容。之后会逐步分类更多工作流步骤模式;在此之前,hookcheck 不会臆测。

用法

CLI
npx @reneturcios/hookcheck scan
npx @reneturcios/hookcheck scan ./some-repo
npx @reneturcios/hookcheck scan ./some-repo --json
npx @reneturcios/hookcheck explain ./some-repo package.json:postinstall

示例输出:

hookcheck ./some-repo: COMPROMISED-PATTERN
  2 hook(s): 0 SAFE, 2 RISKY, 0 UNKNOWN
  [RISKY] package.json:postinstall (package.json)
      command: node setup.mjs
      rules: NET-FETCH, REMOTE-URL, ENV-BULK, ENV-EXFIL
      evidence: referenced local file setup.mjs is RISKY: NET-FETCH: performs a network fetch
COMPROMISED-PATTERN: at least one risky hook shows strong evidence: fetched or decoded content executed as code, credential values flowing into an outbound send, or credential store access combined with network behavior.
MCP 服务

添加到 MCP 客户端配置中:

{
  "mcpServers": {
    "hookcheck": {
      "command": "npx",
      "args": ["-y", "@reneturcios/hookcheck", "mcp"]
    }
  }
}

工具:

  • scan_hooks(path):完整扫描,提供判定结果、发现项、计数和警告。
  • explain_hook(path, hookId or file):用通俗语言解释一个 hook,并提供相关证据。
Docker
docker run -i ghcr.io/cloudcorprecords/hookcheck mcp
docker run --rm -v "$PWD:/repo" ghcr.io/cloudcorprecords/hookcheck scan /repo

工具集

hookcheck 是同一作者开发的一系列小型、单一用途的开发者工具之一。这些工具均以相同方式构建:一个二进制文件,同时提供 CLI 和 MCP 服务;检查结果确定且附有证据,不会编造答案。

  • greenlight:开源贡献的预检工具:这个 issue 真的可以合并吗?
  • shipcheck:验证某个版本是否确实在所有渠道发布,否则就不要发布。

开发

npm install
npm run build
npm test

许可证

MIT


由 Rene Turcios 开发——一位氛围编程者 / 原生支持 Agent 的开发者,在 SF 公开构建项目。关注 @CloudCorpRecords,获取下一个工具的动态。 这一系列工具包括:greenlight · injectcheck · actionpin · shipcheck——均发布在 npm 上,名称位于 @reneturcios/ 下,并收录于官方 MCP Registry。

如果 hookcheck 为你节省了时间,请为这个仓库点亮星标,帮助其他开发者发现它。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。