security-mcp 1.0.5 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +963 -193
- package/defaults/agent-run-schema.json +98 -0
- 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 +392 -0
- package/defaults/evidence-map.json +194 -0
- package/defaults/security-policy.json +41 -2
- package/dist/cli/index.js +13 -8
- package/dist/cli/install.js +80 -2
- package/dist/cli/onboarding.js +590 -0
- package/dist/cli/update.js +83 -15
- package/dist/gate/baseline.js +115 -0
- package/dist/gate/checks/ai-redteam.js +398 -0
- 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 +126 -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 +316 -0
- package/dist/gate/checks/sbom.js +199 -0
- package/dist/gate/checks/scanners.js +379 -8
- package/dist/gate/checks/secrets.js +85 -20
- package/dist/gate/exceptions.js +6 -1
- package/dist/gate/policy.js +85 -19
- package/dist/gate/threat-intel.js +157 -0
- package/dist/mcp/orchestration.js +586 -0
- package/dist/mcp/server.js +568 -16
- package/dist/repo/search.js +11 -1
- package/dist/review/store.js +133 -0
- package/dist/types/agent-run.js +8 -0
- package/package.json +5 -5
- package/prompts/SECURITY_PROMPT.md +415 -1
- package/skills/agentic-loop-exploiter/SKILL.md +69 -0
- package/skills/ai-llm-redteam/SKILL.md +118 -0
- package/skills/algorithm-implementation-reviewer/SKILL.md +85 -0
- package/skills/android-penetration-tester/SKILL.md +83 -0
- package/skills/appsec-code-auditor/SKILL.md +86 -0
- package/skills/artifact-integrity-analyst/SKILL.md +68 -0
- package/skills/attack-navigator/SKILL.md +64 -0
- package/skills/auth-session-hacker/SKILL.md +87 -0
- package/skills/aws-penetration-tester/SKILL.md +60 -0
- package/skills/azure-penetration-tester/SKILL.md +64 -0
- package/skills/business-logic-attacker/SKILL.md +76 -0
- package/skills/cicd-pipeline-hijacker/SKILL.md +81 -0
- package/skills/ciso-orchestrator/SKILL.md +165 -0
- package/skills/cloud-infra-specialist/SKILL.md +85 -0
- package/skills/compliance-gap-analyst/SKILL.md +77 -0
- package/skills/compliance-grc/SKILL.md +148 -0
- package/skills/crypto-pki-specialist/SKILL.md +136 -0
- package/skills/dependency-confusion-attacker/SKILL.md +78 -0
- package/skills/evidence-collector/SKILL.md +86 -0
- package/skills/gcp-penetration-tester/SKILL.md +63 -0
- package/skills/injection-specialist/SKILL.md +62 -0
- package/skills/ios-security-auditor/SKILL.md +77 -0
- package/skills/k8s-container-escaper/SKILL.md +74 -0
- package/skills/key-management-lifecycle-analyst/SKILL.md +92 -0
- package/skills/logic-race-fuzzer/SKILL.md +67 -0
- package/skills/mobile-api-network-attacker/SKILL.md +81 -0
- package/skills/mobile-security-specialist/SKILL.md +124 -0
- package/skills/model-extraction-attacker/SKILL.md +68 -0
- package/skills/pentest-infra/SKILL.md +69 -0
- package/skills/pentest-social/SKILL.md +72 -0
- package/skills/pentest-team/SKILL.md +126 -0
- package/skills/pentest-web-api/SKILL.md +71 -0
- package/skills/privacy-flow-analyst/SKILL.md +70 -0
- package/skills/prompt-injection-specialist/SKILL.md +76 -0
- package/skills/rag-poisoning-specialist/SKILL.md +71 -0
- package/skills/senior-security-engineer/SKILL.md +75 -13
- package/skills/serialization-memory-attacker/SKILL.md +78 -0
- package/skills/stride-pasta-analyst/SKILL.md +72 -0
- package/skills/supply-chain-devsecops/SKILL.md +82 -0
- package/skills/threat-modeler/SKILL.md +116 -0
- package/skills/tls-certificate-auditor/SKILL.md +76 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* security-mcp interactive onboarding
|
|
3
|
+
*
|
|
4
|
+
* Asks plain-English questions about the project, explains what each
|
|
5
|
+
* security tool does, and installs gitleaks / semgrep / osv-scanner /
|
|
6
|
+
* trivy / syft using every available method before giving up.
|
|
7
|
+
*
|
|
8
|
+
* Install priority per platform:
|
|
9
|
+
* macOS → brew → pip (semgrep) → go install → official script → GitHub binary
|
|
10
|
+
* Linux → apt/dnf/yum → pip → go install → official script → GitHub binary
|
|
11
|
+
* Windows → winget → choco → scoop → pip → go install → manual link
|
|
12
|
+
*/
|
|
13
|
+
import { createInterface } from "node:readline/promises";
|
|
14
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { platform, arch, homedir } from "node:os";
|
|
17
|
+
import { mkdirSync, createWriteStream, chmodSync, existsSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { pipeline } from "node:stream/promises";
|
|
20
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
21
|
+
const PROJECT_TYPES = [
|
|
22
|
+
{
|
|
23
|
+
key: "1",
|
|
24
|
+
label: "Web application",
|
|
25
|
+
examples: "React, Next.js, Vue, Angular, SvelteKit, plain HTML/CSS",
|
|
26
|
+
value: "web"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: "2",
|
|
30
|
+
label: "API or backend service",
|
|
31
|
+
examples: "REST API, GraphQL, gRPC, Node.js, Python Flask/FastAPI, Go, Java Spring",
|
|
32
|
+
value: "api"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "3",
|
|
36
|
+
label: "Mobile app",
|
|
37
|
+
examples: "iOS (Swift/Objective-C), Android (Kotlin/Java), React Native, Flutter, Expo",
|
|
38
|
+
value: "mobile"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "4",
|
|
42
|
+
label: "AI / machine learning",
|
|
43
|
+
examples: "LLM integrations, OpenAI/Anthropic API, embeddings, RAG pipelines, AI agents",
|
|
44
|
+
value: "ai"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "5",
|
|
48
|
+
label: "Infrastructure / cloud",
|
|
49
|
+
examples: "Terraform, Kubernetes, Docker, AWS CDK, Helm charts, Ansible, Pulumi",
|
|
50
|
+
value: "infra"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "6",
|
|
54
|
+
label: "A mix — all of the above",
|
|
55
|
+
examples: "full-stack product, platform team, monorepo with multiple surfaces",
|
|
56
|
+
value: "all"
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
const CI_PLATFORMS = [
|
|
60
|
+
{ key: "1", label: "GitHub Actions", examples: ".github/workflows/*.yml", value: "github-actions" },
|
|
61
|
+
{ key: "2", label: "GitLab CI", examples: ".gitlab-ci.yml", value: "gitlab-ci" },
|
|
62
|
+
{ key: "3", label: "CircleCI", examples: ".circleci/config.yml", value: "circleci" },
|
|
63
|
+
{ key: "4", label: "Jenkins", examples: "Jenkinsfile", value: "jenkins" },
|
|
64
|
+
{ key: "5", label: "Bitbucket Pipelines", examples: "bitbucket-pipelines.yml", value: "bitbucket" },
|
|
65
|
+
{ key: "6", label: "AWS CodePipeline", examples: "CodeBuild, CodeDeploy", value: "aws-codepipeline" },
|
|
66
|
+
{ key: "7", label: "Azure DevOps", examples: "azure-pipelines.yml", value: "azure-devops" },
|
|
67
|
+
{ key: "8", label: "Not sure yet / Other", examples: "TeamCity, Drone, Buildkite, etc.", value: "other" }
|
|
68
|
+
];
|
|
69
|
+
const SENSITIVE_DATA_OPTIONS = [
|
|
70
|
+
{
|
|
71
|
+
key: "1",
|
|
72
|
+
label: "Payment card data",
|
|
73
|
+
examples: "credit/debit cards, billing, Stripe, PayPal — PCI DSS 4.0 applies",
|
|
74
|
+
value: "payments"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: "2",
|
|
78
|
+
label: "Health or medical data",
|
|
79
|
+
examples: "patient records, lab results, prescriptions, mental health — HIPAA applies",
|
|
80
|
+
value: "hipaa"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: "3",
|
|
84
|
+
label: "Personal user data",
|
|
85
|
+
examples: "names, emails, addresses, IP addresses, login history — GDPR / CCPA apply",
|
|
86
|
+
value: "gdpr"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: "4",
|
|
90
|
+
label: "None of the above",
|
|
91
|
+
examples: "internal tooling, open-source, no PII stored",
|
|
92
|
+
value: "none"
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
export const SECURITY_TOOLS = [
|
|
96
|
+
{
|
|
97
|
+
id: "gitleaks",
|
|
98
|
+
displayName: "Gitleaks",
|
|
99
|
+
what_it_does: "Scans your code and git history for accidentally committed passwords, API keys, and tokens",
|
|
100
|
+
github: "gitleaks/gitleaks",
|
|
101
|
+
assetPatterns: {
|
|
102
|
+
macos_x64: "darwin_x64",
|
|
103
|
+
macos_arm64: "darwin_arm64",
|
|
104
|
+
linux_x64: "linux_x64",
|
|
105
|
+
linux_arm64: "linux_arm64",
|
|
106
|
+
windows_x64: "windows_x64"
|
|
107
|
+
},
|
|
108
|
+
tarball: true,
|
|
109
|
+
brew: "gitleaks",
|
|
110
|
+
goInstall: "github.com/gitleaks/gitleaks/v8@latest",
|
|
111
|
+
winget: "Gitleaks.Gitleaks",
|
|
112
|
+
choco: "gitleaks",
|
|
113
|
+
manual_url: "https://github.com/gitleaks/gitleaks#installation"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "semgrep",
|
|
117
|
+
displayName: "Semgrep",
|
|
118
|
+
what_it_does: "Analyzes your source code for security bugs like SQL injection, XSS, and broken authentication",
|
|
119
|
+
brew: "semgrep",
|
|
120
|
+
pip: "semgrep",
|
|
121
|
+
winget: "Semgrep.Semgrep",
|
|
122
|
+
choco: "semgrep",
|
|
123
|
+
manual_url: "https://semgrep.dev/docs/getting-started"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "osv-scanner",
|
|
127
|
+
displayName: "OSV-Scanner",
|
|
128
|
+
what_it_does: "Checks every library your project depends on against Google's open-source vulnerability database",
|
|
129
|
+
github: "google/osv-scanner",
|
|
130
|
+
assetPatterns: {
|
|
131
|
+
macos_x64: "darwin_amd64",
|
|
132
|
+
macos_arm64: "darwin_arm64",
|
|
133
|
+
linux_x64: "linux_amd64",
|
|
134
|
+
linux_arm64: "linux_arm64",
|
|
135
|
+
windows_x64: "windows_amd64"
|
|
136
|
+
},
|
|
137
|
+
tarball: false,
|
|
138
|
+
brew: "osv-scanner",
|
|
139
|
+
goInstall: "github.com/google/osv-scanner/cmd/osv-scanner@latest",
|
|
140
|
+
winget: "Google.OSVScanner",
|
|
141
|
+
choco: "osv-scanner",
|
|
142
|
+
manual_url: "https://google.github.io/osv-scanner/installation"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "trivy",
|
|
146
|
+
displayName: "Trivy",
|
|
147
|
+
what_it_does: "Scans Docker containers, Kubernetes manifests, and cloud infrastructure configs for misconfigurations",
|
|
148
|
+
github: "aquasecurity/trivy",
|
|
149
|
+
assetPatterns: {
|
|
150
|
+
macos_x64: "macOS-64bit",
|
|
151
|
+
macos_arm64: "macOS-ARM64",
|
|
152
|
+
linux_x64: "Linux-64bit",
|
|
153
|
+
linux_arm64: "Linux-ARM64",
|
|
154
|
+
windows_x64: "windows-64bit"
|
|
155
|
+
},
|
|
156
|
+
tarball: true,
|
|
157
|
+
brew: "trivy",
|
|
158
|
+
apt: "trivy",
|
|
159
|
+
installScript: "curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin",
|
|
160
|
+
winget: "AquaSecurity.Trivy",
|
|
161
|
+
choco: "trivy",
|
|
162
|
+
manual_url: "https://aquasecurity.github.io/trivy/latest/getting-started/installation"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "syft",
|
|
166
|
+
displayName: "Syft",
|
|
167
|
+
what_it_does: "Creates a software bill of materials — a complete inventory of every library inside your application",
|
|
168
|
+
github: "anchore/syft",
|
|
169
|
+
assetPatterns: {
|
|
170
|
+
macos_x64: "darwin_amd64",
|
|
171
|
+
macos_arm64: "darwin_arm64",
|
|
172
|
+
linux_x64: "linux_amd64",
|
|
173
|
+
linux_arm64: "linux_arm64",
|
|
174
|
+
windows_x64: "windows_amd64"
|
|
175
|
+
},
|
|
176
|
+
tarball: true,
|
|
177
|
+
brew: "syft",
|
|
178
|
+
installScript: "curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin",
|
|
179
|
+
winget: "Anchore.Syft",
|
|
180
|
+
choco: "syft",
|
|
181
|
+
scoop: "syft",
|
|
182
|
+
manual_url: "https://github.com/anchore/syft#installation"
|
|
183
|
+
}
|
|
184
|
+
];
|
|
185
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
186
|
+
function print(msg = "") {
|
|
187
|
+
process.stdout.write(msg + "\n");
|
|
188
|
+
}
|
|
189
|
+
function hr() {
|
|
190
|
+
print("─".repeat(56));
|
|
191
|
+
}
|
|
192
|
+
function getOsType() {
|
|
193
|
+
const p = platform();
|
|
194
|
+
if (p === "darwin")
|
|
195
|
+
return "macos";
|
|
196
|
+
if (p === "linux")
|
|
197
|
+
return "linux";
|
|
198
|
+
return "windows";
|
|
199
|
+
}
|
|
200
|
+
/** Returns 'x64' or 'arm64' */
|
|
201
|
+
function getCpuArch() {
|
|
202
|
+
const a = arch();
|
|
203
|
+
return a === "arm64" || a === "arm" ? "arm64" : "x64";
|
|
204
|
+
}
|
|
205
|
+
export function commandExists(cmd) {
|
|
206
|
+
try {
|
|
207
|
+
// Use spawnSync (not execSync) to avoid shell injection — cmd is never interpolated into a shell string
|
|
208
|
+
if (process.platform === "win32") {
|
|
209
|
+
return spawnSync("where", [cmd], { stdio: "pipe" }).status === 0;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
return spawnSync("which", [cmd], { stdio: "pipe" }).status === 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function run(cmd, args) {
|
|
220
|
+
const result = spawnSync(cmd, args, { stdio: "inherit" });
|
|
221
|
+
return result.status === 0;
|
|
222
|
+
}
|
|
223
|
+
// ─── GitHub binary download ───────────────────────────────────────────────────
|
|
224
|
+
async function fetchLatestRelease(repo) {
|
|
225
|
+
try {
|
|
226
|
+
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
|
|
227
|
+
headers: {
|
|
228
|
+
Accept: "application/vnd.github.v3+json",
|
|
229
|
+
"User-Agent": "security-mcp-installer/1.0"
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
if (!res.ok)
|
|
233
|
+
return null;
|
|
234
|
+
return (await res.json());
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function pickAsset(assets, pattern) {
|
|
241
|
+
return assets.find((a) => a.name.toLowerCase().includes(pattern.toLowerCase()))?.browser_download_url;
|
|
242
|
+
}
|
|
243
|
+
async function downloadBinary(url, dest) {
|
|
244
|
+
try {
|
|
245
|
+
const res = await fetch(url);
|
|
246
|
+
if (!res.ok || !res.body)
|
|
247
|
+
return false;
|
|
248
|
+
const ws = createWriteStream(dest);
|
|
249
|
+
await pipeline(res.body, ws);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function installFromGitHub(tool, os) {
|
|
257
|
+
if (!tool.github || !tool.assetPatterns)
|
|
258
|
+
return false;
|
|
259
|
+
const cpuArch = getCpuArch();
|
|
260
|
+
const patternKey = `${os}_${cpuArch}`;
|
|
261
|
+
const pattern = tool.assetPatterns[patternKey];
|
|
262
|
+
if (!pattern)
|
|
263
|
+
return false;
|
|
264
|
+
print(` Fetching latest ${tool.displayName} release from GitHub...`);
|
|
265
|
+
const release = await fetchLatestRelease(tool.github);
|
|
266
|
+
if (!release) {
|
|
267
|
+
print(` Could not reach GitHub API. Check your internet connection.`);
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
const downloadUrl = pickAsset(release.assets, pattern);
|
|
271
|
+
if (!downloadUrl) {
|
|
272
|
+
print(` No matching binary found for ${os}/${cpuArch} in ${release.tag_name}.`);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
const tmpDir = join(homedir(), ".cache", "security-mcp-install");
|
|
276
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
277
|
+
const fileName = downloadUrl.split("/").pop() ?? `${tool.id}-download`;
|
|
278
|
+
const tmpFile = join(tmpDir, fileName);
|
|
279
|
+
print(` Downloading ${fileName}...`);
|
|
280
|
+
const downloaded = await downloadBinary(downloadUrl, tmpFile);
|
|
281
|
+
if (!downloaded) {
|
|
282
|
+
print(` Download failed.`);
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const destDir = "/usr/local/bin";
|
|
286
|
+
if (tool.tarball) {
|
|
287
|
+
// Extract the binary from the archive
|
|
288
|
+
const extracted = run("tar", ["xzf", tmpFile, "-C", tmpDir, tool.id]);
|
|
289
|
+
if (!extracted)
|
|
290
|
+
return false;
|
|
291
|
+
const binSrc = join(tmpDir, tool.id);
|
|
292
|
+
if (!existsSync(binSrc))
|
|
293
|
+
return false;
|
|
294
|
+
chmodSync(binSrc, 0o755);
|
|
295
|
+
// Try with sudo if we can't write directly
|
|
296
|
+
return (run("mv", [binSrc, join(destDir, tool.id)]) ||
|
|
297
|
+
run("sudo", ["mv", binSrc, join(destDir, tool.id)]));
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// Plain executable
|
|
301
|
+
chmodSync(tmpFile, 0o755);
|
|
302
|
+
return (run("mv", [tmpFile, join(destDir, tool.id)]) ||
|
|
303
|
+
run("sudo", ["mv", tmpFile, join(destDir, tool.id)]));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// ─── Per-platform install strategies ─────────────────────────────────────────
|
|
307
|
+
async function tryBrew(tool) {
|
|
308
|
+
if (!tool.brew || !commandExists("brew"))
|
|
309
|
+
return false;
|
|
310
|
+
print(` brew install ${tool.brew}`);
|
|
311
|
+
return run("brew", ["install", tool.brew]);
|
|
312
|
+
}
|
|
313
|
+
async function tryPip(tool) {
|
|
314
|
+
if (!tool.pip)
|
|
315
|
+
return false;
|
|
316
|
+
const pip = commandExists("pip3") ? "pip3" : commandExists("pip") ? "pip" : null;
|
|
317
|
+
if (!pip)
|
|
318
|
+
return false;
|
|
319
|
+
print(` ${pip} install ${tool.pip}`);
|
|
320
|
+
return run(pip, ["install", "--user", tool.pip]);
|
|
321
|
+
}
|
|
322
|
+
async function tryGoInstall(tool) {
|
|
323
|
+
if (!tool.goInstall || !commandExists("go"))
|
|
324
|
+
return false;
|
|
325
|
+
print(` go install ${tool.goInstall}`);
|
|
326
|
+
return run("go", ["install", tool.goInstall]);
|
|
327
|
+
}
|
|
328
|
+
async function tryApt(tool) {
|
|
329
|
+
if (!tool.apt || !commandExists("apt-get"))
|
|
330
|
+
return false;
|
|
331
|
+
// For trivy: add Aqua Security apt repo first
|
|
332
|
+
if (tool.id === "trivy") {
|
|
333
|
+
print(` Setting up Aqua Security apt repository for Trivy...`);
|
|
334
|
+
const setup = "sudo apt-get install -y wget gnupg lsb-release && " +
|
|
335
|
+
"wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - && " +
|
|
336
|
+
'echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" ' +
|
|
337
|
+
"| sudo tee /etc/apt/sources.list.d/trivy.list && " +
|
|
338
|
+
"sudo apt-get update -qq";
|
|
339
|
+
run("bash", ["-c", setup]);
|
|
340
|
+
}
|
|
341
|
+
print(` sudo apt-get install -y ${tool.apt}`);
|
|
342
|
+
return run("sudo", ["apt-get", "install", "-y", tool.apt]);
|
|
343
|
+
}
|
|
344
|
+
async function tryDnf(tool) {
|
|
345
|
+
if (tool.id !== "trivy")
|
|
346
|
+
return false;
|
|
347
|
+
const mgr = commandExists("dnf") ? "dnf" : commandExists("yum") ? "yum" : null;
|
|
348
|
+
if (!mgr)
|
|
349
|
+
return false;
|
|
350
|
+
// Add Aqua Security rpm repo
|
|
351
|
+
const repoContent = "[trivy]\\nname=Trivy repository\\n" +
|
|
352
|
+
"baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$releasever/$basearch/\\n" +
|
|
353
|
+
"gpgcheck=0\\nenabled=1";
|
|
354
|
+
print(` Adding Aqua Security yum/dnf repository...`);
|
|
355
|
+
run("bash", ["-c", `echo -e "${repoContent}" | sudo tee /etc/yum.repos.d/trivy.repo`]);
|
|
356
|
+
print(` sudo ${mgr} install -y trivy`);
|
|
357
|
+
return run("sudo", [mgr, "install", "-y", "trivy"]);
|
|
358
|
+
}
|
|
359
|
+
async function tryInstallScript(tool) {
|
|
360
|
+
if (!tool.installScript || !commandExists("curl") || !commandExists("sh"))
|
|
361
|
+
return false;
|
|
362
|
+
print(` Running official install script for ${tool.displayName}...`);
|
|
363
|
+
return run("bash", ["-c", tool.installScript]);
|
|
364
|
+
}
|
|
365
|
+
async function tryWinget(tool) {
|
|
366
|
+
if (!tool.winget || !commandExists("winget"))
|
|
367
|
+
return false;
|
|
368
|
+
print(` winget install --id ${tool.winget}`);
|
|
369
|
+
return run("winget", ["install", "--id", tool.winget, "--silent", "--accept-source-agreements"]);
|
|
370
|
+
}
|
|
371
|
+
async function tryChoco(tool) {
|
|
372
|
+
if (!tool.choco || !commandExists("choco"))
|
|
373
|
+
return false;
|
|
374
|
+
print(` choco install ${tool.choco} -y`);
|
|
375
|
+
return run("choco", ["install", tool.choco, "-y"]);
|
|
376
|
+
}
|
|
377
|
+
async function tryScoop(tool) {
|
|
378
|
+
if (!tool.scoop || !commandExists("scoop"))
|
|
379
|
+
return false;
|
|
380
|
+
print(` scoop install ${tool.scoop}`);
|
|
381
|
+
return run("scoop", ["install", tool.scoop]);
|
|
382
|
+
}
|
|
383
|
+
// ─── Orchestrator: try everything, stop on first success ─────────────────────
|
|
384
|
+
async function installSingleTool(tool, os) {
|
|
385
|
+
print(`\n Installing ${tool.displayName}...`);
|
|
386
|
+
const strategies = [];
|
|
387
|
+
if (os === "macos") {
|
|
388
|
+
strategies.push(() => tryBrew(tool), () => tryPip(tool), () => tryGoInstall(tool), () => tryInstallScript(tool), () => installFromGitHub(tool, os));
|
|
389
|
+
}
|
|
390
|
+
else if (os === "linux") {
|
|
391
|
+
strategies.push(() => tryApt(tool), () => tryDnf(tool), () => tryPip(tool), () => tryGoInstall(tool), () => tryInstallScript(tool), () => installFromGitHub(tool, os));
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// Windows
|
|
395
|
+
strategies.push(() => tryWinget(tool), () => tryChoco(tool), () => tryScoop(tool), () => tryPip(tool), () => tryGoInstall(tool));
|
|
396
|
+
}
|
|
397
|
+
for (const strategy of strategies) {
|
|
398
|
+
try {
|
|
399
|
+
const ok = await strategy();
|
|
400
|
+
if (ok && commandExists(tool.id)) {
|
|
401
|
+
print(` ✓ ${tool.displayName} installed successfully`);
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// try next method
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
print(` ✗ Could not install ${tool.displayName} automatically.`);
|
|
410
|
+
print(` Manual install: ${tool.manual_url}`);
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
export async function installSecurityTools(tools) {
|
|
414
|
+
if (tools.length === 0) {
|
|
415
|
+
print(" All tools are already installed.");
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const os = getOsType();
|
|
419
|
+
if (os === "windows" && !commandExists("winget") && !commandExists("choco") && !commandExists("scoop")) {
|
|
420
|
+
print("\n No package manager found (winget / choco / scoop).");
|
|
421
|
+
print(" Please install the tools manually:\n");
|
|
422
|
+
for (const tool of tools) {
|
|
423
|
+
print(` • ${tool.displayName.padEnd(16)} ${tool.manual_url}`);
|
|
424
|
+
}
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
for (const tool of tools) {
|
|
428
|
+
await installSingleTool(tool, os);
|
|
429
|
+
}
|
|
430
|
+
print("");
|
|
431
|
+
// Final verification
|
|
432
|
+
const stillMissing = tools.filter((t) => !commandExists(t.id));
|
|
433
|
+
if (stillMissing.length > 0) {
|
|
434
|
+
print(" Some tools could not be installed automatically:");
|
|
435
|
+
for (const t of stillMissing) {
|
|
436
|
+
print(` • ${t.displayName.padEnd(14)} ${t.manual_url}`);
|
|
437
|
+
}
|
|
438
|
+
print("");
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// ─── Onboarding wizard ────────────────────────────────────────────────────────
|
|
442
|
+
export async function runOnboarding() {
|
|
443
|
+
if (!process.stdin.isTTY) {
|
|
444
|
+
return null; // CI/piped environment — skip interactive questions
|
|
445
|
+
}
|
|
446
|
+
const rl = createInterface({ input, output });
|
|
447
|
+
const ask = (q) => rl.question(q);
|
|
448
|
+
try {
|
|
449
|
+
print("");
|
|
450
|
+
print("╔════════════════════════════════════════════════════════╗");
|
|
451
|
+
print("║ Welcome to security-mcp Setup ║");
|
|
452
|
+
print("╚════════════════════════════════════════════════════════╝");
|
|
453
|
+
print("");
|
|
454
|
+
print("security-mcp adds AI-powered security scanning to your");
|
|
455
|
+
print("coding workflow. It catches security issues right inside");
|
|
456
|
+
print("your editor — before they ever reach production.");
|
|
457
|
+
print("");
|
|
458
|
+
print("Answer 3 quick questions to tailor the setup to your project.");
|
|
459
|
+
print("(Press Ctrl+C at any time to skip and use defaults.)");
|
|
460
|
+
print("");
|
|
461
|
+
hr();
|
|
462
|
+
print("");
|
|
463
|
+
// ── Step 1: Project type ─────────────────────────────────────────────────
|
|
464
|
+
print("QUESTION 1 of 3 — What type of project are you building?");
|
|
465
|
+
print("");
|
|
466
|
+
for (const t of PROJECT_TYPES) {
|
|
467
|
+
print(` ${t.key}. ${t.label}`);
|
|
468
|
+
print(` [e.g. ${t.examples}]`);
|
|
469
|
+
print("");
|
|
470
|
+
}
|
|
471
|
+
const typeAnswer = await ask("Enter number(s) separated by spaces (e.g. 1 2): ");
|
|
472
|
+
const selectedTypeKeys = typeAnswer.trim().split(/[\s,]+/).filter(Boolean);
|
|
473
|
+
let projectTypes;
|
|
474
|
+
if (selectedTypeKeys.includes("6")) {
|
|
475
|
+
projectTypes = ["web", "api", "mobile", "ai", "infra"];
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
projectTypes = selectedTypeKeys
|
|
479
|
+
.map((k) => PROJECT_TYPES.find((t) => t.key === k)?.value)
|
|
480
|
+
.filter((v) => v !== undefined);
|
|
481
|
+
}
|
|
482
|
+
if (projectTypes.length === 0)
|
|
483
|
+
projectTypes = ["web", "api"];
|
|
484
|
+
print("");
|
|
485
|
+
hr();
|
|
486
|
+
print("");
|
|
487
|
+
// ── Step 2: CI/CD ────────────────────────────────────────────────────────
|
|
488
|
+
print("QUESTION 2 of 3 — Does this project use a CI/CD pipeline?");
|
|
489
|
+
print("");
|
|
490
|
+
print(" CI/CD automatically builds, tests, and deploys your code.");
|
|
491
|
+
print(" security-mcp can add a security gate that blocks risky releases.");
|
|
492
|
+
print("");
|
|
493
|
+
const ciAnswer = await ask("Do you use or plan to use CI/CD? (y/n): ");
|
|
494
|
+
const hasCiCd = ciAnswer.trim().toLowerCase().startsWith("y");
|
|
495
|
+
let ciPlatform;
|
|
496
|
+
if (hasCiCd) {
|
|
497
|
+
print("");
|
|
498
|
+
print(" Which CI/CD platform?");
|
|
499
|
+
print("");
|
|
500
|
+
for (const p of CI_PLATFORMS) {
|
|
501
|
+
print(` ${p.key}. ${p.label}`);
|
|
502
|
+
print(` [e.g. ${p.examples}]`);
|
|
503
|
+
print("");
|
|
504
|
+
}
|
|
505
|
+
const platformAnswer = await ask(" Enter number: ");
|
|
506
|
+
ciPlatform = CI_PLATFORMS.find((p) => p.key === platformAnswer.trim())?.value ?? "other";
|
|
507
|
+
}
|
|
508
|
+
print("");
|
|
509
|
+
hr();
|
|
510
|
+
print("");
|
|
511
|
+
// ── Step 3: Sensitive data ───────────────────────────────────────────────
|
|
512
|
+
print("QUESTION 3 of 3 — Does your app handle sensitive information?");
|
|
513
|
+
print("");
|
|
514
|
+
print(" This applies the right compliance controls automatically,");
|
|
515
|
+
print(" such as PCI DSS for payment cards or HIPAA for health data.");
|
|
516
|
+
print("");
|
|
517
|
+
for (const d of SENSITIVE_DATA_OPTIONS) {
|
|
518
|
+
print(` ${d.key}. ${d.label}`);
|
|
519
|
+
print(` [e.g. ${d.examples}]`);
|
|
520
|
+
print("");
|
|
521
|
+
}
|
|
522
|
+
const dataAnswer = await ask("Enter number(s) (or 4 for none): ");
|
|
523
|
+
const selectedDataKeys = dataAnswer.trim().split(/[\s,]+/).filter(Boolean);
|
|
524
|
+
let sensitiveData = [];
|
|
525
|
+
if (!selectedDataKeys.includes("4")) {
|
|
526
|
+
sensitiveData = selectedDataKeys
|
|
527
|
+
.map((k) => SENSITIVE_DATA_OPTIONS.find((d) => d.key === k)?.value)
|
|
528
|
+
.filter((v) => v !== undefined && v !== "none");
|
|
529
|
+
}
|
|
530
|
+
print("");
|
|
531
|
+
hr();
|
|
532
|
+
print("");
|
|
533
|
+
// ── Summary ──────────────────────────────────────────────────────────────
|
|
534
|
+
print("Here's what security-mcp will configure for your project:");
|
|
535
|
+
print("");
|
|
536
|
+
print(` ✓ Security policy tailored to: ${projectTypes.join(", ")}`);
|
|
537
|
+
if (sensitiveData.includes("payments"))
|
|
538
|
+
print(" ✓ PCI DSS 4.0 controls and payment-specific checklists");
|
|
539
|
+
if (sensitiveData.includes("hipaa"))
|
|
540
|
+
print(" ✓ HIPAA technical safeguard controls");
|
|
541
|
+
if (sensitiveData.includes("gdpr"))
|
|
542
|
+
print(" ✓ GDPR / CCPA data privacy controls");
|
|
543
|
+
if (hasCiCd)
|
|
544
|
+
print(` ✓ CI/CD security gate for ${ciPlatform ?? "your pipeline"}`);
|
|
545
|
+
print(" ✓ 200+ controls mapped to OWASP, NIST 800-53, SOC 2, MITRE ATT&CK");
|
|
546
|
+
print(" ✓ Pre-release security checklists for your team");
|
|
547
|
+
print("");
|
|
548
|
+
// ── Tool installation prompt ─────────────────────────────────────────────
|
|
549
|
+
const alreadyInstalled = SECURITY_TOOLS.filter((t) => commandExists(t.id));
|
|
550
|
+
const toInstall = SECURITY_TOOLS.filter((t) => !commandExists(t.id));
|
|
551
|
+
if (alreadyInstalled.length > 0) {
|
|
552
|
+
print(` Already installed: ${alreadyInstalled.map((t) => t.displayName).join(", ")}`);
|
|
553
|
+
print("");
|
|
554
|
+
}
|
|
555
|
+
let installTools = false;
|
|
556
|
+
if (toInstall.length > 0) {
|
|
557
|
+
print("The following security scanning tools are not yet on your machine.");
|
|
558
|
+
print("They run 100% locally — your code is never uploaded anywhere.");
|
|
559
|
+
print("");
|
|
560
|
+
for (const tool of toInstall) {
|
|
561
|
+
print(` ${tool.displayName.padEnd(14)} ${tool.what_it_does}`);
|
|
562
|
+
}
|
|
563
|
+
print("");
|
|
564
|
+
const os = getOsType();
|
|
565
|
+
const osNote = os === "macos"
|
|
566
|
+
? "We'll use Homebrew (with multiple fallbacks if needed)."
|
|
567
|
+
: os === "linux"
|
|
568
|
+
? "We'll try apt/dnf, then official install scripts, then GitHub releases."
|
|
569
|
+
: "We'll try winget, chocolatey, and scoop.";
|
|
570
|
+
print(` ${osNote}`);
|
|
571
|
+
print("");
|
|
572
|
+
const toolAnswer = await ask("Install these tools now? (y/n): ");
|
|
573
|
+
installTools = toolAnswer.trim().toLowerCase().startsWith("y");
|
|
574
|
+
}
|
|
575
|
+
print("");
|
|
576
|
+
hr();
|
|
577
|
+
print("");
|
|
578
|
+
return { projectTypes, hasCiCd, ciPlatform, sensitiveData, installTools };
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
const code = err.code;
|
|
582
|
+
if (code !== "ERR_USE_AFTER_CLOSE") {
|
|
583
|
+
print("\n\nSetup skipped — installing with defaults.\n");
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
finally {
|
|
588
|
+
rl.close();
|
|
589
|
+
}
|
|
590
|
+
}
|