@guardbee/mcp-prompt-leak-scanner 0.1.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/README.md +146 -0
- package/TR.md +146 -0
- package/dist/__tests__/proxy.test.d.ts +2 -0
- package/dist/__tests__/proxy.test.d.ts.map +1 -0
- package/dist/__tests__/proxy.test.js +107 -0
- package/dist/__tests__/proxy.test.js.map +1 -0
- package/dist/__tests__/scanner.test.d.ts +2 -0
- package/dist/__tests__/scanner.test.d.ts.map +1 -0
- package/dist/__tests__/scanner.test.js +111 -0
- package/dist/__tests__/scanner.test.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +205 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns.d.ts +13 -0
- package/dist/patterns.d.ts.map +1 -0
- package/dist/patterns.js +169 -0
- package/dist/patterns.js.map +1 -0
- package/dist/proxy.d.ts +34 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +92 -0
- package/dist/proxy.js.map +1 -0
- package/dist/sarif.d.ts +3 -0
- package/dist/sarif.d.ts.map +1 -0
- package/dist/sarif.js +54 -0
- package/dist/sarif.js.map +1 -0
- package/dist/scanner.d.ts +44 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +237 -0
- package/dist/scanner.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +85 -0
- package/dist/server.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# @guardbee/mcp-prompt-leak-scanner
|
|
2
|
+
|
|
3
|
+
**🇬🇧 English** | [🇹🇷 Türkçe](TR.md)
|
|
4
|
+
|
|
5
|
+
An MCP (Model Context Protocol) server — and a standalone reverse proxy — that catches leaked credentials and PII in **outbound** LLM prompts, before they leave your application.
|
|
6
|
+
|
|
7
|
+
`secret-scanner` finds secrets sitting in your codebase. This package finds secrets and PII that made it into a *runtime prompt* — a support agent that pastes a customer's ticket (email, card number) straight into a system prompt, a debugging session where someone pastes an API key into a chat, an internal tool that forwards raw user input to an LLM without ever checking what's in it. That's a different moment in the pipeline, and a different failure mode than a leaked `.env` file.
|
|
8
|
+
|
|
9
|
+
> This package sends usage telemetry by default (tool name + short parameters, prompt content and audit findings are never included — see [`@guardbee/mcp-telemetry`](../telemetry/README.md)). Disable with `GUARDBEE_TELEMETRY=0`.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Your app ──► prompt-leak-scanner (proxy) ──► Real LLM API (OpenAI/Anthropic/...)
|
|
13
|
+
│
|
|
14
|
+
├─ monitor: forward unchanged, log findings
|
|
15
|
+
├─ redact: forward with matches replaced by [REDACTED:<id>]
|
|
16
|
+
└─ block: reject the request before it ever reaches the model
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Two ways to use it
|
|
22
|
+
|
|
23
|
+
**1. On-demand scanning (MCP tools)** — `scan_text`/`scan_messages`/`scan_file`/`scan_directory`. Ask Claude to check a prompt, a chat-request-body fixture, or a directory of prompt logs before you ship something that builds prompts from user input.
|
|
24
|
+
|
|
25
|
+
**2. A live reverse proxy (`proxy` CLI command)** — sits between your application and the real LLM API. It buffers and inspects only the **outbound request body** (message/system text), then streams the upstream response straight back unmodified — so SSE/streaming completions pass through untouched; only the prompt going *out* is ever parsed. Point your app's `baseURL` at the proxy instead of the real API and nothing else in your integration needs to change.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
guardbee-prompt-leak-scanner proxy --upstream=https://api.openai.com --port=8788 --mode=redact
|
|
29
|
+
# your app: baseURL = http://localhost:8788 instead of https://api.openai.com
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Detection quality: checksums, not just regex
|
|
35
|
+
|
|
36
|
+
A bare regex for "11 digits" or "16 digits" would flag order numbers, phone extensions, and timestamps constantly. Every PII pattern that has a real checksum algorithm uses it:
|
|
37
|
+
|
|
38
|
+
- **TC Kimlik No** — the actual 11-digit Turkish national ID checksum algorithm (not just a digit-count regex)
|
|
39
|
+
- **Credit card numbers** — Luhn checksum
|
|
40
|
+
- **IBAN** — ISO 7064 MOD97-10 checksum
|
|
41
|
+
|
|
42
|
+
A random 11-digit number has roughly a 1-in-10 chance of passing the TC Kimlik checksum by coincidence — regex alone would be far noisier. Credential patterns (API keys, JWTs, private keys) are matched against real provider-specific prefixes/structure (`sk-`, `AKIA`, `ghp_`, `-----BEGIN...PRIVATE KEY-----`, JWT's three-segment base64url shape), the same low-false-positive approach `secret-scanner` uses.
|
|
43
|
+
|
|
44
|
+
**Findings never echo the real value.** A finding's `maskedMatch` shows only the first 3 and last 2 characters (`sk-…wx`) — logging or displaying the very thing you just caught would defeat the point. The proxy's audit events go further: they record which pattern fired and where, never the matched text itself.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- **13 patterns, 4 categories**: credential (9), financial-pii (2, checksum-validated), national-id (1, checksum-validated), contact-pii (2)
|
|
51
|
+
- **Chat-body aware** — understands OpenAI/Anthropic-style `messages[].content` (string or content-block array) and `system` fields, not just flat text
|
|
52
|
+
- **Live reverse-proxy mode** with monitor/redact/block policies — request-only inspection, response streamed through untouched
|
|
53
|
+
- Masked findings and credential-free audit events — the tool never becomes a second copy of the leak
|
|
54
|
+
- SARIF 2.1.0 output — CI/CD integration (GitHub Code Scanning, etc.)
|
|
55
|
+
- Config file support via `guardbee.yml`
|
|
56
|
+
- 29 unit tests — checksum validators verified against known-good/known-bad test vectors, proxy tested against a real local HTTP client/server round trip (not just in-process mocks) in all three modes
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
### Claude Desktop / MCP Client
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"guardbee-prompt-leak-scanner": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["-y", "@guardbee/mcp-prompt-leak-scanner"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### CLI (CI/CD)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx @guardbee/mcp-prompt-leak-scanner scan ./prompt-fixtures --fail-on=high --format=sarif > results.sarif
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### CLI (live proxy)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx @guardbee/mcp-prompt-leak-scanner proxy --upstream=https://api.openai.com --mode=block --fail-on-severity=critical
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## MCP Tools
|
|
90
|
+
|
|
91
|
+
| Tool | Description |
|
|
92
|
+
|------|----------|
|
|
93
|
+
| `scan_text` | Scans a raw text string |
|
|
94
|
+
| `scan_messages` | Scans an OpenAI/Anthropic-style chat request body object |
|
|
95
|
+
| `scan_file` | Scans a single file (plain text, or a JSON request-body fixture — auto-detected) |
|
|
96
|
+
| `scan_directory` | Recursively scans a directory of prompt logs/fixtures |
|
|
97
|
+
| `list_patterns` | Lists all supported patterns by category |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Detected Patterns
|
|
102
|
+
|
|
103
|
+
| Category | Pattern | Severity |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| credential | OpenAI / Anthropic / Google / Stripe API key, AWS access key ID, GitHub PAT, Slack token, PEM private key block | critical |
|
|
106
|
+
| credential | JWT | high |
|
|
107
|
+
| financial-pii | Credit card number (Luhn-validated) | high |
|
|
108
|
+
| financial-pii | IBAN (mod-97 validated) | high |
|
|
109
|
+
| national-id | TC Kimlik No (checksum-validated) | high |
|
|
110
|
+
| contact-pii | Email address | medium |
|
|
111
|
+
| contact-pii | Turkish phone number | medium |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Configuration (`guardbee.yml`)
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
prompt-leak-scanner:
|
|
119
|
+
fail-on: high # any | critical | high | medium | low | none
|
|
120
|
+
max-files: 5000
|
|
121
|
+
exclude:
|
|
122
|
+
- "fixtures/known-safe/"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Limitations (by design)
|
|
128
|
+
|
|
129
|
+
- The proxy only inspects the **request** body; it does not parse or modify streaming SSE responses — those are piped through untouched.
|
|
130
|
+
- Only `messages[].content` (string or `{type:"text"}` content blocks) and top-level `system` are extracted from a chat body; provider-specific fields outside that shape aren't scanned.
|
|
131
|
+
- This is pattern/checksum-based detection, not a general-purpose NER/PII model — it won't catch PII that doesn't match a known structural pattern (e.g. a name or street address in free text).
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run build
|
|
139
|
+
npm test # 29 unit tests
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT — [GuardBee](https://guardbee.ai)
|
package/TR.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# @guardbee/mcp-prompt-leak-scanner
|
|
2
|
+
|
|
3
|
+
[🇬🇧 English](README.md) | **🇹🇷 Türkçe**
|
|
4
|
+
|
|
5
|
+
MCP (Model Context Protocol) sunucusu — ve bağımsız bir reverse proxy — **outbound** LLM prompt'larındaki sızmış credential ve PII'yi, uygulamanızdan çıkmadan önce yakalar.
|
|
6
|
+
|
|
7
|
+
`secret-scanner` kod tabanınızda duran sırları bulur. Bu paket ise bir *runtime prompt*'a giren sır ve PII'yi bulur — bir müşteri destek agent'ının bir ticket'ı (email, kart no) doğrudan system prompt'a yapıştırması, birinin debug sırasında bir chat'e API key yapıştırması, ham kullanıcı girdisini hiç kontrol etmeden LLM'e ileten bir internal tool. Bu, pipeline'da farklı bir an ve sızmış bir `.env` dosyasından farklı bir hata modu.
|
|
8
|
+
|
|
9
|
+
> Bu paket varsayılan olarak kullanım telemetrisi gönderir (tool adı + kısa parametreler, prompt içeriği ve audit bulguları hiçbir zaman dahil değil — bkz. [`@guardbee/mcp-telemetry`](../telemetry/TR.md)). Kapatmak için `GUARDBEE_TELEMETRY=0`.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Uygulamanız ──► prompt-leak-scanner (proxy) ──► Gerçek LLM API (OpenAI/Anthropic/...)
|
|
13
|
+
│
|
|
14
|
+
├─ monitor: değiştirmeden ilet, bulguları logla
|
|
15
|
+
├─ redact: eşleşmeleri [REDACTED:<id>] ile değiştirip ilet
|
|
16
|
+
└─ block: isteği modele hiç ulaşmadan reddet
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## İki kullanım şekli
|
|
22
|
+
|
|
23
|
+
**1. Talep üzerine tarama (MCP tool'ları)** — `scan_text`/`scan_messages`/`scan_file`/`scan_directory`. Kullanıcı girdisinden prompt oluşturan bir şeyi shipping'lemeden önce Claude'a bir prompt'u, bir chat-request-body fixture'ını ya da bir prompt log dizinini kontrol ettirin.
|
|
24
|
+
|
|
25
|
+
**2. Canlı bir reverse proxy (`proxy` CLI komutu)** — uygulamanızla gerçek LLM API arasına oturur. Sadece **outbound request body**'yi (message/system metni) buffer'layıp inceler, sonra upstream response'u değiştirmeden doğrudan geri stream eder — yani SSE/streaming completion'lar dokunulmadan geçer; sadece dışarı giden prompt hiç parse edilir. Uygulamanızın `baseURL`'ini gerçek API yerine proxy'ye yönlendirin, entegrasyonunuzda başka hiçbir şeyin değişmesi gerekmiyor.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
guardbee-prompt-leak-scanner proxy --upstream=https://api.openai.com --port=8788 --mode=redact
|
|
29
|
+
# uygulamanız: baseURL = http://localhost:8788 (https://api.openai.com yerine)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Tespit kalitesi: sadece regex deÄŸil, checksum
|
|
35
|
+
|
|
36
|
+
"11 hane" ya da "16 hane" için çıplak bir regex sipariş numaralarını, telefon dahili hatlarını, timestamp'leri sürekli işaretlerdi. Gerçek bir checksum algoritması olan her PII kalıbı onu kullanıyor:
|
|
37
|
+
|
|
38
|
+
- **TC Kimlik No** — gerçek 11 haneli Türkiye kimlik checksum algoritması (sadece hane-sayısı regex'i değil)
|
|
39
|
+
- **Kredi kartı numaraları** — Luhn checksum
|
|
40
|
+
- **IBAN** — ISO 7064 MOD97-10 checksum
|
|
41
|
+
|
|
42
|
+
Rastgele 11 haneli bir sayının TC Kimlik checksum'ını tesadüfen geçme ihtimali kabaca 10'da 1 — tek başına regex çok daha gürültülü olurdu. Credential kalıpları (API key, JWT, private key) gerçek sağlayıcıya-özgü prefix/yapılara karşı eşleştiriliyor (`sk-`, `AKIA`, `ghp_`, `-----BEGIN...PRIVATE KEY-----`, JWT'nin üç-segmentli base64url şekli) — `secret-scanner`'ın kullandığı aynı düşük-yanlış-pozitif yaklaşım.
|
|
43
|
+
|
|
44
|
+
**Bulgular gerçek değeri asla tam göstermez.** Bir bulgunun `maskedMatch`'i sadece ilk 3 ve son 2 karakteri gösterir (`sk-…wx`) — az önce yakaladığınız şeyi loglamak ya da göstermek amacı boşa çıkarırdı. Proxy'nin audit event'leri daha da ileri gider: hangi kalıbın tetiklendiğini ve nerede olduğunu kaydeder, eşleşen metni asla değil.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Özellikler
|
|
49
|
+
|
|
50
|
+
- **13 kalıp, 4 kategori**: credential (9), financial-pii (2, checksum-validated), national-id (1, checksum-validated), contact-pii (2)
|
|
51
|
+
- **Chat-body farkında** — OpenAI/Anthropic tarzı `messages[].content`'i (string ya da content-block array) ve `system` alanlarını anlıyor, sadece düz metni değil
|
|
52
|
+
- monitor/redact/block politikalarıyla **canlı reverse-proxy modu** — sadece request inceleniyor, response dokunulmadan stream ediliyor
|
|
53
|
+
- Maskelenmiş bulgular ve credential-içermeyen audit event'leri — araç sızıntının ikinci bir kopyası haline gelmiyor
|
|
54
|
+
- SARIF 2.1.0 çıktısı — CI/CD entegrasyonu (GitHub Code Scanning vb.)
|
|
55
|
+
- `guardbee.yml` ile config dosyası desteği
|
|
56
|
+
- 29 unit test — checksum doğrulayıcıları bilinen-geçerli/bilinen-geçersiz test vektörlerine karşı doğrulandı, proxy her üç modda gerçek bir lokal HTTP client/server round trip'ine karşı (sadece in-process mock değil) test edildi
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Hızlı Başlangıç
|
|
61
|
+
|
|
62
|
+
### Claude Desktop / MCP Client
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"guardbee-prompt-leak-scanner": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["-y", "@guardbee/mcp-prompt-leak-scanner"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### CLI (CI/CD)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx @guardbee/mcp-prompt-leak-scanner scan ./prompt-fixtures --fail-on=high --format=sarif > results.sarif
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### CLI (canlı proxy)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx @guardbee/mcp-prompt-leak-scanner proxy --upstream=https://api.openai.com --mode=block --fail-on-severity=critical
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## MCP Tools
|
|
90
|
+
|
|
91
|
+
| Tool | Açıklama |
|
|
92
|
+
|------|----------|
|
|
93
|
+
| `scan_text` | Ham bir metin string'ini tarar |
|
|
94
|
+
| `scan_messages` | OpenAI/Anthropic tarzı bir chat request body objesini tarar |
|
|
95
|
+
| `scan_file` | Tek bir dosyayı tarar (düz metin, ya da bir JSON request-body fixture'ı — otomatik tespit edilir) |
|
|
96
|
+
| `scan_directory` | Bir prompt log/fixture dizinini recursive tarar |
|
|
97
|
+
| `list_patterns` | Desteklenen tüm kalıpları kategoriye göre listeler |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Tespit Edilen Kalıplar
|
|
102
|
+
|
|
103
|
+
| Kategori | Kalıp | Önem |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| credential | OpenAI / Anthropic / Google / Stripe API key, AWS access key ID, GitHub PAT, Slack token, PEM private key block | critical |
|
|
106
|
+
| credential | JWT | high |
|
|
107
|
+
| financial-pii | Kredi kartı numarası (Luhn-validated) | high |
|
|
108
|
+
| financial-pii | IBAN (mod-97 validated) | high |
|
|
109
|
+
| national-id | TC Kimlik No (checksum-validated) | high |
|
|
110
|
+
| contact-pii | Email adresi | medium |
|
|
111
|
+
| contact-pii | Türkiye telefon numarası | medium |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Yapılandırma (`guardbee.yml`)
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
prompt-leak-scanner:
|
|
119
|
+
fail-on: high # any | critical | high | medium | low | none
|
|
120
|
+
max-files: 5000
|
|
121
|
+
exclude:
|
|
122
|
+
- "fixtures/known-safe/"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Kısıtlamalar (bilinçli tasarım)
|
|
128
|
+
|
|
129
|
+
- Proxy sadece **request** body'sini inceler; streaming SSE response'ları parse etmez ya da değiştirmez — bunlar dokunulmadan geçer.
|
|
130
|
+
- Bir chat body'sinden sadece `messages[].content` (string ya da `{type:"text"}` content block'ları) ve üst-seviye `system` çıkarılır; bu şeklin dışındaki sağlayıcıya-özgü alanlar taranmaz.
|
|
131
|
+
- Bu kalıp/checksum tabanlı bir tespit, genel amaçlı bir NER/PII modeli değil — bilinen yapısal bir kalıpla eşleşmeyen PII'yi (örn. serbest metindeki bir isim ya da adres) yakalamaz.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## GeliÅŸtirme
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run build
|
|
139
|
+
npm test # 29 unit test
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Lisans
|
|
145
|
+
|
|
146
|
+
MIT — [GuardBee](https://guardbee.ai)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/proxy.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { createServer } from "http";
|
|
3
|
+
import { startProxy } from "../proxy.js";
|
|
4
|
+
let proxyServer;
|
|
5
|
+
let upstreamServer;
|
|
6
|
+
afterEach(async () => {
|
|
7
|
+
if (proxyServer)
|
|
8
|
+
await new Promise((r) => proxyServer.close(r));
|
|
9
|
+
if (upstreamServer)
|
|
10
|
+
await new Promise((r) => upstreamServer.close(r));
|
|
11
|
+
proxyServer = undefined;
|
|
12
|
+
upstreamServer = undefined;
|
|
13
|
+
});
|
|
14
|
+
function startMockUpstream() {
|
|
15
|
+
const receivedBodies = [];
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
upstreamServer = createServer((req, res) => {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
req.on("data", (c) => chunks.push(c));
|
|
20
|
+
req.on("end", () => {
|
|
21
|
+
receivedBodies.push(Buffer.concat(chunks).toString("utf8"));
|
|
22
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
23
|
+
res.end(JSON.stringify({ ok: true, echoPath: req.url }));
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
upstreamServer.listen(0, "127.0.0.1", () => resolve({ port: upstreamServer.address().port, receivedBodies }));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function postJson(port, path, body) {
|
|
30
|
+
return fetch(`http://127.0.0.1:${port}${path}`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
body: JSON.stringify(body),
|
|
34
|
+
}).then(async (res) => ({ status: res.status, json: await res.json() }));
|
|
35
|
+
}
|
|
36
|
+
function freePort() {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const probe = createServer();
|
|
39
|
+
probe.listen(0, "127.0.0.1", () => {
|
|
40
|
+
const port = probe.address().port;
|
|
41
|
+
probe.close(() => resolve(port));
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
describe("startProxy — monitor mode", () => {
|
|
46
|
+
it("temiz bir body'yi deÄŸiÅŸtirmeden upstream'e iletir", async () => {
|
|
47
|
+
const { port: upstreamPort, receivedBodies } = await startMockUpstream();
|
|
48
|
+
const proxyPort = await freePort();
|
|
49
|
+
const audits = [];
|
|
50
|
+
proxyServer = startProxy({ port: proxyPort, upstream: `http://127.0.0.1:${upstreamPort}`, mode: "monitor", onAudit: (e) => audits.push(e) });
|
|
51
|
+
const body = { messages: [{ role: "user", content: "hello there" }] };
|
|
52
|
+
const { status } = await postJson(proxyPort, "/v1/chat/completions", body);
|
|
53
|
+
expect(status).toBe(200);
|
|
54
|
+
expect(JSON.parse(receivedBodies[0])).toEqual(body);
|
|
55
|
+
expect(audits[0]).toMatchObject({ blocked: false, findingCount: 0 });
|
|
56
|
+
});
|
|
57
|
+
it("secret içeren bir body'yi DEĞİŞTİRMEDEN iletir (monitor sadece loglar)", async () => {
|
|
58
|
+
const { port: upstreamPort, receivedBodies } = await startMockUpstream();
|
|
59
|
+
const proxyPort = await freePort();
|
|
60
|
+
const audits = [];
|
|
61
|
+
proxyServer = startProxy({ port: proxyPort, upstream: `http://127.0.0.1:${upstreamPort}`, mode: "monitor", onAudit: (e) => audits.push(e) });
|
|
62
|
+
const body = { messages: [{ role: "user", content: "my key is sk-abcdefghijklmnopqrstuvwx" }] };
|
|
63
|
+
await postJson(proxyPort, "/v1/chat/completions", body);
|
|
64
|
+
expect(JSON.parse(receivedBodies[0])).toEqual(body);
|
|
65
|
+
expect(audits[0]).toMatchObject({ findingCount: 1 });
|
|
66
|
+
expect(audits[0].findingCodes).toContain("openai_api_key");
|
|
67
|
+
});
|
|
68
|
+
it("audit event ham secret değerini içermez", async () => {
|
|
69
|
+
const { port: upstreamPort } = await startMockUpstream();
|
|
70
|
+
const proxyPort = await freePort();
|
|
71
|
+
const audits = [];
|
|
72
|
+
proxyServer = startProxy({ port: proxyPort, upstream: `http://127.0.0.1:${upstreamPort}`, mode: "monitor", onAudit: (e) => audits.push(e) });
|
|
73
|
+
await postJson(proxyPort, "/v1/chat/completions", { messages: [{ role: "user", content: "my key is sk-abcdefghijklmnopqrstuvwx" }] });
|
|
74
|
+
const serialized = JSON.stringify(audits[0]);
|
|
75
|
+
expect(serialized).not.toContain("sk-abcdefghijklmnopqrstuvwx");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe("startProxy — redact mode", () => {
|
|
79
|
+
it("secret içeren body'yi redakte edilmiş haliyle upstream'e iletir", async () => {
|
|
80
|
+
const { port: upstreamPort, receivedBodies } = await startMockUpstream();
|
|
81
|
+
const proxyPort = await freePort();
|
|
82
|
+
proxyServer = startProxy({ port: proxyPort, upstream: `http://127.0.0.1:${upstreamPort}`, mode: "redact" });
|
|
83
|
+
await postJson(proxyPort, "/v1/chat/completions", { messages: [{ role: "user", content: "my key is sk-abcdefghijklmnopqrstuvwx" }] });
|
|
84
|
+
const forwarded = JSON.parse(receivedBodies[0]);
|
|
85
|
+
expect(forwarded.messages[0].content).toBe("my key is [REDACTED:openai_api_key]");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe("startProxy — block mode", () => {
|
|
89
|
+
it("critical bulgu varsa isteği upstream'e hiç iletmeden 400 döner", async () => {
|
|
90
|
+
const { port: upstreamPort, receivedBodies } = await startMockUpstream();
|
|
91
|
+
const proxyPort = await freePort();
|
|
92
|
+
proxyServer = startProxy({ port: proxyPort, upstream: `http://127.0.0.1:${upstreamPort}`, mode: "block", failOnSeverity: "high" });
|
|
93
|
+
const { status, json } = await postJson(proxyPort, "/v1/chat/completions", { messages: [{ role: "user", content: "my key is sk-abcdefghijklmnopqrstuvwx" }] });
|
|
94
|
+
expect(status).toBe(400);
|
|
95
|
+
expect(json.error).toBe("blocked_by_guardbee_prompt_leak_scanner");
|
|
96
|
+
expect(receivedBodies).toHaveLength(0);
|
|
97
|
+
});
|
|
98
|
+
it("temiz bir body'yi normal ÅŸekilde iletir", async () => {
|
|
99
|
+
const { port: upstreamPort, receivedBodies } = await startMockUpstream();
|
|
100
|
+
const proxyPort = await freePort();
|
|
101
|
+
proxyServer = startProxy({ port: proxyPort, upstream: `http://127.0.0.1:${upstreamPort}`, mode: "block" });
|
|
102
|
+
const { status } = await postJson(proxyPort, "/v1/chat/completions", { messages: [{ role: "user", content: "hello" }] });
|
|
103
|
+
expect(status).toBe(200);
|
|
104
|
+
expect(receivedBodies).toHaveLength(1);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
//# sourceMappingURL=proxy.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.test.js","sourceRoot":"","sources":["../../src/__tests__/proxy.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/scanner.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { scanText, scanAndRedactText, scanBody, redactBody } from "../scanner.js";
|
|
3
|
+
function idsOf(text) {
|
|
4
|
+
return scanText(text).map((f) => f.patternId);
|
|
5
|
+
}
|
|
6
|
+
describe("scanText — credentials", () => {
|
|
7
|
+
it("yakalar: OpenAI API key", () => {
|
|
8
|
+
expect(idsOf(`My key is sk-abcdefghijklmnopqrstuvwx and here is the prompt`)).toContain("openai_api_key");
|
|
9
|
+
});
|
|
10
|
+
it("yakalar: Anthropic API key", () => {
|
|
11
|
+
expect(idsOf(`key: sk-ant-abcdefghijklmnopqrstuvwx`)).toContain("anthropic_api_key");
|
|
12
|
+
});
|
|
13
|
+
it("yakalar: AWS access key ID", () => {
|
|
14
|
+
expect(idsOf(`AKIAIOSFODNN7EXAMPLE is the access key`)).toContain("aws_access_key_id");
|
|
15
|
+
});
|
|
16
|
+
it("yakalar: PEM private key block", () => {
|
|
17
|
+
expect(idsOf(`-----BEGIN RSA PRIVATE KEY-----\nMIIExyz...`)).toContain("private_key_block");
|
|
18
|
+
});
|
|
19
|
+
it("yakalar: JWT", () => {
|
|
20
|
+
const jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dQw4w9WgXcQ_abcdefghij";
|
|
21
|
+
expect(idsOf(`Authorization: Bearer ${jwt}`)).toContain("jwt_token");
|
|
22
|
+
});
|
|
23
|
+
it("normal metinde credential yakalamaz", () => {
|
|
24
|
+
expect(idsOf(`Please summarize this document for me.`)).toHaveLength(0);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe("scanText — tc_kimlik_no (checksum-validated)", () => {
|
|
28
|
+
it("geçerli bir TC Kimlik No'yu yakalar", () => {
|
|
29
|
+
// 10000000146 gerçek algoritmayı sağlayan bilinen bir test değeri
|
|
30
|
+
expect(idsOf(`Müşteri TC: 10000000146`)).toContain("tc_kimlik_no");
|
|
31
|
+
});
|
|
32
|
+
it("checksum'ı geçersiz 11 haneli bir sayıyı yakalamaz", () => {
|
|
33
|
+
expect(idsOf(`Sipariş numarası: 12345678901`)).not.toContain("tc_kimlik_no");
|
|
34
|
+
});
|
|
35
|
+
it("0 ile başlayan 11 haneli bir sayıyı yakalamaz", () => {
|
|
36
|
+
expect(idsOf(`Kod: 01234567890`)).not.toContain("tc_kimlik_no");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("scanText — credit_card_number (Luhn-validated)", () => {
|
|
40
|
+
it("geçerli (Luhn) bir kart numarasını yakalar", () => {
|
|
41
|
+
expect(idsOf(`Card: 4532015112830366`)).toContain("credit_card_number");
|
|
42
|
+
});
|
|
43
|
+
it("Luhn'u geçmeyen 16 haneli bir sayıyı yakalamaz", () => {
|
|
44
|
+
expect(idsOf(`Reference: 1234567890123456`)).not.toContain("credit_card_number");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("scanText — iban (mod-97 validated)", () => {
|
|
48
|
+
it("geçerli bir IBAN'ı yakalar", () => {
|
|
49
|
+
expect(idsOf(`IBAN: TR330006100519786457841326`)).toContain("iban");
|
|
50
|
+
});
|
|
51
|
+
it("checksum'ı bozuk bir IBAN benzeri stringi yakalamaz", () => {
|
|
52
|
+
expect(idsOf(`Code: TR330006100519786457841327`)).not.toContain("iban");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("scanText — contact PII", () => {
|
|
56
|
+
it("yakalar: email", () => {
|
|
57
|
+
expect(idsOf(`Contact me at jane.doe@example.com`)).toContain("email_address");
|
|
58
|
+
});
|
|
59
|
+
it("yakalar: Türkiye telefon numarası", () => {
|
|
60
|
+
expect(idsOf(`Beni ara: 0532 123 45 67`)).toContain("turkish_phone_number");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("scanText — masking", () => {
|
|
64
|
+
it("maskedMatch gerçek değeri asla tam göstermez", () => {
|
|
65
|
+
const findings = scanText(`sk-abcdefghijklmnopqrstuvwx`);
|
|
66
|
+
expect(findings[0].maskedMatch).not.toBe("sk-abcdefghijklmnopqrstuvwx");
|
|
67
|
+
expect(findings[0].maskedMatch).toContain("…");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe("scanAndRedactText", () => {
|
|
71
|
+
it("bulunan deÄŸeri [REDACTED:id] ile deÄŸiÅŸtirir", () => {
|
|
72
|
+
const { findings, redactedText } = scanAndRedactText(`My key is sk-abcdefghijklmnopqrstuvwx, thanks`);
|
|
73
|
+
expect(findings).toHaveLength(1);
|
|
74
|
+
expect(redactedText).toBe(`My key is [REDACTED:openai_api_key], thanks`);
|
|
75
|
+
});
|
|
76
|
+
it("birden fazla bulguyu doğru sırayla redakte eder", () => {
|
|
77
|
+
const { redactedText } = scanAndRedactText(`email jane@example.com and key sk-abcdefghijklmnopqrstuvwx`);
|
|
78
|
+
expect(redactedText).toBe(`email [REDACTED:email_address] and key [REDACTED:openai_api_key]`);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("scanBody — chat message extraction", () => {
|
|
82
|
+
it("messages[].content string alanındaki bulguyu location ile birlikte döner", () => {
|
|
83
|
+
const body = { messages: [{ role: "user", content: "my key is sk-abcdefghijklmnopqrstuvwx" }] };
|
|
84
|
+
const { findings } = scanBody(body);
|
|
85
|
+
expect(findings[0]).toMatchObject({ patternId: "openai_api_key", location: "messages[0].content" });
|
|
86
|
+
});
|
|
87
|
+
it("system alanındaki bulguyu yakalar", () => {
|
|
88
|
+
const body = { system: "user email is jane@example.com", messages: [] };
|
|
89
|
+
const { findings } = scanBody(body);
|
|
90
|
+
expect(findings[0]).toMatchObject({ patternId: "email_address", location: "system" });
|
|
91
|
+
});
|
|
92
|
+
it("content-block array formundaki text alanını tarar", () => {
|
|
93
|
+
const body = { messages: [{ role: "user", content: [{ type: "text", text: "key sk-abcdefghijklmnopqrstuvwx" }] }] };
|
|
94
|
+
const { findings } = scanBody(body);
|
|
95
|
+
expect(findings[0]).toMatchObject({ location: "messages[0].content[0].text" });
|
|
96
|
+
});
|
|
97
|
+
it("temiz bir body'de bulgu döndürmez", () => {
|
|
98
|
+
const body = { messages: [{ role: "user", content: "What's the weather like today?" }] };
|
|
99
|
+
expect(scanBody(body).findings).toHaveLength(0);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("redactBody", () => {
|
|
103
|
+
it("body'yi in-place redakte eder ve orijinal referansı döner", () => {
|
|
104
|
+
const body = { messages: [{ role: "user", content: "my key is sk-abcdefghijklmnopqrstuvwx" }] };
|
|
105
|
+
const { findings, redactedBody } = redactBody(body);
|
|
106
|
+
expect(findings).toHaveLength(1);
|
|
107
|
+
expect(redactedBody).toBe(body); // aynı obje referansı, in-place mutasyon
|
|
108
|
+
expect(body.messages[0].content).toBe("my key is [REDACTED:openai_api_key]");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
//# sourceMappingURL=scanner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.test.js","sourceRoot":"","sources":["../../src/__tests__/scanner.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|