security-mcp 1.1.0 → 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 (118) hide show
  1. package/README.md +966 -193
  2. package/defaults/agent-run-schema.json +98 -0
  3. package/dist/ci/pr-gate.js +18 -1
  4. package/dist/cli/install.js +69 -2
  5. package/dist/cli/onboarding.js +82 -11
  6. package/dist/cli/update.js +83 -15
  7. package/dist/gate/checks/ai-redteam.js +83 -59
  8. package/dist/gate/checks/api.js +93 -0
  9. package/dist/gate/checks/ci-pipeline.js +135 -0
  10. package/dist/gate/checks/crypto.js +91 -22
  11. package/dist/gate/checks/database.js +5 -1
  12. package/dist/gate/checks/dependencies.js +297 -2
  13. package/dist/gate/checks/dlp.js +6 -1
  14. package/dist/gate/checks/graphql.js +6 -1
  15. package/dist/gate/checks/k8s.js +229 -181
  16. package/dist/gate/checks/nuclei.js +133 -0
  17. package/dist/gate/checks/runtime.js +75 -8
  18. package/dist/gate/checks/scanners.js +8 -2
  19. package/dist/gate/diff.js +2 -0
  20. package/dist/gate/exceptions.js +6 -1
  21. package/dist/gate/policy.js +47 -4
  22. package/dist/gate/result.js +7 -1
  23. package/dist/mcp/audit-chain.js +253 -0
  24. package/dist/mcp/learning.js +228 -0
  25. package/dist/mcp/model-router.js +544 -0
  26. package/dist/mcp/orchestration.js +604 -0
  27. package/dist/mcp/server.js +160 -12
  28. package/dist/repo/search.js +5 -7
  29. package/dist/review/store.js +15 -0
  30. package/dist/types/agent-run.js +8 -0
  31. package/package.json +5 -5
  32. package/skills/_TEMPLATE/SKILL.md +99 -0
  33. package/skills/advanced-dos-tester/SKILL.md +225 -0
  34. package/skills/agentic-loop-exploiter/SKILL.md +69 -0
  35. package/skills/ai-llm-redteam/SKILL.md +118 -0
  36. package/skills/ai-model-supply-chain-agent/SKILL.md +198 -0
  37. package/skills/algorithm-implementation-reviewer/SKILL.md +85 -0
  38. package/skills/android-penetration-tester/SKILL.md +83 -0
  39. package/skills/anti-replay-tester/SKILL.md +195 -0
  40. package/skills/appsec-code-auditor/SKILL.md +86 -0
  41. package/skills/artifact-integrity-analyst/SKILL.md +68 -0
  42. package/skills/attack-navigator/SKILL.md +64 -0
  43. package/skills/auth-session-hacker/SKILL.md +87 -0
  44. package/skills/aws-penetration-tester/SKILL.md +60 -0
  45. package/skills/azure-penetration-tester/SKILL.md +64 -0
  46. package/skills/binary-auth-validator/SKILL.md +184 -0
  47. package/skills/bot-detection-specialist/SKILL.md +221 -0
  48. package/skills/business-logic-attacker/SKILL.md +76 -0
  49. package/skills/capec-code-mapper/SKILL.md +163 -0
  50. package/skills/cert-pin-rotation-specialist/SKILL.md +200 -0
  51. package/skills/cicd-pipeline-hijacker/SKILL.md +81 -0
  52. package/skills/ciso-orchestrator/SKILL.md +165 -0
  53. package/skills/cloud-infra-specialist/SKILL.md +85 -0
  54. package/skills/compliance-gap-analyst/SKILL.md +77 -0
  55. package/skills/compliance-grc/SKILL.md +148 -0
  56. package/skills/compliance-lifecycle-tracker/SKILL.md +169 -0
  57. package/skills/credential-stuffing-specialist/SKILL.md +192 -0
  58. package/skills/crypto-pki-specialist/SKILL.md +136 -0
  59. package/skills/csa-ccm-mapper/SKILL.md +178 -0
  60. package/skills/csf2-governance-mapper/SKILL.md +159 -0
  61. package/skills/deep-link-fuzzer/SKILL.md +195 -0
  62. package/skills/dependency-confusion-attacker/SKILL.md +78 -0
  63. package/skills/device-integrity-aggregator/SKILL.md +221 -0
  64. package/skills/dos-resilience-tester/SKILL.md +184 -0
  65. package/skills/dread-scorer/SKILL.md +157 -0
  66. package/skills/egress-policy-enforcer/SKILL.md +208 -0
  67. package/skills/evidence-collector/SKILL.md +86 -0
  68. package/skills/file-upload-attacker/SKILL.md +208 -0
  69. package/skills/gcp-penetration-tester/SKILL.md +63 -0
  70. package/skills/git-history-secret-scanner/SKILL.md +182 -0
  71. package/skills/iam-privesc-graph-builder/SKILL.md +216 -0
  72. package/skills/incident-responder/SKILL.md +192 -0
  73. package/skills/injection-specialist/SKILL.md +62 -0
  74. package/skills/ios-security-auditor/SKILL.md +77 -0
  75. package/skills/json-ambiguity-tester/SKILL.md +175 -0
  76. package/skills/k8s-container-escaper/SKILL.md +74 -0
  77. package/skills/key-management-lifecycle-analyst/SKILL.md +92 -0
  78. package/skills/kill-switch-engineer/SKILL.md +205 -0
  79. package/skills/linddun-privacy-analyst/SKILL.md +196 -0
  80. package/skills/logic-race-fuzzer/SKILL.md +67 -0
  81. package/skills/mobile-api-network-attacker/SKILL.md +81 -0
  82. package/skills/mobile-binary-hardener/SKILL.md +199 -0
  83. package/skills/mobile-security-specialist/SKILL.md +124 -0
  84. package/skills/mobile-webview-auditor/SKILL.md +200 -0
  85. package/skills/model-extraction-attacker/SKILL.md +68 -0
  86. package/skills/multipart-abuse-tester/SKILL.md +146 -0
  87. package/skills/oauth-pkce-specialist/SKILL.md +191 -0
  88. package/skills/parser-exhaustion-tester/SKILL.md +177 -0
  89. package/skills/pentest-infra/SKILL.md +69 -0
  90. package/skills/pentest-social/SKILL.md +72 -0
  91. package/skills/pentest-team/SKILL.md +126 -0
  92. package/skills/pentest-web-api/SKILL.md +71 -0
  93. package/skills/privacy-flow-analyst/SKILL.md +70 -0
  94. package/skills/prompt-injection-specialist/SKILL.md +76 -0
  95. package/skills/quantum-migration-planner/SKILL.md +184 -0
  96. package/skills/rag-poisoning-specialist/SKILL.md +71 -0
  97. package/skills/registry-mirror-enforcer/SKILL.md +142 -0
  98. package/skills/rotation-validation-agent/SKILL.md +188 -0
  99. package/skills/samm-assessor/SKILL.md +168 -0
  100. package/skills/secrets-mask-bypass-tester/SKILL.md +167 -0
  101. package/skills/senior-security-engineer/SKILL.md +42 -12
  102. package/skills/serialization-memory-attacker/SKILL.md +78 -0
  103. package/skills/session-timeout-tester/SKILL.md +197 -0
  104. package/skills/slsa-level3-enforcer/SKILL.md +185 -0
  105. package/skills/slsa-provenance-enforcer/SKILL.md +181 -0
  106. package/skills/ssrf-detection-validator/SKILL.md +229 -0
  107. package/skills/step-up-auth-enforcer/SKILL.md +176 -0
  108. package/skills/stride-pasta-analyst/SKILL.md +72 -0
  109. package/skills/supply-chain-devsecops/SKILL.md +82 -0
  110. package/skills/threat-infrastructure-analyst/SKILL.md +167 -0
  111. package/skills/threat-modeler/SKILL.md +116 -0
  112. package/skills/tls-certificate-auditor/SKILL.md +76 -0
  113. package/skills/token-reuse-detector/SKILL.md +203 -0
  114. package/skills/trike-risk-modeler/SKILL.md +139 -0
  115. package/skills/unicode-homograph-tester/SKILL.md +179 -0
  116. package/skills/waf-rule-lifecycle-agent/SKILL.md +213 -0
  117. package/skills/webhook-security-tester/SKILL.md +184 -0
  118. package/skills/zero-trust-architect/SKILL.md +211 -0
