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