Spec Driven Development
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
作者提供的说明(英文原文)
Persistence hook auditor: scan repos for auto-exec hooks, flag Shai-Hulud patterns.
这三项永远不会合并成一个分数:意见、测量和人气是不同的东西。
我们还没有为它写通俗的介绍。下面是我们从注册中心读取到的事实。
选择你使用的 AI 应用,然后复制设置。
你需要
你用的是哪个应用?
在上方选择你的应用。我们会记住它,下次按钮会显示“安装到 …”。
打开 Claude Desktop → Settings → Developer → Edit Config,把下面的内容粘贴到 claude_desktop_config.json
{
"mcpServers": {
"hookcheck": {
"command": "npx",
"args": [
"-y",
"@reneturcios/hookcheck",
"mcp"
]
}
}
}在终端中运行这条命令
claude mcp add hookcheck -- npx -y @reneturcios/hookcheck mcp粘贴到项目中的 .cursor/mcp.json,或 ~/.cursor/mcp.json
{
"mcpServers": {
"hookcheck": {
"command": "npx",
"args": [
"-y",
"@reneturcios/hookcheck",
"mcp"
]
}
}
}浏览器会先询问是否打开应用,然后应用会显示设置并请你确认。密钥的值会留空,由你自己填写。
粘贴到项目中的 .vscode/mcp.json
{
"servers": {
"hookcheck": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@reneturcios/hookcheck",
"mcp"
]
}
}
}浏览器会先询问是否打开应用,然后应用会显示设置并请你确认。密钥的值会留空,由你自己填写。
这个服务器在你的电脑上运行,而 ChatGPT 只能连接互联网上的服务器。请选择其他应用。
你的选择通过 Cookie 保存在这台设备上,无需账户。
这些设置是根据作者在注册中心的条目生成的。如果 README 的写法不同,请以 README 为准。
它在你的电脑上运行;安装后,工具列表会显示在你的 AI 应用中。
我们检查的是任何人都能看到的信息;我们没有阅读代码。不适用的检查项不计入总数。 · 检查于 2026年11月6日
注册名称已通过 GitHub 账户 CloudCorpRecords 验证,该账户拥有此仓库
我们还没有从 GitHub 读取这个仓库
我们还没有从 GitHub 读取这个仓库
它在你的电脑上运行,没有互联网端点
在你的电脑上运行,改用本地设置
它在你的电脑上运行;我们只能读取互联网端点的工具列表
与注册中心里这个分类中所有活跃的服务器比较。只有当发布者拥有该仓库时,星标数才计入。
它在 1 项适用的基础检查中通过了 1 项。我们没有阅读代码。在添加密钥或令牌之前,请先看看这个服务器要求哪些权限,并使用只具备所需权限的密钥。
注册中心没有列出必需的密钥,不过有些服务器会在首次使用时要求你登录。
它在你的电脑上运行。你的 AI 应用会在需要时启动它,数据不会经过作者的服务器(除非它自己调用在线服务)。
GitHub 仓库最后更新于–,最新版本(0.1.18)于14天前进入注册中心。
支持 MCP 的应用,例如 Claude Desktop、Claude Code、Cursor 和 VS Code。你的电脑上还需要有 Node.js, Docker。
同一分类中有:Spec Driven Development, Remote Desktop Commander, AI Deprecations
同一分类中其他作者的服务器,按星标数排序。
如何看卡片:★ GitHub 星标i三大支柱:评价 · 检查 · 使用情况i更新 = 最新版本发布到注册中心的时间i
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Remote Desktop Commander: use files, terminal and processes on your own computers from any AI app.
AI model shutdown dates, replacements, change feed and webhook alerts for 8 providers.
按原文从作者的仓库复制。PopNx MCP 没有审查或背书其内容。HTML、图片和脚本已被移除。链接会离开本站。
Find the hooks that run before you do. One scan, every auto-exec surface, one verdict.
npx @reneturcios/hookcheck scan ./some-repo
Modern supply chain attacks do not need you to run anything. They persist through hooks that run automatically: an npm postinstall script, a Claude Code SessionStart hook in .claude/settings.json, a VS Code task set to run on folder open. The Shai-Hulud npm worm used exactly this pattern, planting a preinstall dropper and then persisting through agent and editor hooks so it survived cleanup and stole credentials on the next open.
hookcheck scans a local repo for every auto-exec hook, classifies each one, and prints a single verdict: CLEAN, REVIEW, or COMPROMISED-PATTERN, with the file, command, rules, and evidence for every finding. Deterministic only. No LLM, no network in the core scan.
Reports in October 2026 described AI agents orchestrating attacks and a self-propagating npm worm that backdoored hundreds of packages. Its persistence layer was not exotic malware. It was ordinary config files that every developer and every coding agent already trusts: package.json install scripts, .claude/settings.json hooks, and .vscode/tasks.json folderOpen tasks. Defenders needed a fast local check that an agent itself can call before opening, installing, or trusting a repo. That is hookcheck.
| surface | what it looks for | |
|---|---|---|
package.json | preinstall, install, postinstall, prepare scripts. The root package.json and nested workspace package.json files (for example packages/*/package.json, apps/*/package.json, any directory up to 8 levels below the root, node_modules excluded) are inventoried the same way | |
.claude/settings.json and settings.local.json | SessionStart, PreToolUse, and other hook commands. Also discovered: the GNU stow layout (claude/settings.json, no dot) and settings nested one level under a config dir (for example config/.claude/settings.json). A settings path that exists as a directory is reported as a warning, not silently skipped | |
| Claude Code plugin hooks | hooks/hooks.json in a plugin root: at the repo root, or nested under plugin directories (for example plugins/ /hooks/hooks.json), with the plugin root at most 8 levels below the scan root. The file carries the same events map as the hooks key of .claude/settings.json (SessionStart, PreToolUse, PostToolUse, Stop, UserPromptSubmit, and the rest of the Claude Code event set) and its commands are classified exactly the same way. The sibling hooks/hooks-cursor.json variant carries the same command object shape under Cursor event names and is inventoried the same way | |
.vscode/tasks.json | tasks with runOn folderOpen, plus task inventory | |
.husky/ | every hook file and its full command content | |
lefthook.yml | run commands | |
.pre-commit-config.yaml | entry commands and remote repo URLs | |
.cursor/ and .codex/ | config commands and files | |
| MCP configs | .mcp.json, mcp.json, .vscode/mcp.json, .cursor/mcp.json, .windsurf/mcp.json, .codex/mcp.json servers that autostart, each inventoried exactly once | |
.github/workflows/ | run steps, as inventory, including block scalar bodies (`run: | and run: >, with chomping variants). Every workflow finding carries the line number of its run:` key |
.githooks/ | hook files |
Every surface above is discovered by one bounded recursive walk, not only at the scan root: a surface that appears inside a subdirectory (for example packages/foo/.claude/settings.json, sub/.vscode/tasks.json, or sub/.github/workflows/ci.yml) is inventoried from a parent scan with its real relative path in the finding, within the discovery bound documented under Limits. A surface file is inventoried exactly once however many discovery paths reach it.
Classification per hook:
env -u NAME unsets a variable, os.environ.setdefault(...) writes one key with a default, and a [ -n "$NAME" ] test checks existence. A hook whose target is a shell script is classified by that script's contents, not by the fact that it is a shell script. Comments are stripped before classification, so a docs URL inside a comment is not a fetch, and a fetch whose every URL is loopback (a localhost health check) is not treated as network behavior. In script files (JavaScript, Python), the contents of string and regex literals are data, not behavior: a defensive guard whose pattern list quotes eval( or curl | sh performs neither, and hookcheck does not classify it as an attacker. Literals handed to exec style calls (execSync("..."), os.system("...")) are code by construction and are still matched. Two rules are about value flow, not co-occurrence: ENV-EXFIL fires only when a credential value demonstrably flows into an outbound send (a request payload, a query string, a pipe into a network tool, or printed output next to a send), and CRED-EXFIL when credential store contents are piped or posted straight into a network send. Environment inheritance is not a send: when the only environment movement is inheritance or bulk access passed as a subprocess environment (env=os.environ, a {**os.environ, ...} spread, dict(os.environ), os.environ.copy(), and their JavaScript forms), ENV-EXFIL additionally requires a non-loopback destination in the flow, so a test harness that starts its own server on 127.0.0.1 and hands it the inherited environment does not verdict as exfiltration; the same forms serialized into a payload, or paired with a remote destination, still fire. The ENV-BULK evidence names the form it saw: inheritance and copy forms are described as bulk access in inheritance or copy form, not as a serialization or dump. A token used as authentication (an Authorization header, the userinfo of a push URL to the repo's own host, a --token flag) is authentication, not exfiltration, and never fires either rule. Authentication positions are recognized by shape, not only by the Authorization spelling: any curl -H header argument whatever its name, any dict or object entry whose key is an authentication header name (Authorization, x-api-key, *-api-key, *-auth-token, and friends) in a one hop script file, and any header shaped entry inside a headers construction (a dict assigned to a headers variable, a headers= argument, a headers property of a request options object) all count as authentication positions. Further authentication positions are recognized by the same shape principle: a curl -u or --user basic auth pair, the value configured for a git http.extraheader key (an Authorization header value however it was built), an ::add-mask:: registration (printing a value there is the masking idiom itself), a credential hand-off position (the value configured for a git credential.helper key, including the body of a shell function the helper value names: git reads the helper's echoed username and password lines as its authentication protocol, so that echo is presentation, not disclosure; the same family covers a core.askPass value, a GIT_ASKPASS or SSH_ASKPASS assignment, input piped into a git credential invocation, and a heredoc feeding one), and destination positions in the URL of a send itself: a credential that is an entire URL path segment, the whole URL, or the URL origin (capability URLs are credentials by design: a build hook URL, a preview token in the path, a token query parameter), the value of an authentication named query parameter (token, api_key, access_token, auth, secret, signature, key), a bare reference in the curl URL operand position, and the destination first argument of a fetch style call. One more authentication position in the same family (since v0.1.18): a credential written to ~/.npmrc, the npm client's own auth file, as the target of a redirection (the canonical npm publish step, echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc) is presented to its own client and does not count as credential store access on its own; reading ~/.npmrc, copying it, or posting its contents or the token anywhere else still counts at full strength. The body test decides: a credential presented in one of those positions with no credential or bulk data in the body is authentication, while the same credential in a body, in a data named query parameter, in a partial URL path segment, or in printed output still counts as exfiltration, and a capability URL shape plus credential data in the body still fires. Taint does not pass through a locally defined helper function's return value unless the helper itself returns tainted data (its body is analyzed as part of the same text), a printed length or count of a value (len(x), x.length, ${#NAME}) is metadata rather than a disclosure of the value, and a name occurrence that is an object key, a property name, an assignment target, or a parameter declaration is a binding, not a reference to the variable of that name. A shell variable assigned in the same text from a value containing no credential atom (a token named response variable bound to a literal file path, used as a curl --output sink), or bound via a command substitution whose only credential atoms sit in destination positions (an HTTP status code variable), is not itself a credential value. Fetch detection requires command position: curl or wget counts as a network fetch only when it is the invoked command (the head of a statement or pipeline segment, after sudo or env wrappers, or the head of a command string handed to an interpreter), never when the word merely names a package in an install line (pacman -S curl, apt-get install curl). In script files the remote URL rule follows the same data view as the other behavior rules: a URL that appears only inside a string literal or a comment (an error message, a documentation link, an API base constant) is data, while a URL handed to a network call (fetch, requests, http.get, urlopen, axios, sendBeacon) is a fetch target and still fires. Inline code execution (DOWNLOAD-EXEC) fires only when the evaluated content itself carries a download or remote reference; constant inline code such as a node -e version comparison downloads nothing and is not flagged. Since v0.1.17 the same rule also fires on the file level loader shape: when content fetched over the network reaches an eval style call (eval, Function, exec, a vm runIn context call), directly or through variables (a fetch result bound to one variable, its body text bound to a second, the second handed to eval), the finding fires DOWNLOAD-EXEC together with REMOTE-EVAL and verdicts COMPROMISED-PATTERN. A fetch whose result is only logged or parsed as data, and an eval of a constant or of a variable with no fetch in its derivation, do not fire只显示了开头部分;其余内容请在仓库中阅读