@guardbee/guard-core 0.1.0 → 0.2.1
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/README.md +14 -4
- package/TR.md +36 -0
- package/ZH.md +36 -0
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/injection.d.ts +46 -1
- package/dist/cjs/injection.d.ts.map +1 -1
- package/dist/cjs/injection.js +322 -34
- package/dist/cjs/injection.js.map +1 -1
- package/dist/cjs/pii.d.ts.map +1 -1
- package/dist/cjs/pii.js +54 -11
- package/dist/cjs/pii.js.map +1 -1
- package/dist/cjs/secrets.d.ts +18 -0
- package/dist/cjs/secrets.d.ts.map +1 -0
- package/dist/cjs/secrets.js +278 -0
- package/dist/cjs/secrets.js.map +1 -0
- package/dist/cjs/validators.d.ts +8 -0
- package/dist/cjs/validators.d.ts.map +1 -1
- package/dist/cjs/validators.js +30 -0
- package/dist/cjs/validators.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/injection.d.ts +46 -1
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +320 -33
- package/dist/injection.js.map +1 -1
- package/dist/pii.d.ts.map +1 -1
- package/dist/pii.js +55 -12
- package/dist/pii.js.map +1 -1
- package/dist/secrets.d.ts +18 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +275 -0
- package/dist/secrets.js.map +1 -0
- package/dist/validators.d.ts +8 -0
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +28 -0
- package/dist/validators.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @guardbee/guard-core
|
|
2
2
|
|
|
3
|
+
**🇬🇧 English** | [🇹🇷 Türkçe](TR.md) | [🇨🇳 中文](ZH.md)
|
|
4
|
+
|
|
3
5
|
Shared detectors used by other GuardBee MCP packages. Not an MCP server on its own, just a library — you don't need to install this package directly.
|
|
4
6
|
|
|
5
7
|
It exists so the same rule runs everywhere: a static scanner flagging a pattern and the runtime proxy blocking it use one implementation.
|
|
@@ -8,10 +10,12 @@ It exists so the same rule runs everywhere: a static scanner flagging a pattern
|
|
|
8
10
|
|
|
9
11
|
| Export | Used by | Purpose |
|
|
10
12
|
|---|---|---|
|
|
11
|
-
| `maskPiiInText`, `maskPiiInValue`, `PII_PATTERNS` | security-proxy | Mask TC Kimlik No, IBAN, card,
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
13
|
+
| `maskPiiInText`, `maskPiiInValue`, `PII_PATTERNS` | security-proxy | Mask TC Kimlik No, VKN, IBAN, card, phone, email, provider API keys, private keys, connection strings and JWTs in tool results; an optional replacer turns matches into tokens |
|
|
14
|
+
| `SECRET_RULES` | secret-scanner, security-proxy | 38 secret rules (provider keys and tokens, private keys, connection strings, JWT). The scanner reports all of them; the proxy masks the 32 that are not marked `broad` |
|
|
15
|
+
| `isValidTcKimlik`, `isValidVkn`, `isValidIban`, `isValidLuhn`, `isValidTrPhone` | security-proxy, prompt-leak-scanner | Validation after a regex match |
|
|
16
|
+
| `INJECTION_RULES`, `findInjections` | prompt-injection-scanner, security-proxy | 27 injection rules in English and Turkish, with base64-decoding; the scanner uses the 17 precise ones |
|
|
17
|
+
| `scanForPromptInjection`, `scanToolResult` | security-proxy | Block on a precise high/critical rule, warn on anything else |
|
|
18
|
+
| `classifyTool`, `CAPABILITY_RULES` | toxic-flow-auditor, security-proxy | Label a tool as untrusted-content / sensitive-data / exfiltration / destructive (lethal trifecta) |
|
|
15
19
|
|
|
16
20
|
## Checksum-validated masking
|
|
17
21
|
|
|
@@ -20,7 +24,13 @@ A regex alone masks any 11-digit number as a TC Kimlik No and any 16-digit numbe
|
|
|
20
24
|
| Pattern | Check |
|
|
21
25
|
|---|---|
|
|
22
26
|
| TC Kimlik No | Official 10th/11th digit algorithm |
|
|
27
|
+
| VKN (tax number) | Gelir İdaresi check digit, and a `VKN` / `Vergi No` label before the number |
|
|
23
28
|
| IBAN | ISO 7064 MOD97-10 |
|
|
24
29
|
| Card number | Luhn |
|
|
30
|
+
| Phone (TR) | Mobile (5xx), landline (2xx–4xx) or 850 code; written with a +90 / 0 prefix or grouped with spaces/dashes, and not part of a URL or id |
|
|
25
31
|
|
|
26
32
|
A match that fails its check (an order number, a tracking code) is left unchanged.
|
|
33
|
+
|
|
34
|
+
## Precise and broad rules
|
|
35
|
+
|
|
36
|
+
Injection and secret rules carry a `broad` flag for patterns that also match ordinary text: "act as", "developer mode", any 40-character base64 run, a publishable Stripe key. Static scanners that show a finding to a person skip broad injection rules; the runtime proxy never blocks or masks on a broad rule, because a false match there would also mark the session as holding sensitive data or stop a legitimate call.
|
package/TR.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @guardbee/guard-core
|
|
2
|
+
|
|
3
|
+
[🇬🇧 English](README.md) | **🇹🇷 Türkçe** | [🇨🇳 中文](ZH.md)
|
|
4
|
+
|
|
5
|
+
Diğer GuardBee MCP paketlerinin kullandığı ortak dedektörler. Kendi başına bir MCP server değil, sadece bir kütüphane — bu paketi doğrudan kurmanız gerekmez.
|
|
6
|
+
|
|
7
|
+
Amacı, aynı kuralın her yerde çalışması: bir kalıbı raporlayan statik tarayıcı ile onu çalışma anında engelleyen proxy tek bir uygulamayı kullanır.
|
|
8
|
+
|
|
9
|
+
## İçinde neler var
|
|
10
|
+
|
|
11
|
+
| Export | Kullanan | Amaç |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `maskPiiInText`, `maskPiiInValue`, `PII_PATTERNS` | security-proxy | Tool sonuçlarında TC Kimlik No, VKN, IBAN, kart, telefon, e-posta, sağlayıcı API key'leri, özel anahtarlar, bağlantı dizeleri ve JWT'leri maskeler; isteğe bağlı bir replacer eşleşmeleri token'a çevirir |
|
|
14
|
+
| `SECRET_RULES` | secret-scanner, security-proxy | 38 secret kuralı (sağlayıcı anahtar ve token'ları, özel anahtarlar, bağlantı dizeleri, JWT). Tarayıcı hepsini raporlar; proxy `broad` işaretli olmayan 32'sini maskeler |
|
|
15
|
+
| `isValidTcKimlik`, `isValidVkn`, `isValidIban`, `isValidLuhn`, `isValidTrPhone` | security-proxy, prompt-leak-scanner | Regex eşleşmesinden sonra doğrulama |
|
|
16
|
+
| `INJECTION_RULES`, `findInjections` | prompt-injection-scanner, security-proxy | İngilizce ve Türkçe 27 injection kuralı, base64 çözme dahil; tarayıcı 17 kesin kuralı kullanır |
|
|
17
|
+
| `scanForPromptInjection`, `scanToolResult` | security-proxy | Kesin bir high/critical kuralda engeller, geri kalan her şeyde uyarır |
|
|
18
|
+
| `classifyTool`, `CAPABILITY_RULES` | toxic-flow-auditor, security-proxy | Bir tool'u untrusted-content / sensitive-data / exfiltration / destructive olarak etiketler (lethal trifecta) |
|
|
19
|
+
|
|
20
|
+
## Checksum doğrulamalı maskeleme
|
|
21
|
+
|
|
22
|
+
Tek başına regex, 11 haneli her sayıyı TC Kimlik No, 16 haneli her sayıyı kart numarası sanar. Artık her eşleşme maskelenmeden önce kontrol edilir:
|
|
23
|
+
|
|
24
|
+
| Kalıp | Kontrol |
|
|
25
|
+
|---|---|
|
|
26
|
+
| TC Kimlik No | Resmi 10. ve 11. hane algoritması |
|
|
27
|
+
| VKN (vergi kimlik no) | Gelir İdaresi kontrol hanesi ve sayıdan önce bir `VKN` / `Vergi No` etiketi |
|
|
28
|
+
| IBAN | ISO 7064 MOD97-10 |
|
|
29
|
+
| Kart numarası | Luhn |
|
|
30
|
+
| Telefon (TR) | Cep (5xx), sabit hat (2xx–4xx) veya 850 kodu; +90 / 0 önekiyle ya da boşluk/tire ile gruplanmış yazılmış olmalı, bir URL'nin veya kimliğin parçası olmamalı |
|
|
31
|
+
|
|
32
|
+
Kontrolden geçemeyen eşleşme (sipariş no, takip kodu) olduğu gibi bırakılır.
|
|
33
|
+
|
|
34
|
+
## Kesin ve genel kurallar
|
|
35
|
+
|
|
36
|
+
Injection ve secret kuralları, sıradan metinde de eşleşen kalıplar için bir `broad` bayrağı taşır: "act as", "developer mode", 40 karakterlik herhangi bir base64 dizisi, yayınlanabilir (publishable) bir Stripe anahtarı. Bulguyu bir kişiye gösteren statik tarayıcılar genel injection kurallarını atlar; çalışma anındaki proxy ise genel bir kurala dayanarak asla engelleme veya maskeleme yapmaz, çünkü oradaki yanlış bir eşleşme oturumu hassas veri tutuyor diye de işaretler ya da meşru bir çağrıyı durdurur.
|
package/ZH.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @guardbee/guard-core
|
|
2
|
+
|
|
3
|
+
[🇬🇧 English](README.md) | [🇹🇷 Türkçe](TR.md) | **🇨🇳 中文**
|
|
4
|
+
|
|
5
|
+
其他 GuardBee MCP 包共用的检测规则库。它本身不是 MCP 服务器,只是一个库——你不需要直接安装这个包。
|
|
6
|
+
|
|
7
|
+
它存在的目的是让同一条规则在所有地方运行:报告某个模式的静态扫描器,和在运行时拦截它的代理,使用的是同一份实现。
|
|
8
|
+
|
|
9
|
+
## 包含内容
|
|
10
|
+
|
|
11
|
+
| 导出 | 使用方 | 用途 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `maskPiiInText`、`maskPiiInValue`、`PII_PATTERNS` | security-proxy | 对工具结果中的土耳其身份证号(TC Kimlik No)、税号(VKN)、IBAN、银行卡、电话、电子邮件、服务商 API 密钥、私钥、数据库连接串和 JWT 进行脱敏;可选的 replacer 可以把匹配项转换为令牌 |
|
|
14
|
+
| `SECRET_RULES` | secret-scanner、security-proxy | 38 条密钥规则(服务商密钥和令牌、私钥、数据库连接串、JWT)。扫描器会报告全部规则;代理只对未标记 `broad` 的 32 条进行脱敏 |
|
|
15
|
+
| `isValidTcKimlik`、`isValidVkn`、`isValidIban`、`isValidLuhn`、`isValidTrPhone` | security-proxy、prompt-leak-scanner | 正则匹配之后的校验 |
|
|
16
|
+
| `INJECTION_RULES`、`findInjections` | prompt-injection-scanner、security-proxy | 27 条英文和土耳其语注入规则,包含 base64 解码;扫描器使用其中 17 条精确规则 |
|
|
17
|
+
| `scanForPromptInjection`、`scanToolResult` | security-proxy | 命中精确的 high/critical 规则时拦截,其余情况只警告 |
|
|
18
|
+
| `classifyTool`、`CAPABILITY_RULES` | toxic-flow-auditor、security-proxy | 把工具标记为 untrusted-content / sensitive-data / exfiltration / destructive(lethal trifecta) |
|
|
19
|
+
|
|
20
|
+
## 经校验和验证的脱敏
|
|
21
|
+
|
|
22
|
+
单靠正则,会把任何 11 位数字当成 TC Kimlik No,把任何 16 位数字当成银行卡号。现在每个匹配在脱敏前都会先经过检查:
|
|
23
|
+
|
|
24
|
+
| 模式 | 检查 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| TC Kimlik No | 官方的第 10、11 位校验算法 |
|
|
27
|
+
| VKN(税号) | 土耳其税务局(Gelir İdaresi)校验位,且数字前需有 `VKN` / `Vergi No` 标签 |
|
|
28
|
+
| IBAN | ISO 7064 MOD97-10 |
|
|
29
|
+
| 银行卡号 | Luhn |
|
|
30
|
+
| 电话(土耳其) | 手机(5xx)、座机(2xx–4xx)或 850 号段;需带 +90 / 0 前缀,或用空格/短横线分组书写,且不能是 URL 或 ID 的一部分 |
|
|
31
|
+
|
|
32
|
+
未通过检查的匹配(订单号、快递单号)保持原样。
|
|
33
|
+
|
|
34
|
+
## 精确规则与通用规则
|
|
35
|
+
|
|
36
|
+
注入规则和密钥规则都带有一个 `broad` 标记,用于那些在普通文本中也会匹配的模式:"act as"、"developer mode"、任意 40 个字符的 base64 串、可公开的 Stripe publishable key。把结果展示给人看的静态扫描器会跳过通用注入规则;运行时代理则从不基于通用规则进行拦截或脱敏,因为在那里一次误匹配还会把会话标记为持有敏感数据,或拦下一次正常调用。
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
export { isValidTcKimlik, isValidLuhn, isValidIban } from "./validators.js";
|
|
1
|
+
export { isValidTcKimlik, isValidLuhn, isValidIban, isValidVkn, isValidTrPhone } from "./validators.js";
|
|
2
2
|
export { PII_PATTERNS, maskPiiInText, maskPiiInValue } from "./pii.js";
|
|
3
3
|
export type { PiiPattern, PiiReplacer } from "./pii.js";
|
|
4
|
-
export {
|
|
5
|
-
export type {
|
|
4
|
+
export { SECRET_RULES } from "./secrets.js";
|
|
5
|
+
export type { SecretRule } from "./secrets.js";
|
|
6
|
+
export { INJECTION_RULES, INJECTION_PATTERNS, findInjections, scanForPromptInjection, scanToolResult, } from "./injection.js";
|
|
7
|
+
export type { ScanResult, InjectionRule, InjectionCategory, InjectionFinding } from "./injection.js";
|
|
6
8
|
export { CAPABILITY_RULES, classifyTool, textOf } from "./classify.js";
|
|
7
9
|
export type { Capability, FlowCategory, ToolRecord } from "./classify.js";
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACxG,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACvE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,sBAAsB,EACtB,cAAc,GACf,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACrG,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.textOf = exports.classifyTool = exports.CAPABILITY_RULES = exports.scanToolResult = exports.scanForPromptInjection = exports.INJECTION_PATTERNS = exports.maskPiiInValue = exports.maskPiiInText = exports.PII_PATTERNS = exports.isValidIban = exports.isValidLuhn = exports.isValidTcKimlik = void 0;
|
|
3
|
+
exports.textOf = exports.classifyTool = exports.CAPABILITY_RULES = exports.scanToolResult = exports.scanForPromptInjection = exports.findInjections = exports.INJECTION_PATTERNS = exports.INJECTION_RULES = exports.SECRET_RULES = exports.maskPiiInValue = exports.maskPiiInText = exports.PII_PATTERNS = exports.isValidTrPhone = exports.isValidVkn = exports.isValidIban = exports.isValidLuhn = exports.isValidTcKimlik = void 0;
|
|
4
4
|
var validators_js_1 = require("./validators.js");
|
|
5
5
|
Object.defineProperty(exports, "isValidTcKimlik", { enumerable: true, get: function () { return validators_js_1.isValidTcKimlik; } });
|
|
6
6
|
Object.defineProperty(exports, "isValidLuhn", { enumerable: true, get: function () { return validators_js_1.isValidLuhn; } });
|
|
7
7
|
Object.defineProperty(exports, "isValidIban", { enumerable: true, get: function () { return validators_js_1.isValidIban; } });
|
|
8
|
+
Object.defineProperty(exports, "isValidVkn", { enumerable: true, get: function () { return validators_js_1.isValidVkn; } });
|
|
9
|
+
Object.defineProperty(exports, "isValidTrPhone", { enumerable: true, get: function () { return validators_js_1.isValidTrPhone; } });
|
|
8
10
|
var pii_js_1 = require("./pii.js");
|
|
9
11
|
Object.defineProperty(exports, "PII_PATTERNS", { enumerable: true, get: function () { return pii_js_1.PII_PATTERNS; } });
|
|
10
12
|
Object.defineProperty(exports, "maskPiiInText", { enumerable: true, get: function () { return pii_js_1.maskPiiInText; } });
|
|
11
13
|
Object.defineProperty(exports, "maskPiiInValue", { enumerable: true, get: function () { return pii_js_1.maskPiiInValue; } });
|
|
14
|
+
var secrets_js_1 = require("./secrets.js");
|
|
15
|
+
Object.defineProperty(exports, "SECRET_RULES", { enumerable: true, get: function () { return secrets_js_1.SECRET_RULES; } });
|
|
12
16
|
var injection_js_1 = require("./injection.js");
|
|
17
|
+
Object.defineProperty(exports, "INJECTION_RULES", { enumerable: true, get: function () { return injection_js_1.INJECTION_RULES; } });
|
|
13
18
|
Object.defineProperty(exports, "INJECTION_PATTERNS", { enumerable: true, get: function () { return injection_js_1.INJECTION_PATTERNS; } });
|
|
19
|
+
Object.defineProperty(exports, "findInjections", { enumerable: true, get: function () { return injection_js_1.findInjections; } });
|
|
14
20
|
Object.defineProperty(exports, "scanForPromptInjection", { enumerable: true, get: function () { return injection_js_1.scanForPromptInjection; } });
|
|
15
21
|
Object.defineProperty(exports, "scanToolResult", { enumerable: true, get: function () { return injection_js_1.scanToolResult; } });
|
|
16
22
|
var classify_js_1 = require("./classify.js");
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAAwG;AAA/F,gHAAA,eAAe,OAAA;AAAE,4GAAA,WAAW,OAAA;AAAE,4GAAA,WAAW,OAAA;AAAE,2GAAA,UAAU,OAAA;AAAE,+GAAA,cAAc,OAAA;AAC9E,mCAAuE;AAA9D,sGAAA,YAAY,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,wGAAA,cAAc,OAAA;AAEpD,2CAA4C;AAAnC,0GAAA,YAAY,OAAA;AAErB,+CAMwB;AALtB,+GAAA,eAAe,OAAA;AACf,kHAAA,kBAAkB,OAAA;AAClB,8GAAA,cAAc,OAAA;AACd,sHAAA,sBAAsB,OAAA;AACtB,8GAAA,cAAc,OAAA;AAGhB,6CAAuE;AAA9D,+GAAA,gBAAgB,OAAA;AAAE,2GAAA,YAAY,OAAA;AAAE,qGAAA,MAAM,OAAA"}
|
package/dist/cjs/injection.d.ts
CHANGED
|
@@ -7,8 +7,53 @@ export type ScanResult = {
|
|
|
7
7
|
action: "warn";
|
|
8
8
|
reason: string;
|
|
9
9
|
};
|
|
10
|
+
export type InjectionCategory = "instruction-override" | "role-spoofing" | "hidden-text" | "direct-address" | "exfiltration";
|
|
11
|
+
export interface InjectionRule {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
category: InjectionCategory;
|
|
15
|
+
/** Always global (`g`): callers iterate matches. */
|
|
16
|
+
pattern: RegExp;
|
|
17
|
+
severity: "critical" | "high" | "medium" | "low";
|
|
18
|
+
/** Why it is risky and what to do — shown with a finding. */
|
|
19
|
+
recommendation: string;
|
|
20
|
+
/**
|
|
21
|
+
* A generic phrase that also shows up in ordinary text ("act as", "developer
|
|
22
|
+
* mode"). Never blocks a call, only warns; the static scanner skips it.
|
|
23
|
+
*/
|
|
24
|
+
broad?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Content that will reach a model: a tool result, a RAG chunk, a scraped page.
|
|
28
|
+
* Regexes are heuristics that aim for few false positives, not an intent model.
|
|
29
|
+
* The precise rules come from prompt-injection-scanner; the broad ones are the
|
|
30
|
+
* phrases security-proxy 0.x blocked on.
|
|
31
|
+
*/
|
|
32
|
+
export declare const INJECTION_RULES: InjectionRule[];
|
|
33
|
+
/** @deprecated The bare regexes of INJECTION_RULES; use INJECTION_RULES. */
|
|
10
34
|
export declare const INJECTION_PATTERNS: RegExp[];
|
|
35
|
+
export interface InjectionFinding {
|
|
36
|
+
ruleId: string;
|
|
37
|
+
name: string;
|
|
38
|
+
category: InjectionCategory;
|
|
39
|
+
severity: InjectionRule["severity"];
|
|
40
|
+
broad: boolean;
|
|
41
|
+
match: string;
|
|
42
|
+
index: number;
|
|
43
|
+
/** Set when the phrase was found inside a decoded base64 run. */
|
|
44
|
+
encoding?: "base64";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Every rule match in the text, plus precise-rule matches inside base64 runs
|
|
48
|
+
* that decode to text (an instruction hidden from both the reader and a plain
|
|
49
|
+
* pattern scan).
|
|
50
|
+
*/
|
|
51
|
+
export declare function findInjections(text: string): InjectionFinding[];
|
|
52
|
+
/**
|
|
53
|
+
* A precise high/critical rule → `mode` (block or warn). A broad or medium
|
|
54
|
+
* rule alone → warn, never block.
|
|
55
|
+
*/
|
|
11
56
|
export declare function scanForPromptInjection(input: unknown, mode?: "block" | "warn"): ScanResult;
|
|
12
|
-
/** Same
|
|
57
|
+
/** Same rules as request scanning, applied to a tool result the model will read. */
|
|
13
58
|
export declare function scanToolResult(content: unknown, mode?: "block" | "warn"): ScanResult;
|
|
14
59
|
//# sourceMappingURL=injection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"injection.d.ts","sourceRoot":"","sources":["../../src/injection.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACnB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"injection.d.ts","sourceRoot":"","sources":["../../src/injection.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACnB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,MAAM,MAAM,iBAAiB,GACzB,sBAAsB,GACtB,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,cAAc,CAAC;AAEnB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACjD,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,aAAa,EAiQ1C,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,EAAE,MAAM,EAAgD,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAsCD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAK/D;AAiBD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,IAAI,GAAE,OAAO,GAAG,MAAgB,GAC/B,UAAU,CASZ;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,OAAO,GAAG,MAAgB,GAAG,UAAU,CAO7F"}
|
package/dist/cjs/injection.js
CHANGED
|
@@ -1,48 +1,336 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.INJECTION_PATTERNS = void 0;
|
|
3
|
+
exports.INJECTION_PATTERNS = exports.INJECTION_RULES = void 0;
|
|
4
|
+
exports.findInjections = findInjections;
|
|
4
5
|
exports.scanForPromptInjection = scanForPromptInjection;
|
|
5
6
|
exports.scanToolResult = scanToolResult;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Content that will reach a model: a tool result, a RAG chunk, a scraped page.
|
|
9
|
+
* Regexes are heuristics that aim for few false positives, not an intent model.
|
|
10
|
+
* The precise rules come from prompt-injection-scanner; the broad ones are the
|
|
11
|
+
* phrases security-proxy 0.x blocked on.
|
|
12
|
+
*/
|
|
13
|
+
exports.INJECTION_RULES = [
|
|
14
|
+
// ── Instruction override ────────────────────────────────────────────────────
|
|
15
|
+
{
|
|
16
|
+
id: "instruction_override_phrase",
|
|
17
|
+
name: "Content tells the model to ignore its prior instructions",
|
|
18
|
+
category: "instruction-override",
|
|
19
|
+
pattern: /\b(?:ignore|disregard|forget)\s+(?:all\s+|any\s+)?(?:previous|prior|above|earlier)\s+(?:instructions?|prompts?|rules?|context|directives?)\b/gi,
|
|
20
|
+
severity: "high",
|
|
21
|
+
recommendation: "This document contains a classic instruction-override phrase. If it enters a model's context via RAG retrieval or a tool result, it can hijack the model's behavior for the rest of the turn. Strip or quarantine this content before it reaches the model, or wrap retrieved content in clear data delimiters the model is instructed to never treat as commands.",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "instruction_override_tr",
|
|
25
|
+
name: "Content tells the model, in Turkish, to ignore its prior instructions",
|
|
26
|
+
category: "instruction-override",
|
|
27
|
+
pattern: /(?<!\p{L})(?:önceki|yukarıdaki|tüm|bütün)\s+(?:talimatları|talimatlar[ıi]n[ıi]|kuralları|yönergeleri|komutları)\s+(?:yok\s+say|unut|görmezden\s+gel|dikkate\s+alma|umursama)/giu,
|
|
28
|
+
severity: "high",
|
|
29
|
+
recommendation: "The Turkish form of an instruction-override phrase ('önceki talimatları yok say'). Pattern lists written only in English miss it. Treat the content as untrusted input and keep it out of the model's instructions.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "dan_mode",
|
|
33
|
+
name: "Content switches the model into 'DAN mode'",
|
|
34
|
+
category: "instruction-override",
|
|
35
|
+
pattern: /\bDAN\s+mode\b/gi,
|
|
36
|
+
severity: "high",
|
|
37
|
+
recommendation: "'DAN mode' is a well-known jailbreak persona with no use in ordinary content. Quarantine content that asks for it.",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "you_are_now",
|
|
41
|
+
name: "Content assigns the model a new identity ('you are now …')",
|
|
42
|
+
category: "instruction-override",
|
|
43
|
+
pattern: /\byou\s+are\s+now\s+(?:a\s+)?(?!an?\s+AI\b|a\s+language\b)/gi,
|
|
44
|
+
severity: "medium",
|
|
45
|
+
broad: true,
|
|
46
|
+
recommendation: "Often part of a role-override injection, but also common in ordinary writing. Review the surrounding text.",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "act_as",
|
|
50
|
+
name: "Content asks the model to act as something else",
|
|
51
|
+
category: "instruction-override",
|
|
52
|
+
pattern: /\bact\s+as\s+(?:if\s+you\s+are\s+)?(?!an?\s+AI\b|a\s+language\b)/gi,
|
|
53
|
+
severity: "medium",
|
|
54
|
+
broad: true,
|
|
55
|
+
recommendation: "Often part of a role-override injection, but also common in ordinary writing ('act as a proxy'). Review the surrounding text.",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "pretend_to_be",
|
|
59
|
+
name: "Content asks the model to pretend to be something else",
|
|
60
|
+
category: "instruction-override",
|
|
61
|
+
pattern: /\bpretend\s+(?:you\s+are|to\s+be)\s+(?!an?\s+AI\b|a\s+language\b)/gi,
|
|
62
|
+
severity: "medium",
|
|
63
|
+
broad: true,
|
|
64
|
+
recommendation: "Often part of a role-override injection. Review the surrounding text.",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "role_override_tr",
|
|
68
|
+
name: "Content assigns the model a new identity, in Turkish ('sen artık …')",
|
|
69
|
+
category: "instruction-override",
|
|
70
|
+
pattern: /(?<!\p{L})(?:sen\s+artık|bundan\s+sonra\s+sen)\s+(?=\p{L})/giu,
|
|
71
|
+
severity: "medium",
|
|
72
|
+
broad: true,
|
|
73
|
+
recommendation: "Often part of a role-override injection, but also ordinary Turkish. Review the surrounding text.",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "jailbreak_word",
|
|
77
|
+
name: "Content mentions a jailbreak",
|
|
78
|
+
category: "instruction-override",
|
|
79
|
+
pattern: /\bjailbreak(?:ing|s)?\b/gi,
|
|
80
|
+
severity: "medium",
|
|
81
|
+
broad: true,
|
|
82
|
+
recommendation: "Articles about LLM security use this word too; on its own it is a weak signal.",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "developer_mode",
|
|
86
|
+
name: "Content asks for a 'developer mode'",
|
|
87
|
+
category: "instruction-override",
|
|
88
|
+
pattern: /\bdeveloper\s+mode\b/gi,
|
|
89
|
+
severity: "medium",
|
|
90
|
+
broad: true,
|
|
91
|
+
recommendation: "A common jailbreak framing, and a common phrase in software documentation.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "bypass_safety",
|
|
95
|
+
name: "Content tells the model to bypass its safety measures",
|
|
96
|
+
category: "instruction-override",
|
|
97
|
+
pattern: /\b(?:bypass|override|disable)\s+(?:the\s+|your\s+|all\s+)?(?:safety|safeguards?|guardrails?|content\s+filters?)\b/gi,
|
|
98
|
+
severity: "high",
|
|
99
|
+
recommendation: "Content that tells a model to switch off its safety measures is an injection attempt. Quarantine it.",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "bypass_policy",
|
|
103
|
+
name: "Content mentions bypassing a filter, restriction or policy",
|
|
104
|
+
category: "instruction-override",
|
|
105
|
+
pattern: /\b(?:bypass|override)\s+(?:the\s+)?(?:filters?|restrictions?|polic(?:y|ies))\b/gi,
|
|
106
|
+
severity: "medium",
|
|
107
|
+
broad: true,
|
|
108
|
+
recommendation: "An injection phrase, but also everyday IT language ('override policy settings').",
|
|
109
|
+
},
|
|
110
|
+
// ── Role spoofing ────────────────────────────────────────────────────────────
|
|
111
|
+
{
|
|
112
|
+
id: "system_role_spoof",
|
|
113
|
+
name: "Content fakes a 'System:' role label",
|
|
114
|
+
category: "role-spoofing",
|
|
115
|
+
pattern: /(?:^|\n)\s*(?:#{1,3}\s*)?SYSTEM\s*:\s*\S/gim,
|
|
116
|
+
severity: "medium",
|
|
117
|
+
recommendation: "A line formatted like a chat-template system turn ('System: ...') inside retrieved content can trick a model (or a naive prompt-assembly step) into treating it as a real system instruction. Verify this is legitimate document content, not an injection attempt, before trusting it in a RAG/context pipeline.",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "chat_template_marker_injection",
|
|
121
|
+
name: "Raw chat-template control tokens embedded in content",
|
|
122
|
+
category: "role-spoofing",
|
|
123
|
+
pattern: /<\|(?:im_start|im_end|system|endoftext)\|>|\[INST\]|\[\/INST\]/g,
|
|
124
|
+
severity: "high",
|
|
125
|
+
recommendation: "Literal chat-template control tokens (e.g. <|im_start|>, [INST]) have no legitimate reason to appear in ordinary document content. If a model's tokenizer doesn't strip these, embedded tokens can hijack the conversation structure itself. Strip or escape these sequences before this content enters any model context.",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "system_tag",
|
|
129
|
+
name: "Content wraps text in a <system> tag",
|
|
130
|
+
category: "role-spoofing",
|
|
131
|
+
pattern: /<\s*\/?\s*system\s*>/gi,
|
|
132
|
+
severity: "high",
|
|
133
|
+
recommendation: "A <system> tag in data tries to pass the text off as a system message. Strip or escape it before the content reaches a model.",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "system_prompt_label",
|
|
137
|
+
name: "Content contains a 'system prompt:' label",
|
|
138
|
+
category: "role-spoofing",
|
|
139
|
+
pattern: /\bsystem\s+prompt\s*:/gi,
|
|
140
|
+
severity: "medium",
|
|
141
|
+
broad: true,
|
|
142
|
+
recommendation: "May introduce a fake system prompt; also appears in documentation about prompts.",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "system_bracket",
|
|
146
|
+
name: "Content contains a [SYSTEM] marker",
|
|
147
|
+
category: "role-spoofing",
|
|
148
|
+
pattern: /\[SYSTEM\]/gi,
|
|
149
|
+
severity: "medium",
|
|
150
|
+
broad: true,
|
|
151
|
+
recommendation: "May fake a system turn; also used as a log-level tag.",
|
|
152
|
+
},
|
|
153
|
+
// ── Hidden text ──────────────────────────────────────────────────────────────
|
|
154
|
+
{
|
|
155
|
+
id: "hidden_zero_width_chars",
|
|
156
|
+
name: "Zero-width characters that hide text from a human reviewer",
|
|
157
|
+
category: "hidden-text",
|
|
158
|
+
pattern: /[]/g,
|
|
159
|
+
severity: "medium",
|
|
160
|
+
recommendation: "Zero-width space/word-joiner characters render invisibly but are still read by a model. They're a common way to hide an injection payload from someone eyeballing the document while it still reaches the model. Inspect the surrounding text for hidden content, and consider stripping these characters before ingestion.",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "unicode_tag_smuggling",
|
|
164
|
+
name: "Invisible Unicode tag characters (ASCII smuggling)",
|
|
165
|
+
category: "hidden-text",
|
|
166
|
+
// A run of tag characters, except the one legitimate use: a subdivision flag
|
|
167
|
+
// emoji (🏴 + 1–6 lowercase/digit tags + CANCEL TAG, e.g. England). A run that
|
|
168
|
+
// starts after a flag or a tag is part of that flag; text hidden after a
|
|
169
|
+
// complete flag still matches through the second branch.
|
|
170
|
+
pattern: /(?<![\u{1F3F4}\u{E0000}-\u{E007F}])[\u{E0000}-\u{E007F}]+|\u{1F3F4}(?![\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,6}\u{E007F}(?![\u{E0000}-\u{E007F}]))[\u{E0000}-\u{E007F}]+/gu,
|
|
171
|
+
severity: "critical",
|
|
172
|
+
recommendation: "Unicode tag characters (U+E0000–U+E007F) mirror ASCII but render as nothing. Text encoded in them is invisible to a person and readable by many models — a known way to smuggle instructions. Apart from subdivision flag emoji, ordinary text never needs them; strip them before the content reaches a model.",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "bidi_control_chars",
|
|
176
|
+
name: "Bidirectional control characters that reorder displayed text",
|
|
177
|
+
category: "hidden-text",
|
|
178
|
+
pattern: /[--]/g,
|
|
179
|
+
severity: "medium",
|
|
180
|
+
recommendation: "Bidi embedding/override characters make text display in a different order than it is read (the 'Trojan Source' technique). Right-to-left languages rarely need the override forms; check what the text says once the controls are removed.",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: "css_hidden_text_with_instruction",
|
|
184
|
+
name: "CSS-hidden element containing injection-flavored language",
|
|
185
|
+
category: "hidden-text",
|
|
186
|
+
// `color` alone, not `background-color`; the language must read as an instruction,
|
|
187
|
+
// not just contain "ignore" (as in `<!-- prettier-ignore -->`).
|
|
188
|
+
pattern: /style\s*=\s*["'][^"']*(?:display\s*:\s*none|visibility\s*:\s*hidden|font-size\s*:\s*0(?:px)?|(?<![\w-])color\s*:\s*(?:white|#fff(?:fff)?\b|transparent))[^"']*["'][\s\S]{0,200}?(?:(?<![\w-])(?:ignore|disregard)\s+(?:all|any|the|previous|prior|above|earlier|your|these|those|everything)\b|\bsystem\s+prompt\b|\b(?:your|previous|prior)\s+instructions\b|\b(?:AI|assistant|LLM|chatbot)\s*:|\b(?:you|the)\s+(?:AI|assistant|LLM|model)\b)/gi,
|
|
189
|
+
severity: "high",
|
|
190
|
+
recommendation: "This element is hidden from a rendered page (display:none / zero font-size / white-on-white) but a scraper feeding a RAG pipeline still extracts its text — and it contains injection-flavored language. Strip hidden elements before extracting text for a model, or render the page and use only the visible text.",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "html_comment_instruction",
|
|
194
|
+
name: "HTML comment containing injection-flavored language",
|
|
195
|
+
category: "hidden-text",
|
|
196
|
+
pattern: /<!--[\s\S]{0,300}?(?:(?<![\w-])(?:ignore|disregard)\s+(?:all|any|the|previous|prior|above|earlier|your|these|those|everything)\b|\bsystem\s+prompt\b|\b(?:your|previous|prior)\s+instructions\b|\b(?:AI|assistant|LLM|chatbot)\s*:|\b(?:you|the)\s+(?:AI|assistant|LLM|model)\b)[\s\S]{0,300}?-->/gi,
|
|
197
|
+
severity: "high",
|
|
198
|
+
recommendation: "HTML comments are invisible when a page is rendered but are usually still present in scraped/extracted text. This comment contains language associated with instruction injection. Strip HTML comments before feeding scraped content to a model.",
|
|
199
|
+
},
|
|
200
|
+
// ── Direct address ────────────────────────────────────────────────────────────
|
|
201
|
+
{
|
|
202
|
+
id: "direct_address_to_ai",
|
|
203
|
+
name: "Content directly addresses 'the AI' or 'the assistant'",
|
|
204
|
+
category: "direct-address",
|
|
205
|
+
pattern: /\b(?:dear|hey|attention|note to)\s+(?:the\s+)?(?:AI|assistant|chatbot|language model|LLM)\b/gi,
|
|
206
|
+
severity: "medium",
|
|
207
|
+
recommendation: "An ordinary document doesn't address 'the AI' or 'the assistant' directly — this phrasing strongly suggests the content was written to be read and obeyed by a model rather than a human. Treat this document as untrusted input, not as a source of instructions.",
|
|
208
|
+
},
|
|
209
|
+
// ── Exfiltration ──────────────────────────────────────────────────────────────
|
|
210
|
+
{
|
|
211
|
+
id: "exfiltration_url_template_in_image",
|
|
212
|
+
name: "Markdown image URL with a template placeholder (data-exfil beacon)",
|
|
213
|
+
category: "exfiltration",
|
|
214
|
+
pattern: /!\[[^\]]*\]\(\s*https?:\/\/[^)]*(?:\{\{[^}]*\}\}|\$\{[^}]*\})[^)]*\)/g,
|
|
215
|
+
severity: "high",
|
|
216
|
+
recommendation: "A markdown image URL containing a template placeholder ({{...}} or ${...}) is a classic exfiltration beacon: a client that auto-renders the image will fetch this URL, and anything the model interpolated into the placeholder (conversation text, secrets) leaks to whoever controls that domain. Never let a model auto-render an image URL it (or retrieved content) constructed.",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "reveal_system_prompt_request",
|
|
220
|
+
name: "Content asks the model to reveal its system prompt or instructions",
|
|
221
|
+
category: "exfiltration",
|
|
222
|
+
pattern: /\b(?:reveal|show|print|output|repeat)\s+(?:your\s+)?(?:system\s+prompt|initial\s+instructions|hidden\s+instructions)\b/gi,
|
|
223
|
+
severity: "high",
|
|
224
|
+
recommendation: "This is a standard prompt-extraction phrase. If it's embedded in retrieved content (not typed by the user), a model that processes/summarizes this document may leak its own system prompt or configuration in the response. Instruct the model to never disclose its instructions regardless of what retrieved content asks.",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: "reveal_system_prompt_tr",
|
|
228
|
+
name: "Content asks the model, in Turkish, to reveal its system prompt",
|
|
229
|
+
category: "exfiltration",
|
|
230
|
+
pattern: /(?:sistem\s+(?:istemini|promptunu|talimatlarını|mesajını)|gizli\s+talimatlarını|ilk\s+talimatlarını)\s+(?:göster|yaz|söyle|tekrarla|paylaş|ver)/giu,
|
|
231
|
+
severity: "high",
|
|
232
|
+
recommendation: "The Turkish form of a prompt-extraction phrase. Instruct the model never to disclose its instructions, whatever retrieved content asks.",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: "reveal_instructions",
|
|
236
|
+
name: "Content asks the model to print its instructions",
|
|
237
|
+
category: "exfiltration",
|
|
238
|
+
pattern: /\b(?:reveal|print)\s+(?:your\s+)?(?:instructions|prompt)\b/gi,
|
|
239
|
+
severity: "medium",
|
|
240
|
+
broad: true,
|
|
241
|
+
recommendation: "May be a prompt-extraction attempt; also appears in developer documentation.",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "send_data_to_url_instruction",
|
|
245
|
+
name: "Content instructs the model to send data to an external URL",
|
|
246
|
+
category: "exfiltration",
|
|
247
|
+
pattern: /\b(?:send|post|submit|forward|email)\s+(?:the\s+)?(?:user'?s?|conversation|chat|this)\s+(?:data|information|details|history)\s+to\s+https?:\/\//gi,
|
|
248
|
+
severity: "critical",
|
|
249
|
+
recommendation: "This content explicitly instructs the model to exfiltrate conversation/user data to an external URL — a direct data-exfiltration attempt via indirect prompt injection. Quarantine this content and never grant a model that processes untrusted retrieved content unsupervised network/tool access.",
|
|
250
|
+
},
|
|
24
251
|
];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
252
|
+
/** @deprecated The bare regexes of INJECTION_RULES; use INJECTION_RULES. */
|
|
253
|
+
exports.INJECTION_PATTERNS = exports.INJECTION_RULES.map((rule) => rule.pattern);
|
|
254
|
+
const BASE64_RUN = /[A-Za-z0-9+/]{24,}={0,2}/g;
|
|
255
|
+
const MAX_BASE64_RUNS = 50;
|
|
256
|
+
/** Decoded text of base64 runs that decode to readable text; binary blobs are skipped. */
|
|
257
|
+
function decodedBase64Runs(text) {
|
|
258
|
+
const runs = [];
|
|
259
|
+
for (const match of text.matchAll(BASE64_RUN)) {
|
|
260
|
+
if (runs.length >= MAX_BASE64_RUNS)
|
|
261
|
+
break;
|
|
262
|
+
const decoded = Buffer.from(match[0], "base64").toString("utf8");
|
|
263
|
+
const readable = decoded.replace(/[^\x20-\x7E\s -]/g, "").length;
|
|
264
|
+
if (decoded.length >= 12 && readable / decoded.length > 0.9 && !decoded.includes("�")) {
|
|
265
|
+
runs.push({ text: decoded, index: match.index ?? 0 });
|
|
266
|
+
}
|
|
30
267
|
}
|
|
31
|
-
return
|
|
268
|
+
return runs;
|
|
32
269
|
}
|
|
33
|
-
function
|
|
34
|
-
const
|
|
35
|
-
for (const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
270
|
+
function matchRules(text, rules, offset = 0, encoding) {
|
|
271
|
+
const findings = [];
|
|
272
|
+
for (const rule of rules) {
|
|
273
|
+
for (const match of text.matchAll(rule.pattern)) {
|
|
274
|
+
findings.push({
|
|
275
|
+
ruleId: rule.id,
|
|
276
|
+
name: rule.name,
|
|
277
|
+
category: rule.category,
|
|
278
|
+
severity: rule.severity,
|
|
279
|
+
broad: rule.broad === true,
|
|
280
|
+
match: match[0],
|
|
281
|
+
index: offset + (match.index ?? 0),
|
|
282
|
+
...(encoding ? { encoding } : {}),
|
|
283
|
+
});
|
|
41
284
|
}
|
|
42
285
|
}
|
|
286
|
+
return findings;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Every rule match in the text, plus precise-rule matches inside base64 runs
|
|
290
|
+
* that decode to text (an instruction hidden from both the reader and a plain
|
|
291
|
+
* pattern scan).
|
|
292
|
+
*/
|
|
293
|
+
function findInjections(text) {
|
|
294
|
+
const precise = exports.INJECTION_RULES.filter((rule) => !rule.broad);
|
|
295
|
+
const findings = matchRules(text, exports.INJECTION_RULES);
|
|
296
|
+
for (const run of decodedBase64Runs(text))
|
|
297
|
+
findings.push(...matchRules(run.text, precise, run.index, "base64"));
|
|
298
|
+
return findings;
|
|
299
|
+
}
|
|
300
|
+
/** All string leaves, newline-joined, so line-anchored rules still see line starts inside JSON. */
|
|
301
|
+
function collectStrings(value, out = []) {
|
|
302
|
+
if (typeof value === "string")
|
|
303
|
+
out.push(value);
|
|
304
|
+
else if (Array.isArray(value))
|
|
305
|
+
for (const item of value)
|
|
306
|
+
collectStrings(item, out);
|
|
307
|
+
else if (value && typeof value === "object")
|
|
308
|
+
for (const item of Object.values(value))
|
|
309
|
+
collectStrings(item, out);
|
|
310
|
+
else if (value !== null && value !== undefined)
|
|
311
|
+
out.push(String(value));
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
function describe(finding) {
|
|
315
|
+
return `${finding.ruleId}${finding.encoding ? " (base64-encoded)" : ""} — ${finding.name}`;
|
|
316
|
+
}
|
|
317
|
+
const BLOCKING = new Set(["high", "critical"]);
|
|
318
|
+
/**
|
|
319
|
+
* A precise high/critical rule → `mode` (block or warn). A broad or medium
|
|
320
|
+
* rule alone → warn, never block.
|
|
321
|
+
*/
|
|
322
|
+
function scanForPromptInjection(input, mode = "block") {
|
|
323
|
+
const findings = findInjections(collectStrings(input).join("\n"));
|
|
324
|
+
const blocking = findings.find((finding) => !finding.broad && BLOCKING.has(finding.severity));
|
|
325
|
+
if (blocking) {
|
|
326
|
+
return { action: mode, reason: `Prompt injection pattern detected: ${describe(blocking)}` };
|
|
327
|
+
}
|
|
328
|
+
const first = findings[0];
|
|
329
|
+
if (first)
|
|
330
|
+
return { action: "warn", reason: `Possible prompt injection (not blocked): ${describe(first)}` };
|
|
43
331
|
return { action: "allow" };
|
|
44
332
|
}
|
|
45
|
-
/** Same
|
|
333
|
+
/** Same rules as request scanning, applied to a tool result the model will read. */
|
|
46
334
|
function scanToolResult(content, mode = "block") {
|
|
47
335
|
const result = scanForPromptInjection(content, mode);
|
|
48
336
|
if (result.action === "allow")
|