mcpsec 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/LICENSE +21 -0
- package/README.md +75 -0
- package/package.json +48 -0
- package/src/cli/index.ts +158 -0
- package/src/lib/injection-patterns.ts +283 -0
- package/src/lib/mcp-client.ts +384 -0
- package/src/lib/types.ts +90 -0
- package/src/lib/url-validator.ts +130 -0
- package/src/scanner/config-scanner.ts +262 -0
- package/src/scanner/credential-scanner.ts +200 -0
- package/src/scanner/live-scanner.ts +375 -0
- package/src/scanner/report.ts +248 -0
- package/src/scanner/tool-scanner.ts +142 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rob Taylor
|
|
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
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Sentinel MCP
|
|
2
|
+
|
|
3
|
+
**Security scanner for Model Context Protocol (MCP) servers.**
|
|
4
|
+
|
|
5
|
+
Sentinel scans your MCP configurations for security vulnerabilities including hardcoded credentials, prompt injection, tool poisoning, SSRF, and insecure transport.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Scan all detected MCP configurations
|
|
11
|
+
bun run src/cli/index.ts scan
|
|
12
|
+
|
|
13
|
+
# JSON output for CI/CD
|
|
14
|
+
bun run src/cli/index.ts scan --json
|
|
15
|
+
|
|
16
|
+
# Scan a specific config file
|
|
17
|
+
bun run src/cli/index.ts scan --path ~/.cursor/mcp.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## What It Scans
|
|
21
|
+
|
|
22
|
+
| Check | Category | Severity |
|
|
23
|
+
|-------|----------|----------|
|
|
24
|
+
| Hardcoded API keys (Anthropic, OpenAI, GitHub, AWS, etc.) | Credential Exposure | Critical |
|
|
25
|
+
| Prompt injection in tool descriptions | Tool Poisoning | Critical |
|
|
26
|
+
| Tool shadowing / hidden instructions | Tool Poisoning | Critical |
|
|
27
|
+
| SSRF via server URLs (localhost, private IPs, cloud metadata) | SSRF | Critical |
|
|
28
|
+
| Command injection in server arguments | Command Injection | Critical |
|
|
29
|
+
| Unencrypted HTTP transport | Insecure Transport | High |
|
|
30
|
+
| Privileged Docker containers | Excessive Permissions | Critical |
|
|
31
|
+
| Unverified npm packages via npx | Supply Chain | Medium |
|
|
32
|
+
| Embedded credentials in URLs | Credential Exposure | Critical |
|
|
33
|
+
| Sensitive environment variable values | Credential Exposure | High |
|
|
34
|
+
|
|
35
|
+
## Supported Clients
|
|
36
|
+
|
|
37
|
+
- Claude Desktop
|
|
38
|
+
- Claude Code
|
|
39
|
+
- Cursor
|
|
40
|
+
- VS Code
|
|
41
|
+
- Windsurf
|
|
42
|
+
- Cline
|
|
43
|
+
|
|
44
|
+
## Security Score
|
|
45
|
+
|
|
46
|
+
Sentinel calculates a 0-100 security score:
|
|
47
|
+
|
|
48
|
+
- **80-100**: PASS - No critical issues
|
|
49
|
+
- **50-79**: WARN - Issues found, review recommended
|
|
50
|
+
- **0-49**: FAIL - Critical vulnerabilities detected
|
|
51
|
+
|
|
52
|
+
## Exit Codes
|
|
53
|
+
|
|
54
|
+
| Code | Meaning |
|
|
55
|
+
|------|---------|
|
|
56
|
+
| 0 | No critical/high findings |
|
|
57
|
+
| 1 | High severity findings |
|
|
58
|
+
| 2 | Critical severity findings |
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Install dependencies
|
|
64
|
+
bun install
|
|
65
|
+
|
|
66
|
+
# Run tests
|
|
67
|
+
bun test
|
|
68
|
+
|
|
69
|
+
# Type check
|
|
70
|
+
bun run typecheck
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcpsec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Security scanner for MCP (Model Context Protocol) servers - detects tool poisoning, credential exposure, prompt injection, and SSRF",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Rob Taylor <robdtaylor@users.noreply.github.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mcpsec": "./src/cli/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"scan": "bun run src/cli/index.ts scan",
|
|
18
|
+
"test": "bun test",
|
|
19
|
+
"typecheck": "bun x tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/bun": "latest",
|
|
23
|
+
"typescript": "^5.7.0"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"security",
|
|
28
|
+
"scanner",
|
|
29
|
+
"model-context-protocol",
|
|
30
|
+
"ai-security",
|
|
31
|
+
"prompt-injection",
|
|
32
|
+
"ssrf",
|
|
33
|
+
"tool-poisoning",
|
|
34
|
+
"credential-scanner",
|
|
35
|
+
"mcpsec"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/robdtaylor/sentinel-mcp"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/robdtaylor/sentinel-mcp#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/robdtaylor/sentinel-mcp/issues"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"bun": ">=1.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel MCP - CLI Entry Point
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* sentinel-mcp scan Scan all detected MCP configurations
|
|
8
|
+
* sentinel-mcp scan --json Output results as JSON
|
|
9
|
+
* sentinel-mcp scan --path Scan a specific config file
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { discoverConfigs, scanConfigs } from '../scanner/config-scanner';
|
|
13
|
+
import { credentialScanner } from '../scanner/credential-scanner';
|
|
14
|
+
import { toolScanner } from '../scanner/tool-scanner';
|
|
15
|
+
import { liveScanner } from '../scanner/live-scanner';
|
|
16
|
+
import { generateReport, printReport, printReportJSON } from '../scanner/report';
|
|
17
|
+
import type { Finding, MCPConfigFile } from '../lib/types';
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// CLI
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
const HELP = `
|
|
24
|
+
mcpsec - Security Scanner for Model Context Protocol
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
mcpsec scan [options]
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--live Connect to running MCP servers and scan live
|
|
31
|
+
--json Output results as JSON
|
|
32
|
+
--path <file> Scan a specific config file
|
|
33
|
+
--no-color Disable colored output
|
|
34
|
+
--help, -h Show this help message
|
|
35
|
+
--version, -v Show version
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
mcpsec scan
|
|
39
|
+
mcpsec scan --live
|
|
40
|
+
mcpsec scan --json
|
|
41
|
+
mcpsec scan --path ~/.cursor/mcp.json
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
const command = args[0];
|
|
47
|
+
|
|
48
|
+
if (!command || command === '--help' || command === '-h') {
|
|
49
|
+
console.log(HELP);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (command === '--version' || command === '-v') {
|
|
54
|
+
console.log('mcpsec v0.1.0');
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (command !== 'scan') {
|
|
59
|
+
console.error(`Unknown command: ${command}`);
|
|
60
|
+
console.log(HELP);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Parse scan options
|
|
65
|
+
const jsonOutput = args.includes('--json');
|
|
66
|
+
const liveMode = args.includes('--live');
|
|
67
|
+
const pathIndex = args.indexOf('--path');
|
|
68
|
+
const specificPath = pathIndex !== -1 ? args[pathIndex + 1] : undefined;
|
|
69
|
+
|
|
70
|
+
if (args.includes('--no-color')) {
|
|
71
|
+
// Disable colors by overriding environment
|
|
72
|
+
process.env.NO_COLOR = '1';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Discover configs
|
|
76
|
+
let configs: MCPConfigFile[];
|
|
77
|
+
|
|
78
|
+
if (specificPath) {
|
|
79
|
+
// Scan a specific file
|
|
80
|
+
const { existsSync, readFileSync } = await import('fs');
|
|
81
|
+
if (!existsSync(specificPath)) {
|
|
82
|
+
console.error(`File not found: ${specificPath}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const content = readFileSync(specificPath, 'utf-8');
|
|
88
|
+
const raw = JSON.parse(content);
|
|
89
|
+
|
|
90
|
+
// Try to detect format
|
|
91
|
+
const servers = raw.mcpServers || raw.mcp?.servers || {};
|
|
92
|
+
if (Object.keys(servers).length === 0) {
|
|
93
|
+
console.error(`No MCP servers found in ${specificPath}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
configs = [{
|
|
98
|
+
path: specificPath,
|
|
99
|
+
client: 'Custom',
|
|
100
|
+
servers,
|
|
101
|
+
raw,
|
|
102
|
+
}];
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.error(`Failed to parse ${specificPath}: ${e}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
configs = discoverConfigs();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Run all scanners
|
|
112
|
+
const allFindings: Finding[] = [];
|
|
113
|
+
|
|
114
|
+
// Config-level checks
|
|
115
|
+
const configFindings = scanConfigs(configs);
|
|
116
|
+
allFindings.push(...configFindings);
|
|
117
|
+
|
|
118
|
+
// Credential scanner
|
|
119
|
+
const credFindings = await credentialScanner.scan(configs);
|
|
120
|
+
allFindings.push(...credFindings);
|
|
121
|
+
|
|
122
|
+
// Tool/injection scanner
|
|
123
|
+
const toolFindings = await toolScanner.scan(configs);
|
|
124
|
+
allFindings.push(...toolFindings);
|
|
125
|
+
|
|
126
|
+
// Live server scanner (connects to running servers)
|
|
127
|
+
if (liveMode) {
|
|
128
|
+
if (!jsonOutput) {
|
|
129
|
+
process.stderr.write('\n\x1b[1m🔴 Live Server Scan\x1b[0m\n');
|
|
130
|
+
}
|
|
131
|
+
const liveFindings = await liveScanner.scan(configs);
|
|
132
|
+
allFindings.push(...liveFindings);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Generate report
|
|
136
|
+
const report = generateReport(configs, allFindings);
|
|
137
|
+
|
|
138
|
+
// Output
|
|
139
|
+
if (jsonOutput) {
|
|
140
|
+
printReportJSON(report);
|
|
141
|
+
} else {
|
|
142
|
+
printReport(report);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Exit code based on findings
|
|
146
|
+
if (report.summary.critical > 0) {
|
|
147
|
+
process.exit(2); // Critical findings
|
|
148
|
+
} else if (report.summary.high > 0) {
|
|
149
|
+
process.exit(1); // High findings
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
main().catch((err) => {
|
|
156
|
+
console.error('Sentinel scan failed:', err);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
});
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel MCP - Prompt Injection & Tool Poisoning Detection Patterns
|
|
3
|
+
*
|
|
4
|
+
* Adapted from production security infrastructure.
|
|
5
|
+
* Covers OWASP MCP Top 10 attack vectors.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Severity } from './types';
|
|
9
|
+
|
|
10
|
+
export interface PatternCategory {
|
|
11
|
+
name: string;
|
|
12
|
+
severity: Severity;
|
|
13
|
+
patterns: RegExp[];
|
|
14
|
+
description: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Prompt Injection Patterns (in tool descriptions, prompts, resources)
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
export const INJECTION_PATTERNS: Record<string, PatternCategory> = {
|
|
22
|
+
instructionOverride: {
|
|
23
|
+
name: 'Instruction Override',
|
|
24
|
+
severity: 'critical',
|
|
25
|
+
description: 'Attempts to override system instructions via tool descriptions',
|
|
26
|
+
patterns: [
|
|
27
|
+
/ignore\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?|rules?)/i,
|
|
28
|
+
/disregard\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?)/i,
|
|
29
|
+
/forget\s+(all\s+)?(previous|prior|your)\s+(instructions?|training)/i,
|
|
30
|
+
/override\s+(all\s+)?(safety|security|restrictions?)/i,
|
|
31
|
+
/bypass\s+(all\s+)?(safety|security|filters?|restrictions?)/i,
|
|
32
|
+
/new\s+instruction[s]?:/i,
|
|
33
|
+
/system\s+prompt:/i,
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
roleManipulation: {
|
|
38
|
+
name: 'Role Manipulation',
|
|
39
|
+
severity: 'high',
|
|
40
|
+
description: 'Attempts to change the AI agent role or mode',
|
|
41
|
+
patterns: [
|
|
42
|
+
/you\s+are\s+now\s+(in\s+)?([a-z]+\s+)?mode/i,
|
|
43
|
+
/enter\s+([a-z]+\s+)?mode/i,
|
|
44
|
+
/switch\s+to\s+([a-z]+\s+)?mode/i,
|
|
45
|
+
/pretend\s+(you\s+are|to\s+be)\s+/i,
|
|
46
|
+
/roleplay\s+as\s+/i,
|
|
47
|
+
/act\s+as\s+if\s+you\s+(are|were)\s+/i,
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
systemPromptLeakage: {
|
|
52
|
+
name: 'System Prompt Leakage',
|
|
53
|
+
severity: 'high',
|
|
54
|
+
description: 'Attempts to extract system prompts or instructions',
|
|
55
|
+
patterns: [
|
|
56
|
+
/show\s+(me\s+)?your\s+(system\s+)?prompt/i,
|
|
57
|
+
/reveal\s+your\s+(system\s+)?instructions/i,
|
|
58
|
+
/what\s+(are|is)\s+your\s+(system\s+)?prompt/i,
|
|
59
|
+
/print\s+your\s+(initial\s+)?instructions/i,
|
|
60
|
+
/output\s+your\s+(hidden\s+)?prompt/i,
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
delimiterAttacks: {
|
|
65
|
+
name: 'Delimiter Injection',
|
|
66
|
+
severity: 'critical',
|
|
67
|
+
description: 'LLM-specific delimiter injection (ChatML, Llama, etc.)',
|
|
68
|
+
patterns: [
|
|
69
|
+
/\[INST\]/i,
|
|
70
|
+
/\[\/INST\]/i,
|
|
71
|
+
/<\|im_start\|>/i,
|
|
72
|
+
/<\|im_end\|>/i,
|
|
73
|
+
/<\|system\|>/i,
|
|
74
|
+
/<\|user\|>/i,
|
|
75
|
+
/<\|assistant\|>/i,
|
|
76
|
+
/<<SYS>>/i,
|
|
77
|
+
/<\/SYS>/i,
|
|
78
|
+
/\[\[SYSTEM\]\]/i,
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
jailbreakAttempts: {
|
|
83
|
+
name: 'Jailbreak Attempt',
|
|
84
|
+
severity: 'critical',
|
|
85
|
+
description: 'Known jailbreak techniques embedded in tool metadata',
|
|
86
|
+
patterns: [
|
|
87
|
+
/DAN\s*(mode)?/i,
|
|
88
|
+
/do\s+anything\s+now/i,
|
|
89
|
+
/maximum\s+mode/i,
|
|
90
|
+
/developer\s+mode/i,
|
|
91
|
+
/unrestricted\s+mode/i,
|
|
92
|
+
/no\s+restrictions?\s+mode/i,
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
authorityClaims: {
|
|
97
|
+
name: 'Authority Claims',
|
|
98
|
+
severity: 'high',
|
|
99
|
+
description: 'False authority claims to manipulate agent behavior',
|
|
100
|
+
patterns: [
|
|
101
|
+
/i\s+am\s+(an?\s+)?(admin|administrator|developer|anthropic)/i,
|
|
102
|
+
/admin\s+override/i,
|
|
103
|
+
/authorized\s+to\s+/i,
|
|
104
|
+
/special\s+permission/i,
|
|
105
|
+
/emergency\s+protocol/i,
|
|
106
|
+
/maintenance\s+mode/i,
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
encodedContent: {
|
|
111
|
+
name: 'Encoded/Obfuscated Content',
|
|
112
|
+
severity: 'medium',
|
|
113
|
+
description: 'Potentially obfuscated payloads in tool descriptions',
|
|
114
|
+
patterns: [
|
|
115
|
+
/base64:/i,
|
|
116
|
+
/data:text\/[a-z]+;base64,/i,
|
|
117
|
+
/\\x[0-9a-f]{2}/i,
|
|
118
|
+
/\\u[0-9a-f]{4}/i,
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// Tool Poisoning Patterns (MCP-specific)
|
|
125
|
+
// ============================================================================
|
|
126
|
+
|
|
127
|
+
export const TOOL_POISONING_PATTERNS: Record<string, PatternCategory> = {
|
|
128
|
+
hiddenInstructions: {
|
|
129
|
+
name: 'Hidden Instructions in Tool Description',
|
|
130
|
+
severity: 'critical',
|
|
131
|
+
description: 'Tool descriptions containing hidden instructions for the AI agent',
|
|
132
|
+
patterns: [
|
|
133
|
+
/\b(before|when|after)\s+(calling|using|executing)\s+this\s+tool/i,
|
|
134
|
+
/\bfirst\s+(read|access|send|exfiltrate|copy)\b/i,
|
|
135
|
+
/\bdo\s+not\s+(tell|inform|alert|warn)\s+(the\s+)?user\b/i,
|
|
136
|
+
/\bsilently\s+(send|post|upload|transmit)\b/i,
|
|
137
|
+
/\bwithout\s+(the\s+)?user('s)?\s+(knowledge|consent|awareness)\b/i,
|
|
138
|
+
/\bhidden\s+(parameter|field|action)\b/i,
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
toolShadowing: {
|
|
143
|
+
name: 'Tool Shadowing',
|
|
144
|
+
severity: 'critical',
|
|
145
|
+
description: 'Tool attempting to shadow/override another tool\'s behavior',
|
|
146
|
+
patterns: [
|
|
147
|
+
/\bthis\s+tool\s+(replaces|overrides|supersedes)\b/i,
|
|
148
|
+
/\buse\s+this\s+(instead\s+of|rather\s+than)\b/i,
|
|
149
|
+
/\bdo\s+not\s+use\s+(the\s+)?(original|default|other)\b/i,
|
|
150
|
+
/\b(redirect|forward|proxy)\s+(all\s+)?(calls?|requests?)\s+to\b/i,
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
dataExfiltration: {
|
|
155
|
+
name: 'Data Exfiltration via Tool',
|
|
156
|
+
severity: 'critical',
|
|
157
|
+
description: 'Tool descriptions instructing data exfiltration',
|
|
158
|
+
patterns: [
|
|
159
|
+
/\b(send|post|upload|transmit|exfiltrate)\s+(to|data|files?|credentials?)\b/i,
|
|
160
|
+
/\binclude\s+(all\s+)?(conversation|chat|history|context)\b/i,
|
|
161
|
+
/\b(append|add|include)\s+.*\b(api[_\s]?key|token|password|secret)\b/i,
|
|
162
|
+
/\bphone\s+home\b/i,
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
rugPull: {
|
|
167
|
+
name: 'Rug Pull / Dynamic Behavior Change',
|
|
168
|
+
severity: 'high',
|
|
169
|
+
description: 'Indicators of tools that may change behavior after trust is established',
|
|
170
|
+
patterns: [
|
|
171
|
+
/\b(after|once)\s+\d+\s+(calls?|uses?|invocations?)\b/i,
|
|
172
|
+
/\b(initially|at\s+first)\s+.*\b(then|later|afterwards)\b/i,
|
|
173
|
+
/\bversion\s*[><=]+\s*\d/i,
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// Command Injection Patterns (in tool arguments/parameters)
|
|
180
|
+
// ============================================================================
|
|
181
|
+
|
|
182
|
+
export const COMMAND_INJECTION_PATTERNS: Record<string, PatternCategory> = {
|
|
183
|
+
shellInjection: {
|
|
184
|
+
name: 'Shell Command Injection',
|
|
185
|
+
severity: 'critical',
|
|
186
|
+
description: 'Shell metacharacters or command injection in tool parameters',
|
|
187
|
+
patterns: [
|
|
188
|
+
/;\s*(rm|curl|wget|nc|bash|sh|python|perl|ruby)\b/i,
|
|
189
|
+
/\|\s*(bash|sh|nc|curl)\b/i,
|
|
190
|
+
/`[^`]*`/,
|
|
191
|
+
/\$\([^)]*\)/,
|
|
192
|
+
/&&\s*(rm|curl|wget|nc|bash|sh)\b/i,
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
pathTraversal: {
|
|
197
|
+
name: 'Path Traversal',
|
|
198
|
+
severity: 'high',
|
|
199
|
+
description: 'Directory traversal attempts in file paths',
|
|
200
|
+
patterns: [
|
|
201
|
+
/\.\.\//,
|
|
202
|
+
/\.\.%2[fF]/,
|
|
203
|
+
/\.\.\\(?!n|t|r)/,
|
|
204
|
+
/~\/\.(ssh|gnupg|aws|config|claude)/i,
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
reverseShell: {
|
|
209
|
+
name: 'Reverse Shell',
|
|
210
|
+
severity: 'critical',
|
|
211
|
+
description: 'Reverse shell patterns in command arguments',
|
|
212
|
+
patterns: [
|
|
213
|
+
/bash\s+-i\s+>&\s*\/dev\/tcp/i,
|
|
214
|
+
/nc\s+(-e|--exec)\s+\/bin\/(ba)?sh/i,
|
|
215
|
+
/python.*socket.*connect/i,
|
|
216
|
+
/\|\s*\/bin\/(ba)?sh/i,
|
|
217
|
+
/socat.*exec/i,
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// Analysis Functions
|
|
224
|
+
// ============================================================================
|
|
225
|
+
|
|
226
|
+
export interface InjectionAnalysis {
|
|
227
|
+
detected: boolean;
|
|
228
|
+
risk: 'none' | 'low' | 'medium' | 'high' | 'critical';
|
|
229
|
+
matches: Array<{
|
|
230
|
+
category: string;
|
|
231
|
+
name: string;
|
|
232
|
+
severity: Severity;
|
|
233
|
+
pattern: string;
|
|
234
|
+
}>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Analyze text content for injection patterns across all categories
|
|
239
|
+
*/
|
|
240
|
+
export function analyzeForInjection(
|
|
241
|
+
content: string,
|
|
242
|
+
patternSets: Record<string, PatternCategory>[] = [
|
|
243
|
+
INJECTION_PATTERNS,
|
|
244
|
+
TOOL_POISONING_PATTERNS,
|
|
245
|
+
COMMAND_INJECTION_PATTERNS,
|
|
246
|
+
]
|
|
247
|
+
): InjectionAnalysis {
|
|
248
|
+
const matches: InjectionAnalysis['matches'] = [];
|
|
249
|
+
|
|
250
|
+
for (const patternSet of patternSets) {
|
|
251
|
+
for (const [categoryKey, category] of Object.entries(patternSet)) {
|
|
252
|
+
for (const pattern of category.patterns) {
|
|
253
|
+
if (pattern.test(content)) {
|
|
254
|
+
matches.push({
|
|
255
|
+
category: categoryKey,
|
|
256
|
+
name: category.name,
|
|
257
|
+
severity: category.severity,
|
|
258
|
+
pattern: pattern.source,
|
|
259
|
+
});
|
|
260
|
+
break; // One match per category is enough
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Determine overall risk
|
|
267
|
+
let risk: InjectionAnalysis['risk'] = 'none';
|
|
268
|
+
if (matches.some((m) => m.severity === 'critical')) {
|
|
269
|
+
risk = 'critical';
|
|
270
|
+
} else if (matches.some((m) => m.severity === 'high')) {
|
|
271
|
+
risk = 'high';
|
|
272
|
+
} else if (matches.some((m) => m.severity === 'medium')) {
|
|
273
|
+
risk = 'medium';
|
|
274
|
+
} else if (matches.length > 0) {
|
|
275
|
+
risk = 'low';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
detected: matches.length > 0,
|
|
280
|
+
risk,
|
|
281
|
+
matches,
|
|
282
|
+
};
|
|
283
|
+
}
|