aws-security-mcp 0.5.3 → 0.6.1
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/dist/bin/aws-security-mcp.js +871 -788
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +871 -788
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
240
|
+
var VERSION = "0.6.1";
|
|
241
241
|
|
|
242
242
|
// src/utils/aws-client.ts
|
|
243
243
|
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
@@ -316,7 +316,9 @@ async function listOrgAccounts(region) {
|
|
|
316
316
|
var AGGREGATION_MODULES = /* @__PURE__ */ new Set([
|
|
317
317
|
"security_hub_findings",
|
|
318
318
|
"guardduty_findings",
|
|
319
|
-
"inspector_findings"
|
|
319
|
+
"inspector_findings",
|
|
320
|
+
"config_rules_findings",
|
|
321
|
+
"access_analyzer_findings"
|
|
320
322
|
]);
|
|
321
323
|
function buildSummary(modules) {
|
|
322
324
|
let critical = 0;
|
|
@@ -708,9 +710,15 @@ var ServiceDetectionScanner = class {
|
|
|
708
710
|
const insp = createClient(Inspector2Client, region, ctx.credentials);
|
|
709
711
|
const resp = await insp.send(new BatchGetAccountStatusCommand({ accountIds: [accountId] }));
|
|
710
712
|
const accounts = resp.accounts ?? [];
|
|
711
|
-
const active = accounts.some(
|
|
712
|
-
|
|
713
|
-
|
|
713
|
+
const active = accounts.some((a) => {
|
|
714
|
+
const s = a.state?.status;
|
|
715
|
+
if (s === "ENABLED" || s === "ENABLING") return true;
|
|
716
|
+
const rs = a.resourceState;
|
|
717
|
+
if (!rs) return false;
|
|
718
|
+
return ["ec2", "ecr", "lambda", "lambdaCode", "codeRepository"].some(
|
|
719
|
+
(k) => rs[k]?.status === "ENABLED"
|
|
720
|
+
);
|
|
721
|
+
});
|
|
714
722
|
if (active) {
|
|
715
723
|
services.push({
|
|
716
724
|
name: "Inspector",
|
|
@@ -2963,14 +2971,21 @@ var SecurityHubFindingsScanner = class {
|
|
|
2963
2971
|
const resourceType = f.Resources?.[0]?.Type ?? "AWS::Unknown";
|
|
2964
2972
|
const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:securityhub:${region}:${accountId}:finding/${f.Id ?? "unknown"}`;
|
|
2965
2973
|
const remediationSteps = [];
|
|
2966
|
-
|
|
2967
|
-
|
|
2974
|
+
const title = f.Title ?? "Security Hub Finding";
|
|
2975
|
+
if (/^KB\d+$/.test(title)) {
|
|
2976
|
+
remediationSteps.push(`Install Windows patch ${title} via WSUS or SSM Patch Manager`);
|
|
2977
|
+
remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${title}`);
|
|
2978
|
+
} else if (/^CVE-/.test(title)) {
|
|
2979
|
+
remediationSteps.push(`Fix vulnerability ${title}: update affected software to patched version`);
|
|
2980
|
+
} else {
|
|
2981
|
+
remediationSteps.push(title);
|
|
2968
2982
|
}
|
|
2969
2983
|
if (f.Remediation?.Recommendation?.Url) {
|
|
2970
|
-
remediationSteps.push(`
|
|
2984
|
+
remediationSteps.push(`Documentation: ${f.Remediation.Recommendation.Url}`);
|
|
2971
2985
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2986
|
+
const recText = f.Remediation?.Recommendation?.Text ?? "";
|
|
2987
|
+
if (recText && !["See References", "None Provided", ""].includes(recText.trim())) {
|
|
2988
|
+
remediationSteps.push(recText);
|
|
2974
2989
|
}
|
|
2975
2990
|
findings.push({
|
|
2976
2991
|
severity,
|
|
@@ -3031,125 +3046,37 @@ var SecurityHubFindingsScanner = class {
|
|
|
3031
3046
|
// src/scanners/guardduty-findings.ts
|
|
3032
3047
|
import {
|
|
3033
3048
|
GuardDutyClient as GuardDutyClient2,
|
|
3034
|
-
ListDetectorsCommand as ListDetectorsCommand2
|
|
3035
|
-
ListFindingsCommand,
|
|
3036
|
-
GetFindingsCommand as GetFindingsCommand2
|
|
3049
|
+
ListDetectorsCommand as ListDetectorsCommand2
|
|
3037
3050
|
} from "@aws-sdk/client-guardduty";
|
|
3038
|
-
function gdSeverityToScore(severity) {
|
|
3039
|
-
if (severity >= 7) return 8;
|
|
3040
|
-
if (severity >= 4) return 5.5;
|
|
3041
|
-
return 3;
|
|
3042
|
-
}
|
|
3043
3051
|
var GuardDutyFindingsScanner = class {
|
|
3044
3052
|
moduleName = "guardduty_findings";
|
|
3045
3053
|
async scan(ctx) {
|
|
3046
|
-
const { region
|
|
3054
|
+
const { region } = ctx;
|
|
3047
3055
|
const startMs = Date.now();
|
|
3048
|
-
const findings = [];
|
|
3049
3056
|
const warnings = [];
|
|
3050
|
-
let resourcesScanned = 0;
|
|
3051
3057
|
try {
|
|
3052
3058
|
const client = createClient(GuardDutyClient2, region, ctx.credentials);
|
|
3053
|
-
const
|
|
3054
|
-
const detectorIds =
|
|
3059
|
+
const resp = await client.send(new ListDetectorsCommand2({}));
|
|
3060
|
+
const detectorIds = resp.DetectorIds ?? [];
|
|
3055
3061
|
if (detectorIds.length === 0) {
|
|
3056
3062
|
warnings.push("GuardDuty is not enabled in this region (no detectors found).");
|
|
3057
|
-
return {
|
|
3058
|
-
module: this.moduleName,
|
|
3059
|
-
status: "success",
|
|
3060
|
-
warnings,
|
|
3061
|
-
resourcesScanned: 0,
|
|
3062
|
-
findingsCount: 0,
|
|
3063
|
-
scanTimeMs: Date.now() - startMs,
|
|
3064
|
-
findings: []
|
|
3065
|
-
};
|
|
3066
|
-
}
|
|
3067
|
-
const detectorId = detectorIds[0];
|
|
3068
|
-
let nextToken;
|
|
3069
|
-
const findingIds = [];
|
|
3070
|
-
do {
|
|
3071
|
-
const listResp = await client.send(
|
|
3072
|
-
new ListFindingsCommand({
|
|
3073
|
-
DetectorId: detectorId,
|
|
3074
|
-
FindingCriteria: {
|
|
3075
|
-
Criterion: {
|
|
3076
|
-
"service.archived": {
|
|
3077
|
-
Eq: ["false"]
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
},
|
|
3081
|
-
MaxResults: 50,
|
|
3082
|
-
NextToken: nextToken
|
|
3083
|
-
})
|
|
3084
|
-
);
|
|
3085
|
-
findingIds.push(...listResp.FindingIds ?? []);
|
|
3086
|
-
nextToken = listResp.NextToken;
|
|
3087
|
-
} while (nextToken);
|
|
3088
|
-
resourcesScanned = findingIds.length;
|
|
3089
|
-
if (findingIds.length === 0) {
|
|
3090
|
-
return {
|
|
3091
|
-
module: this.moduleName,
|
|
3092
|
-
status: "success",
|
|
3093
|
-
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3094
|
-
resourcesScanned: 0,
|
|
3095
|
-
findingsCount: 0,
|
|
3096
|
-
scanTimeMs: Date.now() - startMs,
|
|
3097
|
-
findings: []
|
|
3098
|
-
};
|
|
3099
|
-
}
|
|
3100
|
-
for (let i = 0; i < findingIds.length; i += 50) {
|
|
3101
|
-
const batch = findingIds.slice(i, i + 50);
|
|
3102
|
-
const detailsResp = await client.send(
|
|
3103
|
-
new GetFindingsCommand2({
|
|
3104
|
-
DetectorId: detectorId,
|
|
3105
|
-
FindingIds: batch
|
|
3106
|
-
})
|
|
3107
|
-
);
|
|
3108
|
-
for (const gdf of detailsResp.Findings ?? []) {
|
|
3109
|
-
const gdSeverity = gdf.Severity ?? 0;
|
|
3110
|
-
const score = gdSeverityToScore(gdSeverity);
|
|
3111
|
-
const severity = severityFromScore(score);
|
|
3112
|
-
const resourceType = gdf.Resource?.ResourceType ?? "AWS::Unknown";
|
|
3113
|
-
const resourceId = gdf.Resource?.InstanceDetails?.InstanceId ?? gdf.Resource?.AccessKeyDetails?.AccessKeyId ?? gdf.Arn ?? "unknown";
|
|
3114
|
-
const resourceArn = gdf.Arn ?? `arn:${partition}:guardduty:${region}:${accountId}:detector/${detectorId}/finding/${gdf.Id ?? "unknown"}`;
|
|
3115
|
-
findings.push({
|
|
3116
|
-
severity,
|
|
3117
|
-
title: `[GuardDuty] ${gdf.Title ?? gdf.Type ?? "Finding"}`,
|
|
3118
|
-
resourceType,
|
|
3119
|
-
resourceId,
|
|
3120
|
-
resourceArn,
|
|
3121
|
-
region: gdf.Region ?? region,
|
|
3122
|
-
description: gdf.Description ?? gdf.Title ?? "No description",
|
|
3123
|
-
impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
|
|
3124
|
-
riskScore: score,
|
|
3125
|
-
remediationSteps: [
|
|
3126
|
-
"Review the finding in the Amazon GuardDuty console.",
|
|
3127
|
-
`Finding type: ${gdf.Type ?? "unknown"}`,
|
|
3128
|
-
"Follow the recommended remediation in the GuardDuty documentation."
|
|
3129
|
-
],
|
|
3130
|
-
priority: priorityFromSeverity(severity),
|
|
3131
|
-
module: this.moduleName,
|
|
3132
|
-
accountId: gdf.AccountId ?? accountId
|
|
3133
|
-
});
|
|
3134
|
-
}
|
|
3135
3063
|
}
|
|
3136
3064
|
return {
|
|
3137
3065
|
module: this.moduleName,
|
|
3138
3066
|
status: "success",
|
|
3139
3067
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3140
|
-
resourcesScanned,
|
|
3141
|
-
findingsCount:
|
|
3068
|
+
resourcesScanned: 0,
|
|
3069
|
+
findingsCount: 0,
|
|
3142
3070
|
scanTimeMs: Date.now() - startMs,
|
|
3143
|
-
findings
|
|
3071
|
+
findings: []
|
|
3144
3072
|
};
|
|
3145
3073
|
} catch (err) {
|
|
3146
3074
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3147
3075
|
return {
|
|
3148
3076
|
module: this.moduleName,
|
|
3149
3077
|
status: "error",
|
|
3150
|
-
error: `GuardDuty
|
|
3151
|
-
|
|
3152
|
-
resourcesScanned,
|
|
3078
|
+
error: `GuardDuty detection check failed: ${msg}`,
|
|
3079
|
+
resourcesScanned: 0,
|
|
3153
3080
|
findingsCount: 0,
|
|
3154
3081
|
scanTimeMs: Date.now() - startMs,
|
|
3155
3082
|
findings: []
|
|
@@ -3161,138 +3088,59 @@ var GuardDutyFindingsScanner = class {
|
|
|
3161
3088
|
// src/scanners/inspector-findings.ts
|
|
3162
3089
|
import {
|
|
3163
3090
|
Inspector2Client as Inspector2Client2,
|
|
3164
|
-
|
|
3091
|
+
BatchGetAccountStatusCommand as BatchGetAccountStatusCommand2
|
|
3165
3092
|
} from "@aws-sdk/client-inspector2";
|
|
3166
|
-
function inspectorSeverityToScore(label) {
|
|
3167
|
-
switch (label) {
|
|
3168
|
-
case "CRITICAL":
|
|
3169
|
-
return 9.5;
|
|
3170
|
-
case "HIGH":
|
|
3171
|
-
return 8;
|
|
3172
|
-
case "MEDIUM":
|
|
3173
|
-
return 5.5;
|
|
3174
|
-
case "LOW":
|
|
3175
|
-
return 3;
|
|
3176
|
-
case "INFORMATIONAL":
|
|
3177
|
-
return null;
|
|
3178
|
-
case "UNTRIAGED":
|
|
3179
|
-
return 5.5;
|
|
3180
|
-
default:
|
|
3181
|
-
return null;
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
3093
|
var InspectorFindingsScanner = class {
|
|
3185
3094
|
moduleName = "inspector_findings";
|
|
3186
3095
|
async scan(ctx) {
|
|
3187
|
-
const { region
|
|
3096
|
+
const { region } = ctx;
|
|
3188
3097
|
const startMs = Date.now();
|
|
3189
|
-
const findings = [];
|
|
3190
3098
|
const warnings = [];
|
|
3191
|
-
let resourcesScanned = 0;
|
|
3192
3099
|
try {
|
|
3193
3100
|
const client = createClient(Inspector2Client2, region, ctx.credentials);
|
|
3194
|
-
|
|
3195
|
-
const
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
const
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
}
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
const severity = severityFromScore(score);
|
|
3213
|
-
const cveId = f.packageVulnerabilityDetails?.vulnerabilityId;
|
|
3214
|
-
const titleBase = f.title ?? "Inspector Finding";
|
|
3215
|
-
const title = cveId ? `[${cveId}] ${titleBase}` : titleBase;
|
|
3216
|
-
const resourceId = f.resources?.[0]?.id ?? "unknown";
|
|
3217
|
-
const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
|
|
3218
|
-
const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
|
|
3219
|
-
const remediationSteps = [];
|
|
3220
|
-
if (f.remediation?.recommendation?.text) {
|
|
3221
|
-
remediationSteps.push(f.remediation.recommendation.text);
|
|
3222
|
-
}
|
|
3223
|
-
if (f.remediation?.recommendation?.Url) {
|
|
3224
|
-
remediationSteps.push(`Reference: ${f.remediation.recommendation.Url}`);
|
|
3225
|
-
}
|
|
3226
|
-
if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
|
|
3227
|
-
remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
|
|
3228
|
-
}
|
|
3229
|
-
if (remediationSteps.length === 0) {
|
|
3230
|
-
remediationSteps.push("Review the finding in the Amazon Inspector console and apply the recommended patch or update.");
|
|
3231
|
-
}
|
|
3232
|
-
const description = f.description ?? titleBase;
|
|
3233
|
-
const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
|
|
3234
|
-
findings.push({
|
|
3235
|
-
severity,
|
|
3236
|
-
title,
|
|
3237
|
-
resourceType,
|
|
3238
|
-
resourceId,
|
|
3239
|
-
resourceArn,
|
|
3240
|
-
region,
|
|
3241
|
-
description,
|
|
3242
|
-
impact,
|
|
3243
|
-
riskScore: score,
|
|
3244
|
-
remediationSteps,
|
|
3245
|
-
priority: priorityFromSeverity(severity),
|
|
3246
|
-
module: this.moduleName,
|
|
3247
|
-
accountId: f.awsAccountId ?? accountId
|
|
3248
|
-
});
|
|
3101
|
+
const resp = await client.send(new BatchGetAccountStatusCommand2({ accountIds: [] }));
|
|
3102
|
+
const account = resp.accounts?.[0];
|
|
3103
|
+
if (!account || account.state?.status !== "ENABLED") {
|
|
3104
|
+
warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
|
|
3105
|
+
} else {
|
|
3106
|
+
const rs = account.resourceState;
|
|
3107
|
+
const types = [
|
|
3108
|
+
{ name: "EC2", status: rs?.ec2?.status },
|
|
3109
|
+
{ name: "Lambda", status: rs?.lambda?.status },
|
|
3110
|
+
{ name: "ECR", status: rs?.ecr?.status },
|
|
3111
|
+
{ name: "Lambda Code", status: rs?.lambdaCode?.status },
|
|
3112
|
+
{ name: "Code Repository", status: rs?.codeRepository?.status }
|
|
3113
|
+
];
|
|
3114
|
+
const disabled = types.filter((t) => t.status && t.status !== "ENABLED");
|
|
3115
|
+
if (disabled.length > 0) {
|
|
3116
|
+
warnings.push(
|
|
3117
|
+
`Inspector scan types not enabled: ${disabled.map((t) => t.name).join(", ")}. Enable them for full vulnerability coverage.`
|
|
3118
|
+
);
|
|
3249
3119
|
}
|
|
3250
|
-
|
|
3251
|
-
} while (nextToken);
|
|
3120
|
+
}
|
|
3252
3121
|
return {
|
|
3253
3122
|
module: this.moduleName,
|
|
3254
3123
|
status: "success",
|
|
3255
3124
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3256
|
-
resourcesScanned,
|
|
3257
|
-
findingsCount:
|
|
3125
|
+
resourcesScanned: 0,
|
|
3126
|
+
findingsCount: 0,
|
|
3258
3127
|
scanTimeMs: Date.now() - startMs,
|
|
3259
|
-
findings
|
|
3128
|
+
findings: []
|
|
3260
3129
|
};
|
|
3261
3130
|
} catch (err) {
|
|
3262
3131
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3263
3132
|
const errName = err instanceof Error ? err.name : "";
|
|
3264
3133
|
const isAccessDenied2 = errName === "AccessDeniedException" || msg.includes("AccessDeniedException");
|
|
3265
|
-
const isNotEnabled2 = msg.includes("not enabled") || msg.includes("not subscribed");
|
|
3266
3134
|
if (isAccessDenied2) {
|
|
3267
|
-
warnings.push("Insufficient permissions to access Inspector. Grant inspector2:
|
|
3268
|
-
|
|
3269
|
-
module: this.moduleName,
|
|
3270
|
-
status: "success",
|
|
3271
|
-
warnings,
|
|
3272
|
-
resourcesScanned: 0,
|
|
3273
|
-
findingsCount: 0,
|
|
3274
|
-
scanTimeMs: Date.now() - startMs,
|
|
3275
|
-
findings: []
|
|
3276
|
-
};
|
|
3277
|
-
}
|
|
3278
|
-
if (isNotEnabled2) {
|
|
3135
|
+
warnings.push("Insufficient permissions to access Inspector. Grant inspector2:BatchGetAccountStatus to check enablement.");
|
|
3136
|
+
} else {
|
|
3279
3137
|
warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
|
|
3280
|
-
return {
|
|
3281
|
-
module: this.moduleName,
|
|
3282
|
-
status: "success",
|
|
3283
|
-
warnings,
|
|
3284
|
-
resourcesScanned: 0,
|
|
3285
|
-
findingsCount: 0,
|
|
3286
|
-
scanTimeMs: Date.now() - startMs,
|
|
3287
|
-
findings: []
|
|
3288
|
-
};
|
|
3289
3138
|
}
|
|
3290
3139
|
return {
|
|
3291
3140
|
module: this.moduleName,
|
|
3292
|
-
status: "
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
resourcesScanned,
|
|
3141
|
+
status: "success",
|
|
3142
|
+
warnings,
|
|
3143
|
+
resourcesScanned: 0,
|
|
3296
3144
|
findingsCount: 0,
|
|
3297
3145
|
scanTimeMs: Date.now() - startMs,
|
|
3298
3146
|
findings: []
|
|
@@ -3465,128 +3313,29 @@ var TrustedAdvisorFindingsScanner = class {
|
|
|
3465
3313
|
// src/scanners/config-rules-findings.ts
|
|
3466
3314
|
import {
|
|
3467
3315
|
ConfigServiceClient as ConfigServiceClient2,
|
|
3468
|
-
|
|
3469
|
-
GetComplianceDetailsByConfigRuleCommand
|
|
3316
|
+
DescribeConfigurationRecordersCommand as DescribeConfigurationRecordersCommand2
|
|
3470
3317
|
} from "@aws-sdk/client-config-service";
|
|
3471
|
-
var SECURITY_RULE_PATTERNS = [
|
|
3472
|
-
"securitygroup",
|
|
3473
|
-
"security-group",
|
|
3474
|
-
"encryption",
|
|
3475
|
-
"encrypted",
|
|
3476
|
-
"public",
|
|
3477
|
-
"unrestricted",
|
|
3478
|
-
"mfa",
|
|
3479
|
-
"password",
|
|
3480
|
-
"access-key",
|
|
3481
|
-
"root",
|
|
3482
|
-
"admin",
|
|
3483
|
-
"logging",
|
|
3484
|
-
"cloudtrail",
|
|
3485
|
-
"iam",
|
|
3486
|
-
"kms",
|
|
3487
|
-
"ssl",
|
|
3488
|
-
"tls",
|
|
3489
|
-
"vpc-flow",
|
|
3490
|
-
"guardduty",
|
|
3491
|
-
"securityhub"
|
|
3492
|
-
];
|
|
3493
|
-
function ruleIsSecurityRelated(ruleName) {
|
|
3494
|
-
const lower = ruleName.toLowerCase();
|
|
3495
|
-
return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
|
|
3496
|
-
}
|
|
3497
3318
|
var ConfigRulesFindingsScanner = class {
|
|
3498
3319
|
moduleName = "config_rules_findings";
|
|
3499
3320
|
async scan(ctx) {
|
|
3500
|
-
const { region
|
|
3321
|
+
const { region } = ctx;
|
|
3501
3322
|
const startMs = Date.now();
|
|
3502
|
-
const findings = [];
|
|
3503
3323
|
const warnings = [];
|
|
3504
|
-
let resourcesScanned = 0;
|
|
3505
3324
|
try {
|
|
3506
3325
|
const client = createClient(ConfigServiceClient2, region, ctx.credentials);
|
|
3507
|
-
|
|
3508
|
-
const
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
|
|
3512
|
-
);
|
|
3513
|
-
for (const rule of resp.ComplianceByConfigRules ?? []) {
|
|
3514
|
-
resourcesScanned++;
|
|
3515
|
-
if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
|
|
3516
|
-
nonCompliantRules.push(rule);
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
nextToken = resp.NextToken;
|
|
3520
|
-
} while (nextToken);
|
|
3521
|
-
if (resourcesScanned === 0) {
|
|
3522
|
-
warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
|
|
3523
|
-
return {
|
|
3524
|
-
module: this.moduleName,
|
|
3525
|
-
status: "success",
|
|
3526
|
-
warnings,
|
|
3527
|
-
resourcesScanned: 0,
|
|
3528
|
-
findingsCount: 0,
|
|
3529
|
-
scanTimeMs: Date.now() - startMs,
|
|
3530
|
-
findings: []
|
|
3531
|
-
};
|
|
3532
|
-
}
|
|
3533
|
-
for (const rule of nonCompliantRules) {
|
|
3534
|
-
const ruleName = rule.ConfigRuleName ?? "unknown";
|
|
3535
|
-
try {
|
|
3536
|
-
let detailToken;
|
|
3537
|
-
do {
|
|
3538
|
-
const detailResp = await client.send(
|
|
3539
|
-
new GetComplianceDetailsByConfigRuleCommand({
|
|
3540
|
-
ConfigRuleName: ruleName,
|
|
3541
|
-
ComplianceTypes: ["NON_COMPLIANT"],
|
|
3542
|
-
NextToken: detailToken
|
|
3543
|
-
})
|
|
3544
|
-
);
|
|
3545
|
-
for (const evalResult of detailResp.EvaluationResults ?? []) {
|
|
3546
|
-
const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
|
|
3547
|
-
const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
|
|
3548
|
-
const resourceId = qualifier?.ResourceId ?? "unknown";
|
|
3549
|
-
const annotation = evalResult.Annotation;
|
|
3550
|
-
const isSecurityRule = ruleIsSecurityRelated(ruleName);
|
|
3551
|
-
const riskScore = isSecurityRule ? 7.5 : 5.5;
|
|
3552
|
-
const severity = severityFromScore(riskScore);
|
|
3553
|
-
const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
|
|
3554
|
-
if (annotation) descParts.push(`Annotation: ${annotation}`);
|
|
3555
|
-
findings.push({
|
|
3556
|
-
severity,
|
|
3557
|
-
title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
|
|
3558
|
-
resourceType,
|
|
3559
|
-
resourceId,
|
|
3560
|
-
resourceArn: resourceId,
|
|
3561
|
-
region,
|
|
3562
|
-
description: descParts.join(". "),
|
|
3563
|
-
impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
|
|
3564
|
-
riskScore,
|
|
3565
|
-
remediationSteps: [
|
|
3566
|
-
`Review the Config Rule "${ruleName}" in the AWS Config console.`,
|
|
3567
|
-
`Check resource ${resourceId} for compliance violations.`,
|
|
3568
|
-
"Follow the rule's remediation guidance to bring the resource into compliance."
|
|
3569
|
-
],
|
|
3570
|
-
priority: priorityFromSeverity(severity),
|
|
3571
|
-
module: this.moduleName,
|
|
3572
|
-
accountId
|
|
3573
|
-
});
|
|
3574
|
-
}
|
|
3575
|
-
detailToken = detailResp.NextToken;
|
|
3576
|
-
} while (detailToken);
|
|
3577
|
-
} catch (detailErr) {
|
|
3578
|
-
const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
|
|
3579
|
-
warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
|
|
3580
|
-
}
|
|
3326
|
+
const resp = await client.send(new DescribeConfigurationRecordersCommand2({}));
|
|
3327
|
+
const recorders = resp.ConfigurationRecorders ?? [];
|
|
3328
|
+
if (recorders.length === 0) {
|
|
3329
|
+
warnings.push("AWS Config is not enabled in this region.");
|
|
3581
3330
|
}
|
|
3582
3331
|
return {
|
|
3583
3332
|
module: this.moduleName,
|
|
3584
3333
|
status: "success",
|
|
3585
3334
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3586
|
-
resourcesScanned,
|
|
3587
|
-
findingsCount:
|
|
3335
|
+
resourcesScanned: 0,
|
|
3336
|
+
findingsCount: 0,
|
|
3588
3337
|
scanTimeMs: Date.now() - startMs,
|
|
3589
|
-
findings
|
|
3338
|
+
findings: []
|
|
3590
3339
|
};
|
|
3591
3340
|
} catch (err) {
|
|
3592
3341
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3605,9 +3354,8 @@ var ConfigRulesFindingsScanner = class {
|
|
|
3605
3354
|
return {
|
|
3606
3355
|
module: this.moduleName,
|
|
3607
3356
|
status: "error",
|
|
3608
|
-
error: `Config Rules
|
|
3609
|
-
|
|
3610
|
-
resourcesScanned,
|
|
3357
|
+
error: `Config Rules detection check failed: ${msg}`,
|
|
3358
|
+
resourcesScanned: 0,
|
|
3611
3359
|
findingsCount: 0,
|
|
3612
3360
|
scanTimeMs: Date.now() - startMs,
|
|
3613
3361
|
findings: []
|
|
@@ -3619,146 +3367,50 @@ var ConfigRulesFindingsScanner = class {
|
|
|
3619
3367
|
// src/scanners/access-analyzer-findings.ts
|
|
3620
3368
|
import {
|
|
3621
3369
|
AccessAnalyzerClient,
|
|
3622
|
-
ListAnalyzersCommand
|
|
3623
|
-
ListFindingsV2Command
|
|
3370
|
+
ListAnalyzersCommand
|
|
3624
3371
|
} from "@aws-sdk/client-accessanalyzer";
|
|
3625
|
-
function findingTypeToScore(findingType) {
|
|
3626
|
-
const ft = findingType;
|
|
3627
|
-
switch (ft) {
|
|
3628
|
-
case "ExternalAccess":
|
|
3629
|
-
return 8;
|
|
3630
|
-
case "UnusedIAMRole":
|
|
3631
|
-
case "UnusedIAMUserAccessKey":
|
|
3632
|
-
case "UnusedIAMUserPassword":
|
|
3633
|
-
return 5.5;
|
|
3634
|
-
case "UnusedPermission":
|
|
3635
|
-
return 3;
|
|
3636
|
-
default:
|
|
3637
|
-
return 5.5;
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
|
|
3641
|
-
"UnusedIAMRole",
|
|
3642
|
-
"UnusedIAMUserAccessKey",
|
|
3643
|
-
"UnusedIAMUserPassword",
|
|
3644
|
-
"UnusedPermission"
|
|
3645
|
-
]);
|
|
3646
|
-
function isSecurityRelevant(findingType) {
|
|
3647
|
-
const ft = findingType;
|
|
3648
|
-
return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
|
|
3649
|
-
}
|
|
3650
|
-
function isExternalAccess(findingType) {
|
|
3651
|
-
return findingType === "ExternalAccess";
|
|
3652
|
-
}
|
|
3653
3372
|
var AccessAnalyzerFindingsScanner = class {
|
|
3654
3373
|
moduleName = "access_analyzer_findings";
|
|
3655
3374
|
async scan(ctx) {
|
|
3656
|
-
const { region
|
|
3375
|
+
const { region } = ctx;
|
|
3657
3376
|
const startMs = Date.now();
|
|
3658
|
-
const findings = [];
|
|
3659
3377
|
const warnings = [];
|
|
3660
|
-
let resourcesScanned = 0;
|
|
3661
3378
|
try {
|
|
3662
3379
|
const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
|
|
3663
3380
|
let analyzerToken;
|
|
3664
|
-
|
|
3381
|
+
let hasActiveAnalyzer = false;
|
|
3665
3382
|
do {
|
|
3666
3383
|
const resp = await client.send(
|
|
3667
3384
|
new ListAnalyzersCommand({ nextToken: analyzerToken })
|
|
3668
3385
|
);
|
|
3669
3386
|
for (const analyzer of resp.analyzers ?? []) {
|
|
3670
3387
|
if (analyzer.status === "ACTIVE") {
|
|
3671
|
-
|
|
3388
|
+
hasActiveAnalyzer = true;
|
|
3389
|
+
break;
|
|
3672
3390
|
}
|
|
3673
3391
|
}
|
|
3392
|
+
if (hasActiveAnalyzer) break;
|
|
3674
3393
|
analyzerToken = resp.nextToken;
|
|
3675
3394
|
} while (analyzerToken);
|
|
3676
|
-
if (
|
|
3395
|
+
if (!hasActiveAnalyzer) {
|
|
3677
3396
|
warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
|
|
3678
|
-
return {
|
|
3679
|
-
module: this.moduleName,
|
|
3680
|
-
status: "success",
|
|
3681
|
-
warnings,
|
|
3682
|
-
resourcesScanned: 0,
|
|
3683
|
-
findingsCount: 0,
|
|
3684
|
-
scanTimeMs: Date.now() - startMs,
|
|
3685
|
-
findings: []
|
|
3686
|
-
};
|
|
3687
|
-
}
|
|
3688
|
-
for (const analyzer of analyzers) {
|
|
3689
|
-
const analyzerArn = analyzer.arn ?? "unknown";
|
|
3690
|
-
let findingToken;
|
|
3691
|
-
do {
|
|
3692
|
-
const listResp = await client.send(
|
|
3693
|
-
new ListFindingsV2Command({
|
|
3694
|
-
analyzerArn,
|
|
3695
|
-
filter: {
|
|
3696
|
-
status: { eq: ["ACTIVE"] }
|
|
3697
|
-
},
|
|
3698
|
-
nextToken: findingToken
|
|
3699
|
-
})
|
|
3700
|
-
);
|
|
3701
|
-
for (const aaf of listResp.findings ?? []) {
|
|
3702
|
-
if (!isSecurityRelevant(aaf.findingType)) {
|
|
3703
|
-
continue;
|
|
3704
|
-
}
|
|
3705
|
-
resourcesScanned++;
|
|
3706
|
-
const score = findingTypeToScore(aaf.findingType);
|
|
3707
|
-
const severity = severityFromScore(score);
|
|
3708
|
-
const resourceArn = aaf.resource ?? "unknown";
|
|
3709
|
-
const resourceType = aaf.resourceType ?? "AWS::Unknown";
|
|
3710
|
-
const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
|
|
3711
|
-
const external = isExternalAccess(aaf.findingType);
|
|
3712
|
-
const descParts = [`Resource Type: ${resourceType}`];
|
|
3713
|
-
if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
|
|
3714
|
-
if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
|
|
3715
|
-
const title = buildFindingTitle(aaf);
|
|
3716
|
-
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"}`;
|
|
3717
|
-
const remediationSteps = external ? [
|
|
3718
|
-
"Review the finding in the IAM Access Analyzer console.",
|
|
3719
|
-
`Check resource ${resourceId} for unintended external access.`,
|
|
3720
|
-
"Remove or restrict the resource policy to eliminate external access."
|
|
3721
|
-
] : [
|
|
3722
|
-
"Review the finding in the IAM Access Analyzer console.",
|
|
3723
|
-
`Check resource ${resourceId} for unused access permissions.`,
|
|
3724
|
-
"Remove unused permissions, roles, or credentials to follow least-privilege."
|
|
3725
|
-
];
|
|
3726
|
-
findings.push({
|
|
3727
|
-
severity,
|
|
3728
|
-
title,
|
|
3729
|
-
resourceType: mapResourceType(resourceType),
|
|
3730
|
-
resourceId,
|
|
3731
|
-
resourceArn,
|
|
3732
|
-
region,
|
|
3733
|
-
description: descParts.join(". "),
|
|
3734
|
-
impact,
|
|
3735
|
-
riskScore: score,
|
|
3736
|
-
remediationSteps,
|
|
3737
|
-
priority: priorityFromSeverity(severity),
|
|
3738
|
-
module: this.moduleName,
|
|
3739
|
-
accountId: aaf.resourceOwnerAccount ?? accountId
|
|
3740
|
-
});
|
|
3741
|
-
}
|
|
3742
|
-
findingToken = listResp.nextToken;
|
|
3743
|
-
} while (findingToken);
|
|
3744
3397
|
}
|
|
3745
3398
|
return {
|
|
3746
3399
|
module: this.moduleName,
|
|
3747
3400
|
status: "success",
|
|
3748
3401
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3749
|
-
resourcesScanned,
|
|
3750
|
-
findingsCount:
|
|
3402
|
+
resourcesScanned: 0,
|
|
3403
|
+
findingsCount: 0,
|
|
3751
3404
|
scanTimeMs: Date.now() - startMs,
|
|
3752
|
-
findings
|
|
3405
|
+
findings: []
|
|
3753
3406
|
};
|
|
3754
3407
|
} catch (err) {
|
|
3755
3408
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3756
3409
|
return {
|
|
3757
3410
|
module: this.moduleName,
|
|
3758
3411
|
status: "error",
|
|
3759
|
-
error: `Access Analyzer
|
|
3760
|
-
|
|
3761
|
-
resourcesScanned,
|
|
3412
|
+
error: `Access Analyzer detection check failed: ${msg}`,
|
|
3413
|
+
resourcesScanned: 0,
|
|
3762
3414
|
findingsCount: 0,
|
|
3763
3415
|
scanTimeMs: Date.now() - startMs,
|
|
3764
3416
|
findings: []
|
|
@@ -3766,29 +3418,6 @@ var AccessAnalyzerFindingsScanner = class {
|
|
|
3766
3418
|
}
|
|
3767
3419
|
}
|
|
3768
3420
|
};
|
|
3769
|
-
function buildFindingTitle(finding) {
|
|
3770
|
-
const resourceType = finding.resourceType ?? "Resource";
|
|
3771
|
-
const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
|
|
3772
|
-
const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
|
|
3773
|
-
return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
|
|
3774
|
-
}
|
|
3775
|
-
function mapResourceType(aaType) {
|
|
3776
|
-
const mapping = {
|
|
3777
|
-
"AWS::S3::Bucket": "AWS::S3::Bucket",
|
|
3778
|
-
"AWS::IAM::Role": "AWS::IAM::Role",
|
|
3779
|
-
"AWS::SQS::Queue": "AWS::SQS::Queue",
|
|
3780
|
-
"AWS::Lambda::Function": "AWS::Lambda::Function",
|
|
3781
|
-
"AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
|
|
3782
|
-
"AWS::KMS::Key": "AWS::KMS::Key",
|
|
3783
|
-
"AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
|
|
3784
|
-
"AWS::SNS::Topic": "AWS::SNS::Topic",
|
|
3785
|
-
"AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
|
|
3786
|
-
"AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
|
|
3787
|
-
"AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
|
|
3788
|
-
"AWS::ECR::Repository": "AWS::ECR::Repository"
|
|
3789
|
-
};
|
|
3790
|
-
return mapping[aaType] ?? aaType;
|
|
3791
|
-
}
|
|
3792
3421
|
|
|
3793
3422
|
// src/scanners/patch-compliance-findings.ts
|
|
3794
3423
|
import {
|
|
@@ -4196,6 +3825,479 @@ var WafCoverageScanner = class {
|
|
|
4196
3825
|
}
|
|
4197
3826
|
};
|
|
4198
3827
|
|
|
3828
|
+
// src/i18n/zh.ts
|
|
3829
|
+
var zhI18n = {
|
|
3830
|
+
// HTML Security Report
|
|
3831
|
+
securityReportTitle: "AWS \u5B89\u5168\u626B\u63CF\u62A5\u544A",
|
|
3832
|
+
securityScore: "\u5B89\u5168\u8BC4\u5206",
|
|
3833
|
+
critical: "\u4E25\u91CD",
|
|
3834
|
+
high: "\u9AD8",
|
|
3835
|
+
medium: "\u4E2D",
|
|
3836
|
+
low: "\u4F4E",
|
|
3837
|
+
scanStatistics: "\u626B\u63CF\u7EDF\u8BA1",
|
|
3838
|
+
module: "\u6A21\u5757",
|
|
3839
|
+
resources: "\u8D44\u6E90",
|
|
3840
|
+
findings: "\u53D1\u73B0",
|
|
3841
|
+
status: "\u72B6\u6001",
|
|
3842
|
+
allFindings: "\u6240\u6709\u53D1\u73B0",
|
|
3843
|
+
recommendations: "\u5EFA\u8BAE",
|
|
3844
|
+
unique: "\u53BB\u91CD",
|
|
3845
|
+
showMore: "\u663E\u793A\u66F4\u591A",
|
|
3846
|
+
noIssuesFound: "\u672A\u53D1\u73B0\u5B89\u5168\u95EE\u9898\u3002",
|
|
3847
|
+
allModulesClean: "\u6240\u6709\u6A21\u5757\u6B63\u5E38",
|
|
3848
|
+
generatedBy: "\u7531 AWS Security MCP Server \u751F\u6210",
|
|
3849
|
+
informationalOnly: "\u672C\u62A5\u544A\u4EC5\u4F9B\u53C2\u8003\u3002",
|
|
3850
|
+
// MLPS Report
|
|
3851
|
+
mlpsTitle: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A",
|
|
3852
|
+
mlpsDisclaimer: "\u672C\u62A5\u544A\u4E3A\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u53C2\u8003\uFF0C\u63D0\u4F9B\u4E91\u5E73\u53F0\u914D\u7F6E\u68C0\u67E5\u6570\u636E\u4E0E\u5EFA\u8BAE\u3002\u5408\u89C4\u5224\u5B9A\uFF08\u7B26\u5408/\u90E8\u5206\u7B26\u5408/\u4E0D\u7B26\u5408\uFF09\u9700\u7531\u6301\u8BC1\u6D4B\u8BC4\u673A\u6784\u6839\u636E\u5B9E\u9645\u60C5\u51B5\u786E\u8BA4\u3002\uFF08GB/T 22239-2019 \u5B8C\u6574\u68C0\u67E5\u6E05\u5355 184 \u9879\uFF09",
|
|
3853
|
+
checkedItems: "\u5DF2\u68C0\u67E5\u9879",
|
|
3854
|
+
noIssues: "\u672A\u53D1\u73B0\u95EE\u9898",
|
|
3855
|
+
issuesFound: "\u53D1\u73B0\u95EE\u9898",
|
|
3856
|
+
notChecked: "\u672A\u68C0\u67E5",
|
|
3857
|
+
cloudProvider: "\u4E91\u5E73\u53F0\u8D1F\u8D23",
|
|
3858
|
+
manualReview: "\u9700\u4EBA\u5DE5\u8BC4\u4F30",
|
|
3859
|
+
notApplicable: "\u4E0D\u9002\u7528",
|
|
3860
|
+
checkResult: "\u68C0\u67E5\u7ED3\u679C",
|
|
3861
|
+
noRelatedIssues: "\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898",
|
|
3862
|
+
issuesFoundCount: (n) => `\u68C0\u67E5\u7ED3\u679C\uFF1A\u53D1\u73B0 ${n} \u4E2A\u76F8\u5173\u95EE\u9898`,
|
|
3863
|
+
remediation: "\u5EFA\u8BAE",
|
|
3864
|
+
remediationItems: (n) => `\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${n} \u9879\u53BB\u91CD\uFF09`,
|
|
3865
|
+
showRemaining: (n) => `\u663E\u793A\u5176\u4F59 ${n} \u9879`,
|
|
3866
|
+
// HW Defense Checklist
|
|
3867
|
+
hwChecklistTitle: "\u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09",
|
|
3868
|
+
hwChecklistSubtitle: "\u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A",
|
|
3869
|
+
hwEmergencyIsolation: `\u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
|
|
3870
|
+
\u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
|
|
3871
|
+
\u25A1 \u5236\u5B9A\u5B9E\u4F8B\u9694\u79BB SOP\uFF1A\u544A\u8B66 \u2192 \u6392\u67E5 \u2192 \u5C01\u9501\u653B\u51FBIP \u2192 \u7F51\u7EDC\u9694\u79BB \u2192 \u5B89\u5168\u5904\u7F6E \u2192 \u8BB0\u5F55\u653B\u51FB\u9879
|
|
3872
|
+
\u25A1 \u660E\u786E\u5404\u7CFB\u7EDF\uFF08\u751F\u4EA7\u6838\u5FC3/\u751F\u4EA7\u975E\u6838\u5FC3/\u6D4B\u8BD5/\u5F00\u53D1\uFF09\u7684\u5E94\u6025\u5904\u7F6E\u65B9\u5F0F
|
|
3873
|
+
\u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F`,
|
|
3874
|
+
hwTestEnvShutdown: `\u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
|
|
3875
|
+
\u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
|
|
3876
|
+
\u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
|
|
3877
|
+
\u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563`,
|
|
3878
|
+
hwDutyTeam: `\u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
|
|
3879
|
+
\u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
|
|
3880
|
+
\u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
|
|
3881
|
+
\u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
|
|
3882
|
+
\u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
|
|
3883
|
+
\u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09`,
|
|
3884
|
+
hwNetworkDiagram: `\u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
|
|
3885
|
+
\u25A1 \u786E\u4FDD\u6240\u6709\u4E92\u8054\u7F51/DX \u4E13\u7EBF\u51FA\u5165\u7AD9\u8DEF\u5F84\u5728\u67B6\u6784\u56FE\u4E2D\u6E05\u6670\u6807\u6CE8
|
|
3886
|
+
\u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
|
|
3887
|
+
\u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3`,
|
|
3888
|
+
hwPentest: `\u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
|
|
3889
|
+
\u25A1 \u62A4\u7F51\u524D\u8054\u7CFB\u5B89\u5168\u5382\u5546\uFF08\u9752\u85E4/\u957F\u4EAD/\u5FAE\u6B65\u7B49\uFF09\u8FDB\u884C\u6A21\u62DF\u653B\u51FB\u6F14\u7EC3
|
|
3890
|
+
\u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
|
|
3891
|
+
\u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09`,
|
|
3892
|
+
hwWarRoom: `\u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
|
|
3893
|
+
\u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
|
|
3894
|
+
\u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
|
|
3895
|
+
\u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D`,
|
|
3896
|
+
hwCredentials: `\u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
|
|
3897
|
+
\u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
|
|
3898
|
+
\u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
|
|
3899
|
+
\u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
|
|
3900
|
+
\u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801`,
|
|
3901
|
+
hwPostOptimization: `\u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
|
|
3902
|
+
\u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
|
|
3903
|
+
\u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
|
|
3904
|
+
\u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669`,
|
|
3905
|
+
hwReference: "\u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)",
|
|
3906
|
+
// Service Reminders
|
|
3907
|
+
serviceReminderTitle: "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
|
|
3908
|
+
serviceReminderFooter: "\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002",
|
|
3909
|
+
serviceImpact: "\u5F71\u54CD",
|
|
3910
|
+
serviceAction: "\u5EFA\u8BAE",
|
|
3911
|
+
// Common
|
|
3912
|
+
account: "\u8D26\u6237",
|
|
3913
|
+
region: "\u533A\u57DF",
|
|
3914
|
+
scanTime: "\u626B\u63CF\u65F6\u95F4",
|
|
3915
|
+
duration: "\u8017\u65F6",
|
|
3916
|
+
severityDistribution: "\u4E25\u91CD\u6027\u5206\u5E03",
|
|
3917
|
+
findingsByModule: "\u6309\u6A21\u5757\u5206\u7C7B\u7684\u53D1\u73B0",
|
|
3918
|
+
details: "\u8BE6\u60C5",
|
|
3919
|
+
// Extended — HTML Security Report extras
|
|
3920
|
+
topHighestRiskFindings: (n) => `\u524D ${n} \u9879\u6700\u9AD8\u98CE\u9669\u53D1\u73B0`,
|
|
3921
|
+
resource: "\u8D44\u6E90",
|
|
3922
|
+
impact: "\u5F71\u54CD",
|
|
3923
|
+
riskScore: "\u98CE\u9669\u8BC4\u5206",
|
|
3924
|
+
showRemainingFindings: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u53D1\u73B0\u2026`,
|
|
3925
|
+
trendTitle: "30\u65E5\u8D8B\u52BF",
|
|
3926
|
+
findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
|
|
3927
|
+
showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
|
|
3928
|
+
// Extended — MLPS extras
|
|
3929
|
+
// Markdown report
|
|
3930
|
+
executiveSummary: "\u6267\u884C\u6458\u8981",
|
|
3931
|
+
totalFindingsLabel: "\u53D1\u73B0\u603B\u6570",
|
|
3932
|
+
description: "\u63CF\u8FF0",
|
|
3933
|
+
priority: "\u4F18\u5148\u7EA7",
|
|
3934
|
+
noFindingsForSeverity: (severity) => `\u65E0${severity}\u53D1\u73B0\u3002`,
|
|
3935
|
+
preCheckOverview: "\u9884\u68C0\u603B\u89C8",
|
|
3936
|
+
accountInfo: "\u8D26\u6237\u4FE1\u606F",
|
|
3937
|
+
checkedCount: (total, clean, issues) => `\u5DF2\u68C0\u67E5: ${total} \u9879\uFF08\u672A\u53D1\u73B0\u95EE\u9898: ${clean} \u9879 | \u53D1\u73B0\u95EE\u9898: ${issues} \u9879\uFF09`,
|
|
3938
|
+
uncheckedCount: (n) => `\u672A\u68C0\u67E5: ${n} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
|
|
3939
|
+
cloudProviderCount: (n) => `\u4E91\u5E73\u53F0\u8D1F\u8D23: ${n} \u9879`,
|
|
3940
|
+
manualReviewCount: (n) => `\u9700\u4EBA\u5DE5\u8BC4\u4F30: ${n} \u9879`,
|
|
3941
|
+
naCount: (n) => `\u4E0D\u9002\u7528: ${n} \u9879`,
|
|
3942
|
+
naNote: (n) => `\u4E0D\u9002\u7528\u9879: ${n} \u9879\uFF08\u7269\u8054\u7F51/\u65E0\u7EBF\u7F51\u7EDC/\u79FB\u52A8\u7EC8\u7AEF/\u5DE5\u63A7\u7CFB\u7EDF/\u53EF\u4FE1\u9A8C\u8BC1\u7B49\uFF09`,
|
|
3943
|
+
unknownNote: (n) => `\uFF08${n} \u9879\u672A\u68C0\u67E5\uFF0C\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
|
|
3944
|
+
cloudItemsNote: (n) => `\u4EE5\u4E0B ${n} \u9879\u7531 AWS \u4E91\u5E73\u53F0\u8D1F\u8D23\uFF0C\u6839\u636E\u5B89\u5168\u8D23\u4EFB\u5171\u62C5\u6A21\u578B\u4E0D\u5728\u672C\u62A5\u544A\u68C0\u67E5\u8303\u56F4\u5185\u3002`,
|
|
3945
|
+
mlpsFooterGenerated: (version) => `\u7531 AWS Security MCP Server v${version} \u751F\u6210`,
|
|
3946
|
+
mlpsFooterDisclaimer: "\u672C\u62A5\u544A\u4E3A\u8BC1\u636E\u6536\u96C6\u53C2\u8003\uFF0C\u4E0D\u5305\u542B\u5408\u89C4\u5224\u5B9A\u3002\u5B8C\u6574\u7B49\u4FDD\u6D4B\u8BC4\u9700\u7531\u6301\u8BC1\u6D4B\u8BC4\u673A\u6784\u6267\u884C\u3002",
|
|
3947
|
+
andMore: (n) => `... \u53CA\u5176\u4ED6 ${n} \u9879`,
|
|
3948
|
+
remediationByPriority: "\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09",
|
|
3949
|
+
affectedResources: (n) => `\u6D89\u53CA ${n} \u4E2A\u8D44\u6E90`,
|
|
3950
|
+
installWindowsPatches: (n, kbs) => `\u5B89\u88C5 ${n} \u4E2A Windows \u8865\u4E01 (${kbs})`,
|
|
3951
|
+
mlpsCategorySection: {
|
|
3952
|
+
"\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
|
|
3953
|
+
"\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
|
|
3954
|
+
"\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
|
|
3955
|
+
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
|
|
3956
|
+
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
|
|
3957
|
+
},
|
|
3958
|
+
// Service Recommendations
|
|
3959
|
+
notEnabled: "\u672A\u542F\u7528",
|
|
3960
|
+
serviceRecommendations: {
|
|
3961
|
+
security_hub_findings: {
|
|
3962
|
+
icon: "\u{1F534}",
|
|
3963
|
+
service: "Security Hub",
|
|
3964
|
+
impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
|
|
3965
|
+
action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
|
|
3966
|
+
},
|
|
3967
|
+
guardduty_findings: {
|
|
3968
|
+
icon: "\u{1F534}",
|
|
3969
|
+
service: "GuardDuty",
|
|
3970
|
+
impact: "\u65E0\u6CD5\u68C0\u6D4B\u5A01\u80C1\u6D3B\u52A8\uFF08\u6076\u610F IP\u3001\u5F02\u5E38 API \u8C03\u7528\u3001\u52A0\u5BC6\u8D27\u5E01\u6316\u77FF\u7B49\uFF09",
|
|
3971
|
+
action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
|
|
3972
|
+
},
|
|
3973
|
+
inspector_findings: {
|
|
3974
|
+
icon: "\u{1F7E1}",
|
|
3975
|
+
service: "Inspector",
|
|
3976
|
+
impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
|
|
3977
|
+
action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
|
|
3978
|
+
},
|
|
3979
|
+
trusted_advisor_findings: {
|
|
3980
|
+
icon: "\u{1F7E1}",
|
|
3981
|
+
service: "Trusted Advisor",
|
|
3982
|
+
impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
|
|
3983
|
+
action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
|
|
3984
|
+
},
|
|
3985
|
+
config_rules_findings: {
|
|
3986
|
+
icon: "\u{1F7E1}",
|
|
3987
|
+
service: "AWS Config",
|
|
3988
|
+
impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
|
|
3989
|
+
action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
|
|
3990
|
+
},
|
|
3991
|
+
access_analyzer_findings: {
|
|
3992
|
+
icon: "\u{1F7E1}",
|
|
3993
|
+
service: "IAM Access Analyzer",
|
|
3994
|
+
impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
|
|
3995
|
+
action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
|
|
3996
|
+
},
|
|
3997
|
+
patch_compliance_findings: {
|
|
3998
|
+
icon: "\u{1F7E1}",
|
|
3999
|
+
service: "SSM Patch Manager",
|
|
4000
|
+
impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
|
|
4001
|
+
action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
|
|
4002
|
+
}
|
|
4003
|
+
},
|
|
4004
|
+
// HW Checklist (full composite)
|
|
4005
|
+
hwChecklist: `
|
|
4006
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
4007
|
+
\u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
|
|
4008
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
4009
|
+
|
|
4010
|
+
\u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
|
|
4011
|
+
|
|
4012
|
+
\u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
|
|
4013
|
+
\u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
|
|
4014
|
+
\u25A1 \u5236\u5B9A\u5B9E\u4F8B\u9694\u79BB SOP\uFF1A\u544A\u8B66 \u2192 \u6392\u67E5 \u2192 \u5C01\u9501\u653B\u51FBIP \u2192 \u7F51\u7EDC\u9694\u79BB \u2192 \u5B89\u5168\u5904\u7F6E \u2192 \u8BB0\u5F55\u653B\u51FB\u9879
|
|
4015
|
+
\u25A1 \u660E\u786E\u5404\u7CFB\u7EDF\uFF08\u751F\u4EA7\u6838\u5FC3/\u751F\u4EA7\u975E\u6838\u5FC3/\u6D4B\u8BD5/\u5F00\u53D1\uFF09\u7684\u5E94\u6025\u5904\u7F6E\u65B9\u5F0F
|
|
4016
|
+
\u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
|
|
4017
|
+
|
|
4018
|
+
\u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
|
|
4019
|
+
\u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
|
|
4020
|
+
\u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
|
|
4021
|
+
\u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
|
|
4022
|
+
|
|
4023
|
+
\u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
|
|
4024
|
+
\u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
|
|
4025
|
+
\u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
|
|
4026
|
+
\u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
|
|
4027
|
+
\u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
|
|
4028
|
+
\u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
|
|
4029
|
+
|
|
4030
|
+
\u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
|
|
4031
|
+
\u25A1 \u786E\u4FDD\u6240\u6709\u4E92\u8054\u7F51/DX \u4E13\u7EBF\u51FA\u5165\u7AD9\u8DEF\u5F84\u5728\u67B6\u6784\u56FE\u4E2D\u6E05\u6670\u6807\u6CE8
|
|
4032
|
+
\u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
|
|
4033
|
+
\u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
|
|
4034
|
+
|
|
4035
|
+
\u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
|
|
4036
|
+
\u25A1 \u62A4\u7F51\u524D\u8054\u7CFB\u5B89\u5168\u5382\u5546\uFF08\u9752\u85E4/\u957F\u4EAD/\u5FAE\u6B65\u7B49\uFF09\u8FDB\u884C\u6A21\u62DF\u653B\u51FB\u6F14\u7EC3
|
|
4037
|
+
\u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
|
|
4038
|
+
\u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
|
|
4039
|
+
|
|
4040
|
+
\u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
|
|
4041
|
+
\u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
|
|
4042
|
+
\u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
|
|
4043
|
+
\u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D
|
|
4044
|
+
|
|
4045
|
+
\u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
|
|
4046
|
+
\u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
|
|
4047
|
+
\u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
|
|
4048
|
+
\u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
|
|
4049
|
+
\u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
|
|
4050
|
+
|
|
4051
|
+
\u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
|
|
4052
|
+
\u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
|
|
4053
|
+
\u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
|
|
4054
|
+
\u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
|
|
4055
|
+
|
|
4056
|
+
\u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
|
|
4057
|
+
`
|
|
4058
|
+
};
|
|
4059
|
+
|
|
4060
|
+
// src/i18n/en.ts
|
|
4061
|
+
var enI18n = {
|
|
4062
|
+
// HTML Security Report
|
|
4063
|
+
securityReportTitle: "AWS Security Scan Report",
|
|
4064
|
+
securityScore: "Security Score",
|
|
4065
|
+
critical: "Critical",
|
|
4066
|
+
high: "High",
|
|
4067
|
+
medium: "Medium",
|
|
4068
|
+
low: "Low",
|
|
4069
|
+
scanStatistics: "Scan Statistics",
|
|
4070
|
+
module: "Module",
|
|
4071
|
+
resources: "Resources",
|
|
4072
|
+
findings: "Findings",
|
|
4073
|
+
status: "Status",
|
|
4074
|
+
allFindings: "All Findings",
|
|
4075
|
+
recommendations: "Recommendations",
|
|
4076
|
+
unique: "unique",
|
|
4077
|
+
showMore: "Show more",
|
|
4078
|
+
noIssuesFound: "No security issues found.",
|
|
4079
|
+
allModulesClean: "All modules clean",
|
|
4080
|
+
generatedBy: "Generated by AWS Security MCP Server",
|
|
4081
|
+
informationalOnly: "This report is for informational purposes only.",
|
|
4082
|
+
// MLPS Report
|
|
4083
|
+
mlpsTitle: "MLPS Level 3 Pre-Check Report",
|
|
4084
|
+
mlpsDisclaimer: "This report is for MLPS Level 3 pre-check reference, providing cloud platform configuration check data and recommendations. Compliance determination (compliant/partially compliant/non-compliant) must be confirmed by a certified assessment institution. (GB/T 22239-2019 full checklist: 184 items)",
|
|
4085
|
+
checkedItems: "Checked Items",
|
|
4086
|
+
noIssues: "No Issues Found",
|
|
4087
|
+
issuesFound: "Issues Found",
|
|
4088
|
+
notChecked: "Not Checked",
|
|
4089
|
+
cloudProvider: "Cloud Provider Responsible",
|
|
4090
|
+
manualReview: "Manual Review Required",
|
|
4091
|
+
notApplicable: "Not Applicable",
|
|
4092
|
+
checkResult: "Check Result",
|
|
4093
|
+
noRelatedIssues: "Check Result: No related issues found",
|
|
4094
|
+
issuesFoundCount: (n) => `Check Result: Found ${n} related issue${n === 1 ? "" : "s"}`,
|
|
4095
|
+
remediation: "Remediation",
|
|
4096
|
+
remediationItems: (n) => `Remediation Items (${n} unique)`,
|
|
4097
|
+
showRemaining: (n) => `Show remaining ${n} items`,
|
|
4098
|
+
// HW Defense Checklist
|
|
4099
|
+
hwChecklistTitle: "\u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)",
|
|
4100
|
+
hwChecklistSubtitle: "The following items require manual verification and execution:",
|
|
4101
|
+
hwEmergencyIsolation: `\u26A0\uFE0F Emergency Isolation / Incident Response Plan
|
|
4102
|
+
\u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
|
|
4103
|
+
\u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
|
|
4104
|
+
\u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
|
|
4105
|
+
\u25A1 Identify responsible personnel and contacts for each project account and resource`,
|
|
4106
|
+
hwTestEnvShutdown: `\u26A0\uFE0F Test/Development Environment Handling
|
|
4107
|
+
\u25A1 Shut down non-critical systems during the drill period
|
|
4108
|
+
\u25A1 Shut down test/dev environments or maintain same security baseline as production
|
|
4109
|
+
\u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation`,
|
|
4110
|
+
hwDutyTeam: `\u26A0\uFE0F On-Duty Team Formation
|
|
4111
|
+
\u25A1 7\xD724 monitoring and rapid response team
|
|
4112
|
+
\u25A1 Technical and risk analysis team
|
|
4113
|
+
\u25A1 Security policy deployment team
|
|
4114
|
+
\u25A1 Business response team
|
|
4115
|
+
\u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)`,
|
|
4116
|
+
hwNetworkDiagram: `\u26A0\uFE0F Ingress/Egress Path Architecture Diagram
|
|
4117
|
+
\u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
|
|
4118
|
+
\u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
|
|
4119
|
+
\u25A1 Identify all internet-facing data interaction interfaces`,
|
|
4120
|
+
hwPentest: `\u26A0\uFE0F Proactive Penetration Testing
|
|
4121
|
+
\u25A1 Contact security vendors for simulated attack drills before the exercise
|
|
4122
|
+
\u25A1 Conduct security hardening based on penetration test reports
|
|
4123
|
+
\u25A1 Monitor AWS security advisories (known vulnerabilities and patches)`,
|
|
4124
|
+
hwWarRoom: `\u26A0\uFE0F WAR-ROOM Real-Time Communication
|
|
4125
|
+
\u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
|
|
4126
|
+
\u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
|
|
4127
|
+
\u25A1 Standardize case title format: "[CyberDrill] + Issue Description"`,
|
|
4128
|
+
hwCredentials: `\u26A0\uFE0F Password & Credential Management
|
|
4129
|
+
\u25A1 All IAM users must have MFA enabled
|
|
4130
|
+
\u25A1 Access key rotation cycle \u2264 90 days
|
|
4131
|
+
\u25A1 Avoid shared account usage
|
|
4132
|
+
\u25A1 No plaintext passwords in S3/Lambda/application code`,
|
|
4133
|
+
hwPostOptimization: `\u26A0\uFE0F Post-Drill Optimization
|
|
4134
|
+
\u25A1 Address and remediate each item from the attack report
|
|
4135
|
+
\u25A1 Establish periodic security maintenance processes with the security team
|
|
4136
|
+
\u25A1 Continuously fill security risk gaps`,
|
|
4137
|
+
hwReference: "Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)",
|
|
4138
|
+
// Service Reminders
|
|
4139
|
+
serviceReminderTitle: "\u26A1 The following security services are not enabled; some checks cannot be performed:",
|
|
4140
|
+
serviceReminderFooter: "Re-scan after enabling the above services for a more complete security assessment.",
|
|
4141
|
+
serviceImpact: "Impact",
|
|
4142
|
+
serviceAction: "Action",
|
|
4143
|
+
// Common
|
|
4144
|
+
account: "Account",
|
|
4145
|
+
region: "Region",
|
|
4146
|
+
scanTime: "Scan Time",
|
|
4147
|
+
duration: "Duration",
|
|
4148
|
+
severityDistribution: "Severity Distribution",
|
|
4149
|
+
findingsByModule: "Findings by Module",
|
|
4150
|
+
details: "Details",
|
|
4151
|
+
// Extended \u2014 HTML Security Report extras
|
|
4152
|
+
topHighestRiskFindings: (n) => `Top ${n} Highest Risk Findings`,
|
|
4153
|
+
resource: "Resource",
|
|
4154
|
+
impact: "Impact",
|
|
4155
|
+
riskScore: "Risk Score",
|
|
4156
|
+
showRemainingFindings: (n) => `Show remaining ${n} findings\u2026`,
|
|
4157
|
+
trendTitle: "30-Day Trends",
|
|
4158
|
+
findingsBySeverity: "Findings by Severity",
|
|
4159
|
+
showMoreCount: (n) => `Show ${n} more\u2026`,
|
|
4160
|
+
// Extended \u2014 MLPS extras
|
|
4161
|
+
// Markdown report
|
|
4162
|
+
executiveSummary: "Executive Summary",
|
|
4163
|
+
totalFindingsLabel: "Total Findings",
|
|
4164
|
+
description: "Description",
|
|
4165
|
+
priority: "Priority",
|
|
4166
|
+
noFindingsForSeverity: (severity) => `No ${severity.toLowerCase()} findings.`,
|
|
4167
|
+
preCheckOverview: "Pre-Check Overview",
|
|
4168
|
+
accountInfo: "Account Information",
|
|
4169
|
+
checkedCount: (total, clean, issues) => `Checked: ${total} items (No issues: ${clean} | Issues found: ${issues})`,
|
|
4170
|
+
uncheckedCount: (n) => `Not checked: ${n} items (corresponding scan modules not run)`,
|
|
4171
|
+
cloudProviderCount: (n) => `Cloud provider responsible: ${n} items`,
|
|
4172
|
+
manualReviewCount: (n) => `Manual review required: ${n} items`,
|
|
4173
|
+
naCount: (n) => `Not applicable: ${n} items`,
|
|
4174
|
+
naNote: (n) => `Not applicable: ${n} items (IoT/wireless networks/mobile terminals/ICS/trusted verification, etc.)`,
|
|
4175
|
+
unknownNote: (n) => `(${n} items not checked \u2014 corresponding scan modules not run)`,
|
|
4176
|
+
cloudItemsNote: (n) => `The following ${n} items are the responsibility of the AWS cloud platform and are outside the scope of this report per the shared responsibility model.`,
|
|
4177
|
+
mlpsFooterGenerated: (version) => `Generated by AWS Security MCP Server v${version}`,
|
|
4178
|
+
mlpsFooterDisclaimer: "This report is for evidence collection reference and does not include compliance determination. A complete MLPS assessment must be conducted by a certified assessment institution.",
|
|
4179
|
+
andMore: (n) => `\u2026 and ${n} more`,
|
|
4180
|
+
remediationByPriority: "Remediation Items (by Priority)",
|
|
4181
|
+
affectedResources: (n) => `${n} resource${n === 1 ? "" : "s"} affected`,
|
|
4182
|
+
installWindowsPatches: (n, kbs) => `Install ${n} Windows patch${n === 1 ? "" : "es"} (${kbs})`,
|
|
4183
|
+
mlpsCategorySection: {
|
|
4184
|
+
"\u5B89\u5168\u7269\u7406\u73AF\u5883": "I. Physical Environment Security",
|
|
4185
|
+
"\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "II. Communication Network Security",
|
|
4186
|
+
"\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "III. Area Boundary Security",
|
|
4187
|
+
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
|
|
4188
|
+
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
|
|
4189
|
+
},
|
|
4190
|
+
// Service Recommendations
|
|
4191
|
+
notEnabled: "Not Enabled",
|
|
4192
|
+
serviceRecommendations: {
|
|
4193
|
+
security_hub_findings: {
|
|
4194
|
+
icon: "\u{1F534}",
|
|
4195
|
+
service: "Security Hub",
|
|
4196
|
+
impact: "Cannot obtain 300+ automated security checks (FSBP/CIS/PCI DSS standards)",
|
|
4197
|
+
action: "Enable Security Hub for the most comprehensive security posture assessment"
|
|
4198
|
+
},
|
|
4199
|
+
guardduty_findings: {
|
|
4200
|
+
icon: "\u{1F534}",
|
|
4201
|
+
service: "GuardDuty",
|
|
4202
|
+
impact: "Cannot detect threat activity (malicious IPs, anomalous API calls, crypto mining, etc.)",
|
|
4203
|
+
action: "Enable GuardDuty for continuous threat detection"
|
|
4204
|
+
},
|
|
4205
|
+
inspector_findings: {
|
|
4206
|
+
icon: "\u{1F7E1}",
|
|
4207
|
+
service: "Inspector",
|
|
4208
|
+
impact: "Cannot scan EC2/Lambda/container software vulnerabilities (CVEs)",
|
|
4209
|
+
action: "Enable Inspector to discover known security vulnerabilities"
|
|
4210
|
+
},
|
|
4211
|
+
trusted_advisor_findings: {
|
|
4212
|
+
icon: "\u{1F7E1}",
|
|
4213
|
+
service: "Trusted Advisor",
|
|
4214
|
+
impact: "Cannot obtain AWS best practice security checks",
|
|
4215
|
+
action: "Upgrade to Business/Enterprise Support plan to use Trusted Advisor security checks"
|
|
4216
|
+
},
|
|
4217
|
+
config_rules_findings: {
|
|
4218
|
+
icon: "\u{1F7E1}",
|
|
4219
|
+
service: "AWS Config",
|
|
4220
|
+
impact: "Cannot check resource configuration compliance status",
|
|
4221
|
+
action: "Enable AWS Config and configure Config Rules"
|
|
4222
|
+
},
|
|
4223
|
+
access_analyzer_findings: {
|
|
4224
|
+
icon: "\u{1F7E1}",
|
|
4225
|
+
service: "IAM Access Analyzer",
|
|
4226
|
+
impact: "Cannot detect whether resources are accessed by external accounts or public networks",
|
|
4227
|
+
action: "Create IAM Access Analyzer (account-level or organization-level)"
|
|
4228
|
+
},
|
|
4229
|
+
patch_compliance_findings: {
|
|
4230
|
+
icon: "\u{1F7E1}",
|
|
4231
|
+
service: "SSM Patch Manager",
|
|
4232
|
+
impact: "Cannot check instance patch compliance status",
|
|
4233
|
+
action: "Install SSM Agent and configure Patch Manager"
|
|
4234
|
+
}
|
|
4235
|
+
},
|
|
4236
|
+
// HW Checklist (full composite)
|
|
4237
|
+
hwChecklist: `
|
|
4238
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
4239
|
+
\u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)
|
|
4240
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
4241
|
+
|
|
4242
|
+
The following items require manual verification and execution:
|
|
4243
|
+
|
|
4244
|
+
\u26A0\uFE0F Emergency Isolation / Incident Response Plan
|
|
4245
|
+
\u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
|
|
4246
|
+
\u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
|
|
4247
|
+
\u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
|
|
4248
|
+
\u25A1 Identify responsible personnel and contacts for each project account and resource
|
|
4249
|
+
|
|
4250
|
+
\u26A0\uFE0F Test/Development Environment Handling
|
|
4251
|
+
\u25A1 Shut down non-critical systems during the drill period
|
|
4252
|
+
\u25A1 Shut down test/dev environments or maintain same security baseline as production
|
|
4253
|
+
\u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation
|
|
4254
|
+
|
|
4255
|
+
\u26A0\uFE0F On-Duty Team Formation
|
|
4256
|
+
\u25A1 7\xD724 monitoring and rapid response team
|
|
4257
|
+
\u25A1 Technical and risk analysis team
|
|
4258
|
+
\u25A1 Security policy deployment team
|
|
4259
|
+
\u25A1 Business response team
|
|
4260
|
+
\u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)
|
|
4261
|
+
|
|
4262
|
+
\u26A0\uFE0F Ingress/Egress Path Architecture Diagram
|
|
4263
|
+
\u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
|
|
4264
|
+
\u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
|
|
4265
|
+
\u25A1 Identify all internet-facing data interaction interfaces
|
|
4266
|
+
|
|
4267
|
+
\u26A0\uFE0F Proactive Penetration Testing
|
|
4268
|
+
\u25A1 Contact security vendors for simulated attack drills before the exercise
|
|
4269
|
+
\u25A1 Conduct security hardening based on penetration test reports
|
|
4270
|
+
\u25A1 Monitor AWS security advisories (known vulnerabilities and patches)
|
|
4271
|
+
|
|
4272
|
+
\u26A0\uFE0F WAR-ROOM Real-Time Communication
|
|
4273
|
+
\u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
|
|
4274
|
+
\u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
|
|
4275
|
+
\u25A1 Standardize case title format: "[CyberDrill] + Issue Description"
|
|
4276
|
+
|
|
4277
|
+
\u26A0\uFE0F Password & Credential Management
|
|
4278
|
+
\u25A1 All IAM users must have MFA enabled
|
|
4279
|
+
\u25A1 Access key rotation cycle \u2264 90 days
|
|
4280
|
+
\u25A1 Avoid shared account usage
|
|
4281
|
+
\u25A1 No plaintext passwords in S3/Lambda/application code
|
|
4282
|
+
|
|
4283
|
+
\u26A0\uFE0F Post-Drill Optimization
|
|
4284
|
+
\u25A1 Address and remediate each item from the attack report
|
|
4285
|
+
\u25A1 Establish periodic security maintenance processes with the security team
|
|
4286
|
+
\u25A1 Continuously fill security risk gaps
|
|
4287
|
+
|
|
4288
|
+
Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)
|
|
4289
|
+
`
|
|
4290
|
+
};
|
|
4291
|
+
|
|
4292
|
+
// src/i18n/index.ts
|
|
4293
|
+
var translations = {
|
|
4294
|
+
zh: zhI18n,
|
|
4295
|
+
en: enI18n
|
|
4296
|
+
};
|
|
4297
|
+
function getI18n(lang = "zh") {
|
|
4298
|
+
return translations[lang] ?? translations.zh;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4199
4301
|
// src/tools/report-tool.ts
|
|
4200
4302
|
var SEVERITY_ICON = {
|
|
4201
4303
|
CRITICAL: "\u{1F534}",
|
|
@@ -4213,38 +4315,45 @@ function formatDuration(start, end) {
|
|
|
4213
4315
|
const remainSecs = secs % 60;
|
|
4214
4316
|
return `${mins}m ${remainSecs}s`;
|
|
4215
4317
|
}
|
|
4216
|
-
function
|
|
4217
|
-
const
|
|
4218
|
-
return [
|
|
4219
|
-
`#### ${f.title}`,
|
|
4220
|
-
`- **Resource:** ${f.resourceId} (\`${f.resourceArn}\`)`,
|
|
4221
|
-
`- **Description:** ${f.description}`,
|
|
4222
|
-
`- **Impact:** ${f.impact}`,
|
|
4223
|
-
`- **Risk Score:** ${f.riskScore}/10`,
|
|
4224
|
-
`- **Remediation:**`,
|
|
4225
|
-
steps,
|
|
4226
|
-
`- **Priority:** ${f.priority}`
|
|
4227
|
-
].join("\n");
|
|
4228
|
-
}
|
|
4229
|
-
function generateMarkdownReport(scanResults) {
|
|
4318
|
+
function generateMarkdownReport(scanResults, lang) {
|
|
4319
|
+
const t = getI18n(lang ?? "zh");
|
|
4230
4320
|
const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
|
|
4231
4321
|
const date = scanStart.split("T")[0];
|
|
4232
4322
|
const duration = formatDuration(scanStart, scanEnd);
|
|
4323
|
+
const sevLabel = {
|
|
4324
|
+
CRITICAL: t.critical,
|
|
4325
|
+
HIGH: t.high,
|
|
4326
|
+
MEDIUM: t.medium,
|
|
4327
|
+
LOW: t.low
|
|
4328
|
+
};
|
|
4329
|
+
function renderFinding(f) {
|
|
4330
|
+
const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
|
|
4331
|
+
return [
|
|
4332
|
+
`#### ${f.title}`,
|
|
4333
|
+
`- **${t.resource}:** ${f.resourceId} (\`${f.resourceArn}\`)`,
|
|
4334
|
+
`- **${t.description}:** ${f.description}`,
|
|
4335
|
+
`- **${t.impact}:** ${f.impact}`,
|
|
4336
|
+
`- **${t.riskScore}:** ${f.riskScore}/10`,
|
|
4337
|
+
`- **${t.remediation}:**`,
|
|
4338
|
+
steps,
|
|
4339
|
+
`- **${t.priority}:** ${f.priority}`
|
|
4340
|
+
].join("\n");
|
|
4341
|
+
}
|
|
4233
4342
|
const lines = [];
|
|
4234
|
-
lines.push(`#
|
|
4343
|
+
lines.push(`# ${t.securityReportTitle} \u2014 ${date}`);
|
|
4235
4344
|
lines.push("");
|
|
4236
|
-
lines.push(
|
|
4237
|
-
lines.push(`-
|
|
4238
|
-
lines.push(`-
|
|
4239
|
-
lines.push(`-
|
|
4345
|
+
lines.push(`## ${t.executiveSummary}`);
|
|
4346
|
+
lines.push(`- **${t.account}:** ${accountId}`);
|
|
4347
|
+
lines.push(`- **${t.region}:** ${region}`);
|
|
4348
|
+
lines.push(`- **${t.duration}:** ${duration}`);
|
|
4240
4349
|
lines.push(
|
|
4241
|
-
`-
|
|
4350
|
+
`- **${t.totalFindingsLabel}:** ${summary.totalFindings} (${SEVERITY_ICON.CRITICAL} ${summary.critical} ${t.critical} | ${SEVERITY_ICON.HIGH} ${summary.high} ${t.high} | ${SEVERITY_ICON.MEDIUM} ${summary.medium} ${t.medium} | ${SEVERITY_ICON.LOW} ${summary.low} ${t.low})`
|
|
4242
4351
|
);
|
|
4243
4352
|
lines.push("");
|
|
4244
4353
|
if (summary.totalFindings === 0) {
|
|
4245
|
-
lines.push(
|
|
4354
|
+
lines.push(`## ${t.findingsBySeverity}`);
|
|
4246
4355
|
lines.push("");
|
|
4247
|
-
lines.push(
|
|
4356
|
+
lines.push(`\u2705 ${t.noIssuesFound}`);
|
|
4248
4357
|
lines.push("");
|
|
4249
4358
|
} else {
|
|
4250
4359
|
const allFindings = modules.flatMap((m) => m.findings);
|
|
@@ -4255,15 +4364,15 @@ function generateMarkdownReport(scanResults) {
|
|
|
4255
4364
|
for (const f of allFindings) {
|
|
4256
4365
|
grouped.get(f.severity).push(f);
|
|
4257
4366
|
}
|
|
4258
|
-
lines.push(
|
|
4367
|
+
lines.push(`## ${t.findingsBySeverity}`);
|
|
4259
4368
|
lines.push("");
|
|
4260
4369
|
for (const sev of SEVERITY_ORDER) {
|
|
4261
4370
|
const findings = grouped.get(sev);
|
|
4262
4371
|
const icon = SEVERITY_ICON[sev];
|
|
4263
|
-
lines.push(`### ${icon} ${sev
|
|
4372
|
+
lines.push(`### ${icon} ${sevLabel[sev]}`);
|
|
4264
4373
|
lines.push("");
|
|
4265
4374
|
if (findings.length === 0) {
|
|
4266
|
-
lines.push(
|
|
4375
|
+
lines.push(t.noFindingsForSeverity(sevLabel[sev]));
|
|
4267
4376
|
lines.push("");
|
|
4268
4377
|
continue;
|
|
4269
4378
|
}
|
|
@@ -4274,9 +4383,9 @@ function generateMarkdownReport(scanResults) {
|
|
|
4274
4383
|
}
|
|
4275
4384
|
}
|
|
4276
4385
|
}
|
|
4277
|
-
lines.push(
|
|
4386
|
+
lines.push(`## ${t.scanStatistics}`);
|
|
4278
4387
|
lines.push(
|
|
4279
|
-
|
|
4388
|
+
`| ${t.module} | ${t.resources} | ${t.findings} | ${t.status} |`
|
|
4280
4389
|
);
|
|
4281
4390
|
lines.push("|--------|------------------|----------|--------|");
|
|
4282
4391
|
for (const m of modules) {
|
|
@@ -4289,7 +4398,7 @@ function generateMarkdownReport(scanResults) {
|
|
|
4289
4398
|
if (summary.totalFindings > 0) {
|
|
4290
4399
|
const allFindings = modules.flatMap((m) => m.findings);
|
|
4291
4400
|
allFindings.sort((a, b) => b.riskScore - a.riskScore);
|
|
4292
|
-
lines.push(
|
|
4401
|
+
lines.push(`## ${t.recommendations}`);
|
|
4293
4402
|
for (let i = 0; i < allFindings.length; i++) {
|
|
4294
4403
|
const f = allFindings[i];
|
|
4295
4404
|
lines.push(`${i + 1}. [${f.priority}] ${f.title}: ${f.remediationSteps[0] ?? "Review and remediate."}`);
|
|
@@ -6336,13 +6445,6 @@ var MLPS3_CATEGORY_ORDER = [
|
|
|
6336
6445
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
|
|
6337
6446
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
|
|
6338
6447
|
];
|
|
6339
|
-
var MLPS3_CATEGORY_SECTION = {
|
|
6340
|
-
"\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
|
|
6341
|
-
"\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
|
|
6342
|
-
"\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
|
|
6343
|
-
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
|
|
6344
|
-
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
|
|
6345
|
-
};
|
|
6346
6448
|
var MLPS3_CHECK_MAPPING = [
|
|
6347
6449
|
// =========================================================================
|
|
6348
6450
|
// 安全物理环境 — L3-PES1-* (22 items) → cloud_provider
|
|
@@ -7002,7 +7104,9 @@ function evaluateAllFullChecks(scanResults) {
|
|
|
7002
7104
|
return evaluateFullCheck(item, mapping, allFindings, scanModules);
|
|
7003
7105
|
});
|
|
7004
7106
|
}
|
|
7005
|
-
function generateMlps3Report(scanResults) {
|
|
7107
|
+
function generateMlps3Report(scanResults, lang) {
|
|
7108
|
+
const t = getI18n(lang ?? "zh");
|
|
7109
|
+
const isEn = (lang ?? "zh") === "en";
|
|
7006
7110
|
const { accountId, region, scanStart } = scanResults;
|
|
7007
7111
|
const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
|
|
7008
7112
|
const results = evaluateAllFullChecks(scanResults);
|
|
@@ -7014,27 +7118,28 @@ function generateMlps3Report(scanResults) {
|
|
|
7014
7118
|
const cloudCount = results.filter((r) => r.status === "cloud_provider").length;
|
|
7015
7119
|
const manualCount = results.filter((r) => r.status === "manual").length;
|
|
7016
7120
|
const naCount = results.filter((r) => r.status === "not_applicable").length;
|
|
7121
|
+
const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
|
|
7122
|
+
const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
|
|
7017
7123
|
const lines = [];
|
|
7018
|
-
lines.push(
|
|
7019
|
-
lines.push(
|
|
7020
|
-
lines.push("> **\uFF08GB/T 22239-2019 \u5B8C\u6574\u68C0\u67E5\u6E05\u5355 184 \u9879\uFF09**");
|
|
7124
|
+
lines.push(`# ${t.mlpsTitle}`);
|
|
7125
|
+
lines.push(`> **${t.mlpsDisclaimer}**`);
|
|
7021
7126
|
lines.push("");
|
|
7022
|
-
lines.push(
|
|
7023
|
-
lines.push(`-
|
|
7127
|
+
lines.push(`## ${t.accountInfo}`);
|
|
7128
|
+
lines.push(`- ${t.account}: ${accountId} | ${t.region}: ${region} | ${t.scanTime}: ${scanTime}`);
|
|
7024
7129
|
lines.push("");
|
|
7025
|
-
lines.push(
|
|
7026
|
-
lines.push(`-
|
|
7130
|
+
lines.push(`## ${t.preCheckOverview}`);
|
|
7131
|
+
lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
|
|
7027
7132
|
if (autoUnknown > 0) {
|
|
7028
|
-
lines.push(`-
|
|
7133
|
+
lines.push(`- ${t.uncheckedCount(autoUnknown)}`);
|
|
7029
7134
|
}
|
|
7030
|
-
lines.push(`-
|
|
7031
|
-
lines.push(`-
|
|
7135
|
+
lines.push(`- ${t.cloudProviderCount(cloudCount)}`);
|
|
7136
|
+
lines.push(`- ${t.manualReviewCount(manualCount)}`);
|
|
7032
7137
|
if (naCount > 0) {
|
|
7033
|
-
lines.push(`-
|
|
7138
|
+
lines.push(`- ${t.naCount(naCount)}`);
|
|
7034
7139
|
}
|
|
7035
7140
|
lines.push("");
|
|
7036
7141
|
for (const category of MLPS3_CATEGORY_ORDER) {
|
|
7037
|
-
const sectionTitle =
|
|
7142
|
+
const sectionTitle = t.mlpsCategorySection[category] ?? category;
|
|
7038
7143
|
const catResults = results.filter(
|
|
7039
7144
|
(r) => r.item.categoryCn === category && r.status !== "not_applicable"
|
|
7040
7145
|
);
|
|
@@ -7047,18 +7152,20 @@ function generateMlps3Report(scanResults) {
|
|
|
7047
7152
|
if (!controlMap.has(key)) controlMap.set(key, []);
|
|
7048
7153
|
controlMap.get(key).push(r);
|
|
7049
7154
|
}
|
|
7050
|
-
for (const [
|
|
7155
|
+
for (const [_controlKey, controlResults] of controlMap) {
|
|
7156
|
+
const controlName = itemControl(controlResults[0]);
|
|
7051
7157
|
lines.push(`### ${controlName}`);
|
|
7052
7158
|
for (const r of controlResults) {
|
|
7053
7159
|
const icon = r.status === "clean" ? "\u2705" : r.status === "issues" ? "\u274C" : r.status === "unknown" ? "\u26A0\uFE0F" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
|
|
7054
|
-
const suffix = r.status === "unknown" ?
|
|
7055
|
-
|
|
7160
|
+
const suffix = r.status === "unknown" ? ` \u2014 ${t.notChecked}` : r.status === "manual" ? ` \u2014 ${r.mapping.guidance ?? t.manualReview}` : r.status === "cloud_provider" ? ` \u2014 ${r.mapping.note ?? t.cloudProvider}` : r.status === "clean" ? ` ${t.noIssues}` : ` ${t.issuesFound}`;
|
|
7161
|
+
const reqText = itemReq(r);
|
|
7162
|
+
lines.push(`- [${icon}] ${r.item.id} ${reqText.slice(0, 60)}${reqText.length > 60 ? "\u2026" : ""}${suffix}`);
|
|
7056
7163
|
if (r.status === "issues" && r.relatedFindings.length > 0) {
|
|
7057
7164
|
for (const f of r.relatedFindings.slice(0, 3)) {
|
|
7058
7165
|
lines.push(` - ${f.severity}: ${f.title}`);
|
|
7059
7166
|
}
|
|
7060
7167
|
if (r.relatedFindings.length > 3) {
|
|
7061
|
-
lines.push(` -
|
|
7168
|
+
lines.push(` - ${t.andMore(r.relatedFindings.length - 3)}`);
|
|
7062
7169
|
}
|
|
7063
7170
|
}
|
|
7064
7171
|
}
|
|
@@ -7067,7 +7174,7 @@ function generateMlps3Report(scanResults) {
|
|
|
7067
7174
|
}
|
|
7068
7175
|
const failedResults = results.filter((r) => r.status === "issues");
|
|
7069
7176
|
if (failedResults.length > 0) {
|
|
7070
|
-
lines.push(
|
|
7177
|
+
lines.push(`## ${t.remediationByPriority}`);
|
|
7071
7178
|
lines.push("");
|
|
7072
7179
|
const allFailedFindings = /* @__PURE__ */ new Map();
|
|
7073
7180
|
for (const r of failedResults) {
|
|
@@ -7090,7 +7197,7 @@ function generateMlps3Report(scanResults) {
|
|
|
7090
7197
|
lines.push("");
|
|
7091
7198
|
}
|
|
7092
7199
|
if (naCount > 0) {
|
|
7093
|
-
lines.push(`>
|
|
7200
|
+
lines.push(`> ${t.naNote(naCount)}`);
|
|
7094
7201
|
lines.push("");
|
|
7095
7202
|
}
|
|
7096
7203
|
return lines.join("\n");
|
|
@@ -7100,6 +7207,15 @@ function generateMlps3Report(scanResults) {
|
|
|
7100
7207
|
function esc(s) {
|
|
7101
7208
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7102
7209
|
}
|
|
7210
|
+
function escWithLinks(s) {
|
|
7211
|
+
const parts = s.split(/(https?:\/\/\S+)/);
|
|
7212
|
+
return parts.map((part, i) => {
|
|
7213
|
+
if (i % 2 === 1) {
|
|
7214
|
+
return `<a href="${esc(part)}" style="color:#60a5fa" target="_blank" rel="noopener">${esc(part)}</a>`;
|
|
7215
|
+
}
|
|
7216
|
+
return esc(part);
|
|
7217
|
+
}).join("");
|
|
7218
|
+
}
|
|
7103
7219
|
function calcScore(summary) {
|
|
7104
7220
|
const raw = 100 - summary.critical * 15 - summary.high * 5 - summary.medium * 2 - summary.low * 0.5;
|
|
7105
7221
|
return Math.max(0, Math.min(100, Math.round(raw)));
|
|
@@ -7123,50 +7239,6 @@ function scoreColor(score) {
|
|
|
7123
7239
|
if (score >= 50) return "#eab308";
|
|
7124
7240
|
return "#ef4444";
|
|
7125
7241
|
}
|
|
7126
|
-
var SERVICE_RECOMMENDATIONS = {
|
|
7127
|
-
security_hub_findings: {
|
|
7128
|
-
icon: "\u{1F534}",
|
|
7129
|
-
service: "Security Hub",
|
|
7130
|
-
impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
|
|
7131
|
-
action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
|
|
7132
|
-
},
|
|
7133
|
-
guardduty_findings: {
|
|
7134
|
-
icon: "\u{1F534}",
|
|
7135
|
-
service: "GuardDuty",
|
|
7136
|
-
impact: "\u65E0\u6CD5\u68C0\u6D4B\u5A01\u80C1\u6D3B\u52A8\uFF08\u6076\u610F IP\u3001\u5F02\u5E38 API \u8C03\u7528\u3001\u52A0\u5BC6\u8D27\u5E01\u6316\u77FF\u7B49\uFF09",
|
|
7137
|
-
action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
|
|
7138
|
-
},
|
|
7139
|
-
inspector_findings: {
|
|
7140
|
-
icon: "\u{1F7E1}",
|
|
7141
|
-
service: "Inspector",
|
|
7142
|
-
impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
|
|
7143
|
-
action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
|
|
7144
|
-
},
|
|
7145
|
-
trusted_advisor_findings: {
|
|
7146
|
-
icon: "\u{1F7E1}",
|
|
7147
|
-
service: "Trusted Advisor",
|
|
7148
|
-
impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
|
|
7149
|
-
action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
|
|
7150
|
-
},
|
|
7151
|
-
config_rules_findings: {
|
|
7152
|
-
icon: "\u{1F7E1}",
|
|
7153
|
-
service: "AWS Config",
|
|
7154
|
-
impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
|
|
7155
|
-
action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
|
|
7156
|
-
},
|
|
7157
|
-
access_analyzer_findings: {
|
|
7158
|
-
icon: "\u{1F7E1}",
|
|
7159
|
-
service: "IAM Access Analyzer",
|
|
7160
|
-
impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
|
|
7161
|
-
action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
|
|
7162
|
-
},
|
|
7163
|
-
patch_compliance_findings: {
|
|
7164
|
-
icon: "\u{1F7E1}",
|
|
7165
|
-
service: "SSM Patch Manager",
|
|
7166
|
-
impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
|
|
7167
|
-
action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
|
|
7168
|
-
}
|
|
7169
|
-
};
|
|
7170
7242
|
var SERVICE_NOT_ENABLED_PATTERNS = [
|
|
7171
7243
|
"not enabled",
|
|
7172
7244
|
"not found",
|
|
@@ -7176,10 +7248,11 @@ var SERVICE_NOT_ENABLED_PATTERNS = [
|
|
|
7176
7248
|
"not available",
|
|
7177
7249
|
"is not enabled"
|
|
7178
7250
|
];
|
|
7179
|
-
function getDisabledServices(modules) {
|
|
7251
|
+
function getDisabledServices(modules, lang) {
|
|
7252
|
+
const t = getI18n(lang ?? "zh");
|
|
7180
7253
|
const disabled = [];
|
|
7181
7254
|
for (const mod of modules) {
|
|
7182
|
-
const rec =
|
|
7255
|
+
const rec = t.serviceRecommendations[mod.module];
|
|
7183
7256
|
if (!rec) continue;
|
|
7184
7257
|
if (!mod.warnings?.length) continue;
|
|
7185
7258
|
const hasNotEnabled = mod.warnings.some(
|
|
@@ -7191,21 +7264,22 @@ function getDisabledServices(modules) {
|
|
|
7191
7264
|
}
|
|
7192
7265
|
return disabled;
|
|
7193
7266
|
}
|
|
7194
|
-
function buildServiceReminderHtml(modules) {
|
|
7195
|
-
const
|
|
7267
|
+
function buildServiceReminderHtml(modules, lang) {
|
|
7268
|
+
const t = getI18n(lang ?? "zh");
|
|
7269
|
+
const disabled = getDisabledServices(modules, lang);
|
|
7196
7270
|
if (disabled.length === 0) return "";
|
|
7197
7271
|
const items = disabled.map((svc) => `
|
|
7198
7272
|
<div style="margin-bottom:12px">
|
|
7199
|
-
<div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)}
|
|
7200
|
-
<div style="margin-left:28px;color:#cbd5e1;font-size:13px"
|
|
7201
|
-
<div style="margin-left:28px;color:#cbd5e1;font-size:13px"
|
|
7273
|
+
<div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} ${esc(t.notEnabled)}</div>
|
|
7274
|
+
<div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceImpact)}\uFF1A${esc(svc.impact)}</div>
|
|
7275
|
+
<div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceAction)}\uFF1A${esc(svc.action)}</div>
|
|
7202
7276
|
</div>`).join("\n");
|
|
7203
7277
|
return `
|
|
7204
7278
|
<section>
|
|
7205
7279
|
<div style="background:#2d1f00;border:1px solid #b45309;border-radius:8px;padding:20px;margin-bottom:32px">
|
|
7206
|
-
<div style="font-size:17px;font-weight:700;margin-bottom:12px"
|
|
7280
|
+
<div style="font-size:17px;font-weight:700;margin-bottom:12px">${esc(t.serviceReminderTitle)}</div>
|
|
7207
7281
|
${items}
|
|
7208
|
-
<div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500"
|
|
7282
|
+
<div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500">${esc(t.serviceReminderFooter)}</div>
|
|
7209
7283
|
</div>
|
|
7210
7284
|
</section>`;
|
|
7211
7285
|
}
|
|
@@ -7407,12 +7481,12 @@ function donutChart(summary) {
|
|
|
7407
7481
|
"</svg>"
|
|
7408
7482
|
].join("\n");
|
|
7409
7483
|
}
|
|
7410
|
-
function barChart(modules) {
|
|
7484
|
+
function barChart(modules, allCleanLabel = "All modules clean") {
|
|
7411
7485
|
const withFindings = modules.filter((m) => m.findingsCount > 0).sort((a, b) => b.findingsCount - a.findingsCount).slice(0, 12);
|
|
7412
7486
|
if (withFindings.length === 0) {
|
|
7413
7487
|
return [
|
|
7414
7488
|
'<svg viewBox="0 0 400 50" width="100%">',
|
|
7415
|
-
|
|
7489
|
+
` <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">${esc(allCleanLabel)}</text>`,
|
|
7416
7490
|
"</svg>"
|
|
7417
7491
|
].join("\n");
|
|
7418
7492
|
}
|
|
@@ -7527,7 +7601,9 @@ function scoreTrendChart(history) {
|
|
|
7527
7601
|
"</svg>"
|
|
7528
7602
|
].join("\n");
|
|
7529
7603
|
}
|
|
7530
|
-
function generateHtmlReport(scanResults, history) {
|
|
7604
|
+
function generateHtmlReport(scanResults, history, lang) {
|
|
7605
|
+
const t = getI18n(lang ?? "zh");
|
|
7606
|
+
const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
|
|
7531
7607
|
const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
|
|
7532
7608
|
const date = scanStart.split("T")[0];
|
|
7533
7609
|
const duration = formatDuration2(scanStart, scanEnd);
|
|
@@ -7545,23 +7621,23 @@ function generateHtmlReport(scanResults, history) {
|
|
|
7545
7621
|
<div class="top5-content">
|
|
7546
7622
|
<span class="badge badge-${esc(f.severity.toLowerCase())}">${esc(f.severity)}</span>
|
|
7547
7623
|
<div class="top5-title">${esc(f.title)}</div>
|
|
7548
|
-
<div class="top5-detail"><strong
|
|
7549
|
-
<div class="top5-detail"><strong
|
|
7550
|
-
<div class="top5-detail"><strong
|
|
7551
|
-
<h4
|
|
7552
|
-
<ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${
|
|
7624
|
+
<div class="top5-detail"><strong>${t.resource}:</strong> ${esc(f.resourceId)}</div>
|
|
7625
|
+
<div class="top5-detail"><strong>${t.impact}:</strong> ${esc(f.impact)}</div>
|
|
7626
|
+
<div class="top5-detail"><strong>${t.riskScore}:</strong> ${f.riskScore}/10</div>
|
|
7627
|
+
<h4>${t.remediation}</h4>
|
|
7628
|
+
<ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
|
|
7553
7629
|
</div>
|
|
7554
7630
|
</div>`
|
|
7555
7631
|
).join("\n");
|
|
7556
7632
|
top5Html = `
|
|
7557
7633
|
<section>
|
|
7558
|
-
<h2
|
|
7634
|
+
<h2>${esc(t.topHighestRiskFindings(top5.length))}</h2>
|
|
7559
7635
|
${cards}
|
|
7560
7636
|
</section>`;
|
|
7561
7637
|
}
|
|
7562
7638
|
let findingsHtml;
|
|
7563
7639
|
if (summary.totalFindings === 0) {
|
|
7564
|
-
findingsHtml =
|
|
7640
|
+
findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
|
|
7565
7641
|
} else {
|
|
7566
7642
|
const FOLD_THRESHOLD = 20;
|
|
7567
7643
|
const renderCard = (f) => {
|
|
@@ -7570,10 +7646,10 @@ function generateHtmlReport(scanResults, history) {
|
|
|
7570
7646
|
<span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
|
|
7571
7647
|
<span class="finding-title-text">${esc(f.title)}</span>
|
|
7572
7648
|
<span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
|
|
7573
|
-
<details><summary
|
|
7649
|
+
<details><summary>${t.details}</summary><div class="finding-card-body">
|
|
7574
7650
|
<p>${esc(f.description)}</p>
|
|
7575
|
-
<p><strong
|
|
7576
|
-
<ol>${f.remediationSteps.map((s) => `<li>${
|
|
7651
|
+
<p><strong>${t.remediation}:</strong></p>
|
|
7652
|
+
<ol>${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
|
|
7577
7653
|
</div></details>
|
|
7578
7654
|
</div>`;
|
|
7579
7655
|
};
|
|
@@ -7584,7 +7660,7 @@ function generateHtmlReport(scanResults, history) {
|
|
|
7584
7660
|
const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
|
|
7585
7661
|
const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
|
|
7586
7662
|
return `${first}
|
|
7587
|
-
<details><summary
|
|
7663
|
+
<details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
|
|
7588
7664
|
${rest}
|
|
7589
7665
|
</details>`;
|
|
7590
7666
|
};
|
|
@@ -7636,13 +7712,13 @@ ${rest}
|
|
|
7636
7712
|
if (history && history.length >= 2) {
|
|
7637
7713
|
trendHtml = `
|
|
7638
7714
|
<section class="trend-section">
|
|
7639
|
-
<h2
|
|
7715
|
+
<h2>${esc(t.trendTitle)}</h2>
|
|
7640
7716
|
<div class="trend-chart">
|
|
7641
|
-
<div class="trend-title"
|
|
7717
|
+
<div class="trend-title">${esc(t.findingsBySeverity)}</div>
|
|
7642
7718
|
${findingsTrendChart(history)}
|
|
7643
7719
|
</div>
|
|
7644
7720
|
<div class="trend-chart">
|
|
7645
|
-
<div class="trend-title"
|
|
7721
|
+
<div class="trend-title">${esc(t.securityScore)}</div>
|
|
7646
7722
|
${scoreTrendChart(history)}
|
|
7647
7723
|
</div>
|
|
7648
7724
|
</section>`;
|
|
@@ -7653,16 +7729,57 @@ ${rest}
|
|
|
7653
7729
|
let recsHtml = "";
|
|
7654
7730
|
if (summary.totalFindings > 0) {
|
|
7655
7731
|
const recMap = /* @__PURE__ */ new Map();
|
|
7732
|
+
const kbPatches = [];
|
|
7733
|
+
let kbSeverity = "LOW";
|
|
7734
|
+
let kbUrl;
|
|
7735
|
+
const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
7656
7736
|
for (const f of allFindings) {
|
|
7657
7737
|
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
7738
|
+
const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
|
|
7739
|
+
if (genericPatterns.some((p) => rem.startsWith(p))) continue;
|
|
7740
|
+
const kbMatch = f.title.match(/KB\d+/);
|
|
7741
|
+
if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
|
|
7742
|
+
kbPatches.push(kbMatch[0]);
|
|
7743
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(kbSeverity)) kbSeverity = f.severity;
|
|
7744
|
+
if (!kbUrl && url) kbUrl = url;
|
|
7745
|
+
continue;
|
|
7746
|
+
}
|
|
7747
|
+
if (f.module === "security_hub_findings") {
|
|
7748
|
+
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
7749
|
+
if (controlMatch) {
|
|
7750
|
+
const controlId = controlMatch[1];
|
|
7751
|
+
const key = `ctrl:${controlId}`;
|
|
7752
|
+
const existing2 = recMap.get(key);
|
|
7753
|
+
if (existing2) {
|
|
7754
|
+
existing2.count++;
|
|
7755
|
+
if (!existing2.url && url) existing2.url = url;
|
|
7756
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
|
|
7757
|
+
} else {
|
|
7758
|
+
recMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
|
|
7759
|
+
}
|
|
7760
|
+
continue;
|
|
7761
|
+
}
|
|
7762
|
+
}
|
|
7658
7763
|
const existing = recMap.get(rem);
|
|
7659
7764
|
if (existing) {
|
|
7660
7765
|
existing.count++;
|
|
7766
|
+
if (!existing.url && url) existing.url = url;
|
|
7661
7767
|
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
|
|
7662
7768
|
existing.severity = f.severity;
|
|
7663
7769
|
}
|
|
7664
7770
|
} else {
|
|
7665
|
-
recMap.set(rem, { text: rem, severity: f.severity, count: 1 });
|
|
7771
|
+
recMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
|
|
7772
|
+
}
|
|
7773
|
+
}
|
|
7774
|
+
if (kbPatches.length > 0) {
|
|
7775
|
+
const unique = [...new Set(kbPatches)];
|
|
7776
|
+
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
7777
|
+
recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
|
|
7778
|
+
}
|
|
7779
|
+
for (const [key, rec] of recMap) {
|
|
7780
|
+
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
7781
|
+
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
7782
|
+
rec.count = 1;
|
|
7666
7783
|
}
|
|
7667
7784
|
}
|
|
7668
7785
|
const uniqueRecs = [...recMap.values()].sort((a, b) => {
|
|
@@ -7673,60 +7790,61 @@ ${rest}
|
|
|
7673
7790
|
const renderRec = (r) => {
|
|
7674
7791
|
const sev = r.severity.toLowerCase();
|
|
7675
7792
|
const countLabel = r.count > 1 ? ` (× ${r.count})` : "";
|
|
7676
|
-
|
|
7793
|
+
const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">📖</a>` : "";
|
|
7794
|
+
return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
|
|
7677
7795
|
};
|
|
7678
7796
|
const TOP_N = 10;
|
|
7679
7797
|
const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
|
|
7680
7798
|
const remaining = uniqueRecs.slice(TOP_N);
|
|
7681
7799
|
const moreHtml = remaining.length > 0 ? `
|
|
7682
|
-
<details><summary
|
|
7800
|
+
<details><summary>${t.showMoreCount(remaining.length)}</summary>
|
|
7683
7801
|
${remaining.map(renderRec).join("\n")}
|
|
7684
7802
|
</details>` : "";
|
|
7685
7803
|
recsHtml = `
|
|
7686
7804
|
<details class="rec-fold">
|
|
7687
|
-
<summary><h2 style="margin:0;border:0;display:inline"
|
|
7805
|
+
<summary><h2 style="margin:0;border:0;display:inline">${esc(t.recommendations)} (${uniqueRecs.length} ${esc(t.unique)})</h2></summary>
|
|
7688
7806
|
<div class="rec-body">
|
|
7689
7807
|
<ol>${topItems}${moreHtml}</ol>
|
|
7690
7808
|
</div>
|
|
7691
7809
|
</details>`;
|
|
7692
7810
|
}
|
|
7693
7811
|
return `<!DOCTYPE html>
|
|
7694
|
-
<html lang="
|
|
7812
|
+
<html lang="${htmlLang}">
|
|
7695
7813
|
<head>
|
|
7696
7814
|
<meta charset="UTF-8">
|
|
7697
7815
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7698
|
-
<title
|
|
7816
|
+
<title>${esc(t.securityReportTitle)} — ${esc(date)}</title>
|
|
7699
7817
|
<style>${sharedCss()}</style>
|
|
7700
7818
|
</head>
|
|
7701
7819
|
<body>
|
|
7702
7820
|
<div class="container">
|
|
7703
7821
|
|
|
7704
7822
|
<header>
|
|
7705
|
-
<h1>🛡️
|
|
7706
|
-
<div class="meta"
|
|
7823
|
+
<h1>🛡️ ${esc(t.securityReportTitle)}</h1>
|
|
7824
|
+
<div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(date)} | ${esc(t.duration)}: ${esc(duration)}</div>
|
|
7707
7825
|
</header>
|
|
7708
7826
|
|
|
7709
7827
|
<section class="summary">
|
|
7710
7828
|
<div class="score-card">
|
|
7711
7829
|
<div class="score-value" style="color:${scoreColor(score)}">${score}</div>
|
|
7712
|
-
<div class="score-label"
|
|
7830
|
+
<div class="score-label">${esc(t.securityScore)}</div>
|
|
7713
7831
|
</div>
|
|
7714
7832
|
<div class="severity-stats">
|
|
7715
|
-
<div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label"
|
|
7716
|
-
<div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label"
|
|
7717
|
-
<div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label"
|
|
7718
|
-
<div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label"
|
|
7833
|
+
<div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">${esc(t.critical)}</div></div>
|
|
7834
|
+
<div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">${esc(t.high)}</div></div>
|
|
7835
|
+
<div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">${esc(t.medium)}</div></div>
|
|
7836
|
+
<div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">${esc(t.low)}</div></div>
|
|
7719
7837
|
</div>
|
|
7720
7838
|
</section>
|
|
7721
7839
|
|
|
7722
7840
|
<section class="charts">
|
|
7723
7841
|
<div class="chart-box">
|
|
7724
|
-
<div class="chart-title"
|
|
7842
|
+
<div class="chart-title">${esc(t.severityDistribution)}</div>
|
|
7725
7843
|
<div style="text-align:center">${donutChart(summary)}</div>
|
|
7726
7844
|
</div>
|
|
7727
7845
|
<div class="chart-box">
|
|
7728
|
-
<div class="chart-title"
|
|
7729
|
-
${barChart(modules)}
|
|
7846
|
+
<div class="chart-title">${esc(t.findingsByModule)}</div>
|
|
7847
|
+
${barChart(modules, t.allModulesClean)}
|
|
7730
7848
|
</div>
|
|
7731
7849
|
</section>
|
|
7732
7850
|
|
|
@@ -7734,33 +7852,35 @@ ${trendHtml}
|
|
|
7734
7852
|
|
|
7735
7853
|
${top5Html}
|
|
7736
7854
|
|
|
7737
|
-
${buildServiceReminderHtml(modules)}
|
|
7855
|
+
${buildServiceReminderHtml(modules, lang)}
|
|
7738
7856
|
|
|
7739
7857
|
<section>
|
|
7740
|
-
<h2
|
|
7858
|
+
<h2>${esc(t.scanStatistics)}</h2>
|
|
7741
7859
|
<table>
|
|
7742
|
-
<thead><tr><th
|
|
7860
|
+
<thead><tr><th>${esc(t.module)}</th><th>${esc(t.resources)}</th><th>${esc(t.findings)}</th><th>${esc(t.status)}</th></tr></thead>
|
|
7743
7861
|
<tbody>${statsRows}</tbody>
|
|
7744
7862
|
</table>
|
|
7745
7863
|
</section>
|
|
7746
7864
|
|
|
7747
7865
|
<section>
|
|
7748
|
-
<h2
|
|
7866
|
+
<h2>${esc(t.allFindings)}</h2>
|
|
7749
7867
|
${findingsHtml}
|
|
7750
7868
|
</section>
|
|
7751
7869
|
|
|
7752
7870
|
${recsHtml}
|
|
7753
7871
|
|
|
7754
7872
|
<footer>
|
|
7755
|
-
<p
|
|
7756
|
-
<p
|
|
7873
|
+
<p>${esc(t.generatedBy)} v${VERSION}</p>
|
|
7874
|
+
<p>${esc(t.informationalOnly)}</p>
|
|
7757
7875
|
</footer>
|
|
7758
7876
|
|
|
7759
7877
|
</div>
|
|
7760
7878
|
</body>
|
|
7761
7879
|
</html>`;
|
|
7762
7880
|
}
|
|
7763
|
-
function generateMlps3HtmlReport(scanResults, history) {
|
|
7881
|
+
function generateMlps3HtmlReport(scanResults, history, lang) {
|
|
7882
|
+
const t = getI18n(lang ?? "zh");
|
|
7883
|
+
const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
|
|
7764
7884
|
const { accountId, region, scanStart } = scanResults;
|
|
7765
7885
|
const date = scanStart.split("T")[0];
|
|
7766
7886
|
const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
|
|
@@ -7777,17 +7897,21 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
7777
7897
|
if (history && history.length >= 2) {
|
|
7778
7898
|
trendHtml = `
|
|
7779
7899
|
<section class="trend-section">
|
|
7780
|
-
<h2
|
|
7900
|
+
<h2>${esc(t.trendTitle)}</h2>
|
|
7781
7901
|
<div class="trend-chart">
|
|
7782
|
-
<div class="trend-title"
|
|
7902
|
+
<div class="trend-title">${esc(t.findingsBySeverity)}</div>
|
|
7783
7903
|
${findingsTrendChart(history)}
|
|
7784
7904
|
</div>
|
|
7785
7905
|
<div class="trend-chart">
|
|
7786
|
-
<div class="trend-title"
|
|
7906
|
+
<div class="trend-title">${esc(t.securityScore)}</div>
|
|
7787
7907
|
${scoreTrendChart(history)}
|
|
7788
7908
|
</div>
|
|
7789
7909
|
</section>`;
|
|
7790
7910
|
}
|
|
7911
|
+
const isEn = (lang ?? "zh") === "en";
|
|
7912
|
+
const itemCat = (r) => isEn ? r.item.categoryEn : r.item.categoryCn;
|
|
7913
|
+
const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
|
|
7914
|
+
const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
|
|
7791
7915
|
const categoryMap = /* @__PURE__ */ new Map();
|
|
7792
7916
|
for (const r of results) {
|
|
7793
7917
|
if (r.status === "not_applicable") continue;
|
|
@@ -7796,7 +7920,7 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
7796
7920
|
categoryMap.get(cat).push(r);
|
|
7797
7921
|
}
|
|
7798
7922
|
const categorySections = MLPS3_CATEGORY_ORDER.map((category) => {
|
|
7799
|
-
const sectionTitle =
|
|
7923
|
+
const sectionTitle = t.mlpsCategorySection[category] ?? category;
|
|
7800
7924
|
const catResults = categoryMap.get(category);
|
|
7801
7925
|
if (!catResults || catResults.length === 0) return "";
|
|
7802
7926
|
const allCloud = catResults.every((r) => r.status === "cloud_provider");
|
|
@@ -7804,11 +7928,11 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
7804
7928
|
return `<details class="category-fold mlps-cloud-section">
|
|
7805
7929
|
<summary>
|
|
7806
7930
|
<span class="category-title">${esc(sectionTitle)}</span>
|
|
7807
|
-
<span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length}
|
|
7931
|
+
<span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length} ${esc(t.cloudProvider)}</span></span>
|
|
7808
7932
|
</summary>
|
|
7809
7933
|
<div class="category-body">
|
|
7810
|
-
<div class="mlps-cloud-note"
|
|
7811
|
-
${catResults.map((r) => `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(r
|
|
7934
|
+
<div class="mlps-cloud-note">${esc(t.cloudItemsNote(catResults.length))}</div>
|
|
7935
|
+
${catResults.map((r) => `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(itemControl(r))}</span><span class="check-note">${esc(r.mapping.note ?? "")}</span></div>`).join("\n")}
|
|
7812
7936
|
</div>
|
|
7813
7937
|
</details>`;
|
|
7814
7938
|
}
|
|
@@ -7830,31 +7954,34 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
7830
7954
|
if (!controlMap.has(key)) controlMap.set(key, []);
|
|
7831
7955
|
controlMap.get(key).push(r);
|
|
7832
7956
|
}
|
|
7833
|
-
const controlGroups = [...controlMap.entries()].map(([
|
|
7957
|
+
const controlGroups = [...controlMap.entries()].map(([_controlKey, controlResults]) => {
|
|
7958
|
+
const controlName = itemControl(controlResults[0]);
|
|
7834
7959
|
const cloudItems = controlResults.filter((r) => r.status === "cloud_provider");
|
|
7835
7960
|
const nonCloudItems = controlResults.filter((r) => r.status !== "cloud_provider");
|
|
7836
7961
|
let itemsHtml = "";
|
|
7837
7962
|
for (const r of nonCloudItems) {
|
|
7838
7963
|
const icon = r.status === "clean" ? "\u{1F7E2}" : r.status === "issues" ? "\u{1F534}" : r.status === "unknown" ? "\u2B1C" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
|
|
7839
7964
|
const cls = `check-${r.status === "cloud_provider" ? "cloud" : r.status}`;
|
|
7840
|
-
const suffix = r.status === "unknown" ?
|
|
7965
|
+
const suffix = r.status === "unknown" ? ` \u2014 ${esc(t.notChecked)}` : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? t.manualReview)}` : "";
|
|
7841
7966
|
let findingsDetail = "";
|
|
7842
7967
|
if (r.status === "clean") {
|
|
7843
|
-
findingsDetail = `<div class="check-detail"
|
|
7968
|
+
findingsDetail = `<div class="check-detail">${esc(t.noRelatedIssues)}</div>`;
|
|
7844
7969
|
} else if (r.status === "issues" && r.relatedFindings.length > 0) {
|
|
7845
7970
|
const fItems = r.relatedFindings.slice(0, 5).map((f) => `<li>${esc(f.severity)}: ${esc(f.title)}</li>`);
|
|
7846
7971
|
if (r.relatedFindings.length > 5) {
|
|
7847
|
-
fItems.push(`<li
|
|
7972
|
+
fItems.push(`<li>${esc(t.andMore(r.relatedFindings.length - 5))}</li>`);
|
|
7848
7973
|
}
|
|
7849
|
-
const remediationHint = r.relatedFindings[0]?.remediationSteps?.[0] ? `<p style="color:#fbbf24;font-size:12px;margin-top:4px"
|
|
7850
|
-
findingsDetail = `<div class="check-findings-wrap"><details><summary
|
|
7974
|
+
const remediationHint = r.relatedFindings[0]?.remediationSteps?.[0] ? `<p style="color:#fbbf24;font-size:12px;margin-top:4px">${esc(t.remediation)}\uFF1A${escWithLinks(r.relatedFindings[0].remediationSteps[0])}</p>` : "";
|
|
7975
|
+
findingsDetail = `<div class="check-findings-wrap"><details><summary>${esc(t.issuesFoundCount(r.relatedFindings.length))}</summary><ul class="check-findings">${fItems.join("")}</ul>${remediationHint}</details></div>`;
|
|
7851
7976
|
}
|
|
7852
|
-
|
|
7977
|
+
const reqText = itemReq(r);
|
|
7978
|
+
itemsHtml += `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc(r.item.id)} ${esc(reqText.slice(0, 60))}${reqText.length > 60 ? "\u2026" : ""}${suffix}</span></div>
|
|
7853
7979
|
${findingsDetail}`;
|
|
7854
7980
|
}
|
|
7855
7981
|
if (cloudItems.length > 0) {
|
|
7856
7982
|
for (const r of cloudItems) {
|
|
7857
|
-
|
|
7983
|
+
const reqText = itemReq(r);
|
|
7984
|
+
itemsHtml += `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(reqText.slice(0, 50))}${reqText.length > 50 ? "\u2026" : ""}</span><span class="check-note">${esc(t.cloudProvider)}</span></div>
|
|
7858
7985
|
`;
|
|
7859
7986
|
}
|
|
7860
7987
|
}
|
|
@@ -7887,20 +8014,61 @@ ${itemsHtml}
|
|
|
7887
8014
|
let remediationHtml = "";
|
|
7888
8015
|
if (failedResults.length > 0) {
|
|
7889
8016
|
const mlpsRecMap = /* @__PURE__ */ new Map();
|
|
8017
|
+
const mlpsKbPatches = [];
|
|
8018
|
+
let mlpsKbSeverity = "LOW";
|
|
8019
|
+
let mlpsKbUrl;
|
|
8020
|
+
const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
7890
8021
|
for (const r of failedResults) {
|
|
7891
8022
|
for (const f of r.relatedFindings) {
|
|
7892
8023
|
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
8024
|
+
const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
|
|
8025
|
+
if (mlpsGenericPatterns.some((p) => rem.startsWith(p))) continue;
|
|
8026
|
+
const kbMatch = f.title.match(/KB\d+/);
|
|
8027
|
+
if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
|
|
8028
|
+
mlpsKbPatches.push(kbMatch[0]);
|
|
8029
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsKbSeverity)) mlpsKbSeverity = f.severity;
|
|
8030
|
+
if (!mlpsKbUrl && url) mlpsKbUrl = url;
|
|
8031
|
+
continue;
|
|
8032
|
+
}
|
|
8033
|
+
if (f.module === "security_hub_findings") {
|
|
8034
|
+
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
8035
|
+
if (controlMatch) {
|
|
8036
|
+
const controlId = controlMatch[1];
|
|
8037
|
+
const key = `ctrl:${controlId}`;
|
|
8038
|
+
const existing2 = mlpsRecMap.get(key);
|
|
8039
|
+
if (existing2) {
|
|
8040
|
+
existing2.count++;
|
|
8041
|
+
if (!existing2.url && url) existing2.url = url;
|
|
8042
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
|
|
8043
|
+
} else {
|
|
8044
|
+
mlpsRecMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
|
|
8045
|
+
}
|
|
8046
|
+
continue;
|
|
8047
|
+
}
|
|
8048
|
+
}
|
|
7893
8049
|
const existing = mlpsRecMap.get(rem);
|
|
7894
8050
|
if (existing) {
|
|
7895
8051
|
existing.count++;
|
|
8052
|
+
if (!existing.url && url) existing.url = url;
|
|
7896
8053
|
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
|
|
7897
8054
|
existing.severity = f.severity;
|
|
7898
8055
|
}
|
|
7899
8056
|
} else {
|
|
7900
|
-
mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
|
|
8057
|
+
mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
|
|
7901
8058
|
}
|
|
7902
8059
|
}
|
|
7903
8060
|
}
|
|
8061
|
+
if (mlpsKbPatches.length > 0) {
|
|
8062
|
+
const unique = [...new Set(mlpsKbPatches)];
|
|
8063
|
+
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
8064
|
+
mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
|
|
8065
|
+
}
|
|
8066
|
+
for (const [key, rec] of mlpsRecMap) {
|
|
8067
|
+
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
8068
|
+
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
8069
|
+
rec.count = 1;
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
7904
8072
|
const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
|
|
7905
8073
|
const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
|
|
7906
8074
|
if (sevDiff !== 0) return sevDiff;
|
|
@@ -7910,26 +8078,27 @@ ${itemsHtml}
|
|
|
7910
8078
|
const renderMlpsRec = (r) => {
|
|
7911
8079
|
const sev = r.severity.toLowerCase();
|
|
7912
8080
|
const countLabel = r.count > 1 ? ` (× ${r.count})` : "";
|
|
7913
|
-
|
|
8081
|
+
const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">📖</a>` : "";
|
|
8082
|
+
return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
|
|
7914
8083
|
};
|
|
7915
8084
|
const MLPS_TOP_N = 10;
|
|
7916
8085
|
const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
|
|
7917
8086
|
const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
|
|
7918
8087
|
const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
|
|
7919
|
-
<details><summary
|
|
8088
|
+
<details><summary>${esc(t.showRemaining(mlpsRemaining.length))}…</summary>
|
|
7920
8089
|
${mlpsRemaining.map(renderMlpsRec).join("\n")}
|
|
7921
8090
|
</details>` : "";
|
|
7922
8091
|
remediationHtml = `
|
|
7923
8092
|
<details class="rec-fold" open>
|
|
7924
|
-
<summary><h2 style="margin:0;border:0;display:inline"
|
|
8093
|
+
<summary><h2 style="margin:0;border:0;display:inline">${esc(t.remediationItems(mlpsUniqueRecs.length))}</h2></summary>
|
|
7925
8094
|
<div class="rec-body">
|
|
7926
8095
|
<ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
|
|
7927
8096
|
</div>
|
|
7928
8097
|
</details>`;
|
|
7929
8098
|
}
|
|
7930
8099
|
}
|
|
7931
|
-
const naNote = naCount > 0 ? `<p style="color:#64748b;font-size:13px;margin-top:24px"
|
|
7932
|
-
const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px"
|
|
8100
|
+
const naNote = naCount > 0 ? `<p style="color:#64748b;font-size:13px;margin-top:24px">${esc(t.naNote(naCount))}</p>` : "";
|
|
8101
|
+
const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">${esc(t.unknownNote(autoUnknown))}</div>` : "";
|
|
7933
8102
|
const mlpsCss = `
|
|
7934
8103
|
.mlps-cloud-section>summary{color:#94a3b8}
|
|
7935
8104
|
.mlps-cloud-note{color:#94a3b8;font-size:13px;margin-bottom:12px;font-style:italic}
|
|
@@ -7951,40 +8120,40 @@ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
|
|
|
7951
8120
|
.mlps-summary-card .stat-label{font-size:12px;color:#94a3b8;margin-top:2px}
|
|
7952
8121
|
`;
|
|
7953
8122
|
return `<!DOCTYPE html>
|
|
7954
|
-
<html lang="
|
|
8123
|
+
<html lang="${htmlLang}">
|
|
7955
8124
|
<head>
|
|
7956
8125
|
<meta charset="UTF-8">
|
|
7957
8126
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7958
|
-
<title
|
|
8127
|
+
<title>${esc(t.mlpsTitle)} — ${esc(date)}</title>
|
|
7959
8128
|
<style>${sharedCss()}${mlpsCss}</style>
|
|
7960
8129
|
</head>
|
|
7961
8130
|
<body>
|
|
7962
8131
|
<div class="container">
|
|
7963
8132
|
|
|
7964
8133
|
<header>
|
|
7965
|
-
<h1>🛡️
|
|
7966
|
-
<div class="disclaimer"
|
|
7967
|
-
<div class="meta"
|
|
8134
|
+
<h1>🛡️ ${esc(t.mlpsTitle)}</h1>
|
|
8135
|
+
<div class="disclaimer">${esc(t.mlpsDisclaimer)}</div>
|
|
8136
|
+
<div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(t.scanTime)}: ${esc(scanTime)}</div>
|
|
7968
8137
|
</header>
|
|
7969
8138
|
|
|
7970
8139
|
<section class="summary" style="display:block;text-align:center">
|
|
7971
8140
|
<div style="font-size:36px;font-weight:700;margin-bottom:12px">
|
|
7972
|
-
<span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px"
|
|
8141
|
+
<span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.noIssues)}</span>
|
|
7973
8142
|
<span style="color:#475569;margin:0 16px">/</span>
|
|
7974
|
-
<span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px"
|
|
8143
|
+
<span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.issuesFound)}</span>
|
|
7975
8144
|
</div>
|
|
7976
8145
|
<div class="mlps-summary-cards" style="justify-content:center">
|
|
7977
|
-
<div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label"
|
|
7978
|
-
<div class="mlps-summary-card"><div class="stat-count" style="color:#94a3b8">${cloudCount}</div><div class="stat-label">\u{1F3E2}
|
|
7979
|
-
<div class="mlps-summary-card"><div class="stat-count" style="color:#eab308">${manualCount}</div><div class="stat-label">\u{1F4CB}
|
|
7980
|
-
${naCount > 0 ? `<div class="mlps-summary-card"><div class="stat-count" style="color:#64748b">${naCount}</div><div class="stat-label">\u2796
|
|
8146
|
+
<div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">${esc(t.checkedItems)}</div></div>
|
|
8147
|
+
<div class="mlps-summary-card"><div class="stat-count" style="color:#94a3b8">${cloudCount}</div><div class="stat-label">\u{1F3E2} ${esc(t.cloudProvider)}</div></div>
|
|
8148
|
+
<div class="mlps-summary-card"><div class="stat-count" style="color:#eab308">${manualCount}</div><div class="stat-label">\u{1F4CB} ${esc(t.manualReview)}</div></div>
|
|
8149
|
+
${naCount > 0 ? `<div class="mlps-summary-card"><div class="stat-count" style="color:#64748b">${naCount}</div><div class="stat-label">\u2796 ${esc(t.notApplicable)}</div></div>` : ""}
|
|
7981
8150
|
</div>
|
|
7982
8151
|
</section>
|
|
7983
8152
|
${unknownNote}
|
|
7984
8153
|
|
|
7985
8154
|
${trendHtml}
|
|
7986
8155
|
|
|
7987
|
-
${buildServiceReminderHtml(scanResults.modules)}
|
|
8156
|
+
${buildServiceReminderHtml(scanResults.modules, lang)}
|
|
7988
8157
|
|
|
7989
8158
|
${categorySections}
|
|
7990
8159
|
|
|
@@ -7993,8 +8162,8 @@ ${remediationHtml}
|
|
|
7993
8162
|
${naNote}
|
|
7994
8163
|
|
|
7995
8164
|
<footer>
|
|
7996
|
-
<p
|
|
7997
|
-
<p
|
|
8165
|
+
<p>${esc(t.mlpsFooterGenerated(VERSION))}</p>
|
|
8166
|
+
<p>${esc(t.mlpsFooterDisclaimer)}</p>
|
|
7998
8167
|
</footer>
|
|
7999
8168
|
|
|
8000
8169
|
</div>
|
|
@@ -8209,16 +8378,14 @@ Aggregates active findings from AWS Security Hub. Replaces individual config sca
|
|
|
8209
8378
|
- INFORMATIONAL findings are skipped.
|
|
8210
8379
|
|
|
8211
8380
|
## 3. GuardDuty Findings (guardduty_findings)
|
|
8212
|
-
|
|
8213
|
-
-
|
|
8214
|
-
-
|
|
8215
|
-
- Only non-archived findings are included.
|
|
8381
|
+
Detection-only: checks if GuardDuty is enabled in the region.
|
|
8382
|
+
- GuardDuty findings are aggregated via Security Hub (security_hub_findings module).
|
|
8383
|
+
- Reports whether GuardDuty detectors are active.
|
|
8216
8384
|
|
|
8217
8385
|
## 4. Inspector Findings (inspector_findings)
|
|
8218
|
-
|
|
8219
|
-
-
|
|
8220
|
-
-
|
|
8221
|
-
- CVE IDs are included in finding titles when available.
|
|
8386
|
+
Detection-only: checks if Inspector is enabled in the region.
|
|
8387
|
+
- Inspector findings are aggregated via Security Hub (security_hub_findings module).
|
|
8388
|
+
- Reports whether Inspector scanning (EC2/Lambda) is active.
|
|
8222
8389
|
|
|
8223
8390
|
## 5. Trusted Advisor Findings (trusted_advisor_findings)
|
|
8224
8391
|
Aggregates security checks from AWS Trusted Advisor.
|
|
@@ -8254,19 +8421,15 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
|
|
|
8254
8421
|
Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
|
|
8255
8422
|
|
|
8256
8423
|
## 15. Config Rules Findings (config_rules_findings)
|
|
8257
|
-
|
|
8258
|
-
-
|
|
8259
|
-
-
|
|
8260
|
-
- Security-related rules (encryption, IAM, public access, etc.) mapped to HIGH severity (7.5).
|
|
8261
|
-
- Other non-compliant rules mapped to MEDIUM severity (5.5).
|
|
8424
|
+
Detection-only: checks if AWS Config Rules are configured.
|
|
8425
|
+
- Config Rule compliance findings are aggregated via Security Hub (security_hub_findings module).
|
|
8426
|
+
- Reports whether Config is enabled and counts active rules.
|
|
8262
8427
|
- Gracefully handles regions where AWS Config is not enabled.
|
|
8263
8428
|
|
|
8264
8429
|
## 16. IAM Access Analyzer Findings (access_analyzer_findings)
|
|
8265
|
-
|
|
8266
|
-
-
|
|
8267
|
-
-
|
|
8268
|
-
- Covers S3 buckets, IAM roles, SQS queues, Lambda functions, KMS keys, and more.
|
|
8269
|
-
- Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
|
|
8430
|
+
Detection-only: checks if IAM Access Analyzer is configured.
|
|
8431
|
+
- Access Analyzer findings are aggregated via Security Hub (security_hub_findings module).
|
|
8432
|
+
- Reports whether active analyzers exist.
|
|
8270
8433
|
- Returns warning if no analyzer is configured.
|
|
8271
8434
|
|
|
8272
8435
|
## 17. SSM Patch Compliance (patch_compliance_findings)
|
|
@@ -8351,112 +8514,18 @@ var MODULE_DESCRIPTIONS = {
|
|
|
8351
8514
|
idle_resources: "Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped instances, unused security groups) that waste money and increase attack surface.",
|
|
8352
8515
|
disaster_recovery: "Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.",
|
|
8353
8516
|
security_hub_findings: "Aggregates active findings from AWS Security Hub \u2014 replaces individual config scanners with centralized compliance checks.",
|
|
8354
|
-
guardduty_findings: "
|
|
8355
|
-
inspector_findings: "
|
|
8517
|
+
guardduty_findings: "Checks if GuardDuty is enabled. Findings are aggregated via Security Hub.",
|
|
8518
|
+
inspector_findings: "Checks if Inspector is enabled. Findings are aggregated via Security Hub.",
|
|
8356
8519
|
trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
|
|
8357
|
-
config_rules_findings: "
|
|
8358
|
-
access_analyzer_findings: "
|
|
8520
|
+
config_rules_findings: "Checks if AWS Config Rules are configured. Findings are aggregated via Security Hub.",
|
|
8521
|
+
access_analyzer_findings: "Checks if IAM Access Analyzer is configured. Findings are aggregated via Security Hub.",
|
|
8359
8522
|
patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches.",
|
|
8360
8523
|
imdsv2_enforcement: "Checks if EC2 instances enforce IMDSv2 (HttpTokens: required) \u2014 IMDSv1 allows credential theft via SSRF.",
|
|
8361
8524
|
waf_coverage: "Checks if internet-facing ALBs have WAF Web ACL associated for protection against common web exploits."
|
|
8362
8525
|
};
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
8367
|
-
|
|
8368
|
-
\u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
|
|
8369
|
-
|
|
8370
|
-
\u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
|
|
8371
|
-
\u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
|
|
8372
|
-
\u25A1 \u5236\u5B9A\u5B9E\u4F8B\u9694\u79BB SOP\uFF1A\u544A\u8B66 \u2192 \u6392\u67E5 \u2192 \u5C01\u9501\u653B\u51FBIP \u2192 \u7F51\u7EDC\u9694\u79BB \u2192 \u5B89\u5168\u5904\u7F6E \u2192 \u8BB0\u5F55\u653B\u51FB\u9879
|
|
8373
|
-
\u25A1 \u660E\u786E\u5404\u7CFB\u7EDF\uFF08\u751F\u4EA7\u6838\u5FC3/\u751F\u4EA7\u975E\u6838\u5FC3/\u6D4B\u8BD5/\u5F00\u53D1\uFF09\u7684\u5E94\u6025\u5904\u7F6E\u65B9\u5F0F
|
|
8374
|
-
\u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
|
|
8375
|
-
|
|
8376
|
-
\u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
|
|
8377
|
-
\u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
|
|
8378
|
-
\u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
|
|
8379
|
-
\u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
|
|
8380
|
-
|
|
8381
|
-
\u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
|
|
8382
|
-
\u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
|
|
8383
|
-
\u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
|
|
8384
|
-
\u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
|
|
8385
|
-
\u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
|
|
8386
|
-
\u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
|
|
8387
|
-
|
|
8388
|
-
\u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
|
|
8389
|
-
\u25A1 \u786E\u4FDD\u6240\u6709\u4E92\u8054\u7F51/DX \u4E13\u7EBF\u51FA\u5165\u7AD9\u8DEF\u5F84\u5728\u67B6\u6784\u56FE\u4E2D\u6E05\u6670\u6807\u6CE8
|
|
8390
|
-
\u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
|
|
8391
|
-
\u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
|
|
8392
|
-
|
|
8393
|
-
\u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
|
|
8394
|
-
\u25A1 \u62A4\u7F51\u524D\u8054\u7CFB\u5B89\u5168\u5382\u5546\uFF08\u9752\u85E4/\u957F\u4EAD/\u5FAE\u6B65\u7B49\uFF09\u8FDB\u884C\u6A21\u62DF\u653B\u51FB\u6F14\u7EC3
|
|
8395
|
-
\u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
|
|
8396
|
-
\u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
|
|
8397
|
-
|
|
8398
|
-
\u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
|
|
8399
|
-
\u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
|
|
8400
|
-
\u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
|
|
8401
|
-
\u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A"\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0"
|
|
8402
|
-
|
|
8403
|
-
\u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
|
|
8404
|
-
\u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
|
|
8405
|
-
\u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
|
|
8406
|
-
\u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
|
|
8407
|
-
\u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
|
|
8408
|
-
|
|
8409
|
-
\u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
|
|
8410
|
-
\u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
|
|
8411
|
-
\u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
|
|
8412
|
-
\u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
|
|
8413
|
-
|
|
8414
|
-
\u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
|
|
8415
|
-
`;
|
|
8416
|
-
var SERVICE_RECOMMENDATIONS2 = {
|
|
8417
|
-
security_hub_findings: {
|
|
8418
|
-
icon: "\u{1F534}",
|
|
8419
|
-
service: "Security Hub",
|
|
8420
|
-
impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
|
|
8421
|
-
action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
|
|
8422
|
-
},
|
|
8423
|
-
guardduty_findings: {
|
|
8424
|
-
icon: "\u{1F534}",
|
|
8425
|
-
service: "GuardDuty",
|
|
8426
|
-
impact: "\u65E0\u6CD5\u68C0\u6D4B\u5A01\u80C1\u6D3B\u52A8\uFF08\u6076\u610F IP\u3001\u5F02\u5E38 API \u8C03\u7528\u3001\u52A0\u5BC6\u8D27\u5E01\u6316\u77FF\u7B49\uFF09",
|
|
8427
|
-
action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
|
|
8428
|
-
},
|
|
8429
|
-
inspector_findings: {
|
|
8430
|
-
icon: "\u{1F7E1}",
|
|
8431
|
-
service: "Inspector",
|
|
8432
|
-
impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
|
|
8433
|
-
action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
|
|
8434
|
-
},
|
|
8435
|
-
trusted_advisor_findings: {
|
|
8436
|
-
icon: "\u{1F7E1}",
|
|
8437
|
-
service: "Trusted Advisor",
|
|
8438
|
-
impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
|
|
8439
|
-
action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
|
|
8440
|
-
},
|
|
8441
|
-
config_rules_findings: {
|
|
8442
|
-
icon: "\u{1F7E1}",
|
|
8443
|
-
service: "AWS Config",
|
|
8444
|
-
impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
|
|
8445
|
-
action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
|
|
8446
|
-
},
|
|
8447
|
-
access_analyzer_findings: {
|
|
8448
|
-
icon: "\u{1F7E1}",
|
|
8449
|
-
service: "IAM Access Analyzer",
|
|
8450
|
-
impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
|
|
8451
|
-
action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
|
|
8452
|
-
},
|
|
8453
|
-
patch_compliance_findings: {
|
|
8454
|
-
icon: "\u{1F7E1}",
|
|
8455
|
-
service: "SSM Patch Manager",
|
|
8456
|
-
impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
|
|
8457
|
-
action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
|
|
8458
|
-
}
|
|
8459
|
-
};
|
|
8526
|
+
function getHwDefenseChecklist(lang) {
|
|
8527
|
+
return getI18n(lang ?? "zh").hwChecklist;
|
|
8528
|
+
}
|
|
8460
8529
|
var SERVICE_NOT_ENABLED_PATTERNS2 = [
|
|
8461
8530
|
"not enabled",
|
|
8462
8531
|
"not found",
|
|
@@ -8466,10 +8535,11 @@ var SERVICE_NOT_ENABLED_PATTERNS2 = [
|
|
|
8466
8535
|
"not available",
|
|
8467
8536
|
"is not enabled"
|
|
8468
8537
|
];
|
|
8469
|
-
function buildServiceReminder(modules) {
|
|
8538
|
+
function buildServiceReminder(modules, lang) {
|
|
8539
|
+
const t = getI18n(lang ?? "zh");
|
|
8470
8540
|
const disabledServices = [];
|
|
8471
8541
|
for (const mod of modules) {
|
|
8472
|
-
const rec =
|
|
8542
|
+
const rec = t.serviceRecommendations[mod.module];
|
|
8473
8543
|
if (!rec) continue;
|
|
8474
8544
|
if (!mod.warnings?.length) continue;
|
|
8475
8545
|
const hasNotEnabled = mod.warnings.some(
|
|
@@ -8482,26 +8552,26 @@ function buildServiceReminder(modules) {
|
|
|
8482
8552
|
if (disabledServices.length === 0) return "";
|
|
8483
8553
|
const lines = [
|
|
8484
8554
|
"",
|
|
8485
|
-
|
|
8555
|
+
t.serviceReminderTitle,
|
|
8486
8556
|
""
|
|
8487
8557
|
];
|
|
8488
8558
|
for (const svc of disabledServices) {
|
|
8489
|
-
lines.push(`${svc.icon} ${svc.service}
|
|
8490
|
-
lines.push(`
|
|
8491
|
-
lines.push(`
|
|
8559
|
+
lines.push(`${svc.icon} ${svc.service} ${t.notEnabled}`);
|
|
8560
|
+
lines.push(` ${t.serviceImpact}: ${svc.impact}`);
|
|
8561
|
+
lines.push(` ${t.serviceAction}: ${svc.action}`);
|
|
8492
8562
|
lines.push("");
|
|
8493
8563
|
}
|
|
8494
|
-
lines.push(
|
|
8564
|
+
lines.push(t.serviceReminderFooter);
|
|
8495
8565
|
return lines.join("\n");
|
|
8496
8566
|
}
|
|
8497
|
-
function summarizeResult(result) {
|
|
8567
|
+
function summarizeResult(result, lang) {
|
|
8498
8568
|
const { summary } = result;
|
|
8499
8569
|
const lines = [
|
|
8500
8570
|
`Scan complete for account ${result.accountId} in ${result.region}.`,
|
|
8501
8571
|
`Total findings: ${summary.totalFindings} (${summary.critical} Critical, ${summary.high} High, ${summary.medium} Medium, ${summary.low} Low)`,
|
|
8502
8572
|
`Modules: ${summary.modulesSuccess} succeeded, ${summary.modulesError} errored`
|
|
8503
8573
|
];
|
|
8504
|
-
const reminder = buildServiceReminder(result.modules);
|
|
8574
|
+
const reminder = buildServiceReminder(result.modules, lang);
|
|
8505
8575
|
if (reminder) {
|
|
8506
8576
|
lines.push(reminder);
|
|
8507
8577
|
}
|
|
@@ -8563,9 +8633,10 @@ function createServer(defaultRegion) {
|
|
|
8563
8633
|
region: z.string().optional().describe("AWS region to scan (default: server region)"),
|
|
8564
8634
|
org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
|
|
8565
8635
|
role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
|
|
8566
|
-
account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)")
|
|
8636
|
+
account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
|
|
8637
|
+
lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
|
|
8567
8638
|
},
|
|
8568
|
-
async ({ region, org_mode, role_name, account_ids }) => {
|
|
8639
|
+
async ({ region, org_mode, role_name, account_ids, lang }) => {
|
|
8569
8640
|
try {
|
|
8570
8641
|
const r = region ?? defaultRegion;
|
|
8571
8642
|
let result;
|
|
@@ -8580,7 +8651,7 @@ function createServer(defaultRegion) {
|
|
|
8580
8651
|
}
|
|
8581
8652
|
return {
|
|
8582
8653
|
content: [
|
|
8583
|
-
{ type: "text", text: summarizeResult(result) },
|
|
8654
|
+
{ type: "text", text: summarizeResult(result, lang ?? "zh") },
|
|
8584
8655
|
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
8585
8656
|
]
|
|
8586
8657
|
};
|
|
@@ -8641,9 +8712,10 @@ function createServer(defaultRegion) {
|
|
|
8641
8712
|
region: z.string().optional().describe("AWS region to scan (default: server region)"),
|
|
8642
8713
|
org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
|
|
8643
8714
|
role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
|
|
8644
|
-
account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)")
|
|
8715
|
+
account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
|
|
8716
|
+
lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
|
|
8645
8717
|
},
|
|
8646
|
-
async ({ group, region, org_mode, role_name, account_ids }) => {
|
|
8718
|
+
async ({ group, region, org_mode, role_name, account_ids, lang }) => {
|
|
8647
8719
|
try {
|
|
8648
8720
|
const groupDef = SCAN_GROUPS[group];
|
|
8649
8721
|
if (!groupDef) {
|
|
@@ -8725,7 +8797,7 @@ function createServer(defaultRegion) {
|
|
|
8725
8797
|
`Scan group: ${groupDef.name} (${group})`,
|
|
8726
8798
|
groupDef.description,
|
|
8727
8799
|
"",
|
|
8728
|
-
summarizeResult(result)
|
|
8800
|
+
summarizeResult(result, lang ?? "zh")
|
|
8729
8801
|
];
|
|
8730
8802
|
if (missingModules.length > 0) {
|
|
8731
8803
|
lines.push("");
|
|
@@ -8738,7 +8810,7 @@ function createServer(defaultRegion) {
|
|
|
8738
8810
|
if (group === "hw_defense") {
|
|
8739
8811
|
const summaryContent = content[0];
|
|
8740
8812
|
if (summaryContent && summaryContent.type === "text") {
|
|
8741
|
-
summaryContent.text += "\n\n" +
|
|
8813
|
+
summaryContent.text += "\n\n" + getHwDefenseChecklist(lang ?? "zh");
|
|
8742
8814
|
}
|
|
8743
8815
|
}
|
|
8744
8816
|
return { content };
|
|
@@ -8768,11 +8840,14 @@ function createServer(defaultRegion) {
|
|
|
8768
8840
|
server.tool(
|
|
8769
8841
|
"generate_report",
|
|
8770
8842
|
"Generate a Markdown security report from scan results. Read-only. Does not modify any AWS resources.",
|
|
8771
|
-
{
|
|
8772
|
-
|
|
8843
|
+
{
|
|
8844
|
+
scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
|
|
8845
|
+
lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
|
|
8846
|
+
},
|
|
8847
|
+
async ({ scan_results, lang }) => {
|
|
8773
8848
|
try {
|
|
8774
8849
|
const parsed = JSON.parse(scan_results);
|
|
8775
|
-
const report = generateMarkdownReport(parsed);
|
|
8850
|
+
const report = generateMarkdownReport(parsed, lang ?? "zh");
|
|
8776
8851
|
return { content: [{ type: "text", text: report }] };
|
|
8777
8852
|
} catch (err) {
|
|
8778
8853
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
@@ -8782,11 +8857,14 @@ function createServer(defaultRegion) {
|
|
|
8782
8857
|
server.tool(
|
|
8783
8858
|
"generate_mlps3_report",
|
|
8784
8859
|
"Generate a GB/T 22239-2019 \u7B49\u4FDD\u4E09\u7EA7 compliance pre-check report from scan results. Best used with scan_group mlps3_precheck results. Read-only.",
|
|
8785
|
-
{
|
|
8786
|
-
|
|
8860
|
+
{
|
|
8861
|
+
scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
|
|
8862
|
+
lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
|
|
8863
|
+
},
|
|
8864
|
+
async ({ scan_results, lang }) => {
|
|
8787
8865
|
try {
|
|
8788
8866
|
const parsed = JSON.parse(scan_results);
|
|
8789
|
-
const report = generateMlps3Report(parsed);
|
|
8867
|
+
const report = generateMlps3Report(parsed, lang ?? "zh");
|
|
8790
8868
|
return { content: [{ type: "text", text: report }] };
|
|
8791
8869
|
} catch (err) {
|
|
8792
8870
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
@@ -8798,13 +8876,14 @@ function createServer(defaultRegion) {
|
|
|
8798
8876
|
"Generate a professional HTML security report. Save the output as an .html file.",
|
|
8799
8877
|
{
|
|
8800
8878
|
scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
|
|
8801
|
-
history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts")
|
|
8879
|
+
history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
|
|
8880
|
+
lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
|
|
8802
8881
|
},
|
|
8803
|
-
async ({ scan_results, history }) => {
|
|
8882
|
+
async ({ scan_results, history, lang }) => {
|
|
8804
8883
|
try {
|
|
8805
8884
|
const parsed = JSON.parse(scan_results);
|
|
8806
8885
|
const historyData = history ? JSON.parse(history) : void 0;
|
|
8807
|
-
const report = generateHtmlReport(parsed, historyData);
|
|
8886
|
+
const report = generateHtmlReport(parsed, historyData, lang ?? "zh");
|
|
8808
8887
|
return { content: [{ type: "text", text: report }] };
|
|
8809
8888
|
} catch (err) {
|
|
8810
8889
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
@@ -8816,13 +8895,14 @@ function createServer(defaultRegion) {
|
|
|
8816
8895
|
"Generate a professional HTML MLPS Level 3 compliance report (\u7B49\u4FDD\u4E09\u7EA7). Save as .html file.",
|
|
8817
8896
|
{
|
|
8818
8897
|
scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
|
|
8819
|
-
history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts")
|
|
8898
|
+
history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
|
|
8899
|
+
lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
|
|
8820
8900
|
},
|
|
8821
|
-
async ({ scan_results, history }) => {
|
|
8901
|
+
async ({ scan_results, history, lang }) => {
|
|
8822
8902
|
try {
|
|
8823
8903
|
const parsed = JSON.parse(scan_results);
|
|
8824
8904
|
const historyData = history ? JSON.parse(history) : void 0;
|
|
8825
|
-
const report = generateMlps3HtmlReport(parsed, historyData);
|
|
8905
|
+
const report = generateMlps3HtmlReport(parsed, historyData, lang ?? "zh");
|
|
8826
8906
|
return { content: [{ type: "text", text: report }] };
|
|
8827
8907
|
} catch (err) {
|
|
8828
8908
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
@@ -8832,7 +8912,10 @@ function createServer(defaultRegion) {
|
|
|
8832
8912
|
server.tool(
|
|
8833
8913
|
"generate_maturity_report",
|
|
8834
8914
|
"Generate a security maturity assessment report from scan_all results. Requires service_detection module output. Read-only.",
|
|
8835
|
-
{
|
|
8915
|
+
{
|
|
8916
|
+
scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
|
|
8917
|
+
lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
|
|
8918
|
+
},
|
|
8836
8919
|
async ({ scan_results }) => {
|
|
8837
8920
|
try {
|
|
8838
8921
|
const parsed = JSON.parse(scan_results);
|
|
@@ -9103,7 +9186,7 @@ ${finding}`
|
|
|
9103
9186
|
type: "text",
|
|
9104
9187
|
text: `\u8BF7\u57FA\u4E8E\u4EE5\u4E0B\u62A4\u7F51\u884C\u52A8\u68C0\u67E5\u6E05\u5355\uFF0C\u5E2E\u52A9\u6211\u5236\u5B9A\u62A4\u7F51\u51C6\u5907\u8BA1\u5212\uFF1A
|
|
9105
9188
|
|
|
9106
|
-
${
|
|
9189
|
+
${getHwDefenseChecklist("zh")}
|
|
9107
9190
|
|
|
9108
9191
|
\u81EA\u52A8\u5316\u626B\u63CF\u90E8\u5206\u8BF7\u4F7F\u7528 scan_group hw_defense \u6267\u884C\u3002\u4EE5\u4E0A\u4EBA\u5DE5\u68C0\u67E5\u9879\u8BF7\u9010\u9879\u786E\u8BA4\u5E76\u63D0\u4F9B\u5177\u4F53\u5EFA\u8BAE\u3002`
|
|
9109
9192
|
}
|