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.
@@ -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.5.3";
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
- (a) => a.state?.status === "ENABLED" || a.state?.status === "ENABLING"
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
- if (f.Remediation?.Recommendation?.Text) {
2967
- remediationSteps.push(f.Remediation.Recommendation.Text);
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(`Reference: ${f.Remediation.Recommendation.Url}`);
2984
+ remediationSteps.push(`Documentation: ${f.Remediation.Recommendation.Url}`);
2971
2985
  }
2972
- if (remediationSteps.length === 0) {
2973
- remediationSteps.push("Review the finding in the AWS Security Hub console and follow the recommended remediation.");
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, partition, accountId } = ctx;
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 detectorsResp = await client.send(new ListDetectorsCommand2({}));
3054
- const detectorIds = detectorsResp.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: findings.length,
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 findings scan failed: ${msg}`,
3151
- warnings: warnings.length > 0 ? warnings : void 0,
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
- ListFindingsCommand as ListFindingsCommand2
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, partition, accountId } = ctx;
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
- let nextToken;
3195
- const filterCriteria = {
3196
- findingStatus: [{ comparison: "EQUALS", value: "ACTIVE" }]
3197
- };
3198
- do {
3199
- const resp = await client.send(
3200
- new ListFindingsCommand2({
3201
- filterCriteria,
3202
- maxResults: 100,
3203
- nextToken
3204
- })
3205
- );
3206
- const inspFindings = resp.findings ?? [];
3207
- resourcesScanned += inspFindings.length;
3208
- for (const f of inspFindings) {
3209
- const severityLabel = f.severity ?? "INFORMATIONAL";
3210
- const score = inspectorSeverityToScore(severityLabel);
3211
- if (score === null) continue;
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
- nextToken = resp.nextToken;
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: findings.length,
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:ListFindings to scan for vulnerabilities.");
3268
- return {
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: "error",
3293
- error: `Inspector findings scan failed: ${msg}`,
3294
- warnings: warnings.length > 0 ? warnings : void 0,
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
- DescribeComplianceByConfigRuleCommand,
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, partition, accountId } = ctx;
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
- let nextToken;
3508
- const nonCompliantRules = [];
3509
- do {
3510
- const resp = await client.send(
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: findings.length,
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 scan failed: ${msg}`,
3609
- warnings: warnings.length > 0 ? warnings : void 0,
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, partition, accountId } = ctx;
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
- const analyzers = [];
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
- analyzers.push(analyzer);
3388
+ hasActiveAnalyzer = true;
3389
+ break;
3672
3390
  }
3673
3391
  }
3392
+ if (hasActiveAnalyzer) break;
3674
3393
  analyzerToken = resp.nextToken;
3675
3394
  } while (analyzerToken);
3676
- if (analyzers.length === 0) {
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: findings.length,
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 scan failed: ${msg}`,
3760
- warnings: warnings.length > 0 ? warnings : void 0,
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 renderFinding(f) {
4217
- const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
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(`# AWS Security Scan Report \u2014 ${date}`);
4343
+ lines.push(`# ${t.securityReportTitle} \u2014 ${date}`);
4235
4344
  lines.push("");
4236
- lines.push("## Executive Summary");
4237
- lines.push(`- **Account:** ${accountId}`);
4238
- lines.push(`- **Region:** ${region}`);
4239
- lines.push(`- **Scan Duration:** ${duration}`);
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
- `- **Total Findings:** ${summary.totalFindings} (\u{1F534} ${summary.critical} Critical | \u{1F7E0} ${summary.high} High | \u{1F7E1} ${summary.medium} Medium | \u{1F7E2} ${summary.low} Low)`
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("## Findings by Severity");
4354
+ lines.push(`## ${t.findingsBySeverity}`);
4246
4355
  lines.push("");
4247
- lines.push("\u2705 No security issues found.");
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("## Findings by Severity");
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.charAt(0)}${sev.slice(1).toLowerCase()}`);
4372
+ lines.push(`### ${icon} ${sevLabel[sev]}`);
4264
4373
  lines.push("");
