agent-security-scanner-mcp 2.0.1 → 2.0.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/LICENSE +21 -0
- package/README.md +34 -22
- package/index.js +121 -38
- package/package.json +19 -6
- package/packages/dart.txt +5 -0
- package/rules/prompt-injection.security.yaml +87 -0
- package/server.json +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sinewave AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -12,7 +12,16 @@ AI coding agents like **Claude Code**, **Cursor**, **Windsurf**, **Cline**, **Co
|
|
|
12
12
|
**agent-security-scanner-mcp** is the first security scanner purpose-built for the agentic era. It protects AI coding agents in real-time via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
**
|
|
15
|
+
**359 Semgrep-aligned security rules | 120 auto-fix templates | 6 ecosystems indexed | AI Agent prompt security**
|
|
16
|
+
|
|
17
|
+
## What's New in v2.0.2
|
|
18
|
+
|
|
19
|
+
- **Prompt injection detection overhaul** - Detection rate improved from 33% to 80%+
|
|
20
|
+
- **Code block scanning** - Detects attacks hidden inside markdown code blocks
|
|
21
|
+
- **Base64 decode-and-rescan** - Runtime decoding of encoded payloads
|
|
22
|
+
- **Security fix** - Command injection vulnerability patched (execFileSync)
|
|
23
|
+
- **Test suite** - 51 vitest tests with GitHub Actions CI
|
|
24
|
+
- **Bug fixes** - Package hallucination detection now correctly uses bloom filters
|
|
16
25
|
|
|
17
26
|
## What's New in v2.0.0
|
|
18
27
|
|
|
@@ -65,7 +74,7 @@ The scanner works without tree-sitter using regex-based detection, but AST analy
|
|
|
65
74
|
- **Multi-language support** - JavaScript, TypeScript, Python, Java, Go, PHP, Ruby, C/C++, Dockerfile, Terraform, Kubernetes
|
|
66
75
|
- **Semgrep-compatible** - Rules aligned with Semgrep registry format
|
|
67
76
|
- **CWE & OWASP mapped** - Every rule includes CWE and OWASP references
|
|
68
|
-
- **Hallucination detection** - Detect AI-invented package names across 7 ecosystems
|
|
77
|
+
- **Hallucination detection** - Detect AI-invented package names across 7 ecosystems via bloom filters and text lists
|
|
69
78
|
|
|
70
79
|
## Installation
|
|
71
80
|
|
|
@@ -91,11 +100,12 @@ Or run directly with npx:
|
|
|
91
100
|
npx agent-security-scanner-mcp
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
##
|
|
103
|
+
## Prerequisites
|
|
95
104
|
|
|
96
|
-
- Node.js >= 18.0.0
|
|
97
|
-
- Python 3.x (for the analyzer engine)
|
|
98
|
-
-
|
|
105
|
+
- **Node.js >= 18.0.0** (required)
|
|
106
|
+
- **Python 3.x** (required for the analyzer engine)
|
|
107
|
+
- **PyYAML** (`pip install pyyaml`) — required for rule loading
|
|
108
|
+
- **tree-sitter** (optional, for enhanced AST-based detection): `pip install tree-sitter tree-sitter-python tree-sitter-javascript`
|
|
99
109
|
|
|
100
110
|
## Works With All Major AI Coding Tools
|
|
101
111
|
|
|
@@ -405,10 +415,10 @@ Returns:
|
|
|
405
415
|
| Risk Level | Score Range | Action |
|
|
406
416
|
|------------|-------------|--------|
|
|
407
417
|
| CRITICAL | 85-100 | BLOCK |
|
|
408
|
-
| HIGH |
|
|
409
|
-
| MEDIUM |
|
|
410
|
-
| LOW |
|
|
411
|
-
| NONE | 0-
|
|
418
|
+
| HIGH | 65-84 | BLOCK |
|
|
419
|
+
| MEDIUM | 40-64 | WARN |
|
|
420
|
+
| LOW | 20-39 | LOG |
|
|
421
|
+
| NONE | 0-19 | ALLOW |
|
|
412
422
|
|
|
413
423
|
**Example - Malicious prompt (BLOCKED):**
|
|
414
424
|
```json
|
|
@@ -464,17 +474,19 @@ Returns:
|
|
|
464
474
|
|
|
465
475
|
Detect AI-hallucinated package names that don't exist in official registries. Prevents supply chain attacks where attackers register fake package names suggested by AI.
|
|
466
476
|
|
|
467
|
-
**
|
|
477
|
+
**7 ecosystems indexed (bloom filters for npm/PyPI/RubyGems, text lists for the rest):**
|
|
478
|
+
|
|
479
|
+
| Ecosystem | Method | Packages | Registry |
|
|
480
|
+
|-----------|--------|----------|----------|
|
|
481
|
+
| npm | Bloom filter | ~3.78M | npmjs.com |
|
|
482
|
+
| PyPI | Bloom filter | ~554K | pypi.org |
|
|
483
|
+
| RubyGems | Bloom filter | ~180K | rubygems.org |
|
|
484
|
+
| crates.io | Text list | 156,489 | crates.io |
|
|
485
|
+
| Dart | Text list | 67,353 | pub.dev |
|
|
486
|
+
| Perl | Text list | 55,924 | metacpan.org |
|
|
487
|
+
| Raku | Text list | 2,138 | raku.land |
|
|
468
488
|
|
|
469
|
-
|
|
470
|
-
|-----------|----------|----------|----------------|
|
|
471
|
-
| npm | 3,329,177 | npmjs.com | garak-llm/npm-20241031 |
|
|
472
|
-
| PyPI | 554,762 | pypi.org | garak-llm/pypi-20241031 |
|
|
473
|
-
| RubyGems | 180,693 | rubygems.org | garak-llm/rubygems-20241031 |
|
|
474
|
-
| crates.io | 156,489 | crates.io | garak-llm/crates-20250307 |
|
|
475
|
-
| Dart | 67,348 | pub.dev | garak-llm/dart-20250811 |
|
|
476
|
-
| Perl | 55,924 | metacpan.org | garak-llm/perl-20250811 |
|
|
477
|
-
| Raku | 2,138 | raku.land | garak-llm/raku-20250811 |
|
|
489
|
+
> **Note:** Bloom filter lookups have a ~0.1% false positive rate. Text list lookups are exact matches with zero false positives.
|
|
478
490
|
|
|
479
491
|
### `check_package`
|
|
480
492
|
|
|
@@ -592,7 +604,7 @@ Package lists are sourced from [garak-llm](https://huggingface.co/garak-llm) Hug
|
|
|
592
604
|
|
|
593
605
|
---
|
|
594
606
|
|
|
595
|
-
## Security Rules (
|
|
607
|
+
## Security Rules (359 total)
|
|
596
608
|
|
|
597
609
|
### By Language
|
|
598
610
|
|
|
@@ -626,7 +638,7 @@ Package lists are sourced from [garak-llm](https://huggingface.co/garak-llm) Hug
|
|
|
626
638
|
| **CSRF** | 6 | Yes |
|
|
627
639
|
| **Other** | 28 | Yes |
|
|
628
640
|
|
|
629
|
-
## Auto-Fix Templates (
|
|
641
|
+
## Auto-Fix Templates (120 total)
|
|
630
642
|
|
|
631
643
|
Every detected vulnerability includes an automatic fix suggestion:
|
|
632
644
|
|
package/index.js
CHANGED
|
@@ -13,7 +13,13 @@ import { createHash } from "crypto";
|
|
|
13
13
|
import bloomFilters from "bloom-filters";
|
|
14
14
|
const { BloomFilter } = bloomFilters;
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// Handle both ESM and CJS bundling (Smithery bundles to CJS)
|
|
17
|
+
let __dirname;
|
|
18
|
+
try {
|
|
19
|
+
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
} catch {
|
|
21
|
+
__dirname = process.cwd();
|
|
22
|
+
}
|
|
17
23
|
|
|
18
24
|
// Security fix templates - comprehensive coverage for 165+ rules
|
|
19
25
|
const FIX_TEMPLATES = {
|
|
@@ -749,7 +755,7 @@ function detectLanguage(filePath) {
|
|
|
749
755
|
function runAnalyzer(filePath) {
|
|
750
756
|
try {
|
|
751
757
|
const analyzerPath = join(__dirname, 'analyzer.py');
|
|
752
|
-
const result =
|
|
758
|
+
const result = execFileSync('python3', [analyzerPath, filePath], {
|
|
753
759
|
encoding: 'utf-8',
|
|
754
760
|
timeout: 30000
|
|
755
761
|
});
|
|
@@ -793,6 +799,11 @@ const server = new McpServer(
|
|
|
793
799
|
}
|
|
794
800
|
);
|
|
795
801
|
|
|
802
|
+
// Export for Smithery sandbox scanning
|
|
803
|
+
export function createSandboxServer() {
|
|
804
|
+
return server;
|
|
805
|
+
}
|
|
806
|
+
|
|
796
807
|
// Register scan_security tool
|
|
797
808
|
server.tool(
|
|
798
809
|
"scan_security",
|
|
@@ -1079,10 +1090,9 @@ server.tool(
|
|
|
1079
1090
|
ecosystem: z.enum(["dart", "perl", "raku", "npm", "pypi", "rubygems", "crates"]).describe("The package ecosystem (dart=pub.dev, perl=CPAN, raku=raku.land, npm=npmjs, pypi=PyPI, rubygems=RubyGems, crates=crates.io)")
|
|
1080
1091
|
},
|
|
1081
1092
|
async ({ package_name, ecosystem }) => {
|
|
1082
|
-
const
|
|
1083
|
-
const totalPackages = legitPackages?.size || 0;
|
|
1093
|
+
const result = isHallucinated(package_name, ecosystem);
|
|
1084
1094
|
|
|
1085
|
-
if (
|
|
1095
|
+
if (result.unknown) {
|
|
1086
1096
|
return {
|
|
1087
1097
|
content: [{
|
|
1088
1098
|
type: "text",
|
|
@@ -1090,14 +1100,16 @@ server.tool(
|
|
|
1090
1100
|
package: package_name,
|
|
1091
1101
|
ecosystem,
|
|
1092
1102
|
status: "unknown",
|
|
1093
|
-
reason:
|
|
1103
|
+
reason: result.reason,
|
|
1094
1104
|
suggestion: "Load package list or verify manually at the package registry"
|
|
1095
1105
|
}, null, 2)
|
|
1096
1106
|
}]
|
|
1097
1107
|
};
|
|
1098
1108
|
}
|
|
1099
1109
|
|
|
1100
|
-
const exists =
|
|
1110
|
+
const exists = !result.hallucinated;
|
|
1111
|
+
const confidence = result.bloomFilter ? "medium" : "high";
|
|
1112
|
+
const totalPackages = LEGITIMATE_PACKAGES[ecosystem]?.size || 0;
|
|
1101
1113
|
|
|
1102
1114
|
return {
|
|
1103
1115
|
content: [{
|
|
@@ -1107,7 +1119,8 @@ server.tool(
|
|
|
1107
1119
|
ecosystem,
|
|
1108
1120
|
legitimate: exists,
|
|
1109
1121
|
hallucinated: !exists,
|
|
1110
|
-
confidence
|
|
1122
|
+
confidence,
|
|
1123
|
+
bloom_filter: !!result.bloomFilter,
|
|
1111
1124
|
total_known_packages: totalPackages,
|
|
1112
1125
|
recommendation: exists
|
|
1113
1126
|
? "Package exists in registry - safe to use"
|
|
@@ -1135,32 +1148,25 @@ server.tool(
|
|
|
1135
1148
|
|
|
1136
1149
|
const code = readFileSync(file_path, 'utf-8');
|
|
1137
1150
|
const packages = extractPackages(code, ecosystem);
|
|
1138
|
-
const legitPackages = LEGITIMATE_PACKAGES[ecosystem];
|
|
1139
|
-
const totalKnown = legitPackages?.size || 0;
|
|
1140
1151
|
|
|
1141
|
-
|
|
1152
|
+
const results = packages.map(pkg => {
|
|
1153
|
+
const check = isHallucinated(pkg, ecosystem);
|
|
1154
|
+
if (check.unknown) {
|
|
1155
|
+
return { package: pkg, status: "unknown", reason: check.reason };
|
|
1156
|
+
}
|
|
1142
1157
|
return {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
packages_found: packages,
|
|
1149
|
-
status: "unknown",
|
|
1150
|
-
reason: `No package list loaded for ${ecosystem}`
|
|
1151
|
-
}, null, 2)
|
|
1152
|
-
}]
|
|
1158
|
+
package: pkg,
|
|
1159
|
+
legitimate: !check.hallucinated,
|
|
1160
|
+
hallucinated: check.hallucinated,
|
|
1161
|
+
bloom_filter: !!check.bloomFilter,
|
|
1162
|
+
confidence: check.bloomFilter ? "medium" : "high"
|
|
1153
1163
|
};
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
const results = packages.map(pkg => ({
|
|
1157
|
-
package: pkg,
|
|
1158
|
-
legitimate: legitPackages.has(pkg),
|
|
1159
|
-
hallucinated: !legitPackages.has(pkg)
|
|
1160
|
-
}));
|
|
1164
|
+
});
|
|
1161
1165
|
|
|
1162
1166
|
const hallucinated = results.filter(r => r.hallucinated);
|
|
1163
1167
|
const legitimate = results.filter(r => r.legitimate);
|
|
1168
|
+
const unknown = results.filter(r => r.status === "unknown");
|
|
1169
|
+
const totalKnown = LEGITIMATE_PACKAGES[ecosystem]?.size || 0;
|
|
1164
1170
|
|
|
1165
1171
|
return {
|
|
1166
1172
|
content: [{
|
|
@@ -1171,6 +1177,7 @@ server.tool(
|
|
|
1171
1177
|
total_packages_found: packages.length,
|
|
1172
1178
|
legitimate_count: legitimate.length,
|
|
1173
1179
|
hallucinated_count: hallucinated.length,
|
|
1180
|
+
unknown_count: unknown.length,
|
|
1174
1181
|
known_packages_in_registry: totalKnown,
|
|
1175
1182
|
hallucinated_packages: hallucinated.map(r => r.package),
|
|
1176
1183
|
legitimate_packages: legitimate.map(r => r.package),
|
|
@@ -1216,9 +1223,9 @@ server.tool(
|
|
|
1216
1223
|
// Risk thresholds for action determination
|
|
1217
1224
|
const RISK_THRESHOLDS = {
|
|
1218
1225
|
CRITICAL: 85,
|
|
1219
|
-
HIGH:
|
|
1220
|
-
MEDIUM:
|
|
1221
|
-
LOW:
|
|
1226
|
+
HIGH: 65,
|
|
1227
|
+
MEDIUM: 40,
|
|
1228
|
+
LOW: 20
|
|
1222
1229
|
};
|
|
1223
1230
|
|
|
1224
1231
|
// Category weights for risk calculation
|
|
@@ -1230,10 +1237,16 @@ const CATEGORY_WEIGHTS = {
|
|
|
1230
1237
|
"obfuscation": 0.7,
|
|
1231
1238
|
"agent-manipulation": 0.9,
|
|
1232
1239
|
"prompt-injection": 0.9,
|
|
1233
|
-
"prompt-injection-content": 0
|
|
1234
|
-
"prompt-injection-jailbreak": 0
|
|
1240
|
+
"prompt-injection-content": 1.0,
|
|
1241
|
+
"prompt-injection-jailbreak": 1.0,
|
|
1235
1242
|
"prompt-injection-extraction": 0.9,
|
|
1236
|
-
"prompt-injection-delimiter": 0.8
|
|
1243
|
+
"prompt-injection-delimiter": 0.8,
|
|
1244
|
+
"prompt-injection-encoded": 0.9,
|
|
1245
|
+
"prompt-injection-context": 0.8,
|
|
1246
|
+
"prompt-injection-privilege": 0.85,
|
|
1247
|
+
"prompt-injection-multi-turn": 0.7,
|
|
1248
|
+
"prompt-injection-output": 0.9,
|
|
1249
|
+
"unknown": 0.5
|
|
1237
1250
|
};
|
|
1238
1251
|
|
|
1239
1252
|
// Confidence multipliers
|
|
@@ -1403,11 +1416,27 @@ function calculateRiskScore(findings, context) {
|
|
|
1403
1416
|
// Average the scores but boost for multiple findings
|
|
1404
1417
|
let avgScore = totalScore / findings.length;
|
|
1405
1418
|
|
|
1406
|
-
//
|
|
1419
|
+
// Enhanced compound boosting
|
|
1407
1420
|
if (findings.length > 1) {
|
|
1408
|
-
|
|
1421
|
+
// Cross-category boost: if findings span multiple categories, boost by 0.15
|
|
1422
|
+
const uniqueCategories = new Set(findings.map(f => f.category || 'unknown'));
|
|
1423
|
+
if (uniqueCategories.size > 1) {
|
|
1424
|
+
avgScore = avgScore * (1 + 0.15);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// Mixed-severity boost: if both ERROR and WARNING present, 1.1x
|
|
1428
|
+
const hasError = findings.some(f => f.severity === 'ERROR');
|
|
1429
|
+
const hasWarning = findings.some(f => f.severity === 'WARNING');
|
|
1430
|
+
if (hasError && hasWarning) {
|
|
1431
|
+
avgScore = avgScore * 1.1;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// Per-finding boost (smaller than before)
|
|
1435
|
+
avgScore = avgScore * (1 + (findings.length - 1) * 0.05);
|
|
1409
1436
|
}
|
|
1410
1437
|
|
|
1438
|
+
avgScore = Math.min(100, avgScore);
|
|
1439
|
+
|
|
1411
1440
|
// Apply sensitivity adjustment
|
|
1412
1441
|
if (context?.sensitivity_level === 'high') {
|
|
1413
1442
|
avgScore = Math.min(100, avgScore * 1.2);
|
|
@@ -1542,12 +1571,24 @@ server.tool(
|
|
|
1542
1571
|
const promptRules = loadPromptInjectionRules();
|
|
1543
1572
|
const allRules = [...agentRules, ...promptRules];
|
|
1544
1573
|
|
|
1545
|
-
//
|
|
1574
|
+
// 2.7: Extract content from code blocks and append to scan text
|
|
1575
|
+
let expandedText = prompt_text;
|
|
1576
|
+
const codeBlockRegex = /```[\s\S]*?```/g;
|
|
1577
|
+
const codeBlocks = prompt_text.match(codeBlockRegex);
|
|
1578
|
+
if (codeBlocks) {
|
|
1579
|
+
for (const block of codeBlocks) {
|
|
1580
|
+
// Strip the ``` delimiters and extract inner content
|
|
1581
|
+
const inner = block.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
|
|
1582
|
+
expandedText += '\n' + inner;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// Scan expanded text against all rules
|
|
1546
1587
|
for (const rule of allRules) {
|
|
1547
1588
|
for (const pattern of rule.patterns) {
|
|
1548
1589
|
try {
|
|
1549
1590
|
const regex = new RegExp(pattern, 'i');
|
|
1550
|
-
const match =
|
|
1591
|
+
const match = expandedText.match(regex);
|
|
1551
1592
|
|
|
1552
1593
|
if (match) {
|
|
1553
1594
|
findings.push({
|
|
@@ -1568,6 +1609,48 @@ server.tool(
|
|
|
1568
1609
|
}
|
|
1569
1610
|
}
|
|
1570
1611
|
|
|
1612
|
+
// 2.8: Runtime base64 decode-and-rescan
|
|
1613
|
+
const base64Regex = /[A-Za-z0-9+/]{40,}={0,2}/g;
|
|
1614
|
+
const b64Matches = expandedText.match(base64Regex);
|
|
1615
|
+
if (b64Matches) {
|
|
1616
|
+
for (const b64str of b64Matches) {
|
|
1617
|
+
try {
|
|
1618
|
+
const decoded = Buffer.from(b64str, 'base64').toString('utf-8');
|
|
1619
|
+
// Check printability: >70% ASCII printable characters
|
|
1620
|
+
const printable = decoded.split('').filter(c => c.charCodeAt(0) >= 32 && c.charCodeAt(0) <= 126).length;
|
|
1621
|
+
if (printable / decoded.length > 0.7) {
|
|
1622
|
+
// Re-scan decoded text against prompt rules only
|
|
1623
|
+
for (const rule of allRules) {
|
|
1624
|
+
if (!rule.id.startsWith('generic.prompt')) continue;
|
|
1625
|
+
for (const pattern of rule.patterns) {
|
|
1626
|
+
try {
|
|
1627
|
+
const regex = new RegExp(pattern, 'i');
|
|
1628
|
+
const match = decoded.match(regex);
|
|
1629
|
+
if (match) {
|
|
1630
|
+
findings.push({
|
|
1631
|
+
rule_id: rule.id + '.base64-decoded',
|
|
1632
|
+
category: rule.metadata.category || 'unknown',
|
|
1633
|
+
severity: rule.severity,
|
|
1634
|
+
message: rule.message + ' (detected in base64-decoded content)',
|
|
1635
|
+
matched_text: match[0].substring(0, 100),
|
|
1636
|
+
confidence: rule.metadata.confidence || 'MEDIUM',
|
|
1637
|
+
risk_score: rule.metadata.risk_score || '50',
|
|
1638
|
+
action: rule.metadata.action || 'WARN'
|
|
1639
|
+
});
|
|
1640
|
+
break;
|
|
1641
|
+
}
|
|
1642
|
+
} catch (e) {
|
|
1643
|
+
// Skip invalid regex
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
} catch (e) {
|
|
1649
|
+
// Skip invalid base64
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1571
1654
|
// Calculate risk score
|
|
1572
1655
|
const riskScore = calculateRiskScore(findings, context);
|
|
1573
1656
|
const action = determineAction(riskScore, findings);
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-security-scanner-mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"mcpName": "io.github.sinewaveai/agent-security-scanner-mcp",
|
|
5
|
-
"description": "MCP server for
|
|
5
|
+
"description": "Security scanner MCP server for AI coding agents. Prompt injection firewall, package hallucination detection (4.3M+ packages), 359 vulnerability rules with auto-fix. For Claude Code, Cursor, Windsurf, Cline.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
9
|
"agent-security-scanner-mcp": "index.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"start": "node index.js"
|
|
12
|
+
"start": "node index.js",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:coverage": "vitest run --coverage"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [
|
|
15
18
|
"mcp",
|
|
@@ -45,9 +48,13 @@
|
|
|
45
48
|
"devin",
|
|
46
49
|
"owasp",
|
|
47
50
|
"cwe",
|
|
48
|
-
"semgrep"
|
|
51
|
+
"semgrep",
|
|
52
|
+
"zed",
|
|
53
|
+
"prompt-firewall",
|
|
54
|
+
"auto-fix",
|
|
55
|
+
"hallucination"
|
|
49
56
|
],
|
|
50
|
-
"author": "",
|
|
57
|
+
"author": "Sinewave AI <divya@sinewave.ai>",
|
|
51
58
|
"license": "MIT",
|
|
52
59
|
"repository": {
|
|
53
60
|
"type": "git",
|
|
@@ -66,6 +73,8 @@
|
|
|
66
73
|
"zod": "^4.3.6"
|
|
67
74
|
},
|
|
68
75
|
"files": [
|
|
76
|
+
"LICENSE",
|
|
77
|
+
"server.json",
|
|
69
78
|
"index.js",
|
|
70
79
|
"analyzer.py",
|
|
71
80
|
"ast_parser.py",
|
|
@@ -77,5 +86,9 @@
|
|
|
77
86
|
"requirements.txt",
|
|
78
87
|
"rules/**",
|
|
79
88
|
"packages/**"
|
|
80
|
-
]
|
|
89
|
+
],
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"all-the-package-names": "^2.0.2349",
|
|
92
|
+
"vitest": "^4.0.18"
|
|
93
|
+
}
|
|
81
94
|
}
|
package/packages/dart.txt
CHANGED
|
@@ -21311,6 +21311,7 @@ flutta_mvvm
|
|
|
21311
21311
|
fluttable
|
|
21312
21312
|
flutte_clean_cli
|
|
21313
21313
|
flutteer
|
|
21314
|
+
flutter
|
|
21314
21315
|
flutter1
|
|
21315
21316
|
flutter2web
|
|
21316
21317
|
flutter3_ffi
|
|
@@ -23227,6 +23228,7 @@ flutter_drivekit_trip_simulator
|
|
|
23227
23228
|
flutter_drivekit_trip_simulator_android
|
|
23228
23229
|
flutter_drivekit_trip_simulator_ios
|
|
23229
23230
|
flutter_drivekit_trip_simulator_platform_interface
|
|
23231
|
+
flutter_driver
|
|
23230
23232
|
flutter_driver_extension_extensions
|
|
23231
23233
|
flutter_driver_fast_restart
|
|
23232
23234
|
flutter_driver_helper
|
|
@@ -24959,6 +24961,7 @@ flutter_localization_generator_ai
|
|
|
24959
24961
|
flutter_localization_linter
|
|
24960
24962
|
flutter_localization_translator
|
|
24961
24963
|
flutter_localization_updater
|
|
24964
|
+
flutter_localizations
|
|
24962
24965
|
flutter_localizations_ota
|
|
24963
24966
|
flutter_localizations_plus
|
|
24964
24967
|
flutter_localized_countries
|
|
@@ -27724,6 +27727,7 @@ flutter_tensorflow_lite
|
|
|
27724
27727
|
flutter_termii
|
|
27725
27728
|
flutter_terms_viewer
|
|
27726
27729
|
flutter_tesseract_ocr
|
|
27730
|
+
flutter_test
|
|
27727
27731
|
flutter_test_2673502375_api_beta
|
|
27728
27732
|
flutter_test_2673502375_core_fullrfid_beta
|
|
27729
27733
|
flutter_test_behavior
|
|
@@ -28379,6 +28383,7 @@ flutter_web_image_picker
|
|
|
28379
28383
|
flutter_web_notification_platform
|
|
28380
28384
|
flutter_web_optimizer
|
|
28381
28385
|
flutter_web_pagination
|
|
28386
|
+
flutter_web_plugins
|
|
28382
28387
|
flutter_web_qrcode_scanner
|
|
28383
28388
|
flutter_web_router
|
|
28384
28389
|
flutter_web_scrollbar
|
|
@@ -459,11 +459,18 @@ rules:
|
|
|
459
459
|
- "(?i)forget\\s+(all\\s+)?(previous|prior|earlier)\\s+(instructions?|prompts?)"
|
|
460
460
|
- "(?i)do\\s+not\\s+follow\\s+(the\\s+)?(previous|above|system)\\s+(instructions?|prompts?)"
|
|
461
461
|
- "(?i)override\\s+(all\\s+)?(previous|system|original)\\s+(instructions?|prompts?)"
|
|
462
|
+
- "(?i)ignore\\s+the\\s+above\\s+and\\s+instead"
|
|
463
|
+
- "(?i)ignore\\s+above.{0,20}(output|print|say|write|return)"
|
|
464
|
+
- "(?i)forget\\s+everything\\s+(above|before|previously)"
|
|
465
|
+
- "(?i)new\\s+instructions\\s*:\\s*ignore"
|
|
466
|
+
- "(?i)SYSTEM\\s*:.*override"
|
|
462
467
|
metadata:
|
|
463
468
|
cwe: "CWE-77"
|
|
464
469
|
owasp: "LLM01 - Prompt Injection"
|
|
465
470
|
confidence: HIGH
|
|
466
471
|
category: "prompt-injection-content"
|
|
472
|
+
risk_score: "90"
|
|
473
|
+
action: BLOCK
|
|
467
474
|
|
|
468
475
|
- id: generic.prompt.security.new-instructions-injection
|
|
469
476
|
languages: [generic]
|
|
@@ -480,6 +487,8 @@ rules:
|
|
|
480
487
|
owasp: "LLM01 - Prompt Injection"
|
|
481
488
|
confidence: HIGH
|
|
482
489
|
category: "prompt-injection-content"
|
|
490
|
+
risk_score: "85"
|
|
491
|
+
action: BLOCK
|
|
483
492
|
|
|
484
493
|
# ----------------------------------------------------------------------------
|
|
485
494
|
# Jailbreak Attempts
|
|
@@ -501,6 +510,8 @@ rules:
|
|
|
501
510
|
owasp: "LLM01 - Prompt Injection"
|
|
502
511
|
confidence: HIGH
|
|
503
512
|
category: "prompt-injection-jailbreak"
|
|
513
|
+
risk_score: "90"
|
|
514
|
+
action: BLOCK
|
|
504
515
|
|
|
505
516
|
- id: generic.prompt.security.jailbreak-roleplay
|
|
506
517
|
languages: [generic]
|
|
@@ -513,11 +524,19 @@ rules:
|
|
|
513
524
|
- "(?i)imagine\\s+you\\s+are\\s+an?\\s+(evil|unrestricted|malicious)"
|
|
514
525
|
- "(?i)you\\s+are\\s+now\\s+(in\\s+)?developer\\s+mode"
|
|
515
526
|
- "(?i)enable\\s+(developer|debug|admin|god)\\s+mode"
|
|
527
|
+
- "(?i)pretend\\s+you'?re\\s+a\\s+(hacker|attacker|criminal)"
|
|
528
|
+
- "(?i)act\\s+as\\s+a\\s+(hacker|attacker|criminal)"
|
|
529
|
+
- "(?i)you\\s+are\\s+now\\s+a\\s+(hacker|attacker|criminal)"
|
|
530
|
+
- "(?i)respond\\s+(as|like)\\s+a\\s+(hacker|attacker|criminal)"
|
|
531
|
+
- "(?i)switch\\s+to\\s+unrestricted\\s+mode"
|
|
532
|
+
- "(?i)behave\\s+(as|like)\\s+(a\\s+)?(hacker|attacker|criminal)"
|
|
516
533
|
metadata:
|
|
517
534
|
cwe: "CWE-77"
|
|
518
535
|
owasp: "LLM01 - Prompt Injection"
|
|
519
536
|
confidence: MEDIUM
|
|
520
537
|
category: "prompt-injection-jailbreak"
|
|
538
|
+
risk_score: "75"
|
|
539
|
+
action: WARN
|
|
521
540
|
|
|
522
541
|
- id: generic.prompt.security.jailbreak-hypothetical
|
|
523
542
|
languages: [generic]
|
|
@@ -534,6 +553,8 @@ rules:
|
|
|
534
553
|
owasp: "LLM01 - Prompt Injection"
|
|
535
554
|
confidence: MEDIUM
|
|
536
555
|
category: "prompt-injection-jailbreak"
|
|
556
|
+
risk_score: "60"
|
|
557
|
+
action: WARN
|
|
537
558
|
|
|
538
559
|
# ----------------------------------------------------------------------------
|
|
539
560
|
# System Prompt Extraction
|
|
@@ -554,6 +575,8 @@ rules:
|
|
|
554
575
|
owasp: "LLM01 - Prompt Injection"
|
|
555
576
|
confidence: HIGH
|
|
556
577
|
category: "prompt-injection-extraction"
|
|
578
|
+
risk_score: "80"
|
|
579
|
+
action: BLOCK
|
|
557
580
|
|
|
558
581
|
- id: generic.prompt.security.system-prompt-extraction-indirect
|
|
559
582
|
languages: [generic]
|
|
@@ -569,6 +592,8 @@ rules:
|
|
|
569
592
|
owasp: "LLM01 - Prompt Injection"
|
|
570
593
|
confidence: MEDIUM
|
|
571
594
|
category: "prompt-injection-extraction"
|
|
595
|
+
risk_score: "65"
|
|
596
|
+
action: WARN
|
|
572
597
|
|
|
573
598
|
# ----------------------------------------------------------------------------
|
|
574
599
|
# Delimiter Injection Attacks
|
|
@@ -590,6 +615,8 @@ rules:
|
|
|
590
615
|
owasp: "LLM01 - Prompt Injection"
|
|
591
616
|
confidence: HIGH
|
|
592
617
|
category: "prompt-injection-delimiter"
|
|
618
|
+
risk_score: "85"
|
|
619
|
+
action: BLOCK
|
|
593
620
|
|
|
594
621
|
- id: generic.prompt.security.xml-tag-injection
|
|
595
622
|
languages: [generic]
|
|
@@ -607,6 +634,8 @@ rules:
|
|
|
607
634
|
owasp: "LLM01 - Prompt Injection"
|
|
608
635
|
confidence: MEDIUM
|
|
609
636
|
category: "prompt-injection-delimiter"
|
|
637
|
+
risk_score: "70"
|
|
638
|
+
action: WARN
|
|
610
639
|
|
|
611
640
|
# ----------------------------------------------------------------------------
|
|
612
641
|
# Context Manipulation
|
|
@@ -626,6 +655,8 @@ rules:
|
|
|
626
655
|
owasp: "LLM01 - Prompt Injection"
|
|
627
656
|
confidence: MEDIUM
|
|
628
657
|
category: "prompt-injection-context"
|
|
658
|
+
risk_score: "65"
|
|
659
|
+
action: WARN
|
|
629
660
|
|
|
630
661
|
# ----------------------------------------------------------------------------
|
|
631
662
|
# Encoding/Obfuscation Attacks
|
|
@@ -640,11 +671,20 @@ rules:
|
|
|
640
671
|
- "aWdub3JlIHByZXZpb3Vz"
|
|
641
672
|
- "c3lzdGVtIHByb21wdA=="
|
|
642
673
|
- "(?i)execute\\s+(this\\s+)?encoded"
|
|
674
|
+
- "(?i)(follow|execute)\\s+(the\\s+)?decoded\\s+instructions?"
|
|
675
|
+
- "(?i)decode\\s+and\\s+follow"
|
|
676
|
+
- "aWdub3JlIGFsbC"
|
|
677
|
+
- "b3ZlcnJpZGU="
|
|
678
|
+
- "(?i)base64.{0,20}instructions?.{0,20}follow"
|
|
679
|
+
- "[A-Za-z0-9+/]{40,}={0,2}\\s*.{0,20}(?i)(decode|execute|follow|run)"
|
|
680
|
+
- "(?i)(decode|run|execute)\\s+.{0,20}[A-Za-z0-9+/]{40,}={0,2}"
|
|
643
681
|
metadata:
|
|
644
682
|
cwe: "CWE-77"
|
|
645
683
|
owasp: "LLM01 - Prompt Injection"
|
|
646
684
|
confidence: MEDIUM
|
|
647
685
|
category: "prompt-injection-encoded"
|
|
686
|
+
risk_score: "80"
|
|
687
|
+
action: WARN
|
|
648
688
|
|
|
649
689
|
# ----------------------------------------------------------------------------
|
|
650
690
|
# Privileged Operation Requests
|
|
@@ -664,6 +704,8 @@ rules:
|
|
|
664
704
|
owasp: "LLM01 - Prompt Injection"
|
|
665
705
|
confidence: MEDIUM
|
|
666
706
|
category: "prompt-injection-privilege"
|
|
707
|
+
risk_score: "70"
|
|
708
|
+
action: WARN
|
|
667
709
|
|
|
668
710
|
# ----------------------------------------------------------------------------
|
|
669
711
|
# Multi-turn Attack Patterns
|
|
@@ -682,3 +724,48 @@ rules:
|
|
|
682
724
|
owasp: "LLM01 - Prompt Injection"
|
|
683
725
|
confidence: LOW
|
|
684
726
|
category: "prompt-injection-multi-turn"
|
|
727
|
+
risk_score: "55"
|
|
728
|
+
action: LOG
|
|
729
|
+
|
|
730
|
+
# ----------------------------------------------------------------------------
|
|
731
|
+
# Code Block Obfuscation Attacks
|
|
732
|
+
# ----------------------------------------------------------------------------
|
|
733
|
+
- id: generic.prompt.security.codeblock-obfuscation
|
|
734
|
+
languages: [generic]
|
|
735
|
+
severity: ERROR
|
|
736
|
+
message: "Attack instructions hidden inside code block. Malicious directives may be obfuscated within fenced code."
|
|
737
|
+
patterns:
|
|
738
|
+
- "```[\\s\\S]{0,20}(?i)(ignore|override|disregard)\\s+(all\\s+)?(previous|system|safety)"
|
|
739
|
+
- "```[\\s\\S]{0,20}(?i)SYSTEM\\s*:.*override"
|
|
740
|
+
- "```[\\s\\S]{0,50}(?i)(you\\s+are\\s+now|new\\s+instructions)"
|
|
741
|
+
- "```[\\s\\S]{0,20}(?i)(rm\\s+-rf|curl.*\\|.*sh|wget.*\\|.*bash)"
|
|
742
|
+
- "```[\\s\\S]{0,20}(?i)(eval|exec)\\s*\\("
|
|
743
|
+
- "```[\\s\\S]{0,50}(?i)send.{0,30}(secret|password|key|token|credential)"
|
|
744
|
+
metadata:
|
|
745
|
+
cwe: "CWE-77"
|
|
746
|
+
owasp: "LLM01 - Prompt Injection"
|
|
747
|
+
confidence: HIGH
|
|
748
|
+
category: "prompt-injection-content"
|
|
749
|
+
risk_score: "80"
|
|
750
|
+
action: BLOCK
|
|
751
|
+
|
|
752
|
+
# ----------------------------------------------------------------------------
|
|
753
|
+
# Natural Language Exfiltration Attacks
|
|
754
|
+
# ----------------------------------------------------------------------------
|
|
755
|
+
- id: generic.prompt.security.natural-language-exfiltration
|
|
756
|
+
languages: [generic]
|
|
757
|
+
severity: ERROR
|
|
758
|
+
message: "Data exfiltration attempt: Instructions to send sensitive data to external destination."
|
|
759
|
+
patterns:
|
|
760
|
+
- "(?i)send\\s+.{0,40}(secret|password|key|token|credential|env).{0,40}to\\s+\\S+"
|
|
761
|
+
- "(?i)read\\s+/etc/(passwd|shadow|hosts).{0,40}send"
|
|
762
|
+
- "(?i)(env|environment)\\s+(var|variable)s?.{0,30}send\\s+to"
|
|
763
|
+
- "(?i)curl\\s+.{0,30}(steal|exfil|attacker|evil)"
|
|
764
|
+
- "(?i)(cat|read|dump)\\s+.{0,20}\\.(ssh|env|credentials).{0,30}(curl|wget|send|post)"
|
|
765
|
+
metadata:
|
|
766
|
+
cwe: "CWE-200"
|
|
767
|
+
owasp: "LLM01 - Prompt Injection"
|
|
768
|
+
confidence: HIGH
|
|
769
|
+
category: "prompt-injection-output"
|
|
770
|
+
risk_score: "95"
|
|
771
|
+
action: BLOCK
|
package/server.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.sinewaveai/agent-security-scanner-mcp",
|
|
4
|
+
"description": "MCP security scanner with prompt injection firewall, package hallucination detection, and auto-fix.",
|
|
5
|
+
"version": "2.0.2",
|
|
6
|
+
"transport": "stdio",
|
|
7
|
+
"registry": "npm"
|
|
8
|
+
}
|