aws-security-mcp 0.6.0 → 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 +326 -556
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.js +326 -556
- 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.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;
|
|
@@ -3046,126 +2995,37 @@ var SecurityHubFindingsScanner = class {
|
|
|
3046
2995
|
// src/scanners/guardduty-findings.ts
|
|
3047
2996
|
import {
|
|
3048
2997
|
GuardDutyClient as GuardDutyClient2,
|
|
3049
|
-
ListDetectorsCommand as ListDetectorsCommand2
|
|
3050
|
-
ListFindingsCommand,
|
|
3051
|
-
GetFindingsCommand as GetFindingsCommand2
|
|
2998
|
+
ListDetectorsCommand as ListDetectorsCommand2
|
|
3052
2999
|
} from "@aws-sdk/client-guardduty";
|
|
3053
|
-
function gdSeverityToScore(severity) {
|
|
3054
|
-
if (severity >= 7) return 8;
|
|
3055
|
-
if (severity >= 4) return 5.5;
|
|
3056
|
-
return 3;
|
|
3057
|
-
}
|
|
3058
3000
|
var GuardDutyFindingsScanner = class {
|
|
3059
3001
|
moduleName = "guardduty_findings";
|
|
3060
3002
|
async scan(ctx) {
|
|
3061
|
-
const { region
|
|
3003
|
+
const { region } = ctx;
|
|
3062
3004
|
const startMs = Date.now();
|
|
3063
|
-
const findings = [];
|
|
3064
3005
|
const warnings = [];
|
|
3065
|
-
let resourcesScanned = 0;
|
|
3066
3006
|
try {
|
|
3067
3007
|
const client = createClient(GuardDutyClient2, region, ctx.credentials);
|
|
3068
|
-
const
|
|
3069
|
-
const detectorIds =
|
|
3008
|
+
const resp = await client.send(new ListDetectorsCommand2({}));
|
|
3009
|
+
const detectorIds = resp.DetectorIds ?? [];
|
|
3070
3010
|
if (detectorIds.length === 0) {
|
|
3071
3011
|
warnings.push("GuardDuty is not enabled in this region (no detectors found).");
|
|
3072
|
-
return {
|
|
3073
|
-
module: this.moduleName,
|
|
3074
|
-
status: "success",
|
|
3075
|
-
warnings,
|
|
3076
|
-
resourcesScanned: 0,
|
|
3077
|
-
findingsCount: 0,
|
|
3078
|
-
scanTimeMs: Date.now() - startMs,
|
|
3079
|
-
findings: []
|
|
3080
|
-
};
|
|
3081
|
-
}
|
|
3082
|
-
const detectorId = detectorIds[0];
|
|
3083
|
-
let nextToken;
|
|
3084
|
-
const findingIds = [];
|
|
3085
|
-
do {
|
|
3086
|
-
const listResp = await client.send(
|
|
3087
|
-
new ListFindingsCommand({
|
|
3088
|
-
DetectorId: detectorId,
|
|
3089
|
-
FindingCriteria: {
|
|
3090
|
-
Criterion: {
|
|
3091
|
-
"service.archived": {
|
|
3092
|
-
Eq: ["false"]
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
},
|
|
3096
|
-
MaxResults: 50,
|
|
3097
|
-
NextToken: nextToken
|
|
3098
|
-
})
|
|
3099
|
-
);
|
|
3100
|
-
findingIds.push(...listResp.FindingIds ?? []);
|
|
3101
|
-
nextToken = listResp.NextToken;
|
|
3102
|
-
} while (nextToken);
|
|
3103
|
-
resourcesScanned = findingIds.length;
|
|
3104
|
-
if (findingIds.length === 0) {
|
|
3105
|
-
return {
|
|
3106
|
-
module: this.moduleName,
|
|
3107
|
-
status: "success",
|
|
3108
|
-
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3109
|
-
resourcesScanned: 0,
|
|
3110
|
-
findingsCount: 0,
|
|
3111
|
-
scanTimeMs: Date.now() - startMs,
|
|
3112
|
-
findings: []
|
|
3113
|
-
};
|
|
3114
|
-
}
|
|
3115
|
-
for (let i = 0; i < findingIds.length; i += 50) {
|
|
3116
|
-
const batch = findingIds.slice(i, i + 50);
|
|
3117
|
-
const detailsResp = await client.send(
|
|
3118
|
-
new GetFindingsCommand2({
|
|
3119
|
-
DetectorId: detectorId,
|
|
3120
|
-
FindingIds: batch
|
|
3121
|
-
})
|
|
3122
|
-
);
|
|
3123
|
-
for (const gdf of detailsResp.Findings ?? []) {
|
|
3124
|
-
const gdSeverity = gdf.Severity ?? 0;
|
|
3125
|
-
const score = gdSeverityToScore(gdSeverity);
|
|
3126
|
-
const severity = severityFromScore(score);
|
|
3127
|
-
const resourceType = gdf.Resource?.ResourceType ?? "AWS::Unknown";
|
|
3128
|
-
const resourceId = gdf.Resource?.InstanceDetails?.InstanceId ?? gdf.Resource?.AccessKeyDetails?.AccessKeyId ?? gdf.Arn ?? "unknown";
|
|
3129
|
-
const resourceArn = gdf.Arn ?? `arn:${partition}:guardduty:${region}:${accountId}:detector/${detectorId}/finding/${gdf.Id ?? "unknown"}`;
|
|
3130
|
-
findings.push({
|
|
3131
|
-
severity,
|
|
3132
|
-
title: `[GuardDuty] ${gdf.Title ?? gdf.Type ?? "Finding"}`,
|
|
3133
|
-
resourceType,
|
|
3134
|
-
resourceId,
|
|
3135
|
-
resourceArn,
|
|
3136
|
-
region: gdf.Region ?? region,
|
|
3137
|
-
description: gdf.Description ?? gdf.Title ?? "No description",
|
|
3138
|
-
impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
|
|
3139
|
-
riskScore: score,
|
|
3140
|
-
remediationSteps: [
|
|
3141
|
-
`Investigate ${gdf.Type ?? "unknown threat"}: ${gdf.Title ?? "threat detected"}`,
|
|
3142
|
-
gdf.Description ? `Details: ${gdf.Description.substring(0, 200)}` : "",
|
|
3143
|
-
"Isolate affected resources if compromise is confirmed.",
|
|
3144
|
-
"Review CloudTrail logs for related suspicious activity."
|
|
3145
|
-
].filter(Boolean),
|
|
3146
|
-
priority: priorityFromSeverity(severity),
|
|
3147
|
-
module: this.moduleName,
|
|
3148
|
-
accountId: gdf.AccountId ?? accountId
|
|
3149
|
-
});
|
|
3150
|
-
}
|
|
3151
3012
|
}
|
|
3152
3013
|
return {
|
|
3153
3014
|
module: this.moduleName,
|
|
3154
3015
|
status: "success",
|
|
3155
3016
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3156
|
-
resourcesScanned,
|
|
3157
|
-
findingsCount:
|
|
3017
|
+
resourcesScanned: 0,
|
|
3018
|
+
findingsCount: 0,
|
|
3158
3019
|
scanTimeMs: Date.now() - startMs,
|
|
3159
|
-
findings
|
|
3020
|
+
findings: []
|
|
3160
3021
|
};
|
|
3161
3022
|
} catch (err) {
|
|
3162
3023
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3163
3024
|
return {
|
|
3164
3025
|
module: this.moduleName,
|
|
3165
3026
|
status: "error",
|
|
3166
|
-
error: `GuardDuty
|
|
3167
|
-
|
|
3168
|
-
resourcesScanned,
|
|
3027
|
+
error: `GuardDuty detection check failed: ${msg}`,
|
|
3028
|
+
resourcesScanned: 0,
|
|
3169
3029
|
findingsCount: 0,
|
|
3170
3030
|
scanTimeMs: Date.now() - startMs,
|
|
3171
3031
|
findings: []
|
|
@@ -3177,164 +3037,59 @@ var GuardDutyFindingsScanner = class {
|
|
|
3177
3037
|
// src/scanners/inspector-findings.ts
|
|
3178
3038
|
import {
|
|
3179
3039
|
Inspector2Client as Inspector2Client2,
|
|
3180
|
-
|
|
3040
|
+
BatchGetAccountStatusCommand as BatchGetAccountStatusCommand2
|
|
3181
3041
|
} from "@aws-sdk/client-inspector2";
|
|
3182
|
-
function inspectorSeverityToScore(label) {
|
|
3183
|
-
switch (label) {
|
|
3184
|
-
case "CRITICAL":
|
|
3185
|
-
return 9.5;
|
|
3186
|
-
case "HIGH":
|
|
3187
|
-
return 8;
|
|
3188
|
-
case "MEDIUM":
|
|
3189
|
-
return 5.5;
|
|
3190
|
-
case "LOW":
|
|
3191
|
-
return 3;
|
|
3192
|
-
case "INFORMATIONAL":
|
|
3193
|
-
return null;
|
|
3194
|
-
case "UNTRIAGED":
|
|
3195
|
-
return 5.5;
|
|
3196
|
-
default:
|
|
3197
|
-
return null;
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
3042
|
var InspectorFindingsScanner = class {
|
|
3201
3043
|
moduleName = "inspector_findings";
|
|
3202
3044
|
async scan(ctx) {
|
|
3203
|
-
const { region
|
|
3045
|
+
const { region } = ctx;
|
|
3204
3046
|
const startMs = Date.now();
|
|
3205
|
-
const findings = [];
|
|
3206
3047
|
const warnings = [];
|
|
3207
|
-
let resourcesScanned = 0;
|
|
3208
3048
|
try {
|
|
3209
3049
|
const client = createClient(Inspector2Client2, region, ctx.credentials);
|
|
3210
|
-
|
|
3211
|
-
const
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
const
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
const severity = severityFromScore(score);
|
|
3229
|
-
const cveId = f.packageVulnerabilityDetails?.vulnerabilityId;
|
|
3230
|
-
const titleBase = f.title ?? "Inspector Finding";
|
|
3231
|
-
const title = cveId ? `[${cveId}] ${titleBase}` : titleBase;
|
|
3232
|
-
const resourceId = f.resources?.[0]?.id ?? "unknown";
|
|
3233
|
-
const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
|
|
3234
|
-
const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
|
|
3235
|
-
const remediationSteps = [];
|
|
3236
|
-
const vulnPkgs = f.packageVulnerabilityDetails?.vulnerablePackages;
|
|
3237
|
-
if (vulnPkgs?.length) {
|
|
3238
|
-
for (const pkg of vulnPkgs.slice(0, 3)) {
|
|
3239
|
-
const name = pkg.name ?? "unknown-package";
|
|
3240
|
-
const installed = pkg.version ?? "unknown";
|
|
3241
|
-
const fixed = pkg.fixedInVersion ?? "latest";
|
|
3242
|
-
const cveRef = cveId ? ` to fix ${cveId}` : "";
|
|
3243
|
-
remediationSteps.push(`Update ${name} from ${installed} to ${fixed}${cveRef}`);
|
|
3244
|
-
}
|
|
3245
|
-
} else if (f.remediation?.recommendation?.text) {
|
|
3246
|
-
remediationSteps.push(f.remediation.recommendation.text);
|
|
3247
|
-
}
|
|
3248
|
-
const genericPatterns = ["See References", "None Provided", "Review the finding"];
|
|
3249
|
-
if (remediationSteps.length === 0 || genericPatterns.some((p) => remediationSteps[0]?.startsWith(p))) {
|
|
3250
|
-
remediationSteps.length = 0;
|
|
3251
|
-
const rawTitle = f.title ?? "";
|
|
3252
|
-
if (rawTitle.includes("KB")) {
|
|
3253
|
-
const kbMatch = rawTitle.match(/KB\d+/);
|
|
3254
|
-
const kb = kbMatch ? kbMatch[0] : "patch";
|
|
3255
|
-
remediationSteps.push(`Install Windows patch ${kb} via WSUS or AWS Systems Manager Patch Manager`);
|
|
3256
|
-
remediationSteps.push(`Run: aws ssm send-command --document-name "AWS-InstallWindowsUpdates" --targets "Key=InstanceIds,Values=${resourceId}"`);
|
|
3257
|
-
if (kbMatch) {
|
|
3258
|
-
remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${kb}`);
|
|
3259
|
-
}
|
|
3260
|
-
} else if (rawTitle.includes("CVE-") || cveId) {
|
|
3261
|
-
const cveMatch = rawTitle.match(/CVE-[\d-]+/);
|
|
3262
|
-
const cve = cveMatch ? cveMatch[0] : cveId ?? "vulnerability";
|
|
3263
|
-
remediationSteps.push(`Fix ${cve}: update the affected software package to the latest patched version`);
|
|
3264
|
-
} else {
|
|
3265
|
-
remediationSteps.push(`Review and remediate: ${rawTitle}`);
|
|
3266
|
-
}
|
|
3267
|
-
}
|
|
3268
|
-
if (f.remediation?.recommendation?.Url) {
|
|
3269
|
-
remediationSteps.push(`Documentation: ${f.remediation.recommendation.Url}`);
|
|
3270
|
-
}
|
|
3271
|
-
if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
|
|
3272
|
-
remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
|
|
3273
|
-
}
|
|
3274
|
-
const description = f.description ?? titleBase;
|
|
3275
|
-
const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
|
|
3276
|
-
findings.push({
|
|
3277
|
-
severity,
|
|
3278
|
-
title,
|
|
3279
|
-
resourceType,
|
|
3280
|
-
resourceId,
|
|
3281
|
-
resourceArn,
|
|
3282
|
-
region,
|
|
3283
|
-
description,
|
|
3284
|
-
impact,
|
|
3285
|
-
riskScore: score,
|
|
3286
|
-
remediationSteps,
|
|
3287
|
-
priority: priorityFromSeverity(severity),
|
|
3288
|
-
module: this.moduleName,
|
|
3289
|
-
accountId: f.awsAccountId ?? accountId
|
|
3290
|
-
});
|
|
3050
|
+
const resp = await client.send(new BatchGetAccountStatusCommand2({ accountIds: [] }));
|
|
3051
|
+
const account = resp.accounts?.[0];
|
|
3052
|
+
if (!account || account.state?.status !== "ENABLED") {
|
|
3053
|
+
warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
|
|
3054
|
+
} else {
|
|
3055
|
+
const rs = account.resourceState;
|
|
3056
|
+
const types = [
|
|
3057
|
+
{ name: "EC2", status: rs?.ec2?.status },
|
|
3058
|
+
{ name: "Lambda", status: rs?.lambda?.status },
|
|
3059
|
+
{ name: "ECR", status: rs?.ecr?.status },
|
|
3060
|
+
{ name: "Lambda Code", status: rs?.lambdaCode?.status },
|
|
3061
|
+
{ name: "Code Repository", status: rs?.codeRepository?.status }
|
|
3062
|
+
];
|
|
3063
|
+
const disabled = types.filter((t) => t.status && t.status !== "ENABLED");
|
|
3064
|
+
if (disabled.length > 0) {
|
|
3065
|
+
warnings.push(
|
|
3066
|
+
`Inspector scan types not enabled: ${disabled.map((t) => t.name).join(", ")}. Enable them for full vulnerability coverage.`
|
|
3067
|
+
);
|
|
3291
3068
|
}
|
|
3292
|
-
|
|
3293
|
-
} while (nextToken);
|
|
3069
|
+
}
|
|
3294
3070
|
return {
|
|
3295
3071
|
module: this.moduleName,
|
|
3296
3072
|
status: "success",
|
|
3297
3073
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3298
|
-
resourcesScanned,
|
|
3299
|
-
findingsCount:
|
|
3074
|
+
resourcesScanned: 0,
|
|
3075
|
+
findingsCount: 0,
|
|
3300
3076
|
scanTimeMs: Date.now() - startMs,
|
|
3301
|
-
findings
|
|
3077
|
+
findings: []
|
|
3302
3078
|
};
|
|
3303
3079
|
} catch (err) {
|
|
3304
3080
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3305
3081
|
const errName = err instanceof Error ? err.name : "";
|
|
3306
3082
|
const isAccessDenied2 = errName === "AccessDeniedException" || msg.includes("AccessDeniedException");
|
|
3307
|
-
const isNotEnabled2 = msg.includes("not enabled") || msg.includes("not subscribed");
|
|
3308
3083
|
if (isAccessDenied2) {
|
|
3309
|
-
warnings.push("Insufficient permissions to access Inspector. Grant inspector2:
|
|
3310
|
-
|
|
3311
|
-
module: this.moduleName,
|
|
3312
|
-
status: "success",
|
|
3313
|
-
warnings,
|
|
3314
|
-
resourcesScanned: 0,
|
|
3315
|
-
findingsCount: 0,
|
|
3316
|
-
scanTimeMs: Date.now() - startMs,
|
|
3317
|
-
findings: []
|
|
3318
|
-
};
|
|
3319
|
-
}
|
|
3320
|
-
if (isNotEnabled2) {
|
|
3084
|
+
warnings.push("Insufficient permissions to access Inspector. Grant inspector2:BatchGetAccountStatus to check enablement.");
|
|
3085
|
+
} else {
|
|
3321
3086
|
warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
|
|
3322
|
-
return {
|
|
3323
|
-
module: this.moduleName,
|
|
3324
|
-
status: "success",
|
|
3325
|
-
warnings,
|
|
3326
|
-
resourcesScanned: 0,
|
|
3327
|
-
findingsCount: 0,
|
|
3328
|
-
scanTimeMs: Date.now() - startMs,
|
|
3329
|
-
findings: []
|
|
3330
|
-
};
|
|
3331
3087
|
}
|
|
3332
3088
|
return {
|
|
3333
3089
|
module: this.moduleName,
|
|
3334
|
-
status: "
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
resourcesScanned,
|
|
3090
|
+
status: "success",
|
|
3091
|
+
warnings,
|
|
3092
|
+
resourcesScanned: 0,
|
|
3338
3093
|
findingsCount: 0,
|
|
3339
3094
|
scanTimeMs: Date.now() - startMs,
|
|
3340
3095
|
findings: []
|
|
@@ -3507,128 +3262,29 @@ var TrustedAdvisorFindingsScanner = class {
|
|
|
3507
3262
|
// src/scanners/config-rules-findings.ts
|
|
3508
3263
|
import {
|
|
3509
3264
|
ConfigServiceClient as ConfigServiceClient2,
|
|
3510
|
-
|
|
3511
|
-
GetComplianceDetailsByConfigRuleCommand
|
|
3265
|
+
DescribeConfigurationRecordersCommand as DescribeConfigurationRecordersCommand2
|
|
3512
3266
|
} from "@aws-sdk/client-config-service";
|
|
3513
|
-
var SECURITY_RULE_PATTERNS = [
|
|
3514
|
-
"securitygroup",
|
|
3515
|
-
"security-group",
|
|
3516
|
-
"encryption",
|
|
3517
|
-
"encrypted",
|
|
3518
|
-
"public",
|
|
3519
|
-
"unrestricted",
|
|
3520
|
-
"mfa",
|
|
3521
|
-
"password",
|
|
3522
|
-
"access-key",
|
|
3523
|
-
"root",
|
|
3524
|
-
"admin",
|
|
3525
|
-
"logging",
|
|
3526
|
-
"cloudtrail",
|
|
3527
|
-
"iam",
|
|
3528
|
-
"kms",
|
|
3529
|
-
"ssl",
|
|
3530
|
-
"tls",
|
|
3531
|
-
"vpc-flow",
|
|
3532
|
-
"guardduty",
|
|
3533
|
-
"securityhub"
|
|
3534
|
-
];
|
|
3535
|
-
function ruleIsSecurityRelated(ruleName) {
|
|
3536
|
-
const lower = ruleName.toLowerCase();
|
|
3537
|
-
return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
|
|
3538
|
-
}
|
|
3539
3267
|
var ConfigRulesFindingsScanner = class {
|
|
3540
3268
|
moduleName = "config_rules_findings";
|
|
3541
3269
|
async scan(ctx) {
|
|
3542
|
-
const { region
|
|
3270
|
+
const { region } = ctx;
|
|
3543
3271
|
const startMs = Date.now();
|
|
3544
|
-
const findings = [];
|
|
3545
3272
|
const warnings = [];
|
|
3546
|
-
let resourcesScanned = 0;
|
|
3547
3273
|
try {
|
|
3548
3274
|
const client = createClient(ConfigServiceClient2, region, ctx.credentials);
|
|
3549
|
-
|
|
3550
|
-
const
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
|
|
3554
|
-
);
|
|
3555
|
-
for (const rule of resp.ComplianceByConfigRules ?? []) {
|
|
3556
|
-
resourcesScanned++;
|
|
3557
|
-
if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
|
|
3558
|
-
nonCompliantRules.push(rule);
|
|
3559
|
-
}
|
|
3560
|
-
}
|
|
3561
|
-
nextToken = resp.NextToken;
|
|
3562
|
-
} while (nextToken);
|
|
3563
|
-
if (resourcesScanned === 0) {
|
|
3564
|
-
warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
|
|
3565
|
-
return {
|
|
3566
|
-
module: this.moduleName,
|
|
3567
|
-
status: "success",
|
|
3568
|
-
warnings,
|
|
3569
|
-
resourcesScanned: 0,
|
|
3570
|
-
findingsCount: 0,
|
|
3571
|
-
scanTimeMs: Date.now() - startMs,
|
|
3572
|
-
findings: []
|
|
3573
|
-
};
|
|
3574
|
-
}
|
|
3575
|
-
for (const rule of nonCompliantRules) {
|
|
3576
|
-
const ruleName = rule.ConfigRuleName ?? "unknown";
|
|
3577
|
-
try {
|
|
3578
|
-
let detailToken;
|
|
3579
|
-
do {
|
|
3580
|
-
const detailResp = await client.send(
|
|
3581
|
-
new GetComplianceDetailsByConfigRuleCommand({
|
|
3582
|
-
ConfigRuleName: ruleName,
|
|
3583
|
-
ComplianceTypes: ["NON_COMPLIANT"],
|
|
3584
|
-
NextToken: detailToken
|
|
3585
|
-
})
|
|
3586
|
-
);
|
|
3587
|
-
for (const evalResult of detailResp.EvaluationResults ?? []) {
|
|
3588
|
-
const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
|
|
3589
|
-
const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
|
|
3590
|
-
const resourceId = qualifier?.ResourceId ?? "unknown";
|
|
3591
|
-
const annotation = evalResult.Annotation;
|
|
3592
|
-
const isSecurityRule = ruleIsSecurityRelated(ruleName);
|
|
3593
|
-
const riskScore = isSecurityRule ? 7.5 : 5.5;
|
|
3594
|
-
const severity = severityFromScore(riskScore);
|
|
3595
|
-
const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
|
|
3596
|
-
if (annotation) descParts.push(`Annotation: ${annotation}`);
|
|
3597
|
-
findings.push({
|
|
3598
|
-
severity,
|
|
3599
|
-
title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
|
|
3600
|
-
resourceType,
|
|
3601
|
-
resourceId,
|
|
3602
|
-
resourceArn: resourceId,
|
|
3603
|
-
region,
|
|
3604
|
-
description: descParts.join(". "),
|
|
3605
|
-
impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
|
|
3606
|
-
riskScore,
|
|
3607
|
-
remediationSteps: [
|
|
3608
|
-
`Fix Config Rule violation: ${ruleName}`,
|
|
3609
|
-
annotation ? `Details: ${annotation}` : "",
|
|
3610
|
-
`Resource: ${resourceType}/${resourceId}`
|
|
3611
|
-
].filter(Boolean),
|
|
3612
|
-
priority: priorityFromSeverity(severity),
|
|
3613
|
-
module: this.moduleName,
|
|
3614
|
-
accountId
|
|
3615
|
-
});
|
|
3616
|
-
}
|
|
3617
|
-
detailToken = detailResp.NextToken;
|
|
3618
|
-
} while (detailToken);
|
|
3619
|
-
} catch (detailErr) {
|
|
3620
|
-
const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
|
|
3621
|
-
warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
|
|
3622
|
-
}
|
|
3275
|
+
const resp = await client.send(new DescribeConfigurationRecordersCommand2({}));
|
|
3276
|
+
const recorders = resp.ConfigurationRecorders ?? [];
|
|
3277
|
+
if (recorders.length === 0) {
|
|
3278
|
+
warnings.push("AWS Config is not enabled in this region.");
|
|
3623
3279
|
}
|
|
3624
3280
|
return {
|
|
3625
3281
|
module: this.moduleName,
|
|
3626
3282
|
status: "success",
|
|
3627
3283
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3628
|
-
resourcesScanned,
|
|
3629
|
-
findingsCount:
|
|
3284
|
+
resourcesScanned: 0,
|
|
3285
|
+
findingsCount: 0,
|
|
3630
3286
|
scanTimeMs: Date.now() - startMs,
|
|
3631
|
-
findings
|
|
3287
|
+
findings: []
|
|
3632
3288
|
};
|
|
3633
3289
|
} catch (err) {
|
|
3634
3290
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3647,9 +3303,8 @@ var ConfigRulesFindingsScanner = class {
|
|
|
3647
3303
|
return {
|
|
3648
3304
|
module: this.moduleName,
|
|
3649
3305
|
status: "error",
|
|
3650
|
-
error: `Config Rules
|
|
3651
|
-
|
|
3652
|
-
resourcesScanned,
|
|
3306
|
+
error: `Config Rules detection check failed: ${msg}`,
|
|
3307
|
+
resourcesScanned: 0,
|
|
3653
3308
|
findingsCount: 0,
|
|
3654
3309
|
scanTimeMs: Date.now() - startMs,
|
|
3655
3310
|
findings: []
|
|
@@ -3661,146 +3316,50 @@ var ConfigRulesFindingsScanner = class {
|
|
|
3661
3316
|
// src/scanners/access-analyzer-findings.ts
|
|
3662
3317
|
import {
|
|
3663
3318
|
AccessAnalyzerClient,
|
|
3664
|
-
ListAnalyzersCommand
|
|
3665
|
-
ListFindingsV2Command
|
|
3319
|
+
ListAnalyzersCommand
|
|
3666
3320
|
} from "@aws-sdk/client-accessanalyzer";
|
|
3667
|
-
function findingTypeToScore(findingType) {
|
|
3668
|
-
const ft = findingType;
|
|
3669
|
-
switch (ft) {
|
|
3670
|
-
case "ExternalAccess":
|
|
3671
|
-
return 8;
|
|
3672
|
-
case "UnusedIAMRole":
|
|
3673
|
-
case "UnusedIAMUserAccessKey":
|
|
3674
|
-
case "UnusedIAMUserPassword":
|
|
3675
|
-
return 5.5;
|
|
3676
|
-
case "UnusedPermission":
|
|
3677
|
-
return 3;
|
|
3678
|
-
default:
|
|
3679
|
-
return 5.5;
|
|
3680
|
-
}
|
|
3681
|
-
}
|
|
3682
|
-
var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
|
|
3683
|
-
"UnusedIAMRole",
|
|
3684
|
-
"UnusedIAMUserAccessKey",
|
|
3685
|
-
"UnusedIAMUserPassword",
|
|
3686
|
-
"UnusedPermission"
|
|
3687
|
-
]);
|
|
3688
|
-
function isSecurityRelevant(findingType) {
|
|
3689
|
-
const ft = findingType;
|
|
3690
|
-
return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
|
|
3691
|
-
}
|
|
3692
|
-
function isExternalAccess(findingType) {
|
|
3693
|
-
return findingType === "ExternalAccess";
|
|
3694
|
-
}
|
|
3695
3321
|
var AccessAnalyzerFindingsScanner = class {
|
|
3696
3322
|
moduleName = "access_analyzer_findings";
|
|
3697
3323
|
async scan(ctx) {
|
|
3698
|
-
const { region
|
|
3324
|
+
const { region } = ctx;
|
|
3699
3325
|
const startMs = Date.now();
|
|
3700
|
-
const findings = [];
|
|
3701
3326
|
const warnings = [];
|
|
3702
|
-
let resourcesScanned = 0;
|
|
3703
3327
|
try {
|
|
3704
3328
|
const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
|
|
3705
3329
|
let analyzerToken;
|
|
3706
|
-
|
|
3330
|
+
let hasActiveAnalyzer = false;
|
|
3707
3331
|
do {
|
|
3708
3332
|
const resp = await client.send(
|
|
3709
3333
|
new ListAnalyzersCommand({ nextToken: analyzerToken })
|
|
3710
3334
|
);
|
|
3711
3335
|
for (const analyzer of resp.analyzers ?? []) {
|
|
3712
3336
|
if (analyzer.status === "ACTIVE") {
|
|
3713
|
-
|
|
3337
|
+
hasActiveAnalyzer = true;
|
|
3338
|
+
break;
|
|
3714
3339
|
}
|
|
3715
3340
|
}
|
|
3341
|
+
if (hasActiveAnalyzer) break;
|
|
3716
3342
|
analyzerToken = resp.nextToken;
|
|
3717
3343
|
} while (analyzerToken);
|
|
3718
|
-
if (
|
|
3344
|
+
if (!hasActiveAnalyzer) {
|
|
3719
3345
|
warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
|
|
3720
|
-
return {
|
|
3721
|
-
module: this.moduleName,
|
|
3722
|
-
status: "success",
|
|
3723
|
-
warnings,
|
|
3724
|
-
resourcesScanned: 0,
|
|
3725
|
-
findingsCount: 0,
|
|
3726
|
-
scanTimeMs: Date.now() - startMs,
|
|
3727
|
-
findings: []
|
|
3728
|
-
};
|
|
3729
|
-
}
|
|
3730
|
-
for (const analyzer of analyzers) {
|
|
3731
|
-
const analyzerArn = analyzer.arn ?? "unknown";
|
|
3732
|
-
let findingToken;
|
|
3733
|
-
do {
|
|
3734
|
-
const listResp = await client.send(
|
|
3735
|
-
new ListFindingsV2Command({
|
|
3736
|
-
analyzerArn,
|
|
3737
|
-
filter: {
|
|
3738
|
-
status: { eq: ["ACTIVE"] }
|
|
3739
|
-
},
|
|
3740
|
-
nextToken: findingToken
|
|
3741
|
-
})
|
|
3742
|
-
);
|
|
3743
|
-
for (const aaf of listResp.findings ?? []) {
|
|
3744
|
-
if (!isSecurityRelevant(aaf.findingType)) {
|
|
3745
|
-
continue;
|
|
3746
|
-
}
|
|
3747
|
-
resourcesScanned++;
|
|
3748
|
-
const score = findingTypeToScore(aaf.findingType);
|
|
3749
|
-
const severity = severityFromScore(score);
|
|
3750
|
-
const resourceArn = aaf.resource ?? "unknown";
|
|
3751
|
-
const resourceType = aaf.resourceType ?? "AWS::Unknown";
|
|
3752
|
-
const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
|
|
3753
|
-
const external = isExternalAccess(aaf.findingType);
|
|
3754
|
-
const descParts = [`Resource Type: ${resourceType}`];
|
|
3755
|
-
if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
|
|
3756
|
-
if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
|
|
3757
|
-
const title = buildFindingTitle(aaf);
|
|
3758
|
-
const impact = external ? `Resource is accessible from outside the account. Type: ${aaf.findingType ?? "unknown"}` : `Unused access detected \u2014 review and remove to follow least-privilege. Type: ${aaf.findingType ?? "unknown"}`;
|
|
3759
|
-
const remediationSteps = external ? [
|
|
3760
|
-
`Restrict external access on ${resourceType} ${resourceId}`,
|
|
3761
|
-
"Remove or narrow the resource policy to eliminate unintended external access.",
|
|
3762
|
-
`Resource ARN: ${resourceArn}`
|
|
3763
|
-
] : [
|
|
3764
|
-
`Remove unused access on ${resourceType} ${resourceId}`,
|
|
3765
|
-
"Remove unused permissions, roles, or credentials to follow least-privilege.",
|
|
3766
|
-
`Resource ARN: ${resourceArn}`
|
|
3767
|
-
];
|
|
3768
|
-
findings.push({
|
|
3769
|
-
severity,
|
|
3770
|
-
title,
|
|
3771
|
-
resourceType: mapResourceType(resourceType),
|
|
3772
|
-
resourceId,
|
|
3773
|
-
resourceArn,
|
|
3774
|
-
region,
|
|
3775
|
-
description: descParts.join(". "),
|
|
3776
|
-
impact,
|
|
3777
|
-
riskScore: score,
|
|
3778
|
-
remediationSteps,
|
|
3779
|
-
priority: priorityFromSeverity(severity),
|
|
3780
|
-
module: this.moduleName,
|
|
3781
|
-
accountId: aaf.resourceOwnerAccount ?? accountId
|
|
3782
|
-
});
|
|
3783
|
-
}
|
|
3784
|
-
findingToken = listResp.nextToken;
|
|
3785
|
-
} while (findingToken);
|
|
3786
3346
|
}
|
|
3787
3347
|
return {
|
|
3788
3348
|
module: this.moduleName,
|
|
3789
3349
|
status: "success",
|
|
3790
3350
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3791
|
-
resourcesScanned,
|
|
3792
|
-
findingsCount:
|
|
3351
|
+
resourcesScanned: 0,
|
|
3352
|
+
findingsCount: 0,
|
|
3793
3353
|
scanTimeMs: Date.now() - startMs,
|
|
3794
|
-
findings
|
|
3354
|
+
findings: []
|
|
3795
3355
|
};
|
|
3796
3356
|
} catch (err) {
|
|
3797
3357
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3798
3358
|
return {
|
|
3799
3359
|
module: this.moduleName,
|
|
3800
3360
|
status: "error",
|
|
3801
|
-
error: `Access Analyzer
|
|
3802
|
-
|
|
3803
|
-
resourcesScanned,
|
|
3361
|
+
error: `Access Analyzer detection check failed: ${msg}`,
|
|
3362
|
+
resourcesScanned: 0,
|
|
3804
3363
|
findingsCount: 0,
|
|
3805
3364
|
scanTimeMs: Date.now() - startMs,
|
|
3806
3365
|
findings: []
|
|
@@ -3808,29 +3367,6 @@ var AccessAnalyzerFindingsScanner = class {
|
|
|
3808
3367
|
}
|
|
3809
3368
|
}
|
|
3810
3369
|
};
|
|
3811
|
-
function buildFindingTitle(finding) {
|
|
3812
|
-
const resourceType = finding.resourceType ?? "Resource";
|
|
3813
|
-
const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
|
|
3814
|
-
const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
|
|
3815
|
-
return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
|
|
3816
|
-
}
|
|
3817
|
-
function mapResourceType(aaType) {
|
|
3818
|
-
const mapping = {
|
|
3819
|
-
"AWS::S3::Bucket": "AWS::S3::Bucket",
|
|
3820
|
-
"AWS::IAM::Role": "AWS::IAM::Role",
|
|
3821
|
-
"AWS::SQS::Queue": "AWS::SQS::Queue",
|
|
3822
|
-
"AWS::Lambda::Function": "AWS::Lambda::Function",
|
|
3823
|
-
"AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
|
|
3824
|
-
"AWS::KMS::Key": "AWS::KMS::Key",
|
|
3825
|
-
"AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
|
|
3826
|
-
"AWS::SNS::Topic": "AWS::SNS::Topic",
|
|
3827
|
-
"AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
|
|
3828
|
-
"AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
|
|
3829
|
-
"AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
|
|
3830
|
-
"AWS::ECR::Repository": "AWS::ECR::Repository"
|
|
3831
|
-
};
|
|
3832
|
-
return mapping[aaType] ?? aaType;
|
|
3833
|
-
}
|
|
3834
3370
|
|
|
3835
3371
|
// src/scanners/patch-compliance-findings.ts
|
|
3836
3372
|
import {
|
|
@@ -4368,6 +3904,44 @@ var zhI18n = {
|
|
|
4368
3904
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
|
|
4369
3905
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
|
|
4370
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
|
+
},
|
|
4371
3945
|
// Service Recommendations
|
|
4372
3946
|
notEnabled: "\u672A\u542F\u7528",
|
|
4373
3947
|
serviceRecommendations: {
|
|
@@ -4600,6 +4174,44 @@ var enI18n = {
|
|
|
4600
4174
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
|
|
4601
4175
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
|
|
4602
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
|
+
},
|
|
4603
4215
|
// Service Recommendations
|
|
4604
4216
|
notEnabled: "Not Enabled",
|
|
4605
4217
|
serviceRecommendations: {
|
|
@@ -4804,7 +4416,7 @@ function generateMarkdownReport(scanResults, lang) {
|
|
|
4804
4416
|
for (const m of modules) {
|
|
4805
4417
|
const status = m.status === "success" ? "\u2705" : "\u274C";
|
|
4806
4418
|
lines.push(
|
|
4807
|
-
`| ${m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
|
|
4419
|
+
`| ${t.moduleNames[m.module] ?? m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
|
|
4808
4420
|
);
|
|
4809
4421
|
}
|
|
4810
4422
|
lines.push("");
|
|
@@ -7647,6 +7259,22 @@ var SEV_COLOR = {
|
|
|
7647
7259
|
LOW: "#22c55e"
|
|
7648
7260
|
};
|
|
7649
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"];
|
|
7650
7278
|
function scoreColor(score) {
|
|
7651
7279
|
if (score >= 80) return "#22c55e";
|
|
7652
7280
|
if (score >= 50) return "#eab308";
|
|
@@ -8024,6 +7652,47 @@ function generateHtmlReport(scanResults, history, lang) {
|
|
|
8024
7652
|
const allFindings = modules.flatMap(
|
|
8025
7653
|
(m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
|
|
8026
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
|
+
});
|
|
8027
7696
|
let top5Html = "";
|
|
8028
7697
|
if (allFindings.length > 0) {
|
|
8029
7698
|
const top5 = [...allFindings].sort((a, b) => b.riskScore - a.riskScore).slice(0, 5);
|
|
@@ -8089,34 +7758,51 @@ ${rest}
|
|
|
8089
7758
|
if (!moduleMap.has(mod)) moduleMap.set(mod, []);
|
|
8090
7759
|
moduleMap.get(mod).push(f);
|
|
8091
7760
|
}
|
|
8092
|
-
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) => {
|
|
8093
7773
|
const aHasCritHigh = a[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
8094
7774
|
const bHasCritHigh = b[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
8095
7775
|
if (aHasCritHigh !== bHasCritHigh) return aHasCritHigh ? -1 : 1;
|
|
8096
7776
|
return b[1].length - a[1].length;
|
|
8097
7777
|
});
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
const findings = modFindings.filter((f) => f.severity === sev);
|
|
8104
|
-
if (findings.length === 0) return "";
|
|
8105
|
-
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);
|
|
8106
7783
|
const emoji = SEV_EMOJI[sev] ?? "";
|
|
8107
7784
|
const label = sev.charAt(0) + sev.slice(1).toLowerCase();
|
|
8108
7785
|
return `<details class="severity-group-fold">
|
|
8109
|
-
<summary><h4>${emoji} ${label} (${
|
|
8110
|
-
${renderCards(
|
|
7786
|
+
<summary><h4>${emoji} ${label} (${sevFindings.length})</h4></summary>
|
|
7787
|
+
${renderCards(sevFindings)}
|
|
8111
7788
|
</details>`;
|
|
8112
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);
|
|
8113
7799
|
return `<details class="module-fold">
|
|
8114
7800
|
<summary>
|
|
8115
|
-
<h3>🔒 ${esc(
|
|
7801
|
+
<h3>🔒 ${esc(displayName)} (${modFindings.length})</h3>
|
|
8116
7802
|
<span class="module-badges">${badges}</span>
|
|
8117
7803
|
</summary>
|
|
8118
7804
|
<div class="module-body">
|
|
8119
|
-
${
|
|
7805
|
+
${renderSeverityGroups(modFindings)}
|
|
8120
7806
|
</div>
|
|
8121
7807
|
</details>`;
|
|
8122
7808
|
}).join("\n");
|
|
@@ -8136,8 +7822,29 @@ ${rest}
|
|
|
8136
7822
|
</div>
|
|
8137
7823
|
</section>`;
|
|
8138
7824
|
}
|
|
8139
|
-
const
|
|
8140
|
-
|
|
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
|
+
}
|
|
8141
7848
|
).join("\n");
|
|
8142
7849
|
let recsHtml = "";
|
|
8143
7850
|
if (summary.totalFindings > 0) {
|
|
@@ -8145,6 +7852,9 @@ ${rest}
|
|
|
8145
7852
|
const kbPatches = [];
|
|
8146
7853
|
let kbSeverity = "LOW";
|
|
8147
7854
|
let kbUrl;
|
|
7855
|
+
const cveList = [];
|
|
7856
|
+
let cveSeverity = "LOW";
|
|
7857
|
+
let cveUrl;
|
|
8148
7858
|
const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
8149
7859
|
for (const f of allFindings) {
|
|
8150
7860
|
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
@@ -8157,6 +7867,13 @@ ${rest}
|
|
|
8157
7867
|
if (!kbUrl && url) kbUrl = url;
|
|
8158
7868
|
continue;
|
|
8159
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
|
+
}
|
|
8160
7877
|
if (f.module === "security_hub_findings") {
|
|
8161
7878
|
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
8162
7879
|
if (controlMatch) {
|
|
@@ -8173,6 +7890,23 @@ ${rest}
|
|
|
8173
7890
|
continue;
|
|
8174
7891
|
}
|
|
8175
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
|
+
}
|
|
8176
7910
|
const existing = recMap.get(rem);
|
|
8177
7911
|
if (existing) {
|
|
8178
7912
|
existing.count++;
|
|
@@ -8189,8 +7923,14 @@ ${rest}
|
|
|
8189
7923
|
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
8190
7924
|
recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
|
|
8191
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
|
+
}
|
|
8192
7932
|
for (const [key, rec] of recMap) {
|
|
8193
|
-
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
7933
|
+
if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
|
|
8194
7934
|
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
8195
7935
|
rec.count = 1;
|
|
8196
7936
|
}
|
|
@@ -8257,7 +7997,7 @@ ${remaining.map(renderRec).join("\n")}
|
|
|
8257
7997
|
</div>
|
|
8258
7998
|
<div class="chart-box">
|
|
8259
7999
|
<div class="chart-title">${esc(t.findingsByModule)}</div>
|
|
8260
|
-
${barChart(
|
|
8000
|
+
${barChart(barChartModules, t.allModulesClean)}
|
|
8261
8001
|
</div>
|
|
8262
8002
|
</section>
|
|
8263
8003
|
|
|
@@ -8430,6 +8170,9 @@ ${itemsHtml}
|
|
|
8430
8170
|
const mlpsKbPatches = [];
|
|
8431
8171
|
let mlpsKbSeverity = "LOW";
|
|
8432
8172
|
let mlpsKbUrl;
|
|
8173
|
+
const mlpsCveList = [];
|
|
8174
|
+
let mlpsCveSeverity = "LOW";
|
|
8175
|
+
let mlpsCveUrl;
|
|
8433
8176
|
const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
8434
8177
|
for (const r of failedResults) {
|
|
8435
8178
|
for (const f of r.relatedFindings) {
|
|
@@ -8443,6 +8186,13 @@ ${itemsHtml}
|
|
|
8443
8186
|
if (!mlpsKbUrl && url) mlpsKbUrl = url;
|
|
8444
8187
|
continue;
|
|
8445
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
|
+
}
|
|
8446
8196
|
if (f.module === "security_hub_findings") {
|
|
8447
8197
|
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
8448
8198
|
if (controlMatch) {
|
|
@@ -8459,6 +8209,23 @@ ${itemsHtml}
|
|
|
8459
8209
|
continue;
|
|
8460
8210
|
}
|
|
8461
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
|
+
}
|
|
8462
8229
|
const existing = mlpsRecMap.get(rem);
|
|
8463
8230
|
if (existing) {
|
|
8464
8231
|
existing.count++;
|
|
@@ -8476,8 +8243,14 @@ ${itemsHtml}
|
|
|
8476
8243
|
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
8477
8244
|
mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
|
|
8478
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
|
+
}
|
|
8479
8252
|
for (const [key, rec] of mlpsRecMap) {
|
|
8480
|
-
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
8253
|
+
if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
|
|
8481
8254
|
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
8482
8255
|
rec.count = 1;
|
|
8483
8256
|
}
|
|
@@ -8773,7 +8546,6 @@ Detects which AWS security services are enabled and assesses overall security ma
|
|
|
8773
8546
|
- **GuardDuty not enabled** \u2014 Risk 7.5: Provides continuous threat detection.
|
|
8774
8547
|
- **Inspector not enabled** \u2014 Risk 6.0: Scans for software vulnerabilities.
|
|
8775
8548
|
- **AWS Config not enabled** \u2014 Risk 6.0: Tracks configuration changes.
|
|
8776
|
-
- **Macie not enabled** \u2014 Risk 5.0: Detects sensitive data in S3 (not available in China regions).
|
|
8777
8549
|
- CloudTrail detection is included for coverage metrics.
|
|
8778
8550
|
|
|
8779
8551
|
### Maturity Levels
|
|
@@ -8781,8 +8553,8 @@ Detects which AWS security services are enabled and assesses overall security ma
|
|
|
8781
8553
|
|------------------|-------|
|
|
8782
8554
|
| 0\u20131 | Basic |
|
|
8783
8555
|
| 2\u20133 | Intermediate |
|
|
8784
|
-
| 4
|
|
8785
|
-
|
|
|
8556
|
+
| 4 | Advanced |
|
|
8557
|
+
| 5 | Comprehensive |
|
|
8786
8558
|
|
|
8787
8559
|
## 2. Security Hub Findings (security_hub_findings)
|
|
8788
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.
|
|
@@ -8916,7 +8688,7 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
8916
8688
|
import { join as join2, dirname } from "path";
|
|
8917
8689
|
import { fileURLToPath } from "url";
|
|
8918
8690
|
var MODULE_DESCRIPTIONS = {
|
|
8919
|
-
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.",
|
|
8920
8692
|
secret_exposure: "Checks Lambda env vars and EC2 userData for exposed secrets (AWS keys, private keys, passwords).",
|
|
8921
8693
|
ssl_certificate: "Checks ACM certificates for expiry, failed status, and upcoming renewals.",
|
|
8922
8694
|
dns_dangling: "Checks Route53 CNAME records for dangling DNS (subdomain takeover risk).",
|
|
@@ -9351,14 +9123,12 @@ function createServer(defaultRegion) {
|
|
|
9351
9123
|
"Security Hub": "+300 security checks",
|
|
9352
9124
|
"GuardDuty": "Threat detection",
|
|
9353
9125
|
"Inspector": "Vulnerability scanning",
|
|
9354
|
-
"AWS Config": "Configuration tracking"
|
|
9355
|
-
"Macie": "Sensitive data detection"
|
|
9126
|
+
"AWS Config": "Configuration tracking"
|
|
9356
9127
|
};
|
|
9357
9128
|
const serviceFreeTrials = {
|
|
9358
9129
|
"Security Hub": true,
|
|
9359
9130
|
"GuardDuty": true,
|
|
9360
|
-
"Inspector": true
|
|
9361
|
-
"Macie": true
|
|
9131
|
+
"Inspector": true
|
|
9362
9132
|
};
|
|
9363
9133
|
const services = detection.services;
|
|
9364
9134
|
const coveragePercent = detection.coveragePercent;
|
|
@@ -9393,7 +9163,7 @@ function createServer(defaultRegion) {
|
|
|
9393
9163
|
lines.push("");
|
|
9394
9164
|
lines.push("### Recommendations (Priority Order)");
|
|
9395
9165
|
lines.push("");
|
|
9396
|
-
const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "
|
|
9166
|
+
const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "CloudTrail"];
|
|
9397
9167
|
const sorted = disabled.sort(
|
|
9398
9168
|
(a, b) => priorityOrder.indexOf(a.name) - priorityOrder.indexOf(b.name)
|
|
9399
9169
|
);
|
|
@@ -9416,7 +9186,7 @@ function createServer(defaultRegion) {
|
|
|
9416
9186
|
const nextMilestones = {
|
|
9417
9187
|
basic: { level: "Intermediate", target: 2, suggestions: ["Security Hub", "GuardDuty"] },
|
|
9418
9188
|
intermediate: { level: "Advanced", target: 4, suggestions: ["Inspector", "AWS Config"] },
|
|
9419
|
-
advanced: { level: "Comprehensive", target:
|
|
9189
|
+
advanced: { level: "Comprehensive", target: 5, suggestions: ["CloudTrail"] }
|
|
9420
9190
|
};
|
|
9421
9191
|
const next = nextMilestones[maturityLevel];
|
|
9422
9192
|
if (next) {
|