@guardbee/mcp-secret-scanner 0.1.0 → 0.1.2
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 +99 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +141 -4
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @guardbee/mcp-secret-scanner
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@guardbee/mcp-secret-scanner)
|
|
4
|
+
[](https://www.npmjs.com/package/@guardbee/mcp-secret-scanner)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Kaynak dosyalarınızı, dizinleri ve ortam konfigürasyonlarını açık API key, parola, token ve diğer gizli bilgiler açısından tarayan MCP sunucusu. Claude'a doğrudan projenizden secret sızdırıp sızdırmadığınızı sorabilirsiniz.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Özellikler
|
|
12
|
+
|
|
13
|
+
- **40+ Secret Deseni** — AWS, GitHub, GitLab, Stripe, OpenAI, Anthropic, HuggingFace, Slack, Twilio, SendGrid ve daha fazlası
|
|
14
|
+
- **Dosya & Dizin Tarama** — Tek dosya veya tüm proje ağacı
|
|
15
|
+
- **Akıllı Atlama** — `node_modules`, `.git`, `dist`, `build`, `.next` gibi dizinler otomatik atlanır
|
|
16
|
+
- **Güvenli Redaksyon** — Eşleşmeler ilk 4 + yıldız + son 4 karakter olarak gösterilir
|
|
17
|
+
- **Allowlist Desteği** — Bilinen test/sahte değerleri beyaz listeye alın
|
|
18
|
+
- **16 Unit Test** — %100 geçen test paketi
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Hızlı Başlangıç
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @guardbee/mcp-secret-scanner
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`claude_desktop_config.json` dosyasına ekleyin:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"guardbee-secret-scanner": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["-y", "@guardbee/mcp-secret-scanner"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## MCP Tools
|
|
44
|
+
|
|
45
|
+
| Tool | Açıklama |
|
|
46
|
+
|------|----------|
|
|
47
|
+
| `scan_text` | Verilen metin içinde secret tarar |
|
|
48
|
+
| `scan_file` | Tek bir dosyayı tarar |
|
|
49
|
+
| `scan_directory` | Bir dizini ve alt dizinlerini yinelemeli olarak tarar |
|
|
50
|
+
| `list_patterns` | Tüm aktif secret desenlerini listeler |
|
|
51
|
+
|
|
52
|
+
### Örnek Kullanım
|
|
53
|
+
|
|
54
|
+
Claude'a şunu sorabilirsiniz:
|
|
55
|
+
|
|
56
|
+
> "Projemdeki gizli bilgileri tara: `/Users/me/my-app`"
|
|
57
|
+
|
|
58
|
+
> "Bu `.env` dosyasında secret var mı?"
|
|
59
|
+
|
|
60
|
+
> "Şu metin güvenli mi: `export API_KEY=sk-abc123...`"
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Tespit Edilen Secret Türleri
|
|
65
|
+
|
|
66
|
+
| Kategori | Örnekler |
|
|
67
|
+
|----------|---------|
|
|
68
|
+
| Cloud | AWS Access Key, AWS Secret, GCP API Key |
|
|
69
|
+
| Source Control | GitHub PAT, GitLab Token |
|
|
70
|
+
| Ödeme | Stripe Secret/Publishable Key |
|
|
71
|
+
| AI | OpenAI API Key, Anthropic API Key, HuggingFace Token |
|
|
72
|
+
| İletişim | Slack Bot Token, Twilio Auth Token, SendGrid Key |
|
|
73
|
+
| Veritabanı | PostgreSQL URL, MySQL URL, MongoDB URI, Redis URL |
|
|
74
|
+
| Kriptografi | RSA Private Key, EC Private Key, OpenSSH Key, PGP Key |
|
|
75
|
+
| Web | JWT Token, Bearer Token |
|
|
76
|
+
| Paket / Platform | npm Token, Docker Hub Token, Vercel Token |
|
|
77
|
+
| Genel | `SECRET=`, `PASSWORD=`, `API_KEY=` kalıpları |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Güvenlik Notu
|
|
82
|
+
|
|
83
|
+
Bu araç tarama sonuçlarında eşleşen değerleri **kısmen redakte eder** (`sk_live_abc1...xyz9` gibi). Tam değerler asla log'a yazılmaz veya dışarı aktarılmaz.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Geliştirme
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm install
|
|
91
|
+
npm test # 16 unit test
|
|
92
|
+
npm run build # TypeScript derleme
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Lisans
|
|
98
|
+
|
|
99
|
+
MIT — [GuardBee](https://guardbee.ai)
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { startServer } from "./server.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { scanFile, scanDirectory } from "./scanner.js";
|
|
4
|
+
import { statSync } from "fs";
|
|
5
|
+
// ── Output ─────────────────────────────────────────────────────────────────────
|
|
6
|
+
const SEV_ICON = {
|
|
7
|
+
critical: "🔴",
|
|
8
|
+
high: "🟠",
|
|
9
|
+
medium: "🟡",
|
|
10
|
+
low: "🔵",
|
|
11
|
+
};
|
|
12
|
+
function printText(findings, scannedFiles, durationMs) {
|
|
13
|
+
if (findings.length === 0) {
|
|
14
|
+
console.log(`✅ No secrets found in ${scannedFiles} file(s) (${durationMs}ms)`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const counts = {};
|
|
18
|
+
for (const f of findings)
|
|
19
|
+
counts[f.severity] = (counts[f.severity] ?? 0) + 1;
|
|
20
|
+
console.log(`⚠️ Found ${findings.length} secret(s) in ${scannedFiles} file(s) (${durationMs}ms)`);
|
|
21
|
+
console.log(` Critical: ${counts["critical"] ?? 0} High: ${counts["high"] ?? 0} Medium: ${counts["medium"] ?? 0} Low: ${counts["low"] ?? 0}`);
|
|
22
|
+
console.log("");
|
|
23
|
+
for (const f of findings) {
|
|
24
|
+
const icon = SEV_ICON[f.severity] ?? "⚪";
|
|
25
|
+
console.log(`${icon} [${f.severity.toUpperCase()}] ${f.patternName}`);
|
|
26
|
+
if (f.file)
|
|
27
|
+
console.log(` File : ${f.file}:${f.line}:${f.column}`);
|
|
28
|
+
console.log(` Match : ${f.match}`);
|
|
29
|
+
console.log(` Context : ${f.context}`);
|
|
30
|
+
console.log("");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// ── CLI ────────────────────────────────────────────────────────────────────────
|
|
34
|
+
function parseArgs(args) {
|
|
35
|
+
const positionals = [];
|
|
36
|
+
let failOn = "any";
|
|
37
|
+
let format = "text";
|
|
38
|
+
let maxFiles = 5000;
|
|
39
|
+
for (const arg of args) {
|
|
40
|
+
if (arg.startsWith("--fail-on="))
|
|
41
|
+
failOn = arg.split("=")[1] ?? "any";
|
|
42
|
+
else if (arg.startsWith("--format="))
|
|
43
|
+
format = arg.split("=")[1] ?? "text";
|
|
44
|
+
else if (arg.startsWith("--max-files="))
|
|
45
|
+
maxFiles = parseInt(arg.split("=")[1] ?? "5000", 10);
|
|
46
|
+
else if (!arg.startsWith("--"))
|
|
47
|
+
positionals.push(arg);
|
|
48
|
+
}
|
|
49
|
+
return { positionals, failOn, format, maxFiles };
|
|
50
|
+
}
|
|
51
|
+
function shouldFail(findings, failOn) {
|
|
52
|
+
if (failOn === "none")
|
|
53
|
+
return false;
|
|
54
|
+
if (failOn === "any")
|
|
55
|
+
return findings.length > 0;
|
|
56
|
+
const RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
57
|
+
const threshold = RANK[failOn] ?? 1;
|
|
58
|
+
return findings.some((f) => (RANK[f.severity] ?? 3) <= threshold);
|
|
59
|
+
}
|
|
60
|
+
async function runCli(rawArgs) {
|
|
61
|
+
const { positionals, failOn, format, maxFiles } = parseArgs(rawArgs);
|
|
62
|
+
const target = positionals[0];
|
|
63
|
+
if (!target) {
|
|
64
|
+
console.error("Usage: guardbee-secret-scanner scan <path> [--fail-on=any] [--format=text|json]");
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
let stat;
|
|
68
|
+
try {
|
|
69
|
+
stat = statSync(target);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
console.error(`Path not found: ${target}`);
|
|
73
|
+
process.exit(2);
|
|
74
|
+
}
|
|
75
|
+
let findings;
|
|
76
|
+
let scannedFiles;
|
|
77
|
+
let durationMs;
|
|
78
|
+
if (stat.isDirectory()) {
|
|
79
|
+
const result = scanDirectory(target, { maxFiles });
|
|
80
|
+
findings = result.findings;
|
|
81
|
+
scannedFiles = result.scannedFiles;
|
|
82
|
+
durationMs = result.durationMs;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const start = Date.now();
|
|
86
|
+
const result = scanFile(target);
|
|
87
|
+
findings = result.findings;
|
|
88
|
+
scannedFiles = result.skipped ? 0 : 1;
|
|
89
|
+
durationMs = Date.now() - start;
|
|
90
|
+
}
|
|
91
|
+
if (format === "json") {
|
|
92
|
+
console.log(JSON.stringify({ findings, scannedFiles, durationMs }, null, 2));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
printText(findings, scannedFiles, durationMs);
|
|
96
|
+
}
|
|
97
|
+
process.exit(shouldFail(findings, failOn) ? 1 : 0);
|
|
98
|
+
}
|
|
99
|
+
function printHelp() {
|
|
100
|
+
console.log(`@guardbee/mcp-secret-scanner
|
|
101
|
+
|
|
102
|
+
Usage (MCP server):
|
|
103
|
+
guardbee-secret-scanner [serve]
|
|
104
|
+
|
|
105
|
+
Usage (CLI):
|
|
106
|
+
guardbee-secret-scanner scan <path> Scan a file or directory for secrets
|
|
107
|
+
|
|
108
|
+
Options:
|
|
109
|
+
--fail-on=<level> Exit 1 if secrets at this severity or above are found
|
|
110
|
+
Levels: any (default) | critical | high | medium | low | none
|
|
111
|
+
--format=<fmt> Output format: text (default) | json
|
|
112
|
+
--max-files=<n> Max files to scan (default: 5000)
|
|
113
|
+
|
|
114
|
+
Exit codes:
|
|
115
|
+
0 No secrets found (or none above --fail-on threshold)
|
|
116
|
+
1 Secrets found at or above threshold
|
|
117
|
+
2 Error / bad arguments
|
|
118
|
+
`);
|
|
119
|
+
}
|
|
120
|
+
// ── Entry point ────────────────────────────────────────────────────────────────
|
|
121
|
+
const [, , firstArg, ...rest] = process.argv;
|
|
122
|
+
if (!firstArg || firstArg === "serve") {
|
|
123
|
+
startServer().catch((err) => {
|
|
124
|
+
process.stderr.write(`[guardbee-secret-scanner] Fatal error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else if (firstArg === "--help" || firstArg === "-h") {
|
|
129
|
+
printHelp();
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
else if (firstArg === "scan") {
|
|
133
|
+
runCli(rest).catch((err) => {
|
|
134
|
+
console.error("Error:", err.message);
|
|
135
|
+
process.exit(2);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.error(`Unknown command: ${firstArg}`);
|
|
140
|
+
console.error("Run with --help for usage.");
|
|
141
|
+
process.exit(2);
|
|
142
|
+
}
|
|
6
143
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|