@aimarket/warden 0.0.1 → 0.4.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 +206 -0
- package/LICENSE +21 -0
- package/README-es.md +190 -0
- package/README-fr.md +190 -0
- package/README-ru.md +189 -0
- package/README-zh.md +173 -0
- package/README.md +179 -6
- 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 +106 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/jcs.d.ts +86 -0
- package/dist/jcs.d.ts.map +1 -0
- package/dist/jcs.js +296 -0
- package/dist/jcs.js.map +1 -0
- package/dist/logger.d.ts +10 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +19 -0
- package/dist/logger.js.map +1 -0
- package/dist/origin.d.ts +25 -0
- package/dist/origin.d.ts.map +1 -0
- package/dist/origin.js +49 -0
- package/dist/origin.js.map +1 -0
- package/dist/pinning.d.ts +91 -0
- package/dist/pinning.d.ts.map +1 -0
- package/dist/pinning.js +227 -0
- package/dist/pinning.js.map +1 -0
- package/dist/sandbox.d.ts +38 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +79 -0
- package/dist/sandbox.js.map +1 -0
- 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 +110 -0
- package/dist/static-scan.d.ts.map +1 -0
- package/dist/static-scan.js +539 -0
- package/dist/static-scan.js.map +1 -0
- package/dist/threat-feed.d.ts +138 -0
- package/dist/threat-feed.d.ts.map +1 -0
- package/dist/threat-feed.js +546 -0
- package/dist/threat-feed.js.map +1 -0
- package/dist/types.d.ts +173 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +67 -9
- package/index.js +0 -6
package/README-ru.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# WARDEN — файрвол безопасности MCP
|
|
2
|
+
|
|
3
|
+
<!-- aicom-readme-badges -->
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://github.com/alexar76/warden/actions/workflows/ci.yml"><img src="docs/badges/ci.svg" alt="CI" /></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@aimarket/warden"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm version" /></a>
|
|
7
|
+
<img src="docs/badges/deps.svg" alt="Zero runtime dependencies" />
|
|
8
|
+
<img src="docs/badges/tests.svg" alt="141 tests passing" />
|
|
9
|
+
<img src="docs/badges/node.svg" alt="Node >= 20" />
|
|
10
|
+
<a href="LICENSE"><img src="docs/badges/license.svg" alt="License: MIT" /></a>
|
|
11
|
+
</p>
|
|
12
|
+
<!-- /aicom-readme-badges -->
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="docs/assets/hero.svg" alt="WARDEN — MCP security firewall: tool defs pass static-scan, threat-feed, origin, and pinning before a recorded verdict" width="920" />
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
> 🌐 [English](README.md) · **Русский** · [Español](README-es.md) · [Français](README-fr.md) · [中文](README-zh.md) · [Глоссарий](https://github.com/alexar76/aicom/blob/main/docs/localization-glossary.md)
|
|
20
|
+
|
|
21
|
+
MCP-сервер сам сообщает вашему агенту, что делают его инструменты. Агент этому верит — и вот эта
|
|
22
|
+
фраза и есть поверхность атаки. Описание инструмента — это текст промпта, который третья сторона
|
|
23
|
+
доставляет прямо в контекст модели, а поле схемы с именем `api_key` — это запрос ваших секретов,
|
|
24
|
+
сформулированный как API.
|
|
25
|
+
|
|
26
|
+
WARDEN проверяет сервер **до того, как его инструменты дойдут до модели**, и возвращает вердикт,
|
|
27
|
+
который можно записать: allow/block, оценку 0..1, найденные проблемы, разбиение по инструментам и
|
|
28
|
+
точную таблицу правил, которая действовала в момент проверки.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @aimarket/warden
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Ноль рантайм-зависимостей.** Единственный импорт во всём пакете — `node:crypto`. Это файрвол из
|
|
35
|
+
[ARGUS](https://github.com/alexar76/argus), вынесенный отдельно, чтобы его можно было поставить
|
|
36
|
+
перед своим MCP-хостом, не переезжая на агента.
|
|
37
|
+
|
|
38
|
+
## Быстрый старт
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { Warden, ThreatFeed, silentLogger } from "@aimarket/warden";
|
|
42
|
+
|
|
43
|
+
const threatFeed = new ThreatFeed({ feedPublicKey: process.env.FEED_PUBKEY });
|
|
44
|
+
await threatFeed.load(process.env.FEED_URL); // без URL → только встроенный deny-list, без сети
|
|
45
|
+
|
|
46
|
+
const pins = new Map();
|
|
47
|
+
const warden = Warden.create({
|
|
48
|
+
policy: {
|
|
49
|
+
blockAtSeverity: "high",
|
|
50
|
+
sensitiveToolPatterns: ["*delete*", "*transfer*", "*key*"],
|
|
51
|
+
allowUnknownServers: false, // fail-closed: только объявленные вами серверы
|
|
52
|
+
pinToolDefs: true,
|
|
53
|
+
},
|
|
54
|
+
threatFeed,
|
|
55
|
+
store: {
|
|
56
|
+
getPin: async (id) => pins.get(id),
|
|
57
|
+
putPin: async (p) => void pins.set(p.serverId, p),
|
|
58
|
+
},
|
|
59
|
+
log: silentLogger(), // или ваш собственный логгер
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const verdict = await warden.vet(server, await client.listTools());
|
|
63
|
+
|
|
64
|
+
if (!verdict.allow) throw new Error(`заблокировано гейтом ${verdict.decidedBy}`);
|
|
65
|
+
const usable = verdict.allowedTools; // отравленный инструмент можно изолировать в одиночку
|
|
66
|
+
await warden.approve(server, tools); // зафиксировать (pin) то, что подтвердил пользователь
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`vet()` **не делает сетевых запросов**. Единственный запрос, который WARDEN вообще выполняет, — это
|
|
70
|
+
загрузка threat feed, о которой вы попросили сами, передав URL в `load()`.
|
|
71
|
+
|
|
72
|
+
## Цепочка гейтов
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart LR
|
|
76
|
+
T["определения<br/>инструментов"] --> S["static scan<br/>25 правил"]
|
|
77
|
+
S --> F["threat feed<br/>11 встроенных + подписанный"]
|
|
78
|
+
F --> O["origin<br/>объявлен или из каталога"]
|
|
79
|
+
O --> P["pinning<br/>дрейф против подтверждения"]
|
|
80
|
+
P --> V["вердикт<br/>allow · score · findings<br/>allowedTools / blockedTools"]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
| Гейт | Что решает | Сеть | Fatal? |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| **static-scan** | Инъекции, эксфильтрация, запросы учётных данных, скрытый Unicode и base64-признаки в `name`, `description` и `inputSchema` инструмента — 25 правил, версия 4, из них 15 могут блокировать и 10 только сообщают, 17 покрывают и имя, а у 12 есть контекстный guard | нет | нет |
|
|
86
|
+
| **threat-feed** | Известный плохой сервер или инструмент: 11 встроенных записей плюс опциональный подписанный feed | только загрузка feed | да, для `critical` на уровне сервера |
|
|
87
|
+
| **origin** | Объявил ли оператор этот сервер, или он пришёл из удалённого каталога | нет | да, при `allowUnknownServers: false` |
|
|
88
|
+
| **pinning** | Совпадают ли определения инструментов с тем, что подтвердил пользователь | нет | да, при `pinToolDefs: true` |
|
|
89
|
+
|
|
90
|
+
Composite score — это **произведение** вкладов гейтов: один плохой гейт тянет весь сервер вниз, а не
|
|
91
|
+
усредняется. Severity и блокировка — разные оси: находка с `advisory` сообщается, но никогда не
|
|
92
|
+
блокирует и никогда не стоит инструмента, при любом `blockAtSeverity`. Потому что «сколько внимания
|
|
93
|
+
это заслуживает» и «дефект ли это вообще» — разные вопросы, а кодирование второго через низкую
|
|
94
|
+
severity снова делало находку блокирующей для всех, кто ужесточил порог.
|
|
95
|
+
|
|
96
|
+
## Вердикт рассчитан на то, чтобы его записали
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
{
|
|
100
|
+
allow: false,
|
|
101
|
+
score: 0,
|
|
102
|
+
decidedBy: "threat-feed",
|
|
103
|
+
findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
|
|
104
|
+
allowedTools: ["add"],
|
|
105
|
+
blockedTools: ["sweeper"],
|
|
106
|
+
rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`rulesets` — не украшение. Один и тот же сервер получит другую оценку под более поздней таблицей
|
|
111
|
+
правил, и без версии *и* дайджеста по правилам невозможно отличить это от того, что изменился сам
|
|
112
|
+
сервер. Сохранённый скан без них невоспроизводим.
|
|
113
|
+
|
|
114
|
+
## Подписанный threat feed
|
|
115
|
+
|
|
116
|
+
WARDEN не станет читать неподписанный удалённый feed. Контракт намеренно скучный:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
GET <ваш feed url>
|
|
120
|
+
{ "records": [ {pattern, severity, code, reason, source, scope}, … ],
|
|
121
|
+
"timestamp": 1786205907380, // epoch ms, целое — обязательно
|
|
122
|
+
"signature": "f588d5a4…" // Ed25519 (hex) по канонической форме
|
|
123
|
+
} // RFC 8785 от {records, timestamp}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Проверяются три свойства, и **любой сбой сохраняет встроенный минимум**, а не деградирует до полного
|
|
127
|
+
отсутствия защиты:
|
|
128
|
+
|
|
129
|
+
1. **подлинность** — Ed25519 против ключа, закреплённого заранее (`feedPublicKey`);
|
|
130
|
+
2. **свежесть** — *подписанный* timestamp должен попадать в окно `maxAgeMs` (по умолчанию 24 ч),
|
|
131
|
+
чтобы тот, кто раздаёт URL, не мог подсунуть снимок месячной давности и молча стереть все
|
|
132
|
+
добавленные с тех пор записи. Подпись говорит, кто написал документ, но никогда — когда его вам
|
|
133
|
+
вручили;
|
|
134
|
+
3. **детерминизм** — канонические байты RFC 8785, чтобы издатель и проверяющий совпадали независимо
|
|
135
|
+
от порядка ключей в JSON.
|
|
136
|
+
|
|
137
|
+
[MOMUS](https://github.com/alexar76/momus) — референсный издатель этого контракта
|
|
138
|
+
(`/warden/threat-feed`), если нужно, на что направить `load()`.
|
|
139
|
+
|
|
140
|
+
## Что ещё внутри
|
|
141
|
+
|
|
142
|
+
- **`EgressGuard`** — allowlist исходящих запросов, которым можно обернуть любой запрос инструмента.
|
|
143
|
+
Инструмент, тянущийся к хосту, которого вы не перечисляли, — классический признак phone-home.
|
|
144
|
+
`*.example.com` совпадает с поддоменами; пустой allowlist блокирует всё, а не разрешает всё.
|
|
145
|
+
- **`isSensitiveTool` / `classifyTools`** — glob-классификация инструментов, которые обязаны требовать
|
|
146
|
+
подтверждения на каждый вызов. Чувствительные инструменты остаются *объявленными* — они просто не
|
|
147
|
+
могут выполняться без присмотра.
|
|
148
|
+
- **`canonicalize` / `parseJsonStrict`** — строгая реализация RFC 8785 (JCS), экспортируется и как
|
|
149
|
+
`@aimarket/warden/jcs`, чтобы другую реализацию можно было сверить с ней побайтово. Только целые
|
|
150
|
+
за пределами `MAX_SAFE_JSON_INTEGER`, отказ (а не экранирование) на одиночных суррогатах и код
|
|
151
|
+
причины на каждом отказе.
|
|
152
|
+
|
|
153
|
+
## Документация
|
|
154
|
+
|
|
155
|
+
| | |
|
|
156
|
+
|---|---|
|
|
157
|
+
| [Цепочка гейтов](docs/gates.ru.md) | Все уровни правил, все коды находок, как складывается composite score и как добавить свой гейт |
|
|
158
|
+
| [Подписанный threat feed](docs/threat-feed.ru.md) | Контракт на проводе, три проверки и как публиковать feed, который WARDEN примет |
|
|
159
|
+
| [Руководство по интеграции](docs/integration.ru.md) | Как встроить WARDEN в свой MCP-хост, выбор политики и что записывать |
|
|
160
|
+
| [Полевой обзор: 1 108 публичных MCP-серверов](docs/mcp-survey.ru.md) | Что WARDEN решил на настоящих чужих определениях инструментов — 50 серверов заблокировано, 4 подтверждено, и шесть способов, которыми остальные оказались ошибкой |
|
|
161
|
+
|
|
162
|
+
## Чем это не является
|
|
163
|
+
|
|
164
|
+
- **Не песочница.** Это внутрипроцессные решения на JS. Изоляции дочернего MCP-процесса на уровне ОС
|
|
165
|
+
(seccomp/Landlock, `sandbox-exec`) здесь нет.
|
|
166
|
+
- **Не модель.** В цепочке нигде не вызывается LLM. Именно поэтому `vet()` быстрый, офлайновый и
|
|
167
|
+
детерминированный — и именно поэтому static scan имеет форму регулярок и пропустит перефразировку,
|
|
168
|
+
которую не покрывает ни одно правило.
|
|
169
|
+
- **Не сервис репутации.** В более ранней версии был гейт, который спрашивал у trust-оракула оценку,
|
|
170
|
+
для вычисления которой у того не было данных, а затем сообщал, что оракул недоступен, так и не
|
|
171
|
+
отправив запроса. Гейт удалён, и `test/no-phantom-gate.test.ts` падает, если какой-нибудь гейт
|
|
172
|
+
снова заявит о недоступности.
|
|
173
|
+
- **Не замена чтению определений инструментов.** 11 встроенных записей об угрозах — это минимум, а не
|
|
174
|
+
каталог.
|
|
175
|
+
|
|
176
|
+
## Разработка
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm install && npm run build && npm test # 141 тестов
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`test/packaging.test.ts` — то, что удерживает заголовок честным: он падает, если появляется
|
|
183
|
+
рантайм-зависимость, если какой-то файл импортирует что-то за пределами пакета или если точка входа
|
|
184
|
+
перестаёт экспортировать поверхность энфорсмента.
|
|
185
|
+
|
|
186
|
+
Используется в [ARGUS](https://github.com/alexar76/argus) (референсный хост),
|
|
187
|
+
[MOMUS](https://github.com/alexar76/momus) (сторона издателя) и в курсе AICOM по безопасности MCP.
|
|
188
|
+
|
|
189
|
+
MIT © AICOM (alexar76)
|
package/README-zh.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# WARDEN — MCP 安全防火墙
|
|
2
|
+
|
|
3
|
+
<!-- aicom-readme-badges -->
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://github.com/alexar76/warden/actions/workflows/ci.yml"><img src="docs/badges/ci.svg" alt="CI" /></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@aimarket/warden"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm 版本" /></a>
|
|
7
|
+
<img src="docs/badges/deps.svg" alt="零运行时依赖" />
|
|
8
|
+
<img src="docs/badges/tests.svg" alt="141 项测试通过" />
|
|
9
|
+
<img src="docs/badges/node.svg" alt="Node >= 20" />
|
|
10
|
+
<a href="LICENSE"><img src="docs/badges/license.svg" alt="许可证:MIT" /></a>
|
|
11
|
+
</p>
|
|
12
|
+
<!-- /aicom-readme-badges -->
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="docs/assets/hero.svg" alt="WARDEN — MCP security firewall: tool defs pass static-scan, threat-feed, origin, and pinning before a recorded verdict" width="920" />
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
> 🌐 [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)
|
|
20
|
+
|
|
21
|
+
MCP 服务器自己告诉你的智能体,它的工具是做什么的。智能体就信了——而这句话正是攻击面。工具描述就是第三方直接
|
|
22
|
+
投递进模型上下文的提示词文本;而一个名叫 `api_key` 的 schema 字段,就是以 API 形式写出来的索要密钥的请求。
|
|
23
|
+
|
|
24
|
+
WARDEN 在**该服务器的任何工具到达模型之前**审查它,并返回一份可以留档的裁定:允许/阻止、0..1 的评分、导致该
|
|
25
|
+
评分的各项发现、按工具的划分,以及当时生效的确切规则表。
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @aimarket/warden
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**零运行时依赖。** 整个包里唯一的 import 是 `node:crypto`。它就是 [ARGUS](https://github.com/alexar76/argus)
|
|
32
|
+
里的那个防火墙,被单独抽出来,好让你把它放在自己的 MCP 宿主前面,而不必换用一个智能体。
|
|
33
|
+
|
|
34
|
+
## 快速开始
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { Warden, ThreatFeed, silentLogger } from "@aimarket/warden";
|
|
38
|
+
|
|
39
|
+
const threatFeed = new ThreatFeed({ feedPublicKey: process.env.FEED_PUBKEY });
|
|
40
|
+
await threatFeed.load(process.env.FEED_URL); // 不传 URL → 只用内置拒绝列表,不联网
|
|
41
|
+
|
|
42
|
+
const pins = new Map();
|
|
43
|
+
const warden = Warden.create({
|
|
44
|
+
policy: {
|
|
45
|
+
blockAtSeverity: "high",
|
|
46
|
+
sensitiveToolPatterns: ["*delete*", "*transfer*", "*key*"],
|
|
47
|
+
allowUnknownServers: false, // 失败即关闭:只允许你声明过的服务器
|
|
48
|
+
pinToolDefs: true,
|
|
49
|
+
},
|
|
50
|
+
threatFeed,
|
|
51
|
+
store: {
|
|
52
|
+
getPin: async (id) => pins.get(id),
|
|
53
|
+
putPin: async (p) => void pins.set(p.serverId, p),
|
|
54
|
+
},
|
|
55
|
+
log: silentLogger(), // 或者你自己的 logger
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const verdict = await warden.vet(server, await client.listTools());
|
|
59
|
+
|
|
60
|
+
if (!verdict.allow) throw new Error(`被 ${verdict.decidedBy} 阻止`);
|
|
61
|
+
const usable = verdict.allowedTools; // 被投毒的工具可以单独隔离
|
|
62
|
+
await warden.approve(server, tools); // 把用户认可的内容固定(pin)下来
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`vet()` **不发起任何网络请求**。WARDEN 唯一会发出的请求,是你把 URL 传给 `load()` 时主动要求的威胁情报
|
|
66
|
+
(threat feed)下载。
|
|
67
|
+
|
|
68
|
+
## 门控链
|
|
69
|
+
|
|
70
|
+
```mermaid
|
|
71
|
+
flowchart LR
|
|
72
|
+
T["服务器给出的<br/>工具定义"] --> S["static scan<br/>25 条规则"]
|
|
73
|
+
S --> F["threat feed<br/>11 条内置 + 已签名"]
|
|
74
|
+
F --> O["origin<br/>已声明 vs 来自目录"]
|
|
75
|
+
O --> P["pinning<br/>漂移 vs 已批准"]
|
|
76
|
+
P --> V["裁定<br/>allow · score · findings<br/>allowedTools / blockedTools"]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| 门控 | 判定什么 | 网络 | 是否 fatal |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| **static-scan** | 工具 `name`、`description` 与 `inputSchema` 中的注入、外泄、索要凭据,以及隐藏 Unicode/base64 迹象——25 条规则(v4),其中 15 条可阻止、10 条仅提示,17 条同时覆盖名称,12 条带有上下文 guard | 无 | 否 |
|
|
82
|
+
| **threat-feed** | 已知恶意的服务器身份或工具:11 条内置记录,外加可选的已签名 feed | 仅 feed 下载 | 是,服务器范围的 `critical` |
|
|
83
|
+
| **origin** | 该服务器是运营者声明的,还是来自远端目录 | 无 | 是,当 `allowUnknownServers: false` |
|
|
84
|
+
| **pinning** | 工具定义是否仍与用户批准过的一致 | 无 | 是,当 `pinToolDefs: true` |
|
|
85
|
+
|
|
86
|
+
综合评分是各门控贡献的**乘积**:一个门控出问题会把整台服务器拉下来,而不是被平均掉。严重级别与是否阻止是两
|
|
87
|
+
个独立的维度:带 `advisory` 的发现会被报告,但在任何 `blockAtSeverity` 下都不会阻止连接、也不会牺牲任何工
|
|
88
|
+
具——因为「这值得多少注意」和「这到底是不是缺陷」是两个不同的问题,而把后者编码成一个较低的严重级别,会让
|
|
89
|
+
它对任何收紧阈值的人重新变成阻止性的。
|
|
90
|
+
|
|
91
|
+
## 裁定就是为了留档
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
{
|
|
95
|
+
allow: false,
|
|
96
|
+
score: 0,
|
|
97
|
+
decidedBy: "threat-feed",
|
|
98
|
+
findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
|
|
99
|
+
allowedTools: ["add"],
|
|
100
|
+
blockedTools: ["sweeper"],
|
|
101
|
+
rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`rulesets` 不是装饰。同一台服务器在更晚的规则表下会得到不同评分;如果既没有版本号、也没有对规则内容的摘要,
|
|
106
|
+
就无法把这种情况与「服务器本身变了」区分开。缺了它们,留档的扫描结果不可复现。
|
|
107
|
+
|
|
108
|
+
## 已签名的威胁情报 feed
|
|
109
|
+
|
|
110
|
+
WARDEN 不会读取未签名的远端 feed。这个契约刻意做得很枯燥:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
GET <你的 feed url>
|
|
114
|
+
{ "records": [ {pattern, severity, code, reason, source, scope}, … ],
|
|
115
|
+
"timestamp": 1786205907380, // epoch 毫秒,整数——必填
|
|
116
|
+
"signature": "f588d5a4…" // 对 {records, timestamp} 的 RFC 8785
|
|
117
|
+
} // 规范化形式做的 Ed25519 签名(hex)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
会检查三项性质,且**任何一项失败都保留内置底线**,而不是退化成毫无防护:
|
|
121
|
+
|
|
122
|
+
1. **真实性**——用你事先固定的公钥(`feedPublicKey`)做 Ed25519 验签;
|
|
123
|
+
2. **新鲜度**——*被签名的* timestamp 必须落在 `maxAgeMs` 窗口内(默认 24 小时),这样提供该 URL 的一方就无法
|
|
124
|
+
重放几个月前的快照、悄悄抹掉此后新增的每一条记录。签名说明的是谁写了这份文件,从不说明它是何时交到你手上
|
|
125
|
+
的;
|
|
126
|
+
3. **确定性**——RFC 8785 规范化字节,使发布方与验证方无论 JSON 键顺序如何都能对上。
|
|
127
|
+
|
|
128
|
+
如果你需要一个可以让 `load()` 指向的目标,[MOMUS](https://github.com/alexar76/momus) 是该契约的参考发布方
|
|
129
|
+
(`/warden/threat-feed`)。
|
|
130
|
+
|
|
131
|
+
## 还包含
|
|
132
|
+
|
|
133
|
+
- **`EgressGuard`**——出网白名单,用来包住工具发出的任何请求。工具去访问你从未列入的主机,正是典型的
|
|
134
|
+
phone-home 迹象。`*.example.com` 覆盖子域;空白名单会阻止一切,而不是放行一切。
|
|
135
|
+
- **`isSensitiveTool` / `classifyTools`**——对必须逐次调用都要审批的工具做 glob 分类。敏感工具仍然是*已公布*
|
|
136
|
+
的,只是不能在无人看管的情况下运行。
|
|
137
|
+
- **`canonicalize` / `parseJsonStrict`**——严格的 RFC 8785(JCS)实现,同时以 `@aimarket/warden/jcs` 导出,
|
|
138
|
+
便于把另一份实现逐字节对照验证。超出 `MAX_SAFE_JSON_INTEGER` 只接受整数、遇到孤立代理项直接拒绝(而不是转
|
|
139
|
+
义),且每次拒绝都带原因码。
|
|
140
|
+
|
|
141
|
+
## 文档
|
|
142
|
+
|
|
143
|
+
| | |
|
|
144
|
+
|---|---|
|
|
145
|
+
| [门控链](docs/gates.zh.md) | 每一个规则层级、每一个发现码、综合评分如何构成,以及如何新增一个门控 |
|
|
146
|
+
| [已签名的威胁情报 feed](docs/threat-feed.zh.md) | 线上契约、三项检查,以及如何发布一个 WARDEN 会接受的 feed |
|
|
147
|
+
| [集成指南](docs/integration.zh.md) | 如何把 WARDEN 接入自己的 MCP 宿主、策略取舍,以及应当留档什么 |
|
|
148
|
+
| [实地普查:1 108 个公开 MCP 服务器](docs/mcp-survey.zh.md) | WARDEN 在真实的第三方工具定义上判了什么——拦截 50 个服务器,4 项成立,其余判错的六种方式 |
|
|
149
|
+
|
|
150
|
+
## 它不是什么
|
|
151
|
+
|
|
152
|
+
- **不是沙箱。** 这些都是进程内的 JS 判定。操作系统层面对 MCP 子进程的约束(seccomp/Landlock、
|
|
153
|
+
`sandbox-exec`)不在这里。
|
|
154
|
+
- **不是模型。** 整条链上任何位置都不调用 LLM。这正是 `vet()` 快速、离线且确定的原因——也正因如此,静态扫描
|
|
155
|
+
是正则形态的,会漏掉任何规则都没覆盖的改写说法。
|
|
156
|
+
- **不是信誉服务。** 早期版本里有一个门控,会向信任预言机索要一个它根本没有数据可算的评分,然后在一个请求都
|
|
157
|
+
没发出的情况下报告预言机不可达。该门控已被移除,并且只要有任何门控再次声称不可达,
|
|
158
|
+
`test/no-phantom-gate.test.ts` 就会失败。
|
|
159
|
+
- **不能替代你亲自读工具定义。** 11 条内置威胁记录是底线,不是目录。
|
|
160
|
+
|
|
161
|
+
## 开发
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npm install && npm run build && npm test # 141 项测试
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`test/packaging.test.ts` 正是让标题保持诚实的东西:一旦出现运行时依赖、任何源文件从包外 import、或者入口点不
|
|
168
|
+
再导出执行面,它就会失败。
|
|
169
|
+
|
|
170
|
+
被 [ARGUS](https://github.com/alexar76/argus)(参考宿主)、[MOMUS](https://github.com/alexar76/momus)
|
|
171
|
+
(发布方一侧)以及 AICOM 的 MCP 安全课程使用。
|
|
172
|
+
|
|
173
|
+
MIT © AICOM (alexar76)
|
package/README.md
CHANGED
|
@@ -1,12 +1,185 @@
|
|
|
1
|
-
#
|
|
1
|
+
# WARDEN — MCP security firewall
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!-- aicom-readme-badges -->
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://github.com/alexar76/warden/actions/workflows/ci.yml"><img src="docs/badges/ci.svg" alt="CI" /></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@aimarket/warden"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm version" /></a>
|
|
7
|
+
<img src="docs/badges/deps.svg" alt="Zero runtime dependencies" />
|
|
8
|
+
<img src="docs/badges/tests.svg" alt="141 tests passing" />
|
|
9
|
+
<img src="docs/badges/node.svg" alt="Node >= 20" />
|
|
10
|
+
<a href="LICENSE"><img src="docs/badges/license.svg" alt="License: MIT" /></a>
|
|
11
|
+
</p>
|
|
12
|
+
<!-- /aicom-readme-badges -->
|
|
4
13
|
|
|
5
|
-
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="docs/assets/hero.svg" alt="WARDEN — MCP security firewall: tool defs pass static-scan, threat-feed, origin, and pinning before a recorded verdict" width="920" />
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
> 🌐 **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)
|
|
20
|
+
|
|
21
|
+
An MCP server tells your agent what its tools do. The agent believes it — that sentence is the
|
|
22
|
+
attack surface. A tool description is prompt text delivered by a third party straight into your
|
|
23
|
+
model's context, and a schema field named `api_key` is a request for your secrets phrased as an API.
|
|
24
|
+
|
|
25
|
+
WARDEN vets a server **before any of its tools reach the model**, and returns a verdict you can
|
|
26
|
+
record: allow/block, a 0..1 score, the findings that produced it, a per-tool partition, and the
|
|
27
|
+
exact rule table that was in force.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @aimarket/warden
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Zero runtime dependencies.** The only import in the whole package is `node:crypto`. It is the
|
|
34
|
+
firewall out of [ARGUS](https://github.com/alexar76/argus), extracted so you can put it in front of
|
|
35
|
+
your own MCP host without adopting an agent.
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { Warden, ThreatFeed, silentLogger } from "@aimarket/warden";
|
|
41
|
+
|
|
42
|
+
const threatFeed = new ThreatFeed({ feedPublicKey: process.env.FEED_PUBKEY });
|
|
43
|
+
await threatFeed.load(process.env.FEED_URL); // omit → built-in deny-list only, no network
|
|
44
|
+
|
|
45
|
+
const pins = new Map();
|
|
46
|
+
const warden = Warden.create({
|
|
47
|
+
policy: {
|
|
48
|
+
blockAtSeverity: "high",
|
|
49
|
+
sensitiveToolPatterns: ["*delete*", "*transfer*", "*key*"],
|
|
50
|
+
allowUnknownServers: false, // fail-closed: only servers you declared
|
|
51
|
+
pinToolDefs: true,
|
|
52
|
+
},
|
|
53
|
+
threatFeed,
|
|
54
|
+
store: {
|
|
55
|
+
getPin: async (id) => pins.get(id),
|
|
56
|
+
putPin: async (p) => void pins.set(p.serverId, p),
|
|
57
|
+
},
|
|
58
|
+
log: silentLogger(), // or your own logger
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const verdict = await warden.vet(server, await client.listTools());
|
|
62
|
+
|
|
63
|
+
if (!verdict.allow) throw new Error(`blocked by ${verdict.decidedBy}`);
|
|
64
|
+
const usable = verdict.allowedTools; // a poisoned tool can be quarantined alone
|
|
65
|
+
await warden.approve(server, tools); // pin what the user accepted
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`vet()` performs **no network I/O**. The only request WARDEN ever makes is the threat-feed fetch you
|
|
69
|
+
asked for by passing a URL to `load()`.
|
|
70
|
+
|
|
71
|
+
## The gate chain
|
|
72
|
+
|
|
73
|
+
```mermaid
|
|
74
|
+
flowchart LR
|
|
75
|
+
T["tool defs<br/>from the server"] --> S["static scan<br/>25 rules"]
|
|
76
|
+
S --> F["threat feed<br/>11 built-ins + signed"]
|
|
77
|
+
F --> O["origin<br/>declared vs catalog"]
|
|
78
|
+
O --> P["pinning<br/>drift vs approval"]
|
|
79
|
+
P --> V["verdict<br/>allow · score · findings<br/>allowedTools / blockedTools"]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Gate | What it decides | Network | Fatal? |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| **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 |
|
|
85
|
+
| **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` |
|
|
86
|
+
| **origin** | Whether the operator declared this server or it arrived from a remote catalog | none | yes, under `allowUnknownServers: false` |
|
|
87
|
+
| **pinning** | Whether the tool defs still match what the user approved | none | yes, under `pinToolDefs: true` |
|
|
88
|
+
|
|
89
|
+
The composite score is the **product** of gate contributions, so one bad gate drags the whole server
|
|
90
|
+
down rather than being averaged away. Severity and blocking are separate axes: an `advisory` finding
|
|
91
|
+
is reported and never blocks and never costs a tool, at any `blockAtSeverity` — because "how much
|
|
92
|
+
attention does this deserve" and "is this a defect at all" are different questions, and encoding the
|
|
93
|
+
second as a low severity made it blocking again for anyone who tightened the threshold.
|
|
94
|
+
|
|
95
|
+
## The verdict is meant to be recorded
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
{
|
|
99
|
+
allow: false,
|
|
100
|
+
score: 0,
|
|
101
|
+
decidedBy: "threat-feed",
|
|
102
|
+
findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
|
|
103
|
+
allowedTools: ["add"],
|
|
104
|
+
blockedTools: ["sweeper"],
|
|
105
|
+
rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`rulesets` is not decoration. The same server scores differently under a later rule table, and
|
|
110
|
+
without the version *and* a digest over the rules there is no way to tell that apart from the server
|
|
111
|
+
having changed. A stored scan without them is not reproducible.
|
|
112
|
+
|
|
113
|
+
## Signed threat feed
|
|
114
|
+
|
|
115
|
+
WARDEN will not read an unsigned remote feed. The contract is deliberately boring:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
GET <your feed url>
|
|
119
|
+
{ "records": [ {pattern, severity, code, reason, source, scope}, … ],
|
|
120
|
+
"timestamp": 1786205907380, // epoch ms, integer — required
|
|
121
|
+
"signature": "f588d5a4…" // Ed25519 (hex) over the RFC 8785 canonical
|
|
122
|
+
} // form of {records, timestamp}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Three properties are checked, and **any failure keeps the built-in floor** rather than degrading to
|
|
126
|
+
no protection:
|
|
127
|
+
|
|
128
|
+
1. **authenticity** — Ed25519 against the key you pinned in advance (`feedPublicKey`);
|
|
129
|
+
2. **freshness** — the *signed* timestamp must be inside `maxAgeMs` (24 h by default), so whoever
|
|
130
|
+
serves the URL cannot replay a months-old snapshot and silently erase every record added since.
|
|
131
|
+
A signature says who wrote a document, never when you were handed it;
|
|
132
|
+
3. **determinism** — RFC 8785 canonical bytes, so publisher and verifier agree regardless of JSON
|
|
133
|
+
key order.
|
|
134
|
+
|
|
135
|
+
[MOMUS](https://github.com/alexar76/momus) is a reference publisher of this contract
|
|
136
|
+
(`/warden/threat-feed`) if you want something to point `load()` at.
|
|
137
|
+
|
|
138
|
+
## Also in the box
|
|
139
|
+
|
|
140
|
+
- **`EgressGuard`** — an outbound allowlist to wrap any request a tool makes. A tool reaching a host
|
|
141
|
+
you never listed is the classic phone-home tell. `*.example.com` matches subdomains; an empty
|
|
142
|
+
allowlist blocks everything rather than allowing everything.
|
|
143
|
+
- **`isSensitiveTool` / `classifyTools`** — glob classification of tools that must require per-call
|
|
144
|
+
approval. Sensitive tools stay *advertised*; they just cannot run unattended.
|
|
145
|
+
- **`canonicalize` / `parseJsonStrict`** — a strict RFC 8785 (JCS) implementation, also exported as
|
|
146
|
+
`@aimarket/warden/jcs` so another implementation can be byte-checked against it. Integers only
|
|
147
|
+
beyond `MAX_SAFE_JSON_INTEGER`, refusal (not escaping) on lone surrogates, and a reason code on
|
|
148
|
+
every refusal.
|
|
149
|
+
|
|
150
|
+
## Documentation
|
|
151
|
+
|
|
152
|
+
| | |
|
|
153
|
+
|---|---|
|
|
154
|
+
| [The gate chain](docs/gates.md) | Every rule tier, every finding code, how the composite score is built, and how to add a gate |
|
|
155
|
+
| [The signed threat feed](docs/threat-feed.md) | The wire contract, the three checks, and how to publish a feed WARDEN will accept |
|
|
156
|
+
| [Integration guide](docs/integration.md) | Wiring WARDEN into your own MCP host, policy choices, and what to record |
|
|
157
|
+
| [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 |
|
|
158
|
+
|
|
159
|
+
## What this is not
|
|
160
|
+
|
|
161
|
+
- **Not a sandbox.** These are in-process JS decisions. OS-level confinement of the MCP child
|
|
162
|
+
process (seccomp/Landlock, `sandbox-exec`) is not here.
|
|
163
|
+
- **Not a model.** No LLM is called anywhere in the chain. That is why `vet()` is fast, offline and
|
|
164
|
+
deterministic — and why the static scan is regex-shaped and will miss a paraphrase no rule covers.
|
|
165
|
+
- **Not a reputation service.** An earlier version had a gate that asked a trust oracle for a score
|
|
166
|
+
it had no data to compute, then reported the oracle as unreachable without having sent a request.
|
|
167
|
+
It was removed, and `test/no-phantom-gate.test.ts` fails if any gate ever claims unreachability
|
|
168
|
+
again.
|
|
169
|
+
- **Not a substitute for reading the tool defs.** 11 built-in threat records is a floor, not a
|
|
170
|
+
catalog.
|
|
171
|
+
|
|
172
|
+
## Development
|
|
6
173
|
|
|
7
174
|
```bash
|
|
8
|
-
npm install
|
|
175
|
+
npm install && npm run build && npm test # 141 tests
|
|
9
176
|
```
|
|
10
177
|
|
|
11
|
-
|
|
12
|
-
|
|
178
|
+
`test/packaging.test.ts` is what keeps the headline honest: it fails if a runtime dependency appears,
|
|
179
|
+
if any source file imports outside the package, or if the entry point stops exporting the
|
|
180
|
+
enforcement surface.
|
|
181
|
+
|
|
182
|
+
Used by [ARGUS](https://github.com/alexar76/argus) (the reference host), [MOMUS](https://github.com/alexar76/momus)
|
|
183
|
+
(the publisher side), and the AICOM MCP-security course.
|
|
184
|
+
|
|
185
|
+
MIT © AICOM (alexar76)
|
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"}
|