security-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/defaults/evidence-map.json +126 -0
- package/defaults/security-policy.json +93 -0
- package/dist/ci/pr-gate.js +17 -0
- package/dist/cli/index.js +140 -0
- package/dist/cli/install.js +161 -0
- package/dist/gate/checks/ai.js +39 -0
- package/dist/gate/checks/api.js +46 -0
- package/dist/gate/checks/dependencies.js +39 -0
- package/dist/gate/checks/infra.js +38 -0
- package/dist/gate/checks/mobile-android.js +35 -0
- package/dist/gate/checks/mobile-ios.js +23 -0
- package/dist/gate/checks/required-artifacts.js +25 -0
- package/dist/gate/checks/secrets.js +31 -0
- package/dist/gate/checks/web-nextjs.js +76 -0
- package/dist/gate/diff.js +11 -0
- package/dist/gate/findings.js +11 -0
- package/dist/gate/policy.js +68 -0
- package/dist/gate/result.js +1 -0
- package/dist/mcp/server.js +463 -0
- package/dist/repo/fs.js +9 -0
- package/dist/repo/search.js +41 -0
- package/package.json +76 -0
- package/prompts/SECURITY_PROMPT.md +931 -0
- package/skills/security-review/SKILL.md +922 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { readFileSync, existsSync } from "fs";
|
|
4
|
+
import { dirname, join, resolve } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { runPrGate } from "../gate/policy.js";
|
|
8
|
+
import { readFileSafe } from "../repo/fs.js";
|
|
9
|
+
import { searchRepo } from "../repo/search.js";
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const PKG_ROOT = resolve(__dirname, "../..");
|
|
12
|
+
const PROMPTS_DIR = join(PKG_ROOT, "prompts");
|
|
13
|
+
// Load the generalized security prompt at startup.
|
|
14
|
+
// Falls back to a short notice if the file has not been built yet.
|
|
15
|
+
function loadPromptFile(name) {
|
|
16
|
+
const path = join(PROMPTS_DIR, name);
|
|
17
|
+
if (existsSync(path)) {
|
|
18
|
+
return readFileSync(path, "utf-8");
|
|
19
|
+
}
|
|
20
|
+
return `[security-mcp] Prompt file not found: ${name}. Run "npm run build" from the package root.`;
|
|
21
|
+
}
|
|
22
|
+
const SECURITY_PROMPT = loadPromptFile("SECURITY_PROMPT.md");
|
|
23
|
+
/* eslint-disable deprecation/deprecation */
|
|
24
|
+
const server = new McpServer({
|
|
25
|
+
name: "security-mcp",
|
|
26
|
+
version: "1.0.0"
|
|
27
|
+
});
|
|
28
|
+
const tool = server.tool.bind(server);
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Helper
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
function asTextResponse(data) {
|
|
33
|
+
const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
34
|
+
return { content: [{ type: "text", text }] };
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Existing tools (unchanged)
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
const RunPrGateParams = {
|
|
40
|
+
baseRef: z.string().optional().describe("Base git ref for diff (e.g. origin/main). Optional."),
|
|
41
|
+
headRef: z.string().optional().describe("Head git ref for diff (e.g. HEAD). Optional."),
|
|
42
|
+
policyPath: z.string().optional().describe("Override policy path. Default: .mcp/policies/security-policy.json")
|
|
43
|
+
};
|
|
44
|
+
const RunPrGateSchema = z.object(RunPrGateParams);
|
|
45
|
+
tool("security.run_pr_gate", "Run the security policy gate against the current workspace. Returns PASS/FAIL plus findings and required actions.", RunPrGateParams, async (args, _extra) => {
|
|
46
|
+
const { baseRef, headRef, policyPath } = RunPrGateSchema.parse(args);
|
|
47
|
+
const result = await runPrGate({
|
|
48
|
+
baseRef,
|
|
49
|
+
headRef,
|
|
50
|
+
policyPath: policyPath ?? ".mcp/policies/security-policy.json"
|
|
51
|
+
});
|
|
52
|
+
return asTextResponse(result);
|
|
53
|
+
});
|
|
54
|
+
const ReadFileParams = {
|
|
55
|
+
path: z.string().describe("Relative path in the repo.")
|
|
56
|
+
};
|
|
57
|
+
const ReadFileSchema = z.object(ReadFileParams);
|
|
58
|
+
tool("repo.read_file", "Read a file from the repo workspace.", ReadFileParams, async (args, _extra) => {
|
|
59
|
+
const { path } = ReadFileSchema.parse(args);
|
|
60
|
+
const data = await readFileSafe(path);
|
|
61
|
+
return asTextResponse(data);
|
|
62
|
+
});
|
|
63
|
+
const SearchParams = {
|
|
64
|
+
query: z.string().describe("Plain string or regex pattern."),
|
|
65
|
+
isRegex: z.boolean().optional().describe("Treat query as regex. Default false."),
|
|
66
|
+
maxMatches: z.number().int().min(1).max(500).optional().describe("Default 200.")
|
|
67
|
+
};
|
|
68
|
+
const SearchSchema = z.object(SearchParams);
|
|
69
|
+
tool("repo.search", "Search the repo for a regex or string. Returns matches with file + line numbers.", SearchParams, async (args, _extra) => {
|
|
70
|
+
const { query, isRegex, maxMatches } = SearchSchema.parse(args);
|
|
71
|
+
const matches = await searchRepo({ query, isRegex: !!isRegex, maxMatches: maxMatches ?? 200 });
|
|
72
|
+
return asTextResponse(matches);
|
|
73
|
+
});
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// New tool: security.get_system_prompt
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
const GetSystemPromptParams = {
|
|
78
|
+
stack: z.string().optional().describe("Your tech stack, e.g. 'Next.js, TypeScript, PostgreSQL, AWS Lambda'. " +
|
|
79
|
+
"Appended as a Scope section to the prompt."),
|
|
80
|
+
cloud: z.string().optional().describe("Primary cloud provider(s), e.g. 'AWS', 'GCP', 'Azure', 'multi-cloud'."),
|
|
81
|
+
payment_processor: z.string().optional().describe("Payment processor in use, e.g. 'Stripe', 'Braintree', 'Adyen', or 'none'.")
|
|
82
|
+
};
|
|
83
|
+
const GetSystemPromptSchema = z.object(GetSystemPromptParams);
|
|
84
|
+
tool("security.get_system_prompt", "Return the full security engineering system prompt. Optionally customized with your stack, cloud provider, and payment processor. Use this as the system prompt to configure Claude as an elite security engineer for your project.", GetSystemPromptParams, async (args, _extra) => {
|
|
85
|
+
const { stack, cloud, payment_processor } = GetSystemPromptSchema.parse(args);
|
|
86
|
+
let prompt = SECURITY_PROMPT;
|
|
87
|
+
// Append a project-specific scope section if any context was provided
|
|
88
|
+
if (stack ?? cloud ?? payment_processor) {
|
|
89
|
+
const scopeLines = [
|
|
90
|
+
"",
|
|
91
|
+
"---",
|
|
92
|
+
"",
|
|
93
|
+
"## PROJECT SCOPE (user-defined)",
|
|
94
|
+
""
|
|
95
|
+
];
|
|
96
|
+
if (stack)
|
|
97
|
+
scopeLines.push(`- **Stack**: ${stack}`);
|
|
98
|
+
if (cloud)
|
|
99
|
+
scopeLines.push(`- **Primary cloud**: ${cloud}`);
|
|
100
|
+
if (payment_processor)
|
|
101
|
+
scopeLines.push(`- **Payment processor**: ${payment_processor}`);
|
|
102
|
+
scopeLines.push("");
|
|
103
|
+
prompt = prompt + scopeLines.join("\n");
|
|
104
|
+
}
|
|
105
|
+
return asTextResponse(prompt);
|
|
106
|
+
});
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// New tool: security.threat_model
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
const ThreatModelParams = {
|
|
111
|
+
feature: z.string().describe("One or two sentences describing the feature or component to threat-model. " +
|
|
112
|
+
"Example: 'OAuth 2.0 login flow with PKCE and session cookies'."),
|
|
113
|
+
surfaces: z.array(z.enum(["web", "api", "mobile", "ai", "infra", "data"])).optional().describe("Attack surfaces involved. Defaults to all.")
|
|
114
|
+
};
|
|
115
|
+
const ThreatModelSchema = z.object(ThreatModelParams);
|
|
116
|
+
tool("security.threat_model", "Generate a STRIDE + PASTA + ATT&CK threat model template for a described feature or component. Returns a structured Markdown document ready to fill in.", ThreatModelParams, async (args, _extra) => {
|
|
117
|
+
const { feature, surfaces } = ThreatModelSchema.parse(args);
|
|
118
|
+
const surfaceList = surfaces ?? ["web", "api", "mobile", "ai", "infra", "data"];
|
|
119
|
+
const template = `# Threat Model: ${feature}
|
|
120
|
+
|
|
121
|
+
**Date**: ${new Date().toISOString().slice(0, 10)}
|
|
122
|
+
**Status**: DRAFT
|
|
123
|
+
**Surfaces**: ${surfaceList.join(", ")}
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 1. Asset Inventory
|
|
128
|
+
|
|
129
|
+
| Asset | Sensitivity | Owner |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| _e.g. User session tokens_ | HIGH | |
|
|
132
|
+
| _e.g. PII records_ | CRITICAL | |
|
|
133
|
+
|
|
134
|
+
## 2. Trust Boundaries
|
|
135
|
+
|
|
136
|
+
List every point where the trust level changes (e.g. browser -> API server, API -> DB, service A -> service B).
|
|
137
|
+
|
|
138
|
+
- [ ] Boundary 1:
|
|
139
|
+
- [ ] Boundary 2:
|
|
140
|
+
|
|
141
|
+
## 3. Data Flow Diagram (DFD)
|
|
142
|
+
|
|
143
|
+
Describe Level 0 (context) and Level 1 (process) flows in prose or embed a diagram link.
|
|
144
|
+
|
|
145
|
+
## 4. STRIDE Analysis
|
|
146
|
+
|
|
147
|
+
| Component | Spoofing | Tampering | Repudiation | Info Disclosure | DoS | Elevation of Privilege |
|
|
148
|
+
|---|---|---|---|---|---|---|
|
|
149
|
+
| _component_ | | | | | | |
|
|
150
|
+
|
|
151
|
+
## 5. PASTA Risk Assessment
|
|
152
|
+
|
|
153
|
+
**Stage 1 - Business objectives at risk**:
|
|
154
|
+
|
|
155
|
+
**Stage 2 - Technical scope**:
|
|
156
|
+
|
|
157
|
+
**Stage 3 - Application decomposition** (key entry points, APIs, data stores):
|
|
158
|
+
|
|
159
|
+
**Stage 4 - Threat analysis** (attacker profile, motivation):
|
|
160
|
+
|
|
161
|
+
**Stage 5 - Vulnerability analysis**:
|
|
162
|
+
|
|
163
|
+
**Stage 6 - Attack modeling** (attack trees for top 3 risks):
|
|
164
|
+
|
|
165
|
+
**Stage 7 - Risk and impact analysis**:
|
|
166
|
+
|
|
167
|
+
## 6. MITRE ATT&CK Mapping
|
|
168
|
+
|
|
169
|
+
| Tactic | Technique ID | Technique Name | Applicable? | D3FEND Countermeasure |
|
|
170
|
+
|---|---|---|---|---|
|
|
171
|
+
| Initial Access | T1190 | Exploit Public-Facing Application | | |
|
|
172
|
+
| Credential Access | T1110 | Brute Force | | |
|
|
173
|
+
| Exfiltration | T1041 | Exfiltration Over C2 Channel | | |
|
|
174
|
+
| Collection | T1530 | Data from Cloud Storage | | |
|
|
175
|
+
|
|
176
|
+
## 7. Controls
|
|
177
|
+
|
|
178
|
+
### Preventive
|
|
179
|
+
- [ ]
|
|
180
|
+
|
|
181
|
+
### Detective
|
|
182
|
+
- [ ]
|
|
183
|
+
|
|
184
|
+
### Corrective / Recovery
|
|
185
|
+
- [ ]
|
|
186
|
+
|
|
187
|
+
### Compensating (if primary control is not feasible)
|
|
188
|
+
- [ ]
|
|
189
|
+
|
|
190
|
+
## 8. NIST 800-53 Control Mapping
|
|
191
|
+
|
|
192
|
+
| Control ID | Control Name | Implemented? | Evidence |
|
|
193
|
+
|---|---|---|---|
|
|
194
|
+
| AC-3 | Access Enforcement | | |
|
|
195
|
+
| AU-2 | Event Logging | | |
|
|
196
|
+
| SC-8 | Transmission Confidentiality and Integrity | | |
|
|
197
|
+
| SI-10 | Information Input Validation | | |
|
|
198
|
+
|
|
199
|
+
## 9. Residual Risks
|
|
200
|
+
|
|
201
|
+
| Risk | Likelihood | Impact | Owner | Review Date | Acceptance Rationale |
|
|
202
|
+
|---|---|---|---|---|---|
|
|
203
|
+
| | | | | | |
|
|
204
|
+
|
|
205
|
+
## 10. Security Test Cases (from threat model)
|
|
206
|
+
|
|
207
|
+
| Test ID | Threat | Test Scenario | Expected Result | Status |
|
|
208
|
+
|---|---|---|---|---|
|
|
209
|
+
| TM-001 | | | | PENDING |
|
|
210
|
+
|
|
211
|
+
## 11. Pre-Release Checklist (Section 22E)
|
|
212
|
+
|
|
213
|
+
- [ ] Threat model reviewed by security-designated reviewer
|
|
214
|
+
- [ ] All SAST/SCA/IaC/container scan gates pass
|
|
215
|
+
- [ ] Auth and authorization logic reviewed
|
|
216
|
+
- [ ] Secrets handling reviewed - no hardcoded secrets
|
|
217
|
+
- [ ] Input validation present on all new inputs (server-side confirmed)
|
|
218
|
+
- [ ] Error messages reviewed - no information leakage
|
|
219
|
+
- [ ] Logging confirmed - required events logged, no PII in logs
|
|
220
|
+
- [ ] Security headers verified in staging
|
|
221
|
+
- [ ] Rate limiting confirmed on all new endpoints
|
|
222
|
+
- [ ] CORS configuration reviewed
|
|
223
|
+
- [ ] Dependencies reviewed for new CVEs
|
|
224
|
+
- [ ] Network rules reviewed - no 0.0.0.0/0, all traffic via private paths
|
|
225
|
+
- [ ] IR playbook updated if new attack surface introduced
|
|
226
|
+
- [ ] Compliance requirements addressed and documented
|
|
227
|
+
`;
|
|
228
|
+
return asTextResponse(template);
|
|
229
|
+
});
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// New tool: security.checklist
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
const ChecklistParams = {
|
|
234
|
+
surface: z.enum(["web", "api", "mobile", "ai", "infra", "payments", "all"]).optional()
|
|
235
|
+
.describe("Filter checklist by attack surface. Default: all.")
|
|
236
|
+
};
|
|
237
|
+
const ChecklistSchema = z.object(ChecklistParams);
|
|
238
|
+
const CHECKLIST_ALL = `# Pre-Release Security Checklist
|
|
239
|
+
|
|
240
|
+
Use before every production release. All items must be checked or explicitly risk-accepted.
|
|
241
|
+
|
|
242
|
+
## All Surfaces
|
|
243
|
+
|
|
244
|
+
- [ ] Threat model completed and reviewed by security-designated reviewer
|
|
245
|
+
- [ ] SAST scan results reviewed - all CRITICAL/HIGH findings resolved
|
|
246
|
+
- [ ] SCA scan - no CRITICAL CVEs in dependencies; HIGH CVEs triaged
|
|
247
|
+
- [ ] Secrets scan clean (Trufflehog / Gitleaks)
|
|
248
|
+
- [ ] IaC scan - no HIGH/CRITICAL misconfigurations (Checkov / tfsec)
|
|
249
|
+
- [ ] Container scan - no CRITICAL CVEs with available fix (Trivy / Grype)
|
|
250
|
+
- [ ] Error messages reviewed - no stack traces, schema details, or enum leakage
|
|
251
|
+
- [ ] Logging reviewed - all required events logged; no PII, secrets, or tokens in logs
|
|
252
|
+
- [ ] Dependencies reviewed for new CVEs introduced by this change
|
|
253
|
+
- [ ] SBOM generated for this release artifact
|
|
254
|
+
- [ ] Rollback plan documented and tested
|
|
255
|
+
- [ ] IR playbook updated if a new attack surface was introduced
|
|
256
|
+
|
|
257
|
+
## Web / Frontend
|
|
258
|
+
|
|
259
|
+
- [ ] Content-Security-Policy header present with nonce-based script control (no unsafe-inline)
|
|
260
|
+
- [ ] HSTS header with includeSubDomains and preload
|
|
261
|
+
- [ ] X-Frame-Options: DENY
|
|
262
|
+
- [ ] X-Content-Type-Options: nosniff
|
|
263
|
+
- [ ] Referrer-Policy: strict-origin-when-cross-origin
|
|
264
|
+
- [ ] Permissions-Policy set
|
|
265
|
+
- [ ] No inline JavaScript or inline event handlers
|
|
266
|
+
- [ ] Subresource Integrity (SRI) on any third-party scripts
|
|
267
|
+
- [ ] CSRF protection on all state-changing endpoints
|
|
268
|
+
- [ ] XSS: no dangerouslySetInnerHTML without sanitization
|
|
269
|
+
|
|
270
|
+
## API
|
|
271
|
+
|
|
272
|
+
- [ ] All new endpoints require authentication (JWT RS256/ES256 validated)
|
|
273
|
+
- [ ] Authorization checked server-side for every resource operation (IDOR prevention)
|
|
274
|
+
- [ ] Input validation present on all new inputs - server-side schema validation confirmed
|
|
275
|
+
- [ ] Rate limiting configured on all new endpoints
|
|
276
|
+
- [ ] CORS origin allowlist reviewed (no wildcard on authenticated endpoints)
|
|
277
|
+
- [ ] Request size limits enforced
|
|
278
|
+
- [ ] SSRF protection on any server-side HTTP client (block private IPs, metadata endpoints)
|
|
279
|
+
- [ ] Webhook signatures verified (HMAC-SHA256 + replay protection)
|
|
280
|
+
- [ ] OpenAPI spec updated
|
|
281
|
+
|
|
282
|
+
## Infrastructure / Cloud
|
|
283
|
+
|
|
284
|
+
- [ ] No 0.0.0.0/0 ingress or egress rules in any firewall / security group
|
|
285
|
+
- [ ] All managed services accessed via VPC endpoints / private connectivity
|
|
286
|
+
- [ ] No world-readable storage buckets
|
|
287
|
+
- [ ] Secrets stored in secret manager - not in env files, CI logs, or container images
|
|
288
|
+
- [ ] IAM roles follow least privilege - no wildcard permissions
|
|
289
|
+
- [ ] Network segmentation reviewed (web tier, app tier, data tier isolated)
|
|
290
|
+
- [ ] WAF rules updated if new public endpoints added
|
|
291
|
+
- [ ] Cloud audit logging confirmed for new resources
|
|
292
|
+
|
|
293
|
+
## Mobile
|
|
294
|
+
|
|
295
|
+
- [ ] iOS: NSAllowsArbitraryLoads is false (ATS enforced)
|
|
296
|
+
- [ ] Android: android:debuggable="false" in release build
|
|
297
|
+
- [ ] Android: cleartext traffic disabled (usesCleartextTraffic="false")
|
|
298
|
+
- [ ] Certificate pinning verified for high-value API calls
|
|
299
|
+
- [ ] Sensitive data not stored in shared preferences or external storage
|
|
300
|
+
|
|
301
|
+
## AI / LLM
|
|
302
|
+
|
|
303
|
+
- [ ] All AI inputs sanitized and validated
|
|
304
|
+
- [ ] System prompt structurally separated from user content (no string concatenation)
|
|
305
|
+
- [ ] Indirect prompt injection: retrieved context (RAG, external data) treated as untrusted
|
|
306
|
+
- [ ] Model outputs validated against JSON schema before acting on them
|
|
307
|
+
- [ ] Output PII scan: no SSN, card numbers, tokens in model responses
|
|
308
|
+
- [ ] AI endpoints rate-limited independently from regular API
|
|
309
|
+
- [ ] Model access logging enabled (user, timestamp, token counts)
|
|
310
|
+
- [ ] Red-team test cases executed and results reviewed
|
|
311
|
+
|
|
312
|
+
## Payments (PCI DSS 4.0)
|
|
313
|
+
|
|
314
|
+
- [ ] No card numbers, CVV, or PAN in any log, database, cache, or error message
|
|
315
|
+
- [ ] Stripe / payment processor webhook verified (HMAC-SHA256)
|
|
316
|
+
- [ ] PCI scope clearly defined and documented
|
|
317
|
+
- [ ] Payment-adjacent systems network-segmented from non-payment systems
|
|
318
|
+
- [ ] Audit trail maintained for all payment operations
|
|
319
|
+
`;
|
|
320
|
+
tool("security.checklist", "Return the pre-release security checklist, optionally filtered by attack surface (web, api, mobile, ai, infra, payments, all).", ChecklistParams, async (args, _extra) => {
|
|
321
|
+
const { surface } = ChecklistSchema.parse(args);
|
|
322
|
+
if (!surface || surface === "all") {
|
|
323
|
+
return asTextResponse(CHECKLIST_ALL);
|
|
324
|
+
}
|
|
325
|
+
// Extract the relevant section
|
|
326
|
+
const sectionMap = {
|
|
327
|
+
web: "## Web / Frontend",
|
|
328
|
+
api: "## API",
|
|
329
|
+
infra: "## Infrastructure / Cloud",
|
|
330
|
+
mobile: "## Mobile",
|
|
331
|
+
ai: "## AI / LLM",
|
|
332
|
+
payments: "## Payments (PCI DSS 4.0)"
|
|
333
|
+
};
|
|
334
|
+
const header = sectionMap[surface];
|
|
335
|
+
const lines = CHECKLIST_ALL.split("\n");
|
|
336
|
+
const start = lines.findIndex((l) => l === header);
|
|
337
|
+
if (start === -1) {
|
|
338
|
+
return asTextResponse(CHECKLIST_ALL);
|
|
339
|
+
}
|
|
340
|
+
// Include "All Surfaces" section + the requested section
|
|
341
|
+
const allSurfacesEnd = lines.findIndex((l, i) => i > 0 && l.startsWith("## ") && l !== "## All Surfaces");
|
|
342
|
+
const allSurfaces = lines.slice(0, allSurfacesEnd).join("\n");
|
|
343
|
+
const sectionEnd = lines.findIndex((l, i) => i > start + 1 && l.startsWith("## "));
|
|
344
|
+
const section = lines.slice(start, sectionEnd === -1 ? undefined : sectionEnd).join("\n");
|
|
345
|
+
return asTextResponse(`# Pre-Release Security Checklist (${surface})\n\n${allSurfaces}\n\n${section}`);
|
|
346
|
+
});
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// New tool: security.generate_policy
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
const GeneratePolicyParams = {
|
|
351
|
+
surfaces: z.array(z.enum(["web", "api", "mobile", "ai", "infra"])).optional().describe("Active surfaces in your project. Determines which gate requirements are included."),
|
|
352
|
+
cloud: z.enum(["gcp", "aws", "azure", "multi", "none"]).optional()
|
|
353
|
+
.describe("Primary cloud provider. Adjusts cloud-specific evidence expectations.")
|
|
354
|
+
};
|
|
355
|
+
const GeneratePolicySchema = z.object(GeneratePolicyParams);
|
|
356
|
+
tool("security.generate_policy", "Generate a security-policy.json for your project based on your active surfaces and cloud provider. Save the output to .mcp/policies/security-policy.json.", GeneratePolicyParams, async (args, _extra) => {
|
|
357
|
+
const { surfaces, cloud } = GeneratePolicySchema.parse(args);
|
|
358
|
+
const activeSurfaces = surfaces ?? ["web", "api", "infra"];
|
|
359
|
+
const requirements = [
|
|
360
|
+
{ id: "ZERO_TRUST", type: "gate", evidence: ["deny_by_default_authz", "service_to_service_auth"] },
|
|
361
|
+
{ id: "SECRET_MANAGER_ONLY", type: "gate", evidence: ["no_hardcoded_secrets", "secret_manager_refs"] },
|
|
362
|
+
{ id: "TLS_13", type: "gate", evidence: ["tls_config_verified"] }
|
|
363
|
+
];
|
|
364
|
+
if (activeSurfaces.includes("web") || activeSurfaces.includes("api")) {
|
|
365
|
+
requirements.push({ id: "CSP_NO_INLINE", type: "gate", evidence: ["security_headers_present"] });
|
|
366
|
+
requirements.push({ id: "CSRF", type: "gate", evidence: ["csrf_protection_present", "csrf_tests_present"] });
|
|
367
|
+
requirements.push({ id: "SSRF", type: "gate", evidence: ["ssrf_guard_present", "ssrf_tests_present"] });
|
|
368
|
+
}
|
|
369
|
+
if (activeSurfaces.includes("mobile")) {
|
|
370
|
+
requirements.push({
|
|
371
|
+
id: "MOBILE_MASVS",
|
|
372
|
+
type: "gate",
|
|
373
|
+
evidence: ["ios_ats_strict", "android_nsc_strict", "release_not_debuggable"]
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
if (activeSurfaces.includes("ai")) {
|
|
377
|
+
requirements.push({
|
|
378
|
+
id: "AI_BOUNDED_OUTPUTS",
|
|
379
|
+
type: "gate",
|
|
380
|
+
evidence: ["json_schema_validation", "tool_allowlist_router"]
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
const onChanges = ["src/**", "api/**"];
|
|
384
|
+
if (activeSurfaces.includes("infra"))
|
|
385
|
+
onChanges.push("infra/**", "terraform/**", "k8s/**");
|
|
386
|
+
if (activeSurfaces.includes("mobile"))
|
|
387
|
+
onChanges.push("ios/**", "android/**");
|
|
388
|
+
if (activeSurfaces.includes("ai"))
|
|
389
|
+
onChanges.push("ai/**");
|
|
390
|
+
const policy = {
|
|
391
|
+
name: "security-policy",
|
|
392
|
+
version: "1.0.0",
|
|
393
|
+
required_checks: {
|
|
394
|
+
secrets_scan: { severity_block: ["HIGH", "CRITICAL"] },
|
|
395
|
+
dependency_scan: { severity_block: ["CRITICAL"] },
|
|
396
|
+
sast: { severity_block: ["CRITICAL"] },
|
|
397
|
+
...(activeSurfaces.includes("infra") ? { iac_scan: { severity_block: ["HIGH", "CRITICAL"] } } : {})
|
|
398
|
+
},
|
|
399
|
+
requirements,
|
|
400
|
+
artifacts_required: [
|
|
401
|
+
{
|
|
402
|
+
pattern: "security/threat-models/*.md",
|
|
403
|
+
on_changes: onChanges
|
|
404
|
+
}
|
|
405
|
+
],
|
|
406
|
+
exceptions: {
|
|
407
|
+
require_ticket: true,
|
|
408
|
+
approval_roles: ["SecurityLead", "GRC", "CTO"]
|
|
409
|
+
},
|
|
410
|
+
_meta: {
|
|
411
|
+
generated_by: "security-mcp",
|
|
412
|
+
surfaces: activeSurfaces,
|
|
413
|
+
cloud: cloud ?? "unspecified"
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const comment = "// Save this to .mcp/policies/security-policy.json and customize as needed.\n" +
|
|
417
|
+
"// See https://github.com/AbrahamOO/security-mcp for full documentation.\n\n";
|
|
418
|
+
return asTextResponse(comment + JSON.stringify(policy, null, 2));
|
|
419
|
+
});
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
// MCP Prompts capability
|
|
422
|
+
// ---------------------------------------------------------------------------
|
|
423
|
+
server.prompt("security-engineer", "Activate the security-mcp system prompt. Sets up the model as an elite, threat-informed security engineer applying OWASP, MITRE ATT&CK, NIST 800-53, Zero Trust, PCI DSS, SOC 2, and ISO 27001 to every code and architecture decision.", async () => ({
|
|
424
|
+
messages: [
|
|
425
|
+
{
|
|
426
|
+
role: "user",
|
|
427
|
+
content: {
|
|
428
|
+
type: "text",
|
|
429
|
+
text: SECURITY_PROMPT
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
]
|
|
433
|
+
}));
|
|
434
|
+
server.prompt("threat-model-template", "Generate a blank STRIDE + PASTA + MITRE ATT&CK threat model template for a feature.", { feature: z.string().describe("Name or brief description of the feature to threat-model.") }, async ({ feature }) => ({
|
|
435
|
+
messages: [
|
|
436
|
+
{
|
|
437
|
+
role: "user",
|
|
438
|
+
content: {
|
|
439
|
+
type: "text",
|
|
440
|
+
text: `You are a principal security engineer. Produce a complete, filled-out STRIDE + PASTA + ` +
|
|
441
|
+
`MITRE ATT&CK threat model for the following feature:\n\n**${feature}**\n\n` +
|
|
442
|
+
`Use the Section 22 output format from the security-mcp system prompt: ` +
|
|
443
|
+
`Threat Model, Controls (preventive/detective/corrective), Compliance Mapping, ` +
|
|
444
|
+
`Residual Risks, and a Security Checklist. Be specific and actionable.`
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
]
|
|
448
|
+
}));
|
|
449
|
+
// ---------------------------------------------------------------------------
|
|
450
|
+
// Server startup
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
export async function main() {
|
|
453
|
+
const transport = new StdioServerTransport();
|
|
454
|
+
await server.connect(transport);
|
|
455
|
+
}
|
|
456
|
+
// Only auto-start when this file is the direct entry point (not imported by CLI)
|
|
457
|
+
const isMain = process.argv[1]?.endsWith("server.js") || process.argv[1]?.endsWith("server.ts");
|
|
458
|
+
if (isMain) {
|
|
459
|
+
main().catch((err) => {
|
|
460
|
+
console.error("MCP server crashed:", err);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
});
|
|
463
|
+
}
|
package/dist/repo/fs.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const ROOT = process.cwd();
|
|
4
|
+
export async function readFileSafe(relPath) {
|
|
5
|
+
const p = path.resolve(ROOT, relPath);
|
|
6
|
+
if (!p.startsWith(ROOT))
|
|
7
|
+
throw new Error("Path traversal blocked");
|
|
8
|
+
return await readFile(p, "utf8");
|
|
9
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fg from "fast-glob";
|
|
2
|
+
import { readFileSafe } from "./fs.js";
|
|
3
|
+
const MAX_PREVIEW_LEN = 240;
|
|
4
|
+
function isHit(line, query, re) {
|
|
5
|
+
return re ? re.test(line) : line.includes(query);
|
|
6
|
+
}
|
|
7
|
+
function scanLines(file, lines, opts, re, matches) {
|
|
8
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9
|
+
if (matches.length >= opts.maxMatches)
|
|
10
|
+
return;
|
|
11
|
+
const line = lines[i];
|
|
12
|
+
if (!isHit(line, opts.query, re))
|
|
13
|
+
continue;
|
|
14
|
+
matches.push({
|
|
15
|
+
file,
|
|
16
|
+
line: i + 1,
|
|
17
|
+
preview: line.slice(0, MAX_PREVIEW_LEN)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function searchRepo(opts) {
|
|
22
|
+
const files = await fg(["**/*.*"], {
|
|
23
|
+
dot: true,
|
|
24
|
+
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"]
|
|
25
|
+
});
|
|
26
|
+
const re = opts.isRegex ? new RegExp(opts.query, "i") : null;
|
|
27
|
+
const matches = [];
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
if (matches.length >= opts.maxMatches)
|
|
30
|
+
break;
|
|
31
|
+
let text = "";
|
|
32
|
+
try {
|
|
33
|
+
text = await readFileSafe(file);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
scanLines(file, text.split("\n"), opts, re, matches);
|
|
39
|
+
}
|
|
40
|
+
return matches;
|
|
41
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "security-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI security MCP server and enforcement gate for Claude Code, Cursor, GitHub Copilot, Codex, Replit, and any MCP-compatible editor. Applies OWASP, MITRE ATT&CK, NIST, Zero Trust, PCI DSS, SOC 2, and ISO 27001.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "security-mcp contributors",
|
|
8
|
+
"homepage": "https://github.com/AbrahamOO/security-mcp#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/AbrahamOO/security-mcp.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/AbrahamOO/security-mcp/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"security",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"cursor",
|
|
21
|
+
"copilot",
|
|
22
|
+
"codex",
|
|
23
|
+
"replit",
|
|
24
|
+
"owasp",
|
|
25
|
+
"devsecops",
|
|
26
|
+
"security-gate",
|
|
27
|
+
"ai-security",
|
|
28
|
+
"threat-model",
|
|
29
|
+
"zero-trust",
|
|
30
|
+
"nist",
|
|
31
|
+
"mitre",
|
|
32
|
+
"mitre-attack",
|
|
33
|
+
"sast",
|
|
34
|
+
"supply-chain",
|
|
35
|
+
"pci-dss",
|
|
36
|
+
"soc2",
|
|
37
|
+
"iso27001",
|
|
38
|
+
"security-review",
|
|
39
|
+
"code-review",
|
|
40
|
+
"llm-security",
|
|
41
|
+
"model-context-protocol"
|
|
42
|
+
],
|
|
43
|
+
"bin": {
|
|
44
|
+
"security-mcp": "./dist/cli/index.js"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist/",
|
|
48
|
+
"prompts/",
|
|
49
|
+
"skills/",
|
|
50
|
+
"defaults/",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc -p tsconfig.json",
|
|
56
|
+
"prepublishOnly": "npm run build",
|
|
57
|
+
"start": "node dist/cli/index.js serve",
|
|
58
|
+
"mcp:server": "node dist/mcp/server.js",
|
|
59
|
+
"ci:pr-gate": "node dist/ci/pr-gate.js"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
63
|
+
"execa": "^9.5.2",
|
|
64
|
+
"fast-glob": "^3.3.3",
|
|
65
|
+
"picomatch": "^3.0.1",
|
|
66
|
+
"zod": "^3.24.1"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/node": "^22.13.5",
|
|
70
|
+
"@types/picomatch": "^2.3.4",
|
|
71
|
+
"typescript": "^5.7.3"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=20"
|
|
75
|
+
}
|
|
76
|
+
}
|