@@ -1,4 +1,7 @@
1
+ import { sanitizeErrorMessage } from "../result.js";
1
2
  import { searchRepo } from "../../repo/search.js";
3
+ import fg from "fast-glob";
4
+ import { readFileSafe } from "../../repo/fs.js";
2
5
  export async function checkApi(_) {
3
6
  const findings = [];
4
7
  const zodHits = await searchRepo({ query: "zod|valibot|yup|joi", isRegex: true, maxMatches: 200 });
@@ -135,5 +138,95 @@ export async function checkApi(_) {
135
138
  ]
136
139
  });
137
140
  }
141
+ // 5. API schema drift (OpenAPI/Swagger spec vs code routes)
142
+ findings.push(...await checkApiSchemaDrift());
143
+ return findings;
144
+ }
145
+ function parseDeclaredPaths(specContent) {
146
+ const paths = new Set();
147
+ for (const match of specContent.matchAll(/^\s{0,4}(\/[a-zA-Z0-9/{}_-]+)\s*:/gm)) {
148
+ paths.add(match[1]);
149
+ }
150
+ return paths;
151
+ }
152
+ function findShadowRoutes(codeRouteHits, declaredPaths) {
153
+ const shadows = [];
154
+ for (const hit of codeRouteHits) {
155
+ const routeMatch = /['"](\/?[a-zA-Z0-9/{}_-]+)['"]/.exec(hit.preview);
156
+ if (!routeMatch)
157
+ continue;
158
+ const route = routeMatch[1].startsWith("/") ? routeMatch[1] : `/${routeMatch[1]}`;
159
+ const normalised = route.replaceAll(/:([a-zA-Z_]+)/g, "{$1}");
160
+ if (!declaredPaths.has(normalised) && !declaredPaths.has(route)) {
161
+ shadows.push(`${hit.file}:${hit.line} — ${route}`);
162
+ }
163
+ }
164
+ return shadows;
165
+ }
166
+ async function checkApiSchemaDrift() {
167
+ const findings = [];
168
+ try {
169
+ const specFiles = await fg([
170
+ "openapi.{yaml,yml,json}",
171
+ "swagger.{yaml,yml,json}",
172
+ "**/openapi.{yaml,yml,json}",
173
+ "**/swagger.{yaml,yml,json}",
174
+ "**/api-spec.{yaml,yml,json}",
175
+ "**/openapi/**/*.{yaml,yml,json}"
176
+ ], { ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"], dot: true });
177
+ const codeRouteHits = await searchRepo({
178
+ query: String.raw `(?:router|app|fastify|server)\.(?:get|post|put|delete|patch)\s*\(\s*['"](/[^'"]+)['"]`,
179
+ isRegex: true,
180
+ maxMatches: 300
181
+ });
182
+ if (specFiles.length === 0) {
183
+ if (codeRouteHits.length > 0) {
184
+ findings.push({
185
+ id: "API_NO_OPENAPI_SPEC",
186
+ title: "API routes detected but no OpenAPI/Swagger specification found",
187
+ severity: "MEDIUM",
188
+ evidence: codeRouteHits.slice(0, 10).map((m) => `${m.file}:${m.line}:${m.preview}`),
189
+ requiredActions: [
190
+ "Create an OpenAPI 3.x specification (openapi.yaml) that documents all API routes.",
191
+ "An API contract enables automated schema validation, client SDK generation, and drift detection.",
192
+ "Use tools like `zod-to-openapi` or `tsoa` to generate the spec from existing TypeScript code."
193
+ ]
194
+ });
195
+ }
196
+ return findings;
197
+ }
198
+ const specContent = await readFileSafe(specFiles[0]);
199
+ const declaredPaths = parseDeclaredPaths(specContent);
200
+ const shadowRoutes = findShadowRoutes(codeRouteHits, declaredPaths);
201
+ if (shadowRoutes.length > 0) {
202
+ findings.push({
203
+ id: "API_SHADOW_ENDPOINT",
204
+ title: `${shadowRoutes.length} API route(s) in code not declared in OpenAPI spec — shadow endpoints`,
205
+ severity: "HIGH",
206
+ evidence: [...new Set(shadowRoutes)].slice(0, 15),
207
+ requiredActions: [
208
+ "Add all undocumented routes to the OpenAPI specification.",
209
+ "Shadow endpoints bypass API gateway policies, rate limiting, and schema validation.",
210
+ "Automate spec generation (tsoa, zod-to-openapi) to prevent drift from recurring."
211
+ ]
212
+ });
213
+ }
214
+ if (/type:\s+object/.test(specContent) && !/properties:/.test(specContent)) {
215
+ findings.push({
216
+ id: "API_PERMISSIVE_SCHEMA",
217
+ title: "OpenAPI spec contains `type: object` without `properties` — accepts any payload shape",
218
+ severity: "MEDIUM",
219
+ files: [specFiles[0]],
220
+ requiredActions: [
221
+ "Define explicit `properties` for all object schemas in the OpenAPI spec.",
222
+ "Permissive schemas allow attackers to inject unexpected fields (mass assignment, prototype pollution).",
223
+ "Set `additionalProperties: false` on request body schemas to enforce strict validation."
224
+ ]
225
+ });
226
+ }
227
+ }
228
+ catch (err) {
229
+ console.warn("[checkApiSchemaDrift] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
230
+ }
138
231
  return findings;
139
232
  }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * GitHub Actions CI/CD pipeline hardening checks.
3
+ * Covers supply chain attack vectors specific to GitHub Actions workflows.
4
+ */
5
+ import { sanitizeErrorMessage } from "../result.js";
6
+ import fg from "fast-glob";
7
+ import { readFileSafe } from "../../repo/fs.js";
8
+ export async function runCiPipelineChecks(_opts) {
9
+ const findings = [];
10
+ try {
11
+ const workflowFiles = await fg([".github/workflows/*.yml", ".github/workflows/*.yaml"], {
12
+ dot: true,
13
+ ignore: ["**/node_modules/**", "**/.git/**"]
14
+ });
15
+ if (workflowFiles.length === 0) {
16
+ return [];
17
+ }
18
+ const unpinnedFiles = [];
19
+ const pwnTargetFiles = [];
20
+ const secretEchoFiles = [];
21
+ const noPermissionsFiles = [];
22
+ const selfHostedFiles = [];
23
+ for (const file of workflowFiles) {
24
+ let content;
25
+ try {
26
+ content = await readFileSafe(file);
27
+ }
28
+ catch {
29
+ continue;
30
+ }
31
+ // Check 1: Third-party actions not pinned to a full 40-char SHA
32
+ // Matches `uses: owner/repo@tag` but NOT `uses: owner/repo@<40hex chars>`
33
+ // Also skip `uses: ./.github/actions/` (local actions are fine)
34
+ const actionLines = content.split("\n").filter((line) => /uses:\s+[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+@/.test(line));
35
+ const unpinnedActions = actionLines.filter((line) => {
36
+ // Skip local actions
37
+ if (/uses:\s+\.\//.test(line))
38
+ return false;
39
+ // Flag anything not pinned to a 40-char hex SHA
40
+ return !/uses:\s+[a-zA-Z0-9_.\-/]+@[0-9a-f]{40}/.test(line);
41
+ });
42
+ if (unpinnedActions.length > 0) {
43
+ unpinnedFiles.push(file);
44
+ }
45
+ // Check 2: pull_request_target + dynamic ref usage (pwn-request vector)
46
+ // Attacker controls the ref/sha when a PR from a fork triggers pull_request_target
47
+ if (/pull_request_target/.test(content) &&
48
+ /\$\{\{\s*github\.event\.pull_request\.head\.(sha|ref)\s*\}\}/.test(content)) {
49
+ pwnTargetFiles.push(file);
50
+ }
51
+ // Check 3: Secrets printed to logs via echo
52
+ if (/echo\s+\$\{\{\s*secrets\./.test(content)) {
53
+ secretEchoFiles.push(file);
54
+ }
55
+ // Check 4: No top-level permissions block
56
+ // Without explicit permissions, the default is write access to all scopes
57
+ if (!/^permissions:/m.test(content)) {
58
+ noPermissionsFiles.push(file);
59
+ }
60
+ // Check 5: Self-hosted runners (broader attack surface — runner compromise = code execution)
61
+ if (/runs-on:\s+self-hosted/.test(content)) {
62
+ selfHostedFiles.push(file);
63
+ }
64
+ }
65
+ if (unpinnedFiles.length > 0) {
66
+ findings.push({
67
+ id: "CI_UNPINNED_ACTION",
68
+ title: "GitHub Actions using mutable tags instead of pinned SHA digests",
69
+ severity: "HIGH",
70
+ files: unpinnedFiles.slice(0, 10),
71
+ requiredActions: [
72
+ "Pin all third-party actions to a full 40-character commit SHA (e.g. `uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683`).",
73
+ "Mutable tags like @v3 can be silently redirected to malicious commits — SHA pinning prevents supply chain substitution.",
74
+ "Use a tool like `pin-github-action` or Dependabot to automate SHA pinning."
75
+ ]
76
+ });
77
+ }
78
+ if (pwnTargetFiles.length > 0) {
79
+ findings.push({
80
+ id: "CI_PWNTARGET_SHA",
81
+ title: "pull_request_target workflow uses attacker-controlled ref/SHA — pwn-request vector",
82
+ severity: "CRITICAL",
83
+ files: pwnTargetFiles.slice(0, 10),
84
+ requiredActions: [
85
+ "Never use `${{ github.event.pull_request.head.sha }}` or `head.ref` inside a `pull_request_target` workflow that checks out or runs code from the PR.",
86
+ "`pull_request_target` runs with write permissions and secrets access; the PR head is attacker-controlled.",
87
+ "Use `pull_request` (not `pull_request_target`) for code that executes untrusted contributions, or add explicit guard conditions."
88
+ ]
89
+ });
90
+ }
91
+ if (secretEchoFiles.length > 0) {
92
+ findings.push({
93
+ id: "CI_SECRET_ECHO",
94
+ title: "GitHub Actions workflow echoes secrets to logs",
95
+ severity: "CRITICAL",
96
+ files: secretEchoFiles.slice(0, 10),
97
+ requiredActions: [
98
+ "Remove any `echo ${{ secrets.* }}` statements — secrets printed to logs are visible to anyone with read access to the repository.",
99
+ "GitHub masks known secret values in logs, but this is not reliable for all encodings.",
100
+ "Pass secrets via environment variables (`env: MY_SECRET: ${{ secrets.MY_SECRET }}`) and read them in code, never echo them."
101
+ ]
102
+ });
103
+ }
104
+ if (noPermissionsFiles.length > 0) {
105
+ findings.push({
106
+ id: "CI_NO_PERMISSIONS",
107
+ title: "GitHub Actions workflows without explicit permissions block",
108
+ severity: "MEDIUM",
109
+ files: noPermissionsFiles.slice(0, 10),
110
+ requiredActions: [
111
+ "Add an explicit `permissions:` block at the top of each workflow (or at the job level) to grant only the minimum required scopes.",
112
+ "Without explicit permissions, the default is determined by org/repo settings — often write-all.",
113
+ "Example minimal read-only: `permissions: { contents: read }`."
114
+ ]
115
+ });
116
+ }
117
+ if (selfHostedFiles.length > 0) {
118
+ findings.push({
119
+ id: "CI_SELF_HOSTED_RUNNER",
120
+ title: "GitHub Actions using self-hosted runners",
121
+ severity: "MEDIUM",
122
+ files: selfHostedFiles.slice(0, 10),
123
+ requiredActions: [
124
+ "Self-hosted runners executing untrusted fork PRs can be compromised — restrict `pull_request` triggers on self-hosted runner workflows.",
125
+ "Ensure self-hosted runners are ephemeral (destroyed after each job) and isolated from production networks.",
126
+ "Use GitHub-hosted runners for public repositories or untrusted code paths."
127
+ ]
128
+ });
129
+ }
130
+ }
131
+ catch (err) {
132
+ console.warn("[runCiPipelineChecks] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
133
+ }
134
+ return findings;
135
+ }
@@ -1,4 +1,31 @@
1
+ /**
2
+ * Weak cryptography detection.
3
+ * Mapped to NIST SP 800-131A Rev 2.
4
+ */
5
+ import { sanitizeErrorMessage } from "../result.js";
1
6
  import { searchRepo } from "../../repo/search.js";
7
+ function checkPbkdf2Iterations(hits) {
8
+ for (const hit of hits) {
9
+ const iterMatch = /pbkdf2(?:Sync)?\s*\([^)]*?,\s*[^,]+,\s*(\d+)/.exec(hit.preview);
10
+ if (!iterMatch)
11
+ continue;
12
+ const iters = Number.parseInt(iterMatch[1], 10);
13
+ if (iters < 600000) {
14
+ return {
15
+ id: "CRYPTO_LOW_PBKDF2_ITERATIONS",
16
+ title: `PBKDF2 iteration count too low (${iters} < 600,000)`,
17
+ severity: "HIGH",
18
+ evidence: [`${hit.file}:${hit.line}:${hit.preview}`],
19
+ files: [hit.file],
20
+ requiredActions: [
21
+ "Use ≥ 600,000 iterations for PBKDF2-SHA256 (OWASP 2023 recommendation).",
22
+ "Prefer bcrypt (cost ≥ 12) or Argon2id instead."
23
+ ]
24
+ };
25
+ }
26
+ }
27
+ return null;
28
+ }
2
29
  export async function checkCrypto(_opts) {
3
30
  const findings = [];
4
31
  try {
@@ -86,27 +113,9 @@ export async function checkCrypto(_opts) {
86
113
  isRegex: true,
87
114
  maxMatches: 200
88
115
  });
89
- // Check for numeric iteration counts in the context
90
- for (const hit of pbkdf2Hits) {
91
- const iterMatch = /pbkdf2(?:Sync)?\s*\([^)]*?,\s*[^,]+,\s*(\d+)/.exec(hit.preview);
92
- if (iterMatch) {
93
- const iters = parseInt(iterMatch[1], 10);
94
- if (iters < 600000) {
95
- findings.push({
96
- id: "CRYPTO_LOW_PBKDF2_ITERATIONS",
97
- title: `PBKDF2 iteration count too low (${iters} < 600,000)`,
98
- severity: "HIGH",
99
- evidence: [`${hit.file}:${hit.line}:${hit.preview}`],
100
- files: [hit.file],
101
- requiredActions: [
102
- "Use ≥ 600,000 iterations for PBKDF2-SHA256 (OWASP 2023 recommendation).",
103
- "Prefer bcrypt (cost ≥ 12) or Argon2id instead."
104
- ]
105
- });
106
- break;
107
- }
108
- }
109
- }
116
+ const pbkdf2Finding = checkPbkdf2Iterations(pbkdf2Hits);
117
+ if (pbkdf2Finding)
118
+ findings.push(pbkdf2Finding);
110
119
  // 6. Hardcoded IV/nonce
111
120
  const hardcodedIvHits = await searchRepo({
112
121
  query: String.raw `iv\s*[:=]\s*(?:Buffer\.from\(['"][0-9a-fA-F]+['"]\)|['"][0-9a-fA-F]{16,}['"])`,
@@ -145,9 +154,69 @@ export async function checkCrypto(_opts) {
145
154
  ]
146
155
  });
147
156
  }
157
+ // 8. Post-quantum readiness: RSA-1024
158
+ const rsa1024Hits = await searchRepo({
159
+ query: String.raw `modulusLength\s*:\s*1024|generateKeyPair\s*\(\s*['"]rsa['"][^)]*1024`,
160
+ isRegex: true,
161
+ maxMatches: 200
162
+ });
163
+ if (rsa1024Hits.length > 0) {
164
+ findings.push({
165
+ id: "CRYPTO_RSA_1024",
166
+ title: "RSA-1024 key detected — cryptographically broken",
167
+ severity: "CRITICAL",
168
+ evidence: rsa1024Hits.slice(0, 10).map((m) => `${m.file}:${m.line}:${m.preview}`),
169
+ files: [...new Set(rsa1024Hits.slice(0, 10).map((m) => m.file))],
170
+ requiredActions: [
171
+ "Upgrade to RSA-4096 minimum, or migrate to ML-DSA (FIPS 204) / SLH-DSA (FIPS 205) for new key material.",
172
+ "RSA-1024 is fully broken — NIST deprecated it in 2013 (SP 800-131A).",
173
+ "For TLS certificates, reissue with RSA-4096 or ECDSA P-384 immediately."
174
+ ]
175
+ });
176
+ }
177
+ // 9. Post-quantum readiness: RSA-2048 warning
178
+ const rsa2048Hits = await searchRepo({
179
+ query: String.raw `modulusLength\s*:\s*2048|generateKeyPair\s*\(\s*['"]rsa['"][^)]*2048`,
180
+ isRegex: true,
181
+ maxMatches: 200
182
+ });
183
+ if (rsa2048Hits.length > 0) {
184
+ findings.push({
185
+ id: "CRYPTO_RSA_2048_PQC",
186
+ title: "RSA-2048 detected — quantum-vulnerable; plan migration to post-quantum algorithms",
187
+ severity: "MEDIUM",
188
+ evidence: rsa2048Hits.slice(0, 10).map((m) => `${m.file}:${m.line}:${m.preview}`),
189
+ files: [...new Set(rsa2048Hits.slice(0, 10).map((m) => m.file))],
190
+ requiredActions: [
191
+ "RSA-2048 is currently secure against classical computers but will be broken by sufficiently large quantum computers.",
192
+ "NIST finalized post-quantum standards in 2024: ML-KEM (FIPS 203), ML-DSA (FIPS 204), SLH-DSA (FIPS 205).",
193
+ "For long-lived keys or data requiring 10+ year secrecy: migrate to ML-DSA or use a hybrid classical+PQC scheme."
194
+ ]
195
+ });
196
+ }
197
+ // 10. Post-quantum readiness: ECDSA P-256 (informational)
198
+ const p256Hits = await searchRepo({
199
+ query: String.raw `prime256v1|secp256r1|namedCurve\s*:\s*['"]P-256['"]|namedCurve\s*:\s*['"]p256['"]`,
200
+ isRegex: true,
201
+ maxMatches: 200
202
+ });
203
+ if (p256Hits.length > 0) {
204
+ findings.push({
205
+ id: "CRYPTO_ECDSA_P256_PQC",
206
+ title: "ECDSA P-256 detected — quantum-vulnerable in the long term",
207
+ severity: "LOW",
208
+ evidence: p256Hits.slice(0, 10).map((m) => `${m.file}:${m.line}:${m.preview}`),
209
+ files: [...new Set(p256Hits.slice(0, 10).map((m) => m.file))],
210
+ requiredActions: [
211
+ "P-256 (secp256r1) is secure today but vulnerable to Shor's algorithm on a sufficiently large quantum computer.",
212
+ "NIST post-quantum signature standards: ML-DSA (FIPS 204) and SLH-DSA (FIPS 205) are the recommended replacements.",
213
+ "For new systems handling sensitive long-lived data, evaluate hybrid ECDSA+ML-DSA or pure ML-DSA."
214
+ ]
215
+ });
216
+ }
148
217
  }
149
218
  catch (err) {
150
- console.warn("[checkCrypto] Internal error:", err instanceof Error ? err.message : String(err));
219
+ console.warn("[checkCrypto] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
151
220
  }
152
221
  return findings;
153
222
  }
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Database security checks.
3
+ */
4
+ import { sanitizeErrorMessage } from "../result.js";
1
5
  import { searchRepo } from "../../repo/search.js";
2
6
  export async function checkDatabase(_opts) {
3
7
  const findings = [];
@@ -138,7 +142,7 @@ export async function checkDatabase(_opts) {
138
142
  }
139
143
  }
140
144
  catch (err) {
141
- console.warn("[checkDatabase] Internal error:", err instanceof Error ? err.message : String(err));
145
+ console.warn("[checkDatabase] Internal error:", sanitizeErrorMessage(err instanceof Error ? err.message : String(err)));
142
146
  }
143
147
  return findings;
144
148
  }