security-mcp 1.0.4 → 1.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 +77 -21
- package/defaults/checklists/ai.json +25 -0
- package/defaults/checklists/api.json +27 -0
- package/defaults/checklists/infra.json +27 -0
- package/defaults/checklists/mobile.json +25 -0
- package/defaults/checklists/payments.json +25 -0
- package/defaults/checklists/web.json +30 -0
- package/defaults/control-catalog.json +549 -0
- package/defaults/evidence-map.json +194 -0
- package/defaults/security-exceptions.json +4 -0
- package/defaults/security-policy.json +41 -2
- package/defaults/security-tools.json +41 -0
- package/dist/ci/pr-gate.js +2 -3
- package/dist/cli/index.js +63 -23
- package/dist/cli/install.js +47 -15
- package/dist/cli/onboarding.js +590 -0
- package/dist/cli/update.js +124 -0
- package/dist/gate/baseline.js +115 -0
- package/dist/gate/catalog.js +55 -0
- package/dist/gate/checks/ai-redteam.js +374 -0
- package/dist/gate/checks/ai.js +45 -14
- package/dist/gate/checks/api.js +93 -0
- package/dist/gate/checks/crypto.js +153 -0
- package/dist/gate/checks/database.js +144 -0
- package/dist/gate/checks/dependencies.js +130 -0
- package/dist/gate/checks/dlp.js +153 -0
- package/dist/gate/checks/graphql.js +122 -0
- package/dist/gate/checks/infra.js +126 -12
- package/dist/gate/checks/k8s.js +190 -0
- package/dist/gate/checks/playbook.js +160 -0
- package/dist/gate/checks/runtime.js +263 -0
- package/dist/gate/checks/sbom.js +199 -0
- package/dist/gate/checks/scanners.js +450 -0
- package/dist/gate/checks/secrets.js +119 -27
- package/dist/gate/diff.js +2 -2
- package/dist/gate/evidence.js +116 -0
- package/dist/gate/exceptions.js +85 -0
- package/dist/gate/policy.js +189 -17
- package/dist/gate/threat-intel.js +157 -0
- package/dist/mcp/server.js +938 -9
- package/dist/repo/fs.js +10 -5
- package/dist/repo/search.js +13 -1
- package/dist/review/store.js +208 -0
- package/dist/tests/run.js +103 -0
- package/package.json +13 -3
- package/prompts/SECURITY_PROMPT.md +455 -1
- package/skills/senior-security-engineer/SKILL.md +81 -4
|
@@ -122,5 +122,199 @@
|
|
|
122
122
|
"src/**/tool-router*.ts",
|
|
123
123
|
"src/**/tool*.ts",
|
|
124
124
|
"src/**/agent*.ts"
|
|
125
|
+
],
|
|
126
|
+
"mfa_enforced": [
|
|
127
|
+
"src/**/mfa*.ts",
|
|
128
|
+
"src/**/mfa*.js",
|
|
129
|
+
"src/**/totp*.ts",
|
|
130
|
+
"src/**/webauthn*.ts",
|
|
131
|
+
"src/**/passkey*.ts",
|
|
132
|
+
"src/**/two-factor*.ts",
|
|
133
|
+
"src/**/2fa*.ts",
|
|
134
|
+
"infra/**",
|
|
135
|
+
"terraform/**",
|
|
136
|
+
"k8s/**"
|
|
137
|
+
],
|
|
138
|
+
"audit_logging_configured": [
|
|
139
|
+
"src/**/audit*.ts",
|
|
140
|
+
"src/**/audit*.js",
|
|
141
|
+
"src/**/logger*.ts",
|
|
142
|
+
"src/**/logging*.ts",
|
|
143
|
+
"src/**/log*.ts",
|
|
144
|
+
"lib/**/audit*.ts",
|
|
145
|
+
"infra/**",
|
|
146
|
+
"terraform/**",
|
|
147
|
+
"k8s/**",
|
|
148
|
+
"helm/**"
|
|
149
|
+
],
|
|
150
|
+
"log_retention_policy": [
|
|
151
|
+
"infra/**",
|
|
152
|
+
"terraform/**",
|
|
153
|
+
"k8s/**",
|
|
154
|
+
"helm/**",
|
|
155
|
+
"docs/**",
|
|
156
|
+
"security/**",
|
|
157
|
+
".mcp/**"
|
|
158
|
+
],
|
|
159
|
+
"alerting_configured": [
|
|
160
|
+
"infra/**",
|
|
161
|
+
"terraform/**",
|
|
162
|
+
"k8s/**",
|
|
163
|
+
"helm/**",
|
|
164
|
+
"src/**/alert*.ts",
|
|
165
|
+
"src/**/alarm*.ts",
|
|
166
|
+
"src/**/monitor*.ts",
|
|
167
|
+
"src/**/pagerduty*.ts",
|
|
168
|
+
"src/**/opsgenie*.ts"
|
|
169
|
+
],
|
|
170
|
+
"email_filtering": [
|
|
171
|
+
"infra/**",
|
|
172
|
+
"terraform/**",
|
|
173
|
+
"docs/**",
|
|
174
|
+
"security/**"
|
|
175
|
+
],
|
|
176
|
+
"public_surface_hardened": [
|
|
177
|
+
"infra/**",
|
|
178
|
+
"terraform/**",
|
|
179
|
+
"k8s/**",
|
|
180
|
+
"helm/**",
|
|
181
|
+
"src/**/waf*.ts",
|
|
182
|
+
"src/**/ddos*.ts",
|
|
183
|
+
"nginx/**",
|
|
184
|
+
"caddy/**"
|
|
185
|
+
],
|
|
186
|
+
"rate_limiting_present": [
|
|
187
|
+
"src/**/rate-limit*.ts",
|
|
188
|
+
"src/**/rateLimit*.ts",
|
|
189
|
+
"src/**/throttle*.ts",
|
|
190
|
+
"src/**/middleware*.ts",
|
|
191
|
+
"middleware.ts",
|
|
192
|
+
"middleware.js",
|
|
193
|
+
"app/api/**",
|
|
194
|
+
"src/api/**"
|
|
195
|
+
],
|
|
196
|
+
"egress_controls": [
|
|
197
|
+
"infra/**",
|
|
198
|
+
"terraform/**",
|
|
199
|
+
"k8s/**",
|
|
200
|
+
"helm/**",
|
|
201
|
+
"src/**/egress*.ts",
|
|
202
|
+
"src/**/firewall*.ts",
|
|
203
|
+
"src/**/network*.ts"
|
|
204
|
+
],
|
|
205
|
+
"dlp_configured": [
|
|
206
|
+
"src/**/dlp*.ts",
|
|
207
|
+
"src/**/dlp*.js",
|
|
208
|
+
"src/**/pii*.ts",
|
|
209
|
+
"src/**/redact*.ts",
|
|
210
|
+
"src/**/sanitize*.ts",
|
|
211
|
+
"infra/**",
|
|
212
|
+
"terraform/**"
|
|
213
|
+
],
|
|
214
|
+
"artifact_signing": [
|
|
215
|
+
".github/workflows/**",
|
|
216
|
+
".gitlab-ci.yml",
|
|
217
|
+
"cloudbuild.yaml",
|
|
218
|
+
"Makefile",
|
|
219
|
+
"scripts/**",
|
|
220
|
+
"infra/**",
|
|
221
|
+
"cosign.pub",
|
|
222
|
+
"*.pub"
|
|
223
|
+
],
|
|
224
|
+
"hermetic_build_configured": [
|
|
225
|
+
".github/workflows/**",
|
|
226
|
+
".gitlab-ci.yml",
|
|
227
|
+
"cloudbuild.yaml",
|
|
228
|
+
"Makefile",
|
|
229
|
+
"scripts/**",
|
|
230
|
+
"BUILD",
|
|
231
|
+
"BUILD.bazel"
|
|
232
|
+
],
|
|
233
|
+
"ir_playbook_present": [
|
|
234
|
+
"security/**",
|
|
235
|
+
"docs/security/**",
|
|
236
|
+
"runbooks/**",
|
|
237
|
+
"playbooks/**",
|
|
238
|
+
".mcp/**"
|
|
239
|
+
],
|
|
240
|
+
"ir_playbook_tested": [
|
|
241
|
+
"security/**",
|
|
242
|
+
"docs/security/**",
|
|
243
|
+
"runbooks/**",
|
|
244
|
+
"playbooks/**"
|
|
245
|
+
],
|
|
246
|
+
"network_segmentation": [
|
|
247
|
+
"infra/**",
|
|
248
|
+
"terraform/**",
|
|
249
|
+
"k8s/**",
|
|
250
|
+
"helm/**"
|
|
251
|
+
],
|
|
252
|
+
"zero_trust_network": [
|
|
253
|
+
"infra/**",
|
|
254
|
+
"terraform/**",
|
|
255
|
+
"k8s/**",
|
|
256
|
+
"helm/**",
|
|
257
|
+
"src/**/mtls*.ts",
|
|
258
|
+
"src/**/spiffe*.ts",
|
|
259
|
+
"src/**/zero-trust*.ts"
|
|
260
|
+
],
|
|
261
|
+
"backup_encryption": [
|
|
262
|
+
"infra/**",
|
|
263
|
+
"terraform/**",
|
|
264
|
+
"k8s/**"
|
|
265
|
+
],
|
|
266
|
+
"backup_tested": [
|
|
267
|
+
"docs/**",
|
|
268
|
+
"security/**",
|
|
269
|
+
"runbooks/**"
|
|
270
|
+
],
|
|
271
|
+
"dns_monitoring": [
|
|
272
|
+
"infra/**",
|
|
273
|
+
"terraform/**"
|
|
274
|
+
],
|
|
275
|
+
"pan_encryption_configured": [
|
|
276
|
+
"infra/**",
|
|
277
|
+
"terraform/**",
|
|
278
|
+
"src/**/payment*.ts",
|
|
279
|
+
"src/**/card*.ts",
|
|
280
|
+
"src/**/stripe*.ts",
|
|
281
|
+
"src/**/braintree*.ts"
|
|
282
|
+
],
|
|
283
|
+
"no_plaintext_pan": [
|
|
284
|
+
"**/*.ts",
|
|
285
|
+
"**/*.js",
|
|
286
|
+
"**/*.py",
|
|
287
|
+
"**/*.go",
|
|
288
|
+
"**/*.java"
|
|
289
|
+
],
|
|
290
|
+
"input_validation_schema": [
|
|
291
|
+
"src/**",
|
|
292
|
+
"app/**",
|
|
293
|
+
"lib/**",
|
|
294
|
+
"server/**"
|
|
295
|
+
],
|
|
296
|
+
"output_encoding_present": [
|
|
297
|
+
"src/**",
|
|
298
|
+
"app/**",
|
|
299
|
+
"lib/**"
|
|
300
|
+
],
|
|
301
|
+
"service_account_per_workload": [
|
|
302
|
+
"infra/**",
|
|
303
|
+
"terraform/**",
|
|
304
|
+
"k8s/**",
|
|
305
|
+
"helm/**"
|
|
306
|
+
],
|
|
307
|
+
"human_in_loop_for_actions": [
|
|
308
|
+
"ai/**",
|
|
309
|
+
"src/**/agent*.ts",
|
|
310
|
+
"src/**/tool*.ts",
|
|
311
|
+
"src/**/confirm*.ts",
|
|
312
|
+
"src/**/approval*.ts"
|
|
313
|
+
],
|
|
314
|
+
"input_length_limits": [
|
|
315
|
+
"src/**",
|
|
316
|
+
"app/**",
|
|
317
|
+
"middleware.ts",
|
|
318
|
+
"middleware.js"
|
|
125
319
|
]
|
|
126
320
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "security-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Default security gate policy for security-mcp. Copy to .mcp/policies/security-policy.json and customize for your project.",
|
|
5
5
|
"required_checks": {
|
|
6
6
|
"secrets_scan": { "severity_block": ["HIGH", "CRITICAL"] },
|
|
@@ -8,6 +8,44 @@
|
|
|
8
8
|
"sast": { "severity_block": ["CRITICAL"] },
|
|
9
9
|
"iac_scan": { "severity_block": ["HIGH", "CRITICAL"] }
|
|
10
10
|
},
|
|
11
|
+
"environments": {
|
|
12
|
+
"dev": {
|
|
13
|
+
"severity_block": ["CRITICAL"],
|
|
14
|
+
"required_scanners": ["gitleaks"],
|
|
15
|
+
"required_checks": ["secrets_scan"],
|
|
16
|
+
"vulnerability_slas": {
|
|
17
|
+
"CRITICAL": "7d",
|
|
18
|
+
"HIGH": "30d",
|
|
19
|
+
"MEDIUM": "90d",
|
|
20
|
+
"LOW": "never"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"staging": {
|
|
24
|
+
"severity_block": ["HIGH", "CRITICAL"],
|
|
25
|
+
"required_scanners": ["gitleaks", "semgrep", "osv-scanner"],
|
|
26
|
+
"required_checks": ["secrets_scan", "dependency_scan", "sast"],
|
|
27
|
+
"vulnerability_slas": {
|
|
28
|
+
"CRITICAL": "24h",
|
|
29
|
+
"HIGH": "7d",
|
|
30
|
+
"MEDIUM": "30d",
|
|
31
|
+
"LOW": "90d",
|
|
32
|
+
"CISA_KEV": "24h"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"prod": {
|
|
36
|
+
"severity_block": ["MEDIUM", "HIGH", "CRITICAL"],
|
|
37
|
+
"required_scanners": ["gitleaks", "semgrep", "osv-scanner", "trivy", "checkov"],
|
|
38
|
+
"required_checks": ["secrets_scan", "dependency_scan", "sast", "iac_scan"],
|
|
39
|
+
"vulnerability_slas": {
|
|
40
|
+
"CRITICAL": "24h",
|
|
41
|
+
"HIGH": "7d",
|
|
42
|
+
"MEDIUM": "30d",
|
|
43
|
+
"LOW": "90d",
|
|
44
|
+
"CISA_KEV": "24h",
|
|
45
|
+
"HIGH_EPSS": "48h"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
11
49
|
"requirements": [
|
|
12
50
|
{
|
|
13
51
|
"id": "ZERO_TRUST",
|
|
@@ -84,7 +122,8 @@
|
|
|
84
122
|
"HIGH": "7d",
|
|
85
123
|
"MEDIUM": "30d",
|
|
86
124
|
"LOW": "90d",
|
|
87
|
-
"CISA_KEV": "24h"
|
|
125
|
+
"CISA_KEV": "24h",
|
|
126
|
+
"HIGH_EPSS": "48h"
|
|
88
127
|
},
|
|
89
128
|
"exceptions": {
|
|
90
129
|
"require_ticket": true,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"fail_closed": true,
|
|
4
|
+
"scanners": {
|
|
5
|
+
"gitleaks": {
|
|
6
|
+
"command": "gitleaks",
|
|
7
|
+
"args": ["version"],
|
|
8
|
+
"required_for": ["all"]
|
|
9
|
+
},
|
|
10
|
+
"semgrep": {
|
|
11
|
+
"command": "semgrep",
|
|
12
|
+
"args": ["--version"],
|
|
13
|
+
"required_for": ["web", "api", "ai"]
|
|
14
|
+
},
|
|
15
|
+
"osv-scanner": {
|
|
16
|
+
"command": "osv-scanner",
|
|
17
|
+
"args": ["--version"],
|
|
18
|
+
"required_for": ["web", "api", "ai"]
|
|
19
|
+
},
|
|
20
|
+
"checkov": {
|
|
21
|
+
"command": "checkov",
|
|
22
|
+
"args": ["--version"],
|
|
23
|
+
"required_for": ["infra"]
|
|
24
|
+
},
|
|
25
|
+
"trivy": {
|
|
26
|
+
"command": "trivy",
|
|
27
|
+
"args": ["--version"],
|
|
28
|
+
"required_for": ["infra"]
|
|
29
|
+
},
|
|
30
|
+
"conftest": {
|
|
31
|
+
"command": "conftest",
|
|
32
|
+
"args": ["--version"],
|
|
33
|
+
"required_for": ["infra"]
|
|
34
|
+
},
|
|
35
|
+
"zaproxy": {
|
|
36
|
+
"command": "zaproxy",
|
|
37
|
+
"args": ["-version"],
|
|
38
|
+
"required_for": ["web", "api"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/dist/ci/pr-gate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runPrGate } from "../gate/policy.js";
|
|
2
|
-
//
|
|
3
|
-
const SAFE_REF_RE = /^[a-zA-Z0-9_
|
|
2
|
+
// Allow safe git revision operators (~ and ^) plus ref/path characters. CWE-88.
|
|
3
|
+
const SAFE_REF_RE = /^[a-zA-Z0-9_./~^-]+$/;
|
|
4
4
|
function safeEnvRef(envVar, defaultValue) {
|
|
5
5
|
const val = process.env[envVar] || defaultValue;
|
|
6
6
|
if (!SAFE_REF_RE.test(val)) {
|
|
@@ -20,7 +20,6 @@ async function main() {
|
|
|
20
20
|
process.exit(2);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
// eslint-disable-next-line unicorn/prefer-top-level-await
|
|
24
23
|
main().catch((err) => {
|
|
25
24
|
console.error("security gate crashed:", err);
|
|
26
25
|
process.exit(3);
|
package/dist/cli/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { fileURLToPath } from "url";
|
|
|
14
14
|
import { dirname, resolve } from "path";
|
|
15
15
|
import { runInstall } from "./install.js";
|
|
16
16
|
import { main as runServer } from "../mcp/server.js";
|
|
17
|
+
import { notifyIfUpdateAvailable } from "./update.js";
|
|
17
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
const require = createRequire(import.meta.url);
|
|
19
20
|
function getVersion() {
|
|
@@ -26,25 +27,44 @@ function getVersion() {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
const VERSION = getVersion();
|
|
30
|
+
function getConfigSnippet(useGlobalBinary) {
|
|
31
|
+
return {
|
|
32
|
+
mcpServers: {
|
|
33
|
+
"security-mcp": useGlobalBinary
|
|
34
|
+
? {
|
|
35
|
+
command: "security-mcp",
|
|
36
|
+
args: ["serve"]
|
|
37
|
+
}
|
|
38
|
+
: {
|
|
39
|
+
command: "npx",
|
|
40
|
+
args: ["-y", "security-mcp@latest", "serve"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
29
45
|
const HELP = `
|
|
30
46
|
security-mcp v${VERSION}
|
|
31
47
|
|
|
32
48
|
AI security MCP server and gate for Claude Code, Cursor, Copilot, Codex, Replit, and any MCP-compatible editor.
|
|
33
49
|
|
|
34
50
|
USAGE
|
|
35
|
-
npx security-mcp <command> [options]
|
|
51
|
+
npx -y security-mcp@latest <command> [options]
|
|
36
52
|
|
|
37
53
|
COMMANDS
|
|
38
54
|
serve Start the MCP server over stdio (default for editors)
|
|
39
55
|
install Auto-detect installed editors and write MCP configs
|
|
56
|
+
install-global Install using the globally installed security-mcp binary
|
|
40
57
|
config Print MCP config JSON for manual editor setup
|
|
41
58
|
|
|
42
59
|
OPTIONS (install)
|
|
43
|
-
--claude-code
|
|
44
|
-
--cursor
|
|
45
|
-
--vscode
|
|
46
|
-
--global
|
|
47
|
-
--
|
|
60
|
+
--claude-code Write config for Claude Code only
|
|
61
|
+
--cursor Write config for Cursor only
|
|
62
|
+
--vscode Write config for VS Code only
|
|
63
|
+
--global Write to global editor config (default)
|
|
64
|
+
--use-global-binary Write configs that execute "security-mcp serve" instead of npx
|
|
65
|
+
--dry-run Print what would change without writing
|
|
66
|
+
--yes Skip interactive setup questions (install with defaults)
|
|
67
|
+
--non-interactive Same as --yes (for CI environments)
|
|
48
68
|
|
|
49
69
|
OPTIONS (general)
|
|
50
70
|
--version Print version
|
|
@@ -52,26 +72,31 @@ OPTIONS (general)
|
|
|
52
72
|
|
|
53
73
|
EXAMPLES
|
|
54
74
|
# Start MCP server (called automatically by editors):
|
|
55
|
-
npx -y security-mcp serve
|
|
75
|
+
npx -y security-mcp@latest serve
|
|
56
76
|
|
|
57
77
|
# Install into all detected editors:
|
|
58
|
-
npx security-mcp install
|
|
78
|
+
npx -y security-mcp@latest install
|
|
79
|
+
|
|
80
|
+
# Install globally once, then configure editors to use the global binary:
|
|
81
|
+
npm install -g security-mcp@latest
|
|
82
|
+
security-mcp install-global
|
|
59
83
|
|
|
60
84
|
# Install into Claude Code only:
|
|
61
|
-
npx security-mcp install --claude-code
|
|
85
|
+
npx -y security-mcp@latest install --claude-code
|
|
62
86
|
|
|
63
87
|
# Preview install without writing:
|
|
64
|
-
npx security-mcp install --dry-run
|
|
88
|
+
npx -y security-mcp@latest install --dry-run
|
|
65
89
|
|
|
66
90
|
# Print JSON config snippet:
|
|
67
|
-
npx security-mcp config
|
|
91
|
+
npx -y security-mcp@latest config
|
|
92
|
+
security-mcp config --use-global-binary
|
|
68
93
|
|
|
69
94
|
EDITOR CONFIG (add manually if install fails):
|
|
70
95
|
{
|
|
71
96
|
"mcpServers": {
|
|
72
97
|
"security-mcp": {
|
|
73
98
|
"command": "npx",
|
|
74
|
-
"args": ["-y", "security-mcp", "serve"]
|
|
99
|
+
"args": ["-y", "security-mcp@latest", "serve"]
|
|
75
100
|
}
|
|
76
101
|
}
|
|
77
102
|
}
|
|
@@ -83,16 +108,9 @@ EDITOR CONFIG (add manually if install fails):
|
|
|
83
108
|
MORE INFO
|
|
84
109
|
https://github.com/AbrahamOO/security-mcp
|
|
85
110
|
`;
|
|
86
|
-
const CONFIG_SNIPPET = {
|
|
87
|
-
mcpServers: {
|
|
88
|
-
"security-mcp": {
|
|
89
|
-
command: "npx",
|
|
90
|
-
args: ["-y", "security-mcp", "serve"]
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
111
|
async function main() {
|
|
95
112
|
const args = process.argv.slice(2);
|
|
113
|
+
const useGlobalBinary = args.includes("--use-global-binary");
|
|
96
114
|
if (args.includes("--version") || args.includes("-v")) {
|
|
97
115
|
process.stdout.write(`security-mcp v${VERSION}\n`);
|
|
98
116
|
process.exit(0);
|
|
@@ -102,6 +120,12 @@ async function main() {
|
|
|
102
120
|
process.exit(0);
|
|
103
121
|
}
|
|
104
122
|
const command = args[0] ?? "serve";
|
|
123
|
+
if (command === "serve") {
|
|
124
|
+
void notifyIfUpdateAvailable(VERSION);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
await notifyIfUpdateAvailable(VERSION);
|
|
128
|
+
}
|
|
105
129
|
switch (command) {
|
|
106
130
|
case "serve": {
|
|
107
131
|
// MCP stdio server - never write to stdout except via MCP protocol
|
|
@@ -109,19 +133,35 @@ async function main() {
|
|
|
109
133
|
break;
|
|
110
134
|
}
|
|
111
135
|
case "install": {
|
|
136
|
+
const noEditorFlag = !args.includes("--claude-code") && !args.includes("--cursor") && !args.includes("--vscode");
|
|
137
|
+
const options = {
|
|
138
|
+
claudeCode: args.includes("--claude-code"),
|
|
139
|
+
cursor: args.includes("--cursor"),
|
|
140
|
+
vscode: args.includes("--vscode"),
|
|
141
|
+
dryRun: args.includes("--dry-run"),
|
|
142
|
+
useGlobalBinary,
|
|
143
|
+
all: noEditorFlag,
|
|
144
|
+
interactive: !args.includes("--yes") && !args.includes("--non-interactive")
|
|
145
|
+
};
|
|
146
|
+
await runInstall(options);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "install-global": {
|
|
150
|
+
const noEditorFlag = !args.includes("--claude-code") && !args.includes("--cursor") && !args.includes("--vscode");
|
|
112
151
|
const options = {
|
|
113
152
|
claudeCode: args.includes("--claude-code"),
|
|
114
153
|
cursor: args.includes("--cursor"),
|
|
115
154
|
vscode: args.includes("--vscode"),
|
|
116
155
|
dryRun: args.includes("--dry-run"),
|
|
117
|
-
|
|
118
|
-
all:
|
|
156
|
+
useGlobalBinary: true,
|
|
157
|
+
all: noEditorFlag,
|
|
158
|
+
interactive: !args.includes("--yes") && !args.includes("--non-interactive")
|
|
119
159
|
};
|
|
120
160
|
await runInstall(options);
|
|
121
161
|
break;
|
|
122
162
|
}
|
|
123
163
|
case "config": {
|
|
124
|
-
process.stdout.write(JSON.stringify(
|
|
164
|
+
process.stdout.write(JSON.stringify(getConfigSnippet(useGlobalBinary), null, 2) + "\n");
|
|
125
165
|
process.stdout.write("\nAdd the above to your editor's MCP config file.\n");
|
|
126
166
|
process.stdout.write(" Claude Code: ~/.claude/settings.json\n");
|
|
127
167
|
process.stdout.write(" Cursor: ~/.cursor/mcp.json\n");
|
package/dist/cli/install.js
CHANGED
|
@@ -7,13 +7,9 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync } from
|
|
|
7
7
|
import { dirname, join, resolve } from "node:path";
|
|
8
8
|
import { homedir, platform } from "node:os";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { runOnboarding, installSecurityTools, commandExists, SECURITY_TOOLS } from "./onboarding.js";
|
|
10
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const PKG_ROOT = resolve(__dirname, "../..");
|
|
12
|
-
const MCP_ENTRY = {
|
|
13
|
-
type: "stdio",
|
|
14
|
-
command: "npx",
|
|
15
|
-
args: ["-y", "security-mcp", "serve"]
|
|
16
|
-
};
|
|
17
13
|
function resolveHome(p) {
|
|
18
14
|
return p.replace(/^~/, homedir());
|
|
19
15
|
}
|
|
@@ -28,7 +24,7 @@ function getVsCodeSettingsPath() {
|
|
|
28
24
|
return join(homedir(), ".config", "Code", "User", "settings.json");
|
|
29
25
|
}
|
|
30
26
|
function getEditorTargets(opts) {
|
|
31
|
-
const claudeCodePath = resolveHome("~/.claude.json");
|
|
27
|
+
const claudeCodePath = resolveHome("~/.claude/settings.json");
|
|
32
28
|
const cursorGlobalPath = resolveHome("~/.cursor/mcp.json");
|
|
33
29
|
const cursorLocalPath = ".cursor/mcp.json";
|
|
34
30
|
const vscodePath = getVsCodeSettingsPath();
|
|
@@ -79,10 +75,23 @@ function readJsonSafe(filePath) {
|
|
|
79
75
|
return {};
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
|
-
function
|
|
78
|
+
function getMcpEntry(useGlobalBinary) {
|
|
79
|
+
return useGlobalBinary
|
|
80
|
+
? {
|
|
81
|
+
type: "stdio",
|
|
82
|
+
command: "security-mcp",
|
|
83
|
+
args: ["serve"]
|
|
84
|
+
}
|
|
85
|
+
: {
|
|
86
|
+
type: "stdio",
|
|
87
|
+
command: "npx",
|
|
88
|
+
args: ["-y", "security-mcp@latest", "serve"]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function writeMcpServersJson(configPath, dryRun, useGlobalBinary) {
|
|
83
92
|
const existing = readJsonSafe(configPath);
|
|
84
93
|
const servers = existing["mcpServers"] ?? {};
|
|
85
|
-
servers["security-mcp"] =
|
|
94
|
+
servers["security-mcp"] = getMcpEntry(useGlobalBinary);
|
|
86
95
|
existing["mcpServers"] = servers;
|
|
87
96
|
const content = JSON.stringify(existing, null, 2) + "\n";
|
|
88
97
|
if (!dryRun) {
|
|
@@ -91,10 +100,10 @@ function writeMcpServersJson(configPath, dryRun) {
|
|
|
91
100
|
}
|
|
92
101
|
return configPath;
|
|
93
102
|
}
|
|
94
|
-
function writeVsCodeSettings(configPath, dryRun) {
|
|
103
|
+
function writeVsCodeSettings(configPath, dryRun, useGlobalBinary) {
|
|
95
104
|
const existing = readJsonSafe(configPath);
|
|
96
105
|
const servers = existing["mcp.servers"] ?? {};
|
|
97
|
-
servers["security-mcp"] =
|
|
106
|
+
servers["security-mcp"] = getMcpEntry(useGlobalBinary);
|
|
98
107
|
existing["mcp.servers"] = servers;
|
|
99
108
|
const content = JSON.stringify(existing, null, 2) + "\n";
|
|
100
109
|
if (!dryRun) {
|
|
@@ -108,7 +117,19 @@ function installPolicy(dryRun) {
|
|
|
108
117
|
const policyDest = join(process.cwd(), ".mcp", "policies", "security-policy.json");
|
|
109
118
|
const evidenceSrc = join(PKG_ROOT, "defaults", "evidence-map.json");
|
|
110
119
|
const evidenceDest = join(process.cwd(), ".mcp", "mappings", "evidence-map.json");
|
|
111
|
-
|
|
120
|
+
const catalogSrc = join(PKG_ROOT, "defaults", "control-catalog.json");
|
|
121
|
+
const catalogDest = join(process.cwd(), ".mcp", "catalog", "control-catalog.json");
|
|
122
|
+
const scannersSrc = join(PKG_ROOT, "defaults", "security-tools.json");
|
|
123
|
+
const scannersDest = join(process.cwd(), ".mcp", "scanners", "security-tools.json");
|
|
124
|
+
const exceptionsSrc = join(PKG_ROOT, "defaults", "security-exceptions.json");
|
|
125
|
+
const exceptionsDest = join(process.cwd(), ".mcp", "exceptions", "security-exceptions.json");
|
|
126
|
+
for (const { src, dest } of [
|
|
127
|
+
{ src: policySrc, dest: policyDest },
|
|
128
|
+
{ src: evidenceSrc, dest: evidenceDest },
|
|
129
|
+
{ src: catalogSrc, dest: catalogDest },
|
|
130
|
+
{ src: scannersSrc, dest: scannersDest },
|
|
131
|
+
{ src: exceptionsSrc, dest: exceptionsDest }
|
|
132
|
+
]) {
|
|
112
133
|
if (!existsSync(src)) {
|
|
113
134
|
process.stdout.write(` [skip] ${src} not found in package\n`);
|
|
114
135
|
continue;
|
|
@@ -139,13 +160,23 @@ function installSkill(dryRun) {
|
|
|
139
160
|
}
|
|
140
161
|
export async function runInstall(opts) {
|
|
141
162
|
const dryRun = opts.dryRun;
|
|
163
|
+
// ── Interactive onboarding (skipped when --yes or non-TTY) ──────────────
|
|
164
|
+
if (opts.interactive && !dryRun) {
|
|
165
|
+
const onboarding = await runOnboarding();
|
|
166
|
+
if (onboarding?.installTools) {
|
|
167
|
+
const toInstall = SECURITY_TOOLS.filter((t) => !commandExists(t.id));
|
|
168
|
+
process.stdout.write("\nInstalling security scanning tools...\n");
|
|
169
|
+
await installSecurityTools(toInstall);
|
|
170
|
+
process.stdout.write("\n");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
142
173
|
process.stdout.write(`\nsecurity-mcp installer${dryRun ? " (dry-run)" : ""}\n`);
|
|
143
174
|
process.stdout.write("=".repeat(40) + "\n\n");
|
|
144
175
|
const targets = getEditorTargets(opts);
|
|
145
176
|
if (targets.length === 0) {
|
|
146
177
|
process.stdout.write("No supported editors detected automatically.\n" +
|
|
147
178
|
"Run with --claude-code, --cursor, or --vscode to target a specific editor.\n" +
|
|
148
|
-
'Or add the config manually (run "npx security-mcp config" for the snippet).\n\n');
|
|
179
|
+
'Or add the config manually (run "npx -y security-mcp@latest config" for the snippet).\n\n');
|
|
149
180
|
return;
|
|
150
181
|
}
|
|
151
182
|
for (const target of targets) {
|
|
@@ -153,10 +184,10 @@ export async function runInstall(opts) {
|
|
|
153
184
|
try {
|
|
154
185
|
let written;
|
|
155
186
|
if (target.type === "vscode-settings") {
|
|
156
|
-
written = writeVsCodeSettings(target.configPath, dryRun);
|
|
187
|
+
written = writeVsCodeSettings(target.configPath, dryRun, opts.useGlobalBinary);
|
|
157
188
|
}
|
|
158
189
|
else {
|
|
159
|
-
written = writeMcpServersJson(target.configPath, dryRun);
|
|
190
|
+
written = writeMcpServersJson(target.configPath, dryRun, opts.useGlobalBinary);
|
|
160
191
|
}
|
|
161
192
|
process.stdout.write(` ${dryRun ? "[dry-run] would update" : "updated"}: ${written}\n`);
|
|
162
193
|
}
|
|
@@ -176,8 +207,9 @@ export async function runInstall(opts) {
|
|
|
176
207
|
process.stdout.write(dryRun
|
|
177
208
|
? "Dry-run complete. Re-run without --dry-run to apply.\n"
|
|
178
209
|
: "Done! Restart your editor to activate the security-mcp server.\n");
|
|
210
|
+
process.stdout.write(`Install mode: ${opts.useGlobalBinary ? "global binary (security-mcp serve)" : "npx (npx -y security-mcp@latest serve)"}\n`);
|
|
179
211
|
process.stdout.write("\nNext steps:\n");
|
|
180
212
|
process.stdout.write(" 1. Restart your editor.\n");
|
|
181
213
|
process.stdout.write(' 2. In Claude Code, type /senior-security-engineer to activate the security persona.\n');
|
|
182
|
-
process.stdout.write(' 3. Ask your AI: "Run security.
|
|
214
|
+
process.stdout.write(' 3. Ask your AI: "Run security.start_review with mode=recent_changes" to begin an auditable review.\n\n');
|
|
183
215
|
}
|