security-mcp 1.0.4 → 1.0.5

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 CHANGED
@@ -45,7 +45,7 @@ security-mcp actively hardens every surface of your software:
45
45
  ## Quick Start
46
46
 
47
47
  ```bash
48
- npx security-mcp install
48
+ npx -y security-mcp@latest install
49
49
  ```
50
50
 
51
51
  That's it. The tool auto-detects your editor and writes the MCP config. Restart your editor -- done.
@@ -53,15 +53,43 @@ That's it. The tool auto-detects your editor and writes the MCP config. Restart
53
53
  To target a specific editor:
54
54
 
55
55
  ```bash
56
- npx security-mcp install --claude-code
57
- npx security-mcp install --cursor
58
- npx security-mcp install --vscode
56
+ npx -y security-mcp@latest install --claude-code
57
+ npx -y security-mcp@latest install --cursor
58
+ npx -y security-mcp@latest install --vscode
59
59
  ```
60
60
 
61
61
  Preview without writing anything:
62
62
 
63
63
  ```bash
64
- npx security-mcp install --dry-run
64
+ npx -y security-mcp@latest install --dry-run
65
+ ```
66
+
67
+ ### Global Install
68
+
69
+ Install the package globally, then configure editors to call the global binary directly:
70
+
71
+ ```bash
72
+ npm install -g security-mcp@latest
73
+ security-mcp install-global
74
+ ```
75
+
76
+ Preview the global install flow without writing:
77
+
78
+ ```bash
79
+ security-mcp install-global --dry-run
80
+ ```
81
+
82
+ ### Update Behavior
83
+
84
+ - `npx -y security-mcp@latest ...` always runs the latest published npm version.
85
+ - Global installs (`npm install -g security-mcp`) do not auto-upgrade by themselves.
86
+ - The CLI now checks npm for new releases and prints an update prompt when a newer version is available.
87
+
88
+ Global update command:
89
+
90
+ ```bash
91
+ npm install -g security-mcp@latest
92
+ security-mcp install-global
65
93
  ```
66
94
 
67
95
  In **Claude Code**, activate the security engineer:
@@ -78,28 +106,36 @@ Your AI will now **find and fix** security issues instead of just mentioning the
78
106
 
79
107
  When you invoke `/senior-security-engineer` or call any security-mcp MCP tool, your AI shifts into the role of a Senior Security Engineer. It will:
80
108
 
81
- 1. **Scan your code** for vulnerabilities, misconfigurations, and security anti-patterns
82
- 2. **Fix what it finds** -- not just flag it; it rewrites the insecure code with the secure version
83
- 3. **Enforce policies** -- set up input validation, auth middleware, security headers, and rate limiting
84
- 4. **Block dangerous patterns** -- refuse to implement code that introduces known vulnerabilities
85
- 5. **Explain everything in plain English** -- no security jargon required
109
+ 1. **Ask scan scope first** -- folder-by-folder, file-by-file, or recent changes
110
+ 2. **Start a review run** -- carry a `runId` for ordered execution and attestation
111
+ 3. **Scan your code** for vulnerabilities, misconfigurations, and security anti-patterns
112
+ 4. **Fix what it finds** -- not just flag it; it rewrites the insecure code with the secure version
113
+ 5. **Enforce policies** -- set up input validation, auth middleware, security headers, and rate limiting
114
+ 6. **Block dangerous patterns** -- refuse to implement code that introduces known vulnerabilities
115
+ 7. **Produce an attestation** -- emit a confidence summary and integrity hash for the completed review
86
116
 
87
117
  ### MCP Tools (Your AI Uses These Automatically)
88
118
 
89
119
  | Tool | What It Does |
90
120
  | --- | --- |
121
+ | `security.start_review` | Starts a stateful review run and returns the `runId` used for ordered execution and attestation |
91
122
  | `security.get_system_prompt` | Loads the full security directive into your AI session -- activates the Senior Security Engineer mode |
92
123
  | `security.threat_model` | Generates a complete threat model for any feature before a single line of code is written |
93
124
  | `security.checklist` | Returns a hardened pre-ship checklist specific to your surface (web, API, mobile, AI, cloud) |
