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.
package/dist/src/index.js CHANGED
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { z } from "zod";
5
5
 
6
6
  // src/version.ts
7
- var VERSION = "0.5.3";
7
+ var VERSION = "0.6.0";
8
8
 
9
9
  // src/utils/aws-client.ts
10
10
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -88,7 +88,9 @@ async function listOrgAccounts(region) {
88
88
  var AGGREGATION_MODULES = /* @__PURE__ */ new Set([
89
89
  "security_hub_findings",
90
90
  "guardduty_findings",
91
- "inspector_findings"
91
+ "inspector_findings",
92
+ "config_rules_findings",
93
+ "access_analyzer_findings"
92
94
  ]);
93
95
  function buildSummary(modules) {
94
96
  let critical = 0;
@@ -480,9 +482,15 @@ var ServiceDetectionScanner = class {
480
482
  const insp = createClient(Inspector2Client, region, ctx.credentials);
481
483
  const resp = await insp.send(new BatchGetAccountStatusCommand({ accountIds: [accountId] }));
482
484
  const accounts = resp.accounts ?? [];
483
- const active = accounts.some(
484
- (a) => a.state?.status === "ENABLED" || a.state?.status === "ENABLING"
485
- );
485
+ const active = accounts.some((a) => {
486
+ const s = a.state?.status;
487
+ if (s === "ENABLED" || s === "ENABLING") return true;
488
+ const rs = a.resourceState;
489
+ if (!rs) return false;
490
+ return ["ec2", "ecr", "lambda", "lambdaCode", "codeRepository"].some(
491
+ (k) => rs[k]?.status === "ENABLED"
492
+ );
493
+ });
486
494
  if (active) {
487
495
  services.push({
488
496
  name: "Inspector",
@@ -2735,14 +2743,21 @@ var SecurityHubFindingsScanner = class {
2735
2743
  const resourceType = f.Resources?.[0]?.Type ?? "AWS::Unknown";
2736
2744
  const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:securityhub:${region}:${accountId}:finding/${f.Id ?? "unknown"}`;
2737
2745
  const remediationSteps = [];
2738
- if (f.Remediation?.Recommendation?.Text) {
2739
- remediationSteps.push(f.Remediation.Recommendation.Text);
2746
+ const title = f.Title ?? "Security Hub Finding";
2747
+ if (/^KB\d+$/.test(title)) {
2748
+ remediationSteps.push(`Install Windows patch ${title} via WSUS or SSM Patch Manager`);
2749
+ remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${title}`);
2750
+ } else if (/^CVE-/.test(title)) {
2751
+ remediationSteps.push(`Fix vulnerability ${title}: update affected software to patched version`);
2752
+ } else {
2753
+ remediationSteps.push(title);
2740
2754
  }
2741
2755
  if (f.Remediation?.Recommendation?.Url) {
2742
- remediationSteps.push(`Reference: ${f.Remediation.Recommendation.Url}`);
2756
+ remediationSteps.push(`Documentation: ${f.Remediation.Recommendation.Url}`);
2743
2757
  }
2744
- if (remediationSteps.length === 0) {
2745
- remediationSteps.push("Review the finding in the AWS Security Hub console and follow the recommended remediation.");
2758
+ const recText = f.Remediation?.Recommendation?.Text ?? "";
2759
+ if (recText && !["See References", "None Provided", ""].includes(recText.trim())) {
2760
+ remediationSteps.push(recText);
2746
2761
  }
2747
2762
  findings.push({
2748
2763
  severity,
@@ -2895,10 +2910,11 @@ var GuardDutyFindingsScanner = class {
2895
2910
  impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
2896
2911
  riskScore: score,
2897
2912
  remediationSteps: [
2898
- "Review the finding in the Amazon GuardDuty console.",
2899
- `Finding type: ${gdf.Type ?? "unknown"}`,
2900
- "Follow the recommended remediation in the GuardDuty documentation."
2901
- ],
2913
+ `Investigate ${gdf.Type ?? "unknown threat"}: ${gdf.Title ?? "threat detected"}`,
2914
+ gdf.Description ? `Details: ${gdf.Description.substring(0, 200)}` : "",
2915
+ "Isolate affected resources if compromise is confirmed.",
2916
+ "Review CloudTrail logs for related suspicious activity."
2917
+ ].filter(Boolean),
2902
2918
  priority: priorityFromSeverity(severity),
2903
2919
  module: this.moduleName,
2904
2920
  accountId: gdf.AccountId ?? accountId
@@ -2989,18 +3005,44 @@ var InspectorFindingsScanner = class {
2989
3005
  const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
2990
3006
  const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
2991
3007
  const remediationSteps = [];
2992
- if (f.remediation?.recommendation?.text) {
3008
+ const vulnPkgs = f.packageVulnerabilityDetails?.vulnerablePackages;
3009
+ if (vulnPkgs?.length) {
3010
+ for (const pkg of vulnPkgs.slice(0, 3)) {
3011
+ const name = pkg.name ?? "unknown-package";
3012
+ const installed = pkg.version ?? "unknown";
3013
+ const fixed = pkg.fixedInVersion ?? "latest";
3014
+ const cveRef = cveId ? ` to fix ${cveId}` : "";
3015
+ remediationSteps.push(`Update ${name} from ${installed} to ${fixed}${cveRef}`);
3016
+ }
3017
+ } else if (f.remediation?.recommendation?.text) {
2993
3018
  remediationSteps.push(f.remediation.recommendation.text);
2994
3019
  }
3020
+ const genericPatterns = ["See References", "None Provided", "Review the finding"];
3021
+ if (remediationSteps.length === 0 || genericPatterns.some((p) => remediationSteps[0]?.startsWith(p))) {
3022
+ remediationSteps.length = 0;
3023
+ const rawTitle = f.title ?? "";
3024
+ if (rawTitle.includes("KB")) {
3025
+ const kbMatch = rawTitle.match(/KB\d+/);
3026
+ const kb = kbMatch ? kbMatch[0] : "patch";
3027
+ remediationSteps.push(`Install Windows patch ${kb} via WSUS or AWS Systems Manager Patch Manager`);
3028
+ remediationSteps.push(`Run: aws ssm send-command --document-name "AWS-InstallWindowsUpdates" --targets "Key=InstanceIds,Values=${resourceId}"`);
3029
+ if (kbMatch) {
3030
+ remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${kb}`);
3031
+ }
3032
+ } else if (rawTitle.includes("CVE-") || cveId) {
3033
+ const cveMatch = rawTitle.match(/CVE-[\d-]+/);
3034
+ const cve = cveMatch ? cveMatch[0] : cveId ?? "vulnerability";
3035
+ remediationSteps.push(`Fix ${cve}: update the affected software package to the latest patched version`);
3036
+ } else {
3037
+ remediationSteps.push(`Review and remediate: ${rawTitle}`);
3038
+ }
3039
+ }
2995
3040
  if (f.remediation?.recommendation?.Url) {
2996
- remediationSteps.push(`Reference: ${f.remediation.recommendation.Url}`);
3041
+ remediationSteps.push(`Documentation: ${f.remediation.recommendation.Url}`);
2997
3042
  }
2998
3043
  if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
2999
3044
  remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
3000
3045
  }
3001
- if (remediationSteps.length === 0) {
3002
- remediationSteps.push("Review the finding in the Amazon Inspector console and apply the recommended patch or update.");
3003
- }
3004
3046
  const description = f.description ?? titleBase;
3005
3047
  const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
3006
3048
  findings.push({
@@ -3335,10 +3377,10 @@ var ConfigRulesFindingsScanner = class {
3335
3377
  impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
3336
3378
  riskScore,
3337
3379
  remediationSteps: [
3338
- `Review the Config Rule "${ruleName}" in the AWS Config console.`,
3339
- `Check resource ${resourceId} for compliance violations.`,
3340
- "Follow the rule's remediation guidance to bring the resource into compliance."
3341
- ],
3380
+ `Fix Config Rule violation: ${ruleName}`,
3381
+ annotation ? `Details: ${annotation}` : "",
3382
+ `Resource: ${resourceType}/${resourceId}`
3383
+ ].filter(Boolean),
3342
3384
  priority: priorityFromSeverity(severity),
3343
3385
  module: this.moduleName,
3344
3386
  accountId
@@ -3487,13 +3529,13 @@ var AccessAnalyzerFindingsScanner = class {
3487
3529
  const title = buildFindingTitle(aaf);
3488
3530
  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"}`;
3489
3531
  const remediationSteps = external ? [
3490
- "Review the finding in the IAM Access Analyzer console.",
3491
- `Check resource ${resourceId} for unintended external access.`,
3492
- "Remove or restrict the resource policy to eliminate external access."
3532
+ `Restrict external access on ${resourceType} ${resourceId}`,
3533
+ "Remove or narrow the resource policy to eliminate unintended external access.",
3534
+ `Resource ARN: ${resourceArn}`
3493
3535
  ] : [
3494
- "Review the finding in the IAM Access Analyzer console.",
3495
- `Check resource ${resourceId} for unused access permissions.`,
3496
- "Remove unused permissions, roles, or credentials to follow least-privilege."
3536
+ `Remove unused access on ${resourceType} ${resourceId}`,
3537
+ "Remove unused permissions, roles, or credentials to follow least-privilege.",
3538
+ `Resource ARN: ${resourceArn}`
3497
3539
  ];
3498
3540
  findings.push({
3499
3541
  severity,
@@ -3968,6 +4010,479 @@ var WafCoverageScanner = class {
3968
4010
  }
3969
4011
  };
3970
4012
 
4013
+ // src/i18n/zh.ts
4014
+ var zhI18n = {
4015
+ // HTML Security Report
4016
+ securityReportTitle: "AWS \u5B89\u5168\u626B\u63CF\u62A5\u544A",
4017
+ securityScore: "\u5B89\u5168\u8BC4\u5206",
4018
+ critical: "\u4E25\u91CD",
4019
+ high: "\u9AD8",
4020
+ medium: "\u4E2D",
4021
+ low: "\u4F4E",
4022
+ scanStatistics: "\u626B\u63CF\u7EDF\u8BA1",
4023
+ module: "\u6A21\u5757",
4024
+ resources: "\u8D44\u6E90",
4025
+ findings: "\u53D1\u73B0",
4026
+ status: "\u72B6\u6001",
4027
+ allFindings: "\u6240\u6709\u53D1\u73B0",
4028
+ recommendations: "\u5EFA\u8BAE",
4029
+ unique: "\u53BB\u91CD",
4030
+ showMore: "\u663E\u793A\u66F4\u591A",
4031
+ noIssuesFound: "\u672A\u53D1\u73B0\u5B89\u5168\u95EE\u9898\u3002",
4032
+ allModulesClean: "\u6240\u6709\u6A21\u5757\u6B63\u5E38",
4033
+ generatedBy: "\u7531 AWS Security MCP Server \u751F\u6210",
4034
+ informationalOnly: "\u672C\u62A5\u544A\u4EC5\u4F9B\u53C2\u8003\u3002",
4035
+ // MLPS Report
4036
+ mlpsTitle: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A",
4037
+ 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",
4038
+ checkedItems: "\u5DF2\u68C0\u67E5\u9879",
4039
+ noIssues: "\u672A\u53D1\u73B0\u95EE\u9898",
4040
+ issuesFound: "\u53D1\u73B0\u95EE\u9898",
4041
+ notChecked: "\u672A\u68C0\u67E5",
4042
+ cloudProvider: "\u4E91\u5E73\u53F0\u8D1F\u8D23",
4043
+ manualReview: "\u9700\u4EBA\u5DE5\u8BC4\u4F30",
4044
+ notApplicable: "\u4E0D\u9002\u7528",
4045
+ checkResult: "\u68C0\u67E5\u7ED3\u679C",
4046
+ noRelatedIssues: "\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898",
4047
+ issuesFoundCount: (n) => `\u68C0\u67E5\u7ED3\u679C\uFF1A\u53D1\u73B0 ${n} \u4E2A\u76F8\u5173\u95EE\u9898`,
4048
+ remediation: "\u5EFA\u8BAE",
4049
+ remediationItems: (n) => `\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${n} \u9879\u53BB\u91CD\uFF09`,
4050
+ showRemaining: (n) => `\u663E\u793A\u5176\u4F59 ${n} \u9879`,
4051
+ // HW Defense Checklist
4052
+ hwChecklistTitle: "\u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09",
4053
+ hwChecklistSubtitle: "\u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A",
4054
+ hwEmergencyIsolation: `\u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
4055
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
4056
+ \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
4057
+ \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
4058
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F`,
4059
+ hwTestEnvShutdown: `\u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
4060
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
4061
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
4062
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563`,
4063
+ hwDutyTeam: `\u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
4064
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
4065
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
4066
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
4067
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
4068
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09`,
4069
+ hwNetworkDiagram: `\u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
4070
+ \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
4071
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
4072
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3`,
4073
+ hwPentest: `\u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
4074
+ \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
4075
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
4076
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09`,
4077
+ hwWarRoom: `\u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
4078
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
4079
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
4080
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D`,
4081
+ hwCredentials: `\u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
4082
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
4083
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
4084
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
4085
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801`,
4086
+ hwPostOptimization: `\u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
4087
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
4088
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
4089
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669`,
4090
+ hwReference: "\u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)",
4091
+ // Service Reminders
4092
+ serviceReminderTitle: "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
4093
+ serviceReminderFooter: "\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002",
4094
+ serviceImpact: "\u5F71\u54CD",
4095
+ serviceAction: "\u5EFA\u8BAE",
4096
+ // Common
4097
+ account: "\u8D26\u6237",
4098
+ region: "\u533A\u57DF",
4099
+ scanTime: "\u626B\u63CF\u65F6\u95F4",
4100
+ duration: "\u8017\u65F6",
4101
+ severityDistribution: "\u4E25\u91CD\u6027\u5206\u5E03",
4102
+ findingsByModule: "\u6309\u6A21\u5757\u5206\u7C7B\u7684\u53D1\u73B0",
4103
+ details: "\u8BE6\u60C5",
4104
+ // Extended — HTML Security Report extras
4105
+ topHighestRiskFindings: (n) => `\u524D ${n} \u9879\u6700\u9AD8\u98CE\u9669\u53D1\u73B0`,
4106
+ resource: "\u8D44\u6E90",
4107
+ impact: "\u5F71\u54CD",
4108
+ riskScore: "\u98CE\u9669\u8BC4\u5206",
4109
+ showRemainingFindings: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u53D1\u73B0\u2026`,
4110
+ trendTitle: "30\u65E5\u8D8B\u52BF",
4111
+ findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
4112
+ showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
4113
+ // Extended — MLPS extras
4114
+ // Markdown report
4115
+ executiveSummary: "\u6267\u884C\u6458\u8981",
4116
+ totalFindingsLabel: "\u53D1\u73B0\u603B\u6570",
4117
+ description: "\u63CF\u8FF0",
4118
+ priority: "\u4F18\u5148\u7EA7",
4119
+ noFindingsForSeverity: (severity) => `\u65E0${severity}\u53D1\u73B0\u3002`,
4120
+ preCheckOverview: "\u9884\u68C0\u603B\u89C8",
4121
+ accountInfo: "\u8D26\u6237\u4FE1\u606F",
4122
+ checkedCount: (total, clean, issues) => `\u5DF2\u68C0\u67E5: ${total} \u9879\uFF08\u672A\u53D1\u73B0\u95EE\u9898: ${clean} \u9879 | \u53D1\u73B0\u95EE\u9898: ${issues} \u9879\uFF09`,
4123
+ uncheckedCount: (n) => `\u672A\u68C0\u67E5: ${n} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
4124
+ cloudProviderCount: (n) => `\u4E91\u5E73\u53F0\u8D1F\u8D23: ${n} \u9879`,
4125
+ manualReviewCount: (n) => `\u9700\u4EBA\u5DE5\u8BC4\u4F30: ${n} \u9879`,
4126
+ naCount: (n) => `\u4E0D\u9002\u7528: ${n} \u9879`,
4127
+ 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`,
4128
+ unknownNote: (n) => `\uFF08${n} \u9879\u672A\u68C0\u67E5\uFF0C\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
4129
+ 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`,
4130
+ mlpsFooterGenerated: (version) => `\u7531 AWS Security MCP Server v${version} \u751F\u6210`,
4131
+ 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",
4132
+ andMore: (n) => `... \u53CA\u5176\u4ED6 ${n} \u9879`,
4133
+ remediationByPriority: "\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09",
4134
+ affectedResources: (n) => `\u6D89\u53CA ${n} \u4E2A\u8D44\u6E90`,
4135
+ installWindowsPatches: (n, kbs) => `\u5B89\u88C5 ${n} \u4E2A Windows \u8865\u4E01 (${kbs})`,
4136
+ mlpsCategorySection: {
4137
+ "\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
4138
+ "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
4139
+ "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
4140
+ "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
4141
+ "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
4142
+ },
4143
+ // Service Recommendations
4144
+ notEnabled: "\u672A\u542F\u7528",
4145
+ serviceRecommendations: {
4146
+ security_hub_findings: {
4147
+ icon: "\u{1F534}",
4148
+ service: "Security Hub",
4149
+ impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
4150
+ action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
4151
+ },
4152
+ guardduty_findings: {
4153
+ icon: "\u{1F534}",
4154
+ service: "GuardDuty",
4155
+ 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",
4156
+ action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
4157
+ },
4158
+ inspector_findings: {
4159
+ icon: "\u{1F7E1}",
4160
+ service: "Inspector",
4161
+ impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
4162
+ action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
4163
+ },
4164
+ trusted_advisor_findings: {
4165
+ icon: "\u{1F7E1}",
4166
+ service: "Trusted Advisor",
4167
+ impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
4168
+ action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
4169
+ },
4170
+ config_rules_findings: {
4171
+ icon: "\u{1F7E1}",
4172
+ service: "AWS Config",
4173
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
4174
+ action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
4175
+ },
4176
+ access_analyzer_findings: {
4177
+ icon: "\u{1F7E1}",
4178
+ service: "IAM Access Analyzer",
4179
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
4180
+ action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
4181
+ },
4182
+ patch_compliance_findings: {
4183
+ icon: "\u{1F7E1}",
4184
+ service: "SSM Patch Manager",
4185
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
4186
+ action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
4187
+ }
4188
+ },
4189
+ // HW Checklist (full composite)
4190
+ hwChecklist: `
4191
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4192
+ \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
4193
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4194
+
4195
+ \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
4196
+
4197
+ \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
4198
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
4199
+ \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
4200
+ \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
4201
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
4202
+
4203
+ \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
4204
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
4205
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
4206
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
4207
+
4208
+ \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
4209
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
4210
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
4211
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
4212
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
4213
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
4214
+
4215
+ \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
4216
+ \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
4217
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
4218
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
4219
+
4220
+ \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
4221
+ \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
4222
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
4223
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
4224
+
4225
+ \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
4226
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
4227
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
4228
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D
4229
+
4230
+ \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
4231
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
4232
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
4233
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
4234
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
4235
+
4236
+ \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
4237
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
4238
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
4239
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
4240
+
4241
+ \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
4242
+ `
4243
+ };
4244
+
4245
+ // src/i18n/en.ts
4246
+ var enI18n = {
4247
+ // HTML Security Report
4248
+ securityReportTitle: "AWS Security Scan Report",
4249
+ securityScore: "Security Score",
4250
+ critical: "Critical",
4251
+ high: "High",
4252
+ medium: "Medium",
4253
+ low: "Low",
4254
+ scanStatistics: "Scan Statistics",
4255
+ module: "Module",
4256
+ resources: "Resources",
4257
+ findings: "Findings",
4258
+ status: "Status",
4259
+ allFindings: "All Findings",
4260
+ recommendations: "Recommendations",
4261
+ unique: "unique",
4262
+ showMore: "Show more",
4263
+ noIssuesFound: "No security issues found.",
4264
+ allModulesClean: "All modules clean",
4265
+ generatedBy: "Generated by AWS Security MCP Server",
4266
+ informationalOnly: "This report is for informational purposes only.",
4267
+ // MLPS Report
4268
+ mlpsTitle: "MLPS Level 3 Pre-Check Report",
4269
+ 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)",
4270
+ checkedItems: "Checked Items",
4271
+ noIssues: "No Issues Found",
4272
+ issuesFound: "Issues Found",
4273
+ notChecked: "Not Checked",
4274
+ cloudProvider: "Cloud Provider Responsible",
4275
+ manualReview: "Manual Review Required",
4276
+ notApplicable: "Not Applicable",
4277
+ checkResult: "Check Result",
4278
+ noRelatedIssues: "Check Result: No related issues found",
4279
+ issuesFoundCount: (n) => `Check Result: Found ${n} related issue${n === 1 ? "" : "s"}`,
4280
+ remediation: "Remediation",
4281
+ remediationItems: (n) => `Remediation Items (${n} unique)`,
4282
+ showRemaining: (n) => `Show remaining ${n} items`,
4283
+ // HW Defense Checklist
4284
+ hwChecklistTitle: "\u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)",
4285
+ hwChecklistSubtitle: "The following items require manual verification and execution:",
4286
+ hwEmergencyIsolation: `\u26A0\uFE0F Emergency Isolation / Incident Response Plan
4287
+ \u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
4288
+ \u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
4289
+ \u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
4290
+ \u25A1 Identify responsible personnel and contacts for each project account and resource`,
4291
+ hwTestEnvShutdown: `\u26A0\uFE0F Test/Development Environment Handling
4292
+ \u25A1 Shut down non-critical systems during the drill period
4293
+ \u25A1 Shut down test/dev environments or maintain same security baseline as production
4294
+ \u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation`,
4295
+ hwDutyTeam: `\u26A0\uFE0F On-Duty Team Formation
4296
+ \u25A1 7\xD724 monitoring and rapid response team
4297
+ \u25A1 Technical and risk analysis team
4298
+ \u25A1 Security policy deployment team
4299
+ \u25A1 Business response team
4300
+ \u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)`,
4301
+ hwNetworkDiagram: `\u26A0\uFE0F Ingress/Egress Path Architecture Diagram
4302
+ \u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
4303
+ \u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
4304
+ \u25A1 Identify all internet-facing data interaction interfaces`,
4305
+ hwPentest: `\u26A0\uFE0F Proactive Penetration Testing
4306
+ \u25A1 Contact security vendors for simulated attack drills before the exercise
4307
+ \u25A1 Conduct security hardening based on penetration test reports
4308
+ \u25A1 Monitor AWS security advisories (known vulnerabilities and patches)`,
4309
+ hwWarRoom: `\u26A0\uFE0F WAR-ROOM Real-Time Communication
4310
+ \u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
4311
+ \u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
4312
+ \u25A1 Standardize case title format: "[CyberDrill] + Issue Description"`,
4313
+ hwCredentials: `\u26A0\uFE0F Password & Credential Management
4314
+ \u25A1 All IAM users must have MFA enabled
4315
+ \u25A1 Access key rotation cycle \u2264 90 days
4316
+ \u25A1 Avoid shared account usage
4317
+ \u25A1 No plaintext passwords in S3/Lambda/application code`,
4318
+ hwPostOptimization: `\u26A0\uFE0F Post-Drill Optimization
4319
+ \u25A1 Address and remediate each item from the attack report
4320
+ \u25A1 Establish periodic security maintenance processes with the security team
4321
+ \u25A1 Continuously fill security risk gaps`,
4322
+ hwReference: "Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)",
4323
+ // Service Reminders
4324
+ serviceReminderTitle: "\u26A1 The following security services are not enabled; some checks cannot be performed:",
4325
+ serviceReminderFooter: "Re-scan after enabling the above services for a more complete security assessment.",
4326
+ serviceImpact: "Impact",
4327
+ serviceAction: "Action",
4328
+ // Common
4329
+ account: "Account",
4330
+ region: "Region",
4331
+ scanTime: "Scan Time",
4332
+ duration: "Duration",
4333
+ severityDistribution: "Severity Distribution",
4334
+ findingsByModule: "Findings by Module",
4335
+ details: "Details",
4336
+ // Extended \u2014 HTML Security Report extras
4337
+ topHighestRiskFindings: (n) => `Top ${n} Highest Risk Findings`,
4338
+ resource: "Resource",
4339
+ impact: "Impact",
4340
+ riskScore: "Risk Score",
4341
+ showRemainingFindings: (n) => `Show remaining ${n} findings\u2026`,
4342
+ trendTitle: "30-Day Trends",
4343
+ findingsBySeverity: "Findings by Severity",
4344
+ showMoreCount: (n) => `Show ${n} more\u2026`,
4345
+ // Extended \u2014 MLPS extras
4346
+ // Markdown report
4347
+ executiveSummary: "Executive Summary",
4348
+ totalFindingsLabel: "Total Findings",
4349
+ description: "Description",
4350
+ priority: "Priority",
4351
+ noFindingsForSeverity: (severity) => `No ${severity.toLowerCase()} findings.`,
4352
+ preCheckOverview: "Pre-Check Overview",
4353
+ accountInfo: "Account Information",
4354
+ checkedCount: (total, clean, issues) => `Checked: ${total} items (No issues: ${clean} | Issues found: ${issues})`,
4355
+ uncheckedCount: (n) => `Not checked: ${n} items (corresponding scan modules not run)`,
4356
+ cloudProviderCount: (n) => `Cloud provider responsible: ${n} items`,
4357
+ manualReviewCount: (n) => `Manual review required: ${n} items`,
4358
+ naCount: (n) => `Not applicable: ${n} items`,
4359
+ naNote: (n) => `Not applicable: ${n} items (IoT/wireless networks/mobile terminals/ICS/trusted verification, etc.)`,
4360
+ unknownNote: (n) => `(${n} items not checked \u2014 corresponding scan modules not run)`,
4361
+ 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.`,
4362
+ mlpsFooterGenerated: (version) => `Generated by AWS Security MCP Server v${version}`,
4363
+ 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.",
4364
+ andMore: (n) => `\u2026 and ${n} more`,
4365
+ remediationByPriority: "Remediation Items (by Priority)",
4366
+ affectedResources: (n) => `${n} resource${n === 1 ? "" : "s"} affected`,
4367
+ installWindowsPatches: (n, kbs) => `Install ${n} Windows patch${n === 1 ? "" : "es"} (${kbs})`,
4368
+ mlpsCategorySection: {
4369
+ "\u5B89\u5168\u7269\u7406\u73AF\u5883": "I. Physical Environment Security",
4370
+ "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "II. Communication Network Security",
4371
+ "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "III. Area Boundary Security",
4372
+ "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
4373
+ "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
4374
+ },
4375
+ // Service Recommendations
4376
+ notEnabled: "Not Enabled",
4377
+ serviceRecommendations: {
4378
+ security_hub_findings: {
4379
+ icon: "\u{1F534}",
4380
+ service: "Security Hub",
4381
+ impact: "Cannot obtain 300+ automated security checks (FSBP/CIS/PCI DSS standards)",
4382
+ action: "Enable Security Hub for the most comprehensive security posture assessment"
4383
+ },
4384
+ guardduty_findings: {
4385
+ icon: "\u{1F534}",
4386
+ service: "GuardDuty",
4387
+ impact: "Cannot detect threat activity (malicious IPs, anomalous API calls, crypto mining, etc.)",
4388
+ action: "Enable GuardDuty for continuous threat detection"
4389
+ },
4390
+ inspector_findings: {
4391
+ icon: "\u{1F7E1}",
4392
+ service: "Inspector",
4393
+ impact: "Cannot scan EC2/Lambda/container software vulnerabilities (CVEs)",
4394
+ action: "Enable Inspector to discover known security vulnerabilities"
4395
+ },
4396
+ trusted_advisor_findings: {
4397
+ icon: "\u{1F7E1}",
4398
+ service: "Trusted Advisor",
4399
+ impact: "Cannot obtain AWS best practice security checks",
4400
+ action: "Upgrade to Business/Enterprise Support plan to use Trusted Advisor security checks"
4401
+ },
4402
+ config_rules_findings: {
4403
+ icon: "\u{1F7E1}",
4404
+ service: "AWS Config",
4405
+ impact: "Cannot check resource configuration compliance status",
4406
+ action: "Enable AWS Config and configure Config Rules"
4407
+ },
4408
+ access_analyzer_findings: {
4409
+ icon: "\u{1F7E1}",
4410
+ service: "IAM Access Analyzer",
4411
+ impact: "Cannot detect whether resources are accessed by external accounts or public networks",
4412
+ action: "Create IAM Access Analyzer (account-level or organization-level)"
4413
+ },
4414
+ patch_compliance_findings: {
4415
+ icon: "\u{1F7E1}",
4416
+ service: "SSM Patch Manager",
4417
+ impact: "Cannot check instance patch compliance status",
4418
+ action: "Install SSM Agent and configure Patch Manager"
4419
+ }
4420
+ },
4421
+ // HW Checklist (full composite)
4422
+ hwChecklist: `
4423
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4424
+ \u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)
4425
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4426
+
4427
+ The following items require manual verification and execution:
4428
+
4429
+ \u26A0\uFE0F Emergency Isolation / Incident Response Plan
4430
+ \u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
4431
+ \u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
4432
+ \u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
4433
+ \u25A1 Identify responsible personnel and contacts for each project account and resource
4434
+
4435
+ \u26A0\uFE0F Test/Development Environment Handling
4436
+ \u25A1 Shut down non-critical systems during the drill period
4437
+ \u25A1 Shut down test/dev environments or maintain same security baseline as production
4438
+ \u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation
4439
+
4440
+ \u26A0\uFE0F On-Duty Team Formation
4441
+ \u25A1 7\xD724 monitoring and rapid response team
4442
+ \u25A1 Technical and risk analysis team
4443
+ \u25A1 Security policy deployment team
4444
+ \u25A1 Business response team
4445
+ \u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)
4446
+
4447
+ \u26A0\uFE0F Ingress/Egress Path Architecture Diagram
4448
+ \u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
4449
+ \u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
4450
+ \u25A1 Identify all internet-facing data interaction interfaces
4451
+
4452
+ \u26A0\uFE0F Proactive Penetration Testing
4453
+ \u25A1 Contact security vendors for simulated attack drills before the exercise
4454
+ \u25A1 Conduct security hardening based on penetration test reports
4455
+ \u25A1 Monitor AWS security advisories (known vulnerabilities and patches)
4456
+
4457
+ \u26A0\uFE0F WAR-ROOM Real-Time Communication
4458
+ \u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
4459
+ \u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
4460
+ \u25A1 Standardize case title format: "[CyberDrill] + Issue Description"
4461
+
4462
+ \u26A0\uFE0F Password & Credential Management
4463
+ \u25A1 All IAM users must have MFA enabled
4464
+ \u25A1 Access key rotation cycle \u2264 90 days
4465
+ \u25A1 Avoid shared account usage
4466
+ \u25A1 No plaintext passwords in S3/Lambda/application code
4467
+
4468
+ \u26A0\uFE0F Post-Drill Optimization
4469
+ \u25A1 Address and remediate each item from the attack report
4470
+ \u25A1 Establish periodic security maintenance processes with the security team
4471
+ \u25A1 Continuously fill security risk gaps
4472
+
4473
+ Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)
4474
+ `
4475
+ };
4476
+
4477
+ // src/i18n/index.ts
4478
+ var translations = {
4479
+ zh: zhI18n,
4480
+ en: enI18n
4481
+ };
4482
+ function getI18n(lang = "zh") {
4483
+ return translations[lang] ?? translations.zh;
4484
+ }
4485
+
3971
4486
  // src/tools/report-tool.ts
3972
4487
  var SEVERITY_ICON = {
3973
4488
  CRITICAL: "\u{1F534}",
@@ -3985,38 +4500,45 @@ function formatDuration(start, end) {
3985
4500
  const remainSecs = secs % 60;
3986
4501
  return `${mins}m ${remainSecs}s`;
3987
4502
  }
3988
- function renderFinding(f) {
3989
- const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
3990
- return [
3991
- `#### ${f.title}`,
3992
- `- **Resource:** ${f.resourceId} (\`${f.resourceArn}\`)`,
3993
- `- **Description:** ${f.description}`,
3994
- `- **Impact:** ${f.impact}`,
3995
- `- **Risk Score:** ${f.riskScore}/10`,
3996
- `- **Remediation:**`,
3997
- steps,
3998
- `- **Priority:** ${f.priority}`
3999
- ].join("\n");
4000
- }
4001
- function generateMarkdownReport(scanResults) {
4503
+ function generateMarkdownReport(scanResults, lang) {
4504
+ const t = getI18n(lang ?? "zh");
4002
4505
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
4003
4506
  const date = scanStart.split("T")[0];
4004
4507
  const duration = formatDuration(scanStart, scanEnd);
4508
+ const sevLabel = {
4509
+ CRITICAL: t.critical,
4510
+ HIGH: t.high,
4511
+ MEDIUM: t.medium,
4512
+ LOW: t.low
4513
+ };
4514
+ function renderFinding(f) {
4515
+ const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
4516
+ return [
4517
+ `#### ${f.title}`,
4518
+ `- **${t.resource}:** ${f.resourceId} (\`${f.resourceArn}\`)`,
4519
+ `- **${t.description}:** ${f.description}`,
4520
+ `- **${t.impact}:** ${f.impact}`,
4521
+ `- **${t.riskScore}:** ${f.riskScore}/10`,
4522
+ `- **${t.remediation}:**`,
4523
+ steps,
4524
+ `- **${t.priority}:** ${f.priority}`
4525
+ ].join("\n");
4526
+ }
4005
4527
  const lines = [];
4006
- lines.push(`# AWS Security Scan Report \u2014 ${date}`);
4528
+ lines.push(`# ${t.securityReportTitle} \u2014 ${date}`);
4007
4529
  lines.push("");
4008
- lines.push("## Executive Summary");
4009
- lines.push(`- **Account:** ${accountId}`);
4010
- lines.push(`- **Region:** ${region}`);
4011
- lines.push(`- **Scan Duration:** ${duration}`);
4530
+ lines.push(`## ${t.executiveSummary}`);
4531
+ lines.push(`- **${t.account}:** ${accountId}`);
4532
+ lines.push(`- **${t.region}:** ${region}`);
4533
+ lines.push(`- **${t.duration}:** ${duration}`);
4012
4534
  lines.push(
4013
- `- **Total Findings:** ${summary.totalFindings} (\u{1F534} ${summary.critical} Critical | \u{1F7E0} ${summary.high} High | \u{1F7E1} ${summary.medium} Medium | \u{1F7E2} ${summary.low} Low)`
4535
+ `- **${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})`
4014
4536
  );
4015
4537
  lines.push("");
4016
4538
  if (summary.totalFindings === 0) {
4017
- lines.push("## Findings by Severity");
4539
+ lines.push(`## ${t.findingsBySeverity}`);
4018
4540
  lines.push("");
4019
- lines.push("\u2705 No security issues found.");
4541
+ lines.push(`\u2705 ${t.noIssuesFound}`);
4020
4542
  lines.push("");
4021
4543
  } else {
4022
4544
  const allFindings = modules.flatMap((m) => m.findings);
@@ -4027,15 +4549,15 @@ function generateMarkdownReport(scanResults) {
4027
4549
  for (const f of allFindings) {
4028
4550
  grouped.get(f.severity).push(f);
4029
4551
  }
4030
- lines.push("## Findings by Severity");
4552
+ lines.push(`## ${t.findingsBySeverity}`);
4031
4553
  lines.push("");
4032
4554
  for (const sev of SEVERITY_ORDER) {
4033
4555
  const findings = grouped.get(sev);
4034
4556
  const icon = SEVERITY_ICON[sev];
4035
- lines.push(`### ${icon} ${sev.charAt(0)}${sev.slice(1).toLowerCase()}`);
4557
+ lines.push(`### ${icon} ${sevLabel[sev]}`);
4036
4558
  lines.push("");
4037
4559
  if (findings.length === 0) {
4038
- lines.push(`No ${sev.toLowerCase()} findings.`);
4560
+ lines.push(t.noFindingsForSeverity(sevLabel[sev]));
4039
4561
  lines.push("");
4040
4562
  continue;
4041
4563
  }
@@ -4046,9 +4568,9 @@ function generateMarkdownReport(scanResults) {
4046
4568
  }
4047
4569
  }
4048
4570
  }
4049
- lines.push("## Scan Statistics");
4571
+ lines.push(`## ${t.scanStatistics}`);
4050
4572
  lines.push(
4051
- "| Module | Resources Scanned | Findings | Status |"
4573
+ `| ${t.module} | ${t.resources} | ${t.findings} | ${t.status} |`
4052
4574
  );
4053
4575
  lines.push("|--------|------------------|----------|--------|");
4054
4576
  for (const m of modules) {
@@ -4061,7 +4583,7 @@ function generateMarkdownReport(scanResults) {
4061
4583
  if (summary.totalFindings > 0) {
4062
4584
  const allFindings = modules.flatMap((m) => m.findings);
4063
4585
  allFindings.sort((a, b) => b.riskScore - a.riskScore);
4064
- lines.push("## Recommendations (Priority Order)");
4586
+ lines.push(`## ${t.recommendations}`);
4065
4587
  for (let i = 0; i < allFindings.length; i++) {
4066
4588
  const f = allFindings[i];
4067
4589
  lines.push(`${i + 1}. [${f.priority}] ${f.title}: ${f.remediationSteps[0] ?? "Review and remediate."}`);
@@ -6108,13 +6630,6 @@ var MLPS3_CATEGORY_ORDER = [
6108
6630
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6109
6631
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6110
6632
  ];
6111
- var MLPS3_CATEGORY_SECTION = {
6112
- "\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
6113
- "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
6114
- "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
6115
- "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6116
- "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6117
- };
6118
6633
  var MLPS3_CHECK_MAPPING = [
6119
6634
  // =========================================================================
6120
6635
  // 安全物理环境 — L3-PES1-* (22 items) → cloud_provider
@@ -6774,7 +7289,9 @@ function evaluateAllFullChecks(scanResults) {
6774
7289
  return evaluateFullCheck(item, mapping, allFindings, scanModules);
6775
7290
  });
6776
7291
  }
6777
- function generateMlps3Report(scanResults) {
7292
+ function generateMlps3Report(scanResults, lang) {
7293
+ const t = getI18n(lang ?? "zh");
7294
+ const isEn = (lang ?? "zh") === "en";
6778
7295
  const { accountId, region, scanStart } = scanResults;
6779
7296
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
6780
7297
  const results = evaluateAllFullChecks(scanResults);
@@ -6786,27 +7303,28 @@ function generateMlps3Report(scanResults) {
6786
7303
  const cloudCount = results.filter((r) => r.status === "cloud_provider").length;
6787
7304
  const manualCount = results.filter((r) => r.status === "manual").length;
6788
7305
  const naCount = results.filter((r) => r.status === "not_applicable").length;
7306
+ const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
7307
+ const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
6789
7308
  const lines = [];
6790
- lines.push("# \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A");
6791
- 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**");
6792
- lines.push("> **\uFF08GB/T 22239-2019 \u5B8C\u6574\u68C0\u67E5\u6E05\u5355 184 \u9879\uFF09**");
7309
+ lines.push(`# ${t.mlpsTitle}`);
7310
+ lines.push(`> **${t.mlpsDisclaimer}**`);
6793
7311
  lines.push("");
6794
- lines.push("## \u8D26\u6237\u4FE1\u606F");
6795
- lines.push(`- Account: ${accountId} | Region: ${region} | \u626B\u63CF\u65F6\u95F4: ${scanTime}`);
7312
+ lines.push(`## ${t.accountInfo}`);
7313
+ lines.push(`- ${t.account}: ${accountId} | ${t.region}: ${region} | ${t.scanTime}: ${scanTime}`);
6796
7314
  lines.push("");
6797
- lines.push("## \u9884\u68C0\u603B\u89C8");
6798
- lines.push(`- \u5DF2\u68C0\u67E5: ${checkedTotal} \u9879\uFF08\u672A\u53D1\u73B0\u95EE\u9898: ${autoClean} \u9879 | \u53D1\u73B0\u95EE\u9898: ${autoIssues} \u9879\uFF09`);
7315
+ lines.push(`## ${t.preCheckOverview}`);
7316
+ lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
6799
7317
  if (autoUnknown > 0) {
6800
- lines.push(`- \u672A\u68C0\u67E5: ${autoUnknown} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`);
7318
+ lines.push(`- ${t.uncheckedCount(autoUnknown)}`);
6801
7319
  }
6802
- lines.push(`- \u4E91\u5E73\u53F0\u8D1F\u8D23: ${cloudCount} \u9879`);
6803
- lines.push(`- \u9700\u4EBA\u5DE5\u8BC4\u4F30: ${manualCount} \u9879`);
7320
+ lines.push(`- ${t.cloudProviderCount(cloudCount)}`);
7321
+ lines.push(`- ${t.manualReviewCount(manualCount)}`);
6804
7322
  if (naCount > 0) {
6805
- lines.push(`- \u4E0D\u9002\u7528: ${naCount} \u9879`);
7323
+ lines.push(`- ${t.naCount(naCount)}`);
6806
7324
  }
6807
7325
  lines.push("");
6808
7326
  for (const category of MLPS3_CATEGORY_ORDER) {
6809
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
7327
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
6810
7328
  const catResults = results.filter(
6811
7329
  (r) => r.item.categoryCn === category && r.status !== "not_applicable"
6812
7330
  );
@@ -6819,18 +7337,20 @@ function generateMlps3Report(scanResults) {
6819
7337
  if (!controlMap.has(key)) controlMap.set(key, []);
6820
7338
  controlMap.get(key).push(r);
6821
7339
  }
6822
- for (const [controlName, controlResults] of controlMap) {
7340
+ for (const [_controlKey, controlResults] of controlMap) {
7341
+ const controlName = itemControl(controlResults[0]);
6823
7342
  lines.push(`### ${controlName}`);
6824
7343
  for (const r of controlResults) {
6825
7344
  const icon = r.status === "clean" ? "\u2705" : r.status === "issues" ? "\u274C" : r.status === "unknown" ? "\u26A0\uFE0F" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
6826
- 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";
6827
- lines.push(`- [${icon}] ${r.item.id} ${r.item.requirementCn.slice(0, 60)}${r.item.requirementCn.length > 60 ? "\u2026" : ""}${suffix}`);
7345
+ 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}`;
7346
+ const reqText = itemReq(r);
7347
+ lines.push(`- [${icon}] ${r.item.id} ${reqText.slice(0, 60)}${reqText.length > 60 ? "\u2026" : ""}${suffix}`);
6828
7348
  if (r.status === "issues" && r.relatedFindings.length > 0) {
6829
7349
  for (const f of r.relatedFindings.slice(0, 3)) {
6830
7350
  lines.push(` - ${f.severity}: ${f.title}`);
6831
7351
  }
6832
7352
  if (r.relatedFindings.length > 3) {
6833
- lines.push(` - ... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 3} \u9879`);
7353
+ lines.push(` - ${t.andMore(r.relatedFindings.length - 3)}`);
6834
7354
  }
6835
7355
  }
6836
7356
  }
@@ -6839,7 +7359,7 @@ function generateMlps3Report(scanResults) {
6839
7359
  }
6840
7360
  const failedResults = results.filter((r) => r.status === "issues");
6841
7361
  if (failedResults.length > 0) {
6842
- lines.push("## \u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09");
7362
+ lines.push(`## ${t.remediationByPriority}`);
6843
7363
  lines.push("");
6844
7364
  const allFailedFindings = /* @__PURE__ */ new Map();
6845
7365
  for (const r of failedResults) {
@@ -6862,7 +7382,7 @@ function generateMlps3Report(scanResults) {
6862
7382
  lines.push("");
6863
7383
  }
6864
7384
  if (naCount > 0) {
6865
- 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`);
7385
+ lines.push(`> ${t.naNote(naCount)}`);
6866
7386
  lines.push("");
6867
7387
  }
6868
7388
  return lines.join("\n");
@@ -6872,6 +7392,15 @@ function generateMlps3Report(scanResults) {
6872
7392
  function esc(s) {
6873
7393
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
6874
7394
  }
7395
+ function escWithLinks(s) {
7396
+ const parts = s.split(/(https?:\/\/\S+)/);
7397
+ return parts.map((part, i) => {
7398
+ if (i % 2 === 1) {
7399
+ return `<a href="${esc(part)}" style="color:#60a5fa" target="_blank" rel="noopener">${esc(part)}</a>`;
7400
+ }
7401
+ return esc(part);
7402
+ }).join("");
7403
+ }
6875
7404
  function calcScore(summary) {
6876
7405
  const raw = 100 - summary.critical * 15 - summary.high * 5 - summary.medium * 2 - summary.low * 0.5;
6877
7406
  return Math.max(0, Math.min(100, Math.round(raw)));
@@ -6895,50 +7424,6 @@ function scoreColor(score) {
6895
7424
  if (score >= 50) return "#eab308";
6896
7425
  return "#ef4444";
6897
7426
  }
6898
- var SERVICE_RECOMMENDATIONS = {
6899
- security_hub_findings: {
6900
- icon: "\u{1F534}",
6901
- service: "Security Hub",
6902
- impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
6903
- action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
6904
- },
6905
- guardduty_findings: {
6906
- icon: "\u{1F534}",
6907
- service: "GuardDuty",
6908
- 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",
6909
- action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
6910
- },
6911
- inspector_findings: {
6912
- icon: "\u{1F7E1}",
6913
- service: "Inspector",
6914
- impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
6915
- action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
6916
- },
6917
- trusted_advisor_findings: {
6918
- icon: "\u{1F7E1}",
6919
- service: "Trusted Advisor",
6920
- impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
6921
- action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
6922
- },
6923
- config_rules_findings: {
6924
- icon: "\u{1F7E1}",
6925
- service: "AWS Config",
6926
- impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
6927
- action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
6928
- },
6929
- access_analyzer_findings: {
6930
- icon: "\u{1F7E1}",
6931
- service: "IAM Access Analyzer",
6932
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
6933
- action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
6934
- },
6935
- patch_compliance_findings: {
6936
- icon: "\u{1F7E1}",
6937
- service: "SSM Patch Manager",
6938
- impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
6939
- action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
6940
- }
6941
- };
6942
7427
  var SERVICE_NOT_ENABLED_PATTERNS = [
6943
7428
  "not enabled",
6944
7429
  "not found",
@@ -6948,10 +7433,11 @@ var SERVICE_NOT_ENABLED_PATTERNS = [
6948
7433
  "not available",
6949
7434
  "is not enabled"
6950
7435
  ];
6951
- function getDisabledServices(modules) {
7436
+ function getDisabledServices(modules, lang) {
7437
+ const t = getI18n(lang ?? "zh");
6952
7438
  const disabled = [];
6953
7439
  for (const mod of modules) {
6954
- const rec = SERVICE_RECOMMENDATIONS[mod.module];
7440
+ const rec = t.serviceRecommendations[mod.module];
6955
7441
  if (!rec) continue;
6956
7442
  if (!mod.warnings?.length) continue;
6957
7443
  const hasNotEnabled = mod.warnings.some(
@@ -6963,21 +7449,22 @@ function getDisabledServices(modules) {
6963
7449
  }
6964
7450
  return disabled;
6965
7451
  }
6966
- function buildServiceReminderHtml(modules) {
6967
- const disabled = getDisabledServices(modules);
7452
+ function buildServiceReminderHtml(modules, lang) {
7453
+ const t = getI18n(lang ?? "zh");
7454
+ const disabled = getDisabledServices(modules, lang);
6968
7455
  if (disabled.length === 0) return "";
6969
7456
  const items = disabled.map((svc) => `
6970
7457
  <div style="margin-bottom:12px">
6971
- <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} \u672A\u542F\u7528</div>
6972
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5F71\u54CD\uFF1A${esc(svc.impact)}</div>
6973
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5EFA\u8BAE\uFF1A${esc(svc.action)}</div>
7458
+ <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} ${esc(t.notEnabled)}</div>
7459
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceImpact)}\uFF1A${esc(svc.impact)}</div>
7460
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceAction)}\uFF1A${esc(svc.action)}</div>
6974
7461
  </div>`).join("\n");
6975
7462
  return `
6976
7463
  <section>
6977
7464
  <div style="background:#2d1f00;border:1px solid #b45309;border-radius:8px;padding:20px;margin-bottom:32px">
6978
- <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>
7465
+ <div style="font-size:17px;font-weight:700;margin-bottom:12px">${esc(t.serviceReminderTitle)}</div>
6979
7466
  ${items}
6980
- <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>
7467
+ <div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500">${esc(t.serviceReminderFooter)}</div>
6981
7468
  </div>
6982
7469
  </section>`;
6983
7470
  }
@@ -7179,12 +7666,12 @@ function donutChart(summary) {
7179
7666
  "</svg>"
7180
7667
  ].join("\n");
7181
7668
  }
7182
- function barChart(modules) {
7669
+ function barChart(modules, allCleanLabel = "All modules clean") {
7183
7670
  const withFindings = modules.filter((m) => m.findingsCount > 0).sort((a, b) => b.findingsCount - a.findingsCount).slice(0, 12);
7184
7671
  if (withFindings.length === 0) {
7185
7672
  return [
7186
7673
  '<svg viewBox="0 0 400 50" width="100%">',
7187
- ' <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">All modules clean</text>',
7674
+ ` <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">${esc(allCleanLabel)}</text>`,
7188
7675
  "</svg>"
7189
7676
  ].join("\n");
7190
7677
  }
@@ -7299,7 +7786,9 @@ function scoreTrendChart(history) {
7299
7786
  "</svg>"
7300
7787
  ].join("\n");
7301
7788
  }
7302
- function generateHtmlReport(scanResults, history) {
7789
+ function generateHtmlReport(scanResults, history, lang) {
7790
+ const t = getI18n(lang ?? "zh");
7791
+ const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
7303
7792
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
7304
7793
  const date = scanStart.split("T")[0];
7305
7794
  const duration = formatDuration2(scanStart, scanEnd);
@@ -7317,23 +7806,23 @@ function generateHtmlReport(scanResults, history) {
7317
7806
  <div class="top5-content">
7318
7807
  <span class="badge badge-${esc(f.severity.toLowerCase())}">${esc(f.severity)}</span>
7319
7808
  <div class="top5-title">${esc(f.title)}</div>
7320
- <div class="top5-detail"><strong>Resource:</strong> ${esc(f.resourceId)}</div>
7321
- <div class="top5-detail"><strong>Impact:</strong> ${esc(f.impact)}</div>
7322
- <div class="top5-detail"><strong>Risk Score:</strong> ${f.riskScore}/10</div>
7323
- <h4>Remediation</h4>
7324
- <ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
7809
+ <div class="top5-detail"><strong>${t.resource}:</strong> ${esc(f.resourceId)}</div>
7810
+ <div class="top5-detail"><strong>${t.impact}:</strong> ${esc(f.impact)}</div>
7811
+ <div class="top5-detail"><strong>${t.riskScore}:</strong> ${f.riskScore}/10</div>
7812
+ <h4>${t.remediation}</h4>
7813
+ <ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
7325
7814
  </div>
7326
7815
  </div>`
7327
7816
  ).join("\n");
7328
7817
  top5Html = `
7329
7818
  <section>
7330
- <h2>Top ${top5.length} Highest Risk Findings</h2>
7819
+ <h2>${esc(t.topHighestRiskFindings(top5.length))}</h2>
7331
7820
  ${cards}
7332
7821
  </section>`;
7333
7822
  }
7334
7823
  let findingsHtml;
7335
7824
  if (summary.totalFindings === 0) {
7336
- findingsHtml = '<div class="no-findings">No security issues found.</div>';
7825
+ findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
7337
7826
  } else {
7338
7827
  const FOLD_THRESHOLD = 20;
7339
7828
  const renderCard = (f) => {
@@ -7342,10 +7831,10 @@ function generateHtmlReport(scanResults, history) {
7342
7831
  <span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
7343
7832
  <span class="finding-title-text">${esc(f.title)}</span>
7344
7833
  <span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
7345
- <details><summary>Details</summary><div class="finding-card-body">
7834
+ <details><summary>${t.details}</summary><div class="finding-card-body">
7346
7835
  <p>${esc(f.description)}</p>
7347
- <p><strong>Remediation:</strong></p>
7348
- <ol>${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
7836
+ <p><strong>${t.remediation}:</strong></p>
7837
+ <ol>${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
7349
7838
  </div></details>
7350
7839
  </div>`;
7351
7840
  };
@@ -7356,7 +7845,7 @@ function generateHtmlReport(scanResults, history) {
7356
7845
  const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
7357
7846
  const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
7358
7847
  return `${first}
7359
- <details><summary>Show remaining ${findings.length - FOLD_THRESHOLD} findings...</summary>
7848
+ <details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
7360
7849
  ${rest}
7361
7850
  </details>`;
7362
7851
  };
@@ -7408,13 +7897,13 @@ ${rest}
7408
7897
  if (history && history.length >= 2) {
7409
7898
  trendHtml = `
7410
7899
  <section class="trend-section">
7411
- <h2>30-Day Trends</h2>
7900
+ <h2>${esc(t.trendTitle)}</h2>
7412
7901
  <div class="trend-chart">
7413
- <div class="trend-title">Findings by Severity</div>
7902
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7414
7903
  ${findingsTrendChart(history)}
7415
7904
  </div>
7416
7905
  <div class="trend-chart">
7417
- <div class="trend-title">Security Score</div>
7906
+ <div class="trend-title">${esc(t.securityScore)}</div>
7418
7907
  ${scoreTrendChart(history)}
7419
7908
  </div>
7420
7909
  </section>`;
@@ -7425,16 +7914,57 @@ ${rest}
7425
7914
  let recsHtml = "";
7426
7915
  if (summary.totalFindings > 0) {
7427
7916
  const recMap = /* @__PURE__ */ new Map();
7917
+ const kbPatches = [];
7918
+ let kbSeverity = "LOW";
7919
+ let kbUrl;
7920
+ const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7428
7921
  for (const f of allFindings) {
7429
7922
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
7923
+ const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
7924
+ if (genericPatterns.some((p) => rem.startsWith(p))) continue;
7925
+ const kbMatch = f.title.match(/KB\d+/);
7926
+ if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
7927
+ kbPatches.push(kbMatch[0]);
7928
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(kbSeverity)) kbSeverity = f.severity;
7929
+ if (!kbUrl && url) kbUrl = url;
7930
+ continue;
7931
+ }
7932
+ if (f.module === "security_hub_findings") {
7933
+ const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
7934
+ if (controlMatch) {
7935
+ const controlId = controlMatch[1];
7936
+ const key = `ctrl:${controlId}`;
7937
+ const existing2 = recMap.get(key);
7938
+ if (existing2) {
7939
+ existing2.count++;
7940
+ if (!existing2.url && url) existing2.url = url;
7941
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
7942
+ } else {
7943
+ recMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
7944
+ }
7945
+ continue;
7946
+ }
7947
+ }
7430
7948
  const existing = recMap.get(rem);
7431
7949
  if (existing) {
7432
7950
  existing.count++;
7951
+ if (!existing.url && url) existing.url = url;
7433
7952
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7434
7953
  existing.severity = f.severity;
7435
7954
  }
7436
7955
  } else {
7437
- recMap.set(rem, { text: rem, severity: f.severity, count: 1 });
7956
+ recMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
7957
+ }
7958
+ }
7959
+ if (kbPatches.length > 0) {
7960
+ const unique = [...new Set(kbPatches)];
7961
+ const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
7962
+ recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
7963
+ }
7964
+ for (const [key, rec] of recMap) {
7965
+ if (key.startsWith("ctrl:") && rec.count > 1) {
7966
+ rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
7967
+ rec.count = 1;
7438
7968
  }
7439
7969
  }
7440
7970
  const uniqueRecs = [...recMap.values()].sort((a, b) => {
@@ -7445,60 +7975,61 @@ ${rest}
7445
7975
  const renderRec = (r) => {
7446
7976
  const sev = r.severity.toLowerCase();
7447
7977
  const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
7448
- return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}</li>`;
7978
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</a>` : "";
7979
+ return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
7449
7980
  };
7450
7981
  const TOP_N = 10;
7451
7982
  const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
7452
7983
  const remaining = uniqueRecs.slice(TOP_N);
7453
7984
  const moreHtml = remaining.length > 0 ? `
7454
- <details><summary>Show ${remaining.length} more&hellip;</summary>
7985
+ <details><summary>${t.showMoreCount(remaining.length)}</summary>
7455
7986
  ${remaining.map(renderRec).join("\n")}
7456
7987
  </details>` : "";
7457
7988
  recsHtml = `
7458
7989
  <details class="rec-fold">
7459
- <summary><h2 style="margin:0;border:0;display:inline">Recommendations (${uniqueRecs.length} unique)</h2></summary>
7990
+ <summary><h2 style="margin:0;border:0;display:inline">${esc(t.recommendations)} (${uniqueRecs.length} ${esc(t.unique)})</h2></summary>
7460
7991
  <div class="rec-body">
7461
7992
  <ol>${topItems}${moreHtml}</ol>
7462
7993
  </div>
7463
7994
  </details>`;
7464
7995
  }
7465
7996
  return `<!DOCTYPE html>
7466
- <html lang="en">
7997
+ <html lang="${htmlLang}">
7467
7998
  <head>
7468
7999
  <meta charset="UTF-8">
7469
8000
  <meta name="viewport" content="width=device-width,initial-scale=1">
7470
- <title>AWS Security Scan Report &mdash; ${esc(date)}</title>
8001
+ <title>${esc(t.securityReportTitle)} &mdash; ${esc(date)}</title>
7471
8002
  <style>${sharedCss()}</style>
7472
8003
  </head>
7473
8004
  <body>
7474
8005
  <div class="container">
7475
8006
 
7476
8007
  <header>
7477
- <h1>&#128737;&#65039; AWS Security Scan Report</h1>
7478
- <div class="meta">Account: ${esc(accountId)} | Region: ${esc(region)} | ${esc(date)} | Duration: ${esc(duration)}</div>
8008
+ <h1>&#128737;&#65039; ${esc(t.securityReportTitle)}</h1>
8009
+ <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(date)} | ${esc(t.duration)}: ${esc(duration)}</div>
7479
8010
  </header>
7480
8011
 
7481
8012
  <section class="summary">
7482
8013
  <div class="score-card">
7483
8014
  <div class="score-value" style="color:${scoreColor(score)}">${score}</div>
7484
- <div class="score-label">Security Score</div>
8015
+ <div class="score-label">${esc(t.securityScore)}</div>
7485
8016
  </div>
7486
8017
  <div class="severity-stats">
7487
- <div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">Critical</div></div>
7488
- <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">High</div></div>
7489
- <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">Medium</div></div>
7490
- <div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">Low</div></div>
8018
+ <div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">${esc(t.critical)}</div></div>
8019
+ <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">${esc(t.high)}</div></div>
8020
+ <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">${esc(t.medium)}</div></div>
8021
+ <div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">${esc(t.low)}</div></div>
7491
8022
  </div>
7492
8023
  </section>
7493
8024
 
7494
8025
  <section class="charts">
7495
8026
  <div class="chart-box">
7496
- <div class="chart-title">Severity Distribution</div>
8027
+ <div class="chart-title">${esc(t.severityDistribution)}</div>
7497
8028
  <div style="text-align:center">${donutChart(summary)}</div>
7498
8029
  </div>
7499
8030
  <div class="chart-box">
7500
- <div class="chart-title">Findings by Module</div>
7501
- ${barChart(modules)}
8031
+ <div class="chart-title">${esc(t.findingsByModule)}</div>
8032
+ ${barChart(modules, t.allModulesClean)}
7502
8033
  </div>
7503
8034
  </section>
7504
8035
 
@@ -7506,33 +8037,35 @@ ${trendHtml}
7506
8037
 
7507
8038
  ${top5Html}
7508
8039
 
7509
- ${buildServiceReminderHtml(modules)}
8040
+ ${buildServiceReminderHtml(modules, lang)}
7510
8041
 
7511
8042
  <section>
7512
- <h2>Scan Statistics</h2>
8043
+ <h2>${esc(t.scanStatistics)}</h2>
7513
8044
  <table>
7514
- <thead><tr><th>Module</th><th>Resources</th><th>Findings</th><th>Status</th></tr></thead>
8045
+ <thead><tr><th>${esc(t.module)}</th><th>${esc(t.resources)}</th><th>${esc(t.findings)}</th><th>${esc(t.status)}</th></tr></thead>
7515
8046
  <tbody>${statsRows}</tbody>
7516
8047
  </table>
7517
8048
  </section>
7518
8049
 
7519
8050
  <section>
7520
- <h2>All Findings</h2>
8051
+ <h2>${esc(t.allFindings)}</h2>
7521
8052
  ${findingsHtml}
7522
8053
  </section>
7523
8054
 
7524
8055
  ${recsHtml}
7525
8056
 
7526
8057
  <footer>
7527
- <p>Generated by AWS Security MCP Server v${VERSION}</p>
7528
- <p>This report is for informational purposes only.</p>
8058
+ <p>${esc(t.generatedBy)} v${VERSION}</p>
8059
+ <p>${esc(t.informationalOnly)}</p>
7529
8060
  </footer>
7530
8061
 
7531
8062
  </div>
7532
8063
  </body>
7533
8064
  </html>`;
7534
8065
  }
7535
- function generateMlps3HtmlReport(scanResults, history) {
8066
+ function generateMlps3HtmlReport(scanResults, history, lang) {
8067
+ const t = getI18n(lang ?? "zh");
8068
+ const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
7536
8069
  const { accountId, region, scanStart } = scanResults;
7537
8070
  const date = scanStart.split("T")[0];
7538
8071
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
@@ -7549,17 +8082,21 @@ function generateMlps3HtmlReport(scanResults, history) {
7549
8082
  if (history && history.length >= 2) {
7550
8083
  trendHtml = `
7551
8084
  <section class="trend-section">
7552
- <h2>30\u65E5\u8D8B\u52BF</h2>
8085
+ <h2>${esc(t.trendTitle)}</h2>
7553
8086
  <div class="trend-chart">
7554
- <div class="trend-title">\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0</div>
8087
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7555
8088
  ${findingsTrendChart(history)}
7556
8089
  </div>
7557
8090
  <div class="trend-chart">
7558
- <div class="trend-title">\u5B89\u5168\u8BC4\u5206</div>
8091
+ <div class="trend-title">${esc(t.securityScore)}</div>
7559
8092
  ${scoreTrendChart(history)}
7560
8093
  </div>
7561
8094
  </section>`;
7562
8095
  }
8096
+ const isEn = (lang ?? "zh") === "en";
8097
+ const itemCat = (r) => isEn ? r.item.categoryEn : r.item.categoryCn;
8098
+ const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
8099
+ const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
7563
8100
  const categoryMap = /* @__PURE__ */ new Map();
7564
8101
  for (const r of results) {
7565
8102
  if (r.status === "not_applicable") continue;
@@ -7568,7 +8105,7 @@ function generateMlps3HtmlReport(scanResults, history) {
7568
8105
  categoryMap.get(cat).push(r);
7569
8106
  }
7570
8107
  const categorySections = MLPS3_CATEGORY_ORDER.map((category) => {
7571
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
8108
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
7572
8109
  const catResults = categoryMap.get(category);
7573
8110
  if (!catResults || catResults.length === 0) return "";
7574
8111
  const allCloud = catResults.every((r) => r.status === "cloud_provider");
@@ -7576,11 +8113,11 @@ function generateMlps3HtmlReport(scanResults, history) {
7576
8113
  return `<details class="category-fold mlps-cloud-section">
7577
8114
  <summary>
7578
8115
  <span class="category-title">${esc(sectionTitle)}</span>
7579
- <span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length} \u9879\u4E91\u5E73\u53F0\u8D1F\u8D23</span></span>
8116
+ <span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length} ${esc(t.cloudProvider)}</span></span>
7580
8117
  </summary>
7581
8118
  <div class="category-body">
7582
- <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>
7583
- ${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")}
8119
+ <div class="mlps-cloud-note">${esc(t.cloudItemsNote(catResults.length))}</div>
8120
+ ${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")}
7584
8121
  </div>
7585
8122
  </details>`;
7586
8123
  }
@@ -7602,31 +8139,34 @@ function generateMlps3HtmlReport(scanResults, history) {
7602
8139
  if (!controlMap.has(key)) controlMap.set(key, []);
7603
8140
  controlMap.get(key).push(r);
7604
8141
  }
7605
- const controlGroups = [...controlMap.entries()].map(([controlName, controlResults]) => {
8142
+ const controlGroups = [...controlMap.entries()].map(([_controlKey, controlResults]) => {
8143
+ const controlName = itemControl(controlResults[0]);
7606
8144
  const cloudItems = controlResults.filter((r) => r.status === "cloud_provider");
7607
8145
  const nonCloudItems = controlResults.filter((r) => r.status !== "cloud_provider");
7608
8146
  let itemsHtml = "";
7609
8147
  for (const r of nonCloudItems) {
7610
8148
  const icon = r.status === "clean" ? "\u{1F7E2}" : r.status === "issues" ? "\u{1F534}" : r.status === "unknown" ? "\u2B1C" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
7611
8149
  const cls = `check-${r.status === "cloud_provider" ? "cloud" : r.status}`;
7612
- const suffix = r.status === "unknown" ? " \u2014 \u672A\u68C0\u67E5" : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? "\u9700\u4EBA\u5DE5\u8BC4\u4F30")}` : "";
8150
+ const suffix = r.status === "unknown" ? ` \u2014 ${esc(t.notChecked)}` : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? t.manualReview)}` : "";
7613
8151
  let findingsDetail = "";
7614
8152
  if (r.status === "clean") {
7615
- findingsDetail = `<div class="check-detail">\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898</div>`;
8153
+ findingsDetail = `<div class="check-detail">${esc(t.noRelatedIssues)}</div>`;
7616
8154
  } else if (r.status === "issues" && r.relatedFindings.length > 0) {
7617
8155
  const fItems = r.relatedFindings.slice(0, 5).map((f) => `<li>${esc(f.severity)}: ${esc(f.title)}</li>`);
7618
8156
  if (r.relatedFindings.length > 5) {
7619
- fItems.push(`<li>... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 5} \u9879</li>`);
8157
+ fItems.push(`<li>${esc(t.andMore(r.relatedFindings.length - 5))}</li>`);
7620
8158
  }
7621
- 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>` : "";
7622
- 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>`;
8159
+ 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>` : "";
8160
+ 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>`;
7623
8161
  }
7624
- 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>
8162
+ const reqText = itemReq(r);
8163
+ 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>
7625
8164
  ${findingsDetail}`;
7626
8165
  }
7627
8166
  if (cloudItems.length > 0) {
7628
8167
  for (const r of cloudItems) {
7629
- 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>
8168
+ const reqText = itemReq(r);
8169
+ 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>
7630
8170
  `;
7631
8171
  }
7632
8172
  }
@@ -7659,20 +8199,61 @@ ${itemsHtml}
7659
8199
  let remediationHtml = "";
7660
8200
  if (failedResults.length > 0) {
7661
8201
  const mlpsRecMap = /* @__PURE__ */ new Map();
8202
+ const mlpsKbPatches = [];
8203
+ let mlpsKbSeverity = "LOW";
8204
+ let mlpsKbUrl;
8205
+ const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7662
8206
  for (const r of failedResults) {
7663
8207
  for (const f of r.relatedFindings) {
7664
8208
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
8209
+ const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
8210
+ if (mlpsGenericPatterns.some((p) => rem.startsWith(p))) continue;
8211
+ const kbMatch = f.title.match(/KB\d+/);
8212
+ if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
8213
+ mlpsKbPatches.push(kbMatch[0]);
8214
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsKbSeverity)) mlpsKbSeverity = f.severity;
8215
+ if (!mlpsKbUrl && url) mlpsKbUrl = url;
8216
+ continue;
8217
+ }
8218
+ if (f.module === "security_hub_findings") {
8219
+ const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
8220
+ if (controlMatch) {
8221
+ const controlId = controlMatch[1];
8222
+ const key = `ctrl:${controlId}`;
8223
+ const existing2 = mlpsRecMap.get(key);
8224
+ if (existing2) {
8225
+ existing2.count++;
8226
+ if (!existing2.url && url) existing2.url = url;
8227
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
8228
+ } else {
8229
+ mlpsRecMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
8230
+ }
8231
+ continue;
8232
+ }
8233
+ }
7665
8234
  const existing = mlpsRecMap.get(rem);
7666
8235
  if (existing) {
7667
8236
  existing.count++;
8237
+ if (!existing.url && url) existing.url = url;
7668
8238
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7669
8239
  existing.severity = f.severity;
7670
8240
  }
7671
8241
  } else {
7672
- mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
8242
+ mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
7673
8243
  }
7674
8244
  }
7675
8245
  }
8246
+ if (mlpsKbPatches.length > 0) {
8247
+ const unique = [...new Set(mlpsKbPatches)];
8248
+ const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
8249
+ mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
8250
+ }
8251
+ for (const [key, rec] of mlpsRecMap) {
8252
+ if (key.startsWith("ctrl:") && rec.count > 1) {
8253
+ rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
8254
+ rec.count = 1;
8255
+ }
8256
+ }
7676
8257
  const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
7677
8258
  const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
7678
8259
  if (sevDiff !== 0) return sevDiff;
@@ -7682,26 +8263,27 @@ ${itemsHtml}
7682
8263
  const renderMlpsRec = (r) => {
7683
8264
  const sev = r.severity.toLowerCase();
7684
8265
  const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
7685
- return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}</li>`;
8266
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</a>` : "";
8267
+ return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
7686
8268
  };
7687
8269
  const MLPS_TOP_N = 10;
7688
8270
  const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
7689
8271
  const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
7690
8272
  const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
7691
- <details><summary>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879&hellip;</summary>
8273
+ <details><summary>${esc(t.showRemaining(mlpsRemaining.length))}&hellip;</summary>
7692
8274
  ${mlpsRemaining.map(renderMlpsRec).join("\n")}
7693
8275
  </details>` : "";
7694
8276
  remediationHtml = `
7695
8277
  <details class="rec-fold" open>
7696
- <summary><h2 style="margin:0;border:0;display:inline">\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${mlpsUniqueRecs.length} \u9879\u53BB\u91CD\uFF09</h2></summary>
8278
+ <summary><h2 style="margin:0;border:0;display:inline">${esc(t.remediationItems(mlpsUniqueRecs.length))}</h2></summary>
7697
8279
  <div class="rec-body">
7698
8280
  <ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
7699
8281
  </div>
7700
8282
  </details>`;
7701
8283
  }
7702
8284
  }
7703
- 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>` : "";
7704
- 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>` : "";
8285
+ const naNote = naCount > 0 ? `<p style="color:#64748b;font-size:13px;margin-top:24px">${esc(t.naNote(naCount))}</p>` : "";
8286
+ const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">${esc(t.unknownNote(autoUnknown))}</div>` : "";
7705
8287
  const mlpsCss = `
7706
8288
  .mlps-cloud-section>summary{color:#94a3b8}
7707
8289
  .mlps-cloud-note{color:#94a3b8;font-size:13px;margin-bottom:12px;font-style:italic}
@@ -7723,40 +8305,40 @@ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
7723
8305
  .mlps-summary-card .stat-label{font-size:12px;color:#94a3b8;margin-top:2px}
7724
8306
  `;
7725
8307
  return `<!DOCTYPE html>
7726
- <html lang="zh-CN">
8308
+ <html lang="${htmlLang}">
7727
8309
  <head>
7728
8310
  <meta charset="UTF-8">
7729
8311
  <meta name="viewport" content="width=device-width,initial-scale=1">
7730
- <title>\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A &mdash; ${esc(date)}</title>
8312
+ <title>${esc(t.mlpsTitle)} &mdash; ${esc(date)}</title>
7731
8313
  <style>${sharedCss()}${mlpsCss}</style>
7732
8314
  </head>
7733
8315
  <body>
7734
8316
  <div class="container">
7735
8317
 
7736
8318
  <header>
7737
- <h1>&#128737;&#65039; \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A</h1>
7738
- <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>
7739
- <div class="meta">\u8D26\u6237: ${esc(accountId)} | \u533A\u57DF: ${esc(region)} | \u626B\u63CF\u65F6\u95F4: ${esc(scanTime)}</div>
8319
+ <h1>&#128737;&#65039; ${esc(t.mlpsTitle)}</h1>
8320
+ <div class="disclaimer">${esc(t.mlpsDisclaimer)}</div>
8321
+ <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(t.scanTime)}: ${esc(scanTime)}</div>
7740
8322
  </header>
7741
8323
 
7742
8324
  <section class="summary" style="display:block;text-align:center">
7743
8325
  <div style="font-size:36px;font-weight:700;margin-bottom:12px">
7744
- <span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">\u672A\u53D1\u73B0\u95EE\u9898</span>
8326
+ <span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.noIssues)}</span>
7745
8327
  <span style="color:#475569;margin:0 16px">/</span>
7746
- <span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px">\u53D1\u73B0\u95EE\u9898</span>
8328
+ <span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.issuesFound)}</span>
7747
8329
  </div>
7748
8330
  <div class="mlps-summary-cards" style="justify-content:center">
7749
- <div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">\u5DF2\u68C0\u67E5\u9879</div></div>
7750
- <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>
7751
- <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>
7752
- ${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>` : ""}
8331
+ <div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">${esc(t.checkedItems)}</div></div>
8332
+ <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>
8333
+ <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>
8334
+ ${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>` : ""}
7753
8335
  </div>
7754
8336
  </section>
7755
8337
  ${unknownNote}
7756
8338
 
7757
8339
  ${trendHtml}
7758
8340
 
7759
- ${buildServiceReminderHtml(scanResults.modules)}
8341
+ ${buildServiceReminderHtml(scanResults.modules, lang)}
7760
8342
 
7761
8343
  ${categorySections}
7762
8344
 
@@ -7765,8 +8347,8 @@ ${remediationHtml}
7765
8347
  ${naNote}
7766
8348
 
7767
8349
  <footer>
7768
- <p>\u7531 AWS Security MCP Server v${VERSION} \u751F\u6210</p>
7769
- <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>
8350
+ <p>${esc(t.mlpsFooterGenerated(VERSION))}</p>
8351
+ <p>${esc(t.mlpsFooterDisclaimer)}</p>
7770
8352
  </footer>
7771
8353
 
7772
8354
  </div>
@@ -7981,16 +8563,14 @@ Aggregates active findings from AWS Security Hub. Replaces individual config sca
7981
8563
  - INFORMATIONAL findings are skipped.
7982
8564
 
7983
8565
  ## 3. GuardDuty Findings (guardduty_findings)
7984
- Aggregates threat detection findings from Amazon GuardDuty.
7985
- - Covers account compromise, instance compromise, and reconnaissance.
7986
- - Severity mapped from GuardDuty 0\u201310 scale: \u22657 \u2192 HIGH, \u22654 \u2192 MEDIUM, <4 \u2192 LOW.
7987
- - Only non-archived findings are included.
8566
+ Detection-only: checks if GuardDuty is enabled in the region.
8567
+ - GuardDuty findings are aggregated via Security Hub (security_hub_findings module).
8568
+ - Reports whether GuardDuty detectors are active.
7988
8569
 
7989
8570
  ## 4. Inspector Findings (inspector_findings)
7990
- Aggregates vulnerability findings from Amazon Inspector v2.
7991
- - Covers CVEs in EC2 instances, Lambda functions, and container images.
7992
- - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
7993
- - CVE IDs are included in finding titles when available.
8571
+ Detection-only: checks if Inspector is enabled in the region.
8572
+ - Inspector findings are aggregated via Security Hub (security_hub_findings module).
8573
+ - Reports whether Inspector scanning (EC2/Lambda) is active.
7994
8574
 
7995
8575
  ## 5. Trusted Advisor Findings (trusted_advisor_findings)
7996
8576
  Aggregates security checks from AWS Trusted Advisor.
@@ -8026,19 +8606,15 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
8026
8606
  Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
8027
8607
 
8028
8608
  ## 15. Config Rules Findings (config_rules_findings)
8029
- Pulls non-compliant AWS Config Rule evaluation results.
8030
- - Lists all Config Rules and their compliance status.
8031
- - For NON_COMPLIANT rules, retrieves specific non-compliant resources.
8032
- - Security-related rules (encryption, IAM, public access, etc.) mapped to HIGH severity (7.5).
8033
- - Other non-compliant rules mapped to MEDIUM severity (5.5).
8609
+ Detection-only: checks if AWS Config Rules are configured.
8610
+ - Config Rule compliance findings are aggregated via Security Hub (security_hub_findings module).
8611
+ - Reports whether Config is enabled and counts active rules.
8034
8612
  - Gracefully handles regions where AWS Config is not enabled.
8035
8613
 
8036
8614
  ## 16. IAM Access Analyzer Findings (access_analyzer_findings)
8037
- Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account.
8038
- - Lists active analyzers (ACCOUNT or ORGANIZATION type).
8039
- - Retrieves ACTIVE findings showing external access to resources.
8040
- - Covers S3 buckets, IAM roles, SQS queues, Lambda functions, KMS keys, and more.
8041
- - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
8615
+ Detection-only: checks if IAM Access Analyzer is configured.
8616
+ - Access Analyzer findings are aggregated via Security Hub (security_hub_findings module).
8617
+ - Reports whether active analyzers exist.
8042
8618
  - Returns warning if no analyzer is configured.
8043
8619
 
8044
8620
  ## 17. SSM Patch Compliance (patch_compliance_findings)
@@ -8123,112 +8699,18 @@ var MODULE_DESCRIPTIONS = {
8123
8699
  idle_resources: "Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped instances, unused security groups) that waste money and increase attack surface.",
8124
8700
  disaster_recovery: "Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.",
8125
8701
  security_hub_findings: "Aggregates active findings from AWS Security Hub \u2014 replaces individual config scanners with centralized compliance checks.",
8126
- guardduty_findings: "Aggregates threat detection findings from Amazon GuardDuty \u2014 account compromise, instance compromise, and reconnaissance.",
8127
- inspector_findings: "Aggregates vulnerability findings from Amazon Inspector \u2014 CVEs in EC2, Lambda, and container images.",
8702
+ guardduty_findings: "Checks if GuardDuty is enabled. Findings are aggregated via Security Hub.",
8703
+ inspector_findings: "Checks if Inspector is enabled. Findings are aggregated via Security Hub.",
8128
8704
  trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
8129
- config_rules_findings: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
8130
- access_analyzer_findings: "Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account (external principals, public access).",
8705
+ config_rules_findings: "Checks if AWS Config Rules are configured. Findings are aggregated via Security Hub.",
8706
+ access_analyzer_findings: "Checks if IAM Access Analyzer is configured. Findings are aggregated via Security Hub.",
8131
8707
  patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches.",
8132
8708
  imdsv2_enforcement: "Checks if EC2 instances enforce IMDSv2 (HttpTokens: required) \u2014 IMDSv1 allows credential theft via SSRF.",
8133
8709
  waf_coverage: "Checks if internet-facing ALBs have WAF Web ACL associated for protection against common web exploits."
8134
8710
  };
8135
- var HW_DEFENSE_CHECKLIST = `
8136
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8137
- \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
8138
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8139
-
8140
- \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
8141
-
8142
- \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
8143
- \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
8144
- \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
8145
- \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
8146
- \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
8147
-
8148
- \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
8149
- \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
8150
- \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
8151
- \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
8152
-
8153
- \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
8154
- \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
8155
- \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
8156
- \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
8157
- \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
8158
- \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
8159
-
8160
- \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
8161
- \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
8162
- \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
8163
- \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
8164
-
8165
- \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
8166
- \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
8167
- \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
8168
- \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
8169
-
8170
- \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
8171
- \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
8172
- \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
8173
- \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A"\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0"
8174
-
8175
- \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
8176
- \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
8177
- \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
8178
- \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
8179
- \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
8180
-
8181
- \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
8182
- \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
8183
- \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
8184
- \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
8185
-
8186
- \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
8187
- `;
8188
- var SERVICE_RECOMMENDATIONS2 = {
8189
- security_hub_findings: {
8190
- icon: "\u{1F534}",
8191
- service: "Security Hub",
8192
- impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
8193
- action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
8194
- },
8195
- guardduty_findings: {
8196
- icon: "\u{1F534}",
8197
- service: "GuardDuty",
8198
- 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",
8199
- action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
8200
- },
8201
- inspector_findings: {
8202
- icon: "\u{1F7E1}",
8203
- service: "Inspector",
8204
- impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
8205
- action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
8206
- },
8207
- trusted_advisor_findings: {
8208
- icon: "\u{1F7E1}",
8209
- service: "Trusted Advisor",
8210
- impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
8211
- action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
8212
- },
8213
- config_rules_findings: {
8214
- icon: "\u{1F7E1}",
8215
- service: "AWS Config",
8216
- impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
8217
- action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
8218
- },
8219
- access_analyzer_findings: {
8220
- icon: "\u{1F7E1}",
8221
- service: "IAM Access Analyzer",
8222
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
8223
- action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
8224
- },
8225
- patch_compliance_findings: {
8226
- icon: "\u{1F7E1}",
8227
- service: "SSM Patch Manager",
8228
- impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
8229
- action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
8230
- }
8231
- };
8711
+ function getHwDefenseChecklist(lang) {
8712
+ return getI18n(lang ?? "zh").hwChecklist;
8713
+ }
8232
8714
  var SERVICE_NOT_ENABLED_PATTERNS2 = [
8233
8715
  "not enabled",
8234
8716
  "not found",
@@ -8238,10 +8720,11 @@ var SERVICE_NOT_ENABLED_PATTERNS2 = [
8238
8720
  "not available",
8239
8721
  "is not enabled"
8240
8722
  ];
8241
- function buildServiceReminder(modules) {
8723
+ function buildServiceReminder(modules, lang) {
8724
+ const t = getI18n(lang ?? "zh");
8242
8725
  const disabledServices = [];
8243
8726
  for (const mod of modules) {
8244
- const rec = SERVICE_RECOMMENDATIONS2[mod.module];
8727
+ const rec = t.serviceRecommendations[mod.module];
8245
8728
  if (!rec) continue;
8246
8729
  if (!mod.warnings?.length) continue;
8247
8730
  const hasNotEnabled = mod.warnings.some(
@@ -8254,26 +8737,26 @@ function buildServiceReminder(modules) {
8254
8737
  if (disabledServices.length === 0) return "";
8255
8738
  const lines = [
8256
8739
  "",
8257
- "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
8740
+ t.serviceReminderTitle,
8258
8741
  ""
8259
8742
  ];
8260
8743
  for (const svc of disabledServices) {
8261
- lines.push(`${svc.icon} ${svc.service} \u672A\u542F\u7528`);
8262
- lines.push(` \u5F71\u54CD\uFF1A${svc.impact}`);
8263
- lines.push(` \u5EFA\u8BAE\uFF1A${svc.action}`);
8744
+ lines.push(`${svc.icon} ${svc.service} ${t.notEnabled}`);
8745
+ lines.push(` ${t.serviceImpact}: ${svc.impact}`);
8746
+ lines.push(` ${t.serviceAction}: ${svc.action}`);
8264
8747
  lines.push("");
8265
8748
  }
8266
- 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");
8749
+ lines.push(t.serviceReminderFooter);
8267
8750
  return lines.join("\n");
8268
8751
  }
8269
- function summarizeResult(result) {
8752
+ function summarizeResult(result, lang) {
8270
8753
  const { summary } = result;
8271
8754
  const lines = [
8272
8755
  `Scan complete for account ${result.accountId} in ${result.region}.`,
8273
8756
  `Total findings: ${summary.totalFindings} (${summary.critical} Critical, ${summary.high} High, ${summary.medium} Medium, ${summary.low} Low)`,
8274
8757
  `Modules: ${summary.modulesSuccess} succeeded, ${summary.modulesError} errored`
8275
8758
  ];
8276
- const reminder = buildServiceReminder(result.modules);
8759
+ const reminder = buildServiceReminder(result.modules, lang);
8277
8760
  if (reminder) {
8278
8761
  lines.push(reminder);
8279
8762
  }
@@ -8335,9 +8818,10 @@ function createServer(defaultRegion) {
8335
8818
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8336
8819
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8337
8820
  role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
8338
- account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)")
8821
+ account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
8822
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8339
8823
  },
8340
- async ({ region, org_mode, role_name, account_ids }) => {
8824
+ async ({ region, org_mode, role_name, account_ids, lang }) => {
8341
8825
  try {
8342
8826
  const r = region ?? defaultRegion;
8343
8827
  let result;
@@ -8352,7 +8836,7 @@ function createServer(defaultRegion) {
8352
8836
  }
8353
8837
  return {
8354
8838
  content: [
8355
- { type: "text", text: summarizeResult(result) },
8839
+ { type: "text", text: summarizeResult(result, lang ?? "zh") },
8356
8840
  { type: "text", text: JSON.stringify(result, null, 2) }
8357
8841
  ]
8358
8842
  };
@@ -8413,9 +8897,10 @@ function createServer(defaultRegion) {
8413
8897
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8414
8898
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8415
8899
  role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
8416
- account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)")
8900
+ account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
8901
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8417
8902
  },
8418
- async ({ group, region, org_mode, role_name, account_ids }) => {
8903
+ async ({ group, region, org_mode, role_name, account_ids, lang }) => {
8419
8904
  try {
8420
8905
  const groupDef = SCAN_GROUPS[group];
8421
8906
  if (!groupDef) {
@@ -8497,7 +8982,7 @@ function createServer(defaultRegion) {
8497
8982
  `Scan group: ${groupDef.name} (${group})`,
8498
8983
  groupDef.description,
8499
8984
  "",
8500
- summarizeResult(result)
8985
+ summarizeResult(result, lang ?? "zh")
8501
8986
  ];
8502
8987
  if (missingModules.length > 0) {
8503
8988
  lines.push("");
@@ -8510,7 +8995,7 @@ function createServer(defaultRegion) {
8510
8995
  if (group === "hw_defense") {
8511
8996
  const summaryContent = content[0];
8512
8997
  if (summaryContent && summaryContent.type === "text") {
8513
- summaryContent.text += "\n\n" + HW_DEFENSE_CHECKLIST;
8998
+ summaryContent.text += "\n\n" + getHwDefenseChecklist(lang ?? "zh");
8514
8999
  }
8515
9000
  }
8516
9001
  return { content };
@@ -8540,11 +9025,14 @@ function createServer(defaultRegion) {
8540
9025
  server.tool(
8541
9026
  "generate_report",
8542
9027
  "Generate a Markdown security report from scan results. Read-only. Does not modify any AWS resources.",
8543
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
8544
- async ({ scan_results }) => {
9028
+ {
9029
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9030
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9031
+ },
9032
+ async ({ scan_results, lang }) => {
8545
9033
  try {
8546
9034
  const parsed = JSON.parse(scan_results);
8547
- const report = generateMarkdownReport(parsed);
9035
+ const report = generateMarkdownReport(parsed, lang ?? "zh");
8548
9036
  return { content: [{ type: "text", text: report }] };
8549
9037
  } catch (err) {
8550
9038
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8554,11 +9042,14 @@ function createServer(defaultRegion) {
8554
9042
  server.tool(
8555
9043
  "generate_mlps3_report",
8556
9044
  "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.",
8557
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all") },
8558
- async ({ scan_results }) => {
9045
+ {
9046
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
9047
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9048
+ },
9049
+ async ({ scan_results, lang }) => {
8559
9050
  try {
8560
9051
  const parsed = JSON.parse(scan_results);
8561
- const report = generateMlps3Report(parsed);
9052
+ const report = generateMlps3Report(parsed, lang ?? "zh");
8562
9053
  return { content: [{ type: "text", text: report }] };
8563
9054
  } catch (err) {
8564
9055
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8570,13 +9061,14 @@ function createServer(defaultRegion) {
8570
9061
  "Generate a professional HTML security report. Save the output as an .html file.",
8571
9062
  {
8572
9063
  scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
8573
- history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts")
9064
+ history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
9065
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8574
9066
  },
8575
- async ({ scan_results, history }) => {
9067
+ async ({ scan_results, history, lang }) => {
8576
9068
  try {
8577
9069
  const parsed = JSON.parse(scan_results);
8578
9070
  const historyData = history ? JSON.parse(history) : void 0;
8579
- const report = generateHtmlReport(parsed, historyData);
9071
+ const report = generateHtmlReport(parsed, historyData, lang ?? "zh");
8580
9072
  return { content: [{ type: "text", text: report }] };
8581
9073
  } catch (err) {
8582
9074
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8588,13 +9080,14 @@ function createServer(defaultRegion) {
8588
9080
  "Generate a professional HTML MLPS Level 3 compliance report (\u7B49\u4FDD\u4E09\u7EA7). Save as .html file.",
8589
9081
  {
8590
9082
  scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
8591
- history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts")
9083
+ history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
9084
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8592
9085
  },
8593
- async ({ scan_results, history }) => {
9086
+ async ({ scan_results, history, lang }) => {
8594
9087
  try {
8595
9088
  const parsed = JSON.parse(scan_results);
8596
9089
  const historyData = history ? JSON.parse(history) : void 0;
8597
- const report = generateMlps3HtmlReport(parsed, historyData);
9090
+ const report = generateMlps3HtmlReport(parsed, historyData, lang ?? "zh");
8598
9091
  return { content: [{ type: "text", text: report }] };
8599
9092
  } catch (err) {
8600
9093
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8604,7 +9097,10 @@ function createServer(defaultRegion) {
8604
9097
  server.tool(
8605
9098
  "generate_maturity_report",
8606
9099
  "Generate a security maturity assessment report from scan_all results. Requires service_detection module output. Read-only.",
8607
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
9100
+ {
9101
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9102
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9103
+ },
8608
9104
  async ({ scan_results }) => {
8609
9105
  try {
8610
9106
  const parsed = JSON.parse(scan_results);
@@ -8875,7 +9371,7 @@ ${finding}`
8875
9371
  type: "text",
8876
9372
  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
8877
9373
 
8878
- ${HW_DEFENSE_CHECKLIST}
9374
+ ${getHwDefenseChecklist("zh")}
8879
9375
 
8880
9376
  \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`
8881
9377
  }