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
|
@@ -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.
|
|
240
|
+
var VERSION = "0.6.3";
|
|
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 >=
|
|
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;
|
|
@@ -3925,6 +3874,12 @@ var zhI18n = {
|
|
|
3925
3874
|
trendTitle: "30\u65E5\u8D8B\u52BF",
|
|
3926
3875
|
findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
|
|
3927
3876
|
showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
|
|
3877
|
+
// Filter toolbar
|
|
3878
|
+
filterSeverity: "\u4E25\u91CD\u6027\uFF1A",
|
|
3879
|
+
filterModule: "\u6A21\u5757\uFF1A",
|
|
3880
|
+
filterAll: "\u5168\u90E8",
|
|
3881
|
+
filterAllModules: "\u5168\u90E8\u6A21\u5757",
|
|
3882
|
+
filterCountTpl: "\u663E\u793A {shown} / {total} \u4E2A\u53D1\u73B0",
|
|
3928
3883
|
// Extended — MLPS extras
|
|
3929
3884
|
// Markdown report
|
|
3930
3885
|
executiveSummary: "\u6267\u884C\u6458\u8981",
|
|
@@ -3955,6 +3910,44 @@ var zhI18n = {
|
|
|
3955
3910
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
|
|
3956
3911
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
|
|
3957
3912
|
},
|
|
3913
|
+
// Module display names
|
|
3914
|
+
moduleNames: {
|
|
3915
|
+
service_detection: "\u5B89\u5168\u670D\u52A1\u68C0\u6D4B",
|
|
3916
|
+
secret_exposure: "\u5BC6\u94A5\u66B4\u9732",
|
|
3917
|
+
ssl_certificate: "SSL \u8BC1\u4E66",
|
|
3918
|
+
dns_dangling: "\u60AC\u6302 DNS",
|
|
3919
|
+
network_reachability: "\u7F51\u7EDC\u53EF\u8FBE\u6027",
|
|
3920
|
+
iam_privilege_escalation: "IAM \u63D0\u6743\u5206\u6790",
|
|
3921
|
+
public_access_verify: "\u516C\u7F51\u8BBF\u95EE\u9A8C\u8BC1",
|
|
3922
|
+
tag_compliance: "\u6807\u7B7E\u5408\u89C4",
|
|
3923
|
+
idle_resources: "\u95F2\u7F6E\u8D44\u6E90",
|
|
3924
|
+
disaster_recovery: "\u707E\u5907\u8BC4\u4F30",
|
|
3925
|
+
security_hub_findings: "Security Hub",
|
|
3926
|
+
guardduty_findings: "GuardDuty",
|
|
3927
|
+
inspector_findings: "Inspector",
|
|
3928
|
+
trusted_advisor_findings: "Trusted Advisor",
|
|
3929
|
+
config_rules_findings: "Config Rules",
|
|
3930
|
+
access_analyzer_findings: "Access Analyzer",
|
|
3931
|
+
patch_compliance_findings: "\u8865\u4E01\u5408\u89C4",
|
|
3932
|
+
imdsv2_enforcement: "IMDSv2 \u5F3A\u5236",
|
|
3933
|
+
waf_coverage: "WAF \u8986\u76D6",
|
|
3934
|
+
// Security Hub sub-categories
|
|
3935
|
+
"sh:FSBP": "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5",
|
|
3936
|
+
"sh:Inspector": "\u8F6F\u4EF6\u6F0F\u6D1E",
|
|
3937
|
+
"sh:GuardDuty": "\u5A01\u80C1\u68C0\u6D4B",
|
|
3938
|
+
"sh:Config": "\u914D\u7F6E\u5408\u89C4",
|
|
3939
|
+
"sh:Access Analyzer": "\u5916\u90E8\u8BBF\u95EE",
|
|
3940
|
+
"sh:Other": "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0"
|
|
3941
|
+
},
|
|
3942
|
+
// Security Hub sub-categories
|
|
3943
|
+
securityHubSubCategories: {
|
|
3944
|
+
FSBP: { label: "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5" },
|
|
3945
|
+
Inspector: { label: "\u8F6F\u4EF6\u6F0F\u6D1E" },
|
|
3946
|
+
GuardDuty: { label: "\u5A01\u80C1\u68C0\u6D4B" },
|
|
3947
|
+
Config: { label: "\u914D\u7F6E\u5408\u89C4" },
|
|
3948
|
+
"Access Analyzer": { label: "\u5916\u90E8\u8BBF\u95EE" },
|
|
3949
|
+
Other: { label: "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0" }
|
|
3950
|
+
},
|
|
3958
3951
|
// Service Recommendations
|
|
3959
3952
|
notEnabled: "\u672A\u542F\u7528",
|
|
3960
3953
|
serviceRecommendations: {
|
|
@@ -4157,6 +4150,12 @@ var enI18n = {
|
|
|
4157
4150
|
trendTitle: "30-Day Trends",
|
|
4158
4151
|
findingsBySeverity: "Findings by Severity",
|
|
4159
4152
|
showMoreCount: (n) => `Show ${n} more\u2026`,
|
|
4153
|
+
// Filter toolbar
|
|
4154
|
+
filterSeverity: "Severity:",
|
|
4155
|
+
filterModule: "Module:",
|
|
4156
|
+
filterAll: "All",
|
|
4157
|
+
filterAllModules: "All Modules",
|
|
4158
|
+
filterCountTpl: "Showing {shown} / {total} findings",
|
|
4160
4159
|
// Extended \u2014 MLPS extras
|
|
4161
4160
|
// Markdown report
|
|
4162
4161
|
executiveSummary: "Executive Summary",
|
|
@@ -4187,6 +4186,44 @@ var enI18n = {
|
|
|
4187
4186
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
|
|
4188
4187
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
|
|
4189
4188
|
},
|
|
4189
|
+
// Module display names
|
|
4190
|
+
moduleNames: {
|
|
4191
|
+
service_detection: "Security Service Detection",
|
|
4192
|
+
secret_exposure: "Secret Exposure",
|
|
4193
|
+
ssl_certificate: "SSL Certificate",
|
|
4194
|
+
dns_dangling: "Dangling DNS",
|
|
4195
|
+
network_reachability: "Network Reachability",
|
|
4196
|
+
iam_privilege_escalation: "IAM Privilege Escalation",
|
|
4197
|
+
public_access_verify: "Public Access Verification",
|
|
4198
|
+
tag_compliance: "Tag Compliance",
|
|
4199
|
+
idle_resources: "Idle Resources",
|
|
4200
|
+
disaster_recovery: "Disaster Recovery",
|
|
4201
|
+
security_hub_findings: "Security Hub",
|
|
4202
|
+
guardduty_findings: "GuardDuty",
|
|
4203
|
+
inspector_findings: "Inspector",
|
|
4204
|
+
trusted_advisor_findings: "Trusted Advisor",
|
|
4205
|
+
config_rules_findings: "Config Rules",
|
|
4206
|
+
access_analyzer_findings: "Access Analyzer",
|
|
4207
|
+
patch_compliance_findings: "Patch Compliance",
|
|
4208
|
+
imdsv2_enforcement: "IMDSv2 Enforcement",
|
|
4209
|
+
waf_coverage: "WAF Coverage",
|
|
4210
|
+
// Security Hub sub-categories
|
|
4211
|
+
"sh:FSBP": "Security Best Practices",
|
|
4212
|
+
"sh:Inspector": "Software Vulnerabilities",
|
|
4213
|
+
"sh:GuardDuty": "Threat Detection",
|
|
4214
|
+
"sh:Config": "Configuration Compliance",
|
|
4215
|
+
"sh:Access Analyzer": "External Access",
|
|
4216
|
+
"sh:Other": "Other Security Findings"
|
|
4217
|
+
},
|
|
4218
|
+
// Security Hub sub-categories
|
|
4219
|
+
securityHubSubCategories: {
|
|
4220
|
+
FSBP: { label: "Security Best Practices" },
|
|
4221
|
+
Inspector: { label: "Software Vulnerabilities" },
|
|
4222
|
+
GuardDuty: { label: "Threat Detection" },
|
|
4223
|
+
Config: { label: "Configuration Compliance" },
|
|
4224
|
+
"Access Analyzer": { label: "External Access" },
|
|
4225
|
+
Other: { label: "Other Security Findings" }
|
|
4226
|
+
},
|
|
4190
4227
|
// Service Recommendations
|
|
4191
4228
|
notEnabled: "Not Enabled",
|
|
4192
4229
|
serviceRecommendations: {
|
|
@@ -4391,7 +4428,7 @@ function generateMarkdownReport(scanResults, lang) {
|
|
|
4391
4428
|
for (const m of modules) {
|
|
4392
4429
|
const status = m.status === "success" ? "\u2705" : "\u274C";
|
|
4393
4430
|
lines.push(
|
|
4394
|
-
`| ${m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
|
|
4431
|
+
`| ${t.moduleNames[m.module] ?? m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
|
|
4395
4432
|
);
|
|
4396
4433
|
}
|
|
4397
4434
|
lines.push("");
|
|
@@ -7234,6 +7271,22 @@ var SEV_COLOR = {
|
|
|
7234
7271
|
LOW: "#22c55e"
|
|
7235
7272
|
};
|
|
7236
7273
|
var SEVERITY_ORDER2 = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
7274
|
+
function getRecommendationTemplate(rem) {
|
|
7275
|
+
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}");
|
|
7276
|
+
}
|
|
7277
|
+
function getSecurityHubSource(finding) {
|
|
7278
|
+
const impact = finding.impact ?? "";
|
|
7279
|
+
const match = impact.match(/^Source:\s*([^(]+)/);
|
|
7280
|
+
if (!match) return "Other";
|
|
7281
|
+
const product = match[1].trim();
|
|
7282
|
+
if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
|
|
7283
|
+
if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
|
|
7284
|
+
if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
|
|
7285
|
+
if (product === "Config" || product.includes("Config")) return "Config";
|
|
7286
|
+
if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
|
|
7287
|
+
return "Other";
|
|
7288
|
+
}
|
|
7289
|
+
var SECURITY_HUB_SUB_CAT_ORDER = ["FSBP", "Inspector", "GuardDuty", "Config", "Access Analyzer", "Other"];
|
|
7237
7290
|
function scoreColor(score) {
|
|
7238
7291
|
if (score >= 80) return "#22c55e";
|
|
7239
7292
|
if (score >= 50) return "#eab308";
|
|
@@ -7418,7 +7471,16 @@ function sharedCss() {
|
|
|
7418
7471
|
.rec-body ol{padding-left:24px}
|
|
7419
7472
|
.rec-body li{margin-bottom:8px;color:#cbd5e1;font-size:13px}
|
|
7420
7473
|
.rec-body .badge{margin-right:6px;vertical-align:middle}
|
|
7474
|
+
.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}
|
|
7475
|
+
.filter-group{display:flex;align-items:center;gap:8px}
|
|
7476
|
+
.filter-label{color:#94a3b8;font-size:13px}
|
|
7477
|
+
.filter-btn{padding:4px 12px;border-radius:4px;border:1px solid #475569;background:transparent;color:#cbd5e1;cursor:pointer;font-size:13px}
|
|
7478
|
+
.filter-btn:hover{background:#334155}
|
|
7479
|
+
.filter-btn.active{background:#3b82f6;border-color:#3b82f6;color:#fff}
|
|
7480
|
+
.filter-select{padding:4px 8px;border-radius:4px;border:1px solid #475569;background:#0f172a;color:#cbd5e1;font-size:13px}
|
|
7481
|
+
.filter-count{color:#64748b;font-size:13px;margin-left:auto}
|
|
7421
7482
|
@media print{
|
|
7483
|
+
.filter-toolbar{display:none !important}
|
|
7422
7484
|
body{background:#fff;color:#1e293b;-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
|
7423
7485
|
.container{max-width:100%;padding:20px}
|
|
7424
7486
|
.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}
|
|
@@ -7611,6 +7673,47 @@ function generateHtmlReport(scanResults, history, lang) {
|
|
|
7611
7673
|
const allFindings = modules.flatMap(
|
|
7612
7674
|
(m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
|
|
7613
7675
|
);
|
|
7676
|
+
const shModule = modules.find((m) => m.module === "security_hub_findings");
|
|
7677
|
+
const shSubCats = [];
|
|
7678
|
+
if (shModule && shModule.findingsCount > 0) {
|
|
7679
|
+
const catMap = {};
|
|
7680
|
+
for (const f of shModule.findings) {
|
|
7681
|
+
const cat = getSecurityHubSource(f);
|
|
7682
|
+
if (!catMap[cat]) catMap[cat] = [];
|
|
7683
|
+
catMap[cat].push(f);
|
|
7684
|
+
}
|
|
7685
|
+
for (const cat of SECURITY_HUB_SUB_CAT_ORDER) {
|
|
7686
|
+
const catFindings = catMap[cat];
|
|
7687
|
+
if (catFindings && catFindings.length > 0) {
|
|
7688
|
+
const meta = t.securityHubSubCategories[cat];
|
|
7689
|
+
const shLabel = t.moduleNames[`sh:${cat}`] ?? meta?.label ?? cat;
|
|
7690
|
+
shSubCats.push({
|
|
7691
|
+
key: cat,
|
|
7692
|
+
label: shLabel,
|
|
7693
|
+
count: catFindings.length,
|
|
7694
|
+
findings: catFindings
|
|
7695
|
+
});
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
}
|
|
7699
|
+
const DETECTION_ONLY_MODULES = /* @__PURE__ */ new Set([
|
|
7700
|
+
"guardduty_findings",
|
|
7701
|
+
"inspector_findings",
|
|
7702
|
+
"config_rules_findings",
|
|
7703
|
+
"access_analyzer_findings"
|
|
7704
|
+
]);
|
|
7705
|
+
const barChartModules = modules.flatMap((m) => {
|
|
7706
|
+
if (DETECTION_ONLY_MODULES.has(m.module)) return [];
|
|
7707
|
+
if (m.module === "security_hub_findings" && shSubCats.length > 0) {
|
|
7708
|
+
return shSubCats.map((sc) => ({
|
|
7709
|
+
...m,
|
|
7710
|
+
module: t.moduleNames[`sh:${sc.key}`] ?? sc.key,
|
|
7711
|
+
findingsCount: sc.count,
|
|
7712
|
+
findings: sc.findings
|
|
7713
|
+
}));
|
|
7714
|
+
}
|
|
7715
|
+
return [{ ...m, module: t.moduleNames[m.module] ?? m.module }];
|
|
7716
|
+
});
|
|
7614
7717
|
let top5Html = "";
|
|
7615
7718
|
if (allFindings.length > 0) {
|
|
7616
7719
|
const top5 = [...allFindings].sort((a, b) => b.riskScore - a.riskScore).slice(0, 5);
|
|
@@ -7636,13 +7739,14 @@ function generateHtmlReport(scanResults, history, lang) {
|
|
|
7636
7739
|
</section>`;
|
|
7637
7740
|
}
|
|
7638
7741
|
let findingsHtml;
|
|
7742
|
+
let filterToolbarHtml = "";
|
|
7639
7743
|
if (summary.totalFindings === 0) {
|
|
7640
7744
|
findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
|
|
7641
7745
|
} else {
|
|
7642
7746
|
const FOLD_THRESHOLD = 20;
|
|
7643
|
-
const renderCard = (f) => {
|
|
7747
|
+
const renderCard = (f, moduleKey) => {
|
|
7644
7748
|
const sev = f.severity.toLowerCase();
|
|
7645
|
-
return `<div class="finding-card sev-${esc(sev)}">
|
|
7749
|
+
return `<div class="finding-card sev-${esc(sev)}" data-severity="${esc(f.severity)}" data-module="${esc(moduleKey)}">
|
|
7646
7750
|
<span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
|
|
7647
7751
|
<span class="finding-title-text">${esc(f.title)}</span>
|
|
7648
7752
|
<span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
|
|
@@ -7653,12 +7757,12 @@ function generateHtmlReport(scanResults, history, lang) {
|
|
|
7653
7757
|
</div></details>
|
|
7654
7758
|
</div>`;
|
|
7655
7759
|
};
|
|
7656
|
-
const renderCards = (findings) => {
|
|
7760
|
+
const renderCards = (findings, moduleKey) => {
|
|
7657
7761
|
if (findings.length <= FOLD_THRESHOLD) {
|
|
7658
|
-
return findings.map(renderCard).join("\n");
|
|
7762
|
+
return findings.map((f) => renderCard(f, moduleKey)).join("\n");
|
|
7659
7763
|
}
|
|
7660
|
-
const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
|
|
7661
|
-
const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
|
|
7764
|
+
const first = findings.slice(0, FOLD_THRESHOLD).map((f) => renderCard(f, moduleKey)).join("\n");
|
|
7765
|
+
const rest = findings.slice(FOLD_THRESHOLD).map((f) => renderCard(f, moduleKey)).join("\n");
|
|
7662
7766
|
return `${first}
|
|
7663
7767
|
<details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
|
|
7664
7768
|
${rest}
|
|
@@ -7676,37 +7780,76 @@ ${rest}
|
|
|
7676
7780
|
if (!moduleMap.has(mod)) moduleMap.set(mod, []);
|
|
7677
7781
|
moduleMap.get(mod).push(f);
|
|
7678
7782
|
}
|
|
7679
|
-
const
|
|
7783
|
+
const expandedEntries = [];
|
|
7784
|
+
for (const [mod, findings] of moduleMap.entries()) {
|
|
7785
|
+
if (DETECTION_ONLY_MODULES.has(mod)) continue;
|
|
7786
|
+
if (mod === "security_hub_findings" && shSubCats.length > 0) {
|
|
7787
|
+
for (const sc of shSubCats) {
|
|
7788
|
+
expandedEntries.push([sc.key, sc.findings, sc.label]);
|
|
7789
|
+
}
|
|
7790
|
+
} else {
|
|
7791
|
+
expandedEntries.push([mod, findings, null]);
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
const moduleEntries = expandedEntries.sort((a, b) => {
|
|
7680
7795
|
const aHasCritHigh = a[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
7681
7796
|
const bHasCritHigh = b[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
7682
7797
|
if (aHasCritHigh !== bHasCritHigh) return aHasCritHigh ? -1 : 1;
|
|
7683
7798
|
return b[1].length - a[1].length;
|
|
7684
7799
|
});
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
const findings = modFindings.filter((f) => f.severity === sev);
|
|
7691
|
-
if (findings.length === 0) return "";
|
|
7692
|
-
findings.sort((a, b) => b.riskScore - a.riskScore);
|
|
7800
|
+
const renderSeverityGroups = (findings, moduleKey) => {
|
|
7801
|
+
return SEVERITY_ORDER2.map((sev) => {
|
|
7802
|
+
const sevFindings = findings.filter((f) => f.severity === sev);
|
|
7803
|
+
if (sevFindings.length === 0) return "";
|
|
7804
|
+
sevFindings.sort((a, b) => b.riskScore - a.riskScore);
|
|
7693
7805
|
const emoji = SEV_EMOJI[sev] ?? "";
|
|
7694
7806
|
const label = sev.charAt(0) + sev.slice(1).toLowerCase();
|
|
7695
7807
|
return `<details class="severity-group-fold">
|
|
7696
|
-
<summary><h4>${emoji} ${label} (${
|
|
7697
|
-
${renderCards(
|
|
7808
|
+
<summary><h4>${emoji} ${label} (${sevFindings.length})</h4></summary>
|
|
7809
|
+
${renderCards(sevFindings, moduleKey)}
|
|
7698
7810
|
</details>`;
|
|
7699
7811
|
}).filter(Boolean).join("\n");
|
|
7700
|
-
|
|
7812
|
+
};
|
|
7813
|
+
const renderModuleBadges = (findings) => {
|
|
7814
|
+
const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
7815
|
+
for (const f of findings) sevCounts[f.severity]++;
|
|
7816
|
+
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(" ");
|
|
7817
|
+
};
|
|
7818
|
+
findingsHtml = moduleEntries.map(([modName, modFindings, subCatLabel]) => {
|
|
7819
|
+
const badges = renderModuleBadges(modFindings);
|
|
7820
|
+
const displayName = subCatLabel ?? (t.moduleNames[modName] ?? modName);
|
|
7821
|
+
return `<details class="module-fold" data-module="${esc(modName)}">
|
|
7701
7822
|
<summary>
|
|
7702
|
-
<h3>🔒 ${esc(
|
|
7823
|
+
<h3>🔒 ${esc(displayName)} (${modFindings.length})</h3>
|
|
7703
7824
|
<span class="module-badges">${badges}</span>
|
|
7704
7825
|
</summary>
|
|
7705
7826
|
<div class="module-body">
|
|
7706
|
-
${
|
|
7827
|
+
${renderSeverityGroups(modFindings, modName)}
|
|
7707
7828
|
</div>
|
|
7708
7829
|
</details>`;
|
|
7709
7830
|
}).join("\n");
|
|
7831
|
+
const moduleOptions = moduleEntries.map(([modKey, , subCatLabel]) => {
|
|
7832
|
+
const label = subCatLabel ?? (t.moduleNames[modKey] ?? modKey);
|
|
7833
|
+
return `<option value="${esc(modKey)}">${esc(label)}</option>`;
|
|
7834
|
+
}).join("\n ");
|
|
7835
|
+
filterToolbarHtml = `<div class="filter-toolbar" id="filterBar">
|
|
7836
|
+
<div class="filter-group">
|
|
7837
|
+
<span class="filter-label">${esc(t.filterSeverity)}</span>
|
|
7838
|
+
<button class="filter-btn active" data-severity="ALL">${esc(t.filterAll)}</button>
|
|
7839
|
+
<button class="filter-btn" data-severity="CRITICAL">Critical</button>
|
|
7840
|
+
<button class="filter-btn" data-severity="HIGH">High</button>
|
|
7841
|
+
<button class="filter-btn" data-severity="MEDIUM">Medium</button>
|
|
7842
|
+
<button class="filter-btn" data-severity="LOW">Low</button>
|
|
7843
|
+
</div>
|
|
7844
|
+
<div class="filter-group">
|
|
7845
|
+
<span class="filter-label">${esc(t.filterModule)}</span>
|
|
7846
|
+
<select class="filter-select" id="moduleFilter">
|
|
7847
|
+
<option value="ALL">${esc(t.filterAllModules)}</option>
|
|
7848
|
+
${moduleOptions}
|
|
7849
|
+
</select>
|
|
7850
|
+
</div>
|
|
7851
|
+
<div class="filter-count" id="filterCount" data-tpl="${esc(t.filterCountTpl)}"></div>
|
|
7852
|
+
</div>`;
|
|
7710
7853
|
}
|
|
7711
7854
|
let trendHtml = "";
|
|
7712
7855
|
if (history && history.length >= 2) {
|
|
@@ -7723,8 +7866,29 @@ ${rest}
|
|
|
7723
7866
|
</div>
|
|
7724
7867
|
</section>`;
|
|
7725
7868
|
}
|
|
7726
|
-
const
|
|
7727
|
-
|
|
7869
|
+
const isModuleDisabled = (m) => {
|
|
7870
|
+
if (!m.warnings?.length) return void 0;
|
|
7871
|
+
const w = m.warnings.find(
|
|
7872
|
+
(w2) => SERVICE_NOT_ENABLED_PATTERNS.some((p) => w2.includes(p))
|
|
7873
|
+
);
|
|
7874
|
+
return w;
|
|
7875
|
+
};
|
|
7876
|
+
const statsRows = modules.flatMap(
|
|
7877
|
+
(m) => {
|
|
7878
|
+
if (DETECTION_ONLY_MODULES.has(m.module)) return [];
|
|
7879
|
+
if (m.module === "security_hub_findings" && shSubCats.length > 0) {
|
|
7880
|
+
return shSubCats.map(
|
|
7881
|
+
(sc) => `<tr><td>${esc(sc.label)}</td><td>${m.resourcesScanned}</td><td>${sc.count}</td><td>✓</td></tr>`
|
|
7882
|
+
);
|
|
7883
|
+
}
|
|
7884
|
+
const disabledWarning = isModuleDisabled(m);
|
|
7885
|
+
if (disabledWarning) {
|
|
7886
|
+
const rec = t.serviceRecommendations[m.module];
|
|
7887
|
+
const reason = rec ? rec.action : disabledWarning;
|
|
7888
|
+
return [`<tr><td>${esc(t.moduleNames[m.module] ?? m.module)}</td><td>-</td><td>-</td><td style="color:#eab308">⚠ ${esc(reason)}</td></tr>`];
|
|
7889
|
+
}
|
|
7890
|
+
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>`];
|
|
7891
|
+
}
|
|
7728
7892
|
).join("\n");
|
|
7729
7893
|
let recsHtml = "";
|
|
7730
7894
|
if (summary.totalFindings > 0) {
|
|
@@ -7732,6 +7896,9 @@ ${rest}
|
|
|
7732
7896
|
const kbPatches = [];
|
|
7733
7897
|
let kbSeverity = "LOW";
|
|
7734
7898
|
let kbUrl;
|
|
7899
|
+
const cveList = [];
|
|
7900
|
+
let cveSeverity = "LOW";
|
|
7901
|
+
let cveUrl;
|
|
7735
7902
|
const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
7736
7903
|
for (const f of allFindings) {
|
|
7737
7904
|
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
@@ -7744,6 +7911,13 @@ ${rest}
|
|
|
7744
7911
|
if (!kbUrl && url) kbUrl = url;
|
|
7745
7912
|
continue;
|
|
7746
7913
|
}
|
|
7914
|
+
const cveMatch = f.title.match(/CVE-[\d-]+/);
|
|
7915
|
+
if (cveMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
|
|
7916
|
+
cveList.push(cveMatch[0]);
|
|
7917
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(cveSeverity)) cveSeverity = f.severity;
|
|
7918
|
+
if (!cveUrl && url) cveUrl = url;
|
|
7919
|
+
continue;
|
|
7920
|
+
}
|
|
7747
7921
|
if (f.module === "security_hub_findings") {
|
|
7748
7922
|
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
7749
7923
|
if (controlMatch) {
|
|
@@ -7760,6 +7934,23 @@ ${rest}
|
|
|
7760
7934
|
continue;
|
|
7761
7935
|
}
|
|
7762
7936
|
}
|
|
7937
|
+
if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
|
|
7938
|
+
const template = getRecommendationTemplate(rem);
|
|
7939
|
+
if (template !== rem) {
|
|
7940
|
+
const templateKey = `tmpl:${f.module}:${template}`;
|
|
7941
|
+
const existingTmpl = recMap.get(templateKey);
|
|
7942
|
+
if (existingTmpl) {
|
|
7943
|
+
existingTmpl.count++;
|
|
7944
|
+
if (!existingTmpl.url && url) existingTmpl.url = url;
|
|
7945
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
|
|
7946
|
+
existingTmpl.severity = f.severity;
|
|
7947
|
+
}
|
|
7948
|
+
continue;
|
|
7949
|
+
}
|
|
7950
|
+
recMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
|
|
7951
|
+
continue;
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7763
7954
|
const existing = recMap.get(rem);
|
|
7764
7955
|
if (existing) {
|
|
7765
7956
|
existing.count++;
|
|
@@ -7776,8 +7967,14 @@ ${rest}
|
|
|
7776
7967
|
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
7777
7968
|
recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
|
|
7778
7969
|
}
|
|
7970
|
+
if (cveList.length > 0) {
|
|
7971
|
+
const unique = [...new Set(cveList)];
|
|
7972
|
+
const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
7973
|
+
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`;
|
|
7974
|
+
recMap.set("__cve__", { text: cveText, severity: cveSeverity, count: 1, url: cveUrl });
|
|
7975
|
+
}
|
|
7779
7976
|
for (const [key, rec] of recMap) {
|
|
7780
|
-
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
7977
|
+
if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
|
|
7781
7978
|
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
7782
7979
|
rec.count = 1;
|
|
7783
7980
|
}
|
|
@@ -7808,6 +8005,43 @@ ${remaining.map(renderRec).join("\n")}
|
|
|
7808
8005
|
</div>
|
|
7809
8006
|
</details>`;
|
|
7810
8007
|
}
|
|
8008
|
+
const filterScript = summary.totalFindings > 0 ? `<script>
|
|
8009
|
+
(function(){
|
|
8010
|
+
var activeSev='ALL',activeMod='ALL';
|
|
8011
|
+
var countEl=document.getElementById('filterCount');
|
|
8012
|
+
var tpl=countEl?countEl.getAttribute('data-tpl'):'';
|
|
8013
|
+
function apply(){
|
|
8014
|
+
var cards=document.querySelectorAll('.finding-card[data-severity]');
|
|
8015
|
+
var shown=0,total=cards.length;
|
|
8016
|
+
cards.forEach(function(c){
|
|
8017
|
+
var sevOk=activeSev==='ALL'||c.getAttribute('data-severity')===activeSev;
|
|
8018
|
+
var modOk=activeMod==='ALL'||c.getAttribute('data-module')===activeMod;
|
|
8019
|
+
c.style.display=(sevOk&&modOk)?'':'none';
|
|
8020
|
+
if(sevOk&&modOk)shown++;
|
|
8021
|
+
});
|
|
8022
|
+
if(countEl)countEl.textContent=tpl.replace('{shown}',shown).replace('{total}',total);
|
|
8023
|
+
document.querySelectorAll('.module-fold').forEach(function(f){
|
|
8024
|
+
var mod=f.getAttribute('data-module');
|
|
8025
|
+
if(activeMod!=='ALL'&&mod!==activeMod){f.style.display='none';return;}
|
|
8026
|
+
f.style.display='';
|
|
8027
|
+
});
|
|
8028
|
+
document.querySelectorAll('.severity-group-fold').forEach(function(g){
|
|
8029
|
+
g.style.display=g.querySelectorAll('.finding-card:not([style*="display: none"])').length?'':'none';
|
|
8030
|
+
});
|
|
8031
|
+
}
|
|
8032
|
+
document.querySelectorAll('.filter-btn[data-severity]').forEach(function(b){
|
|
8033
|
+
b.addEventListener('click',function(){
|
|
8034
|
+
document.querySelectorAll('.filter-btn[data-severity]').forEach(function(x){x.classList.remove('active')});
|
|
8035
|
+
b.classList.add('active');
|
|
8036
|
+
activeSev=b.getAttribute('data-severity');
|
|
8037
|
+
apply();
|
|
8038
|
+
});
|
|
8039
|
+
});
|
|
8040
|
+
var sel=document.getElementById('moduleFilter');
|
|
8041
|
+
if(sel)sel.addEventListener('change',function(){activeMod=sel.value;apply();});
|
|
8042
|
+
apply();
|
|
8043
|
+
})();
|
|
8044
|
+
</script>` : "";
|
|
7811
8045
|
return `<!DOCTYPE html>
|
|
7812
8046
|
<html lang="${htmlLang}">
|
|
7813
8047
|
<head>
|
|
@@ -7844,7 +8078,7 @@ ${remaining.map(renderRec).join("\n")}
|
|
|
7844
8078
|
</div>
|
|
7845
8079
|
<div class="chart-box">
|
|
7846
8080
|
<div class="chart-title">${esc(t.findingsByModule)}</div>
|
|
7847
|
-
${barChart(
|
|
8081
|
+
${barChart(barChartModules, t.allModulesClean)}
|
|
7848
8082
|
</div>
|
|
7849
8083
|
</section>
|
|
7850
8084
|
|
|
@@ -7864,6 +8098,7 @@ ${buildServiceReminderHtml(modules, lang)}
|
|
|
7864
8098
|
|
|
7865
8099
|
<section>
|
|
7866
8100
|
<h2>${esc(t.allFindings)}</h2>
|
|
8101
|
+
${filterToolbarHtml}
|
|
7867
8102
|
${findingsHtml}
|
|
7868
8103
|
</section>
|
|
7869
8104
|
|
|
@@ -7875,6 +8110,7 @@ ${recsHtml}
|
|
|
7875
8110
|
</footer>
|
|
7876
8111
|
|
|
7877
8112
|
</div>
|
|
8113
|
+
${filterScript}
|
|
7878
8114
|
</body>
|
|
7879
8115
|
</html>`;
|
|
7880
8116
|
}
|
|
@@ -8017,6 +8253,9 @@ ${itemsHtml}
|
|
|
8017
8253
|
const mlpsKbPatches = [];
|
|
8018
8254
|
let mlpsKbSeverity = "LOW";
|
|
8019
8255
|
let mlpsKbUrl;
|
|
8256
|
+
const mlpsCveList = [];
|
|
8257
|
+
let mlpsCveSeverity = "LOW";
|
|
8258
|
+
let mlpsCveUrl;
|
|
8020
8259
|
const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
8021
8260
|
for (const r of failedResults) {
|
|
8022
8261
|
for (const f of r.relatedFindings) {
|
|
@@ -8030,6 +8269,13 @@ ${itemsHtml}
|
|
|
8030
8269
|
if (!mlpsKbUrl && url) mlpsKbUrl = url;
|
|
8031
8270
|
continue;
|
|
8032
8271
|
}
|
|
8272
|
+
const cveMatch = f.title.match(/CVE-[\d-]+/);
|
|
8273
|
+
if (cveMatch) {
|
|
8274
|
+
mlpsCveList.push(cveMatch[0]);
|
|
8275
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsCveSeverity)) mlpsCveSeverity = f.severity;
|
|
8276
|
+
if (!mlpsCveUrl && url) mlpsCveUrl = url;
|
|
8277
|
+
continue;
|
|
8278
|
+
}
|
|
8033
8279
|
if (f.module === "security_hub_findings") {
|
|
8034
8280
|
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
8035
8281
|
if (controlMatch) {
|
|
@@ -8046,6 +8292,23 @@ ${itemsHtml}
|
|
|
8046
8292
|
continue;
|
|
8047
8293
|
}
|
|
8048
8294
|
}
|
|
8295
|
+
if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
|
|
8296
|
+
const template = getRecommendationTemplate(rem);
|
|
8297
|
+
if (template !== rem) {
|
|
8298
|
+
const templateKey = `tmpl:${f.module}:${template}`;
|
|
8299
|
+
const existingTmpl = mlpsRecMap.get(templateKey);
|
|
8300
|
+
if (existingTmpl) {
|
|
8301
|
+
existingTmpl.count++;
|
|
8302
|
+
if (!existingTmpl.url && url) existingTmpl.url = url;
|
|
8303
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
|
|
8304
|
+
existingTmpl.severity = f.severity;
|
|
8305
|
+
}
|
|
8306
|
+
continue;
|
|
8307
|
+
}
|
|
8308
|
+
mlpsRecMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
|
|
8309
|
+
continue;
|
|
8310
|
+
}
|
|
8311
|
+
}
|
|
8049
8312
|
const existing = mlpsRecMap.get(rem);
|
|
8050
8313
|
if (existing) {
|
|
8051
8314
|
existing.count++;
|
|
@@ -8063,8 +8326,14 @@ ${itemsHtml}
|
|
|
8063
8326
|
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
8064
8327
|
mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
|
|
8065
8328
|
}
|
|
8329
|
+
if (mlpsCveList.length > 0) {
|
|
8330
|
+
const unique = [...new Set(mlpsCveList)];
|
|
8331
|
+
const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
8332
|
+
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`;
|
|
8333
|
+
mlpsRecMap.set("__cve__", { text: cveText, severity: mlpsCveSeverity, count: 1, url: mlpsCveUrl });
|
|
8334
|
+
}
|
|
8066
8335
|
for (const [key, rec] of mlpsRecMap) {
|
|
8067
|
-
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
8336
|
+
if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
|
|
8068
8337
|
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
8069
8338
|
rec.count = 1;
|
|
8070
8339
|
}
|
|
@@ -8360,7 +8629,6 @@ Detects which AWS security services are enabled and assesses overall security ma
|
|
|
8360
8629
|
- **GuardDuty not enabled** \u2014 Risk 7.5: Provides continuous threat detection.
|
|
8361
8630
|
- **Inspector not enabled** \u2014 Risk 6.0: Scans for software vulnerabilities.
|
|
8362
8631
|
- **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
8632
|
- CloudTrail detection is included for coverage metrics.
|
|
8365
8633
|
|
|
8366
8634
|
### Maturity Levels
|
|
@@ -8368,8 +8636,8 @@ Detects which AWS security services are enabled and assesses overall security ma
|
|
|
8368
8636
|
|------------------|-------|
|
|
8369
8637
|
| 0\u20131 | Basic |
|
|
8370
8638
|
| 2\u20133 | Intermediate |
|
|
8371
|
-
| 4
|
|
8372
|
-
|
|
|
8639
|
+
| 4 | Advanced |
|
|
8640
|
+
| 5 | Comprehensive |
|
|
8373
8641
|
|
|
8374
8642
|
## 2. Security Hub Findings (security_hub_findings)
|
|
8375
8643
|
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 +8771,7 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
8503
8771
|
import { join as join2, dirname } from "path";
|
|
8504
8772
|
import { fileURLToPath } from "url";
|
|
8505
8773
|
var MODULE_DESCRIPTIONS = {
|
|
8506
|
-
service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config
|
|
8774
|
+
service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config) are enabled and assesses security maturity.",
|
|
8507
8775
|
secret_exposure: "Checks Lambda env vars and EC2 userData for exposed secrets (AWS keys, private keys, passwords).",
|
|
8508
8776
|
ssl_certificate: "Checks ACM certificates for expiry, failed status, and upcoming renewals.",
|
|
8509
8777
|
dns_dangling: "Checks Route53 CNAME records for dangling DNS (subdomain takeover risk).",
|
|
@@ -8938,14 +9206,12 @@ function createServer(defaultRegion) {
|
|
|
8938
9206
|
"Security Hub": "+300 security checks",
|
|
8939
9207
|
"GuardDuty": "Threat detection",
|
|
8940
9208
|
"Inspector": "Vulnerability scanning",
|
|
8941
|
-
"AWS Config": "Configuration tracking"
|
|
8942
|
-
"Macie": "Sensitive data detection"
|
|
9209
|
+
"AWS Config": "Configuration tracking"
|
|
8943
9210
|
};
|
|
8944
9211
|
const serviceFreeTrials = {
|
|
8945
9212
|
"Security Hub": true,
|
|
8946
9213
|
"GuardDuty": true,
|
|
8947
|
-
"Inspector": true
|
|
8948
|
-
"Macie": true
|
|
9214
|
+
"Inspector": true
|
|
8949
9215
|
};
|
|
8950
9216
|
const services = detection.services;
|
|
8951
9217
|
const coveragePercent = detection.coveragePercent;
|
|
@@ -8980,7 +9246,7 @@ function createServer(defaultRegion) {
|
|
|
8980
9246
|
lines.push("");
|
|
8981
9247
|
lines.push("### Recommendations (Priority Order)");
|
|
8982
9248
|
lines.push("");
|
|
8983
|
-
const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "
|
|
9249
|
+
const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "CloudTrail"];
|
|
8984
9250
|
const sorted = disabled.sort(
|
|
8985
9251
|
(a, b) => priorityOrder.indexOf(a.name) - priorityOrder.indexOf(b.name)
|
|
8986
9252
|
);
|
|
@@ -9003,7 +9269,7 @@ function createServer(defaultRegion) {
|
|
|
9003
9269
|
const nextMilestones = {
|
|
9004
9270
|
basic: { level: "Intermediate", target: 2, suggestions: ["Security Hub", "GuardDuty"] },
|
|
9005
9271
|
intermediate: { level: "Advanced", target: 4, suggestions: ["Inspector", "AWS Config"] },
|
|
9006
|
-
advanced: { level: "Comprehensive", target:
|
|
9272
|
+
advanced: { level: "Comprehensive", target: 5, suggestions: ["CloudTrail"] }
|
|
9007
9273
|
};
|
|
9008
9274
|
const next = nextMilestones[maturityLevel];
|
|
9009
9275
|
if (next) {
|