125
+ | `security.scan_strategy` | Forces scan mode selection (`folder_by_folder`, `file_by_file`, `recent_changes`) and builds an exhaustive review plan |
94
126
  | `security.generate_policy` | Writes a `security-policy.json` for your project that the gate enforces on every PR |
95
- | `security.run_pr_gate` | Scans your current code diff and **blocks merge** if it introduces CRITICAL or HIGH vulnerabilities |
127
+ | `security.terraform_hardening_blueprint` | Produces an advanced Terraform hardening baseline (network, IAM, data, logging, CI controls) |
128
+ | `security.generate_opa_rego` | Generates preventive OPA/Rego policies for Terraform plans, CI pipelines, and Kubernetes admission (requires explicit consent) |
129
+ | `security.self_heal_loop` | Proposes self-healing improvements, but requires explicit human approval before any change |
130
+ | `security.attest_review` | Writes an auditable review attestation with integrity hash and confidence summary |
131
+ | `security.run_pr_gate` | Scans recent changes, selected folders, or selected files and **blocks merge** on CRITICAL/HIGH vulnerabilities; requires `runId` in MCP usage |
96
132
  | `repo.read_file` | Reads files from your workspace for analysis |
97
133
  | `repo.search` | Searches your codebase for vulnerable patterns |
98
134
 
99
135
  ### Security Gate (Blocks Bad Code from Shipping)
100
136
 
101
137
  ```bash
102
- npx security-mcp ci:pr-gate
138
+ npx -y security-mcp@latest ci:pr-gate
103
139
  ```
104
140
 
105
141
  Add this to your CI pipeline. It scans every PR and **blocks the merge** if it finds:
@@ -158,14 +194,17 @@ When your AI has security-mcp active, it will **fix these automatically** -- not
158
194
 
159
195
  | Editor | Install Command | Config Location |
160
196
  | --- | --- | --- |
161
- | Claude Code | `npx security-mcp install --claude-code` | `~/.claude/settings.json` |
162
- | Cursor (global) | `npx security-mcp install --cursor` | `~/.cursor/mcp.json` |
163
- | Cursor (workspace) | `npx security-mcp install --cursor` | `.cursor/mcp.json` |
164
- | VS Code | `npx security-mcp install --vscode` | User `settings.json` |
197
+ | Claude Code | `npx -y security-mcp@latest install --claude-code` | `~/.claude/settings.json` |
198
+ | Claude Code (global binary) | `security-mcp install-global --claude-code` | `~/.claude/settings.json` |
199
+ | Cursor (global) | `npx -y security-mcp@latest install --cursor` | `~/.cursor/mcp.json` |
200
+ | Cursor (global binary) | `security-mcp install-global --cursor` | `~/.cursor/mcp.json` |
201
+ | Cursor (workspace) | `npx -y security-mcp@latest install --cursor` | `.cursor/mcp.json` |
202
+ | VS Code | `npx -y security-mcp@latest install --vscode` | User `settings.json` |
203
+ | VS Code (global binary) | `security-mcp install-global --vscode` | User `settings.json` |
165
204
  | GitHub Copilot | Manual config (see below) | `.vscode/settings.json` |
166
205
  | Codex | Manual config (see below) | Editor config |
167
206
  | Replit | Manual config (see below) | `.replit` config |
168
- | Any MCP-compatible | `npx security-mcp config` | Paste into editor config |
207
+ | Any MCP-compatible | `npx -y security-mcp@latest config` or `security-mcp config --use-global-binary` | Paste into editor config |
169
208
 
170
209
  ---
171
210
 
@@ -203,7 +242,20 @@ security-mcp applies all of them on your behalf:
203
242
  "mcpServers": {
204
243
  "security-mcp": {
205
244
  "command": "npx",
206
- "args": ["-y", "security-mcp", "serve"]
245
+ "args": ["-y", "security-mcp@latest", "serve"]
246
+ }
247
+ }
248
+ }
249
+ ```
250
+
251
+ ### Claude Code With Global Binary (`~/.claude/settings.json`)
252
+
253
+ ```json
254
+ {
255
+ "mcpServers": {
256
+ "security-mcp": {
257
+ "command": "security-mcp",
258
+ "args": ["serve"]
207
259
  }
208
260
  }
209
261
  }
