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/README.md CHANGED
@@ -125,11 +125,11 @@ For multi-account scanning across an AWS Organization:
125
125
  | `scan_idle_resources` | Find unused/idle resources |
126
126
  | `scan_disaster_recovery` | Assess disaster recovery readiness |
127
127
  | `scan_security_hub_findings` | Aggregate findings from AWS Security Hub |
128
- | `scan_guardduty_findings` | Aggregate findings from Amazon GuardDuty |
129
- | `scan_inspector_findings` | Aggregate findings from Amazon Inspector |
128
+ | `scan_guardduty_findings` | Check if GuardDuty is enabled (findings via Security Hub) |
129
+ | `scan_inspector_findings` | Check if Inspector is enabled (findings via Security Hub) |
130
130
  | `scan_trusted_advisor_findings` | Aggregate findings from AWS Trusted Advisor |
131
- | `scan_config_rules_findings` | Aggregate findings from AWS Config Rules |
132
- | `scan_access_analyzer_findings` | Aggregate findings from IAM Access Analyzer |
131
+ | `scan_config_rules_findings` | Check if Config is enabled (findings via Security Hub) |
132
+ | `scan_access_analyzer_findings` | Check if Access Analyzer is enabled (findings via Security Hub) |
133
133
  | `scan_patch_compliance_findings` | Aggregate findings from SSM Patch Compliance |
134
134
  | `scan_imdsv2_enforcement` | Check EC2 instances for IMDSv2 enforcement |
135
135
  | `scan_waf_coverage` | Check internet-facing ALBs for WAF Web ACL protection |
@@ -206,8 +206,6 @@ Attach this policy to the IAM user or role running the scanner. All actions are
206
206
  "lambda:ListFunctions",
207
207
  "lambda:GetFunction",
208
208
 
209
- "macie2:GetMacieSession",
210
-
211
209
  "organizations:ListAccounts",
212
210
 
213
211
  "rds:DescribeDBInstances",
@@ -248,7 +246,7 @@ Attach this policy to the IAM user or role running the scanner. All actions are
248
246
 
249
247
  | Module | What It Checks | Risk Score Range |
250
248
  |--------|---------------|-----------------|
251
- | **Service Detection** | Enabled security services (Security Hub, GuardDuty, Inspector, Config, Macie, CloudTrail) and maturity level | 5.0 - 7.5 |
249
+ | **Service Detection** | Enabled security services (Security Hub, GuardDuty, Inspector, Config, CloudTrail) and maturity level | 5.0 - 7.5 |
252
250
  | **Secret Exposure** | Lambda env vars and EC2 userData for exposed secrets (AWS keys, private keys, passwords) | 7.0 - 9.5 |
253
251
  | **SSL Certificate** | ACM certificate expiry, failed status, upcoming renewals | 5.5 - 9.0 |
254
252
  | **Dangling DNS** | Route53 CNAME records pointing to non-existent resources (subdomain takeover) | 7.0 - 8.5 |
@@ -237,7 +237,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
237
237
  import { z } from "zod";
238
238
 
239
239
  // src/version.ts
240
- var VERSION = "0.6.1";
240
+ var VERSION = "0.6.2";
241
241
 
242
242
  // src/utils/aws-client.ts
243
243
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -502,10 +502,6 @@ import {
502
502
  ConfigServiceClient,
503
503
  DescribeConfigurationRecordersCommand
504
504
  } from "@aws-sdk/client-config-service";