4265
4374
  if (findings.length === 0) {
4266
- lines.push(`No ${sev.toLowerCase()} findings.`);
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("## Scan Statistics");
4386
+ lines.push(`## ${t.scanStatistics}`);
4278
4387
  lines.push(
4279
- "| Module | Resources Scanned | Findings | Status |"
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("## Recommendations (Priority Order)");
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("# \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A");
7019
- lines.push("> **\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**");
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("## \u8D26\u6237\u4FE1\u606F");
7023
- lines.push(`- Account: ${accountId} | Region: ${region} | \u626B\u63CF\u65F6\u95F4: ${scanTime}`);
7127
+ lines.push(`## ${t.accountInfo}`);
7128
+ lines.push(`- ${t.account}: ${accountId} | ${t.region}: ${region} | ${t.scanTime}: ${scanTime}`);
7024
7129
  lines.push("");
7025
- lines.push("## \u9884\u68C0\u603B\u89C8");
7026
- lines.push(`- \u5DF2\u68C0\u67E5: ${checkedTotal} \u9879\uFF08\u672A\u53D1\u73B0\u95EE\u9898: ${autoClean} \u9879 | \u53D1\u73B0\u95EE\u9898: ${autoIssues} \u9879\uFF09`);
7130
+ lines.push(`## ${t.preCheckOverview}`);
7131
+ lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
7027
7132
  if (autoUnknown > 0) {
7028
- lines.push(`- \u672A\u68C0\u67E5: ${autoUnknown} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`);
7133
+ lines.push(`- ${t.uncheckedCount(autoUnknown)}`);
7029
7134
  }
7030
- lines.push(`- \u4E91\u5E73\u53F0\u8D1F\u8D23: ${cloudCount} \u9879`);
7031
- lines.push(`- \u9700\u4EBA\u5DE5\u8BC4\u4F30: ${manualCount} \u9879`);
7135
+ lines.push(`- ${t.cloudProviderCount(cloudCount)}`);
7136
+ lines.push(`- ${t.manualReviewCount(manualCount)}`);
7032
7137
  if (naCount > 0) {
7033
- lines.push(`- \u4E0D\u9002\u7528: ${naCount} \u9879`);
7138
+ lines.push(`- ${t.naCount(naCount)}`);
7034
7139
  }
7035
7140
  lines.push("");
7036
7141
  for (const category of MLPS3_CATEGORY_ORDER) {
7037
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
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 [controlName, controlResults] of controlMap) {
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" ? " \u2014 \u672A\u68C0\u67E5" : r.status === "manual" ? ` \u2014 ${r.mapping.guidance ?? "\u9700\u4EBA\u5DE5\u8BC4\u4F30"}` : r.status === "cloud_provider" ? ` \u2014 ${r.mapping.note ?? "\u4E91\u5E73\u53F0\u8D1F\u8D23"}` : r.status === "clean" ? " \u672A\u53D1\u73B0\u95EE\u9898" : " \u53D1\u73B0\u95EE\u9898";
7055
- lines.push(`- [${icon}] ${r.item.id} ${r.item.requirementCn.slice(0, 60)}${r.item.requirementCn.length > 60 ? "\u2026" : ""}${suffix}`);
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(` - ... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 3} \u9879`);
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("## \u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09");
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(`> \u4E0D\u9002\u7528\u9879: ${naCount} \u9879\uFF08\u7269\u8054\u7F51/\u65E0\u7EBF\u7F51\u7EDC/\u79FB\u52A8\u7EC8\u7AEF/\u5DE5\u63A7\u7CFB\u7EDF/\u53EF\u4FE1\u9A8C\u8BC1\u7B49\uFF09`);
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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 = SERVICE_RECOMMENDATIONS[mod.module];
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 disabled = getDisabledServices(modules);
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)} \u672A\u542F\u7528</div>
7200
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5F71\u54CD\uFF1A${esc(svc.impact)}</div>
7201
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5EFA\u8BAE\uFF1A${esc(svc.action)}</div>
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">&#9889; \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A</div>
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">\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002</div>
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
- ' <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">All modules clean</text>',
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>Resource:</strong> ${esc(f.resourceId)}</div>
7549
- <div class="top5-detail"><strong>Impact:</strong> ${esc(f.impact)}</div>
7550
- <div class="top5-detail"><strong>Risk Score:</strong> ${f.riskScore}/10</div>
7551
- <h4>Remediation</h4>
7552
- <ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
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>Top ${top5.length} Highest Risk Findings</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 = '<div class="no-findings">No security issues found.</div>';
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>Details</summary><div class="finding-card-body">
7649
+ <details><summary>${t.details}</summary><div class="finding-card-body">
7574
7650
  <p>${esc(f.description)}</p>
7575
- <p><strong>Remediation:</strong></p>
7576
- <ol>${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
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>Show remaining ${findings.length - FOLD_THRESHOLD} findings...</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>30-Day Trends</h2>
7715
+ <h2>${esc(t.trendTitle)}</h2>
7640
7716
  <div class="trend-chart">
7641
- <div class="trend-title">Findings by Severity</div>
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">Security Score</div>
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 ? ` (&times; ${r.count})` : "";
7676
- return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}</li>`;
7793
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</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>Show ${remaining.length} more&hellip;</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">Recommendations (${uniqueRecs.length} unique)</h2></summary>
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="en">
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>AWS Security Scan Report &mdash; ${esc(date)}</title>
7816
+ <title>${esc(t.securityReportTitle)} &mdash; ${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>&#128737;&#65039; AWS Security Scan Report</h1>
7706
- <div class="meta">Account: ${esc(accountId)} | Region: ${esc(region)} | ${esc(date)} | Duration: ${esc(duration)}</div>
7823
+ <h1>&#128737;&#65039; ${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">Security Score</div>
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">Critical</div></div>
7716
- <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">High</div></div>
7717
- <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">Medium</div></div>
7718
- <div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">Low</div></div>
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">Severity Distribution</div>
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">Findings by Module</div>
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>Scan Statistics</h2>
7858
+ <h2>${esc(t.scanStatistics)}</h2>
7741
7859
  <table>
7742
- <thead><tr><th>Module</th><th>Resources</th><th>Findings</th><th>Status</th></tr></thead>
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>All Findings</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>Generated by AWS Security MCP Server v${VERSION}</p>
7756
- <p>This report is for informational purposes only.</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>30\u65E5\u8D8B\u52BF</h2>
7900
+ <h2>${esc(t.trendTitle)}</h2>
7781
7901
  <div class="trend-chart">
7782
- <div class="trend-title">\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0</div>
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">\u5B89\u5168\u8BC4\u5206</div>
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 = MLPS3_CATEGORY_SECTION[category];
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} \u9879\u4E91\u5E73\u53F0\u8D1F\u8D23</span></span>
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">\u4EE5\u4E0B ${catResults.length} \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</div>
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.item.controlCn)}</span><span class="check-note">${esc(r.mapping.note ?? "")}</span></div>`).join("\n")}
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(([controlName, controlResults]) => {
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" ? " \u2014 \u672A\u68C0\u67E5" : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? "\u9700\u4EBA\u5DE5\u8BC4\u4F30")}` : "";
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">\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898</div>`;
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>... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 5} \u9879</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">\u5EFA\u8BAE\uFF1A${esc(r.relatedFindings[0].remediationSteps[0])}</p>` : "";
7850
- findingsDetail = `<div class="check-findings-wrap"><details><summary>\u68C0\u67E5\u7ED3\u679C\uFF1A\u53D1\u73B0 ${r.relatedFindings.length} \u4E2A\u76F8\u5173\u95EE\u9898</summary><ul class="check-findings">${fItems.join("")}</ul>${remediationHint}</details></div>`;
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
- itemsHtml += `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc(r.item.id)} ${esc(r.item.requirementCn.slice(0, 60))}${r.item.requirementCn.length > 60 ? "\u2026" : ""}${suffix}</span></div>
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
- itemsHtml += `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(r.item.requirementCn.slice(0, 50))}${r.item.requirementCn.length > 50 ? "\u2026" : ""}</span><span class="check-note">\u4E91\u5E73\u53F0\u8D1F\u8D23</span></div>
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 ? ` (&times; ${r.count})` : "";
7913
- return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}</li>`;
8081
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</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>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879&hellip;</summary>
8088
+ <details><summary>${esc(t.showRemaining(mlpsRemaining.length))}&hellip;</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">\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${mlpsUniqueRecs.length} \u9879\u53BB\u91CD\uFF09</h2></summary>
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">\u4E0D\u9002\u7528\u9879: ${naCount} \u9879\uFF08\u7269\u8054\u7F51/\u65E0\u7EBF\u7F51\u7EDC/\u79FB\u52A8\u7EC8\u7AEF/\u5DE5\u63A7\u7CFB\u7EDF/\u53EF\u4FE1\u9A8C\u8BC1\u7B49\uFF09</p>` : "";
7932
- const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">\uFF08${autoUnknown} \u9879\u672A\u68C0\u67E5\uFF0C\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09</div>` : "";
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="zh-CN">
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>\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A &mdash; ${esc(date)}</title>
8127
+ <title>${esc(t.mlpsTitle)} &mdash; ${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>&#128737;&#65039; \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A</h1>
7966
- <div class="disclaimer">\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</div>
7967
- <div class="meta">\u8D26\u6237: ${esc(accountId)} | \u533A\u57DF: ${esc(region)} | \u626B\u63CF\u65F6\u95F4: ${esc(scanTime)}</div>
8134
+ <h1>&#128737;&#65039; ${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">\u672A\u53D1\u73B0\u95EE\u9898</span>
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">\u53D1\u73B0\u95EE\u9898</span>
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">\u5DF2\u68C0\u67E5\u9879</div></div>
7978
- <div class="mlps-summary-card"><div class="stat-count" style="color:#94a3b8">${cloudCount}</div><div class="stat-label">\u{1F3E2} \u4E91\u5E73\u53F0\u8D1F\u8D23</div></div>
7979
- <div class="mlps-summary-card"><div class="stat-count" style="color:#eab308">${manualCount}</div><div class="stat-label">\u{1F4CB} \u9700\u4EBA\u5DE5\u8BC4\u4F30</div></div>
7980
- ${naCount > 0 ? `<div class="mlps-summary-card"><div class="stat-count" style="color:#64748b">${naCount}</div><div class="stat-label">\u2796 \u4E0D\u9002\u7528</div></div>` : ""}
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>\u7531 AWS Security MCP Server v${VERSION} \u751F\u6210</p>
7997
- <p>\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</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
- Aggregates threat detection findings from Amazon GuardDuty.
8213
- - Covers account compromise, instance compromise, and reconnaissance.
8214
- - Severity mapped from GuardDuty 0\u201310 scale: \u22657 \u2192 HIGH, \u22654 \u2192 MEDIUM, <4 \u2192 LOW.
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
- Aggregates vulnerability findings from Amazon Inspector v2.
8219
- - Covers CVEs in EC2 instances, Lambda functions, and container images.
8220
- - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
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
- Pulls non-compliant AWS Config Rule evaluation results.
8258
- - Lists all Config Rules and their compliance status.
8259
- - For NON_COMPLIANT rules, retrieves specific non-compliant resources.
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
- Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account.
8266
- - Lists active analyzers (ACCOUNT or ORGANIZATION type).
8267
- - Retrieves ACTIVE findings showing external access to resources.
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: "Aggregates threat detection findings from Amazon GuardDuty \u2014 account compromise, instance compromise, and reconnaissance.",
8355
- inspector_findings: "Aggregates vulnerability findings from Amazon Inspector \u2014 CVEs in EC2, Lambda, and container images.",
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: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
8358
- access_analyzer_findings: "Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account (external principals, public access).",
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
- var HW_DEFENSE_CHECKLIST = `
8364
- \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
8365
- \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
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 = SERVICE_RECOMMENDATIONS2[mod.module];
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
- "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
8555
+ t.serviceReminderTitle,
8486
8556
  ""
8487
8557
  ];
8488
8558
  for (const svc of disabledServices) {
8489
- lines.push(`${svc.icon} ${svc.service} \u672A\u542F\u7528`);
8490
- lines.push(` \u5F71\u54CD\uFF1A${svc.impact}`);
8491
- lines.push(` \u5EFA\u8BAE\uFF1A${svc.action}`);
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("\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002");
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" + HW_DEFENSE_CHECKLIST;
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
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
8772
- async ({ scan_results }) => {
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
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all") },
8786
- async ({ scan_results }) => {
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
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
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
- ${HW_DEFENSE_CHECKLIST}
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
  }