@@ -216,7 +268,7 @@ security-mcp applies all of them on your behalf:
216
268
  "mcpServers": {
217
269
  "security-mcp": {
218
270
  "command": "npx",
219
- "args": ["-y", "security-mcp", "serve"]
271
+ "args": ["-y", "security-mcp@latest", "serve"]
220
272
  }
221
273
  }
222
274
  }
@@ -229,7 +281,7 @@ security-mcp applies all of them on your behalf:
229
281
  "mcp.servers": {
230
282
  "security-mcp": {
231
283
  "command": "npx",
232
- "args": ["-y", "security-mcp", "serve"]
284
+ "args": ["-y", "security-mcp@latest", "serve"]
233
285
  }
234
286
  }
235
287
  }
@@ -238,7 +290,8 @@ security-mcp applies all of them on your behalf:
238
290
  Print the config snippet for any editor:
239
291
 
240
292
  ```bash
241
- npx security-mcp config
293
+ npx -y security-mcp@latest config
294
+ security-mcp config --use-global-binary
242
295
  ```
243
296
 
244
297
  ---
@@ -250,6 +303,9 @@ Copy the default policy into your project:
250
303
  ```bash
251
304
  cp node_modules/security-mcp/defaults/security-policy.json .mcp/policies/security-policy.json
252
305
  cp node_modules/security-mcp/defaults/evidence-map.json .mcp/mappings/evidence-map.json
306
+ cp node_modules/security-mcp/defaults/control-catalog.json .mcp/catalog/control-catalog.json
307
+ cp node_modules/security-mcp/defaults/security-tools.json .mcp/scanners/security-tools.json
308
+ cp node_modules/security-mcp/defaults/security-exceptions.json .mcp/exceptions/security-exceptions.json
253
309
  ```
254
310
 
255
311
  Or generate one tailored to your project:
