aws-security-mcp 0.6.1 → 0.6.3
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 +355 -89
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.js +355 -89
- 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.3";
|
|
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;
|
|
@@ -3697,6 +3646,12 @@ var zhI18n = {
|
|
|
3697
3646
|
trendTitle: "30\u65E5\u8D8B\u52BF",
|
|
3698
3647
|
findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
|
|
3699
3648
|
showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
|
|
3649
|
+
// Filter toolbar
|
|
3650
|
+
filterSeverity: "\u4E25\u91CD\u6027\uFF1A",
|
|
3651
|
+
filterModule: "\u6A21\u5757\uFF1A",
|
|
3652
|
+
filterAll: "\u5168\u90E8",
|
|
3653
|
+
filterAllModules: "\u5168\u90E8\u6A21\u5757",
|
|
3654
|
+
filterCountTpl: "\u663E\u793A {shown} / {total} \u4E2A\u53D1\u73B0",
|
|
3700
3655
|
// Extended — MLPS extras
|
|
3701
3656
|
// Markdown report
|
|
3702
3657
|
executiveSummary: "\u6267\u884C\u6458\u8981",
|
|
@@ -3727,6 +3682,44 @@ var zhI18n = {
|
|
|
3727
3682
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
|
|
3728
3683
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
|
|
3729
3684
|
},
|
|
3685
|
+
// Module display names
|
|
3686
|
+
moduleNames: {
|
|
3687
|
+
service_detection: "\u5B89\u5168\u670D\u52A1\u68C0\u6D4B",
|
|
3688
|
+
secret_exposure: "\u5BC6\u94A5\u66B4\u9732",
|
|
3689
|
+
ssl_certificate: "SSL \u8BC1\u4E66",
|
|
3690
|
+
dns_dangling: "\u60AC\u6302 DNS",
|
|
3691
|
+
network_reachability: "\u7F51\u7EDC\u53EF\u8FBE\u6027",
|
|
3692
|
+
iam_privilege_escalation: "IAM \u63D0\u6743\u5206\u6790",
|
|
3693
|
+
public_access_verify: "\u516C\u7F51\u8BBF\u95EE\u9A8C\u8BC1",
|
|
3694
|
+
tag_compliance: "\u6807\u7B7E\u5408\u89C4",
|
|
3695
|
+
idle_resources: "\u95F2\u7F6E\u8D44\u6E90",
|
|
3696
|
+
disaster_recovery: "\u707E\u5907\u8BC4\u4F30",
|
|
3697
|
+
security_hub_findings: "Security Hub",
|
|
3698
|
+
guardduty_findings: "GuardDuty",
|
|
3699
|
+
inspector_findings: "Inspector",
|
|
3700
|
+
trusted_advisor_findings: "Trusted Advisor",
|
|
3701
|
+
config_rules_findings: "Config Rules",
|
|
3702
|
+
access_analyzer_findings: "Access Analyzer",
|
|
3703
|
+
patch_compliance_findings: "\u8865\u4E01\u5408\u89C4",
|
|
3704
|
+
imdsv2_enforcement: "IMDSv2 \u5F3A\u5236",
|
|
3705
|
+
waf_coverage: "WAF \u8986\u76D6",
|
|
3706
|
+
// Security Hub sub-categories
|
|
3707
|
+
"sh:FSBP": "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5",
|
|
3708
|
+
"sh:Inspector": "\u8F6F\u4EF6\u6F0F\u6D1E",
|
|
3709
|
+
"sh:GuardDuty": "\u5A01\u80C1\u68C0\u6D4B",
|
|
3710
|
+
"sh:Config": "\u914D\u7F6E\u5408\u89C4",
|
|
3711
|
+
"sh:Access Analyzer": "\u5916\u90E8\u8BBF\u95EE",
|
|
3712
|
+
"sh:Other": "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0"
|
|
3713
|
+
},
|
|
3714
|
+
// Security Hub sub-categories
|
|
3715
|
+
securityHubSubCategories: {
|
|
3716
|
+
FSBP: { label: "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5" },
|
|
3717
|
+
Inspector: { label: "\u8F6F\u4EF6\u6F0F\u6D1E" },
|
|
3718
|
+
GuardDuty: { label: "\u5A01\u80C1\u68C0\u6D4B" },
|
|
3719
|
+
Config: { label: "\u914D\u7F6E\u5408\u89C4" },
|
|
3720
|
+
"Access Analyzer": { label: "\u5916\u90E8\u8BBF\u95EE" },
|
|
3721
|
+
Other: { label: "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0" }
|
|
3722
|
+
},
|
|
3730
3723
|
// Service Recommendations
|
|
3731
3724
|
notEnabled: "\u672A\u542F\u7528",
|
|
3732
3725
|
serviceRecommendations: {
|
|
@@ -3929,6 +3922,12 @@ var enI18n = {
|
|
|
3929
3922
|
trendTitle: "30-Day Trends",
|
|
3930
3923
|
findingsBySeverity: "Findings by Severity",
|
|
3931
3924
|
showMoreCount: (n) => `Show ${n} more\u2026`,
|
|
3925
|
+
// Filter toolbar
|
|
3926
|
+
filterSeverity: "Severity:",
|
|
3927
|
+
filterModule: "Module:",
|
|
3928
|
+
filterAll: "All",
|
|
3929
|
+
filterAllModules: "All Modules",
|
|
3930
|
+
filterCountTpl: "Showing {shown} / {total} findings",
|
|
3932
3931
|
// Extended \u2014 MLPS extras
|
|
3933
3932
|
// Markdown report
|
|
3934
3933
|
executiveSummary: "Executive Summary",
|
|
@@ -3959,6 +3958,44 @@ var enI18n = {
|
|
|
3959
3958
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
|
|
3960
3959
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
|
|
3961
3960
|
},
|
|
3961
|
+
// Module display names
|
|
3962
|
+
moduleNames: {
|
|
3963
|
+
service_detection: "Security Service Detection",
|
|
3964
|
+
secret_exposure: "Secret Exposure",
|
|
3965
|
+
ssl_certificate: "SSL Certificate",
|
|
3966
|
+
dns_dangling: "Dangling DNS",
|
|
3967
|
+
network_reachability: "Network Reachability",
|
|
3968
|
+
iam_privilege_escalation: "IAM Privilege Escalation",
|
|
3969
|
+
public_access_verify: "Public Access Verification",
|
|
3970
|
+
tag_compliance: "Tag Compliance",
|
|
3971
|
+
idle_resources: "Idle Resources",
|
|
3972
|
+
disaster_recovery: "Disaster Recovery",
|
|
3973
|
+
security_hub_findings: "Security Hub",
|
|
3974
|
+
guardduty_findings: "GuardDuty",
|
|
3975
|
+
inspector_findings: "Inspector",
|
|
3976
|
+
trusted_advisor_findings: "Trusted Advisor",
|
|
3977
|
+
config_rules_findings: "Config Rules",
|
|
3978
|
+
access_analyzer_findings: "Access Analyzer",
|
|
3979
|
+
patch_compliance_findings: "Patch Compliance",
|
|
3980
|
+
imdsv2_enforcement: "IMDSv2 Enforcement",
|
|
3981
|
+
waf_coverage: "WAF Coverage",
|
|
3982
|
+
// Security Hub sub-categories
|
|
3983
|
+
"sh:FSBP": "Security Best Practices",
|
|
3984
|
+
"sh:Inspector": "Software Vulnerabilities",
|
|
3985
|
+
"sh:GuardDuty": "Threat Detection",
|
|
3986
|
+
"sh:Config": "Configuration Compliance",
|
|
3987
|
+
"sh:Access Analyzer": "External Access",
|
|
3988
|
+
"sh:Other": "Other Security Findings"
|
|
3989
|
+
},
|
|
3990
|
+
// Security Hub sub-categories
|
|
3991
|
+
securityHubSubCategories: {
|
|
3992
|
+
FSBP: { label: "Security Best Practices" },
|
|
3993
|
+
Inspector: { label: "Software Vulnerabilities" },
|
|
3994
|
+
GuardDuty: { label: "Threat Detection" },
|
|
3995
|
+
Config: { label: "Configuration Compliance" },
|
|
3996
|
+
"Access Analyzer": { label: "External Access" },
|
|
3997
|
+
Other: { label: "Other Security Findings" }
|
|
3998
|
+
},
|
|
3962
3999
|
// Service Recommendations
|
|
3963
4000
|
notEnabled: "Not Enabled",
|
|
3964
4001
|
serviceRecommendations: {
|
|
@@ -4163,7 +4200,7 @@ function generateMarkdownReport(scanResults, lang) {
|
|
|
4163
4200
|
for (const m of modules) {
|
|
4164
4201
|
const status = m.status === "success" ? "\u2705" : "\u274C";
|
|
4165
4202
|
lines.push(
|
|
4166
|
-
`| ${m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
|
|
4203
|
+
`| ${t.moduleNames[m.module] ?? m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
|
|
4167
4204
|
);
|
|
4168
4205
|
}
|
|
4169
4206
|
lines.push("");
|
|
@@ -7006,6 +7043,22 @@ var SEV_COLOR = {
|
|
|
7006
7043
|
LOW: "#22c55e"
|
|
7007
7044
|
};
|
|
7008
7045
|
var SEVERITY_ORDER2 = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
7046
|
+
function getRecommendationTemplate(rem) {
|
|
7047
|
+
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}");
|
|
7048
|
+
}
|
|
7049
|
+
function getSecurityHubSource(finding) {
|
|
7050
|
+
const impact = finding.impact ?? "";
|
|
7051
|
+
const match = impact.match(/^Source:\s*([^(]+)/);
|
|
7052
|
+
if (!match) return "Other";
|
|
7053
|
+
const product = match[1].trim();
|
|
7054
|
+
if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
|
|
7055
|
+
if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
|
|
7056
|
+
if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
|
|
7057
|
+
if (product === "Config" || product.includes("Config")) return "Config";
|
|
7058
|
+
if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
|
|
7059
|
+
return "Other";
|
|
7060
|
+
}
|
|
7061
|
+
var SECURITY_HUB_SUB_CAT_ORDER = ["FSBP", "Inspector", "GuardDuty", "Config", "Access Analyzer", "Other"];
|
|
7009
7062
|
function scoreColor(score) {
|
|
7010
7063
|
if (score >= 80) return "#22c55e";
|
|
7011
7064
|
if (score >= 50) return "#eab308";
|
|
@@ -7190,7 +7243,16 @@ function sharedCss() {
|
|
|
7190
7243
|
.rec-body ol{padding-left:24px}
|
|
7191
7244
|
.rec-body li{margin-bottom:8px;color:#cbd5e1;font-size:13px}
|
|
7192
7245
|
.rec-body .badge{margin-right:6px;vertical-align:middle}
|
|
7246
|
+
.filter-toolbar{display:flex;flex-wrap:wrap;gap:16px;align-items:center;margin-bottom:20px;padding:12px 16px;background:#1e293b;border:1px solid #334155;border-radius:8px}
|
|
7247
|
+
.filter-group{display:flex;align-items:center;gap:8px}
|
|
7248
|
+
.filter-label{color:#94a3b8;font-size:13px}
|
|
7249
|
+
.filter-btn{padding:4px 12px;border-radius:4px;border:1px solid #475569;background:transparent;color:#cbd5e1;cursor:pointer;font-size:13px}
|
|
7250
|
+
.filter-btn:hover{background:#334155}
|
|
7251
|
+
.filter-btn.active{background:#3b82f6;border-color:#3b82f6;color:#fff}
|
|
7252
|
+
.filter-select{padding:4px 8px;border-radius:4px;border:1px solid #475569;background:#0f172a;color:#cbd5e1;font-size:13px}
|
|
7253
|
+
.filter-count{color:#64748b;font-size:13px;margin-left:auto}
|
|
7193
7254
|
@media print{
|
|
7255
|
+
.filter-toolbar{display:none !important}
|
|
7194
7256
|
body{background:#fff;color:#1e293b;-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
|
7195
7257
|
.container{max-width:100%;padding:20px}
|
|
7196
7258
|
.card,.score-card,.stat-card,.chart-box,.finding-fold,.top5-card,.trend-chart,.category-fold,.module-fold,.finding-card,.rec-fold{background:#fff;border:1px solid #e2e8f0}
|
|
@@ -7383,6 +7445,47 @@ function generateHtmlReport(scanResults, history, lang) {
|
|
|
7383
7445
|
const allFindings = modules.flatMap(
|
|
7384
7446
|
(m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
|
|
7385
7447
|
);
|
|
7448
|
+
const shModule = modules.find((m) => m.module === "security_hub_findings");
|
|
7449
|
+
const shSubCats = [];
|
|
7450
|
+
if (shModule && shModule.findingsCount > 0) {
|
|
7451
|
+
const catMap = {};
|
|
7452
|
+
for (const f of shModule.findings) {
|
|
7453
|
+
const cat = getSecurityHubSource(f);
|
|
7454
|
+
if (!catMap[cat]) catMap[cat] = [];
|
|
7455
|
+
catMap[cat].push(f);
|
|
7456
|
+
}
|
|
7457
|
+
for (const cat of SECURITY_HUB_SUB_CAT_ORDER) {
|
|
7458
|
+
const catFindings = catMap[cat];
|
|
7459
|
+
if (catFindings && catFindings.length > 0) {
|
|
7460
|
+
const meta = t.securityHubSubCategories[cat];
|
|
7461
|
+
const shLabel = t.moduleNames[`sh:${cat}`] ?? meta?.label ?? cat;
|
|
7462
|
+
shSubCats.push({
|
|
7463
|
+
key: cat,
|
|
7464
|
+
label: shLabel,
|
|
7465
|
+
count: catFindings.length,
|
|
7466
|
+
findings: catFindings
|
|
7467
|
+
});
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
}
|
|
7471
|
+
const DETECTION_ONLY_MODULES = /* @__PURE__ */ new Set([
|
|
7472
|
+
"guardduty_findings",
|
|
7473
|
+
"inspector_findings",
|
|
7474
|
+
"config_rules_findings",
|
|
7475
|
+
"access_analyzer_findings"
|
|
7476
|
+
]);
|
|
7477
|
+
const barChartModules = modules.flatMap((m) => {
|
|
7478
|
+
if (DETECTION_ONLY_MODULES.has(m.module)) return [];
|
|
7479
|
+
if (m.module === "security_hub_findings" && shSubCats.length > 0) {
|
|
7480
|
+
return shSubCats.map((sc) => ({
|
|
7481
|
+
...m,
|
|
7482
|
+
module: t.moduleNames[`sh:${sc.key}`] ?? sc.key,
|
|
7483
|
+
findingsCount: sc.count,
|
|
7484
|
+
findings: sc.findings
|
|
7485
|
+
}));
|
|
7486
|
+
}
|
|
7487
|
+
return [{ ...m, module: t.moduleNames[m.module] ?? m.module }];
|
|
7488
|
+
});
|
|
7386
7489
|
let top5Html = "";
|
|
7387
7490
|
if (allFindings.length > 0) {
|
|
7388
7491
|
const top5 = [...allFindings].sort((a, b) => b.riskScore - a.riskScore).slice(0, 5);
|
|
@@ -7408,13 +7511,14 @@ function generateHtmlReport(scanResults, history, lang) {
|
|
|
7408
7511
|
</section>`;
|
|
7409
7512
|
}
|
|
7410
7513
|
let findingsHtml;
|
|
7514
|
+
let filterToolbarHtml = "";
|
|
7411
7515
|
if (summary.totalFindings === 0) {
|
|
7412
7516
|
findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
|
|
7413
7517
|
} else {
|
|
7414
7518
|
const FOLD_THRESHOLD = 20;
|
|
7415
|
-
const renderCard = (f) => {
|
|
7519
|
+
const renderCard = (f, moduleKey) => {
|
|
7416
7520
|
const sev = f.severity.toLowerCase();
|
|
7417
|
-
return `<div class="finding-card sev-${esc(sev)}">
|
|
7521
|
+
return `<div class="finding-card sev-${esc(sev)}" data-severity="${esc(f.severity)}" data-module="${esc(moduleKey)}">
|
|
7418
7522
|
<span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
|
|
7419
7523
|
<span class="finding-title-text">${esc(f.title)}</span>
|
|
7420
7524
|
<span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
|
|
@@ -7425,12 +7529,12 @@ function generateHtmlReport(scanResults, history, lang) {
|
|
|
7425
7529
|
</div></details>
|
|
7426
7530
|
</div>`;
|
|
7427
7531
|
};
|
|
7428
|
-
const renderCards = (findings) => {
|
|
7532
|
+
const renderCards = (findings, moduleKey) => {
|
|
7429
7533
|
if (findings.length <= FOLD_THRESHOLD) {
|
|
7430
|
-
return findings.map(renderCard).join("\n");
|
|
7534
|
+
return findings.map((f) => renderCard(f, moduleKey)).join("\n");
|
|
7431
7535
|
}
|
|
7432
|
-
const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
|
|
7433
|
-
const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
|
|
7536
|
+
const first = findings.slice(0, FOLD_THRESHOLD).map((f) => renderCard(f, moduleKey)).join("\n");
|
|
7537
|
+
const rest = findings.slice(FOLD_THRESHOLD).map((f) => renderCard(f, moduleKey)).join("\n");
|
|
7434
7538
|
return `${first}
|
|
7435
7539
|
<details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
|
|
7436
7540
|
${rest}
|
|
@@ -7448,37 +7552,76 @@ ${rest}
|
|
|
7448
7552
|
if (!moduleMap.has(mod)) moduleMap.set(mod, []);
|
|
7449
7553
|
moduleMap.get(mod).push(f);
|
|
7450
7554
|
}
|
|
7451
|
-
const
|
|
7555
|
+
const expandedEntries = [];
|
|
7556
|
+
for (const [mod, findings] of moduleMap.entries()) {
|
|
7557
|
+
if (DETECTION_ONLY_MODULES.has(mod)) continue;
|
|
7558
|
+
if (mod === "security_hub_findings" && shSubCats.length > 0) {
|
|
7559
|
+
for (const sc of shSubCats) {
|
|
7560
|
+
expandedEntries.push([sc.key, sc.findings, sc.label]);
|
|
7561
|
+
}
|
|
7562
|
+
} else {
|
|
7563
|
+
expandedEntries.push([mod, findings, null]);
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7566
|
+
const moduleEntries = expandedEntries.sort((a, b) => {
|
|
7452
7567
|
const aHasCritHigh = a[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
7453
7568
|
const bHasCritHigh = b[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
7454
7569
|
if (aHasCritHigh !== bHasCritHigh) return aHasCritHigh ? -1 : 1;
|
|
7455
7570
|
return b[1].length - a[1].length;
|
|
7456
7571
|
});
|
|
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);
|
|
7572
|
+
const renderSeverityGroups = (findings, moduleKey) => {
|
|
7573
|
+
return SEVERITY_ORDER2.map((sev) => {
|
|
7574
|
+
const sevFindings = findings.filter((f) => f.severity === sev);
|
|
7575
|
+
if (sevFindings.length === 0) return "";
|
|
7576
|
+
sevFindings.sort((a, b) => b.riskScore - a.riskScore);
|
|
7465
7577
|
const emoji = SEV_EMOJI[sev] ?? "";
|
|
7466
7578
|
const label = sev.charAt(0) + sev.slice(1).toLowerCase();
|
|
7467
7579
|
return `<details class="severity-group-fold">
|
|
7468
|
-
<summary><h4>${emoji} ${label} (${
|
|
7469
|
-
${renderCards(
|
|
7580
|
+
<summary><h4>${emoji} ${label} (${sevFindings.length})</h4></summary>
|
|
7581
|
+
${renderCards(sevFindings, moduleKey)}
|
|
7470
7582
|
</details>`;
|
|
7471
7583
|
}).filter(Boolean).join("\n");
|
|
7472
|
-
|
|
7584
|
+
};
|
|
7585
|
+
const renderModuleBadges = (findings) => {
|
|
7586
|
+
const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
7587
|
+
for (const f of findings) sevCounts[f.severity]++;
|
|
7588
|
+
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(" ");
|
|
7589
|
+
};
|
|
7590
|
+
findingsHtml = moduleEntries.map(([modName, modFindings, subCatLabel]) => {
|
|
7591
|
+
const badges = renderModuleBadges(modFindings);
|
|
7592
|
+
const displayName = subCatLabel ?? (t.moduleNames[modName] ?? modName);
|
|
7593
|
+
return `<details class="module-fold" data-module="${esc(modName)}">
|
|
7473
7594
|
<summary>
|
|
7474
|
-
<h3>🔒 ${esc(
|
|
7595
|
+
<h3>🔒 ${esc(displayName)} (${modFindings.length})</h3>
|
|
7475
7596
|
<span class="module-badges">${badges}</span>
|
|
7476
7597
|
</summary>
|
|
7477
7598
|
<div class="module-body">
|
|
7478
|
-
${
|
|
7599
|
+
${renderSeverityGroups(modFindings, modName)}
|
|
7479
7600
|
</div>
|
|
7480
7601
|
</details>`;
|
|
7481
7602
|
}).join("\n");
|
|
7603
|
+
const moduleOptions = moduleEntries.map(([modKey, , subCatLabel]) => {
|
|
7604
|
+
const label = subCatLabel ?? (t.moduleNames[modKey] ?? modKey);
|
|
7605
|
+
return `<option value="${esc(modKey)}">${esc(label)}</option>`;
|
|
7606
|
+
}).join("\n ");
|
|
7607
|
+
filterToolbarHtml = `<div class="filter-toolbar" id="filterBar">
|
|
7608
|
+
<div class="filter-group">
|
|
7609
|
+
<span class="filter-label">${esc(t.filterSeverity)}</span>
|
|
7610
|
+
<button class="filter-btn active" data-severity="ALL">${esc(t.filterAll)}</button>
|
|
7611
|
+
<button class="filter-btn" data-severity="CRITICAL">Critical</button>
|
|
7612
|
+
<button class="filter-btn" data-severity="HIGH">High</button>
|
|
7613
|
+
<button class="filter-btn" data-severity="MEDIUM">Medium</button>
|
|
7614
|
+
<button class="filter-btn" data-severity="LOW">Low</button>
|
|
7615
|
+
</div>
|
|
7616
|
+
<div class="filter-group">
|
|
7617
|
+
<span class="filter-label">${esc(t.filterModule)}</span>
|
|
7618
|
+
<select class="filter-select" id="moduleFilter">
|
|
7619
|
+
<option value="ALL">${esc(t.filterAllModules)}</option>
|
|
7620
|
+
${moduleOptions}
|
|
7621
|
+
</select>
|
|
7622
|
+
</div>
|
|
7623
|
+
<div class="filter-count" id="filterCount" data-tpl="${esc(t.filterCountTpl)}"></div>
|
|
7624
|
+
</div>`;
|
|
7482
7625
|
}
|
|
7483
7626
|
let trendHtml = "";
|
|
7484
7627
|
if (history && history.length >= 2) {
|
|
@@ -7495,8 +7638,29 @@ ${rest}
|
|
|
7495
7638
|
</div>
|
|
7496
7639
|
</section>`;
|
|
7497
7640
|
}
|
|
7498
|
-
const
|
|
7499
|
-
|
|
7641
|
+
const isModuleDisabled = (m) => {
|
|
7642
|
+
if (!m.warnings?.length) return void 0;
|
|
7643
|
+
const w = m.warnings.find(
|
|
7644
|
+
(w2) => SERVICE_NOT_ENABLED_PATTERNS.some((p) => w2.includes(p))
|
|
7645
|
+
);
|
|
7646
|
+
return w;
|
|
7647
|
+
};
|
|
7648
|
+
const statsRows = modules.flatMap(
|
|
7649
|
+
(m) => {
|
|
7650
|
+
if (DETECTION_ONLY_MODULES.has(m.module)) return [];
|
|
7651
|
+
if (m.module === "security_hub_findings" && shSubCats.length > 0) {
|
|
7652
|
+
return shSubCats.map(
|
|
7653
|
+
(sc) => `<tr><td>${esc(sc.label)}</td><td>${m.resourcesScanned}</td><td>${sc.count}</td><td>✓</td></tr>`
|
|
7654
|
+
);
|
|
7655
|
+
}
|
|
7656
|
+
const disabledWarning = isModuleDisabled(m);
|
|
7657
|
+
if (disabledWarning) {
|
|
7658
|
+
const rec = t.serviceRecommendations[m.module];
|
|
7659
|
+
const reason = rec ? rec.action : disabledWarning;
|
|
7660
|
+
return [`<tr><td>${esc(t.moduleNames[m.module] ?? m.module)}</td><td>-</td><td>-</td><td style="color:#eab308">⚠ ${esc(reason)}</td></tr>`];
|
|
7661
|
+
}
|
|
7662
|
+
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>`];
|
|
7663
|
+
}
|
|
7500
7664
|
).join("\n");
|
|
7501
7665
|
let recsHtml = "";
|
|
7502
7666
|
if (summary.totalFindings > 0) {
|
|
@@ -7504,6 +7668,9 @@ ${rest}
|
|
|
7504
7668
|
const kbPatches = [];
|
|
7505
7669
|
let kbSeverity = "LOW";
|
|
7506
7670
|
let kbUrl;
|
|
7671
|
+
const cveList = [];
|
|
7672
|
+
let cveSeverity = "LOW";
|
|
7673
|
+
let cveUrl;
|
|
7507
7674
|
const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
7508
7675
|
for (const f of allFindings) {
|
|
7509
7676
|
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
@@ -7516,6 +7683,13 @@ ${rest}
|
|
|
7516
7683
|
if (!kbUrl && url) kbUrl = url;
|
|
7517
7684
|
continue;
|
|
7518
7685
|
}
|
|
7686
|
+
const cveMatch = f.title.match(/CVE-[\d-]+/);
|
|
7687
|
+
if (cveMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
|
|
7688
|
+
cveList.push(cveMatch[0]);
|
|
7689
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(cveSeverity)) cveSeverity = f.severity;
|
|
7690
|
+
if (!cveUrl && url) cveUrl = url;
|
|
7691
|
+
continue;
|
|
7692
|
+
}
|
|
7519
7693
|
if (f.module === "security_hub_findings") {
|
|
7520
7694
|
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
7521
7695
|
if (controlMatch) {
|
|
@@ -7532,6 +7706,23 @@ ${rest}
|
|
|
7532
7706
|
continue;
|
|
7533
7707
|
}
|
|
7534
7708
|
}
|
|
7709
|
+
if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
|
|
7710
|
+
const template = getRecommendationTemplate(rem);
|
|
7711
|
+
if (template !== rem) {
|
|
7712
|
+
const templateKey = `tmpl:${f.module}:${template}`;
|
|
7713
|
+
const existingTmpl = recMap.get(templateKey);
|
|
7714
|
+
if (existingTmpl) {
|
|
7715
|
+
existingTmpl.count++;
|
|
7716
|
+
if (!existingTmpl.url && url) existingTmpl.url = url;
|
|
7717
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
|
|
7718
|
+
existingTmpl.severity = f.severity;
|
|
7719
|
+
}
|
|
7720
|
+
continue;
|
|
7721
|
+
}
|
|
7722
|
+
recMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
|
|
7723
|
+
continue;
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7535
7726
|
const existing = recMap.get(rem);
|
|
7536
7727
|
if (existing) {
|
|
7537
7728
|
existing.count++;
|
|
@@ -7548,8 +7739,14 @@ ${rest}
|
|
|
7548
7739
|
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
7549
7740
|
recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
|
|
7550
7741
|
}
|
|
7742
|
+
if (cveList.length > 0) {
|
|
7743
|
+
const unique = [...new Set(cveList)];
|
|
7744
|
+
const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
7745
|
+
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`;
|
|
7746
|
+
recMap.set("__cve__", { text: cveText, severity: cveSeverity, count: 1, url: cveUrl });
|
|
7747
|
+
}
|
|
7551
7748
|
for (const [key, rec] of recMap) {
|
|
7552
|
-
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
7749
|
+
if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
|
|
7553
7750
|
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
7554
7751
|
rec.count = 1;
|
|
7555
7752
|
}
|
|
@@ -7580,6 +7777,43 @@ ${remaining.map(renderRec).join("\n")}
|
|
|
7580
7777
|
</div>
|
|
7581
7778
|
</details>`;
|
|
7582
7779
|
}
|
|
7780
|
+
const filterScript = summary.totalFindings > 0 ? `<script>
|
|
7781
|
+
(function(){
|
|
7782
|
+
var activeSev='ALL',activeMod='ALL';
|
|
7783
|
+
var countEl=document.getElementById('filterCount');
|
|
7784
|
+
var tpl=countEl?countEl.getAttribute('data-tpl'):'';
|
|
7785
|
+
function apply(){
|
|
7786
|
+
var cards=document.querySelectorAll('.finding-card[data-severity]');
|
|
7787
|
+
var shown=0,total=cards.length;
|
|
7788
|
+
cards.forEach(function(c){
|
|
7789
|
+
var sevOk=activeSev==='ALL'||c.getAttribute('data-severity')===activeSev;
|
|
7790
|
+
var modOk=activeMod==='ALL'||c.getAttribute('data-module')===activeMod;
|
|
7791
|
+
c.style.display=(sevOk&&modOk)?'':'none';
|
|
7792
|
+
if(sevOk&&modOk)shown++;
|
|
7793
|
+
});
|
|
7794
|
+
if(countEl)countEl.textContent=tpl.replace('{shown}',shown).replace('{total}',total);
|
|
7795
|
+
document.querySelectorAll('.module-fold').forEach(function(f){
|
|
7796
|
+
var mod=f.getAttribute('data-module');
|
|
7797
|
+
if(activeMod!=='ALL'&&mod!==activeMod){f.style.display='none';return;}
|
|
7798
|
+
f.style.display='';
|
|
7799
|
+
});
|
|
7800
|
+
document.querySelectorAll('.severity-group-fold').forEach(function(g){
|
|
7801
|
+
g.style.display=g.querySelectorAll('.finding-card:not([style*="display: none"])').length?'':'none';
|
|
7802
|
+
});
|
|
7803
|
+
}
|
|
7804
|
+
document.querySelectorAll('.filter-btn[data-severity]').forEach(function(b){
|
|
7805
|
+
b.addEventListener('click',function(){
|
|
7806
|
+
document.querySelectorAll('.filter-btn[data-severity]').forEach(function(x){x.classList.remove('active')});
|
|
7807
|
+
b.classList.add('active');
|
|
7808
|
+
activeSev=b.getAttribute('data-severity');
|
|
7809
|
+
apply();
|
|
7810
|
+
});
|
|
7811
|
+
});
|
|
7812
|
+
var sel=document.getElementById('moduleFilter');
|
|
7813
|
+
if(sel)sel.addEventListener('change',function(){activeMod=sel.value;apply();});
|
|
7814
|
+
apply();
|
|
7815
|
+
})();
|
|
7816
|
+
</script>` : "";
|
|
7583
7817
|
return `<!DOCTYPE html>
|
|
7584
7818
|
<html lang="${htmlLang}">
|
|
7585
7819
|
<head>
|
|
@@ -7616,7 +7850,7 @@ ${remaining.map(renderRec).join("\n")}
|
|
|
7616
7850
|
</div>
|
|
7617
7851
|
<div class="chart-box">
|
|
7618
7852
|
<div class="chart-title">${esc(t.findingsByModule)}</div>
|
|
7619
|
-
${barChart(
|
|
7853
|
+
${barChart(barChartModules, t.allModulesClean)}
|
|
7620
7854
|
</div>
|
|
7621
7855
|
</section>
|
|
7622
7856
|
|
|
@@ -7636,6 +7870,7 @@ ${buildServiceReminderHtml(modules, lang)}
|
|
|
7636
7870
|
|
|
7637
7871
|
<section>
|
|
7638
7872
|
<h2>${esc(t.allFindings)}</h2>
|
|
7873
|
+
${filterToolbarHtml}
|
|
7639
7874
|
${findingsHtml}
|
|
7640
7875
|
</section>
|
|
7641
7876
|
|
|
@@ -7647,6 +7882,7 @@ ${recsHtml}
|
|
|
7647
7882
|
</footer>
|
|
7648
7883
|
|
|
7649
7884
|
</div>
|
|
7885
|
+
${filterScript}
|
|
7650
7886
|
</body>
|
|
7651
7887
|
</html>`;
|
|
7652
7888
|
}
|
|
@@ -7789,6 +8025,9 @@ ${itemsHtml}
|
|
|
7789
8025
|
const mlpsKbPatches = [];
|
|
7790
8026
|
let mlpsKbSeverity = "LOW";
|
|
7791
8027
|
let mlpsKbUrl;
|
|
8028
|
+
const mlpsCveList = [];
|
|
8029
|
+
let mlpsCveSeverity = "LOW";
|
|
8030
|
+
let mlpsCveUrl;
|
|
7792
8031
|
const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
7793
8032
|
for (const r of failedResults) {
|
|
7794
8033
|
for (const f of r.relatedFindings) {
|
|
@@ -7802,6 +8041,13 @@ ${itemsHtml}
|
|
|
7802
8041
|
if (!mlpsKbUrl && url) mlpsKbUrl = url;
|
|
7803
8042
|
continue;
|
|
7804
8043
|
}
|
|
8044
|
+
const cveMatch = f.title.match(/CVE-[\d-]+/);
|
|
8045
|
+
if (cveMatch) {
|
|
8046
|
+
mlpsCveList.push(cveMatch[0]);
|
|
8047
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsCveSeverity)) mlpsCveSeverity = f.severity;
|
|
8048
|
+
if (!mlpsCveUrl && url) mlpsCveUrl = url;
|
|
8049
|
+
continue;
|
|
8050
|
+
}
|
|
7805
8051
|
if (f.module === "security_hub_findings") {
|
|
7806
8052
|
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
7807
8053
|
if (controlMatch) {
|
|
@@ -7818,6 +8064,23 @@ ${itemsHtml}
|
|
|
7818
8064
|
continue;
|
|
7819
8065
|
}
|
|
7820
8066
|
}
|
|
8067
|
+
if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
|
|
8068
|
+
const template = getRecommendationTemplate(rem);
|
|
8069
|
+
if (template !== rem) {
|
|
8070
|
+
const templateKey = `tmpl:${f.module}:${template}`;
|
|
8071
|
+
const existingTmpl = mlpsRecMap.get(templateKey);
|
|
8072
|
+
if (existingTmpl) {
|
|
8073
|
+
existingTmpl.count++;
|
|
8074
|
+
if (!existingTmpl.url && url) existingTmpl.url = url;
|
|
8075
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
|
|
8076
|
+
existingTmpl.severity = f.severity;
|
|
8077
|
+
}
|
|
8078
|
+
continue;
|
|
8079
|
+
}
|
|
8080
|
+
mlpsRecMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
|
|
8081
|
+
continue;
|
|
8082
|
+
}
|
|
8083
|
+
}
|
|
7821
8084
|
const existing = mlpsRecMap.get(rem);
|
|
7822
8085
|
if (existing) {
|
|
7823
8086
|
existing.count++;
|
|
@@ -7835,8 +8098,14 @@ ${itemsHtml}
|
|
|
7835
8098
|
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
7836
8099
|
mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
|
|
7837
8100
|
}
|
|
8101
|
+
if (mlpsCveList.length > 0) {
|
|
8102
|
+
const unique = [...new Set(mlpsCveList)];
|
|
8103
|
+
const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
8104
|
+
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`;
|
|
8105
|
+
mlpsRecMap.set("__cve__", { text: cveText, severity: mlpsCveSeverity, count: 1, url: mlpsCveUrl });
|
|
8106
|
+
}
|
|
7838
8107
|
for (const [key, rec] of mlpsRecMap) {
|
|
7839
|
-
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
8108
|
+
if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
|
|
7840
8109
|
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
7841
8110
|
rec.count = 1;
|
|
7842
8111
|
}
|
|
@@ -8132,7 +8401,6 @@ Detects which AWS security services are enabled and assesses overall security ma
|
|
|
8132
8401
|
- **GuardDuty not enabled** \u2014 Risk 7.5: Provides continuous threat detection.
|
|
8133
8402
|
- **Inspector not enabled** \u2014 Risk 6.0: Scans for software vulnerabilities.
|
|
8134
8403
|
- **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
8404
|
- CloudTrail detection is included for coverage metrics.
|
|
8137
8405
|
|
|
8138
8406
|
### Maturity Levels
|
|
@@ -8140,8 +8408,8 @@ Detects which AWS security services are enabled and assesses overall security ma
|
|
|
8140
8408
|
|------------------|-------|
|
|
8141
8409
|
| 0\u20131 | Basic |
|
|
8142
8410
|
| 2\u20133 | Intermediate |
|
|
8143
|
-
| 4
|
|
8144
|
-
|
|
|
8411
|
+
| 4 | Advanced |
|
|
8412
|
+
| 5 | Comprehensive |
|
|
8145
8413
|
|
|
8146
8414
|
## 2. Security Hub Findings (security_hub_findings)
|
|
8147
8415
|
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 +8543,7 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
8275
8543
|
import { join as join2, dirname } from "path";
|
|
8276
8544
|
import { fileURLToPath } from "url";
|
|
8277
8545
|
var MODULE_DESCRIPTIONS = {
|
|
8278
|
-
service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config
|
|
8546
|
+
service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config) are enabled and assesses security maturity.",
|
|
8279
8547
|
secret_exposure: "Checks Lambda env vars and EC2 userData for exposed secrets (AWS keys, private keys, passwords).",
|
|
8280
8548
|
ssl_certificate: "Checks ACM certificates for expiry, failed status, and upcoming renewals.",
|
|
8281
8549
|
dns_dangling: "Checks Route53 CNAME records for dangling DNS (subdomain takeover risk).",
|
|
@@ -8710,14 +8978,12 @@ function createServer(defaultRegion) {
|
|
|
8710
8978
|
"Security Hub": "+300 security checks",
|
|
8711
8979
|
"GuardDuty": "Threat detection",
|
|
8712
8980
|
"Inspector": "Vulnerability scanning",
|
|
8713
|
-
"AWS Config": "Configuration tracking"
|
|
8714
|
-
"Macie": "Sensitive data detection"
|
|
8981
|
+
"AWS Config": "Configuration tracking"
|
|
8715
8982
|
};
|
|
8716
8983
|
const serviceFreeTrials = {
|
|
8717
8984
|
"Security Hub": true,
|
|
8718
8985
|
"GuardDuty": true,
|
|
8719
|
-
"Inspector": true
|
|
8720
|
-
"Macie": true
|
|
8986
|
+
"Inspector": true
|
|
8721
8987
|
};
|
|
8722
8988
|
const services = detection.services;
|
|
8723
8989
|
const coveragePercent = detection.coveragePercent;
|
|
@@ -8752,7 +9018,7 @@ function createServer(defaultRegion) {
|
|
|
8752
9018
|
lines.push("");
|
|
8753
9019
|
lines.push("### Recommendations (Priority Order)");
|
|
8754
9020
|
lines.push("");
|
|
8755
|
-
const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "
|
|
9021
|
+
const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "CloudTrail"];
|
|
8756
9022
|
const sorted = disabled.sort(
|
|
8757
9023
|
(a, b) => priorityOrder.indexOf(a.name) - priorityOrder.indexOf(b.name)
|
|
8758
9024
|
);
|
|
@@ -8775,7 +9041,7 @@ function createServer(defaultRegion) {
|
|
|
8775
9041
|
const nextMilestones = {
|
|
8776
9042
|
basic: { level: "Intermediate", target: 2, suggestions: ["Security Hub", "GuardDuty"] },
|
|
8777
9043
|
intermediate: { level: "Advanced", target: 4, suggestions: ["Inspector", "AWS Config"] },
|
|
8778
|
-
advanced: { level: "Comprehensive", target:
|
|
9044
|
+
advanced: { level: "Comprehensive", target: 5, suggestions: ["CloudTrail"] }
|
|
8779
9045
|
};
|
|
8780
9046
|
const next = nextMilestones[maturityLevel];
|
|
8781
9047
|
if (next) {
|