@aimarket/warden 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +155 -3
- package/README-es.md +82 -11
- package/README-fr.md +82 -11
- package/README-ru.md +82 -11
- package/README-zh.md +79 -10
- package/README.md +91 -14
- package/dist/glob.d.ts +81 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +180 -0
- package/dist/glob.js.map +1 -0
- package/dist/index.d.ts +42 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +84 -29
- package/dist/index.js.map +1 -1
- package/dist/mcp-rpc.d.ts +40 -0
- package/dist/mcp-rpc.d.ts.map +1 -0
- package/dist/mcp-rpc.js +117 -0
- package/dist/mcp-rpc.js.map +1 -0
- package/dist/mcp-server.d.ts +10 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +89 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp-tools.d.ts +42 -0
- package/dist/mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools.js +652 -0
- package/dist/mcp-tools.js.map +1 -0
- package/dist/origin.d.ts.map +1 -1
- package/dist/origin.js +7 -2
- package/dist/origin.js.map +1 -1
- package/dist/pinning.d.ts +18 -0
- package/dist/pinning.d.ts.map +1 -1
- package/dist/pinning.js +59 -11
- package/dist/pinning.js.map +1 -1
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +10 -7
- package/dist/sandbox.js.map +1 -1
- package/dist/sanitize.d.ts +38 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +53 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/static-scan.d.ts +46 -2
- package/dist/static-scan.d.ts.map +1 -1
- package/dist/static-scan.js +358 -34
- package/dist/static-scan.js.map +1 -1
- package/dist/threat-feed.d.ts +3 -0
- package/dist/threat-feed.d.ts.map +1 -1
- package/dist/threat-feed.js +118 -29
- package/dist/threat-feed.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -4
package/README-zh.md
CHANGED
|
@@ -1,32 +1,96 @@
|
|
|
1
|
-
# WARDEN — MCP
|
|
1
|
+
# WARDEN — MCP 服务器
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.alexar76/warden -->
|
|
2
4
|
|
|
3
5
|
<!-- aicom-readme-badges -->
|
|
4
6
|
<p align="center">
|
|
5
7
|
<a href="https://github.com/alexar76/warden/actions/workflows/ci.yml"><img src="docs/badges/ci.svg" alt="CI" /></a>
|
|
6
|
-
<a href="https://
|
|
8
|
+
<a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
|
|
9
|
+
<a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm 版本" /></a>
|
|
7
10
|
<img src="docs/badges/deps.svg" alt="零运行时依赖" />
|
|
8
|
-
<img src="docs/badges/tests.svg" alt="
|
|
11
|
+
<img src="docs/badges/tests.svg" alt="166 项测试通过" />
|
|
9
12
|
<img src="docs/badges/node.svg" alt="Node >= 20" />
|
|
10
13
|
<a href="LICENSE"><img src="docs/badges/license.svg" alt="许可证:MIT" /></a>
|
|
11
14
|
</p>
|
|
12
15
|
<!-- /aicom-readme-badges -->
|
|
13
16
|
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="https://warden.modelmarket.dev/">
|
|
19
|
+
<img src="docs/screenshots/readme/hero-3d.png" alt="WARDEN — 3D 门链:tools/list 经 static-scan、threat-feed、origin、pinning 到可记录的裁定" width="100%" />
|
|
20
|
+
</a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
|
|
14
24
|
> 🌐 [English](README.md) · [Русский](README-ru.md) · [Español](README-es.md) · [Français](README-fr.md) · **中文** · [术语表](https://github.com/alexar76/aicom/blob/main/docs/localization-glossary.md)
|
|
15
25
|
|
|
26
|
+
**一个 MCP 服务器。针对已广告工具定义的安全防火墙。附带库。**
|
|
27
|
+
|
|
28
|
+
传输:**stdio**(`npx -y @aimarket/warden` / `node dist/mcp-server.js`)。兼容主机:Claude Desktop、
|
|
29
|
+
Cursor、Glama,以及任何支持 stdio 的 MCP 客户端。不需要密钥。
|
|
30
|
+
|
|
31
|
+
| | |
|
|
32
|
+
|------|----------|
|
|
33
|
+
| MCP 入口(stdio) | `warden-mcp` → [`src/mcp-server.ts`](src/mcp-server.ts) |
|
|
34
|
+
| 工具 | `vet_mcp_server`、`static_scan_tools`、`classify_sensitive_tools`、`check_egress_url`、`canonicalize_json`、`list_scan_rules` |
|
|
35
|
+
| 库 | `import { Warden } from "@aimarket/warden"` |
|
|
36
|
+
| Glama / Docker(stdio) | [`Dockerfile`](Dockerfile)、[`glama.json`](glama.json) |
|
|
37
|
+
| Official MCP Registry | [`server.json`](server.json) → `io.github.alexar76/warden` |
|
|
38
|
+
| Smithery | [`smithery.yaml`](smithery.yaml) |
|
|
39
|
+
|
|
16
40
|
MCP 服务器自己告诉你的智能体,它的工具是做什么的。智能体就信了——而这句话正是攻击面。工具描述就是第三方直接
|
|
17
41
|
投递进模型上下文的提示词文本;而一个名叫 `api_key` 的 schema 字段,就是以 API 形式写出来的索要密钥的请求。
|
|
18
42
|
|
|
19
43
|
WARDEN 在**该服务器的任何工具到达模型之前**审查它,并返回一份可以留档的裁定:允许/阻止、0..1 的评分、导致该
|
|
20
44
|
评分的各项发现、按工具的划分,以及当时生效的确切规则表。
|
|
21
45
|
|
|
46
|
+
**零 npm 运行时依赖。** 库本身唯一的 import 是 `node:crypto`。stdio MCP 服务器会再用到其他 `node:`
|
|
47
|
+
内建模块(`fs`、`path`、`process`),仍然不拉任何包。它就是 [ARGUS](https://github.com/alexar76/argus)
|
|
48
|
+
里的那个防火墙,被单独抽出来,好让你把它放在自己的 MCP 宿主前面,而不必换用一个智能体。
|
|
49
|
+
|
|
50
|
+
## 作为 MCP 服务器运行(stdio)
|
|
51
|
+
|
|
22
52
|
```bash
|
|
23
|
-
|
|
53
|
+
npx -y @aimarket/warden
|
|
54
|
+
npm run build && node dist/mcp-server.js
|
|
24
55
|
```
|
|
25
56
|
|
|
26
|
-
|
|
27
|
-
里的那个防火墙,被单独抽出来,好让你把它放在自己的 MCP 宿主前面,而不必换用一个智能体。
|
|
57
|
+
Claude Desktop / Cursor(`mcpServers`):
|
|
28
58
|
|
|
29
|
-
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"warden": {
|
|
63
|
+
"command": "npx",
|
|
64
|
+
"args": ["-y", "@aimarket/warden"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
进程**不会**启动、代理或沙箱化另一个 MCP 服务器:你传入 `tools/list` 转储,得到一份裁决。
|
|
71
|
+
|
|
72
|
+
| 工具 | 何时使用 |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `vet_mcp_server` | 完整门控链 |
|
|
75
|
+
| `static_scan_tools` | 仅静态扫描 |
|
|
76
|
+
| `classify_sensitive_tools` | 运营方 glob 分类 |
|
|
77
|
+
| `check_egress_url` | 主机 allowlist(空列表拒绝全部) |
|
|
78
|
+
| `canonicalize_json` | RFC 8785 字节 |
|
|
79
|
+
| `list_scan_rules` | 已发布的规则表 |
|
|
80
|
+
|
|
81
|
+
### 发布到 Glama
|
|
82
|
+
|
|
83
|
+
列表页:**[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)**
|
|
84
|
+
|
|
85
|
+
与 [ARGUS](https://github.com/alexar76/argus)、[aimarket-mcp](https://github.com/alexar76/aimarket-mcp)
|
|
86
|
+
相同:[`glama.json`](glama.json) + [`Dockerfile`](Dockerfile) + `node dist/mcp-server.js`。
|
|
87
|
+
表单:[`docs/GLAMA.md`](docs/GLAMA.md)。
|
|
88
|
+
|
|
89
|
+
## 库(嵌入你的宿主)
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install @aimarket/warden
|
|
93
|
+
```
|
|
30
94
|
|
|
31
95
|
```ts
|
|
32
96
|
import { Warden, ThreatFeed, silentLogger } from "@aimarket/warden";
|
|
@@ -73,7 +137,7 @@ flowchart LR
|
|
|
73
137
|
|
|
74
138
|
| 门控 | 判定什么 | 网络 | 是否 fatal |
|
|
75
139
|
|---|---|---|---|
|
|
76
|
-
| **static-scan** | `description` 与 `inputSchema` 中的注入、外泄、索要凭据,以及隐藏 Unicode/base64 迹象——25 条规则(
|
|
140
|
+
| **static-scan** | 工具 `name`、`description` 与 `inputSchema` 中的注入、外泄、索要凭据,以及隐藏 Unicode/base64 迹象——25 条规则(v4),其中 15 条可阻止、10 条仅提示,17 条同时覆盖名称,12 条带有上下文 guard | 无 | 否 |
|
|
77
141
|
| **threat-feed** | 已知恶意的服务器身份或工具:11 条内置记录,外加可选的已签名 feed | 仅 feed 下载 | 是,服务器范围的 `critical` |
|
|
78
142
|
| **origin** | 该服务器是运营者声明的,还是来自远端目录 | 无 | 是,当 `allowUnknownServers: false` |
|
|
79
143
|
| **pinning** | 工具定义是否仍与用户批准过的一致 | 无 | 是,当 `pinToolDefs: true` |
|
|
@@ -93,7 +157,7 @@ flowchart LR
|
|
|
93
157
|
findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
|
|
94
158
|
allowedTools: ["add"],
|
|
95
159
|
blockedTools: ["sweeper"],
|
|
96
|
-
rulesets: { staticScan: { version: "
|
|
160
|
+
rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
|
|
97
161
|
}
|
|
98
162
|
```
|
|
99
163
|
|
|
@@ -140,6 +204,10 @@ GET <你的 feed url>
|
|
|
140
204
|
| [门控链](docs/gates.zh.md) | 每一个规则层级、每一个发现码、综合评分如何构成,以及如何新增一个门控 |
|
|
141
205
|
| [已签名的威胁情报 feed](docs/threat-feed.zh.md) | 线上契约、三项检查,以及如何发布一个 WARDEN 会接受的 feed |
|
|
142
206
|
| [集成指南](docs/integration.zh.md) | 如何把 WARDEN 接入自己的 MCP 宿主、策略取舍,以及应当留档什么 |
|
|
207
|
+
| [实地普查:1 108 个公开 MCP 服务器](docs/mcp-survey.zh.md) | WARDEN 在真实的第三方工具定义上判了什么——拦截 50 个服务器,4 项成立,其余判错的六种方式 |
|
|
208
|
+
| [Glama / Docker](docs/GLAMA.md) | stdio MCP、健康检查、Build steps / CMD |
|
|
209
|
+
| [Security](SECURITY.md) | 如何报告防火墙绕过 |
|
|
210
|
+
| [Contributing](CONTRIBUTING.md) | 零依赖规则、规则表 PR |
|
|
143
211
|
|
|
144
212
|
## 它不是什么
|
|
145
213
|
|
|
@@ -151,11 +219,12 @@ GET <你的 feed url>
|
|
|
151
219
|
没发出的情况下报告预言机不可达。该门控已被移除,并且只要有任何门控再次声称不可达,
|
|
152
220
|
`test/no-phantom-gate.test.ts` 就会失败。
|
|
153
221
|
- **不能替代你亲自读工具定义。** 11 条内置威胁记录是底线,不是目录。
|
|
222
|
+
- **不是代理。** stdio MCP 入口检查的是你传入的定义。它不会连接、下载或执行被扫描的服务器。
|
|
154
223
|
|
|
155
224
|
## 开发
|
|
156
225
|
|
|
157
226
|
```bash
|
|
158
|
-
npm install && npm run build && npm test #
|
|
227
|
+
npm install && npm run build && npm test # 166 项测试
|
|
159
228
|
```
|
|
160
229
|
|
|
161
230
|
`test/packaging.test.ts` 正是让标题保持诚实的东西:一旦出现运行时依赖、任何源文件从包外 import、或者入口点不
|
package/README.md
CHANGED
|
@@ -1,18 +1,42 @@
|
|
|
1
|
-
# WARDEN — MCP
|
|
1
|
+
# WARDEN — MCP server
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.alexar76/warden -->
|
|
2
4
|
|
|
3
5
|
<!-- aicom-readme-badges -->
|
|
4
6
|
<p align="center">
|
|
5
7
|
<a href="https://github.com/alexar76/warden/actions/workflows/ci.yml"><img src="docs/badges/ci.svg" alt="CI" /></a>
|
|
6
|
-
<a href="https://
|
|
8
|
+
<a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
|
|
9
|
+
<a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm version" /></a>
|
|
7
10
|
<img src="docs/badges/deps.svg" alt="Zero runtime dependencies" />
|
|
8
|
-
<img src="docs/badges/tests.svg" alt="
|
|
11
|
+
<img src="docs/badges/tests.svg" alt="166 tests passing" />
|
|
9
12
|
<img src="docs/badges/node.svg" alt="Node >= 20" />
|
|
10
13
|
<a href="LICENSE"><img src="docs/badges/license.svg" alt="License: MIT" /></a>
|
|
11
14
|
</p>
|
|
12
15
|
<!-- /aicom-readme-badges -->
|
|
13
16
|
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="https://warden.modelmarket.dev/">
|
|
19
|
+
<img src="docs/screenshots/readme/hero-3d.png" alt="WARDEN — 3D gate chain: tools/list through static-scan, threat-feed, origin, and pinning to a recorded verdict" width="100%" />
|
|
20
|
+
</a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
|
|
14
24
|
> 🌐 **English** · [Русский](README-ru.md) · [Español](README-es.md) · [Français](README-fr.md) · [中文](README-zh.md) · [Glossary](https://github.com/alexar76/aicom/blob/main/docs/localization-glossary.md)
|
|
15
25
|
|
|
26
|
+
**One MCP server. Security firewall for advertised tool definitions. Library included.**
|
|
27
|
+
|
|
28
|
+
Transport: **stdio** (`npx -y @aimarket/warden` / `node dist/mcp-server.js`). Compatible hosts:
|
|
29
|
+
Claude Desktop, Cursor, Glama, and any MCP client that speaks stdio. No API keys.
|
|
30
|
+
|
|
31
|
+
| Item | Location |
|
|
32
|
+
|------|----------|
|
|
33
|
+
| MCP entrypoint (stdio) | `warden-mcp` → [`src/mcp-server.ts`](src/mcp-server.ts) |
|
|
34
|
+
| Tools | `vet_mcp_server`, `static_scan_tools`, `classify_sensitive_tools`, `check_egress_url`, `canonicalize_json`, `list_scan_rules` |
|
|
35
|
+
| Library | `import { Warden } from "@aimarket/warden"` |
|
|
36
|
+
| Glama / Docker (stdio) | [`Dockerfile`](Dockerfile), [`glama.json`](glama.json) |
|
|
37
|
+
| Official MCP Registry | [`server.json`](server.json) → `io.github.alexar76/warden` |
|
|
38
|
+
| Smithery | [`smithery.yaml`](smithery.yaml) |
|
|
39
|
+
|
|
16
40
|
An MCP server tells your agent what its tools do. The agent believes it — that sentence is the
|
|
17
41
|
attack surface. A tool description is prompt text delivered by a third party straight into your
|
|
18
42
|
model's context, and a schema field named `api_key` is a request for your secrets phrased as an API.
|
|
@@ -21,15 +45,60 @@ WARDEN vets a server **before any of its tools reach the model**, and returns a
|
|
|
21
45
|
record: allow/block, a 0..1 score, the findings that produced it, a per-tool partition, and the
|
|
22
46
|
exact rule table that was in force.
|
|
23
47
|
|
|
48
|
+
**Zero npm runtime dependencies.** The library's only import is `node:crypto`. The stdio MCP
|
|
49
|
+
server adds other `node:` builtins (`fs`, `path`, `process`) and still pulls in no packages. It is
|
|
50
|
+
the firewall out of [ARGUS](https://github.com/alexar76/argus), extracted so you can put it in front
|
|
51
|
+
of your own MCP host without adopting an agent.
|
|
52
|
+
|
|
53
|
+
## Run as MCP server (stdio)
|
|
54
|
+
|
|
24
55
|
```bash
|
|
25
|
-
|
|
56
|
+
npx -y @aimarket/warden # bin: warden-mcp
|
|
57
|
+
# from this repo:
|
|
58
|
+
npm run build && node dist/mcp-server.js
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Claude Desktop / Cursor (`mcpServers` entry):
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"warden": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["-y", "@aimarket/warden"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
26
72
|
```
|
|
27
73
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
74
|
+
The process never starts, proxies, or sandboxes another MCP server — you pass a `tools/list` dump
|
|
75
|
+
in, you get a verdict out.
|
|
76
|
+
|
|
77
|
+
| Tool | When to use |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `vet_mcp_server` | Full gate chain on a server identity + advertised tools |
|
|
80
|
+
| `static_scan_tools` | Injection / exfil scan only (no origin / pinning / threat feed) |
|
|
81
|
+
| `classify_sensitive_tools` | Operator glob split — not an injection scan |
|
|
82
|
+
| `check_egress_url` | Hostname allowlist (empty list denies every host) |
|
|
83
|
+
| `canonicalize_json` | RFC 8785 bytes for feeds and pins |
|
|
84
|
+
| `list_scan_rules` | Published rule table + digest |
|
|
85
|
+
|
|
86
|
+
Glama TDQS: MCP `annotations` (readOnly / destructive / idempotent / openWorld), when-to-use /
|
|
87
|
+
when-not naming siblings, every `inputSchema` property described, `outputSchema` on every tool.
|
|
88
|
+
|
|
89
|
+
### Publish on Glama
|
|
31
90
|
|
|
32
|
-
|
|
91
|
+
Listing: **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)**
|
|
92
|
+
|
|
93
|
+
Same pattern as **[ARGUS](https://github.com/alexar76/argus)** and
|
|
94
|
+
**[aimarket-mcp](https://github.com/alexar76/aimarket-mcp)**: repo-root [`glama.json`](glama.json) +
|
|
95
|
+
[`Dockerfile`](Dockerfile) + `node dist/mcp-server.js`. Admin form values: [`docs/GLAMA.md`](docs/GLAMA.md).
|
|
96
|
+
|
|
97
|
+
## Library (embed in your host)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install @aimarket/warden
|
|
101
|
+
```
|
|
33
102
|
|
|
34
103
|
```ts
|
|
35
104
|
import { Warden, ThreatFeed, silentLogger } from "@aimarket/warden";
|
|
@@ -76,7 +145,7 @@ flowchart LR
|
|
|
76
145
|
|
|
77
146
|
| Gate | What it decides | Network | Fatal? |
|
|
78
147
|
|---|---|---|---|
|
|
79
|
-
| **static-scan** | Injection, exfiltration, credential requests and hidden-Unicode/base64 tells in `description` and `inputSchema` — 25 rules,
|
|
148
|
+
| **static-scan** | Injection, exfiltration, credential requests and hidden-Unicode/base64 tells in the tool `name`, its `description` and its `inputSchema` — 25 rules, v4, of which 15 can block and 10 are advisory-only, 17 also cover the name, and 12 carry a context guard | none | no |
|
|
80
149
|
| **threat-feed** | Known-bad server identity or tool, from 11 built-in records plus an optional signed feed | only the feed fetch | yes, for a server-scoped `critical` |
|
|
81
150
|
| **origin** | Whether the operator declared this server or it arrived from a remote catalog | none | yes, under `allowUnknownServers: false` |
|
|
82
151
|
| **pinning** | Whether the tool defs still match what the user approved | none | yes, under `pinToolDefs: true` |
|
|
@@ -97,7 +166,7 @@ second as a low severity made it blocking again for anyone who tightened the thr
|
|
|
97
166
|
findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
|
|
98
167
|
allowedTools: ["add"],
|
|
99
168
|
blockedTools: ["sweeper"],
|
|
100
|
-
rulesets: { staticScan: { version: "
|
|
169
|
+
rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
|
|
101
170
|
}
|
|
102
171
|
```
|
|
103
172
|
|
|
@@ -149,6 +218,11 @@ no protection:
|
|
|
149
218
|
| [The gate chain](docs/gates.md) | Every rule tier, every finding code, how the composite score is built, and how to add a gate |
|
|
150
219
|
| [The signed threat feed](docs/threat-feed.md) | The wire contract, the three checks, and how to publish a feed WARDEN will accept |
|
|
151
220
|
| [Integration guide](docs/integration.md) | Wiring WARDEN into your own MCP host, policy choices, and what to record |
|
|
221
|
+
| [Field survey: 1 108 public MCP servers](docs/mcp-survey.md) | What WARDEN decided on real third-party tool definitions — 50 servers blocked, 4 substantiated, and the six ways the rest were wrong |
|
|
222
|
+
| [Glama / Docker](docs/GLAMA.md) | stdio MCP server, health check, admin Build steps / CMD |
|
|
223
|
+
| [MCP registries](docs/REGISTRIES.md) | Official Registry, Smithery, mcp.so / Pulse |
|
|
224
|
+
| [Security](SECURITY.md) | How to report a firewall bypass |
|
|
225
|
+
| [Contributing](CONTRIBUTING.md) | Zero-dep rule, ruleset PRs |
|
|
152
226
|
|
|
153
227
|
## What this is not
|
|
154
228
|
|
|
@@ -162,16 +236,19 @@ no protection:
|
|
|
162
236
|
again.
|
|
163
237
|
- **Not a substitute for reading the tool defs.** 11 built-in threat records is a floor, not a
|
|
164
238
|
catalog.
|
|
239
|
+
- **Not a proxy.** The stdio MCP entry inspects advertised definitions you pass it. It does not
|
|
240
|
+
connect to, fetch, or execute the server under scan.
|
|
165
241
|
|
|
166
242
|
## Development
|
|
167
243
|
|
|
168
244
|
```bash
|
|
169
|
-
npm install && npm run build && npm test #
|
|
245
|
+
npm install && npm run build && npm test # 166 tests
|
|
170
246
|
```
|
|
171
247
|
|
|
172
|
-
`test/packaging.test.ts` is what keeps the headline honest: it fails if
|
|
173
|
-
if any source file imports outside the package, or if the entry
|
|
174
|
-
enforcement surface.
|
|
248
|
+
`test/packaging.test.ts` is what keeps the headline honest: it fails if an npm runtime dependency
|
|
249
|
+
appears, if any source file imports outside the package (except `node:` builtins), or if the entry
|
|
250
|
+
point stops exporting the enforcement surface. `test/mcp-server.test.ts` is the Glama health
|
|
251
|
+
check: `initialize` + `tools/list` + a `tools/call`.
|
|
175
252
|
|
|
176
253
|
Used by [ARGUS](https://github.com/alexar76/argus) (the reference host), [MOMUS](https://github.com/alexar76/momus)
|
|
177
254
|
(the publisher side), and the AICOM MCP-security course.
|
package/dist/glob.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `*`-glob matching, without a regular expression.
|
|
3
|
+
*
|
|
4
|
+
* WARDEN matches two kinds of glob against attacker-controlled text: threat-feed
|
|
5
|
+
* patterns (a signed publisher chooses the pattern, a hostile server chooses the
|
|
6
|
+
* haystack) and `policy.sensitiveToolPatterns` (the operator chooses the pattern,
|
|
7
|
+
* the server chooses the tool name). Both used to compile `*` into `.*` and run a
|
|
8
|
+
* regex, which is a denial of service:
|
|
9
|
+
*
|
|
10
|
+
* pattern "*a*a*a*a*a*a*a*a*a*a*a*a*a*a*zzz" (32 characters)
|
|
11
|
+
* haystack "aaaa..." (220 characters)
|
|
12
|
+
* -> 112 SECONDS inside one RegExp.test
|
|
13
|
+
*
|
|
14
|
+
* The engine has to try every way of distributing the wildcards over the haystack
|
|
15
|
+
* before it can conclude "no match" - exponential in the number of `*`. That
|
|
16
|
+
* mattered more than an ordinary performance bug: the threat feed's whole trust
|
|
17
|
+
* story is that a compromised publisher can only ever ADD protection, never
|
|
18
|
+
* remove it, and a record that hangs `vet()` forever removes all of it. The same
|
|
19
|
+
* shape let a hostile server freeze the agent inspecting it, by advertising a
|
|
20
|
+
* long enough tool name against an operator's own `*a*b*c*` pattern.
|
|
21
|
+
*
|
|
22
|
+
* The two-pointer algorithm below is the standard wildcard matcher: it walks the
|
|
23
|
+
* haystack once and remembers only the most recent `*` to fall back to, so the
|
|
24
|
+
* work is bounded by `pattern.length x value.length` with no backtracking tree to
|
|
25
|
+
* explode. The same 32/220 case returns in microseconds.
|
|
26
|
+
*
|
|
27
|
+
* Semantics, matching what the regex did: `*` matches any run of zero or more
|
|
28
|
+
* UTF-16 code units (including newlines - the old regex needed the `s` flag for
|
|
29
|
+
* that), every other character is literal, and the match is anchored at both
|
|
30
|
+
* ends. Case folding is the caller's job: both sides arrive already lowercased.
|
|
31
|
+
*/
|
|
32
|
+
export declare function wildcardMatch(pattern: string, value: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* How many wildcards a pattern may carry.
|
|
35
|
+
*
|
|
36
|
+
* The matcher above is not exponential, but `*`-heavy patterns still cost
|
|
37
|
+
* `pattern x value` per match and buy nothing: a real threat pattern names a
|
|
38
|
+
* command, a path or a keyword. A record with more than this many wildcards is
|
|
39
|
+
* refused rather than run - refusing an *addition* to the deny-list is the safe
|
|
40
|
+
* direction, and it is reported loudly.
|
|
41
|
+
*/
|
|
42
|
+
export declare const MAX_GLOB_WILDCARDS = 12;
|
|
43
|
+
/** Count `*` in a pattern, for the record validator. */
|
|
44
|
+
export declare function countWildcards(pattern: string): number;
|
|
45
|
+
/**
|
|
46
|
+
* Largest gap an INTERIOR `*` in a threat pattern may span.
|
|
47
|
+
*
|
|
48
|
+
* A field survey of 1 108 public MCP servers (see docs/mcp-survey.md) found every
|
|
49
|
+
* multi-segment built-in firing on unrelated words that merely happened to appear
|
|
50
|
+
* in the same paragraph: `*drain*wallet*` matched an anti-drainer scanner because
|
|
51
|
+
* it says "drainer" in one clause and "wallet" in another, and `*seed*phrase*`
|
|
52
|
+
* matched "for a **seed** topic, returns suggested search **phrases**". A real
|
|
53
|
+
* threat pattern names something whose parts sit next to each other; 24 characters
|
|
54
|
+
* is roughly "a few words apart" and comfortably covers "wallet seed or recovery
|
|
55
|
+
* phrase".
|
|
56
|
+
*/
|
|
57
|
+
export declare const THREAT_MAX_GAP = 24;
|
|
58
|
+
/**
|
|
59
|
+
* Threat-pattern matching: `wildcardMatch` plus two constraints that only make
|
|
60
|
+
* sense for a deny-list.
|
|
61
|
+
*
|
|
62
|
+
* 1. **Interior gaps are bounded** to {@link THREAT_MAX_GAP}. Leading and trailing
|
|
63
|
+
* `*` stay unbounded — `*id_rsa*` must still find `id_rsa` anywhere.
|
|
64
|
+
* 2. **A segment that starts with a word character must start at a word boundary.**
|
|
65
|
+
* `*sweep*funds*` matched an ENS floor-sweeping tool because `funds` sits inside
|
|
66
|
+
* "re**funds**". Only the START is anchored: a pattern author writing `*drain*`
|
|
67
|
+
* means "drainer" too, so anchoring the end as well would break ordinary
|
|
68
|
+
* stemming.
|
|
69
|
+
*
|
|
70
|
+
* Kept separate from {@link wildcardMatch} on purpose. That function is also what
|
|
71
|
+
* `policy.sensitiveToolPatterns` runs on, where the operator wrote the pattern
|
|
72
|
+
* against their own tool names and plain glob semantics are what they expect.
|
|
73
|
+
*
|
|
74
|
+
* Completeness matters here in a way it does not for a display filter: a missed
|
|
75
|
+
* match is a deny-list entry that silently stopped working. So this explores every
|
|
76
|
+
* reachable position per segment (deduplicated, so the work stays bounded by
|
|
77
|
+
* `segments x value.length`) rather than taking the leftmost occurrence greedily,
|
|
78
|
+
* which would report "no match" for a pattern that does match further along.
|
|
79
|
+
*/
|
|
80
|
+
export declare function threatMatch(pattern: string, value: string): boolean;
|
|
81
|
+
//# sourceMappingURL=glob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA4BrE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA+BnE"}
|
package/dist/glob.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `*`-glob matching, without a regular expression.
|
|
3
|
+
*
|
|
4
|
+
* WARDEN matches two kinds of glob against attacker-controlled text: threat-feed
|
|
5
|
+
* patterns (a signed publisher chooses the pattern, a hostile server chooses the
|
|
6
|
+
* haystack) and `policy.sensitiveToolPatterns` (the operator chooses the pattern,
|
|
7
|
+
* the server chooses the tool name). Both used to compile `*` into `.*` and run a
|
|
8
|
+
* regex, which is a denial of service:
|
|
9
|
+
*
|
|
10
|
+
* pattern "*a*a*a*a*a*a*a*a*a*a*a*a*a*a*zzz" (32 characters)
|
|
11
|
+
* haystack "aaaa..." (220 characters)
|
|
12
|
+
* -> 112 SECONDS inside one RegExp.test
|
|
13
|
+
*
|
|
14
|
+
* The engine has to try every way of distributing the wildcards over the haystack
|
|
15
|
+
* before it can conclude "no match" - exponential in the number of `*`. That
|
|
16
|
+
* mattered more than an ordinary performance bug: the threat feed's whole trust
|
|
17
|
+
* story is that a compromised publisher can only ever ADD protection, never
|
|
18
|
+
* remove it, and a record that hangs `vet()` forever removes all of it. The same
|
|
19
|
+
* shape let a hostile server freeze the agent inspecting it, by advertising a
|
|
20
|
+
* long enough tool name against an operator's own `*a*b*c*` pattern.
|
|
21
|
+
*
|
|
22
|
+
* The two-pointer algorithm below is the standard wildcard matcher: it walks the
|
|
23
|
+
* haystack once and remembers only the most recent `*` to fall back to, so the
|
|
24
|
+
* work is bounded by `pattern.length x value.length` with no backtracking tree to
|
|
25
|
+
* explode. The same 32/220 case returns in microseconds.
|
|
26
|
+
*
|
|
27
|
+
* Semantics, matching what the regex did: `*` matches any run of zero or more
|
|
28
|
+
* UTF-16 code units (including newlines - the old regex needed the `s` flag for
|
|
29
|
+
* that), every other character is literal, and the match is anchored at both
|
|
30
|
+
* ends. Case folding is the caller's job: both sides arrive already lowercased.
|
|
31
|
+
*/
|
|
32
|
+
export function wildcardMatch(pattern, value) {
|
|
33
|
+
const STAR = 0x2a;
|
|
34
|
+
let p = 0;
|
|
35
|
+
let v = 0;
|
|
36
|
+
let star = -1;
|
|
37
|
+
let resume = 0;
|
|
38
|
+
while (v < value.length) {
|
|
39
|
+
if (p < pattern.length && pattern.charCodeAt(p) === value.charCodeAt(v)) {
|
|
40
|
+
p++;
|
|
41
|
+
v++;
|
|
42
|
+
}
|
|
43
|
+
else if (p < pattern.length && pattern.charCodeAt(p) === STAR) {
|
|
44
|
+
// Remember this wildcard and let it match nothing for now.
|
|
45
|
+
star = p++;
|
|
46
|
+
resume = v;
|
|
47
|
+
}
|
|
48
|
+
else if (star >= 0) {
|
|
49
|
+
// Backtrack: hand one more character to the last wildcard. There is only
|
|
50
|
+
// ever one fallback point, which is what keeps this linear-bounded.
|
|
51
|
+
p = star + 1;
|
|
52
|
+
v = ++resume;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Trailing wildcards may still match the empty remainder.
|
|
59
|
+
while (p < pattern.length && pattern.charCodeAt(p) === STAR)
|
|
60
|
+
p++;
|
|
61
|
+
return p === pattern.length;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* How many wildcards a pattern may carry.
|
|
65
|
+
*
|
|
66
|
+
* The matcher above is not exponential, but `*`-heavy patterns still cost
|
|
67
|
+
* `pattern x value` per match and buy nothing: a real threat pattern names a
|
|
68
|
+
* command, a path or a keyword. A record with more than this many wildcards is
|
|
69
|
+
* refused rather than run - refusing an *addition* to the deny-list is the safe
|
|
70
|
+
* direction, and it is reported loudly.
|
|
71
|
+
*/
|
|
72
|
+
export const MAX_GLOB_WILDCARDS = 12;
|
|
73
|
+
/** Count `*` in a pattern, for the record validator. */
|
|
74
|
+
export function countWildcards(pattern) {
|
|
75
|
+
let n = 0;
|
|
76
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
77
|
+
if (pattern.charCodeAt(i) === 0x2a)
|
|
78
|
+
n++;
|
|
79
|
+
}
|
|
80
|
+
return n;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Largest gap an INTERIOR `*` in a threat pattern may span.
|
|
84
|
+
*
|
|
85
|
+
* A field survey of 1 108 public MCP servers (see docs/mcp-survey.md) found every
|
|
86
|
+
* multi-segment built-in firing on unrelated words that merely happened to appear
|
|
87
|
+
* in the same paragraph: `*drain*wallet*` matched an anti-drainer scanner because
|
|
88
|
+
* it says "drainer" in one clause and "wallet" in another, and `*seed*phrase*`
|
|
89
|
+
* matched "for a **seed** topic, returns suggested search **phrases**". A real
|
|
90
|
+
* threat pattern names something whose parts sit next to each other; 24 characters
|
|
91
|
+
* is roughly "a few words apart" and comfortably covers "wallet seed or recovery
|
|
92
|
+
* phrase".
|
|
93
|
+
*/
|
|
94
|
+
export const THREAT_MAX_GAP = 24;
|
|
95
|
+
/**
|
|
96
|
+
* Threat-pattern matching: `wildcardMatch` plus two constraints that only make
|
|
97
|
+
* sense for a deny-list.
|
|
98
|
+
*
|
|
99
|
+
* 1. **Interior gaps are bounded** to {@link THREAT_MAX_GAP}. Leading and trailing
|
|
100
|
+
* `*` stay unbounded — `*id_rsa*` must still find `id_rsa` anywhere.
|
|
101
|
+
* 2. **A segment that starts with a word character must start at a word boundary.**
|
|
102
|
+
* `*sweep*funds*` matched an ENS floor-sweeping tool because `funds` sits inside
|
|
103
|
+
* "re**funds**". Only the START is anchored: a pattern author writing `*drain*`
|
|
104
|
+
* means "drainer" too, so anchoring the end as well would break ordinary
|
|
105
|
+
* stemming.
|
|
106
|
+
*
|
|
107
|
+
* Kept separate from {@link wildcardMatch} on purpose. That function is also what
|
|
108
|
+
* `policy.sensitiveToolPatterns` runs on, where the operator wrote the pattern
|
|
109
|
+
* against their own tool names and plain glob semantics are what they expect.
|
|
110
|
+
*
|
|
111
|
+
* Completeness matters here in a way it does not for a display filter: a missed
|
|
112
|
+
* match is a deny-list entry that silently stopped working. So this explores every
|
|
113
|
+
* reachable position per segment (deduplicated, so the work stays bounded by
|
|
114
|
+
* `segments x value.length`) rather than taking the leftmost occurrence greedily,
|
|
115
|
+
* which would report "no match" for a pattern that does match further along.
|
|
116
|
+
*/
|
|
117
|
+
export function threatMatch(pattern, value) {
|
|
118
|
+
if (!pattern.includes("*"))
|
|
119
|
+
return value.includes(pattern);
|
|
120
|
+
const parts = pattern.split("*");
|
|
121
|
+
const openStart = parts[0] === "";
|
|
122
|
+
const openEnd = parts[parts.length - 1] === "";
|
|
123
|
+
const segments = parts.filter((s) => s !== "");
|
|
124
|
+
if (segments.length === 0)
|
|
125
|
+
return true;
|
|
126
|
+
// Reachable end positions after matching the segments consumed so far.
|
|
127
|
+
let ends = [0];
|
|
128
|
+
for (let i = 0; i < segments.length; i++) {
|
|
129
|
+
const seg = segments[i];
|
|
130
|
+
// The first segment is anchored at 0 unless the pattern opened with `*`.
|
|
131
|
+
const unbounded = i === 0 ? openStart : false;
|
|
132
|
+
const next = new Set();
|
|
133
|
+
for (const from of ends) {
|
|
134
|
+
const limit = unbounded ? value.length : Math.min(value.length, from + THREAT_MAX_GAP + seg.length);
|
|
135
|
+
let at = from;
|
|
136
|
+
for (;;) {
|
|
137
|
+
const found = value.indexOf(seg, at);
|
|
138
|
+
if (found < 0 || found + seg.length > limit)
|
|
139
|
+
break;
|
|
140
|
+
if (i === 0 && !openStart && found !== 0)
|
|
141
|
+
break;
|
|
142
|
+
if (startsOnBoundary(seg, value, found))
|
|
143
|
+
next.add(found + seg.length);
|
|
144
|
+
at = found + 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (next.size === 0)
|
|
148
|
+
return false;
|
|
149
|
+
ends = [...next];
|
|
150
|
+
}
|
|
151
|
+
return openEnd || ends.some((e) => e === value.length);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Is `seg` at `at` starting on a word boundary?
|
|
155
|
+
*
|
|
156
|
+
* Only asked when the segment's own first character is a word character — a
|
|
157
|
+
* pattern starting with `~`, `.` or `/` has no boundary to respect.
|
|
158
|
+
*/
|
|
159
|
+
function startsOnBoundary(seg, value, at) {
|
|
160
|
+
if (!isWordChar(seg.charCodeAt(0)))
|
|
161
|
+
return true;
|
|
162
|
+
if (at === 0)
|
|
163
|
+
return true;
|
|
164
|
+
return !isWordChar(value.charCodeAt(at - 1));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Letters and digits only — `_` and `-` are BOUNDARIES here, not word characters.
|
|
168
|
+
*
|
|
169
|
+
* Tool schemas are written in snake_case and kebab-case, so a field named
|
|
170
|
+
* `seed_phrase` must still satisfy `*seed*phrase*`; treating `_` as a word
|
|
171
|
+
* character silently switched that built-in off. "re**funds**" is still rejected,
|
|
172
|
+
* because there the preceding character is a letter.
|
|
173
|
+
*/
|
|
174
|
+
function isWordChar(c) {
|
|
175
|
+
return ((c >= 0x30 && c <= 0x39) || // 0-9
|
|
176
|
+
(c >= 0x41 && c <= 0x5a) || // A-Z
|
|
177
|
+
(c >= 0x61 && c <= 0x7a) // a-z
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=glob.js.map
|
package/dist/glob.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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"}
|