@@ -0,0 +1,157 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "controls": [
4
+ {
5
+ "id": "SCAN_MODE_SELECTED",
6
+ "description": "A review mode is selected before scanning begins.",
7
+ "automation": "workflow",
8
+ "surfaces": ["all"],
9
+ "frameworks": ["NIST 800-218", "OWASP SAMM"],
10
+ "required_steps": ["scan_strategy"]
11
+ },
12
+ {
13
+ "id": "THREAT_MODEL_COMPLETED",
14
+ "description": "Threat modeling is completed before security review signoff.",
15
+ "automation": "workflow",
16
+ "surfaces": ["web", "api", "mobile", "ai", "infra"],
17
+ "frameworks": ["STRIDE", "PASTA", "MITRE ATT&CK"],
18
+ "required_steps": ["threat_model"]
19
+ },
20
+ {
21
+ "id": "CHECKLIST_COMPLETED",
22
+ "description": "Surface-specific security checklist completed.",
23
+ "automation": "workflow",
24
+ "surfaces": ["all"],
25
+ "frameworks": ["OWASP ASVS", "NIST CSF 2.0"],
26
+ "required_steps": ["checklist"]
27
+ },
28
+ {
29
+ "id": "ZERO_TRUST",
30
+ "description": "All services enforce authentication and authorization with no implicit trust.",
31
+ "automation": "evidence",
32
+ "surfaces": ["web", "api", "infra"],
33
+ "frameworks": ["NIST 800-207", "OWASP ASVS"],
34
+ "evidence": ["deny_by_default_authz", "service_to_service_auth"]
35
+ },
36
+ {
37
+ "id": "SECRET_MANAGER_ONLY",
38
+ "description": "Secrets stored only in a dedicated secret manager.",
39
+ "automation": "evidence",
40
+ "surfaces": ["all"],
41
+ "frameworks": ["NIST 800-53", "SOC 2"],
42
+ "evidence": ["no_hardcoded_secrets", "secret_manager_refs"]
43
+ },
44
+ {
45
+ "id": "TLS_13",
46
+ "description": "TLS configuration is verified.",
47
+ "automation": "evidence",
48
+ "surfaces": ["web", "api", "infra"],
49
+ "frameworks": ["NIST 800-53", "PCI DSS 4.0"],
50
+ "evidence": ["tls_config_verified"]
51
+ },
52
+ {
53
+ "id": "CSP_NO_INLINE",
54
+ "description": "CSP is configured without unsafe inline execution.",
55
+ "automation": "evidence",
56
+ "surfaces": ["web"],
57
+ "frameworks": ["OWASP ASVS", "CWE-79"],
58
+ "evidence": ["security_headers_present"]
59
+ },
60
+ {
61
+ "id": "CSRF",
62
+ "description": "CSRF protections and tests are present.",
63
+ "automation": "evidence",
64
+ "surfaces": ["web", "api"],
65
+ "frameworks": ["OWASP Top 10", "OWASP ASVS"],
66
+ "evidence": ["csrf_protection_present", "csrf_tests_present"]
67
+ },
68
+ {
69
+ "id": "SSRF",
70
+ "description": "SSRF guards and tests are present.",
71
+ "automation": "evidence",
72
+ "surfaces": ["web", "api", "infra"],
73
+ "frameworks": ["OWASP Top 10", "CWE-918"],
74
+ "evidence": ["ssrf_guard_present", "ssrf_tests_present"]
75
+ },
76
+ {
77
+ "id": "MOBILE_MASVS",
78
+ "description": "Mobile release configuration meets MASVS baseline.",
79
+ "automation": "evidence",
80
+ "surfaces": ["mobile"],
81
+ "frameworks": ["OWASP MASVS"],
82
+ "evidence": ["ios_ats_strict", "android_nsc_strict", "release_not_debuggable"]
83
+ },
84
+ {
85
+ "id": "AI_BOUNDED_OUTPUTS",
86
+ "description": "AI outputs are schema-validated and tools are allowlisted.",
87
+ "automation": "evidence",
88
+ "surfaces": ["ai"],
89
+ "frameworks": ["OWASP LLM Top 10", "MITRE ATLAS"],
90
+ "evidence": ["json_schema_validation", "tool_allowlist_router"]
91
+ },
92
+ {
93
+ "id": "SECRETS_SCANNER_READY",
94
+ "description": "A dedicated secrets scanner is installed and runnable.",
95
+ "automation": "tooling",
96
+ "surfaces": ["all"],
97
+ "frameworks": ["NIST 800-218", "SLSA"],
98
+ "required_scanners": ["gitleaks"]
99
+ },
100
+ {
101
+ "id": "SAST_SCANNER_READY",
102
+ "description": "A static analysis scanner is installed and runnable.",
103
+ "automation": "tooling",
104
+ "surfaces": ["web", "api", "ai"],
105
+ "frameworks": ["OWASP Top 10", "OWASP SAMM"],
106
+ "required_scanners": ["semgrep"]
107
+ },
108
+ {
109
+ "id": "DEPENDENCY_SCANNER_READY",
110
+ "description": "A dependency scanner is installed and runnable.",
111
+ "automation": "tooling",
112
+ "surfaces": ["web", "api", "ai"],
113
+ "frameworks": ["SLSA", "NIST 800-218"],
114
+ "required_scanners": ["osv-scanner"]
115
+ },
116
+ {
117
+ "id": "IAC_SCANNER_READY",
118
+ "description": "Infrastructure scanners and policy-as-code tooling are installed and runnable.",
119
+ "automation": "tooling",
120
+ "surfaces": ["infra"],
121
+ "frameworks": ["CIS", "NIST 800-53"],
122
+ "required_scanners": ["checkov", "conftest"]
123
+ },
124
+ {
125
+ "id": "CONTAINER_SCANNER_READY",
126
+ "description": "A container scanner is installed and runnable.",
127
+ "automation": "tooling",
128
+ "surfaces": ["infra"],
129
+ "frameworks": ["NIST 800-190", "CIS"],
130
+ "required_scanners": ["trivy"]
131
+ },
132
+ {
133
+ "id": "DAST_SCANNER_READY",
134
+ "description": "A DAST scanner is installed and runnable.",
135
+ "automation": "tooling",
136
+ "surfaces": ["web", "api"],
137
+ "frameworks": ["OWASP Testing Guide"],
138
+ "required_scanners": ["zaproxy"]
139
+ },
140
+ {
141
+ "id": "HUMAN_APPROVED_REGO",
142
+ "description": "OPA/Rego generation only happens after explicit human approval.",
143
+ "automation": "approval",
144
+ "surfaces": ["infra"],
145
+ "frameworks": ["NIST 800-218"],
146
+ "required_steps": ["generate_opa_rego"]
147
+ },
148
+ {
149
+ "id": "HUMAN_APPROVED_SELF_HEALING",
150
+ "description": "Self-healing changes only happen after explicit human approval.",
151
+ "automation": "approval",
152
+ "surfaces": ["all"],
153
+ "frameworks": ["NIST 800-218", "SOC 2"],
154
+ "required_steps": ["self_heal_loop"]
155
+ }
156
+ ]
157
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "exceptions": []
4
+ }
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  import { runPrGate } from "../gate/policy.js";
2
- // Allowlist refs to the same safe character set enforced in diff.ts. CWE-88.
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,17 +27,33 @@ 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)
@@ -44,6 +61,7 @@ OPTIONS (install)
44
61
  --cursor Write config for Cursor only
