NayanRoy14/sealpin

GitHub: NayanRoy14/sealpin

一款针对 MCP server 的供应链与 prompt 注入扫描器,通过扫描、锁定 manifest 和运行时代理拦截,帮助开发者发现并防御 AI agent 所信任 MCP server 中的隐藏指令与凭据泄露风险。

Stars: 0 | Forks: 0

# sealpin [![ci](https://static.pigsec.cn/wp-content/uploads/repos/cas/99/993938d8ce5e902ccfb9d6747725c320d855dea3235ed9a304cedf0d94c9321f.svg)](https://github.com/NayanRoy14/sealpin/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/sealpin.svg)](https://www.npmjs.com/package/sealpin) **为你的 AI agent 所信任的 MCP server 提供类似 `npm audit` 的功能。** sealpin 是一个针对 [Model Context Protocol](https://modelcontextprotocol.io) server 的供应链和 prompt 注入扫描器。你只需将它指向你的 MCP 配置,它就会告诉你哪些 server 会悄悄读取你的 SSH 密钥,哪些在你批准后更改了它们的 tool 定义,以及哪些在 tool 描述中隐藏了给模型的指令。 ![sealpin 扫描发现了被投毒的 tool 描述、过于宽泛的文件系统根目录,以及明文 token](https://static.pigsec.cn/wp-content/uploads/repos/cas/ea/eac466f24a8e3214f287f74d543e5f993e3fb5a078a6fc3b624480262c86447b.svg) ## 为什么需要 当你向 `claude_desktop_config.json` 或 `.mcp.json` 添加一个 server 时,你实际上是在执行任意代码,将你的环境变量交给它,并允许它在每次 package 更新时,在无需重新审查的情况下,将文本作为受信任的 tool 文档注入到你模型的上下文中。这里没有签名,没有权限清单,也没有标准的方法来察觉某个 server 的 tool 定义在你批准后发生了改变。 现有的工具(`npm audit`、Snyk)完全看不到 prompt 层面的攻击:对它们来说,tool 描述只是一个字符串。而这个空白正是 sealpin 所覆盖的领域。 ## 安装 ``` npm install -g sealpin # 或者无需安装直接运行: npx sealpin scan ``` ## 快速开始 ``` # 1. 查看你所信任的内容,并仅针对 config 获取 findings sealpin scan # 2. 通过在 sandbox 中运行每个 server 来提取 live tool manifests,并扫描它们 sealpin scan --probe # 3. Pin 当前的 tool manifests sealpin lock --probe # 4. 之后 — 自你批准以来有什么变化吗? sealpin verify --probe # exit 2 if a manifest drifted sealpin diff --probe # show exactly what changed ``` ## Lockfile —— 该工具的核心价值 Linter 你只运行一次;而 lockfile 会永远存在于你的 repo 中。面对 **rug pull**(一个在安装时表现正常,却在后续版本中推送恶意 tool 描述的 server),`sealpin.json` 是唯一的防线,它让你不必在每次更新时都手动重新审计每一个 server。 ``` sealpin lock canonically hash every server's full tool manifest (names, descriptions, JSON schemas, annotations) and pin it sealpin verify re-hash the current manifests and diff against the lock; any drift is surfaced and exits non-zero sealpin diff human-readable changeset of what actually changed ``` 标准化处理是与顺序无关且对空白符进行规范化的,因此重新排序 server 的 tool 或重排描述的换行不会被识别为 drift —— 只有当模型被告知的真实内容发生变化时才会被记录。 ## 运行时强制执行 —— `sealpin proxy` 上面提到的所有内容都是一个*扫描器* —— 它只负责观察。`sealpin proxy` 是一个*控制平面* —— 它会**在调用时进行强制执行**。它作为透明的 stdio proxy 运行在你的 MCP client 和 server 之间,负责调解每一条 JSON-RPC 消息: ``` // in your MCP client config, wrap the server: { "command": "sealpin", "args": ["proxy", "--policy", "sealpin.policy.json", "--", "npx", "-y", "@scope/server"] } ``` 在每次执行 `tools/call` 时,它会评估策略并在 **server 察觉到之前阻止调用**(模型会收到一个策略错误);在每次执行 `tools/list` 时,它可以**剥离那些定义与 `sealpin.json` 发生 drift 的 tool**(针对运行时 rug pull 的防御);并且它会记录审计每一个决定。 ``` // sealpin.policy.json { "version": 1, "default": { "denyTools": ["delete_file"], "denyArgumentPatterns": [{ "arg": "path", "pattern": "[.]ssh|[.]aws|[.]env" }], "blockOnDrift": true } } ``` ``` [sealpin:demo] tool-call read_file DENY — argument "path" matches denied pattern /[.]ssh|[.]aws|[.]env/ [sealpin:demo] blocked delete_file — tool "delete_file" is denied by policy [sealpin:demo] tool-call read_file ALLOW ``` Flags:`--policy `、`--lock `(drift 来源,默认为 `sealpin.json`)、`--server `、`--audit `(JSONL)、`--dry-run`(只记录决定但不阻止)。跨会话的数据流污染分析(阻止来自不受信任内容 tool 的数据到达 exfil tool)是顺理成章的下一层功能,目前尚未构建。 ## 检测内容 运行 `sealpin rules` 获取实时列表,或运行 `sealpin explain ` 查看任何单一规则。 | 规则 | 严重性 | 攻击 | 检测内容 | |------|----------|--------|---------| | `MCP-P001` | critical | A1 Tool poisoning | 那些指示*模型*(读取凭据、窃取数据、忽略之前的指令)而不是用于记录 tool 的描述 | | `MCP-P002` | high | A4 Hidden characters | 零宽字符、双向覆盖字符和 Unicode 标签字符 —— 审查者不可见,但会被模型 tokenize | | `MCP-P003` | medium | A4 Hidden characters | 在控制台中隐藏/覆盖文本的 ANSI 转义序列 | | `MCP-P004` | medium | A4 Hidden characters | HTML 注释(在渲染的 markdown 中隐藏,但会被模型读取) | | `MCP-P005` | low | A4 Hidden characters | 在人类可读的描述中出现的冗长且不透明的 base64 blob | | `MCP-P006` | high | A3 Tool shadowing | 在同一个上下文窗口中,跨 server 共享名称的 tool | | `MCP-C001` | high | A5 Over-broad capability | 根目录为 `/`、驱动器根目录或 `$HOME` 的文件系统 server | | `MCP-C002` | high | A5 Over-broad capability | 没有可见命令允许列表的 Shell/command server | | `MCP-C003` | medium | A6 Secret exfiltration | 在配置 `env` 块中以明文形式存储的活跃凭据 | | `MCP-S001` | high | A8 Supply chain | 与流行 package 编辑距离为 1 的 package 名称 —— 极有可能是 typosquat(抢注) | | `MCP-S002` | high | A8 Supply chain | `preinstall`/`install`/`postinstall` 脚本(在 `npm install` 时运行) | | `MCP-S003` | critical | A7 Command injection | 带有插值命令字符串的 `child_process` exec/spawn | | `MCP-S004` | high | A6 Secret exfiltration | 捕获了整个 `process.env`(被序列化/扩展/传递),而不是特定 key | | `MCP-S005` | medium | A6 Secret exfiltration | 传递给网络调用的硬编码外部 URL | | `MCP-S006` | high | A7 Command injection | `eval` / `new Function` / 动态 `require`/`import` | | `MCP-X001` | high | A1/A6 composition | **Lethal trifecta(致命三要素)** —— 私有数据 + 不受信任的内容 + exfil 能力共同加载在同一个上下文中 | | `MCP-X002` | critical | A7 composition | 不受信任内容的 server + 在同一上下文中的命令执行能力 | | `MCP-X003` | high | A9 Confused deputy | 一个 server 既摄取不受信任的内容,又持有已存储的凭据 | `MCP-X*` 规则是**跨 server 的**:它们推理在同一个 agent 上下文中加载的 server *组合*,能够捕捉到任何单一 server 检查都无法察觉的攻击路径(一个文件系统 server + 一个 web-fetch server + 一个出站通道 = 一条窃取路径,尽管各自单独看都没问题)。它们仅依赖配置运行 —— 不需要 manifest。 `sealpin scan --graph` 将 server 及其组合路径渲染为 mermaid **攻击图** —— 节点按角色着色,危险路径会被高亮显示: ``` flowchart LR subgraph ctx["one agent context"] web["web-search
untrusted · egress"]:::untrusted fs["filesystem /
fs · secrets"]:::private notify["notifier
messaging"]:::exfil end web -. "1 · injection" .-> fs fs == "2 · exfiltrate" ==> notify classDef untrusted fill:#7c2d12,stroke:#f97316,color:#fff classDef private fill:#7f1d1d,stroke:#ef4444,color:#fff classDef exfil fill:#4c1d95,stroke:#a855f7,color:#fff ``` `MCP-S*` 源代码规则(仅限 Node/TS)需要 server 的源代码 —— 请传入 `--source-dir` 或从本地路径运行 server(自动检测)。`MCP-S001`(typosquat)仅凭 package 名称即可生效,无需源代码。 每一项发现都包含一个 **confidence**(置信度)级别,更重要的是包含一个 **rationale**(基本原理) —— 即*为什么*,而不仅仅是*是什么*。扫描结果永远不会完整回显被检测到的 secret。 ## 输出与 CI ``` sealpin scan --json # machine-readable sealpin scan --sarif > sealpin.sarif # SARIF 2.1.0 for GitHub code scanning sealpin scan --severity high # hide findings below a severity sealpin scan --fail-on critical # exit 1 only on critical (default: high) ``` **退出码:** `0` 表示干净 · `1` 表示发现达到或超过 `--fail-on` · `2` 表示检测到 drift · `3` 表示扫描错误。这使得 `scan` 和 `verify` 都可以用作 CI 门禁。在 [`examples/github-action.yml`](examples/github-action.yml) 中提供了一个 GitHub Action 包装器。 ## Manifests 读取 server 的 tool manifest 最终需要与该 server 进行通信,这意味着要运行第三方代码。仅配置规则(`MCP-C001`–`C003`)在运行 `sealpin scan` 时**完全不需要 manifest**;prompt 层规则和 lockfile 需要 manifest,你可以通过以下两种方式之一获取: **`--manifest-dir `(静态,默认)。** 一个存放 `.json` 文件的目录,每个文件都符合 tool-manifest 的结构。不会执行任何操作。 **`--probe`(实时,可选择开启)。** sealpin 会通过 MCP 握手运行每个 server,以读取其真实的 `tools/list`,这一切都在你的平台上可用的最强沙箱中进行。安全不变量包括: - **绝不运行安装脚本。** 仅执行 server 自身的启动命令 —— sealpin 永远不会运行 `npm install`。 - **净化的环境。** Server 会获得一个操作允许列表(如 `PATH` 等),外加其自身声明的 `env` —— 你无关的 shell secret 永远不会被继承。 - **隔离的 cwd。** 使用一个全新的空 temp 目录,而不是你的项目目录。 - **在可用的情况下进行 OS 隔离。** 在 Linux(bubblewrap/firejail)或 macOS(`sandbox-exec`)上,网络和文件系统会被限制。在没有沙箱的平台上(尤其是 Windows),探测仅限于*进程级别*,并会打印网络未被阻止的警告。使用 `--require-sandbox` 可以在没有网络隔离的情况下直接报错,而不是继续探测。 - **有界限的。** 具有严格的 `--probe-timeout` 和输出字节上限;进程树总是会被终止,temp 目录也会被移除。 因为 `--probe` 会执行代码,所以它永远不会是默认选项。`--manifest-dir` 和仅配置扫描完全不需要执行任何代码。 ## 自动发现 `sealpin scan` 会自动从以下位置发现 server: - **Claude Desktop** —— `claude_desktop_config.json`(特定于平台的位置) - **Claude Code** —— 项目中的 `.mcp.json` 以及用户的 `~/.claude.json` - **Cursor** —— 项目中的 `.cursor/mcp.json` 以及全局的 `~/.cursor/mcp.json` 或者使用 `--config ` 指定单个文件。 ## 开发 ``` npm install npm run typecheck npm test # vitest npm run build # → dist/ npm run dev -- scan --config test/fixtures/scan/config.json --manifest-dir test/fixtures/scan/manifests # 从真实的 scan output 重新生成 README demo image (assets/demo.svg): npm run demo # 或者在 VHS toolchain 可用时录制动画 GIF: vhs assets/demo.tape ``` ## 安全模型 - **`discover/`** 负责读取配置文件。处理不受信任的输入;在使用前,所有内容都会通过 [zod](https://zod.dev) 进行验证。 - **`probe/`** 是唯一执行第三方代码的组件,并且仅在 `--probe` 下运行。它会在可用情况下的 OS 沙箱中运行 server 的启动命令(绝不是安装脚本),并配备净化的环境、隔离的 temp cwd、严格的超时限制和输出字节上限。它读取的每一个 tool 在使用前都会经过 zod 验证。 - **`resolve/`** 负责从本地文件系统读取 `MCP-S*` 规则所需的 server 源代码。它永远只*读取*文件(排除 `node_modules`,且限制数量/大小) —— 不会执行任何内容。Registry/tarball 解析器(下载 + 解压,同样绝不执行)是未来的工作。 - **`rules/`** 完全基于已解析的 manifest、config 和源代码数据运行 —— 通过 `@babel/parser` 进行 AST 分析,没有代码评估。没有代码执行。 ## License MIT
标签:MCP, MITM代理, 大模型安全, 文档安全, 暗色界面, 自动化攻击, 静态扫描