505
- import {
506
- Macie2Client,
507
- GetMacieSessionCommand
508
- } from "@aws-sdk/client-macie2";
509
505
  import {
510
506
  CloudTrailClient,
511
507
  DescribeTrailsCommand
@@ -549,7 +545,7 @@ function isNotEnabled(err) {
549
545
  err.name === "DisabledException" || err.message.includes("not enabled") || err.message.includes("not subscribed");
550
546
  }
551
547
  function computeMaturityLevel(enabledCount) {
552
- if (enabledCount >= 6) return "comprehensive";
548
+ if (enabledCount >= 5) return "comprehensive";
553
549
  if (enabledCount >= 4) return "advanced";
554
550
  if (enabledCount >= 2) return "intermediate";
555
551
  return "basic";
@@ -853,53 +849,6 @@ var ServiceDetectionScanner = class {
853
849
  services.push({ name: "AWS Config", enabled: null, details: "Detection error" });
854
850
  }
855
851
  }
856
- if (region.startsWith("cn-")) {
857
- services.push({ name: "Macie", enabled: null, details: "Not available in China regions" });
858
- warnings.push("Macie is not available in AWS China regions.");
859
- } else {
860
- try {
861
- const mc = createClient(Macie2Client, region, ctx.credentials);
862
- await mc.send(new GetMacieSessionCommand({}));
863
- services.push({
864
- name: "Macie",
865
- enabled: true,
866
- details: "Sensitive data detection active"
867
- });
868
- } catch (err) {
869
- if (isAccessDenied(err)) {
870
- warnings.push("Macie: insufficient permissions to check status");
871
- services.push({ name: "Macie", enabled: null, details: "Access denied" });
872
- } else if (isNotEnabled(err)) {
873
- services.push({
874
- name: "Macie",
875
- enabled: false,
876
- recommendation: "Enable Macie to detect sensitive data in S3",
877
- freeTrialAvailable: true
878
- });
879
- findings.push(
880
- makeFinding({
881
- riskScore: 5,
882
- title: "Amazon Macie is not enabled",
883
- resourceType: "AWS::Macie::Session",
884
- resourceId: "macie",
885
- resourceArn: `arn:${partition}:macie2:${region}:${accountId}:session`,
886
- region,
887
- description: "Amazon Macie is not enabled in this region. Macie uses machine learning to discover and protect sensitive data stored in S3.",
888
- impact: "Detects sensitive data (PII, credentials, financial data) in S3 buckets. Without it, sensitive data exposure may go unnoticed.",
889
- remediationSteps: [
890
- "Open the Amazon Macie console.",
891
- "Click 'Get Started' and enable Macie.",
892
- "Macie offers a 30-day free trial for sensitive data discovery.",
893
- "Configure automated sensitive data discovery jobs."
894
- ]
895
- })
896
- );
897
- } else {
898
- warnings.push(`Macie detection failed: ${err instanceof Error ? err.message : String(err)}`);
899
- services.push({ name: "Macie", enabled: null, details: "Detection error" });
900
- }
901
- }
902
- }
903
852
  const knownServices = services.filter((s) => s.enabled !== null);
904
853
  const enabledCount = services.filter((s) => s.enabled === true).length;
905
854
  const coveragePercent = knownServices.length > 0 ? Math.round(enabledCount / knownServices.length * 100) : 0;
@@ -3955,6 +3904,44 @@ var zhI18n = {
3955
3904
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
3956
3905
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
3957
3906
  },
3907
+ // Module display names
3908
+ moduleNames: {
3909
+ service_detection: "\u5B89\u5168\u670D\u52A1\u68C0\u6D4B",
3910
+ secret_exposure: "\u5BC6\u94A5\u66B4\u9732",
3911
+ ssl_certificate: "SSL \u8BC1\u4E66",
3912
+ dns_dangling: "\u60AC\u6302 DNS",
3913
+ network_reachability: "\u7F51\u7EDC\u53EF\u8FBE\u6027",
3914
+ iam_privilege_escalation: "IAM \u63D0\u6743\u5206\u6790",
3915
+ public_access_verify: "\u516C\u7F51\u8BBF\u95EE\u9A8C\u8BC1",
3916
+ tag_compliance: "\u6807\u7B7E\u5408\u89C4",
3917
+ idle_resources: "\u95F2\u7F6E\u8D44\u6E90",
3918
+ disaster_recovery: "\u707E\u5907\u8BC4\u4F30",
3919
+ security_hub_findings: "Security Hub",
3920
+ guardduty_findings: "GuardDuty",
3921
+ inspector_findings: "Inspector",
3922
+ trusted_advisor_findings: "Trusted Advisor",
3923
+ config_rules_findings: "Config Rules",
3924
+ access_analyzer_findings: "Access Analyzer",
3925
+ patch_compliance_findings: "\u8865\u4E01\u5408\u89C4",
3926
+ imdsv2_enforcement: "IMDSv2 \u5F3A\u5236",
3927
+ waf_coverage: "WAF \u8986\u76D6",
3928
+ // Security Hub sub-categories
3929
+ "sh:FSBP": "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5",
3930
+ "sh:Inspector": "\u8F6F\u4EF6\u6F0F\u6D1E",
3931
+ "sh:GuardDuty": "\u5A01\u80C1\u68C0\u6D4B",
3932
+ "sh:Config": "\u914D\u7F6E\u5408\u89C4",
3933
+ "sh:Access Analyzer": "\u5916\u90E8\u8BBF\u95EE",
3934
+ "sh:Other": "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0"
3935
+ },
3936
+ // Security Hub sub-categories
3937
+ securityHubSubCategories: {
3938
+ FSBP: { label: "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5" },
3939
+ Inspector: { label: "\u8F6F\u4EF6\u6F0F\u6D1E" },
3940
+ GuardDuty: { label: "\u5A01\u80C1\u68C0\u6D4B" },
3941
+ Config: { label: "\u914D\u7F6E\u5408\u89C4" },
3942
+ "Access Analyzer": { label: "\u5916\u90E8\u8BBF\u95EE" },
3943
+ Other: { label: "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0" }
3944
+ },
3958
3945
  // Service Recommendations
3959
3946
  notEnabled: "\u672A\u542F\u7528",
3960
3947
  serviceRecommendations: {
@@ -4187,6 +4174,44 @@ var enI18n = {
4187
4174
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
4188
4175
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
4189
4176
  },
4177
+ // Module display names
4178
+ moduleNames: {
4179
+ service_detection: "Security Service Detection",
4180
+ secret_exposure: "Secret Exposure",
4181
+ ssl_certificate: "SSL Certificate",
4182
+ dns_dangling: "Dangling DNS",
4183
+ network_reachability: "Network Reachability",
4184
+ iam_privilege_escalation: "IAM Privilege Escalation",
4185
+ public_access_verify: "Public Access Verification",
4186
+ tag_compliance: "Tag Compliance",
4187
+ idle_resources: "Idle Resources",
4188
+ disaster_recovery: "Disaster Recovery",
4189
+ security_hub_findings: "Security Hub",
4190
+ guardduty_findings: "GuardDuty",
4191
+ inspector_findings: "Inspector",
4192
+ trusted_advisor_findings: "Trusted Advisor",
4193
+ config_rules_findings: "Config Rules",
4194
+ access_analyzer_findings: "Access Analyzer",
4195
+ patch_compliance_findings: "Patch Compliance",
4196
+ imdsv2_enforcement: "IMDSv2 Enforcement",
4197
+ waf_coverage: "WAF Coverage",
4198
+ // Security Hub sub-categories
4199
+ "sh:FSBP": "Security Best Practices",
4200
+ "sh:Inspector": "Software Vulnerabilities",
4201
+ "sh:GuardDuty": "Threat Detection",
4202
+ "sh:Config": "Configuration Compliance",
4203
+ "sh:Access Analyzer": "External Access",
4204
+ "sh:Other": "Other Security Findings"
4205
+ },
4206
+ // Security Hub sub-categories
4207
+ securityHubSubCategories: {
4208
+ FSBP: { label: "Security Best Practices" },
4209
+ Inspector: { label: "Software Vulnerabilities" },
4210
+ GuardDuty: { label: "Threat Detection" },
4211
+ Config: { label: "Configuration Compliance" },
4212
+ "Access Analyzer": { label: "External Access" },
4213
+ Other: { label: "Other Security Findings" }
4214
+ },
4190
4215
  // Service Recommendations
4191
4216
  notEnabled: "Not Enabled",
4192
4217
  serviceRecommendations: {
@@ -4391,7 +4416,7 @@ function generateMarkdownReport(scanResults, lang) {
4391
4416
  for (const m of modules) {
4392
4417
  const status = m.status === "success" ? "\u2705" : "\u274C";
4393
4418
  lines.push(
4394
- `| ${m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
4419
+ `| ${t.moduleNames[m.module] ?? m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
4395
4420
  );
4396
4421
  }
4397
4422
  lines.push("");
@@ -7234,6 +7259,22 @@ var SEV_COLOR = {
7234
7259
  LOW: "#22c55e"
7235
7260
  };
7236
7261
  var SEVERITY_ORDER2 = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
7262
+ function getRecommendationTemplate(rem) {
7263
+ 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}");
7264
+ }
7265
+ function getSecurityHubSource(finding) {
7266
+ const impact = finding.impact ?? "";
7267
+ const match = impact.match(/^Source:\s*([^(]+)/);
7268
+ if (!match) return "Other";
7269
+ const product = match[1].trim();
7270
+ if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
7271
+ if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
7272
+ if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
7273
+ if (product === "Config" || product.includes("Config")) return "Config";
7274
+ if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
7275
+ return "Other";
7276
+ }
7277
+ var SECURITY_HUB_SUB_CAT_ORDER = ["FSBP", "Inspector", "GuardDuty", "Config", "Access Analyzer", "Other"];
7237
7278
  function scoreColor(score) {
7238
7279
  if (score >= 80) return "#22c55e";
7239
7280
  if (score >= 50) return "#eab308";
@@ -7611,6 +7652,47 @@ function generateHtmlReport(scanResults, history, lang) {
7611
7652
  const allFindings = modules.flatMap(
7612
7653
  (m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
7613
7654
  );
7655
+ const shModule = modules.find((m) => m.module === "security_hub_findings");
7656
+ const shSubCats = [];
7657
+ if (shModule && shModule.findingsCount > 0) {
7658
+ const catMap = {};
7659
+ for (const f of shModule.findings) {
7660
+ const cat = getSecurityHubSource(f);
7661
+ if (!catMap[cat]) catMap[cat] = [];
7662
+ catMap[cat].push(f);
7663
+ }
7664
+ for (const cat of SECURITY_HUB_SUB_CAT_ORDER) {
7665
+ const catFindings = catMap[cat];
7666
+ if (catFindings && catFindings.length > 0) {
7667
+ const meta = t.securityHubSubCategories[cat];
7668
+ const shLabel = t.moduleNames[`sh:${cat}`] ?? meta?.label ?? cat;
7669
+ shSubCats.push({
7670
+ key: cat,
7671
+ label: shLabel,
7672
+ count: catFindings.length,
7673
+ findings: catFindings
7674
+ });
7675
+ }
7676
+ }
7677
+ }
7678
+ const DETECTION_ONLY_MODULES = /* @__PURE__ */ new Set([
7679
+ "guardduty_findings",
7680
+ "inspector_findings",
7681
+ "config_rules_findings",
7682
+ "access_analyzer_findings"
7683
+ ]);
7684
+ const barChartModules = modules.flatMap((m) => {
7685
+ if (DETECTION_ONLY_MODULES.has(m.module)) return [];
7686
+ if (m.module === "security_hub_findings" && shSubCats.length > 0) {
7687
+ return shSubCats.map((sc) => ({
7688
+ ...m,
7689
+ module: t.moduleNames[`sh:${sc.key}`] ?? sc.key,
7690
+ findingsCount: sc.count,
7691
+ findings: sc.findings
7692
+ }));
7693
+ }
7694
+ return [{ ...m, module: t.moduleNames[m.module] ?? m.module }];
7695
+ });
7614
7696
  let top5Html = "";
7615
7697
  if (allFindings.length > 0) {
7616
7698
  const top5 = [...allFindings].sort((a, b) => b.riskScore - a.riskScore).slice(0, 5);
@@ -7676,34 +7758,51 @@ ${rest}
7676
7758
  if (!moduleMap.has(mod)) moduleMap.set(mod, []);
7677
7759
  moduleMap.get(mod).push(f);
7678
7760
  }
7679
- const moduleEntries = [...moduleMap.entries()].sort((a, b) => {
7761
+ const expandedEntries = [];
7762
+ for (const [mod, findings] of moduleMap.entries()) {
7763
+ if (DETECTION_ONLY_MODULES.has(mod)) continue;
7764
+ if (mod === "security_hub_findings" && shSubCats.length > 0) {
7765
+ for (const sc of shSubCats) {
7766
+ expandedEntries.push([sc.key, sc.findings, sc.label]);
7767
+ }
7768
+ } else {
7769
+ expandedEntries.push([mod, findings, null]);
7770
+ }
7771
+ }
7772
+ const moduleEntries = expandedEntries.sort((a, b) => {
7680
7773
  const aHasCritHigh = a[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
7681
7774
  const bHasCritHigh = b[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
7682
7775
  if (aHasCritHigh !== bHasCritHigh) return aHasCritHigh ? -1 : 1;
7683
7776
  return b[1].length - a[1].length;
7684
7777
  });
7685
- findingsHtml = moduleEntries.map(([modName, modFindings]) => {
7686
- const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
7687
- for (const f of modFindings) sevCounts[f.severity]++;
7688
- 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(" ");
7689
- const sevGroups = SEVERITY_ORDER2.map((sev) => {
7690
- const findings = modFindings.filter((f) => f.severity === sev);
7691
- if (findings.length === 0) return "";
7692
- findings.sort((a, b) => b.riskScore - a.riskScore);
7778
+ const renderSeverityGroups = (findings) => {
7779
+ return SEVERITY_ORDER2.map((sev) => {
7780
+ const sevFindings = findings.filter((f) => f.severity === sev);
7781
+ if (sevFindings.length === 0) return "";
7782
+ sevFindings.sort((a, b) => b.riskScore - a.riskScore);
7693
7783
  const emoji = SEV_EMOJI[sev] ?? "";
7694
7784
  const label = sev.charAt(0) + sev.slice(1).toLowerCase();
7695
7785
  return `<details class="severity-group-fold">
7696
- <summary><h4>${emoji} ${label} (${findings.length})</h4></summary>
7697
- ${renderCards(findings)}
7786
+ <summary><h4>${emoji} ${label} (${sevFindings.length})</h4></summary>
7787
+ ${renderCards(sevFindings)}
7698
7788
  </details>`;
7699
7789
  }).filter(Boolean).join("\n");
7790
+ };
7791
+ const renderModuleBadges = (findings) => {
7792
+ const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
7793
+ for (const f of findings) sevCounts[f.severity]++;
7794
+ 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(" ");
7795
+ };
7796
+ findingsHtml = moduleEntries.map(([modName, modFindings, subCatLabel]) => {
7797
+ const badges = renderModuleBadges(modFindings);
7798
+ const displayName = subCatLabel ?? (t.moduleNames[modName] ?? modName);
7700
7799
  return `<details class="module-fold">
7701
7800
  <summary>
7702
- <h3>&#128274; ${esc(modName)} (${modFindings.length})</h3>
7801
+ <h3>&#128274; ${esc(displayName)} (${modFindings.length})</h3>
7703
7802
  <span class="module-badges">${badges}</span>
7704
7803
  </summary>
7705
7804
  <div class="module-body">
7706
- ${sevGroups}
7805
+ ${renderSeverityGroups(modFindings)}
7707
7806
  </div>
7708
7807
  </details>`;
7709
7808
  }).join("\n");
@@ -7723,8 +7822,29 @@ ${rest}
7723
7822
  </div>
7724
7823
  </section>`;
7725
7824
  }
7726
- const statsRows = modules.map(
7727
- (m) => `<tr><td>${esc(m.module)}</td><td>${m.resourcesScanned}</td><td>${m.findingsCount}</td><td>${m.status === "success" ? "&#10003;" : "&#10007;"}</td></tr>`
7825
+ const isModuleDisabled = (m) => {
7826
+ if (!m.warnings?.length) return void 0;
7827
+ const w = m.warnings.find(
7828
+ (w2) => SERVICE_NOT_ENABLED_PATTERNS.some((p) => w2.includes(p))
7829
+ );
7830
+ return w;
7831
+ };
7832
+ const statsRows = modules.flatMap(
7833
+ (m) => {
7834
+ if (DETECTION_ONLY_MODULES.has(m.module)) return [];
7835
+ if (m.module === "security_hub_findings" && shSubCats.length > 0) {
7836
+ return shSubCats.map(
7837
+ (sc) => `<tr><td>${esc(sc.label)}</td><td>${m.resourcesScanned}</td><td>${sc.count}</td><td>&#10003;</td></tr>`
7838
+ );
7839
+ }
7840
+ const disabledWarning = isModuleDisabled(m);
7841
+ if (disabledWarning) {
7842
+ const rec = t.serviceRecommendations[m.module];
7843
+ const reason = rec ? rec.action : disabledWarning;
7844
+ return [`<tr><td>${esc(t.moduleNames[m.module] ?? m.module)}</td><td>-</td><td>-</td><td style="color:#eab308">&#9888; ${esc(reason)}</td></tr>`];
7845
+ }
7846
+ 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>`];
7847
+ }
7728
7848
  ).join("\n");
7729
7849
  let recsHtml = "";
7730
7850
  if (summary.totalFindings > 0) {
@@ -7732,6 +7852,9 @@ ${rest}
7732
7852
  const kbPatches = [];
7733
7853
  let kbSeverity = "LOW";
7734
7854
  let kbUrl;
7855
+ const cveList = [];
7856
+ let cveSeverity = "LOW";
7857
+ let cveUrl;
7735
7858
  const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7736
7859
  for (const f of allFindings) {
7737
7860
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
@@ -7744,6 +7867,13 @@ ${rest}
7744
7867
  if (!kbUrl && url) kbUrl = url;
7745
7868
  continue;
7746
7869
  }
7870
+ const cveMatch = f.title.match(/CVE-[\d-]+/);
7871
+ if (cveMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
7872
+ cveList.push(cveMatch[0]);
7873
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(cveSeverity)) cveSeverity = f.severity;
7874
+ if (!cveUrl && url) cveUrl = url;
7875
+ continue;
7876
+ }
7747
7877
  if (f.module === "security_hub_findings") {
7748
7878
  const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
7749
7879
  if (controlMatch) {
@@ -7760,6 +7890,23 @@ ${rest}
7760
7890
  continue;
7761
7891
  }
7762
7892
  }
7893
+ if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
7894
+ const template = getRecommendationTemplate(rem);
7895
+ if (template !== rem) {
7896
+ const templateKey = `tmpl:${f.module}:${template}`;
7897
+ const existingTmpl = recMap.get(templateKey);
7898
+ if (existingTmpl) {
7899
+ existingTmpl.count++;
7900
+ if (!existingTmpl.url && url) existingTmpl.url = url;
7901
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
7902
+ existingTmpl.severity = f.severity;
7903
+ }
7904
+ continue;
7905
+ }
7906
+ recMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
7907
+ continue;
7908
+ }
7909
+ }
7763
7910
  const existing = recMap.get(rem);
7764
7911
  if (existing) {
7765
7912
  existing.count++;
@@ -7776,8 +7923,14 @@ ${rest}
7776
7923
  const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
7777
7924
  recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
7778
7925
  }
7926
+ if (cveList.length > 0) {
7927
+ const unique = [...new Set(cveList)];
7928
+ const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
7929
+ 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`;
7930
+ recMap.set("__cve__", { text: cveText, severity: cveSeverity, count: 1, url: cveUrl });
7931
+ }
7779
7932
  for (const [key, rec] of recMap) {
7780
- if (key.startsWith("ctrl:") && rec.count > 1) {
7933
+ if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
7781
7934
  rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
7782
7935
  rec.count = 1;
7783
7936
  }
@@ -7844,7 +7997,7 @@ ${remaining.map(renderRec).join("\n")}
7844
7997
  </div>
7845
7998
  <div class="chart-box">
7846
7999
  <div class="chart-title">${esc(t.findingsByModule)}</div>
7847
- ${barChart(modules, t.allModulesClean)}
8000
+ ${barChart(barChartModules, t.allModulesClean)}
7848
8001
  </div>
7849
8002
  </section>
7850
8003
 
@@ -8017,6 +8170,9 @@ ${itemsHtml}
8017
8170
  const mlpsKbPatches = [];
8018
8171
  let mlpsKbSeverity = "LOW";
8019
8172
  let mlpsKbUrl;
8173
+ const mlpsCveList = [];
8174
+ let mlpsCveSeverity = "LOW";
8175
+ let mlpsCveUrl;
8020
8176
  const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
8021
8177
  for (const r of failedResults) {
8022
8178
  for (const f of r.relatedFindings) {
@@ -8030,6 +8186,13 @@ ${itemsHtml}
8030
8186
  if (!mlpsKbUrl && url) mlpsKbUrl = url;
8031
8187
  continue;
8032
8188
  }
8189
+ const cveMatch = f.title.match(/CVE-[\d-]+/);
8190
+ if (cveMatch) {
8191
+ mlpsCveList.push(cveMatch[0]);
8192
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsCveSeverity)) mlpsCveSeverity = f.severity;
8193
+ if (!mlpsCveUrl && url) mlpsCveUrl = url;
8194
+ continue;
8195
+ }
8033
8196
  if (f.module === "security_hub_findings") {
8034
8197
  const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
8035
8198
  if (controlMatch) {
@@ -8046,6 +8209,23 @@ ${itemsHtml}
8046
8209
  continue;
8047
8210
  }
8048
8211
  }
8212
+ if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
8213
+ const template = getRecommendationTemplate(rem);
8214
+ if (template !== rem) {
8215
+ const templateKey = `tmpl:${f.module}:${template}`;
8216
+ const existingTmpl = mlpsRecMap.get(templateKey);
8217
+ if (existingTmpl) {
8218
+ existingTmpl.count++;
8219
+ if (!existingTmpl.url && url) existingTmpl.url = url;
8220
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
8221
+ existingTmpl.severity = f.severity;
8222
+ }
8223
+ continue;
8224
+ }
8225
+ mlpsRecMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
8226
+ continue;
8227
+ }
8228
+ }
8049
8229
  const existing = mlpsRecMap.get(rem);
8050
8230
  if (existing) {
8051
8231
  existing.count++;
@@ -8063,8 +8243,14 @@ ${itemsHtml}
8063
8243
  const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
8064
8244
  mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
8065
8245
  }
8246
+ if (mlpsCveList.length > 0) {
8247
+ const unique = [...new Set(mlpsCveList)];
8248
+ const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
8249
+ 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`;
8250
+ mlpsRecMap.set("__cve__", { text: cveText, severity: mlpsCveSeverity, count: 1, url: mlpsCveUrl });
8251
+ }
8066
8252
  for (const [key, rec] of mlpsRecMap) {
8067
- if (key.startsWith("ctrl:") && rec.count > 1) {
8253
+ if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
8068
8254
  rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
8069
8255
  rec.count = 1;
8070
8256
  }
@@ -8360,7 +8546,6 @@ Detects which AWS security services are enabled and assesses overall security ma
8360
8546
  - **GuardDuty not enabled** \u2014 Risk 7.5: Provides continuous threat detection.
8361
8547
  - **Inspector not enabled** \u2014 Risk 6.0: Scans for software vulnerabilities.
8362
8548
  - **AWS Config not enabled** \u2014 Risk 6.0: Tracks configuration changes.
8363
- - **Macie not enabled** \u2014 Risk 5.0: Detects sensitive data in S3 (not available in China regions).
8364
8549
  - CloudTrail detection is included for coverage metrics.
8365
8550
 
8366
8551
  ### Maturity Levels
@@ -8368,8 +8553,8 @@ Detects which AWS security services are enabled and assesses overall security ma
8368
8553
  |------------------|-------|
8369
8554
  | 0\u20131 | Basic |
8370
8555
  | 2\u20133 | Intermediate |
8371
- | 4\u20135 | Advanced |
8372
- | 6 | Comprehensive |
8556
+ | 4 | Advanced |
8557
+ | 5 | Comprehensive |
8373
8558
 
8374
8559
  ## 2. Security Hub Findings (security_hub_findings)
8375
8560
  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.
@@ -8503,7 +8688,7 @@ import { readFileSync as readFileSync2 } from "fs";
8503
8688
  import { join as join2, dirname } from "path";
8504
8689
  import { fileURLToPath } from "url";
8505
8690
  var MODULE_DESCRIPTIONS = {
8506
- service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config, Macie) are enabled and assesses security maturity.",
8691
+ service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config) are enabled and assesses security maturity.",
8507
8692
  secret_exposure: "Checks Lambda env vars and EC2 userData for exposed secrets (AWS keys, private keys, passwords).",
8508
8693
  ssl_certificate: "Checks ACM certificates for expiry, failed status, and upcoming renewals.",
8509
8694
  dns_dangling: "Checks Route53 CNAME records for dangling DNS (subdomain takeover risk).",
@@ -8938,14 +9123,12 @@ function createServer(defaultRegion) {
8938
9123
  "Security Hub": "+300 security checks",
8939
9124
  "GuardDuty": "Threat detection",
8940
9125
  "Inspector": "Vulnerability scanning",
8941
- "AWS Config": "Configuration tracking",
8942
- "Macie": "Sensitive data detection"
9126
+ "AWS Config": "Configuration tracking"
8943
9127
  };
8944
9128
  const serviceFreeTrials = {
8945
9129
  "Security Hub": true,
8946
9130
  "GuardDuty": true,
8947
- "Inspector": true,
8948
- "Macie": true
9131
+ "Inspector": true
8949
9132
  };
8950
9133
  const services = detection.services;
8951
9134
  const coveragePercent = detection.coveragePercent;
@@ -8980,7 +9163,7 @@ function createServer(defaultRegion) {
8980
9163
  lines.push("");
8981
9164
  lines.push("### Recommendations (Priority Order)");
8982
9165
  lines.push("");
8983
- const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "Macie", "CloudTrail"];
9166
+ const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "CloudTrail"];
8984
9167
  const sorted = disabled.sort(
8985
9168
  (a, b) => priorityOrder.indexOf(a.name) - priorityOrder.indexOf(b.name)
8986
9169
  );
@@ -9003,7 +9186,7 @@ function createServer(defaultRegion) {
9003
9186
  const nextMilestones = {
9004
9187
  basic: { level: "Intermediate", target: 2, suggestions: ["Security Hub", "GuardDuty"] },
9005
9188
  intermediate: { level: "Advanced", target: 4, suggestions: ["Inspector", "AWS Config"] },
9006
- advanced: { level: "Comprehensive", target: 6, suggestions: ["Macie"] }
9189
+ advanced: { level: "Comprehensive", target: 5, suggestions: ["CloudTrail"] }
9007
9190
  };
9008
9191
  const next = nextMilestones[maturityLevel];
9009
9192
  if (next) {