45
62
  --vscode Write config for VS Code only
46
63
  --global Write to global editor config (default)
64
+ --use-global-binary Write configs that execute "security-mcp serve" instead of npx
47
65
  --dry-run Print what would change without writing
48
66
 
49
67
  OPTIONS (general)
@@ -52,26 +70,31 @@ OPTIONS (general)
52
70
 
53
71
  EXAMPLES
54
72
  # Start MCP server (called automatically by editors):
55
- npx -y security-mcp serve
73
+ npx -y security-mcp@latest serve
56
74
 
57
75
  # Install into all detected editors:
58
- npx security-mcp install
76
+ npx -y security-mcp@latest install
77
+
78
+ # Install globally once, then configure editors to use the global binary:
79
+ npm install -g security-mcp@latest
80
+ security-mcp install-global
59
81
 
60
82
  # Install into Claude Code only:
61
- npx security-mcp install --claude-code
83
+ npx -y security-mcp@latest install --claude-code
62
84
 
63
85
  # Preview install without writing:
64
- npx security-mcp install --dry-run
86
+ npx -y security-mcp@latest install --dry-run
65
87
 
66
88
  # Print JSON config snippet:
67
- npx security-mcp config
89
+ npx -y security-mcp@latest config
90
+ security-mcp config --use-global-binary
68
91
 
69
92
  EDITOR CONFIG (add manually if install fails):
70
93
  {
71
94
  "mcpServers": {
72
95
  "security-mcp": {
73
96
  "command": "npx",
74
- "args": ["-y", "security-mcp", "serve"]
97
+ "args": ["-y", "security-mcp@latest", "serve"]
75
98
  }
76
99
  }
77
100
  }
@@ -83,16 +106,9 @@ EDITOR CONFIG (add manually if install fails):
83
106
  MORE INFO
84
107
  https://github.com/AbrahamOO/security-mcp
