aws-security-mcp 0.5.3 → 0.6.0

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.0";
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,
@@ -3123,10 +3138,11 @@ var GuardDutyFindingsScanner = class {
3123
3138
  impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
3124
3139
  riskScore: score,
3125
3140
  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
- ],
3141
+ `Investigate ${gdf.Type ?? "unknown threat"}: ${gdf.Title ?? "threat detected"}`,
3142
+ gdf.Description ? `Details: ${gdf.Description.substring(0, 200)}` : "",
3143
+ "Isolate affected resources if compromise is confirmed.",
3144
+ "Review CloudTrail logs for related suspicious activity."
3145
+ ].filter(Boolean),
3130
3146
  priority: priorityFromSeverity(severity),
3131
3147
  module: this.moduleName,
3132
3148
  accountId: gdf.AccountId ?? accountId
@@ -3217,18 +3233,44 @@ var InspectorFindingsScanner = class {
3217
3233
  const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
3218
3234
  const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
3219
3235
  const remediationSteps = [];
3220
- if (f.remediation?.recommendation?.text) {
3236
+ const vulnPkgs = f.packageVulnerabilityDetails?.vulnerablePackages;
3237
+ if (vulnPkgs?.length) {
3238
+ for (const pkg of vulnPkgs.slice(0, 3)) {
3239
+ const name = pkg.name ?? "unknown-package";
3240
+ const installed = pkg.version ?? "unknown";
3241
+ const fixed = pkg.fixedInVersion ?? "latest";
3242
+ const cveRef = cveId ? ` to fix ${cveId}` : "";
3243
+ remediationSteps.push(`Update ${name} from ${installed} to ${fixed}${cveRef}`);
3244
+ }
3245
+ } else if (f.remediation?.recommendation?.text) {
3221
3246
  remediationSteps.push(f.remediation.recommendation.text);
3222
3247
  }
3248
+ const genericPatterns = ["See References", "None Provided", "Review the finding"];
3249
+ if (remediationSteps.length === 0 || genericPatterns.some((p) => remediationSteps[0]?.startsWith(p))) {
3250
+ remediationSteps.length = 0;
3251
+ const rawTitle = f.title ?? "";
3252
+ if (rawTitle.includes("KB")) {
3253
+ const kbMatch = rawTitle.match(/KB\d+/);
3254
+ const kb = kbMatch ? kbMatch[0] : "patch";
3255
+ remediationSteps.push(`Install Windows patch ${kb} via WSUS or AWS Systems Manager Patch Manager`);
3256
+ remediationSteps.push(`Run: aws ssm send-command --document-name "AWS-InstallWindowsUpdates" --targets "Key=InstanceIds,Values=${resourceId}"`);
3257
+ if (kbMatch) {
3258
+ remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${kb}`);
3259
+ }
3260
+ } else if (rawTitle.includes("CVE-") || cveId) {
3261
+ const cveMatch = rawTitle.match(/CVE-[\d-]+/);
3262
+ const cve = cveMatch ? cveMatch[0] : cveId ?? "vulnerability";
3263
+ remediationSteps.push(`Fix ${cve}: update the affected software package to the latest patched version`);
3264
+ } else {
3265
+ remediationSteps.push(`Review and remediate: ${rawTitle}`);
3266
+ }
3267
+ }
3223
3268
  if (f.remediation?.recommendation?.Url) {
3224
- remediationSteps.push(`Reference: ${f.remediation.recommendation.Url}`);
3269
+ remediationSteps.push(`Documentation: ${f.remediation.recommendation.Url}`);
3225
3270
  }
3226
3271
  if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
3227
3272
  remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
3228
3273
  }
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
3274
  const description = f.description ?? titleBase;
3233
3275
  const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
3234
3276
  findings.push({
@@ -3563,10 +3605,10 @@ var ConfigRulesFindingsScanner = class {
3563
3605
  impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
3564
3606
  riskScore,
3565
3607
  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
- ],
3608
+ `Fix Config Rule violation: ${ruleName}`,
3609
+ annotation ? `Details: ${annotation}` : "",
3610
+ `Resource: ${resourceType}/${resourceId}`
3611
+ ].filter(Boolean),
3570
3612
  priority: priorityFromSeverity(severity),
3571
3613
  module: this.moduleName,
3572
3614
  accountId
@@ -3715,13 +3757,13 @@ var AccessAnalyzerFindingsScanner = class {
3715
3757
  const title = buildFindingTitle(aaf);
3716
3758
  const impact = external ? `Resource is accessible from outside the account. Type: ${aaf.findingType ?? "unknown"}` : `Unused access detected \u2014 review and remove to follow least-privilege. Type: ${aaf.findingType ?? "unknown"}`;
3717
3759
  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."
3760
+ `Restrict external access on ${resourceType} ${resourceId}`,
3761
+ "Remove or narrow the resource policy to eliminate unintended external access.",
3762
+ `Resource ARN: ${resourceArn}`
3721
3763
  ] : [
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."
3764
+ `Remove unused access on ${resourceType} ${resourceId}`,
3765
+ "Remove unused permissions, roles, or credentials to follow least-privilege.",
3766
+ `Resource ARN: ${resourceArn}`
3725
3767
  ];
3726
3768
  findings.push({
3727
3769
  severity,
@@ -4196,6 +4238,479 @@ var WafCoverageScanner = class {
4196
4238
  }
4197
4239
  };
4198
4240
 
4241
+ // src/i18n/zh.ts
4242
+ var zhI18n = {
4243
+ // HTML Security Report
4244
+ securityReportTitle: "AWS \u5B89\u5168\u626B\u63CF\u62A5\u544A",
4245
+ securityScore: "\u5B89\u5168\u8BC4\u5206",
4246
+ critical: "\u4E25\u91CD",
4247
+ high: "\u9AD8",
4248
+ medium: "\u4E2D",
4249
+ low: "\u4F4E",
4250
+ scanStatistics: "\u626B\u63CF\u7EDF\u8BA1",
4251
+ module: "\u6A21\u5757",
4252
+ resources: "\u8D44\u6E90",
4253
+ findings: "\u53D1\u73B0",
4254
+ status: "\u72B6\u6001",
4255
+ allFindings: "\u6240\u6709\u53D1\u73B0",
4256
+ recommendations: "\u5EFA\u8BAE",
4257
+ unique: "\u53BB\u91CD",
4258
+ showMore: "\u663E\u793A\u66F4\u591A",
4259
+ noIssuesFound: "\u672A\u53D1\u73B0\u5B89\u5168\u95EE\u9898\u3002",
4260
+ allModulesClean: "\u6240\u6709\u6A21\u5757\u6B63\u5E38",
4261
+ generatedBy: "\u7531 AWS Security MCP Server \u751F\u6210",
4262
+ informationalOnly: "\u672C\u62A5\u544A\u4EC5\u4F9B\u53C2\u8003\u3002",
4263
+ // MLPS Report
4264
+ mlpsTitle: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A",
4265
+ 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",
4266
+ checkedItems: "\u5DF2\u68C0\u67E5\u9879",
4267
+ noIssues: "\u672A\u53D1\u73B0\u95EE\u9898",
4268
+ issuesFound: "\u53D1\u73B0\u95EE\u9898",
4269
+ notChecked: "\u672A\u68C0\u67E5",
4270
+ cloudProvider: "\u4E91\u5E73\u53F0\u8D1F\u8D23",
4271
+ manualReview: "\u9700\u4EBA\u5DE5\u8BC4\u4F30",
4272
+ notApplicable: "\u4E0D\u9002\u7528",
4273
+ checkResult: "\u68C0\u67E5\u7ED3\u679C",
4274
+ noRelatedIssues: "\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898",
4275
+ issuesFoundCount: (n) => `\u68C0\u67E5\u7ED3\u679C\uFF1A\u53D1\u73B0 ${n} \u4E2A\u76F8\u5173\u95EE\u9898`,
4276
+ remediation: "\u5EFA\u8BAE",
4277
+ remediationItems: (n) => `\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${n} \u9879\u53BB\u91CD\uFF09`,
4278
+ showRemaining: (n) => `\u663E\u793A\u5176\u4F59 ${n} \u9879`,
4279
+ // HW Defense Checklist
4280
+ hwChecklistTitle: "\u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09",
4281
+ hwChecklistSubtitle: "\u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A",
4282
+ hwEmergencyIsolation: `\u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
4283
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
4284
+ \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
4285
+ \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
4286
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F`,
4287
+ hwTestEnvShutdown: `\u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
4288
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
4289
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
4290
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563`,
4291
+ hwDutyTeam: `\u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
4292
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
4293
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
4294
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
4295
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
4296
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09`,
4297
+ hwNetworkDiagram: `\u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
4298
+ \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
4299
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
4300
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3`,
4301
+ hwPentest: `\u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
4302
+ \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
4303
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
4304
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09`,
4305
+ hwWarRoom: `\u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
4306
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
4307
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
4308
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D`,
4309
+ hwCredentials: `\u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
4310
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
4311
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
4312
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
4313
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801`,
4314
+ hwPostOptimization: `\u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
4315
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
4316
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
4317
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669`,
4318
+ hwReference: "\u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)",
4319
+ // Service Reminders
4320
+ serviceReminderTitle: "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
4321
+ serviceReminderFooter: "\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002",
4322
+ serviceImpact: "\u5F71\u54CD",
4323
+ serviceAction: "\u5EFA\u8BAE",
4324
+ // Common
4325
+ account: "\u8D26\u6237",
4326
+ region: "\u533A\u57DF",
4327
+ scanTime: "\u626B\u63CF\u65F6\u95F4",
4328
+ duration: "\u8017\u65F6",
4329
+ severityDistribution: "\u4E25\u91CD\u6027\u5206\u5E03",
4330
+ findingsByModule: "\u6309\u6A21\u5757\u5206\u7C7B\u7684\u53D1\u73B0",
4331
+ details: "\u8BE6\u60C5",
4332
+ // Extended — HTML Security Report extras
4333
+ topHighestRiskFindings: (n) => `\u524D ${n} \u9879\u6700\u9AD8\u98CE\u9669\u53D1\u73B0`,
4334
+ resource: "\u8D44\u6E90",
4335
+ impact: "\u5F71\u54CD",
4336
+ riskScore: "\u98CE\u9669\u8BC4\u5206",
4337
+ showRemainingFindings: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u53D1\u73B0\u2026`,
4338
+ trendTitle: "30\u65E5\u8D8B\u52BF",
4339
+ findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
4340
+ showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
4341
+ // Extended — MLPS extras
4342
+ // Markdown report
4343
+ executiveSummary: "\u6267\u884C\u6458\u8981",
4344
+ totalFindingsLabel: "\u53D1\u73B0\u603B\u6570",
4345
+ description: "\u63CF\u8FF0",
4346
+ priority: "\u4F18\u5148\u7EA7",
4347
+ noFindingsForSeverity: (severity) => `\u65E0${severity}\u53D1\u73B0\u3002`,
4348
+ preCheckOverview: "\u9884\u68C0\u603B\u89C8",
4349
+ accountInfo: "\u8D26\u6237\u4FE1\u606F",
4350
+ checkedCount: (total, clean, issues) => `\u5DF2\u68C0\u67E5: ${total} \u9879\uFF08\u672A\u53D1\u73B0\u95EE\u9898: ${clean} \u9879 | \u53D1\u73B0\u95EE\u9898: ${issues} \u9879\uFF09`,
4351
+ uncheckedCount: (n) => `\u672A\u68C0\u67E5: ${n} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
4352
+ cloudProviderCount: (n) => `\u4E91\u5E73\u53F0\u8D1F\u8D23: ${n} \u9879`,
4353
+ manualReviewCount: (n) => `\u9700\u4EBA\u5DE5\u8BC4\u4F30: ${n} \u9879`,
4354
+ naCount: (n) => `\u4E0D\u9002\u7528: ${n} \u9879`,
4355
+ 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`,
4356
+ unknownNote: (n) => `\uFF08${n} \u9879\u672A\u68C0\u67E5\uFF0C\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
4357
+ 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`,
4358
+ mlpsFooterGenerated: (version) => `\u7531 AWS Security MCP Server v${version} \u751F\u6210`,
4359
+ 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",
4360
+ andMore: (n) => `... \u53CA\u5176\u4ED6 ${n} \u9879`,
4361
+ remediationByPriority: "\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09",
4362
+ affectedResources: (n) => `\u6D89\u53CA ${n} \u4E2A\u8D44\u6E90`,
4363
+ installWindowsPatches: (n, kbs) => `\u5B89\u88C5 ${n} \u4E2A Windows \u8865\u4E01 (${kbs})`,
4364
+ mlpsCategorySection: {
4365
+ "\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
4366
+ "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
4367
+ "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
4368
+ "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
4369
+ "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
4370
+ },
4371
+ // Service Recommendations
4372
+ notEnabled: "\u672A\u542F\u7528",
4373
+ serviceRecommendations: {
4374
+ security_hub_findings: {
4375
+ icon: "\u{1F534}",
4376
+ service: "Security Hub",
4377
+ impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
4378
+ action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
4379
+ },
4380
+ guardduty_findings: {
4381
+ icon: "\u{1F534}",
4382
+ service: "GuardDuty",
4383
+ 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",
4384
+ action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
4385
+ },
4386
+ inspector_findings: {
4387
+ icon: "\u{1F7E1}",
4388
+ service: "Inspector",
4389
+ impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
4390
+ action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
4391
+ },
4392
+ trusted_advisor_findings: {
4393
+ icon: "\u{1F7E1}",
4394
+ service: "Trusted Advisor",
4395
+ impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
4396
+ action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
4397
+ },
4398
+ config_rules_findings: {
4399
+ icon: "\u{1F7E1}",
4400
+ service: "AWS Config",
4401
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
4402
+ action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
4403
+ },
4404
+ access_analyzer_findings: {
4405
+ icon: "\u{1F7E1}",
4406
+ service: "IAM Access Analyzer",
4407
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
4408
+ action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
4409
+ },
4410
+ patch_compliance_findings: {
4411
+ icon: "\u{1F7E1}",
4412
+ service: "SSM Patch Manager",
4413
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
4414
+ action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
4415
+ }
4416
+ },
4417
+ // HW Checklist (full composite)
4418
+ hwChecklist: `
4419
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4420
+ \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
4421
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4422
+
4423
+ \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
4424
+
4425
+ \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
4426
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
4427
+ \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
4428
+ \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
4429
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
4430
+
4431
+ \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
4432
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
4433
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
4434
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
4435
+
4436
+ \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
4437
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
4438
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
4439
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
4440
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
4441
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
4442
+
4443
+ \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
4444
+ \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
4445
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
4446
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
4447
+
4448
+ \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
4449
+ \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
4450
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
4451
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
4452
+
4453
+ \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
4454
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
4455
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
4456
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D
4457
+
4458
+ \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
4459
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
4460
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
4461
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
4462
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
4463
+
4464
+ \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
4465
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
4466
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
4467
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
4468
+
4469
+ \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
4470
+ `
4471
+ };
4472
+
4473
+ // src/i18n/en.ts
4474
+ var enI18n = {
4475
+ // HTML Security Report
4476
+ securityReportTitle: "AWS Security Scan Report",
4477
+ securityScore: "Security Score",
4478
+ critical: "Critical",
4479
+ high: "High",
4480
+ medium: "Medium",
4481
+ low: "Low",
4482
+ scanStatistics: "Scan Statistics",
4483
+ module: "Module",
4484
+ resources: "Resources",
4485
+ findings: "Findings",
4486
+ status: "Status",
4487
+ allFindings: "All Findings",
4488
+ recommendations: "Recommendations",
4489
+ unique: "unique",
4490
+ showMore: "Show more",
4491
+ noIssuesFound: "No security issues found.",
4492
+ allModulesClean: "All modules clean",
4493
+ generatedBy: "Generated by AWS Security MCP Server",
4494
+ informationalOnly: "This report is for informational purposes only.",
4495
+ // MLPS Report
4496
+ mlpsTitle: "MLPS Level 3 Pre-Check Report",
4497
+ 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)",
4498
+ checkedItems: "Checked Items",
4499
+ noIssues: "No Issues Found",
4500
+ issuesFound: "Issues Found",
4501
+ notChecked: "Not Checked",
4502
+ cloudProvider: "Cloud Provider Responsible",
4503
+ manualReview: "Manual Review Required",
4504
+ notApplicable: "Not Applicable",
4505
+ checkResult: "Check Result",
4506
+ noRelatedIssues: "Check Result: No related issues found",
4507
+ issuesFoundCount: (n) => `Check Result: Found ${n} related issue${n === 1 ? "" : "s"}`,
4508
+ remediation: "Remediation",
4509
+ remediationItems: (n) => `Remediation Items (${n} unique)`,
4510
+ showRemaining: (n) => `Show remaining ${n} items`,
4511
+ // HW Defense Checklist
4512
+ hwChecklistTitle: "\u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)",
4513
+ hwChecklistSubtitle: "The following items require manual verification and execution:",
4514
+ hwEmergencyIsolation: `\u26A0\uFE0F Emergency Isolation / Incident Response Plan
4515
+ \u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
4516
+ \u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
4517
+ \u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
4518
+ \u25A1 Identify responsible personnel and contacts for each project account and resource`,
4519
+ hwTestEnvShutdown: `\u26A0\uFE0F Test/Development Environment Handling
4520
+ \u25A1 Shut down non-critical systems during the drill period
4521
+ \u25A1 Shut down test/dev environments or maintain same security baseline as production
4522
+ \u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation`,
4523
+ hwDutyTeam: `\u26A0\uFE0F On-Duty Team Formation
4524
+ \u25A1 7\xD724 monitoring and rapid response team
4525
+ \u25A1 Technical and risk analysis team
4526
+ \u25A1 Security policy deployment team
4527
+ \u25A1 Business response team
4528
+ \u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)`,
4529
+ hwNetworkDiagram: `\u26A0\uFE0F Ingress/Egress Path Architecture Diagram
4530
+ \u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
4531
+ \u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
4532
+ \u25A1 Identify all internet-facing data interaction interfaces`,
4533
+ hwPentest: `\u26A0\uFE0F Proactive Penetration Testing
4534
+ \u25A1 Contact security vendors for simulated attack drills before the exercise
4535
+ \u25A1 Conduct security hardening based on penetration test reports
4536
+ \u25A1 Monitor AWS security advisories (known vulnerabilities and patches)`,
4537
+ hwWarRoom: `\u26A0\uFE0F WAR-ROOM Real-Time Communication
4538
+ \u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
4539
+ \u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
4540
+ \u25A1 Standardize case title format: "[CyberDrill] + Issue Description"`,
4541
+ hwCredentials: `\u26A0\uFE0F Password & Credential Management
4542
+ \u25A1 All IAM users must have MFA enabled
4543
+ \u25A1 Access key rotation cycle \u2264 90 days
4544
+ \u25A1 Avoid shared account usage
4545
+ \u25A1 No plaintext passwords in S3/Lambda/application code`,
4546
+ hwPostOptimization: `\u26A0\uFE0F Post-Drill Optimization
4547
+ \u25A1 Address and remediate each item from the attack report
4548
+ \u25A1 Establish periodic security maintenance processes with the security team
4549
+ \u25A1 Continuously fill security risk gaps`,
4550
+ hwReference: "Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)",
4551
+ // Service Reminders
4552
+ serviceReminderTitle: "\u26A1 The following security services are not enabled; some checks cannot be performed:",
4553
+ serviceReminderFooter: "Re-scan after enabling the above services for a more complete security assessment.",
4554
+ serviceImpact: "Impact",
4555
+ serviceAction: "Action",
4556
+ // Common
4557
+ account: "Account",
4558
+ region: "Region",
4559
+ scanTime: "Scan Time",
4560
+ duration: "Duration",
4561
+ severityDistribution: "Severity Distribution",
4562
+ findingsByModule: "Findings by Module",
4563
+ details: "Details",
4564
+ // Extended \u2014 HTML Security Report extras
4565
+ topHighestRiskFindings: (n) => `Top ${n} Highest Risk Findings`,
4566
+ resource: "Resource",
4567
+ impact: "Impact",
4568
+ riskScore: "Risk Score",
4569
+ showRemainingFindings: (n) => `Show remaining ${n} findings\u2026`,
4570
+ trendTitle: "30-Day Trends",
4571
+ findingsBySeverity: "Findings by Severity",
4572
+ showMoreCount: (n) => `Show ${n} more\u2026`,
4573
+ // Extended \u2014 MLPS extras
4574
+ // Markdown report
4575
+ executiveSummary: "Executive Summary",
4576
+ totalFindingsLabel: "Total Findings",
4577
+ description: "Description",
4578
+ priority: "Priority",
4579
+ noFindingsForSeverity: (severity) => `No ${severity.toLowerCase()} findings.`,
4580
+ preCheckOverview: "Pre-Check Overview",
4581
+ accountInfo: "Account Information",
4582
+ checkedCount: (total, clean, issues) => `Checked: ${total} items (No issues: ${clean} | Issues found: ${issues})`,
4583
+ uncheckedCount: (n) => `Not checked: ${n} items (corresponding scan modules not run)`,
4584
+ cloudProviderCount: (n) => `Cloud provider responsible: ${n} items`,
4585
+ manualReviewCount: (n) => `Manual review required: ${n} items`,
4586
+ naCount: (n) => `Not applicable: ${n} items`,
4587
+ naNote: (n) => `Not applicable: ${n} items (IoT/wireless networks/mobile terminals/ICS/trusted verification, etc.)`,
4588
+ unknownNote: (n) => `(${n} items not checked \u2014 corresponding scan modules not run)`,
4589
+ 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.`,
4590
+ mlpsFooterGenerated: (version) => `Generated by AWS Security MCP Server v${version}`,
4591
+ 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.",
4592
+ andMore: (n) => `\u2026 and ${n} more`,
4593
+ remediationByPriority: "Remediation Items (by Priority)",
4594
+ affectedResources: (n) => `${n} resource${n === 1 ? "" : "s"} affected`,
4595
+ installWindowsPatches: (n, kbs) => `Install ${n} Windows patch${n === 1 ? "" : "es"} (${kbs})`,
4596
+ mlpsCategorySection: {
4597
+ "\u5B89\u5168\u7269\u7406\u73AF\u5883": "I. Physical Environment Security",
4598
+ "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "II. Communication Network Security",
4599
+ "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "III. Area Boundary Security",
4600
+ "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
4601
+ "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
4602
+ },
4603
+ // Service Recommendations
4604
+ notEnabled: "Not Enabled",
4605
+ serviceRecommendations: {
4606
+ security_hub_findings: {
4607
+ icon: "\u{1F534}",
4608
+ service: "Security Hub",
4609
+ impact: "Cannot obtain 300+ automated security checks (FSBP/CIS/PCI DSS standards)",
4610
+ action: "Enable Security Hub for the most comprehensive security posture assessment"
4611
+ },
4612
+ guardduty_findings: {
4613
+ icon: "\u{1F534}",
4614
+ service: "GuardDuty",
4615
+ impact: "Cannot detect threat activity (malicious IPs, anomalous API calls, crypto mining, etc.)",
4616
+ action: "Enable GuardDuty for continuous threat detection"
4617
+ },
4618
+ inspector_findings: {
4619
+ icon: "\u{1F7E1}",
4620
+ service: "Inspector",
4621
+ impact: "Cannot scan EC2/Lambda/container software vulnerabilities (CVEs)",
4622
+ action: "Enable Inspector to discover known security vulnerabilities"
4623
+ },
4624
+ trusted_advisor_findings: {
4625
+ icon: "\u{1F7E1}",
4626
+ service: "Trusted Advisor",
4627
+ impact: "Cannot obtain AWS best practice security checks",
4628
+ action: "Upgrade to Business/Enterprise Support plan to use Trusted Advisor security checks"
4629
+ },
4630
+ config_rules_findings: {
4631
+ icon: "\u{1F7E1}",
4632
+ service: "AWS Config",
4633
+ impact: "Cannot check resource configuration compliance status",
4634
+ action: "Enable AWS Config and configure Config Rules"
4635
+ },
4636
+ access_analyzer_findings: {
4637
+ icon: "\u{1F7E1}",
4638
+ service: "IAM Access Analyzer",
4639
+ impact: "Cannot detect whether resources are accessed by external accounts or public networks",
4640
+ action: "Create IAM Access Analyzer (account-level or organization-level)"
4641
+ },
4642
+ patch_compliance_findings: {
4643
+ icon: "\u{1F7E1}",
4644
+ service: "SSM Patch Manager",
4645
+ impact: "Cannot check instance patch compliance status",
4646
+ action: "Install SSM Agent and configure Patch Manager"
4647
+ }
4648
+ },
4649
+ // HW Checklist (full composite)
4650
+ hwChecklist: `
4651
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4652
+ \u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)
4653
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4654
+
4655
+ The following items require manual verification and execution:
4656
+
4657
+ \u26A0\uFE0F Emergency Isolation / Incident Response Plan
4658
+ \u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
4659
+ \u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
4660
+ \u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
4661
+ \u25A1 Identify responsible personnel and contacts for each project account and resource
4662
+
4663
+ \u26A0\uFE0F Test/Development Environment Handling
4664
+ \u25A1 Shut down non-critical systems during the drill period
4665
+ \u25A1 Shut down test/dev environments or maintain same security baseline as production
4666
+ \u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation
4667
+
4668
+ \u26A0\uFE0F On-Duty Team Formation
4669
+ \u25A1 7\xD724 monitoring and rapid response team
4670
+ \u25A1 Technical and risk analysis team
4671
+ \u25A1 Security policy deployment team
4672
+ \u25A1 Business response team
4673
+ \u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)
4674
+
4675
+ \u26A0\uFE0F Ingress/Egress Path Architecture Diagram
4676
+ \u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
4677
+ \u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
4678
+ \u25A1 Identify all internet-facing data interaction interfaces
4679
+
4680
+ \u26A0\uFE0F Proactive Penetration Testing
4681
+ \u25A1 Contact security vendors for simulated attack drills before the exercise
4682
+ \u25A1 Conduct security hardening based on penetration test reports
4683
+ \u25A1 Monitor AWS security advisories (known vulnerabilities and patches)
4684
+
4685
+ \u26A0\uFE0F WAR-ROOM Real-Time Communication
4686
+ \u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
4687
+ \u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
4688
+ \u25A1 Standardize case title format: "[CyberDrill] + Issue Description"
4689
+
4690
+ \u26A0\uFE0F Password & Credential Management
4691
+ \u25A1 All IAM users must have MFA enabled
4692
+ \u25A1 Access key rotation cycle \u2264 90 days
4693
+ \u25A1 Avoid shared account usage
4694
+ \u25A1 No plaintext passwords in S3/Lambda/application code
4695
+
4696
+ \u26A0\uFE0F Post-Drill Optimization
4697
+ \u25A1 Address and remediate each item from the attack report
4698
+ \u25A1 Establish periodic security maintenance processes with the security team
4699
+ \u25A1 Continuously fill security risk gaps
4700
+
4701
+ Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)
4702
+ `
4703
+ };
4704
+
4705
+ // src/i18n/index.ts
4706
+ var translations = {
4707
+ zh: zhI18n,
4708
+ en: enI18n
4709
+ };
4710
+ function getI18n(lang = "zh") {
4711
+ return translations[lang] ?? translations.zh;
4712
+ }
4713
+
4199
4714
  // src/tools/report-tool.ts
4200
4715
  var SEVERITY_ICON = {
4201
4716
  CRITICAL: "\u{1F534}",
@@ -4213,38 +4728,45 @@ function formatDuration(start, end) {
4213
4728
  const remainSecs = secs % 60;
4214
4729
  return `${mins}m ${remainSecs}s`;
4215
4730
  }
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) {
4731
+ function generateMarkdownReport(scanResults, lang) {
4732
+ const t = getI18n(lang ?? "zh");
4230
4733
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
4231
4734
  const date = scanStart.split("T")[0];
4232
4735
  const duration = formatDuration(scanStart, scanEnd);
4736
+ const sevLabel = {
4737
+ CRITICAL: t.critical,
4738
+ HIGH: t.high,
4739
+ MEDIUM: t.medium,
4740
+ LOW: t.low
4741
+ };
4742
+ function renderFinding(f) {
4743
+ const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
4744
+ return [
4745
+ `#### ${f.title}`,
4746
+ `- **${t.resource}:** ${f.resourceId} (\`${f.resourceArn}\`)`,
4747
+ `- **${t.description}:** ${f.description}`,
4748
+ `- **${t.impact}:** ${f.impact}`,
4749
+ `- **${t.riskScore}:** ${f.riskScore}/10`,
4750
+ `- **${t.remediation}:**`,
4751
+ steps,
4752
+ `- **${t.priority}:** ${f.priority}`
4753
+ ].join("\n");
4754
+ }
4233
4755
  const lines = [];
