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 +5 -7
- package/dist/bin/aws-security-mcp.js +265 -82
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.js +265 -82
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -2
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.
|
|
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 >=
|
|
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
|
|
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
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
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} (${
|
|
7469
|
-
${renderCards(
|
|
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>🔒 ${esc(
|
|
7573
|
+
<h3>🔒 ${esc(displayName)} (${modFindings.length})</h3>
|
|
7475
7574
|
<span class="module-badges">${badges}</span>
|
|
7476
7575
|
</summary>
|
|
7477
7576
|
<div class="module-body">
|
|
7478
|
-
${
|
|
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
|
|
7499
|
-
|
|
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>✓</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">⚠ ${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" ? "✓" : "✗"}</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(
|
|
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
|
|
8144
|
-
|
|
|
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
|
|
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", "
|
|
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:
|
|
8961
|
+
advanced: { level: "Comprehensive", target: 5, suggestions: ["CloudTrail"] }
|
|
8779
8962
|
};
|
|
8780
8963
|
const next = nextMilestones[maturityLevel];
|
|
8781
8964
|
if (next) {
|