aws-security-mcp 0.6.1 → 0.6.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.
package/dist/src/index.js CHANGED
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { z } from "zod";
5
5
 
6
6
  // src/version.ts
7
- var VERSION = "0.6.1";
7
+ var VERSION = "0.6.2";
8
8
 
9
9
  // src/utils/aws-client.ts
10
10
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -274,10 +274,6 @@ import {
274
274
  ConfigServiceClient,
275
275
  DescribeConfigurationRecordersCommand
276
276
  } from "@aws-sdk/client-config-service";
277
- import {
278
- Macie2Client,
279
- GetMacieSessionCommand
280
- } from "@aws-sdk/client-macie2";
281
277
  import {
282
278
  CloudTrailClient,
283
279
  DescribeTrailsCommand
@@ -321,7 +317,7 @@ function isNotEnabled(err) {
321
317
  err.name === "DisabledException" || err.message.includes("not enabled") || err.message.includes("not subscribed");
322
318
  }
323
319
  function computeMaturityLevel(enabledCount) {
324
- if (enabledCount >= 6) return "comprehensive";
320
+ if (enabledCount >= 5) return "comprehensive";
325
321
  if (enabledCount >= 4) return "advanced";
326
322
  if (enabledCount >= 2) return "intermediate";
327
323
  return "basic";
@@ -625,53 +621,6 @@ var ServiceDetectionScanner = class {
625
621
  services.push({ name: "AWS Config", enabled: null, details: "Detection error" });
626
622
  }
627
623
  }
628
- if (region.startsWith("cn-")) {
629
- services.push({ name: "Macie", enabled: null, details: "Not available in China regions" });
630
- warnings.push("Macie is not available in AWS China regions.");
631
- } else {
632
- try {
633
- const mc = createClient(Macie2Client, region, ctx.credentials);
634
- await mc.send(new GetMacieSessionCommand({}));
635
- services.push({
636
- name: "Macie",
637
- enabled: true,
638
- details: "Sensitive data detection active"
639
- });
640
- } catch (err) {
641
- if (isAccessDenied(err)) {
642
- warnings.push("Macie: insufficient permissions to check status");
643
- services.push({ name: "Macie", enabled: null, details: "Access denied" });
644
- } else if (isNotEnabled(err)) {
645
- services.push({
646
- name: "Macie",
647
- enabled: false,
648
- recommendation: "Enable Macie to detect sensitive data in S3",
649
- freeTrialAvailable: true
650
- });
651
- findings.push(
652
- makeFinding({
653
- riskScore: 5,
654
- title: "Amazon Macie is not enabled",
655
- resourceType: "AWS::Macie::Session",
656
- resourceId: "macie",
657
- resourceArn: `arn:${partition}:macie2:${region}:${accountId}:session`,
658
- region,
659
- description: "Amazon Macie is not enabled in this region. Macie uses machine learning to discover and protect sensitive data stored in S3.",
660
- impact: "Detects sensitive data (PII, credentials, financial data) in S3 buckets. Without it, sensitive data exposure may go unnoticed.",
661
- remediationSteps: [
662
- "Open the Amazon Macie console.",
663
- "Click 'Get Started' and enable Macie.",
664
- "Macie offers a 30-day free trial for sensitive data discovery.",
665
- "Configure automated sensitive data discovery jobs."
666
- ]
667
- })
668
- );
669
- } else {
670
- warnings.push(`Macie detection failed: ${err instanceof Error ? err.message : String(err)}`);
671
- services.push({ name: "Macie", enabled: null, details: "Detection error" });
672
- }
673
- }
674
- }
675
624
  const knownServices = services.filter((s) => s.enabled !== null);
676
625
  const enabledCount = services.filter((s) => s.enabled === true).length;
677
626
  const coveragePercent = knownServices.length > 0 ? Math.round(enabledCount / knownServices.length * 100) : 0;
@@ -3727,6 +3676,44 @@ var zhI18n = {
3727
3676
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
3728
3677
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
3729
3678
  },
3679
+ // Module display names
3680
+ moduleNames: {
3681
+ service_detection: "\u5B89\u5168\u670D\u52A1\u68C0\u6D4B",
3682
+ secret_exposure: "\u5BC6\u94A5\u66B4\u9732",
3683
+ ssl_certificate: "SSL \u8BC1\u4E66",
3684
+ dns_dangling: "\u60AC\u6302 DNS",
3685
+ network_reachability: "\u7F51\u7EDC\u53EF\u8FBE\u6027",
3686
+ iam_privilege_escalation: "IAM \u63D0\u6743\u5206\u6790",
3687
+ public_access_verify: "\u516C\u7F51\u8BBF\u95EE\u9A8C\u8BC1",
3688
+ tag_compliance: "\u6807\u7B7E\u5408\u89C4",
3689
+ idle_resources: "\u95F2\u7F6E\u8D44\u6E90",
3690
+ disaster_recovery: "\u707E\u5907\u8BC4\u4F30",
3691
+ security_hub_findings: "Security Hub",
3692
+ guardduty_findings: "GuardDuty",
3693
+ inspector_findings: "Inspector",
3694
+ trusted_advisor_findings: "Trusted Advisor",
3695
+ config_rules_findings: "Config Rules",
3696
+ access_analyzer_findings: "Access Analyzer",
3697
+ patch_compliance_findings: "\u8865\u4E01\u5408\u89C4",
3698
+ imdsv2_enforcement: "IMDSv2 \u5F3A\u5236",
3699
+ waf_coverage: "WAF \u8986\u76D6",
3700
+ // Security Hub sub-categories
3701
+ "sh:FSBP": "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5",
3702
+ "sh:Inspector": "\u8F6F\u4EF6\u6F0F\u6D1E",
3703
+ "sh:GuardDuty": "\u5A01\u80C1\u68C0\u6D4B",
3704
+ "sh:Config": "\u914D\u7F6E\u5408\u89C4",
3705
+ "sh:Access Analyzer": "\u5916\u90E8\u8BBF\u95EE",
3706
+ "sh:Other": "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0"
3707
+ },
3708
+ // Security Hub sub-categories
3709
+ securityHubSubCategories: {
3710
+ FSBP: { label: "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5" },
3711
+ Inspector: { label: "\u8F6F\u4EF6\u6F0F\u6D1E" },
3712
+ GuardDuty: { label: "\u5A01\u80C1\u68C0\u6D4B" },
3713
+ Config: { label: "\u914D\u7F6E\u5408\u89C4" },
3714
+ "Access Analyzer": { label: "\u5916\u90E8\u8BBF\u95EE" },
3715
+ Other: { label: "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0" }
3716
+ },
3730
3717
  // Service Recommendations
3731
3718
  notEnabled: "\u672A\u542F\u7528",
3732
3719
  serviceRecommendations: {
@@ -3959,6 +3946,44 @@ var enI18n = {
3959
3946
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
3960
3947
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
3961
3948
  },
3949
+ // Module display names
3950
+ moduleNames: {
3951
+ service_detection: "Security Service Detection",
3952
+ secret_exposure: "Secret Exposure",
3953
+ ssl_certificate: "SSL Certificate",
3954
+ dns_dangling: "Dangling DNS",
3955
+ network_reachability: "Network Reachability",
3956
+ iam_privilege_escalation: "IAM Privilege Escalation",
3957
+ public_access_verify: "Public Access Verification",
3958
+ tag_compliance: "Tag Compliance",
3959
+ idle_resources: "Idle Resources",
3960
+ disaster_recovery: "Disaster Recovery",
3961
+ security_hub_findings: "Security Hub",
3962
+ guardduty_findings: "GuardDuty",
3963
+ inspector_findings: "Inspector",
3964
+ trusted_advisor_findings: "Trusted Advisor",
3965
+ config_rules_findings: "Config Rules",
3966
+ access_analyzer_findings: "Access Analyzer",
3967
+ patch_compliance_findings: "Patch Compliance",
3968
+ imdsv2_enforcement: "IMDSv2 Enforcement",
3969
+ waf_coverage: "WAF Coverage",
3970
+ // Security Hub sub-categories
3971
+ "sh:FSBP": "Security Best Practices",
3972
+ "sh:Inspector": "Software Vulnerabilities",
3973
+ "sh:GuardDuty": "Threat Detection",
3974
+ "sh:Config": "Configuration Compliance",
3975
+ "sh:Access Analyzer": "External Access",
3976
+ "sh:Other": "Other Security Findings"
3977
+ },
3978
+ // Security Hub sub-categories
3979
+ securityHubSubCategories: {
3980
+ FSBP: { label: "Security Best Practices" },
3981
+ Inspector: { label: "Software Vulnerabilities" },
3982
+ GuardDuty: { label: "Threat Detection" },
3983
+ Config: { label: "Configuration Compliance" },
3984
+ "Access Analyzer": { label: "External Access" },
3985
+ Other: { label: "Other Security Findings" }
3986
+ },
3962
3987
  // Service Recommendations
3963
3988
  notEnabled: "Not Enabled",
3964
3989
  serviceRecommendations: {
@@ -4163,7 +4188,7 @@ function generateMarkdownReport(scanResults, lang) {
4163
4188
  for (const m of modules) {
4164
4189
  const status = m.status === "success" ? "\u2705" : "\u274C";
4165
4190
  lines.push(
4166
- `| ${m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
4191
+ `| ${t.moduleNames[m.module] ?? m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
4167
4192
  );
4168
4193
  }
4169
4194
  lines.push("");
@@ -7006,6 +7031,22 @@ var SEV_COLOR = {
7006
7031
  LOW: "#22c55e"
7007
7032
  };
7008
7033
  var SEVERITY_ORDER2 = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
7034
+ function getRecommendationTemplate(rem) {
7035
+ return rem.replace(/\b(i-[0-9a-f]+)\b/g, "{instance}").replace(/\b(vol-[0-9a-f]+)\b/g, "{volume}").replace(/\b(sg-[0-9a-f]+)\b/g, "{sg}").replace(/\b(eipalloc-[0-9a-f]+)\b/g, "{eip}").replace(/\b(arn:aws[-\w]*:[^"\s]+)\b/g, "{arn}").replace(/"[^"]+"/g, "{name}").replace(/bucket \S+/g, "bucket {name}").replace(/instance \S+/g, "instance {id}").replace(/volume \S+/g, "volume {id}").replace(/rule \S+/g, "rule {name}");
7036
+ }
7037
+ function getSecurityHubSource(finding) {
7038
+ const impact = finding.impact ?? "";
7039
+ const match = impact.match(/^Source:\s*([^(]+)/);
7040
+ if (!match) return "Other";
7041
+ const product = match[1].trim();
7042
+ if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
7043
+ if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
7044
+ if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
7045
+ if (product === "Config" || product.includes("Config")) return "Config";
7046
+ if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
7047
+ return "Other";
7048
+ }
7049
+ var SECURITY_HUB_SUB_CAT_ORDER = ["FSBP", "Inspector", "GuardDuty", "Config", "Access Analyzer", "Other"];
7009
7050
  function scoreColor(score) {
7010
7051
  if (score >= 80) return "#22c55e";
7011
7052
  if (score >= 50) return "#eab308";
@@ -7383,6 +7424,47 @@ function generateHtmlReport(scanResults, history, lang) {
7383
7424
  const allFindings = modules.flatMap(
7384
7425
  (m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
7385
7426
  );
7427
+ const shModule = modules.find((m) => m.module === "security_hub_findings");
7428
+ const shSubCats = [];
7429
+ if (shModule && shModule.findingsCount > 0) {
7430
+ const catMap = {};
7431
+ for (const f of shModule.findings) {
7432
+ const cat = getSecurityHubSource(f);
7433
+ if (!catMap[cat]) catMap[cat] = [];
7434
+ catMap[cat].push(f);
7435
+ }
7436
+ for (const cat of SECURITY_HUB_SUB_CAT_ORDER) {
7437
+ const catFindings = catMap[cat];
7438
+ if (catFindings && catFindings.length > 0) {
7439
+ const meta = t.securityHubSubCategories[cat];
7440
+ const shLabel = t.moduleNames[`sh:${cat}`] ?? meta?.label ?? cat;
7441
+ shSubCats.push({
7442
+ key: cat,
7443
+ label: shLabel,
7444
+ count: catFindings.length,
7445
+ findings: catFindings
7446
+ });
7447
+ }
7448
+ }
7449
+ }
7450
+ const DETECTION_ONLY_MODULES = /* @__PURE__ */ new Set([
7451
+ "guardduty_findings",
7452
+ "inspector_findings",
7453
+ "config_rules_findings",
7454
+ "access_analyzer_findings"
7455
+ ]);
7456
+ const barChartModules = modules.flatMap((m) => {
7457
+ if (DETECTION_ONLY_MODULES.has(m.module)) return [];
7458
+ if (m.module === "security_hub_findings" && shSubCats.length > 0) {
7459
+ return shSubCats.map((sc) => ({
7460
+ ...m,
7461
+ module: t.moduleNames[`sh:${sc.key}`] ?? sc.key,
7462
+ findingsCount: sc.count,
7463
+ findings: sc.findings
7464
+ }));
7465
+ }
7466
+ return [{ ...m, module: t.moduleNames[m.module] ?? m.module }];
7467
+ });
7386
7468
  let top5Html = "";
7387
7469
  if (allFindings.length > 0) {
7388
7470
  const top5 = [...allFindings].sort((a, b) => b.riskScore - a.riskScore).slice(0, 5);
@@ -7448,34 +7530,51 @@ ${rest}
7448
7530
  if (!moduleMap.has(mod)) moduleMap.set(mod, []);
7449
7531
  moduleMap.get(mod).push(f);
7450
7532
  }
7451
- const moduleEntries = [...moduleMap.entries()].sort((a, b) => {
7533
+ const expandedEntries = [];
7534
+ for (const [mod, findings] of moduleMap.entries()) {
7535
+ if (DETECTION_ONLY_MODULES.has(mod)) continue;
7536
+ if (mod === "security_hub_findings" && shSubCats.length > 0) {
7537
+ for (const sc of shSubCats) {
7538
+ expandedEntries.push([sc.key, sc.findings, sc.label]);
7539
+ }
7540
+ } else {
7541
+ expandedEntries.push([mod, findings, null]);
7542
+ }
7543
+ }
7544
+ const moduleEntries = expandedEntries.sort((a, b) => {
7452
7545
  const aHasCritHigh = a[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
7453
7546
  const bHasCritHigh = b[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
7454
7547
  if (aHasCritHigh !== bHasCritHigh) return aHasCritHigh ? -1 : 1;
7455
7548
  return b[1].length - a[1].length;
7456
7549
  });
7457
- findingsHtml = moduleEntries.map(([modName, modFindings]) => {
7458
- const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
7459
- for (const f of modFindings) sevCounts[f.severity]++;
7460
- const badges = SEVERITY_ORDER2.filter((sev) => sevCounts[sev] > 0).map((sev) => `<span class="badge badge-${sev.toLowerCase()}">${sevCounts[sev]} ${sev.charAt(0) + sev.slice(1).toLowerCase()}</span>`).join(" ");
7461
- const sevGroups = SEVERITY_ORDER2.map((sev) => {
7462
- const findings = modFindings.filter((f) => f.severity === sev);
7463
- if (findings.length === 0) return "";
7464
- findings.sort((a, b) => b.riskScore - a.riskScore);
7550
+ const renderSeverityGroups = (findings) => {
7551
+ return SEVERITY_ORDER2.map((sev) => {
7552
+ const sevFindings = findings.filter((f) => f.severity === sev);
7553
+ if (sevFindings.length === 0) return "";
7554
+ sevFindings.sort((a, b) => b.riskScore - a.riskScore);
7465
7555
  const emoji = SEV_EMOJI[sev] ?? "";
7466
7556
  const label = sev.charAt(0) + sev.slice(1).toLowerCase();
7467
7557
  return `<details class="severity-group-fold">
7468
- <summary><h4>${emoji} ${label} (${findings.length})</h4></summary>
7469
- ${renderCards(findings)}
7558
+ <summary><h4>${emoji} ${label} (${sevFindings.length})</h4></summary>
7559
+ ${renderCards(sevFindings)}
7470
7560
  </details>`;
7471
7561
  }).filter(Boolean).join("\n");
7562
+ };
7563
+ const renderModuleBadges = (findings) => {
7564
+ const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
7565
+ for (const f of findings) sevCounts[f.severity]++;
7566
+ return SEVERITY_ORDER2.filter((sev) => sevCounts[sev] > 0).map((sev) => `<span class="badge badge-${sev.toLowerCase()}">${sevCounts[sev]} ${sev.charAt(0) + sev.slice(1).toLowerCase()}</span>`).join(" ");
7567
+ };
7568
+ findingsHtml = moduleEntries.map(([modName, modFindings, subCatLabel]) => {
7569
+ const badges = renderModuleBadges(modFindings);
7570
+ const displayName = subCatLabel ?? (t.moduleNames[modName] ?? modName);
7472
7571
  return `<details class="module-fold">
7473
7572
  <summary>
7474
- <h3>&#128274; ${esc(modName)} (${modFindings.length})</h3>
7573
+ <h3>&#128274; ${esc(displayName)} (${modFindings.length})</h3>
7475
7574
  <span class="module-badges">${badges}</span>
7476
7575
  </summary>
7477
7576
  <div class="module-body">
7478
- ${sevGroups}
7577
+ ${renderSeverityGroups(modFindings)}
7479
7578
  </div>
7480
7579
  </details>`;
7481
7580
  }).join("\n");
@@ -7495,8 +7594,29 @@ ${rest}
7495
7594
  </div>
7496
7595
  </section>`;
7497
7596
  }
7498
- const statsRows = modules.map(
7499
- (m) => `<tr><td>${esc(m.module)}</td><td>${m.resourcesScanned}</td><td>${m.findingsCount}</td><td>${m.status === "success" ? "&#10003;" : "&#10007;"}</td></tr>`
7597
+ const isModuleDisabled = (m) => {
7598
+ if (!m.warnings?.length) return void 0;
7599
+ const w = m.warnings.find(
7600
+ (w2) => SERVICE_NOT_ENABLED_PATTERNS.some((p) => w2.includes(p))
7601
+ );
7602
+ return w;
7603
+ };
7604
+ const statsRows = modules.flatMap(
7605
+ (m) => {
7606
+ if (DETECTION_ONLY_MODULES.has(m.module)) return [];
7607
+ if (m.module === "security_hub_findings" && shSubCats.length > 0) {
7608
+ return shSubCats.map(
7609
+ (sc) => `<tr><td>${esc(sc.label)}</td><td>${m.resourcesScanned}</td><td>${sc.count}</td><td>&#10003;</td></tr>`
7610
+ );
7611
+ }
7612
+ const disabledWarning = isModuleDisabled(m);
7613
+ if (disabledWarning) {
7614
+ const rec = t.serviceRecommendations[m.module];
7615
+ const reason = rec ? rec.action : disabledWarning;
7616
+ return [`<tr><td>${esc(t.moduleNames[m.module] ?? m.module)}</td><td>-</td><td>-</td><td style="color:#eab308">&#9888; ${esc(reason)}</td></tr>`];
7617
+ }
7618
+ return [`<tr><td>${esc(t.moduleNames[m.module] ?? m.module)}</td><td>${m.resourcesScanned}</td><td>${m.findingsCount}</td><td>${m.status === "success" ? "&#10003;" : "&#10007;"}</td></tr>`];
7619
+ }
7500
7620
  ).join("\n");
7501
7621
  let recsHtml = "";
7502
7622
  if (summary.totalFindings > 0) {
@@ -7504,6 +7624,9 @@ ${rest}
7504
7624
  const kbPatches = [];
7505
7625
  let kbSeverity = "LOW";
7506
7626
  let kbUrl;
7627
+ const cveList = [];
7628
+ let cveSeverity = "LOW";
7629
+ let cveUrl;
7507
7630
  const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7508
7631
  for (const f of allFindings) {
7509
7632
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
@@ -7516,6 +7639,13 @@ ${rest}
7516
7639
  if (!kbUrl && url) kbUrl = url;
7517
7640
  continue;
7518
7641
  }
7642
+ const cveMatch = f.title.match(/CVE-[\d-]+/);
7643
+ if (cveMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
7644
+ cveList.push(cveMatch[0]);
7645
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(cveSeverity)) cveSeverity = f.severity;
7646
+ if (!cveUrl && url) cveUrl = url;
7647
+ continue;
7648
+ }
7519
7649
  if (f.module === "security_hub_findings") {
7520
7650
  const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
7521
7651
  if (controlMatch) {
@@ -7532,6 +7662,23 @@ ${rest}
7532
7662
  continue;
7533
7663
  }
7534
7664
  }
7665
+ if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
7666
+ const template = getRecommendationTemplate(rem);
7667
+ if (template !== rem) {
7668
+ const templateKey = `tmpl:${f.module}:${template}`;
7669
+ const existingTmpl = recMap.get(templateKey);
7670
+ if (existingTmpl) {
7671
+ existingTmpl.count++;
7672
+ if (!existingTmpl.url && url) existingTmpl.url = url;
7673
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
7674
+ existingTmpl.severity = f.severity;
7675
+ }
7676
+ continue;
7677
+ }
7678
+ recMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
7679
+ continue;
7680
+ }
7681
+ }
7535
7682
  const existing = recMap.get(rem);
7536
7683
  if (existing) {
7537
7684
  existing.count++;
@@ -7548,8 +7695,14 @@ ${rest}
7548
7695
  const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
7549
7696
  recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
7550
7697
  }
7698
+ if (cveList.length > 0) {
7699
+ const unique = [...new Set(cveList)];
7700
+ const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
7701
+ const cveText = (lang ?? "zh") === "zh" ? `\u4FEE\u590D ${unique.length} \u4E2A\u8F6F\u4EF6\u6F0F\u6D1E (${cveDisplay})\uFF0C\u66F4\u65B0\u53D7\u5F71\u54CD\u7684\u8F6F\u4EF6\u5305\u5230\u6700\u65B0\u7248\u672C` : `Fix ${unique.length} software vulnerabilities (${cveDisplay}) \u2014 update affected packages to latest patched versions`;
7702
+ recMap.set("__cve__", { text: cveText, severity: cveSeverity, count: 1, url: cveUrl });
7703
+ }
7551
7704
  for (const [key, rec] of recMap) {
7552
- if (key.startsWith("ctrl:") && rec.count > 1) {
7705
+ if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
7553
7706
  rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
7554
7707
  rec.count = 1;
7555
7708
  }
@@ -7616,7 +7769,7 @@ ${remaining.map(renderRec).join("\n")}
7616
7769
  </div>
7617
7770
  <div class="chart-box">
7618
7771
  <div class="chart-title">${esc(t.findingsByModule)}</div>
7619
- ${barChart(modules, t.allModulesClean)}
7772
+ ${barChart(barChartModules, t.allModulesClean)}
7620
7773
  </div>
7621
7774
  </section>
7622
7775
 
@@ -7789,6 +7942,9 @@ ${itemsHtml}
7789
7942
  const mlpsKbPatches = [];
7790
7943
  let mlpsKbSeverity = "LOW";
7791
7944
  let mlpsKbUrl;
7945
+ const mlpsCveList = [];
7946
+ let mlpsCveSeverity = "LOW";
7947
+ let mlpsCveUrl;
7792
7948
  const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7793
7949
  for (const r of failedResults) {
7794
7950
  for (const f of r.relatedFindings) {
@@ -7802,6 +7958,13 @@ ${itemsHtml}
7802
7958
  if (!mlpsKbUrl && url) mlpsKbUrl = url;
7803
7959
  continue;
7804
7960
  }
7961
+ const cveMatch = f.title.match(/CVE-[\d-]+/);
7962
+ if (cveMatch) {
7963
+ mlpsCveList.push(cveMatch[0]);
7964
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsCveSeverity)) mlpsCveSeverity = f.severity;
7965
+ if (!mlpsCveUrl && url) mlpsCveUrl = url;
7966
+ continue;
7967
+ }
7805
7968
  if (f.module === "security_hub_findings") {
7806
7969
  const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
7807
7970
  if (controlMatch) {
@@ -7818,6 +7981,23 @@ ${itemsHtml}
7818
7981
  continue;
7819
7982
  }
7820
7983
  }
7984
+ if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
7985
+ const template = getRecommendationTemplate(rem);
7986
+ if (template !== rem) {
7987
+ const templateKey = `tmpl:${f.module}:${template}`;
7988
+ const existingTmpl = mlpsRecMap.get(templateKey);
7989
+ if (existingTmpl) {
7990
+ existingTmpl.count++;
7991
+ if (!existingTmpl.url && url) existingTmpl.url = url;
7992
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
7993
+ existingTmpl.severity = f.severity;
7994
+ }
7995
+ continue;
7996
+ }
7997
+ mlpsRecMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
7998
+ continue;
7999
+ }
8000
+ }
7821
8001
  const existing = mlpsRecMap.get(rem);
7822
8002
  if (existing) {
7823
8003
  existing.count++;
@@ -7835,8 +8015,14 @@ ${itemsHtml}
7835
8015
  const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
7836
8016
  mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
7837
8017
  }
8018
+ if (mlpsCveList.length > 0) {
8019
+ const unique = [...new Set(mlpsCveList)];
8020
+ const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
8021
+ const cveText = (lang ?? "zh") === "zh" ? `\u4FEE\u590D ${unique.length} \u4E2A\u8F6F\u4EF6\u6F0F\u6D1E (${cveDisplay})\uFF0C\u66F4\u65B0\u53D7\u5F71\u54CD\u7684\u8F6F\u4EF6\u5305\u5230\u6700\u65B0\u7248\u672C` : `Fix ${unique.length} software vulnerabilities (${cveDisplay}) \u2014 update affected packages to latest patched versions`;
8022
+ mlpsRecMap.set("__cve__", { text: cveText, severity: mlpsCveSeverity, count: 1, url: mlpsCveUrl });
8023
+ }
7838
8024
  for (const [key, rec] of mlpsRecMap) {
7839
- if (key.startsWith("ctrl:") && rec.count > 1) {
8025
+ if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
7840
8026
  rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
7841
8027
  rec.count = 1;
7842
8028
  }
@@ -8132,7 +8318,6 @@ Detects which AWS security services are enabled and assesses overall security ma
8132
8318
  - **GuardDuty not enabled** \u2014 Risk 7.5: Provides continuous threat detection.
8133
8319
  - **Inspector not enabled** \u2014 Risk 6.0: Scans for software vulnerabilities.
8134
8320
  - **AWS Config not enabled** \u2014 Risk 6.0: Tracks configuration changes.
8135
- - **Macie not enabled** \u2014 Risk 5.0: Detects sensitive data in S3 (not available in China regions).
8136
8321
  - CloudTrail detection is included for coverage metrics.
8137
8322
 
8138
8323
  ### Maturity Levels
@@ -8140,8 +8325,8 @@ Detects which AWS security services are enabled and assesses overall security ma
8140
8325
  |------------------|-------|
8141
8326
  | 0\u20131 | Basic |
8142
8327
  | 2\u20133 | Intermediate |
8143
- | 4\u20135 | Advanced |
8144
- | 6 | Comprehensive |
8328
+ | 4 | Advanced |
8329
+ | 5 | Comprehensive |
8145
8330
 
8146
8331
  ## 2. Security Hub Findings (security_hub_findings)
8147
8332
  Aggregates active findings from AWS Security Hub. Replaces individual config scanners (SG, S3, IAM, CloudTrail, RDS, EBS, VPC, etc.) with centralized compliance checks from FSBP, CIS, and PCI DSS standards.
@@ -8275,7 +8460,7 @@ import { readFileSync as readFileSync2 } from "fs";
8275
8460
  import { join as join2, dirname } from "path";
8276
8461
  import { fileURLToPath } from "url";
8277
8462
  var MODULE_DESCRIPTIONS = {
8278
- service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config, Macie) are enabled and assesses security maturity.",
8463
+ service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config) are enabled and assesses security maturity.",
8279
8464
  secret_exposure: "Checks Lambda env vars and EC2 userData for exposed secrets (AWS keys, private keys, passwords).",
8280
8465
  ssl_certificate: "Checks ACM certificates for expiry, failed status, and upcoming renewals.",
8281
8466
  dns_dangling: "Checks Route53 CNAME records for dangling DNS (subdomain takeover risk).",
@@ -8710,14 +8895,12 @@ function createServer(defaultRegion) {
8710
8895
  "Security Hub": "+300 security checks",
8711
8896
  "GuardDuty": "Threat detection",
8712
8897
  "Inspector": "Vulnerability scanning",
8713
- "AWS Config": "Configuration tracking",
8714
- "Macie": "Sensitive data detection"
8898
+ "AWS Config": "Configuration tracking"
8715
8899
  };
8716
8900
  const serviceFreeTrials = {
8717
8901
  "Security Hub": true,
8718
8902
  "GuardDuty": true,
8719
- "Inspector": true,
8720
- "Macie": true
8903
+ "Inspector": true
8721
8904
  };
8722
8905
  const services = detection.services;
8723
8906
  const coveragePercent = detection.coveragePercent;
@@ -8752,7 +8935,7 @@ function createServer(defaultRegion) {
8752
8935
  lines.push("");
8753
8936
  lines.push("### Recommendations (Priority Order)");
8754
8937
  lines.push("");
8755
- const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "Macie", "CloudTrail"];
8938
+ const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "CloudTrail"];
8756
8939
  const sorted = disabled.sort(
8757
8940
  (a, b) => priorityOrder.indexOf(a.name) - priorityOrder.indexOf(b.name)
8758
8941
  );
@@ -8775,7 +8958,7 @@ function createServer(defaultRegion) {
8775
8958
  const nextMilestones = {
8776
8959
  basic: { level: "Intermediate", target: 2, suggestions: ["Security Hub", "GuardDuty"] },
8777
8960
  intermediate: { level: "Advanced", target: 4, suggestions: ["Inspector", "AWS Config"] },
8778
- advanced: { level: "Comprehensive", target: 6, suggestions: ["Macie"] }
8961
+ advanced: { level: "Comprehensive", target: 5, suggestions: ["CloudTrail"] }
8779
8962
  };
8780
8963
  const next = nextMilestones[maturityLevel];
8781
8964
  if (next) {