security-mcp 1.1.1 → 1.1.2

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.
Files changed (70) hide show
  1. package/README.md +4 -1
  2. package/dist/ci/pr-gate.js +18 -1
  3. package/dist/cli/onboarding.js +78 -7
  4. package/dist/gate/checks/api.js +93 -0
  5. package/dist/gate/checks/ci-pipeline.js +135 -0
  6. package/dist/gate/checks/crypto.js +91 -22
  7. package/dist/gate/checks/database.js +5 -1
  8. package/dist/gate/checks/dependencies.js +297 -2
  9. package/dist/gate/checks/dlp.js +6 -1
  10. package/dist/gate/checks/graphql.js +6 -1
  11. package/dist/gate/checks/k8s.js +229 -181
  12. package/dist/gate/checks/nuclei.js +133 -0
  13. package/dist/gate/checks/runtime.js +32 -18
  14. package/dist/gate/checks/scanners.js +2 -1
  15. package/dist/gate/diff.js +2 -0
  16. package/dist/gate/policy.js +47 -4
  17. package/dist/gate/result.js +7 -1
  18. package/dist/mcp/audit-chain.js +253 -0
  19. package/dist/mcp/learning.js +228 -0
  20. package/dist/mcp/model-router.js +544 -0
  21. package/dist/mcp/orchestration.js +22 -4
  22. package/dist/mcp/server.js +92 -1
  23. package/dist/review/store.js +10 -0
  24. package/package.json +1 -1
  25. package/skills/_TEMPLATE/SKILL.md +99 -0
  26. package/skills/advanced-dos-tester/SKILL.md +225 -0
  27. package/skills/ai-model-supply-chain-agent/SKILL.md +198 -0
  28. package/skills/anti-replay-tester/SKILL.md +195 -0
  29. package/skills/binary-auth-validator/SKILL.md +184 -0
  30. package/skills/bot-detection-specialist/SKILL.md +221 -0
  31. package/skills/capec-code-mapper/SKILL.md +163 -0
  32. package/skills/cert-pin-rotation-specialist/SKILL.md +200 -0
  33. package/skills/compliance-lifecycle-tracker/SKILL.md +169 -0
  34. package/skills/credential-stuffing-specialist/SKILL.md +192 -0
  35. package/skills/csa-ccm-mapper/SKILL.md +178 -0
  36. package/skills/csf2-governance-mapper/SKILL.md +159 -0
  37. package/skills/deep-link-fuzzer/SKILL.md +195 -0
  38. package/skills/device-integrity-aggregator/SKILL.md +221 -0
  39. package/skills/dos-resilience-tester/SKILL.md +184 -0
  40. package/skills/dread-scorer/SKILL.md +157 -0
  41. package/skills/egress-policy-enforcer/SKILL.md +208 -0
  42. package/skills/file-upload-attacker/SKILL.md +208 -0
  43. package/skills/git-history-secret-scanner/SKILL.md +182 -0
  44. package/skills/iam-privesc-graph-builder/SKILL.md +216 -0
  45. package/skills/incident-responder/SKILL.md +192 -0
  46. package/skills/json-ambiguity-tester/SKILL.md +175 -0
  47. package/skills/kill-switch-engineer/SKILL.md +205 -0
  48. package/skills/linddun-privacy-analyst/SKILL.md +196 -0
  49. package/skills/mobile-binary-hardener/SKILL.md +199 -0
  50. package/skills/mobile-webview-auditor/SKILL.md +200 -0
  51. package/skills/multipart-abuse-tester/SKILL.md +146 -0
  52. package/skills/oauth-pkce-specialist/SKILL.md +191 -0
  53. package/skills/parser-exhaustion-tester/SKILL.md +177 -0
  54. package/skills/quantum-migration-planner/SKILL.md +184 -0
  55. package/skills/registry-mirror-enforcer/SKILL.md +142 -0
  56. package/skills/rotation-validation-agent/SKILL.md +188 -0
  57. package/skills/samm-assessor/SKILL.md +168 -0
  58. package/skills/secrets-mask-bypass-tester/SKILL.md +167 -0
  59. package/skills/session-timeout-tester/SKILL.md +197 -0
  60. package/skills/slsa-level3-enforcer/SKILL.md +185 -0
  61. package/skills/slsa-provenance-enforcer/SKILL.md +181 -0
  62. package/skills/ssrf-detection-validator/SKILL.md +229 -0
  63. package/skills/step-up-auth-enforcer/SKILL.md +176 -0
  64. package/skills/threat-infrastructure-analyst/SKILL.md +167 -0
  65. package/skills/token-reuse-detector/SKILL.md +203 -0
  66. package/skills/trike-risk-modeler/SKILL.md +139 -0
  67. package/skills/unicode-homograph-tester/SKILL.md +179 -0
  68. package/skills/waf-rule-lifecycle-agent/SKILL.md +213 -0
  69. package/skills/webhook-security-tester/SKILL.md +184 -0
  70. package/skills/zero-trust-architect/SKILL.md +211 -0
