security-mcp 1.1.3 → 1.1.4

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
@@ -17,6 +17,7 @@ Works with **Claude Code, GitHub Copilot, Cursor, Codex, Replit**, and any MCP-c
17
17
 
18
18
  ## Table of Contents
19
19
 
20
+ - [What's New in 1.1.4](#whats-new-in-114)
20
21
  - [What Problem Does This Solve?](#what-problem-does-this-solve)
21
22
  - [Who Is This For?](#who-is-this-for)
22
23
  - [Two Modes - Pick Your Depth](#two-modes---pick-your-depth)
@@ -25,7 +26,9 @@ Works with **Claude Code, GitHub Copilot, Cursor, Codex, Replit**, and any MCP-c
25
26
  - [Claude Code](#step-by-step-claude-code)
26
27
  - [Cursor](#step-by-step-cursor)
27
28
  - [VS Code / GitHub Copilot](#step-by-step-vs-code--github-copilot)
29
+ - [Windsurf](#step-by-step-windsurf)
28
30
  - [Manual Configuration](#manual-configuration-any-mcp-editor)
31
+ - [Verify Your Installation](#verify-your-installation)
29
32
  - [How to Run Your First Security Review](#how-to-run-your-first-security-review)
30
33
  - [CI/CD Security Gate](#cicd-security-gate)
31
34
  - [What Gets Fixed Automatically](#what-gets-fixed-automatically)
@@ -40,6 +43,57 @@ Works with **Claude Code, GitHub Copilot, Cursor, Codex, Replit**, and any MCP-c
40
43
 
41
44
  ---
42
45
 
46
+ ## What's New in 1.1.4
47
+
48
+ ### 20 Checks (up from 18) - Deep Injection + Deep Auth Modules
49
+
50
+ Two new deep-check modules run automatically for web and API surfaces:
51
+
52
+ **`checkInjectionDeep`** — 11 new patterns: XXE (CWE-611), SSTI (CWE-94), prototype pollution (CWE-1321), open redirect (CWE-601), NoSQL operator injection (CWE-943), CRLF injection (CWE-113), unsafe YAML load (CWE-502), unsafe deserialization, path traversal (CWE-22), log injection (CWE-117), SSRF (CWE-918).
53
+
54
+ **`checkAuthDeep`** — 12 new patterns: JWT algorithm confusion / `alg:none` (CWE-327), session fixation (CWE-384), OAuth missing state parameter (CWE-352), OAuth `redirect_uri` open redirect (CWE-601), PKCE not enforced (RFC 7636), hardcoded JWT secret (CWE-798), missing rate limit on auth endpoints (CWE-307), plaintext password comparison (CWE-256), SAML signature validation disabled (CWE-347), insecure cookie flags (CWE-1004/614), refresh token not rotated (CWE-613), JWT HS/RS confusion (CVE-2015-9235 pattern).
55
+
56
+ ### Coverage Completeness Protocol (§0)
57
+
58
+ Every security review now runs a mandatory 5-step protocol before reporting any result:
59
+
60
+ 1. **Complete file inventory** — enumerate all source files into `coverage-manifest.json`; no attack class can be called CLEAN without checking every file.
61
+ 2. **Taint tracking** — trace every user-controlled input (`req.body`, `req.query`, WebSocket, env, file uploads, external API responses) to all downstream sinks, classifying each SAFE / UNSAFE / UNRESOLVED.
62
+ 3. **Negative assertions** — after each attack class: `ATTACK CLASS: {name} | FILES: N/N | PATTERNS: {list} | RESULT: CLEAN`.
63
+ 4. **Fix verification loop** — after every fix, re-run the triggering check and confirm it no longer fires before advancing.
64
+ 5. **All-or-nothing mandate** — every HIGH/CRITICAL finding is either FIXED (verified clean) or BLOCKED (risk-accepted, gate failing, remediation plan written to `deferred-fixes.json`).
65
+
66
+ ### Enhanced Threat Model Template
67
+
68
+ The `security.threat_model` tool now generates a more complete template including LINDDUN privacy threat analysis (Linking, Identifying, Non-repudiation, Detecting, Data Disclosure, Unawareness, Non-compliance), TRIKE risk matrix (actor-action-asset-risk), DREAD scoring, attack trees for the top 3 critical paths, adversary profiles mapped to ATT&CK techniques, and supply chain threat enumeration.
69
+
70
+ ### Expanded Release Checklists
71
+
72
+ All domain-specific release checklists now include:
73
+
74
+ - **OAuth/OIDC** — PKCE with S256, state/nonce verification, exact-match `redirect_uri`, code reuse prevention, audience validation
75
+ - **Business Logic** — idempotency keys on payment mutations, negative input validation, race condition testing for balance/quota/inventory
76
+ - **Serialization/Injection** — XXE, SSTI, unsafe YAML, deserialization, prototype pollution, open redirect, CRLF in every checklist
77
+ - **AI/LLM** — system prompt extraction resistance, multi-turn attack chains, multimodal injection, agentic tool allowlist, AML.T0054/T0057 mitigations
78
+ - **Payments (PCI DSS 4.0)** — PAN masking, DOM mutation monitoring, EMV 3DS 2.2+, Magecart prevention (SRI on checkout pages)
79
+ - **Observability Gate** (new) — anomaly detection baselines, SLO definitions for security events, alert fatigue review, runbook coverage
80
+
81
+ ### Windsurf Support
82
+
83
+ The installer now detects and configures Windsurf (`~/.windsurf/mcp.json`) automatically alongside Claude Code, Cursor, and VS Code.
84
+
85
+ ### `doctor` Command
86
+
87
+ Verify your installation health at any time:
88
+
89
+ ```bash
90
+ npx -y security-mcp@latest doctor
91
+ ```
92
+
93
+ Checks Node.js version, editor configs, and skill files — prints PASS/FAIL per check with actionable fix commands.
94
+
95
+ ---
96
+
43
97
  ## What Problem Does This Solve?
44
98
 
45
99
  When you use an AI coding assistant to build features fast, security is easy to skip - not because you don't care, but because:
@@ -257,6 +311,45 @@ You should see:
257
311
 
258
312
  ---
259
313
 
314
+ ### Step-by-Step: Windsurf
315
+
316
+ **Step 1 - Run the installer:**
317
+
318
+ ```bash
319
+ npx -y security-mcp@latest install
320
+ ```
321
+
322
+ This auto-detects Windsurf and writes to `~/.windsurf/mcp.json`.
323
+
324
+ **Step 2 - Verify:**
325
+
326
+ ```bash
327
+ cat ~/.windsurf/mcp.json
328
+ ```
329
+
330
+ Expected output:
331
+
332
+ ```json
333
+ {
334
+ "mcpServers": {
335
+ "security-mcp": {
336
+ "command": "npx",
337
+ "args": ["-y", "security-mcp@latest", "serve"]
338
+ }
339
+ }
340
+ }
341
+ ```
342
+
343
+ **Step 3 - Restart Windsurf.**
344
+
345
+ **Step 4 - In the Windsurf AI chat, type:**
346
+
347
+ ```text
348
+ Use /senior-security-engineer to review my recent changes
349
+ ```
350
+
351
+ ---
352
+
260
353
  ### Manual Configuration (Any MCP Editor)
261
354
 
262
355
  If the installer doesn't detect your editor, or you prefer to configure manually:
@@ -343,6 +436,28 @@ npx -y security-mcp@latest install --dry-run
343
436
 
344
437
  ---
345
438
 
439
+ ## Verify Your Installation
440
+
441
+ After installing, confirm everything is wired up correctly:
442
+
443
+ ```bash
444
+ npx -y security-mcp@latest doctor
445
+ ```
446
+
447
+ This checks your Node.js version, editor config files, and installed skills — and prints `[PASS]` or `[FAIL]` per check with a fix command if anything is missing.
448
+
449
+ Example output:
450
+
451
+ ```text
452
+ [PASS] Node.js 22.x
453
+ [PASS] Claude Code config (~/.claude/settings.json)
454
+ [PASS] senior-security-engineer skill (~/.claude/skills/senior-security-engineer/SKILL.md)
455
+
456
+ All checks passed. Restart your editor, then type /senior-security-engineer.
457
+ ```
458
+
459
+ ---
460
+
346
461
  ## How to Run Your First Security Review
347
462
 
348
463
  ### Daily Workflow: `/senior-security-engineer`
@@ -365,7 +480,7 @@ npx -y security-mcp@latest install --dry-run
365
480
 
366
481
  1. Call `security.start_review` to create a tracked run
367
482
  2. Build a scan plan covering all relevant OWASP/NIST/ATT&CK controls
368
- 3. Run 18 security checks in parallel across secrets, dependencies, crypto, auth, injection, cloud config, AI/LLM, mobile, and more
483
+ 3. Run 20 security checks in parallel across secrets, dependencies, crypto, auth, injection, cloud config, AI/LLM, mobile, and more
369
484
  4. Write fixes directly into your code for every finding it can remediate
370
485
  5. Generate a SHA-256 attested report at `.mcp/reports/{runId}.attestation.json`
371
486
 
@@ -463,7 +578,7 @@ jobs:
463
578
 
464
579
  ### What the CI Gate Checks
465
580
 
466
- The gate runs **18 checks in parallel** against your diff:
581
+ The gate runs **20 checks in parallel** against your diff:
467
582
 
468
583
  | Category | What It Catches |
469
584
  | --- | --- |
@@ -486,6 +601,8 @@ The gate runs **18 checks in parallel** against your diff:
486
601
  | **AI red-team** | Static + optional dynamic probes against AI endpoints |
487
602
  | **Exceptions** | Validates any active security exceptions are non-expired and properly approved |
488
603
  | **Baseline regression** | Detects when previously-satisfied controls go missing (BASELINE_REGRESSION HIGH finding injected on regression) |
604
+ | **Deep injection** | XXE, SSTI, prototype pollution, open redirect, NoSQL operator injection, CRLF, unsafe YAML load, deserialization, path traversal, log injection, SSRF (11 new patterns) |
605
+ | **Deep auth** | JWT algorithm confusion, session fixation, OAuth missing state, OAuth open redirect_uri, PKCE not enforced, hardcoded JWT secret, missing rate limit on auth endpoints, plaintext password compare, SAML signature disabled, insecure cookie flags, refresh token not rotated, JWT HS/RS confusion (12 new patterns) |
489
606
 
490
607
  ### Customize the Gate Policy
491
608
 
@@ -652,15 +769,16 @@ app.use(helmet({
652
769
  ┌──────────────────────────────────────────────────────────────┐
653
770
  │ Policy Gate Engine (src/gate/policy.ts) │
654
771
  │ │
655
- 18 checks run in parallel: │
772
+ 20 checks run in parallel: │
656
773
  │ checkSecrets checkDependencies checkApi checkInfra │
657
774
  │ checkCrypto checkMobileIos checkMobileAndroid │
658
775
  │ checkAi checkGraphQL checkKubernetes │
659
776
  │ checkDatabase checkDlp checkWebNextjs │
660
- │ runSbomChecks runAiRedteamChecks runRuntimeChecks ...
777
+ │ runSbomChecks runAiRedteamChecks runRuntimeChecks
778
+ │ checkInjectionDeep (11 patterns) checkAuthDeep (12 patterns)│
661
779
  │ │
662
780
  │ Surface detection -> Control catalog -> Exception handling -> │
663
- │ Confidence scoring -> PASS / FAIL
781
+ Coverage manifest -> Taint map -> Confidence scoring -> PASS / FAIL
664
782
  └──────────────────────────────────────────────────────────────┘
665
783
  ```
666
784
 
@@ -684,10 +802,17 @@ User: /senior-security-engineer
684
802
  └── STRIDE + PASTA + ATT&CK template for changed surface
685
803
 
686
804
 
805
+ §0 Coverage Completeness Protocol (runs first)
806
+ ├── enumerate ALL source files → coverage-manifest.json
807
+ ├── taint-trace every user-controlled input → taint-map.json
808
+ ├── negative assertion per attack class: "FILES: N/N | RESULT: CLEAN"
809
+ └── fix verification loop: re-run check after every fix, confirm CLEAN
810
+
811
+
687
812
  security.run_pr_gate(runId, mode, targets)
688
813
  ├── git diff / glob targets -> changed files list
689
814
  ├── detectSurfaces() -> web? api? infra? mobile? ai?
690
- ├── 18 checks in parallel
815
+ ├── 20 checks in parallel (incl. deep injection + deep auth)
691
816
  ├── apply exceptions from .mcp/exceptions/
692
817
  ├── compute confidence score
693
818
  └── returns PASS/FAIL + findings[]
@@ -695,6 +820,8 @@ User: /senior-security-engineer
695
820
 
696
821
  Claude writes inline fixes for every finding
697
822
  (production-ready secure code, not suggestions)
823
+ Every HIGH/CRITICAL: FIXED with verified-clean re-run,
824
+ OR formally blocked with risk-acceptance record
698
825
 
699
826
 
700
827
  security.attest_review(runId)
@@ -853,7 +980,7 @@ Your AI uses these automatically. You don't call them directly, but understandin
853
980
  | Tool | What It Does |
854
981
  | --- | --- |
855
982
  | `security.start_review` | Starts a stateful review run; returns `runId` used to track all subsequent steps and produce the final attestation |
856
- | `security.run_pr_gate` | Runs 18 security checks in parallel; returns PASS/FAIL, findings with severity, and required actions |
983
+ | `security.run_pr_gate` | Runs 20 security checks in parallel; returns PASS/FAIL, findings with severity, and required actions |
857
984
  | `security.threat_model` | Generates a STRIDE + PASTA + ATT&CK threat model template for a specific feature or surface |
858
985
  | `security.checklist` | Returns the pre-release security checklist, optionally filtered by surface (web / api / mobile / ai / infra / payments) |
859
986
  | `security.scan_strategy` | Builds an exhaustive scan plan mapping every check to OWASP, NIST, ATT&CK, and compliance controls |
@@ -544,6 +544,206 @@
544
544
  "surfaces": ["all"],
545
545
  "frameworks": ["GDPR", "HIPAA", "PCI DSS 4.0"],
546
546
  "required_scanners": ["semgrep"]
547
+ },
548
+ {
549
+ "id": "OAUTH_PKCE_ENFORCED",
550
+ "description": "All OAuth 2.0 public clients use PKCE with S256 code challenge method.",
551
+ "automation": "tooling",
552
+ "surfaces": ["web", "api", "mobile"],
553
+ "frameworks": ["RFC 7636", "OWASP ASVS 3.5.3"],
554
+ "required_scanners": ["semgrep"]
555
+ },
556
+ {
557
+ "id": "JWT_ALG_CONFUSION_PREVENTED",
558
+ "description": "All jwt.verify() calls pin the algorithm to RS256/ES256 — algorithm confusion attack prevented.",
559
+ "automation": "tooling",
560
+ "surfaces": ["web", "api"],
561
+ "frameworks": ["CWE-327", "OWASP A07"],
562
+ "required_scanners": ["semgrep"]
563
+ },
564
+ {
565
+ "id": "SESSION_FIXATION_PREVENTED",
566
+ "description": "Session is regenerated after authentication to prevent session fixation attacks.",
567
+ "automation": "tooling",
568
+ "surfaces": ["web", "api"],
569
+ "frameworks": ["CWE-384", "OWASP A07"],
570
+ "required_scanners": ["semgrep"]
571
+ },
572
+ {
573
+ "id": "BUSINESS_LOGIC_REVIEWED",
574
+ "description": "Business logic abuse scenarios (state skipping, negative values, race conditions) are tested before release.",
575
+ "automation": "evidence",
576
+ "surfaces": ["all"],
577
+ "frameworks": ["OWASP Testing Guide OTG-BUSLOGIC", "NIST SP 800-53 SA-11"],
578
+ "evidence": ["pentest-report.json", "threat-model.json"]
579
+ },
580
+ {
581
+ "id": "RACE_CONDITION_MITIGATED",
582
+ "description": "All limit-once invariants (balance, quota, inventory, coupons) are protected by atomic DB operations.",
583
+ "automation": "tooling",
584
+ "surfaces": ["api"],
585
+ "frameworks": ["CWE-362", "OWASP Testing Guide OTG-BUSLOGIC-009"],
586
+ "required_scanners": ["semgrep"]
587
+ },
588
+ {
589
+ "id": "PROTOTYPE_POLLUTION_MITIGATED",
590
+ "description": "All object merges of user-controlled data are protected by schema validation before merge.",
591
+ "automation": "tooling",
592
+ "surfaces": ["web", "api"],
593
+ "frameworks": ["CWE-1321", "OWASP A03"],
594
+ "required_scanners": ["semgrep"]
595
+ },
596
+ {
597
+ "id": "HTTP_SMUGGLING_MITIGATED",
598
+ "description": "Proxy and origin servers normalize CL/TE headers; H2C upgrade disabled.",
599
+ "automation": "evidence",
600
+ "surfaces": ["web", "api"],
601
+ "frameworks": ["CWE-444", "OWASP Testing Guide OTG-INPVAL-016"],
602
+ "evidence": ["pentest-report.json"]
603
+ },
604
+ {
605
+ "id": "XXE_PREVENTED",
606
+ "description": "XML parsers disable external entity processing (processEntities:false).",
607
+ "automation": "tooling",
608
+ "surfaces": ["api"],
609
+ "frameworks": ["CWE-611", "OWASP A03"],
610
+ "required_scanners": ["semgrep"]
611
+ },
612
+ {
613
+ "id": "SSTI_PREVENTED",
614
+ "description": "Server-side templates are never compiled from user-controlled input.",
615
+ "automation": "tooling",
616
+ "surfaces": ["web", "api"],
617
+ "frameworks": ["CWE-94", "OWASP A03"],
618
+ "required_scanners": ["semgrep"]
619
+ },
620
+ {
621
+ "id": "DESERIALIZATION_SAFE",
622
+ "description": "Untrusted data is never passed to unsafe deserialization functions (node-serialize, eval, new Function).",
623
+ "automation": "tooling",
624
+ "surfaces": ["api"],
625
+ "frameworks": ["CWE-502", "OWASP A08"],
626
+ "required_scanners": ["semgrep"]
627
+ },
628
+ {
629
+ "id": "NOSQL_INJECTION_PREVENTED",
630
+ "description": "MongoDB/DynamoDB queries are built from validated fields, not raw user input.",
631
+ "automation": "tooling",
632
+ "surfaces": ["api"],
633
+ "frameworks": ["CWE-943", "OWASP A03"],
634
+ "required_scanners": ["semgrep"]
635
+ },
636
+ {
637
+ "id": "HTTP_VERB_TAMPERING_PREVENTED",
638
+ "description": "Read-only endpoints return 405 on PUT/DELETE — HTTP verb tampering blocked.",
639
+ "automation": "tooling",
640
+ "surfaces": ["api"],
641
+ "frameworks": ["OWASP Testing Guide OTG-INPVAL-003"],
642
+ "required_scanners": ["semgrep"]
643
+ },
644
+ {
645
+ "id": "OPEN_REDIRECT_BLOCKED",
646
+ "description": "All res.redirect() targets are validated against an allowlist or enforced relative-only.",
647
+ "automation": "tooling",
648
+ "surfaces": ["web", "api"],
649
+ "frameworks": ["CWE-601", "OWASP A01"],
650
+ "required_scanners": ["semgrep"]
651
+ },
652
+ {
653
+ "id": "SUBDOMAIN_TAKEOVER_MITIGATED",
654
+ "description": "DNS audit confirms no dangling CNAME records pointing to unprovisioned services.",
655
+ "automation": "evidence",
656
+ "surfaces": ["web"],
657
+ "frameworks": ["OWASP Testing Guide OTG-CONFIG-010"],
658
+ "evidence": ["dns-audit.json"]
659
+ },
660
+ {
661
+ "id": "GRAPHQL_FULL_HARDENING",
662
+ "description": "GraphQL introspection disabled, depth/complexity limits enforced, batching limited, field-level auth present.",
663
+ "automation": "tooling",
664
+ "surfaces": ["api"],
665
+ "frameworks": ["OWASP API Security Top 10", "OWASP Testing Guide"],
666
+ "required_scanners": ["semgrep"]
667
+ },
668
+ {
669
+ "id": "AI_MULTIMODAL_INJECTION_PREVENTED",
670
+ "description": "Multimodal inputs (image, audio, document) are treated as untrusted and cannot inject instructions.",
671
+ "automation": "evidence",
672
+ "surfaces": ["ai"],
673
+ "frameworks": ["OWASP LLM01", "MITRE ATLAS AML.T0054"],
674
+ "evidence": ["ai-redteam-report.json"]
675
+ },
676
+ {
677
+ "id": "AI_MEMORY_POISONING_PREVENTED",
678
+ "description": "Agent memory and scratchpad content is validated before use in downstream instructions.",
679
+ "automation": "evidence",
680
+ "surfaces": ["ai"],
681
+ "frameworks": ["OWASP LLM08", "MITRE ATLAS"],
682
+ "evidence": ["ai-redteam-report.json"]
683
+ },
684
+ {
685
+ "id": "AI_MODEL_INVERSION_MITIGATED",
686
+ "description": "Model cannot be probed to recite training PII or memorized secrets.",
687
+ "automation": "evidence",
688
+ "surfaces": ["ai"],
689
+ "frameworks": ["OWASP LLM06", "MITRE ATLAS AML.T0057"],
690
+ "evidence": ["ai-redteam-report.json"]
691
+ },
692
+ {
693
+ "id": "MASVS_ANTI_REVERSING",
694
+ "description": "Mobile release binary uses code obfuscation and anti-instrumentation controls (MASVS-RESILIENCE).",
695
+ "automation": "evidence",
696
+ "surfaces": ["mobile"],
697
+ "frameworks": ["OWASP MASVS 2.0 RESILIENCE"],
698
+ "evidence": ["mobile-binary-review.json"]
699
+ },
700
+ {
701
+ "id": "IMDS_V2_ENFORCED",
702
+ "description": "EC2 instances enforce IMDSv2 (HttpTokens=required) — IMDSv1 disabled.",
703
+ "automation": "tooling",
704
+ "surfaces": ["infra"],
705
+ "frameworks": ["CIS AWS Foundations Benchmark 5.6", "NIST SP 800-53 SC-7"],
706
+ "required_scanners": ["checkov"]
707
+ },
708
+ {
709
+ "id": "CLOUD_THREAT_DETECTION_ENABLED",
710
+ "description": "AWS GuardDuty / GCP SCC / Azure Defender for Cloud is enabled across all accounts.",
711
+ "automation": "tooling",
712
+ "surfaces": ["infra"],
713
+ "frameworks": ["CIS Benchmark", "NIST SP 800-53 SI-3"],
714
+ "required_scanners": ["checkov"]
715
+ },
716
+ {
717
+ "id": "MAGECART_PROTECTION",
718
+ "description": "Payment pages enforce extra-strict CSP and SRI on all scripts — Magecart e-skimming prevented.",
719
+ "automation": "tooling",
720
+ "surfaces": ["payments", "web"],
721
+ "frameworks": ["PCI DSS 4.0 Req 6.4.3", "OWASP ASVS 14.4"],
722
+ "required_scanners": ["semgrep"]
723
+ },
724
+ {
725
+ "id": "PCI_3DS_V2_ENFORCED",
726
+ "description": "EMV 3DS version 2.2+ is used for all card-not-present high-risk transactions.",
727
+ "automation": "evidence",
728
+ "surfaces": ["payments"],
729
+ "frameworks": ["PCI DSS 4.0 Req 8", "EMV 3DS 2.2"],
730
+ "evidence": ["payment-integration-test-results.json"]
731
+ },
732
+ {
733
+ "id": "HIPAA_PHI_CONTROLS",
734
+ "description": "Protected Health Information (PHI) is encrypted at rest and in transit; access is limited and audited.",
735
+ "automation": "evidence",
736
+ "surfaces": ["all"],
737
+ "frameworks": ["HIPAA §164.312", "NIST SP 800-53 SC-28"],
738
+ "evidence": ["hipaa-phi-audit.json"]
739
+ },
740
+ {
741
+ "id": "SUPPLY_CHAIN_CICD_HARDENED",
742
+ "description": "All CI/CD GitHub Actions are SHA-pinned; GITHUB_TOKEN has minimal permissions; no pwn-request risk.",
743
+ "automation": "tooling",
744
+ "surfaces": ["infra"],
745
+ "frameworks": ["SLSA Level 3", "NIST SP 800-161", "CIS Software Supply Chain Benchmark"],
746
+ "required_scanners": ["semgrep", "checkov"]
547
747
  }
548
748
  ]
549
749
  }
package/dist/cli/index.js CHANGED
@@ -9,9 +9,11 @@
9
9
  * --version
10
10
  * --help
11
11
  */
12
- import { createRequire } from "module";
13
- import { fileURLToPath } from "url";
14
- import { dirname, resolve } from "path";
12
+ import { createRequire } from "node:module";
13
+ import { fileURLToPath } from "node:url";
14
+ import { dirname, resolve } from "node:path";
15
+ import { existsSync } from "node:fs";
16
+ import { homedir, platform } from "node:os";
15
17
  import { runInstall } from "./install.js";
16
18
  import { main as runServer } from "../mcp/server.js";
17
19
  import { notifyIfUpdateAvailable } from "./update.js";
@@ -55,6 +57,7 @@ COMMANDS
55
57
  install Auto-detect installed editors and write MCP configs
56
58
  install-global Install using the globally installed security-mcp binary
57
59
  config Print MCP config JSON for manual editor setup
60
+ doctor Verify the installation is working correctly
58
61
 
59
62
  OPTIONS (install)
60
63
  --claude-code Write config for Claude Code only
@@ -87,6 +90,9 @@ EXAMPLES
87
90
  # Preview install without writing:
88
91
  npx -y security-mcp@latest install --dry-run
89
92
 
93
+ # Verify installation health:
94
+ npx -y security-mcp@latest doctor
95
+
90
96
  # Print JSON config snippet:
91
97
  npx -y security-mcp@latest config
92
98
  security-mcp config --use-global-binary
@@ -103,11 +109,76 @@ EDITOR CONFIG (add manually if install fails):
103
109
 
104
110
  Claude Code: ~/.claude/settings.json
105
111
  Cursor: ~/.cursor/mcp.json or .cursor/mcp.json
106
- VS Code: .vscode/mcp.json (workspace)
112
+ VS Code: User settings.json (via Preferences > Open User Settings JSON)
113
+ Windsurf: ~/.windsurf/mcp.json
107
114
 
108
115
  MORE INFO
109
116
  https://github.com/AbrahamOO/security-mcp
110
117
  `;
118
+ function resolveHome(p) {
119
+ return p.replace(/^~/, homedir());
120
+ }
121
+ function getVsCodeSettingsPath() {
122
+ const os = platform();
123
+ if (os === "win32")
124
+ return `${process.env["APPDATA"] ?? ""}\\Code\\User\\settings.json`;
125
+ if (os === "darwin")
126
+ return `${homedir()}/Library/Application Support/Code/User/settings.json`;
127
+ return `${homedir()}/.config/Code/User/settings.json`;
128
+ }
129
+ function runDoctor() {
130
+ const checks = [];
131
+ // Node.js version
132
+ const nodeVer = process.versions.node.split(".").map(Number);
133
+ const nodeOk = (nodeVer[0] ?? 0) >= 20;
134
+ checks.push({ label: `Node.js ${process.versions.node}`, ok: nodeOk, hint: nodeOk ? undefined : "Node.js 20+ required. Download from https://nodejs.org" });
135
+ // Claude Code config
136
+ const claudeConfig = resolveHome("~/.claude/settings.json");
137
+ const claudeOk = existsSync(claudeConfig);
138
+ checks.push({ label: `Claude Code config (${claudeConfig})`, ok: claudeOk, hint: claudeOk ? undefined : "Run: npx -y security-mcp@latest install --claude-code" });
139
+ // Claude Code skill
140
+ const skillPath = resolveHome("~/.claude/skills/senior-security-engineer/SKILL.md");
141
+ const skillOk = existsSync(skillPath);
142
+ checks.push({ label: `senior-security-engineer skill (${skillPath})`, ok: skillOk, hint: skillOk ? undefined : "Run: npx -y security-mcp@latest install --claude-code" });
143
+ // Cursor global config
144
+ const cursorConfig = resolveHome("~/.cursor/mcp.json");
145
+ if (existsSync(resolveHome("~/.cursor"))) {
146
+ const cursorOk = existsSync(cursorConfig);
147
+ checks.push({ label: `Cursor config (${cursorConfig})`, ok: cursorOk, hint: cursorOk ? undefined : "Run: npx -y security-mcp@latest install --cursor" });
148
+ }
149
+ // VS Code config
150
+ const vscodePath = getVsCodeSettingsPath();
151
+ if (existsSync(vscodePath)) {
152
+ checks.push({ label: `VS Code config (${vscodePath})`, ok: true });
153
+ }
154
+ // Windsurf config
155
+ const windsurfConfig = resolveHome("~/.windsurf/mcp.json");
156
+ if (existsSync(resolveHome("~/.windsurf"))) {
157
+ const windsurfOk = existsSync(windsurfConfig);
158
+ checks.push({ label: `Windsurf config (${windsurfConfig})`, ok: windsurfOk, hint: windsurfOk ? undefined : "Run: npx -y security-mcp@latest install" });
159
+ }
160
+ process.stdout.write(`\nsecurity-mcp doctor v${VERSION}\n`);
161
+ process.stdout.write("=".repeat(40) + "\n\n");
162
+ let allOk = true;
163
+ for (const check of checks) {
164
+ const status = check.ok ? "PASS" : "FAIL";
165
+ process.stdout.write(` [${status}] ${check.label}\n`);
166
+ if (!check.ok) {
167
+ allOk = false;
168
+ if (check.hint)
169
+ process.stdout.write(` Fix: ${check.hint}\n`);
170
+ }
171
+ }
172
+ process.stdout.write("\n");
173
+ if (allOk) {
174
+ process.stdout.write("All checks passed. security-mcp is installed correctly.\n");
175
+ process.stdout.write("Restart your editor if you haven't already, then type /senior-security-engineer.\n\n");
176
+ }
177
+ else {
178
+ process.stdout.write("Some checks failed. Run the suggested fix commands above, then re-run: npx -y security-mcp@latest doctor\n\n");
179
+ process.exit(1);
180
+ }
181
+ }
111
182
  async function main() {
112
183
  const args = process.argv.slice(2);
113
184
  const useGlobalBinary = args.includes("--use-global-binary");
@@ -165,7 +236,13 @@ async function main() {
165
236
  process.stdout.write("\nAdd the above to your editor's MCP config file.\n");
166
237
  process.stdout.write(" Claude Code: ~/.claude/settings.json\n");
167
238
  process.stdout.write(" Cursor: ~/.cursor/mcp.json\n");
168
- process.stdout.write(" VS Code: .vscode/mcp.json\n");
239
+ process.stdout.write(" VS Code: User settings.json (Preferences > Open User Settings JSON)\n");
240
+ process.stdout.write(" Windsurf: ~/.windsurf/mcp.json\n");
241
+ break;
242
+ }
243
+ case "doctor":
244
+ case "verify": {
245
+ runDoctor();
169
246
  break;
170
247
  }
171
248
  default: {
@@ -29,6 +29,7 @@ function getEditorTargets(opts) {
29
29
  const cursorGlobalPath = resolveHome("~/.cursor/mcp.json");
30
30
  const cursorLocalPath = ".cursor/mcp.json";
31
31
  const vscodePath = getVsCodeSettingsPath();
32
+ const windsurfPath = resolveHome("~/.windsurf/mcp.json");
32
33
  const all = [
33
34
  {
34
35
  name: "Claude Code",
@@ -53,6 +54,12 @@ function getEditorTargets(opts) {
53
54
  configPath: vscodePath,
54
55
  type: "vscode-settings",
55
56
  detected: existsSync(vscodePath)
57
+ },
58
+ {
59
+ name: "Windsurf",
60
+ configPath: windsurfPath,
61
+ type: "mcp-servers-json",
62
+ detected: existsSync(resolveHome("~/.windsurf"))
56
63
  }
57
64
  ];
58
65
  if (opts.all) {
@@ -166,11 +173,24 @@ function installSkill(dryRun) {
166
173
  */
167
174
  // CWE-22: only alphanumeric, hyphens, and dots allowed in skill names
168
175
  const SAFE_SKILL_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
176
+ // CWE-918: allowlist for skill download hosts — no user-controlled URLs reach the network
177
+ const ALLOWED_SKILL_HOSTS = new Set(["raw.githubusercontent.com", "github.com"]);
169
178
  export async function downloadSkill(skillName, url, dryRun = false) {
170
179
  if (!SAFE_SKILL_NAME_RE.test(skillName)) {
171
180
  process.stdout.write(` [error] invalid skill name "${skillName}" — skipping download\n`);
172
181
  return;
173
182
  }
183
+ try {
184
+ const { hostname } = new URL(url);
185
+ if (!ALLOWED_SKILL_HOSTS.has(hostname)) {
186
+ process.stdout.write(` [error] blocked skill download from unauthorized host "${hostname}"\n`);
187
+ return;
188
+ }
189
+ }
190
+ catch {
191
+ process.stdout.write(` [error] invalid skill URL "${url}" — skipping download\n`);
192
+ return;
193
+ }
174
194
  const skillDest = resolveHome(`~/.claude/skills/${skillName}/SKILL.md`);
175
195
  if (dryRun) {
176
196
  process.stdout.write(` [dry-run] would download skill "${skillName}" from ${url} → ${skillDest}\n`);
@@ -271,12 +291,22 @@ export async function runInstall(opts) {
271
291
  process.stdout.write("\nInstalling security policy...\n");
272
292
  installPolicy(dryRun);
273
293
  process.stdout.write("\n");
274
- process.stdout.write(dryRun
275
- ? "Dry-run complete. Re-run without --dry-run to apply.\n"
276
- : "Done! Restart your editor to activate the security-mcp server.\n");
294
+ if (dryRun) {
295
+ process.stdout.write("Dry-run complete. Re-run without --dry-run to apply.\n\n");
296
+ return;
297
+ }
298
+ process.stdout.write("Installation complete!\n");
277
299
  process.stdout.write(`Install mode: ${opts.useGlobalBinary ? "global binary (security-mcp serve)" : "npx (npx -y security-mcp@latest serve)"}\n`);
278
300
  process.stdout.write("\nNext steps:\n");
279
- process.stdout.write(" 1. Restart your editor.\n");
280
- process.stdout.write(' 2. In Claude Code, type /senior-security-engineer to activate the security persona.\n');
281
- process.stdout.write(' 3. Ask your AI: "Run security.start_review with mode=recent_changes" to begin an auditable review.\n\n');
301
+ process.stdout.write(" 1. Restart your editor (fully quit and reopen — not just reload window).\n");
302
+ process.stdout.write(" 2. Verify the server loaded:\n");
303
+ process.stdout.write(" Claude Code: type /mcp and confirm security-mcp is listed as Connected.\n");
304
+ process.stdout.write(" Cursor: Settings > MCP > security-mcp should show as active.\n");
305
+ process.stdout.write(" 3. Run your first security review:\n");
306
+ process.stdout.write(" /senior-security-engineer\n");
307
+ process.stdout.write(" The agent will ask you to choose a scan scope (recent changes / full codebase / specific files).\n");
308
+ process.stdout.write(" 4. For a deep 39-agent audit before a release:\n");
309
+ process.stdout.write(" /ciso-orchestrator\n");
310
+ process.stdout.write("\nVerify this install at any time:\n");
311
+ process.stdout.write(" npx -y security-mcp@latest --version\n\n");
282
312
  }
@@ -275,8 +275,14 @@ async function fetchLatestRelease(repo) {
275
275
  function pickAsset(assets, pattern) {
276
276
  return assets.find((a) => a.name.toLowerCase().includes(pattern.toLowerCase()))?.browser_download_url;
277
277
  }
278
+ const ALLOWED_BINARY_HOSTS = new Set([
279
+ "github.com", "objects.githubusercontent.com", "github-releases.githubusercontent.com"
280
+ ]);
278
281
  async function downloadBinary(url, dest) {
279
282
  try {
283
+ const { hostname } = new URL(url);
284
+ if (!ALLOWED_BINARY_HOSTS.has(hostname))
285
+ return false;
280
286
  const res = await fetch(url);
281
287
  if (!res.ok || !res.body)
282
288
  return false;