4234
- lines.push(`# AWS Security Scan Report \u2014 ${date}`);
4756
+ lines.push(`# ${t.securityReportTitle} \u2014 ${date}`);
4235
4757
  lines.push("");
4236
- lines.push("## Executive Summary");
4237
- lines.push(`- **Account:** ${accountId}`);
4238
- lines.push(`- **Region:** ${region}`);
4239
- lines.push(`- **Scan Duration:** ${duration}`);
4758
+ lines.push(`## ${t.executiveSummary}`);
4759
+ lines.push(`- **${t.account}:** ${accountId}`);
4760
+ lines.push(`- **${t.region}:** ${region}`);
4761
+ lines.push(`- **${t.duration}:** ${duration}`);
4240
4762
  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)`
4763
+ `- **${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
4764
  );
4243
4765
  lines.push("");
4244
4766
  if (summary.totalFindings === 0) {
4245
- lines.push("## Findings by Severity");
4767
+ lines.push(`## ${t.findingsBySeverity}`);
4246
4768
  lines.push("");
4247
- lines.push("\u2705 No security issues found.");
4769
+ lines.push(`\u2705 ${t.noIssuesFound}`);
4248
4770
  lines.push("");
4249
4771
  } else {
4250
4772
  const allFindings = modules.flatMap((m) => m.findings);
@@ -4255,15 +4777,15 @@ function generateMarkdownReport(scanResults) {
4255
4777
  for (const f of allFindings) {
4256
4778
  grouped.get(f.severity).push(f);
4257
4779
  }
4258
- lines.push("## Findings by Severity");
4780
+ lines.push(`## ${t.findingsBySeverity}`);
4259
4781
  lines.push("");
4260
4782
  for (const sev of SEVERITY_ORDER) {
4261
4783
  const findings = grouped.get(sev);
4262
4784
  const icon = SEVERITY_ICON[sev];
4263
- lines.push(`### ${icon} ${sev.charAt(0)}${sev.slice(1).toLowerCase()}`);
4785
+ lines.push(`### ${icon} ${sevLabel[sev]}`);
4264
4786
  lines.push("");
4265
4787
  if (findings.length === 0) {
4266
- lines.push(`No ${sev.toLowerCase()} findings.`);
4788
+ lines.push(t.noFindingsForSeverity(sevLabel[sev]));
4267
4789
  lines.push("");
4268
4790
  continue;
4269
4791
  }
@@ -4274,9 +4796,9 @@ function generateMarkdownReport(scanResults) {
4274
4796
  }
4275
4797
  }
4276
4798
  }
4277
- lines.push("## Scan Statistics");
4799
+ lines.push(`## ${t.scanStatistics}`);
4278
4800
  lines.push(
4279
- "| Module | Resources Scanned | Findings | Status |"
4801
+ `| ${t.module} | ${t.resources} | ${t.findings} | ${t.status} |`
4280
4802
  );
4281
4803
  lines.push("|--------|------------------|----------|--------|");
4282
4804
  for (const m of modules) {
@@ -4289,7 +4811,7 @@ function generateMarkdownReport(scanResults) {
4289
4811
  if (summary.totalFindings > 0) {
4290
4812
  const allFindings = modules.flatMap((m) => m.findings);
4291
4813
  allFindings.sort((a, b) => b.riskScore - a.riskScore);
4292
- lines.push("## Recommendations (Priority Order)");
4814
+ lines.push(`## ${t.recommendations}`);
4293
4815
  for (let i = 0; i < allFindings.length; i++) {
4294
4816
  const f = allFindings[i];
4295
4817
  lines.push(`${i + 1}. [${f.priority}] ${f.title}: ${f.remediationSteps[0] ?? "Review and remediate."}`);
@@ -6336,13 +6858,6 @@ var MLPS3_CATEGORY_ORDER = [
6336
6858
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6337
6859
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6338
6860
  ];
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
6861
  var MLPS3_CHECK_MAPPING = [
6347
6862
  // =========================================================================
6348
6863
  // 安全物理环境 — L3-PES1-* (22 items) → cloud_provider
@@ -7002,7 +7517,9 @@ function evaluateAllFullChecks(scanResults) {
7002
7517
  return evaluateFullCheck(item, mapping, allFindings, scanModules);
7003
7518
  });
7004
7519
  }
7005
- function generateMlps3Report(scanResults) {
7520
+ function generateMlps3Report(scanResults, lang) {
7521
+ const t = getI18n(lang ?? "zh");
7522
+ const isEn = (lang ?? "zh") === "en";
7006
7523
  const { accountId, region, scanStart } = scanResults;
7007
7524
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
7008
7525
  const results = evaluateAllFullChecks(scanResults);
@@ -7014,27 +7531,28 @@ function generateMlps3Report(scanResults) {
7014
7531
  const cloudCount = results.filter((r) => r.status === "cloud_provider").length;
7015
7532
  const manualCount = results.filter((r) => r.status === "manual").length;
7016
7533
  const naCount = results.filter((r) => r.status === "not_applicable").length;
7534
+ const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
7535
+ const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
7017
7536
  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**");
7537
+ lines.push(`# ${t.mlpsTitle}`);
7538
+ lines.push(`> **${t.mlpsDisclaimer}**`);
7021
7539
  lines.push("");
7022
- lines.push("## \u8D26\u6237\u4FE1\u606F");
7023
- lines.push(`- Account: ${accountId} | Region: ${region} | \u626B\u63CF\u65F6\u95F4: ${scanTime}`);
7540
+ lines.push(`## ${t.accountInfo}`);
7541
+ lines.push(`- ${t.account}: ${accountId} | ${t.region}: ${region} | ${t.scanTime}: ${scanTime}`);
7024
7542
  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`);
7543
+ lines.push(`## ${t.preCheckOverview}`);
7544
+ lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
7027
7545
  if (autoUnknown > 0) {
7028
- lines.push(`- \u672A\u68C0\u67E5: ${autoUnknown} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`);
7546
+ lines.push(`- ${t.uncheckedCount(autoUnknown)}`);
7029
7547
  }
7030
- lines.push(`- \u4E91\u5E73\u53F0\u8D1F\u8D23: ${cloudCount} \u9879`);
7031
- lines.push(`- \u9700\u4EBA\u5DE5\u8BC4\u4F30: ${manualCount} \u9879`);
7548
+ lines.push(`- ${t.cloudProviderCount(cloudCount)}`);
7549
+ lines.push(`- ${t.manualReviewCount(manualCount)}`);
7032
7550
  if (naCount > 0) {
7033
- lines.push(`- \u4E0D\u9002\u7528: ${naCount} \u9879`);
7551
+ lines.push(`- ${t.naCount(naCount)}`);
7034
7552
  }
7035
7553
  lines.push("");
7036
7554
  for (const category of MLPS3_CATEGORY_ORDER) {
7037
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
7555
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
7038
7556
  const catResults = results.filter(
7039
7557
  (r) => r.item.categoryCn === category && r.status !== "not_applicable"
7040
7558
  );
@@ -7047,18 +7565,20 @@ function generateMlps3Report(scanResults) {
7047
7565
  if (!controlMap.has(key)) controlMap.set(key, []);
7048
7566
  controlMap.get(key).push(r);
7049
7567
  }
7050
- for (const [controlName, controlResults] of controlMap) {
7568
+ for (const [_controlKey, controlResults] of controlMap) {
7569
+ const controlName = itemControl(controlResults[0]);
7051
7570
  lines.push(`### ${controlName}`);
7052
7571
  for (const r of controlResults) {
7053
7572
  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}`);
7573
+ 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}`;
7574
+ const reqText = itemReq(r);
7575
+ lines.push(`- [${icon}] ${r.item.id} ${reqText.slice(0, 60)}${reqText.length > 60 ? "\u2026" : ""}${suffix}`);
7056
7576
  if (r.status === "issues" && r.relatedFindings.length > 0) {
7057
7577
  for (const f of r.relatedFindings.slice(0, 3)) {
7058
7578
  lines.push(` - ${f.severity}: ${f.title}`);
7059
7579
  }
7060
7580
  if (r.relatedFindings.length > 3) {
7061
- lines.push(` - ... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 3} \u9879`);
7581
+ lines.push(` - ${t.andMore(r.relatedFindings.length - 3)}`);
7062
7582
  }
7063
7583
  }
7064
7584
  }
@@ -7067,7 +7587,7 @@ function generateMlps3Report(scanResults) {
7067
7587
  }
7068
7588
  const failedResults = results.filter((r) => r.status === "issues");
7069
7589
  if (failedResults.length > 0) {
7070
- lines.push("## \u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09");
7590
+ lines.push(`## ${t.remediationByPriority}`);
7071
7591
  lines.push("");
7072
7592
  const allFailedFindings = /* @__PURE__ */ new Map();
7073
7593
  for (const r of failedResults) {
@@ -7090,7 +7610,7 @@ function generateMlps3Report(scanResults) {
7090
7610
  lines.push("");
7091
7611
  }
7092
7612
  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`);
7613
+ lines.push(`> ${t.naNote(naCount)}`);
7094
7614
  lines.push("");
7095
7615
  }
7096
7616
  return lines.join("\n");
@@ -7100,6 +7620,15 @@ function generateMlps3Report(scanResults) {
7100
7620
  function esc(s) {
7101
7621
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
7102
7622
  }
7623
+ function escWithLinks(s) {
7624
+ const parts = s.split(/(https?:\/\/\S+)/);
7625
+ return parts.map((part, i) => {
7626
+ if (i % 2 === 1) {
7627
+ return `<a href="${esc(part)}" style="color:#60a5fa" target="_blank" rel="noopener">${esc(part)}</a>`;
7628
+ }
7629
+ return esc(part);
7630
+ }).join("");
7631
+ }
7103
7632
  function calcScore(summary) {
7104
7633
  const raw = 100 - summary.critical * 15 - summary.high * 5 - summary.medium * 2 - summary.low * 0.5;
7105
7634
  return Math.max(0, Math.min(100, Math.round(raw)));
@@ -7123,50 +7652,6 @@ function scoreColor(score) {
7123
7652
  if (score >= 50) return "#eab308";
7124
7653
  return "#ef4444";
7125
7654
  }
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
7655
  var SERVICE_NOT_ENABLED_PATTERNS = [
7171
7656
  "not enabled",
7172
7657
  "not found",
@@ -7176,10 +7661,11 @@ var SERVICE_NOT_ENABLED_PATTERNS = [
7176
7661
  "not available",
7177
7662
  "is not enabled"
7178
7663
  ];
7179
- function getDisabledServices(modules) {
7664
+ function getDisabledServices(modules, lang) {
7665
+ const t = getI18n(lang ?? "zh");
7180
7666
  const disabled = [];
7181
7667
  for (const mod of modules) {
7182
- const rec = SERVICE_RECOMMENDATIONS[mod.module];
7668
+ const rec = t.serviceRecommendations[mod.module];
7183
7669
  if (!rec) continue;
7184
7670
  if (!mod.warnings?.length) continue;
7185
7671
  const hasNotEnabled = mod.warnings.some(
@@ -7191,21 +7677,22 @@ function getDisabledServices(modules) {
7191
7677
  }
7192
7678
  return disabled;
7193
7679
  }
7194
- function buildServiceReminderHtml(modules) {
7195
- const disabled = getDisabledServices(modules);
7680
+ function buildServiceReminderHtml(modules, lang) {
7681
+ const t = getI18n(lang ?? "zh");
7682
+ const disabled = getDisabledServices(modules, lang);
7196
7683
  if (disabled.length === 0) return "";
7197
7684
  const items = disabled.map((svc) => `
7198
7685
  <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>
7686
+ <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} ${esc(t.notEnabled)}</div>
7687
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceImpact)}\uFF1A${esc(svc.impact)}</div>
7688
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceAction)}\uFF1A${esc(svc.action)}</div>
7202
7689
  </div>`).join("\n");
7203
7690
  return `
7204
7691
  <section>
7205
7692
  <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>
7693
+ <div style="font-size:17px;font-weight:700;margin-bottom:12px">${esc(t.serviceReminderTitle)}</div>
7207
7694
  ${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>
7695
+ <div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500">${esc(t.serviceReminderFooter)}</div>
7209
7696
  </div>
7210
7697
  </section>`;
7211
7698
  }
@@ -7407,12 +7894,12 @@ function donutChart(summary) {
7407
7894
  "</svg>"
7408
7895
  ].join("\n");
7409
7896
  }
7410
- function barChart(modules) {
7897
+ function barChart(modules, allCleanLabel = "All modules clean") {
7411
7898
  const withFindings = modules.filter((m) => m.findingsCount > 0).sort((a, b) => b.findingsCount - a.findingsCount).slice(0, 12);
7412
7899
  if (withFindings.length === 0) {
7413
7900
  return [
7414
7901
  '<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>',
7902
+ ` <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">${esc(allCleanLabel)}</text>`,
7416
7903
  "</svg>"
7417
7904
  ].join("\n");
7418
7905
  }
@@ -7527,7 +8014,9 @@ function scoreTrendChart(history) {
7527
8014
  "</svg>"
7528
8015
  ].join("\n");
7529
8016
  }
7530
- function generateHtmlReport(scanResults, history) {
8017
+ function generateHtmlReport(scanResults, history, lang) {
8018
+ const t = getI18n(lang ?? "zh");
8019
+ const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
7531
8020
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
7532
8021
  const date = scanStart.split("T")[0];
7533
8022
  const duration = formatDuration2(scanStart, scanEnd);
@@ -7545,23 +8034,23 @@ function generateHtmlReport(scanResults, history) {
7545
8034
  <div class="top5-content">
7546
8035
  <span class="badge badge-${esc(f.severity.toLowerCase())}">${esc(f.severity)}</span>
7547
8036
  <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>
8037
+ <div class="top5-detail"><strong>${t.resource}:</strong> ${esc(f.resourceId)}</div>
8038
+ <div class="top5-detail"><strong>${t.impact}:</strong> ${esc(f.impact)}</div>
8039
+ <div class="top5-detail"><strong>${t.riskScore}:</strong> ${f.riskScore}/10</div>
8040
+ <h4>${t.remediation}</h4>
8041
+ <ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
7553
8042
  </div>
7554
8043
  </div>`
7555
8044
  ).join("\n");
7556
8045
  top5Html = `
7557
8046
  <section>
7558
- <h2>Top ${top5.length} Highest Risk Findings</h2>
8047
+ <h2>${esc(t.topHighestRiskFindings(top5.length))}</h2>
7559
8048
  ${cards}
7560
8049
  </section>`;
7561
8050
  }
7562
8051
  let findingsHtml;
7563
8052
  if (summary.totalFindings === 0) {
7564
- findingsHtml = '<div class="no-findings">No security issues found.</div>';
8053
+ findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
7565
8054
  } else {
7566
8055
  const FOLD_THRESHOLD = 20;
7567
8056
  const renderCard = (f) => {
@@ -7570,10 +8059,10 @@ function generateHtmlReport(scanResults, history) {
7570
8059
  <span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
7571
8060
  <span class="finding-title-text">${esc(f.title)}</span>
7572
8061
  <span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
7573
- <details><summary>Details</summary><div class="finding-card-body">
8062
+ <details><summary>${t.details}</summary><div class="finding-card-body">
7574
8063
  <p>${esc(f.description)}</p>
7575
- <p><strong>Remediation:</strong></p>
7576
- <ol>${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
8064
+ <p><strong>${t.remediation}:</strong></p>
8065
+ <ol>${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
7577
8066
  </div></details>
7578
8067
  </div>`;
7579
8068
  };
@@ -7584,7 +8073,7 @@ function generateHtmlReport(scanResults, history) {
7584
8073
  const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
7585
8074
  const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
7586
8075
  return `${first}
7587
- <details><summary>Show remaining ${findings.length - FOLD_THRESHOLD} findings...</summary>
8076
+ <details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
7588
8077
  ${rest}
7589
8078
  </details>`;
7590
8079
  };
@@ -7636,13 +8125,13 @@ ${rest}
7636
8125
  if (history && history.length >= 2) {
7637
8126
  trendHtml = `
7638
8127
  <section class="trend-section">
7639
- <h2>30-Day Trends</h2>
8128
+ <h2>${esc(t.trendTitle)}</h2>
7640
8129
  <div class="trend-chart">
7641
- <div class="trend-title">Findings by Severity</div>
8130
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7642
8131
  ${findingsTrendChart(history)}
7643
8132
  </div>
7644
8133
  <div class="trend-chart">
7645
- <div class="trend-title">Security Score</div>
8134
+ <div class="trend-title">${esc(t.securityScore)}</div>
7646
8135
  ${scoreTrendChart(history)}
7647
8136
  </div>
7648
8137
  </section>`;
@@ -7653,16 +8142,57 @@ ${rest}
7653
8142
  let recsHtml = "";
7654
8143
  if (summary.totalFindings > 0) {
7655
8144
  const recMap = /* @__PURE__ */ new Map();
8145
+ const kbPatches = [];
8146
+ let kbSeverity = "LOW";
8147
+ let kbUrl;
8148
+ const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7656
8149
  for (const f of allFindings) {
7657
8150
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
8151
+ const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
8152
+ if (genericPatterns.some((p) => rem.startsWith(p))) continue;
8153
+ const kbMatch = f.title.match(/KB\d+/);
8154
+ if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
8155
+ kbPatches.push(kbMatch[0]);
8156
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(kbSeverity)) kbSeverity = f.severity;
8157
+ if (!kbUrl && url) kbUrl = url;
8158
+ continue;
8159
+ }
8160
+ if (f.module === "security_hub_findings") {
8161
+ const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
8162
+ if (controlMatch) {
8163
+ const controlId = controlMatch[1];
8164
+ const key = `ctrl:${controlId}`;
8165
+ const existing2 = recMap.get(key);
8166
+ if (existing2) {
8167
+ existing2.count++;
8168
+ if (!existing2.url && url) existing2.url = url;
8169
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
8170
+ } else {
8171
+ recMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
8172
+ }
8173
+ continue;
8174
+ }
8175
+ }
7658
8176
  const existing = recMap.get(rem);
7659
8177
  if (existing) {
7660
8178
  existing.count++;
8179
+ if (!existing.url && url) existing.url = url;
7661
8180
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7662
8181
  existing.severity = f.severity;
7663
8182
  }
7664
8183
  } else {
7665
- recMap.set(rem, { text: rem, severity: f.severity, count: 1 });
8184
+ recMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
8185
+ }
8186
+ }
8187
+ if (kbPatches.length > 0) {
8188
+ const unique = [...new Set(kbPatches)];
8189
+ const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
8190
+ recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
8191
+ }
8192
+ for (const [key, rec] of recMap) {
8193
+ if (key.startsWith("ctrl:") && rec.count > 1) {
8194
+ rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
8195
+ rec.count = 1;
7666
8196
  }
7667
8197
  }
7668
8198
  const uniqueRecs = [...recMap.values()].sort((a, b) => {
@@ -7673,60 +8203,61 @@ ${rest}
7673
8203
  const renderRec = (r) => {
7674
8204
  const sev = r.severity.toLowerCase();
7675
8205
  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>`;
8206
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</a>` : "";
8207
+ return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
7677
8208
  };
7678
8209
  const TOP_N = 10;
7679
8210
  const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
7680
8211
  const remaining = uniqueRecs.slice(TOP_N);
7681
8212
  const moreHtml = remaining.length > 0 ? `
7682
- <details><summary>Show ${remaining.length} more&hellip;</summary>
8213
+ <details><summary>${t.showMoreCount(remaining.length)}</summary>
7683
8214
  ${remaining.map(renderRec).join("\n")}
7684
8215
  </details>` : "";
7685
8216
  recsHtml = `
7686
8217
  <details class="rec-fold">
7687
- <summary><h2 style="margin:0;border:0;display:inline">Recommendations (${uniqueRecs.length} unique)</h2></summary>
8218
+ <summary><h2 style="margin:0;border:0;display:inline">${esc(t.recommendations)} (${uniqueRecs.length} ${esc(t.unique)})</h2></summary>
7688
8219
  <div class="rec-body">
7689
8220
  <ol>${topItems}${moreHtml}</ol>
7690
8221
  </div>
7691
8222
  </details>`;
7692
8223
  }
7693
8224
  return `<!DOCTYPE html>
7694
- <html lang="en">
8225
+ <html lang="${htmlLang}">
7695
8226
  <head>
7696
8227
  <meta charset="UTF-8">
7697
8228
  <meta name="viewport" content="width=device-width,initial-scale=1">
7698
- <title>AWS Security Scan Report &mdash; ${esc(date)}</title>
8229
+ <title>${esc(t.securityReportTitle)} &mdash; ${esc(date)}</title>
7699
8230
  <style>${sharedCss()}</style>
7700
8231
  </head>
7701
8232
  <body>
7702
8233
  <div class="container">
7703
8234
 
7704
8235
  <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>
8236
+ <h1>&#128737;&#65039; ${esc(t.securityReportTitle)}</h1>
8237
+ <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(date)} | ${esc(t.duration)}: ${esc(duration)}</div>
7707
8238
  </header>
7708
8239
 
7709
8240
  <section class="summary">
7710
8241
  <div class="score-card">
7711
8242
  <div class="score-value" style="color:${scoreColor(score)}">${score}</div>
7712
- <div class="score-label">Security Score</div>
8243
+ <div class="score-label">${esc(t.securityScore)}</div>
7713
8244
  </div>
7714
8245
  <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>
8246
+ <div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">${esc(t.critical)}</div></div>
8247
+ <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">${esc(t.high)}</div></div>
8248
+ <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">${esc(t.medium)}</div></div>
8249
+ <div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">${esc(t.low)}</div></div>
7719
8250
  </div>
7720
8251
  </section>
7721
8252
 
7722
8253
  <section class="charts">
7723
8254
  <div class="chart-box">
7724
- <div class="chart-title">Severity Distribution</div>
8255
+ <div class="chart-title">${esc(t.severityDistribution)}</div>
7725
8256
  <div style="text-align:center">${donutChart(summary)}</div>
7726
8257
  </div>
7727
8258
  <div class="chart-box">
7728
- <div class="chart-title">Findings by Module</div>
7729
- ${barChart(modules)}
8259
+ <div class="chart-title">${esc(t.findingsByModule)}</div>
8260
+ ${barChart(modules, t.allModulesClean)}
7730
8261
  </div>
7731
8262
  </section>
7732
8263
 
@@ -7734,33 +8265,35 @@ ${trendHtml}
7734
8265
 
7735
8266
  ${top5Html}
7736
8267
 
7737
- ${buildServiceReminderHtml(modules)}
8268
+ ${buildServiceReminderHtml(modules, lang)}
7738
8269
 
7739
8270
  <section>
7740
- <h2>Scan Statistics</h2>
8271
+ <h2>${esc(t.scanStatistics)}</h2>
7741
8272
  <table>
7742
- <thead><tr><th>Module</th><th>Resources</th><th>Findings</th><th>Status</th></tr></thead>
8273
+ <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
8274
  <tbody>${statsRows}</tbody>
7744
8275
  </table>
7745
8276
  </section>
7746
8277
 
7747
8278
  <section>
7748
- <h2>All Findings</h2>
8279
+ <h2>${esc(t.allFindings)}</h2>
7749
8280
  ${findingsHtml}
7750
8281
  </section>
7751
8282
 
7752
8283
  ${recsHtml}
7753
8284
 
7754
8285
  <footer>
7755
- <p>Generated by AWS Security MCP Server v${VERSION}</p>
7756
- <p>This report is for informational purposes only.</p>
8286
+ <p>${esc(t.generatedBy)} v${VERSION}</p>
8287
+ <p>${esc(t.informationalOnly)}</p>
7757
8288
  </footer>
7758
8289
 
7759
8290
  </div>
7760
8291
  </body>
7761
8292
  </html>`;
7762
8293
  }
7763
- function generateMlps3HtmlReport(scanResults, history) {
8294
+ function generateMlps3HtmlReport(scanResults, history, lang) {
8295
+ const t = getI18n(lang ?? "zh");
8296
+ const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
7764
8297
  const { accountId, region, scanStart } = scanResults;
7765
8298
  const date = scanStart.split("T")[0];
7766
8299
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
@@ -7777,17 +8310,21 @@ function generateMlps3HtmlReport(scanResults, history) {
7777
8310
  if (history && history.length >= 2) {
7778
8311
  trendHtml = `
7779
8312
  <section class="trend-section">
7780
- <h2>30\u65E5\u8D8B\u52BF</h2>
8313
+ <h2>${esc(t.trendTitle)}</h2>
7781
8314
  <div class="trend-chart">
7782
- <div class="trend-title">\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0</div>
8315
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7783
8316
  ${findingsTrendChart(history)}
7784
8317
  </div>
7785
8318
  <div class="trend-chart">
7786
- <div class="trend-title">\u5B89\u5168\u8BC4\u5206</div>
8319
+ <div class="trend-title">${esc(t.securityScore)}</div>
7787
8320
  ${scoreTrendChart(history)}
7788
8321
  </div>
7789
8322
  </section>`;
7790
8323
  }
8324
+ const isEn = (lang ?? "zh") === "en";
8325
+ const itemCat = (r) => isEn ? r.item.categoryEn : r.item.categoryCn;
8326
+ const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
8327
+ const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
7791
8328
  const categoryMap = /* @__PURE__ */ new Map();
7792
8329
  for (const r of results) {
7793
8330
  if (r.status === "not_applicable") continue;
@@ -7796,7 +8333,7 @@ function generateMlps3HtmlReport(scanResults, history) {
7796
8333
  categoryMap.get(cat).push(r);
7797
8334
  }
7798
8335
  const categorySections = MLPS3_CATEGORY_ORDER.map((category) => {
7799
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
8336
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
7800
8337
  const catResults = categoryMap.get(category);
7801
8338
  if (!catResults || catResults.length === 0) return "";
7802
8339
  const allCloud = catResults.every((r) => r.status === "cloud_provider");
@@ -7804,11 +8341,11 @@ function generateMlps3HtmlReport(scanResults, history) {
7804
8341
  return `<details class="category-fold mlps-cloud-section">
7805
8342
  <summary>
7806
8343
  <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>
8344
+ <span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length} ${esc(t.cloudProvider)}</span></span>
7808
8345
  </summary>
7809
8346
  <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")}
8347
+ <div class="mlps-cloud-note">${esc(t.cloudItemsNote(catResults.length))}</div>
8348
+ ${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
8349
  </div>
7813
8350
  </details>`;
7814
8351
  }
@@ -7830,31 +8367,34 @@ function generateMlps3HtmlReport(scanResults, history) {
7830
8367
  if (!controlMap.has(key)) controlMap.set(key, []);
7831
8368
  controlMap.get(key).push(r);
7832
8369
  }
7833
- const controlGroups = [...controlMap.entries()].map(([controlName, controlResults]) => {
8370
+ const controlGroups = [...controlMap.entries()].map(([_controlKey, controlResults]) => {
8371
+ const controlName = itemControl(controlResults[0]);
7834
8372
  const cloudItems = controlResults.filter((r) => r.status === "cloud_provider");
7835
8373
  const nonCloudItems = controlResults.filter((r) => r.status !== "cloud_provider");
7836
8374
  let itemsHtml = "";
7837
8375
  for (const r of nonCloudItems) {
7838
8376
  const icon = r.status === "clean" ? "\u{1F7E2}" : r.status === "issues" ? "\u{1F534}" : r.status === "unknown" ? "\u2B1C" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
7839
8377
  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")}` : "";
8378
+ const suffix = r.status === "unknown" ? ` \u2014 ${esc(t.notChecked)}` : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? t.manualReview)}` : "";
7841
8379
  let findingsDetail = "";
7842
8380
  if (r.status === "clean") {
7843
- findingsDetail = `<div class="check-detail">\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898</div>`;
8381
+ findingsDetail = `<div class="check-detail">${esc(t.noRelatedIssues)}</div>`;
7844
8382
  } else if (r.status === "issues" && r.relatedFindings.length > 0) {
7845
8383
  const fItems = r.relatedFindings.slice(0, 5).map((f) => `<li>${esc(f.severity)}: ${esc(f.title)}</li>`);
7846
8384
  if (r.relatedFindings.length > 5) {
7847
- fItems.push(`<li>... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 5} \u9879</li>`);
8385
+ fItems.push(`<li>${esc(t.andMore(r.relatedFindings.length - 5))}</li>`);
7848
8386
  }
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>`;
8387
+ 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>` : "";
8388
+ 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
8389
  }
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>
8390
+ const reqText = itemReq(r);
8391
+ 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
8392
  ${findingsDetail}`;
7854
8393
  }
7855
8394
  if (cloudItems.length > 0) {
7856
8395
  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>
8396
+ const reqText = itemReq(r);
8397
+ 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
8398
  `;
7859
8399
  }
7860
8400
  }
@@ -7887,20 +8427,61 @@ ${itemsHtml}
7887
8427
  let remediationHtml = "";
7888
8428
  if (failedResults.length > 0) {
7889
8429
  const mlpsRecMap = /* @__PURE__ */ new Map();
8430
+ const mlpsKbPatches = [];
8431
+ let mlpsKbSeverity = "LOW";
8432
+ let mlpsKbUrl;
8433
+ const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7890
8434
  for (const r of failedResults) {
7891
8435
  for (const f of r.relatedFindings) {
7892
8436
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
8437
+ const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
8438
+ if (mlpsGenericPatterns.some((p) => rem.startsWith(p))) continue;
8439
+ const kbMatch = f.title.match(/KB\d+/);
8440
+ if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
8441
+ mlpsKbPatches.push(kbMatch[0]);
8442
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsKbSeverity)) mlpsKbSeverity = f.severity;
8443
+ if (!mlpsKbUrl && url) mlpsKbUrl = url;
8444
+ continue;
8445
+ }
8446
+ if (f.module === "security_hub_findings") {
8447
+ const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
8448
+ if (controlMatch) {
8449
+ const controlId = controlMatch[1];
8450
+ const key = `ctrl:${controlId}`;
8451
+ const existing2 = mlpsRecMap.get(key);
8452
+ if (existing2) {
8453
+ existing2.count++;
8454
+ if (!existing2.url && url) existing2.url = url;
8455
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
8456
+ } else {
8457
+ mlpsRecMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
8458
+ }
8459
+ continue;
8460
+ }
8461
+ }
7893
8462
  const existing = mlpsRecMap.get(rem);
7894
8463
  if (existing) {
7895
8464
  existing.count++;
8465
+ if (!existing.url && url) existing.url = url;
7896
8466
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7897
8467
  existing.severity = f.severity;
7898
8468
  }
7899
8469
  } else {
7900
- mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
8470
+ mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
7901
8471
  }
7902
8472
  }
7903
8473
  }
8474
+ if (mlpsKbPatches.length > 0) {
8475
+ const unique = [...new Set(mlpsKbPatches)];
8476
+ const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
8477
+ mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
8478
+ }
8479
+ for (const [key, rec] of mlpsRecMap) {
8480
+ if (key.startsWith("ctrl:") && rec.count > 1) {
8481
+ rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
8482
+ rec.count = 1;
8483
+ }
8484
+ }
7904
8485
  const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
7905
8486
  const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
7906
8487
  if (sevDiff !== 0) return sevDiff;
@@ -7910,26 +8491,27 @@ ${itemsHtml}
7910
8491
  const renderMlpsRec = (r) => {
7911
8492
  const sev = r.severity.toLowerCase();
7912
8493
  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>`;
8494
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</a>` : "";
8495
+ return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
7914
8496
  };
7915
8497
  const MLPS_TOP_N = 10;
7916
8498
  const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
7917
8499
  const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
7918
8500
  const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
7919
- <details><summary>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879&hellip;</summary>
8501
+ <details><summary>${esc(t.showRemaining(mlpsRemaining.length))}&hellip;</summary>
7920
8502
  ${mlpsRemaining.map(renderMlpsRec).join("\n")}
7921
8503
  </details>` : "";
7922
8504
  remediationHtml = `
7923
8505
  <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>
8506
+ <summary><h2 style="margin:0;border:0;display:inline">${esc(t.remediationItems(mlpsUniqueRecs.length))}</h2></summary>
7925
8507
  <div class="rec-body">
7926
8508
  <ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
7927
8509
  </div>
7928
8510
  </details>`;
7929
8511
  }
7930
8512
  }
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>` : "";
8513
+ const naNote = naCount > 0 ? `<p style="color:#64748b;font-size:13px;margin-top:24px">${esc(t.naNote(naCount))}</p>` : "";
8514
+ const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">${esc(t.unknownNote(autoUnknown))}</div>` : "";
7933
8515
  const mlpsCss = `
7934
8516
  .mlps-cloud-section>summary{color:#94a3b8}
7935
8517
  .mlps-cloud-note{color:#94a3b8;font-size:13px;margin-bottom:12px;font-style:italic}
@@ -7951,40 +8533,40 @@ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
7951
8533
  .mlps-summary-card .stat-label{font-size:12px;color:#94a3b8;margin-top:2px}
7952
8534
  `;
7953
8535
  return `<!DOCTYPE html>
7954
- <html lang="zh-CN">
8536
+ <html lang="${htmlLang}">
7955
8537
  <head>
7956
8538
  <meta charset="UTF-8">
7957
8539
  <meta name="viewport" content="width=device-width,initial-scale=1">
7958
- <title>\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A &mdash; ${esc(date)}</title>
8540
+ <title>${esc(t.mlpsTitle)} &mdash; ${esc(date)}</title>
7959
8541
  <style>${sharedCss()}${mlpsCss}</style>
7960
8542
  </head>
7961
8543
  <body>
7962
8544
  <div class="container">
7963
8545
 
7964
8546
  <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>
8547
+ <h1>&#128737;&#65039; ${esc(t.mlpsTitle)}</h1>
8548
+ <div class="disclaimer">${esc(t.mlpsDisclaimer)}</div>
8549
+ <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(t.scanTime)}: ${esc(scanTime)}</div>
7968
8550
  </header>
7969
8551
 
7970
8552
  <section class="summary" style="display:block;text-align:center">
7971
8553
  <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>
8554
+ <span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.noIssues)}</span>
7973
8555
  <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>
8556
+ <span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.issuesFound)}</span>
7975
8557
  </div>
7976
8558
  <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>` : ""}
8559
+ <div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">${esc(t.checkedItems)}</div></div>
8560
+ <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>
8561
+ <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>
8562
+ ${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
8563
  </div>
7982
8564
  </section>
7983
8565
  ${unknownNote}
7984
8566
 
7985
8567
  ${trendHtml}
7986
8568
 
7987
- ${buildServiceReminderHtml(scanResults.modules)}
8569
+ ${buildServiceReminderHtml(scanResults.modules, lang)}
7988
8570
 
7989
8571
  ${categorySections}
7990
8572
 
@@ -7993,8 +8575,8 @@ ${remediationHtml}
7993
8575
  ${naNote}
7994
8576
 
7995
8577
  <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>
8578
+ <p>${esc(t.mlpsFooterGenerated(VERSION))}</p>
8579
+ <p>${esc(t.mlpsFooterDisclaimer)}</p>
7998
8580
  </footer>
7999
8581
 
8000
8582
  </div>
@@ -8209,16 +8791,14 @@ Aggregates active findings from AWS Security Hub. Replaces individual config sca
8209
8791
  - INFORMATIONAL findings are skipped.
8210
8792
 
8211
8793
  ## 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.
8794
+ Detection-only: checks if GuardDuty is enabled in the region.
8795
+ - GuardDuty findings are aggregated via Security Hub (security_hub_findings module).
8796
+ - Reports whether GuardDuty detectors are active.
8216
8797
 
8217
8798
  ## 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.
8799
+ Detection-only: checks if Inspector is enabled in the region.
8800
+ - Inspector findings are aggregated via Security Hub (security_hub_findings module).
8801
+ - Reports whether Inspector scanning (EC2/Lambda) is active.
8222
8802
 
8223
8803
  ## 5. Trusted Advisor Findings (trusted_advisor_findings)
8224
8804
  Aggregates security checks from AWS Trusted Advisor.
@@ -8254,19 +8834,15 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
8254
8834
  Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
8255
8835
 
8256
8836
  ## 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).
8837
+ Detection-only: checks if AWS Config Rules are configured.
8838
+ - Config Rule compliance findings are aggregated via Security Hub (security_hub_findings module).
8839
+ - Reports whether Config is enabled and counts active rules.
8262
8840
  - Gracefully handles regions where AWS Config is not enabled.
8263
8841
 
8264
8842
  ## 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.
8843
+ Detection-only: checks if IAM Access Analyzer is configured.
8844
+ - Access Analyzer findings are aggregated via Security Hub (security_hub_findings module).
8845
+ - Reports whether active analyzers exist.
8270
8846
  - Returns warning if no analyzer is configured.
8271
8847
 
8272
8848
  ## 17. SSM Patch Compliance (patch_compliance_findings)
@@ -8351,112 +8927,18 @@ var MODULE_DESCRIPTIONS = {
8351
8927
  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
8928
  disaster_recovery: "Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.",
8353
8929
  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.",
8930
+ guardduty_findings: "Checks if GuardDuty is enabled. Findings are aggregated via Security Hub.",
8931
+ inspector_findings: "Checks if Inspector is enabled. Findings are aggregated via Security Hub.",
8356
8932
  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).",
8933
+ config_rules_findings: "Checks if AWS Config Rules are configured. Findings are aggregated via Security Hub.",
8934
+ access_analyzer_findings: "Checks if IAM Access Analyzer is configured. Findings are aggregated via Security Hub.",
8359
8935
  patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches.",
8360
8936
  imdsv2_enforcement: "Checks if EC2 instances enforce IMDSv2 (HttpTokens: required) \u2014 IMDSv1 allows credential theft via SSRF.",
8361
8937
  waf_coverage: "Checks if internet-facing ALBs have WAF Web ACL associated for protection against common web exploits."
8362
8938
  };
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
- };
8939
+ function getHwDefenseChecklist(lang) {
8940
+ return getI18n(lang ?? "zh").hwChecklist;
8941
+ }
8460
8942
  var SERVICE_NOT_ENABLED_PATTERNS2 = [
8461
8943
  "not enabled",
8462
8944
  "not found",
@@ -8466,10 +8948,11 @@ var SERVICE_NOT_ENABLED_PATTERNS2 = [
8466
8948
  "not available",
8467
8949
  "is not enabled"
8468
8950
  ];
8469
- function buildServiceReminder(modules) {
8951
+ function buildServiceReminder(modules, lang) {
8952
+ const t = getI18n(lang ?? "zh");
8470
8953
  const disabledServices = [];
8471
8954
  for (const mod of modules) {
8472
- const rec = SERVICE_RECOMMENDATIONS2[mod.module];
8955
+ const rec = t.serviceRecommendations[mod.module];
8473
8956
  if (!rec) continue;
8474
8957
  if (!mod.warnings?.length) continue;
8475
8958
  const hasNotEnabled = mod.warnings.some(
@@ -8482,26 +8965,26 @@ function buildServiceReminder(modules) {
8482
8965
  if (disabledServices.length === 0) return "";
8483
8966
  const lines = [
8484
8967
  "",
8485
- "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
8968
+ t.serviceReminderTitle,
8486
8969
  ""
8487
8970
  ];
8488
8971
  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}`);
8972
+ lines.push(`${svc.icon} ${svc.service} ${t.notEnabled}`);
8973
+ lines.push(` ${t.serviceImpact}: ${svc.impact}`);
8974
+ lines.push(` ${t.serviceAction}: ${svc.action}`);
8492
8975
  lines.push("");
8493
8976
  }
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");
8977
+ lines.push(t.serviceReminderFooter);
8495
8978
  return lines.join("\n");
8496
8979
  }
8497
- function summarizeResult(result) {
8980
+ function summarizeResult(result, lang) {
8498
8981
  const { summary } = result;
8499
8982
  const lines = [
8500
8983
  `Scan complete for account ${result.accountId} in ${result.region}.`,
8501
8984
  `Total findings: ${summary.totalFindings} (${summary.critical} Critical, ${summary.high} High, ${summary.medium} Medium, ${summary.low} Low)`,
8502
8985
  `Modules: ${summary.modulesSuccess} succeeded, ${summary.modulesError} errored`
8503
8986
  ];
8504
- const reminder = buildServiceReminder(result.modules);
8987
+ const reminder = buildServiceReminder(result.modules, lang);
8505
8988
  if (reminder) {
8506
8989
  lines.push(reminder);
8507
8990
  }
@@ -8563,9 +9046,10 @@ function createServer(defaultRegion) {
8563
9046
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8564
9047
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8565
9048
  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)")
9049
+ account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
9050
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8567
9051
  },
8568
- async ({ region, org_mode, role_name, account_ids }) => {
9052
+ async ({ region, org_mode, role_name, account_ids, lang }) => {
8569
9053
  try {
8570
9054
  const r = region ?? defaultRegion;
8571
9055
  let result;
@@ -8580,7 +9064,7 @@ function createServer(defaultRegion) {
8580
9064
  }
8581
9065
  return {
8582
9066
  content: [
8583
- { type: "text", text: summarizeResult(result) },
9067
+ { type: "text", text: summarizeResult(result, lang ?? "zh") },
8584
9068
  { type: "text", text: JSON.stringify(result, null, 2) }
8585
9069
  ]
8586
9070
  };
@@ -8641,9 +9125,10 @@ function createServer(defaultRegion) {
8641
9125
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8642
9126
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8643
9127
  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)")
9128
+ account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
9129
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8645
9130
  },
8646
- async ({ group, region, org_mode, role_name, account_ids }) => {
9131
+ async ({ group, region, org_mode, role_name, account_ids, lang }) => {
8647
9132
  try {
8648
9133
  const groupDef = SCAN_GROUPS[group];
8649
9134
  if (!groupDef) {
@@ -8725,7 +9210,7 @@ function createServer(defaultRegion) {
8725
9210
  `Scan group: ${groupDef.name} (${group})`,
8726
9211
  groupDef.description,
8727
9212
  "",
8728
- summarizeResult(result)
9213
+ summarizeResult(result, lang ?? "zh")
8729
9214
  ];
8730
9215
  if (missingModules.length > 0) {
8731
9216
  lines.push("");
@@ -8738,7 +9223,7 @@ function createServer(defaultRegion) {
8738
9223
  if (group === "hw_defense") {
8739
9224
  const summaryContent = content[0];
8740
9225
  if (summaryContent && summaryContent.type === "text") {
8741
- summaryContent.text += "\n\n" + HW_DEFENSE_CHECKLIST;
9226
+ summaryContent.text += "\n\n" + getHwDefenseChecklist(lang ?? "zh");
8742
9227
  }
8743
9228
  }
8744
9229
  return { content };
@@ -8768,11 +9253,14 @@ function createServer(defaultRegion) {
8768
9253
  server.tool(
8769
9254
  "generate_report",
8770
9255
  "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 }) => {
9256
+ {
9257
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9258
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9259
+ },
9260
+ async ({ scan_results, lang }) => {
8773
9261
  try {
8774
9262
  const parsed = JSON.parse(scan_results);
8775
- const report = generateMarkdownReport(parsed);
9263
+ const report = generateMarkdownReport(parsed, lang ?? "zh");
8776
9264
  return { content: [{ type: "text", text: report }] };
8777
9265
  } catch (err) {
8778
9266
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8782,11 +9270,14 @@ function createServer(defaultRegion) {
8782
9270
  server.tool(
8783
9271
  "generate_mlps3_report",
8784
9272
  "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 }) => {
9273
+ {
9274
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
9275
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9276
+ },
9277
+ async ({ scan_results, lang }) => {
8787
9278
  try {
8788
9279
  const parsed = JSON.parse(scan_results);
8789
- const report = generateMlps3Report(parsed);
9280
+ const report = generateMlps3Report(parsed, lang ?? "zh");
8790
9281
  return { content: [{ type: "text", text: report }] };
8791
9282
  } catch (err) {
8792
9283
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8798,13 +9289,14 @@ function createServer(defaultRegion) {
8798
9289
  "Generate a professional HTML security report. Save the output as an .html file.",
8799
9290
  {
8800
9291
  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")
9292
+ history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
9293
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8802
9294
  },
8803
- async ({ scan_results, history }) => {
9295
+ async ({ scan_results, history, lang }) => {
8804
9296
  try {
8805
9297
  const parsed = JSON.parse(scan_results);
8806
9298
  const historyData = history ? JSON.parse(history) : void 0;
8807
- const report = generateHtmlReport(parsed, historyData);
9299
+ const report = generateHtmlReport(parsed, historyData, lang ?? "zh");
8808
9300
  return { content: [{ type: "text", text: report }] };
8809
9301
  } catch (err) {
8810
9302
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8816,13 +9308,14 @@ function createServer(defaultRegion) {
8816
9308
  "Generate a professional HTML MLPS Level 3 compliance report (\u7B49\u4FDD\u4E09\u7EA7). Save as .html file.",
8817
9309
  {
8818
9310
  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")
9311
+ history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
9312
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8820
9313
  },
8821
- async ({ scan_results, history }) => {
9314
+ async ({ scan_results, history, lang }) => {
8822
9315
  try {
8823
9316
  const parsed = JSON.parse(scan_results);
8824
9317
  const historyData = history ? JSON.parse(history) : void 0;
8825
- const report = generateMlps3HtmlReport(parsed, historyData);
9318
+ const report = generateMlps3HtmlReport(parsed, historyData, lang ?? "zh");
8826
9319
  return { content: [{ type: "text", text: report }] };
8827
9320
  } catch (err) {
8828
9321
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8832,7 +9325,10 @@ function createServer(defaultRegion) {
8832
9325
  server.tool(
8833
9326
  "generate_maturity_report",
8834
9327
  "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") },
9328
+ {
9329
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9330
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9331
+ },
8836
9332
  async ({ scan_results }) => {
8837
9333
  try {
8838
9334
  const parsed = JSON.parse(scan_results);
@@ -9103,7 +9599,7 @@ ${finding}`
9103
9599
  type: "text",
9104
9600
  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
9601
 
9106
- ${HW_DEFENSE_CHECKLIST}
9602
+ ${getHwDefenseChecklist("zh")}
9107
9603
 
9108
9604
  \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
9605
  }