@@ -1,8 +1,12 @@
1
+ import { sanitizeErrorMessage } from "../result.js";
1
2
  import fg from "fast-glob";
2
3
  import { readFileSafe } from "../../repo/fs.js";
3
4
  import { execFile } from "node:child_process";
4
5
  import { promisify } from "node:util";
5
6
  import { readFile } from "node:fs/promises";
7
+ import { checkActiveExploitation } from "../threat-intel.js";
8
+ import { join } from "node:path";
9
+ const THREAT_INTEL_CACHE_DIR = join(process.cwd(), ".mcp", "threat-intel");
6
10
  const execFileAsync = promisify(execFile);
7
11
  // 24-hour cache for OpenSSF Scorecard API responses
8
12
  const scorecardCache = new Map();
@@ -61,7 +65,8 @@ async function checkNpmProvenance() {
61
65
  try {
62
66
  const { stdout } = await execFileAsync("npm", ["audit", "signatures", "--json"], {
63
67
  timeout: 30000,
64
- env: process.env
68
+ // CWE-526: pass only PATH — do not propagate API keys or tokens from parent env.
69
+ env: { PATH: process.env["PATH"] ?? "/usr/local/bin:/usr/bin:/bin" }
65
70
  });
66
71
  if (stdout) {
67
72
  let auditResult;
@@ -120,7 +125,7 @@ async function checkNpmProvenance() {
120
125
  }
121
126
  }
122
127
  catch (err) {
123
- console.warn("[checkNpmProvenance] Internal error:", err instanceof Error ? err.message : String(err));
128
+ console.warn("[checkNpmProvenance] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
124
129
  }
125
130
  return { findings };
126
131
  }
@@ -165,5 +170,295 @@ export async function checkDependencies(_) {
165
170
  }
166
171
  const provenance = await checkNpmProvenance();
167
172
  findings.push(...provenance.findings);
173
+ const transitive = await checkTransitiveDependencies();
174
+ findings.push(...transitive);
175
+ const typosquat = await checkTyposquatting();
176
+ findings.push(...typosquat);
177
+ const threatIntel = await checkCveExploitation();
178
+ findings.push(...threatIntel);
179
+ return findings;
180
+ }
181
+ function extractCveIds(audit) {
182
+ const cveSet = new Set();
183
+ for (const vuln of Object.values(audit.vulnerabilities ?? {})) {
184
+ for (const via of vuln.via ?? []) {
185
+ if (typeof via !== "object" || !Array.isArray(via.cve))
186
+ continue;
187
+ for (const cve of via.cve) {
188
+ if (typeof cve === "string" && cve.startsWith("CVE-"))
189
+ cveSet.add(cve);
190
+ }
191
+ }
192
+ }
193
+ return [...cveSet];
194
+ }
195
+ function buildThreatIntelFindings(intel) {
196
+ const findings = [];
197
+ if (intel.kevMatches.length > 0) {
198
+ findings.push({
199
+ id: "DEP_CVE_ACTIVELY_EXPLOITED",
200
+ title: `${intel.kevMatches.length} dependency CVE(s) are actively exploited (CISA KEV)`,
201
+ severity: "CRITICAL",
202
+ evidence: intel.kevMatches.slice(0, 20),
203
+ requiredActions: [
204
+ "Upgrade or patch these dependencies immediately — these CVEs are in CISA's Known Exploited Vulnerabilities catalog.",
205
+ "Run `npm audit fix` or manually update to a patched version.",
206
+ "If no patch is available, apply mitigating controls and document accepted risk."
207
+ ]
208
+ });
209
+ }
210
+ if (intel.highEpss.length > 0) {
211
+ findings.push({
212
+ id: "DEP_CVE_HIGH_EPSS",
213
+ title: `${intel.highEpss.length} dependency CVE(s) have high exploitation probability (EPSS > 50%)`,
214
+ severity: "HIGH",
215
+ evidence: intel.highEpss.slice(0, 20).map((e) => `${e.cve}: ${(e.score * 100).toFixed(1)}% exploitation probability`),
216
+ requiredActions: [
217
+ "Prioritize patching these CVEs — high EPSS scores indicate active exploitation in the wild.",
218
+ "Run `npm audit fix` or update affected packages.",
219
+ "Monitor for exploit availability and treat as high urgency even without a current patch."
220
+ ]
221
+ });
222
+ }
223
+ return findings;
224
+ }
225
+ async function checkCveExploitation() {
226
+ try {
227
+ let stdout;
228
+ try {
229
+ const result = await execFileAsync("npm", ["audit", "--json"], {
230
+ timeout: 30_000,
231
+ env: { PATH: process.env["PATH"] ?? "/usr/local/bin:/usr/bin:/bin" }
232
+ });
233
+ stdout = result.stdout;
234
+ }
235
+ catch (err) {
236
+ // npm audit exits non-zero when vulnerabilities exist — output is still valid JSON.
237
+ stdout = err?.stdout ?? "";
238
+ }
239
+ if (!stdout)
240
+ return [];
241
+ let audit;
242
+ try {
243
+ audit = JSON.parse(stdout);
244
+ }
245
+ catch {
246
+ return [];
247
+ }
248
+ const cveIds = extractCveIds(audit);
249
+ if (cveIds.length === 0)
250
+ return [];
251
+ const intel = await checkActiveExploitation(cveIds, THREAT_INTEL_CACHE_DIR);
252
+ if (intel.failed)
253
+ return [];
254
+ return buildThreatIntelFindings(intel);
255
+ }
256
+ catch (err) {
257
+ console.warn("[checkCveExploitation] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
258
+ return [];
259
+ }
260
+ }
261
+ const LIFECYCLE_SCRIPTS = ["postinstall", "install", "preinstall"];
262
+ function hasLifecycleScript(pkg) {
263
+ return !!pkg.scripts && LIFECYCLE_SCRIPTS.some((s) => !!pkg.scripts[s]);
264
+ }
265
+ function scanLockfilePackages(packages) {
266
+ const scriptPkgs = [];
267
+ const missingIntegrityPkgs = [];
268
+ for (const [name, pkg] of Object.entries(packages)) {
269
+ if (!name)
270
+ continue; // skip root entry
271
+ const pkgName = name.replace(/^node_modules\//, "");
272
+ if (hasLifecycleScript(pkg))
273
+ scriptPkgs.push(pkgName);
274
+ if (pkg.version && !pkg.integrity)
275
+ missingIntegrityPkgs.push(pkgName);
276
+ }
277
+ return { scriptPkgs, missingIntegrityPkgs };
278
+ }
279
+ async function checkTransitiveDependencies() {
280
+ const findings = [];
281
+ try {
282
+ let lockRaw;
283
+ try {
284
+ lockRaw = await readFile("package-lock.json", "utf8");
285
+ }
286
+ catch {
287
+ return [];
288
+ }
289
+ let lock;
290
+ try {
291
+ lock = JSON.parse(lockRaw);
292
+ }
293
+ catch {
294
+ return [];
295
+ }
296
+ const { scriptPkgs, missingIntegrityPkgs } = scanLockfilePackages(lock.packages ?? {});
297
+ if (scriptPkgs.length > 0) {
298
+ findings.push({
299
+ id: "DEP_LIFECYCLE_SCRIPTS",
300
+ title: `${scriptPkgs.length} transitive dependencies contain lifecycle scripts (postinstall/install/preinstall)`,
301
+ severity: "HIGH",
302
+ evidence: scriptPkgs.slice(0, 15),
303
+ requiredActions: [
304
+ "Audit each package's lifecycle script for malicious behavior before trusting.",
305
+ "Add `ignore-scripts=true` to `.npmrc` to prevent automatic execution of install scripts.",
306
+ "For required scripts, explicitly allowlist them via `npm pkg set scripts.prepare=...`."
307
+ ]
308
+ });
309
+ findings.push(...await checkIgnoreScripts());
310
+ }
311
+ if (missingIntegrityPkgs.length > 0) {
312
+ findings.push({
313
+ id: "DEP_MISSING_INTEGRITY",
314
+ title: `${missingIntegrityPkgs.length} lockfile entries are missing integrity hashes — possible tampering`,
315
+ severity: "HIGH",
316
+ evidence: missingIntegrityPkgs.slice(0, 15),
317
+ requiredActions: [
318
+ "Regenerate the lockfile with `npm install` and commit the result.",
319
+ "Missing `integrity` fields prevent npm from verifying the downloaded package matches the expected content.",
320
+ "Consider using `npm ci` in CI/CD — it fails if the lockfile has missing or mismatched integrity hashes."
321
+ ]
322
+ });
323
+ }
324
+ }
325
+ catch (err) {
326
+ console.warn("[checkTransitiveDependencies] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
327
+ }
328
+ return findings;
329
+ }
330
+ async function checkIgnoreScripts() {
331
+ let npmrcContent = "";
332
+ try {
333
+ npmrcContent = await readFile(".npmrc", "utf8");
334
+ }
335
+ catch {
336
+ // .npmrc absent — treat as missing
337
+ }
338
+ if (/ignore-scripts\s*=\s*true/.test(npmrcContent))
339
+ return [];
340
+ return [{
341
+ id: "DEP_IGNORE_SCRIPTS_MISSING",
342
+ title: "`.npmrc` does not set `ignore-scripts=true` — lifecycle scripts run automatically on install",
343
+ severity: "MEDIUM",
344
+ requiredActions: [
345
+ "Add `ignore-scripts=true` to your project's `.npmrc` file.",
346
+ "This prevents malicious postinstall scripts from executing during `npm install`.",
347
+ "Commit `.npmrc` to the repository so CI/CD enforces it consistently."
348
+ ]
349
+ }];
350
+ }
351
+ // ─── Typosquatting / dependency confusion ──────────────────────────────────
352
+ // Known typosquat → legitimate package mappings (1-edit-distance variants of top npm packages)
353
+ const KNOWN_TYPOSQUATS = {
354
+ "lodahs": "lodash",
355
+ "loadsh": "lodash",
356
+ "lodash-": "lodash",
357
+ "expres": "express",
358
+ "expresss": "express",
359
+ "expres-session": "express-session",
360
+ "requets": "request",
361
+ "reqwest": "request",
362
+ "reacts": "react",
363
+ "reactt": "react",
364
+ "react-doms": "react-dom",
365
+ "axois": "axios",
366
+ "axio": "axios",
367
+ "momnet": "moment",
368
+ "momment": "moment",
369
+ "undersocre": "underscore",
370
+ "underscoree": "underscore",
371
+ "babbel": "babel",
372
+ "webpakc": "webpack",
373
+ "webapck": "webpack",
374
+ "eslint-": "eslint",
375
+ "typscript": "typescript",
376
+ "typescrip": "typescript",
377
+ "ts-nod": "ts-node",
378
+ "nod-fetch": "node-fetch",
379
+ "nodefetch": "node-fetch",
380
+ "crossenv": "cross-env",
381
+ "cross-envs": "cross-env",
382
+ "dotenvs": "dotenv",
383
+ "dot-env": "dotenv",
384
+ "jest-": "jest",
385
+ "jests": "jest",
386
+ "chalkk": "chalk",
387
+ "chak": "chalk",
388
+ "commnder": "commander",
389
+ "commanderjs": "commander",
390
+ "yargss": "yargs",
391
+ "uuid-": "uuid",
392
+ "uuidd": "uuid",
393
+ "semverr": "semver",
394
+ "globb": "glob",
395
+ "glo": "glob",
396
+ "mimimatch": "minimatch",
397
+ "minimach": "minimatch",
398
+ "debugg": "debug",
399
+ "debu": "debug",
400
+ "async-": "async",
401
+ "asyncs": "async"
402
+ };
403
+ // Suspicious version patterns used in dependency confusion / version injection attacks
404
+ const SUSPICIOUS_VERSION_RE = /^\^?999\.|^0\.0\.[01]$/;
405
+ async function checkTyposquatting() {
406
+ const findings = [];
407
+ try {
408
+ let pkgRaw;
409
+ try {
410
+ pkgRaw = await readFile("package.json", "utf8");
411
+ }
412
+ catch {
413
+ return [];
414
+ }
415
+ const pkg = JSON.parse(pkgRaw);
416
+ const allDeps = {
417
+ ...pkg.dependencies,
418
+ ...pkg.devDependencies
419
+ };
420
+ const typosquatHits = [];
421
+ const suspiciousVersionHits = [];
422
+ for (const [name, version] of Object.entries(allDeps)) {
423
+ const normalized = name.toLowerCase();
424
+ // Check against known typosquat list
425
+ if (KNOWN_TYPOSQUATS[normalized]) {
426
+ typosquatHits.push(`"${name}" (possible typo of "${KNOWN_TYPOSQUATS[normalized]}")`);
427
+ }
428
+ // Check for suspicious version numbers used in dependency confusion attacks
429
+ if (SUSPICIOUS_VERSION_RE.test(version) && name.length < 8) {
430
+ suspiciousVersionHits.push(`"${name}@${version}"`);
431
+ }
432
+ }
433
+ if (typosquatHits.length > 0) {
434
+ findings.push({
435
+ id: "DEP_TYPOSQUAT",
436
+ title: `Possible typosquatted package name(s) detected in dependencies`,
437
+ severity: "CRITICAL",
438
+ evidence: typosquatHits.slice(0, 10),
439
+ requiredActions: [
440
+ "Verify each flagged package is the intended dependency — typosquatting replaces legitimate packages with malicious ones.",
441
+ "Remove the package, run `npm install` with the correctly-spelled name, and audit `package-lock.json`.",
442
+ "Use `npm audit` and review the package on npmjs.com before reinstalling."
443
+ ]
444
+ });
445
+ }
446
+ if (suspiciousVersionHits.length > 0) {
447
+ findings.push({
448
+ id: "DEP_SUSPICIOUS_VERSION",
449
+ title: "Dependencies with suspicious version numbers — possible dependency confusion attack",
450
+ severity: "HIGH",
451
+ evidence: suspiciousVersionHits.slice(0, 10),
452
+ requiredActions: [
453
+ "Packages with version `999.*` or `0.0.0`/`0.0.1` on short names are a common dependency confusion attack signal.",
454
+ "Verify these packages are legitimate using `npm view <package>` and inspect the publish history.",
455
+ "Use a private registry with an allowlist of approved packages (Artifactory, Verdaccio, GitHub Packages)."
456
+ ]
457
+ });
458
+ }
459
+ }
460
+ catch (err) {
461
+ console.warn("[checkTyposquatting] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
462
+ }
168
463
  return findings;
169
464
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Data Loss Prevention checks.
3
+ * Detects PII leaking into logs, APIs, and error responses.
4
+ */
5
+ import { sanitizeErrorMessage } from "../result.js";
1
6
  import { searchRepo } from "../../repo/search.js";
2
7
  export async function checkDlp(_opts) {
3
8
  const findings = [];
@@ -147,7 +152,7 @@ export async function checkDlp(_opts) {
147
152
  }
148
153
  }
149
154
  catch (err) {
150
- console.warn("[checkDlp] Internal error:", err instanceof Error ? err.message : String(err));
155
+ console.warn("[checkDlp] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
151
156
  }
152
157
  return findings;
153
158
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * GraphQL security checks.
3
+ * Detects GraphQL schemas and validates security controls.
4
+ */
5
+ import { sanitizeErrorMessage } from "../result.js";
1
6
  import { searchRepo } from "../../repo/search.js";
2
7
  import fg from "fast-glob";
3
8
  import { readFileSafe } from "../../repo/fs.js";
@@ -116,7 +121,7 @@ export async function checkGraphQL(_opts) {
116
121
  }
117
122
  }
118
123
  catch (err) {
119
- console.warn("[checkGraphQL] Internal error:", err instanceof Error ? err.message : String(err));
124
+ console.warn("[checkGraphQL] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
120
125
  }
121
126
  return findings;
122
127
  }