85
108
  `;
86
- const CONFIG_SNIPPET = {
87
- mcpServers: {
88
- "security-mcp": {
89
- command: "npx",
90
- args: ["-y", "security-mcp", "serve"]
91
- }
92
- }
93
- };
94
109
  async function main() {
95
110
  const args = process.argv.slice(2);
111
+ const useGlobalBinary = args.includes("--use-global-binary");
96
112
  if (args.includes("--version") || args.includes("-v")) {
97
113
  process.stdout.write(`security-mcp v${VERSION}\n`);
98
114
  process.exit(0);
@@ -102,6 +118,12 @@ async function main() {
102
118
  process.exit(0);
103
119
  }
104
120
  const command = args[0] ?? "serve";
121
+ if (command === "serve") {
122
+ void notifyIfUpdateAvailable(VERSION);
123
+ }
124
+ else {
125
+ await notifyIfUpdateAvailable(VERSION);
126
+ }
105
127
  switch (command) {
106
128
  case "serve": {
107
129
  // MCP stdio server - never write to stdout except via MCP protocol
@@ -114,14 +136,27 @@ async function main() {
114
136
  cursor: args.includes("--cursor"),
115
137
  vscode: args.includes("--vscode"),
116
138
  dryRun: args.includes("--dry-run"),
139
+ useGlobalBinary,
117
140
  // If no editor flag specified, install to all detected
118
141
  all: !args.includes("--claude-code") && !args.includes("--cursor") && !args.includes("--vscode")
119
142
  };
120
143
  await runInstall(options);
121
144
  break;
122
145
  }
146
+ case "install-global": {
147
+ const options = {
148
+ claudeCode: args.includes("--claude-code"),
149
+ cursor: args.includes("--cursor"),
150
+ vscode: args.includes("--vscode"),
151
+ dryRun: args.includes("--dry-run"),
152
+ useGlobalBinary: true,
153
+ all: !args.includes("--claude-code") && !args.includes("--cursor") && !args.includes("--vscode")
154
+ };
155
+ await runInstall(options);
156
+ break;
157
+ }
123
158
  case "config": {
124
- process.stdout.write(JSON.stringify(CONFIG_SNIPPET, null, 2) + "\n");
159
+ process.stdout.write(JSON.stringify(getConfigSnippet(useGlobalBinary), null, 2) + "\n");
125
160
  process.stdout.write("\nAdd the above to your editor's MCP config file.\n");
126
161
  process.stdout.write(" Claude Code: ~/.claude/settings.json\n");
127
162
  process.stdout.write(" Cursor: ~/.cursor/mcp.json\n");
@@ -9,11 +9,6 @@ import { homedir, platform } from "node:os";
9
9
  import { fileURLToPath } from "node:url";
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
11
  const PKG_ROOT = resolve(__dirname, "../..");
12
- const MCP_ENTRY = {
13
- type: "stdio",
14
- command: "npx",
15
- args: ["-y", "security-mcp", "serve"]
16
- };
17
12
  function resolveHome(p) {
18
13
  return p.replace(/^~/, homedir());
19
14
  }
@@ -28,7 +23,7 @@ function getVsCodeSettingsPath() {
28
23
  return join(homedir(), ".config", "Code", "User", "settings.json");
29
24
  }
30
25
  function getEditorTargets(opts) {
31
- const claudeCodePath = resolveHome("~/.claude.json");
26
+ const claudeCodePath = resolveHome("~/.claude/settings.json");
32
27
  const cursorGlobalPath = resolveHome("~/.cursor/mcp.json");
33
28
  const cursorLocalPath = ".cursor/mcp.json";
34
29
  const vscodePath = getVsCodeSettingsPath();
@@ -79,10 +74,23 @@ function readJsonSafe(filePath) {
79
74
  return {};
80
75
  }
81
76
  }
82
- function writeMcpServersJson(configPath, dryRun) {
77
+ function getMcpEntry(useGlobalBinary) {
78
+ return useGlobalBinary
79
+ ? {
80
+ type: "stdio",
81
+ command: "security-mcp",
82
+ args: ["serve"]
83
+ }
84
+ : {
85
+ type: "stdio",
86
+ command: "npx",
87
+ args: ["-y", "security-mcp@latest", "serve"]
88
+ };
89
+ }
90
+ function writeMcpServersJson(configPath, dryRun, useGlobalBinary) {
83
91
  const existing = readJsonSafe(configPath);
84
92
  const servers = existing["mcpServers"] ?? {};
85
- servers["security-mcp"] = MCP_ENTRY;
93
+ servers["security-mcp"] = getMcpEntry(useGlobalBinary);
86
94
  existing["mcpServers"] = servers;
87
95
  const content = JSON.stringify(existing, null, 2) + "\n";
88
96
  if (!dryRun) {
@@ -91,10 +99,10 @@ function writeMcpServersJson(configPath, dryRun) {
91
99
  }
92
100
  return configPath;
93
101
  }
94
- function writeVsCodeSettings(configPath, dryRun) {
102
+ function writeVsCodeSettings(configPath, dryRun, useGlobalBinary) {
95
103
  const existing = readJsonSafe(configPath);
96
104
  const servers = existing["mcp.servers"] ?? {};
97
- servers["security-mcp"] = MCP_ENTRY;
105
+ servers["security-mcp"] = getMcpEntry(useGlobalBinary);
98
106
  existing["mcp.servers"] = servers;
99
107
  const content = JSON.stringify(existing, null, 2) + "\n";
100
108
  if (!dryRun) {
@@ -108,7 +116,19 @@ function installPolicy(dryRun) {
108
116
  const policyDest = join(process.cwd(), ".mcp", "policies", "security-policy.json");
109
117
  const evidenceSrc = join(PKG_ROOT, "defaults", "evidence-map.json");
110
118
  const evidenceDest = join(process.cwd(), ".mcp", "mappings", "evidence-map.json");
111
- for (const { src, dest } of [{ src: policySrc, dest: policyDest }, { src: evidenceSrc, dest: evidenceDest }]) {
119
+ const catalogSrc = join(PKG_ROOT, "defaults", "control-catalog.json");
120
+ const catalogDest = join(process.cwd(), ".mcp", "catalog", "control-catalog.json");
121
+ const scannersSrc = join(PKG_ROOT, "defaults", "security-tools.json");
122
+ const scannersDest = join(process.cwd(), ".mcp", "scanners", "security-tools.json");
123
+ const exceptionsSrc = join(PKG_ROOT, "defaults", "security-exceptions.json");
124
+ const exceptionsDest = join(process.cwd(), ".mcp", "exceptions", "security-exceptions.json");
125
+ for (const { src, dest } of [
126
+ { src: policySrc, dest: policyDest },
127
+ { src: evidenceSrc, dest: evidenceDest },
128
+ { src: catalogSrc, dest: catalogDest },
129
+ { src: scannersSrc, dest: scannersDest },
130
+ { src: exceptionsSrc, dest: exceptionsDest }
131
+ ]) {
112
132
  if (!existsSync(src)) {
113
133
  process.stdout.write(` [skip] ${src} not found in package\n`);
114
134
  continue;
@@ -145,7 +165,7 @@ export async function runInstall(opts) {
145
165
  if (targets.length === 0) {
146
166
  process.stdout.write("No supported editors detected automatically.\n" +
147
167
  "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');
168
+ 'Or add the config manually (run "npx -y security-mcp@latest config" for the snippet).\n\n');
149
169
  return;
150
170
  }
151
171
  for (const target of targets) {
@@ -153,10 +173,10 @@ export async function runInstall(opts) {
153
173
  try {
154
174
  let written;
155
175
  if (target.type === "vscode-settings") {
156
- written = writeVsCodeSettings(target.configPath, dryRun);
176
+ written = writeVsCodeSettings(target.configPath, dryRun, opts.useGlobalBinary);
157
177
  }
158
178
  else {
159
- written = writeMcpServersJson(target.configPath, dryRun);
179
+ written = writeMcpServersJson(target.configPath, dryRun, opts.useGlobalBinary);
160
180
  }
161
181
  process.stdout.write(` ${dryRun ? "[dry-run] would update" : "updated"}: ${written}\n`);
162
182
  }
@@ -176,8 +196,9 @@ export async function runInstall(opts) {
176
196
  process.stdout.write(dryRun
177
197
  ? "Dry-run complete. Re-run without --dry-run to apply.\n"
178
198
  : "Done! Restart your editor to activate the security-mcp server.\n");
199
+ process.stdout.write(`Install mode: ${opts.useGlobalBinary ? "global binary (security-mcp serve)" : "npx (npx -y security-mcp@latest serve)"}\n`);
179
200
  process.stdout.write("\nNext steps:\n");
180
201
  process.stdout.write(" 1. Restart your editor.\n");
181
202
  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.run_pr_gate" to check your current diff.\n\n');
203
+ process.stdout.write(' 3. Ask your AI: "Run security.start_review with mode=recent_changes" to begin an auditable review.\n\n');
183
204
  }