aws-security-mcp 0.5.2 → 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.2";
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",
@@ -2211,7 +2219,6 @@ import {
2211
2219
  DescribeNetworkInterfacesCommand,
2212
2220
  DescribeSecurityGroupsCommand as DescribeSecurityGroupsCommand2
2213
2221
  } from "@aws-sdk/client-ec2";
2214
- var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
2215
2222
  function makeFinding9(opts) {
2216
2223
  const severity = severityFromScore(opts.riskScore);
2217
2224
  return { ...opts, severity, priority: priorityFromSeverity(severity) };
@@ -2736,14 +2743,21 @@ var SecurityHubFindingsScanner = class {
2736
2743
  const resourceType = f.Resources?.[0]?.Type ?? "AWS::Unknown";
2737
2744
  const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:securityhub:${region}:${accountId}:finding/${f.Id ?? "unknown"}`;
2738
2745
  const remediationSteps = [];
2739
- if (f.Remediation?.Recommendation?.Text) {
2740
- 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);
2741
2754
  }
2742
2755
  if (f.Remediation?.Recommendation?.Url) {
2743
- remediationSteps.push(`Reference: ${f.Remediation.Recommendation.Url}`);
2756
+ remediationSteps.push(`Documentation: ${f.Remediation.Recommendation.Url}`);
2744
2757
  }
2745
- if (remediationSteps.length === 0) {
2746
- 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);
2747
2761
  }
2748
2762
  findings.push({
2749
2763
  severity,
@@ -2896,10 +2910,11 @@ var GuardDutyFindingsScanner = class {
2896
2910
  impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
2897
2911
  riskScore: score,
2898
2912
  remediationSteps: [
2899
- "Review the finding in the Amazon GuardDuty console.",
2900
- `Finding type: ${gdf.Type ?? "unknown"}`,
2901
- "Follow the recommended remediation in the GuardDuty documentation."
2902
- ],
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),
2903
2918
  priority: priorityFromSeverity(severity),
2904
2919
  module: this.moduleName,
2905
2920
  accountId: gdf.AccountId ?? accountId
@@ -2990,18 +3005,44 @@ var InspectorFindingsScanner = class {
2990
3005
  const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
2991
3006
  const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
2992
3007
  const remediationSteps = [];
2993
- 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) {
2994
3018
  remediationSteps.push(f.remediation.recommendation.text);
2995
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
+ }
2996
3040
  if (f.remediation?.recommendation?.Url) {
2997
- remediationSteps.push(`Reference: ${f.remediation.recommendation.Url}`);
3041
+ remediationSteps.push(`Documentation: ${f.remediation.recommendation.Url}`);
2998
3042
  }
2999
3043
  if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
3000
3044
  remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
3001
3045
  }
3002
- if (remediationSteps.length === 0) {
3003
- remediationSteps.push("Review the finding in the Amazon Inspector console and apply the recommended patch or update.");
3004
- }
3005
3046
  const description = f.description ?? titleBase;
3006
3047
  const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
3007
3048
  findings.push({
@@ -3336,10 +3377,10 @@ var ConfigRulesFindingsScanner = class {
3336
3377
  impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
3337
3378
  riskScore,
3338
3379
  remediationSteps: [
3339
- `Review the Config Rule "${ruleName}" in the AWS Config console.`,
3340
- `Check resource ${resourceId} for compliance violations.`,
3341
- "Follow the rule's remediation guidance to bring the resource into compliance."
3342
- ],
3380
+ `Fix Config Rule violation: ${ruleName}`,
3381
+ annotation ? `Details: ${annotation}` : "",
3382
+ `Resource: ${resourceType}/${resourceId}`
3383
+ ].filter(Boolean),
3343
3384
  priority: priorityFromSeverity(severity),
3344
3385
  module: this.moduleName,
3345
3386
  accountId
@@ -3488,13 +3529,13 @@ var AccessAnalyzerFindingsScanner = class {
3488
3529
  const title = buildFindingTitle(aaf);
3489
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"}`;
3490
3531
  const remediationSteps = external ? [
3491
- "Review the finding in the IAM Access Analyzer console.",
3492
- `Check resource ${resourceId} for unintended external access.`,
3493
- "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}`
3494
3535
  ] : [
3495
- "Review the finding in the IAM Access Analyzer console.",
3496
- `Check resource ${resourceId} for unused access permissions.`,
3497
- "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}`
3498
3539
  ];
3499
3540
  findings.push({
3500
3541
  severity,
@@ -3969,6 +4010,479 @@ var WafCoverageScanner = class {
3969
4010
  }
3970
4011
  };
3971
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
+
3972
4486
  // src/tools/report-tool.ts
3973
4487
  var SEVERITY_ICON = {
3974
4488
  CRITICAL: "\u{1F534}",
@@ -3986,38 +4500,45 @@ function formatDuration(start, end) {
3986
4500
  const remainSecs = secs % 60;
3987
4501
  return `${mins}m ${remainSecs}s`;
3988
4502
  }
3989
- function renderFinding(f) {
3990
- const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
3991
- return [
3992
- `#### ${f.title}`,
3993
- `- **Resource:** ${f.resourceId} (\`${f.resourceArn}\`)`,
3994
- `- **Description:** ${f.description}`,
3995
- `- **Impact:** ${f.impact}`,
3996
- `- **Risk Score:** ${f.riskScore}/10`,
3997
- `- **Remediation:**`,
3998
- steps,
3999
- `- **Priority:** ${f.priority}`
4000
- ].join("\n");
4001
- }
4002
- function generateMarkdownReport(scanResults) {
4503
+ function generateMarkdownReport(scanResults, lang) {
4504
+ const t = getI18n(lang ?? "zh");
4003
4505
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
4004
4506
  const date = scanStart.split("T")[0];
4005
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
+ }
4006
4527
  const lines = [];
4007
- lines.push(`# AWS Security Scan Report \u2014 ${date}`);
4528
+ lines.push(`# ${t.securityReportTitle} \u2014 ${date}`);
4008
4529
  lines.push("");
4009
- lines.push("## Executive Summary");
4010
- lines.push(`- **Account:** ${accountId}`);
4011
- lines.push(`- **Region:** ${region}`);
4012
- 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}`);
4013
4534
  lines.push(
4014
- `- **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})`
4015
4536
  );
4016
4537
  lines.push("");
4017
4538
  if (summary.totalFindings === 0) {
4018
- lines.push("## Findings by Severity");
4539
+ lines.push(`## ${t.findingsBySeverity}`);
4019
4540
  lines.push("");
4020
- lines.push("\u2705 No security issues found.");
4541
+ lines.push(`\u2705 ${t.noIssuesFound}`);
4021
4542
  lines.push("");
4022
4543
  } else {
4023
4544
  const allFindings = modules.flatMap((m) => m.findings);
@@ -4028,15 +4549,15 @@ function generateMarkdownReport(scanResults) {
4028
4549
  for (const f of allFindings) {
4029
4550
  grouped.get(f.severity).push(f);
4030
4551
  }
4031
- lines.push("## Findings by Severity");
4552
+ lines.push(`## ${t.findingsBySeverity}`);
4032
4553
  lines.push("");
4033
4554
  for (const sev of SEVERITY_ORDER) {
4034
4555
  const findings = grouped.get(sev);
4035
4556
  const icon = SEVERITY_ICON[sev];
4036
- lines.push(`### ${icon} ${sev.charAt(0)}${sev.slice(1).toLowerCase()}`);
4557
+ lines.push(`### ${icon} ${sevLabel[sev]}`);
4037
4558
  lines.push("");
4038
4559
  if (findings.length === 0) {
4039
- lines.push(`No ${sev.toLowerCase()} findings.`);
4560
+ lines.push(t.noFindingsForSeverity(sevLabel[sev]));
4040
4561
  lines.push("");
4041
4562
  continue;
4042
4563
  }
@@ -4047,9 +4568,9 @@ function generateMarkdownReport(scanResults) {
4047
4568
  }
4048
4569
  }
4049
4570
  }
4050
- lines.push("## Scan Statistics");
4571
+ lines.push(`## ${t.scanStatistics}`);
4051
4572
  lines.push(
4052
- "| Module | Resources Scanned | Findings | Status |"
4573
+ `| ${t.module} | ${t.resources} | ${t.findings} | ${t.status} |`
4053
4574
  );
4054
4575
  lines.push("|--------|------------------|----------|--------|");
4055
4576
  for (const m of modules) {
@@ -4062,7 +4583,7 @@ function generateMarkdownReport(scanResults) {
4062
4583
  if (summary.totalFindings > 0) {
4063
4584
  const allFindings = modules.flatMap((m) => m.findings);
4064
4585
  allFindings.sort((a, b) => b.riskScore - a.riskScore);
4065
- lines.push("## Recommendations (Priority Order)");
4586
+ lines.push(`## ${t.recommendations}`);
4066
4587
  for (let i = 0; i < allFindings.length; i++) {
4067
4588
  const f = allFindings[i];
4068
4589
  lines.push(`${i + 1}. [${f.priority}] ${f.title}: ${f.remediationSteps[0] ?? "Review and remediate."}`);
@@ -6109,13 +6630,6 @@ var MLPS3_CATEGORY_ORDER = [
6109
6630
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6110
6631
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6111
6632
  ];
6112
- var MLPS3_CATEGORY_SECTION = {
6113
- "\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
6114
- "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
6115
- "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
6116
- "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6117
- "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6118
- };
6119
6633
  var MLPS3_CHECK_MAPPING = [
6120
6634
  // =========================================================================
6121
6635
  // 安全物理环境 — L3-PES1-* (22 items) → cloud_provider
@@ -6680,9 +7194,8 @@ var MLPS3_CHECK_MAPPING = [
6680
7194
  },
6681
7195
  {
6682
7196
  id: "L3-SMC1-09",
6683
- type: "auto",
6684
- modules: ["service_detection"],
6685
- findingPatterns: ["CloudWatch"]
7197
+ type: "manual",
7198
+ guidance: "\u9700\u914D\u7F6E CloudWatch \u96C6\u4E2D\u76D1\u63A7\u5E73\u53F0\uFF0C\u7ED3\u5408 SNS \u8FDB\u884C\u544A\u8B66\u901A\u77E5"
6686
7199
  },
6687
7200
  {
6688
7201
  id: "L3-SMC1-10",
@@ -6776,232 +7289,68 @@ function evaluateAllFullChecks(scanResults) {
6776
7289
  return evaluateFullCheck(item, mapping, allFindings, scanModules);
6777
7290
  });
6778
7291
  }
6779
- var MLPS_CHECKS = [
6780
- // 一、身份鉴别
6781
- {
6782
- id: "8.1.4.1a",
6783
- category: "\u8EAB\u4EFD\u9274\u522B",
6784
- name: "\u5BC6\u7801\u7B56\u7565",
6785
- modules: ["security_hub_findings"],
6786
- findingPatterns: ["password policy", "password length", "complexity", "password expiry", "reuse prevention", "IAM.7", "IAM.10"]
6787
- },
6788
- {
6789
- id: "8.1.4.1a",
6790
- category: "\u8EAB\u4EFD\u9274\u522B",
6791
- name: "\u5BC6\u94A5\u8F6E\u6362",
6792
- modules: ["security_hub_findings"],
6793
- findingPatterns: ["access key older", "access key rotated", "IAM.3", "IAM.4"]
6794
- },
6795
- {
6796
- id: "8.1.4.1d",
6797
- category: "\u8EAB\u4EFD\u9274\u522B",
6798
- name: "\u53CC\u56E0\u7D20\u8BA4\u8BC1",
6799
- modules: ["security_hub_findings"],
6800
- findingPatterns: ["MFA", "IAM.5", "IAM.6"]
6801
- },
6802
- // 二、访问控制
6803
- {
6804
- id: "8.1.4.2c",
6805
- category: "\u8BBF\u95EE\u63A7\u5236",
6806
- name: "\u6700\u5C0F\u6743\u9650",
6807
- modules: ["iam_privilege_escalation", "security_hub_findings"],
6808
- findingPatterns: [
6809
- "AdministratorAccess",
6810
- "PowerUserAccess",
6811
- "IAMFullAccess",
6812
- "over-permissive",
6813
- "privilege escalation",
6814
- "self-grant",
6815
- "iam:*",
6816
- "create admin",
6817
- "Lambda role passing",
6818
- "CreateAccessKey",
6819
- "AssumeRole"
6820
- ]
6821
- },
6822
- {
6823
- id: "8.1.4.2",
6824
- category: "\u8BBF\u95EE\u63A7\u5236",
6825
- name: "\u5B89\u5168\u7EC4",
6826
- modules: ["network_reachability", "security_hub_findings"],
6827
- findingPatterns: ["allows all ports", "allows SSH", "allows RDP", "MySQL", "PostgreSQL", "MongoDB", "Redis", "high-risk port", "security group", "EC2.18", "EC2.19"]
6828
- },
6829
- // 三、安全审计
6830
- {
6831
- id: "8.1.4.3a",
6832
- category: "\u5B89\u5168\u5BA1\u8BA1",
6833
- name: "\u5BA1\u8BA1\u529F\u80FD",
6834
- modules: ["security_hub_findings"],
6835
- findingPatterns: ["CloudTrail", "not enabled", "multi-region", "not logging", "CloudTrail.1"]
6836
- },
6837
- {
6838
- id: "8.1.4.3b",
6839
- category: "\u5B89\u5168\u5BA1\u8BA1",
6840
- name: "\u5BA1\u8BA1\u5B8C\u6574\u6027",
6841
- modules: ["security_hub_findings"],
6842
- findingPatterns: ["log file validation", "log integrity", "log validation", "CloudTrail.4", "CloudTrail.5"]
6843
- },
6844
- {
6845
- id: "8.1.4.3c",
6846
- category: "\u5B89\u5168\u5BA1\u8BA1",
6847
- name: "\u5BA1\u8BA1\u4FDD\u62A4",
6848
- modules: ["security_hub_findings"],
6849
- findingPatterns: ["CloudTrail", "S3 bucket", "encryption", "versioning", "Block Public Access", "CloudTrail.6", "CloudTrail.7"]
6850
- },
6851
- // 四、入侵防范
6852
- {
6853
- id: "8.1.4.4a",
6854
- category: "\u5165\u4FB5\u9632\u8303",
6855
- name: "GuardDuty \u5A01\u80C1\u68C0\u6D4B",
6856
- modules: ["service_detection", "guardduty_findings"],
6857
- findingPatterns: ["GuardDuty"]
6858
- },
6859
- {
6860
- id: "8.1.4.4a",
6861
- category: "\u5165\u4FB5\u9632\u8303",
6862
- name: "Inspector \u6F0F\u6D1E\u626B\u63CF",
6863
- modules: ["service_detection", "inspector_findings"],
6864
- findingPatterns: ["Inspector", "CVE-"]
6865
- },
6866
- // 五、数据安全
6867
- {
6868
- id: "8.1.4.5a",
6869
- category: "\u6570\u636E\u5B89\u5168",
6870
- name: "\u4F20\u8F93\u52A0\u5BC6",
6871
- modules: ["ssl_certificate", "security_hub_findings"],
6872
- findingPatterns: ["HTTPS", "TLS", "HTTP listener", "certificate", "ELB.1"]
6873
- },
6874
- {
6875
- id: "8.1.4.5b",
6876
- category: "\u6570\u636E\u5B89\u5168",
6877
- name: "S3 \u5B58\u50A8\u52A0\u5BC6",
6878
- modules: ["security_hub_findings"],
6879
- findingPatterns: ["no default encryption", "not encrypted", "S3.4"]
6880
- },
6881
- {
6882
- id: "8.1.4.5b",
6883
- category: "\u6570\u636E\u5B89\u5168",
6884
- name: "EBS \u9ED8\u8BA4\u52A0\u5BC6",
6885
- modules: ["security_hub_findings"],
6886
- findingPatterns: ["EBS default encryption", "EC2.7"]
6887
- },
6888
- {
6889
- id: "8.1.4.5b",
6890
- category: "\u6570\u636E\u5B89\u5168",
6891
- name: "RDS \u5B58\u50A8\u52A0\u5BC6",
6892
- modules: ["security_hub_findings"],
6893
- findingPatterns: ["storage is not encrypted", "RDS.3"]
6894
- },
6895
- // 六、网络安全
6896
- {
6897
- id: "8.1.3.1a",
6898
- category: "\u7F51\u7EDC\u5B89\u5168",
6899
- name: "\u7F51\u7EDC\u67B6\u6784",
6900
- modules: ["security_hub_findings"],
6901
- findingPatterns: ["default VPC", "EC2.2"]
6902
- },
6903
- {
6904
- id: "8.1.3.2a",
6905
- category: "\u7F51\u7EDC\u5B89\u5168",
6906
- name: "\u8FB9\u754C\u9632\u62A4",
6907
- modules: ["network_reachability", "security_hub_findings"],
6908
- findingPatterns: ["allows all ports", "allows SSH", "allows RDP", "security group", "EC2.18", "EC2.19"]
6909
- }
6910
- ];
6911
- var CATEGORY_ORDER = [
6912
- "\u8EAB\u4EFD\u9274\u522B",
6913
- "\u8BBF\u95EE\u63A7\u5236",
6914
- "\u5B89\u5168\u5BA1\u8BA1",
6915
- "\u5165\u4FB5\u9632\u8303",
6916
- "\u6570\u636E\u5B89\u5168",
6917
- "\u7F51\u7EDC\u5B89\u5168"
6918
- ];
6919
- var CATEGORY_SECTION = {
6920
- "\u8EAB\u4EFD\u9274\u522B": "\u4E00\u3001\u8EAB\u4EFD\u9274\u522B",
6921
- "\u8BBF\u95EE\u63A7\u5236": "\u4E8C\u3001\u8BBF\u95EE\u63A7\u5236",
6922
- "\u5B89\u5168\u5BA1\u8BA1": "\u4E09\u3001\u5B89\u5168\u5BA1\u8BA1",
6923
- "\u5165\u4FB5\u9632\u8303": "\u56DB\u3001\u5165\u4FB5\u9632\u8303",
6924
- "\u6570\u636E\u5B89\u5168": "\u4E94\u3001\u6570\u636E\u5B89\u5168",
6925
- "\u7F51\u7EDC\u5B89\u5168": "\u516D\u3001\u7F51\u7EDC\u5B89\u5168"
6926
- };
6927
- function evaluateCheck(check, allFindings, scanModules) {
6928
- const allModulesPresent = check.modules.every(
6929
- (mod) => scanModules.some((m) => m.module === mod && m.status === "success")
6930
- );
6931
- if (!allModulesPresent) {
6932
- return { check, status: "unknown", relatedFindings: [] };
6933
- }
6934
- const relatedFindings = allFindings.filter((f) => {
6935
- const moduleMatch = check.modules.some((mod) => f.module === mod);
6936
- if (!moduleMatch) return false;
6937
- const text = `${f.title} ${f.description}`.toLowerCase();
6938
- return check.findingPatterns.some(
6939
- (pattern) => text.includes(pattern.toLowerCase())
6940
- );
6941
- });
6942
- return {
6943
- check,
6944
- status: relatedFindings.length === 0 ? "clean" : "issues",
6945
- relatedFindings
6946
- };
6947
- }
6948
- function generateMlps3Report(scanResults) {
7292
+ function generateMlps3Report(scanResults, lang) {
7293
+ const t = getI18n(lang ?? "zh");
7294
+ const isEn = (lang ?? "zh") === "en";
6949
7295
  const { accountId, region, scanStart } = scanResults;
6950
7296
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
6951
- const allFindings = scanResults.modules.flatMap(
6952
- (m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
6953
- );
6954
- const scanModules = scanResults.modules.map((m) => ({
6955
- module: m.module,
6956
- status: m.status
6957
- }));
6958
- const results = MLPS_CHECKS.map(
6959
- (check) => evaluateCheck(check, allFindings, scanModules)
6960
- );
6961
- const cleanCount = results.filter((r) => r.status === "clean").length;
6962
- const issuesCount = results.filter((r) => r.status === "issues").length;
6963
- const unknownCount = results.filter((r) => r.status === "unknown").length;
6964
- const checkedTotal = cleanCount + issuesCount;
6965
- const total = results.length;
7297
+ const results = evaluateAllFullChecks(scanResults);
7298
+ const autoResults = results.filter((r) => r.mapping.type === "auto");
7299
+ const autoClean = autoResults.filter((r) => r.status === "clean").length;
7300
+ const autoIssues = autoResults.filter((r) => r.status === "issues").length;
7301
+ const autoUnknown = autoResults.filter((r) => r.status === "unknown").length;
7302
+ const checkedTotal = autoClean + autoIssues;
7303
+ const cloudCount = results.filter((r) => r.status === "cloud_provider").length;
7304
+ const manualCount = results.filter((r) => r.status === "manual").length;
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;
6966
7308
  const lines = [];
6967
- lines.push("# \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A");
6968
- 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**");
7309
+ lines.push(`# ${t.mlpsTitle}`);
7310
+ lines.push(`> **${t.mlpsDisclaimer}**`);
6969
7311
  lines.push("");
6970
- lines.push("## \u8D26\u6237\u4FE1\u606F");
6971
- 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}`);
6972
7314
  lines.push("");
6973
- lines.push("## \u9884\u68C0\u603B\u89C8");
6974
- lines.push(`- \u5DF2\u68C0\u67E5 ${checkedTotal} \u9879 / \u5171 ${total} \u9879`);
6975
- lines.push(` - \u672A\u53D1\u73B0\u95EE\u9898: ${cleanCount} \u9879`);
6976
- lines.push(` - \u53D1\u73B0\u95EE\u9898: ${issuesCount} \u9879`);
6977
- if (unknownCount > 0) {
6978
- lines.push(` - \u672A\u68C0\u67E5: ${unknownCount} \u9879`);
7315
+ lines.push(`## ${t.preCheckOverview}`);
7316
+ lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
7317
+ if (autoUnknown > 0) {
7318
+ lines.push(`- ${t.uncheckedCount(autoUnknown)}`);
7319
+ }
7320
+ lines.push(`- ${t.cloudProviderCount(cloudCount)}`);
7321
+ lines.push(`- ${t.manualReviewCount(manualCount)}`);
7322
+ if (naCount > 0) {
7323
+ lines.push(`- ${t.naCount(naCount)}`);
6979
7324
  }
6980
7325
  lines.push("");
6981
- for (const category of CATEGORY_ORDER) {
6982
- const sectionTitle = CATEGORY_SECTION[category];
6983
- const categoryResults = results.filter((r) => r.check.category === category);
6984
- if (categoryResults.length === 0) continue;
7326
+ for (const category of MLPS3_CATEGORY_ORDER) {
7327
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
7328
+ const catResults = results.filter(
7329
+ (r) => r.item.categoryCn === category && r.status !== "not_applicable"
7330
+ );
7331
+ if (catResults.length === 0) continue;
6985
7332
  lines.push(`## ${sectionTitle}`);
6986
7333
  lines.push("");
6987
- const byId = /* @__PURE__ */ new Map();
6988
- for (const r of categoryResults) {
6989
- const existing = byId.get(r.check.id) ?? [];
6990
- existing.push(r);
6991
- byId.set(r.check.id, existing);
7334
+ const controlMap = /* @__PURE__ */ new Map();
7335
+ for (const r of catResults) {
7336
+ const key = r.item.controlCn;
7337
+ if (!controlMap.has(key)) controlMap.set(key, []);
7338
+ controlMap.get(key).push(r);
6992
7339
  }
6993
- for (const [checkId, checkResults] of byId) {
6994
- lines.push(`### ${checkId} ${checkResults[0].check.name}`);
6995
- for (const r of checkResults) {
6996
- const icon = r.status === "clean" ? "\u2705" : r.status === "issues" ? "\u274C" : "\u26A0\uFE0F";
6997
- const label = r.status === "unknown" ? " \u672A\u68C0\u67E5" : r.status === "clean" ? " \u672A\u53D1\u73B0\u95EE\u9898" : r.status === "issues" ? " \u53D1\u73B0\u95EE\u9898" : "";
6998
- lines.push(`- [${icon}] ${r.check.name}${label}`);
7340
+ for (const [_controlKey, controlResults] of controlMap) {
7341
+ const controlName = itemControl(controlResults[0]);
7342
+ lines.push(`### ${controlName}`);
7343
+ for (const r of controlResults) {
7344
+ const icon = r.status === "clean" ? "\u2705" : r.status === "issues" ? "\u274C" : r.status === "unknown" ? "\u26A0\uFE0F" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
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}`);
6999
7348
  if (r.status === "issues" && r.relatedFindings.length > 0) {
7000
7349
  for (const f of r.relatedFindings.slice(0, 3)) {
7001
7350
  lines.push(` - ${f.severity}: ${f.title}`);
7002
7351
  }
7003
7352
  if (r.relatedFindings.length > 3) {
7004
- lines.push(` - ... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 3} \u9879`);
7353
+ lines.push(` - ${t.andMore(r.relatedFindings.length - 3)}`);
7005
7354
  }
7006
7355
  }
7007
7356
  }
@@ -7010,7 +7359,7 @@ function generateMlps3Report(scanResults) {
7010
7359
  }
7011
7360
  const failedResults = results.filter((r) => r.status === "issues");
7012
7361
  if (failedResults.length > 0) {
7013
- lines.push("## \u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09");
7362
+ lines.push(`## ${t.remediationByPriority}`);
7014
7363
  lines.push("");
7015
7364
  const allFailedFindings = /* @__PURE__ */ new Map();
7016
7365
  for (const r of failedResults) {
@@ -7032,6 +7381,10 @@ function generateMlps3Report(scanResults) {
7032
7381
  }
7033
7382
  lines.push("");
7034
7383
  }
7384
+ if (naCount > 0) {
7385
+ lines.push(`> ${t.naNote(naCount)}`);
7386
+ lines.push("");
7387
+ }
7035
7388
  return lines.join("\n");
7036
7389
  }
7037
7390
 
@@ -7039,6 +7392,15 @@ function generateMlps3Report(scanResults) {
7039
7392
  function esc(s) {
7040
7393
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
7041
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
+ }
7042
7404
  function calcScore(summary) {
7043
7405
  const raw = 100 - summary.critical * 15 - summary.high * 5 - summary.medium * 2 - summary.low * 0.5;
7044
7406
  return Math.max(0, Math.min(100, Math.round(raw)));
@@ -7062,50 +7424,6 @@ function scoreColor(score) {
7062
7424
  if (score >= 50) return "#eab308";
7063
7425
  return "#ef4444";
7064
7426
  }
7065
- var SERVICE_RECOMMENDATIONS = {
7066
- security_hub_findings: {
7067
- icon: "\u{1F534}",
7068
- service: "Security Hub",
7069
- impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
7070
- action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
7071
- },
7072
- guardduty_findings: {
7073
- icon: "\u{1F534}",
7074
- service: "GuardDuty",
7075
- 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",
7076
- action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
7077
- },
7078
- inspector_findings: {
7079
- icon: "\u{1F7E1}",
7080
- service: "Inspector",
7081
- impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
7082
- action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
7083
- },
7084
- trusted_advisor_findings: {
7085
- icon: "\u{1F7E1}",
7086
- service: "Trusted Advisor",
7087
- impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
7088
- action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
7089
- },
7090
- config_rules_findings: {
7091
- icon: "\u{1F7E1}",
7092
- service: "AWS Config",
7093
- impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
7094
- action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
7095
- },
7096
- access_analyzer_findings: {
7097
- icon: "\u{1F7E1}",
7098
- service: "IAM Access Analyzer",
7099
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
7100
- action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
7101
- },
7102
- patch_compliance_findings: {
7103
- icon: "\u{1F7E1}",
7104
- service: "SSM Patch Manager",
7105
- impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
7106
- action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
7107
- }
7108
- };
7109
7427
  var SERVICE_NOT_ENABLED_PATTERNS = [
7110
7428
  "not enabled",
7111
7429
  "not found",
@@ -7115,10 +7433,11 @@ var SERVICE_NOT_ENABLED_PATTERNS = [
7115
7433
  "not available",
7116
7434
  "is not enabled"
7117
7435
  ];
7118
- function getDisabledServices(modules) {
7436
+ function getDisabledServices(modules, lang) {
7437
+ const t = getI18n(lang ?? "zh");
7119
7438
  const disabled = [];
7120
7439
  for (const mod of modules) {
7121
- const rec = SERVICE_RECOMMENDATIONS[mod.module];
7440
+ const rec = t.serviceRecommendations[mod.module];
7122
7441
  if (!rec) continue;
7123
7442
  if (!mod.warnings?.length) continue;
7124
7443
  const hasNotEnabled = mod.warnings.some(
@@ -7130,21 +7449,22 @@ function getDisabledServices(modules) {
7130
7449
  }
7131
7450
  return disabled;
7132
7451
  }
7133
- function buildServiceReminderHtml(modules) {
7134
- const disabled = getDisabledServices(modules);
7452
+ function buildServiceReminderHtml(modules, lang) {
7453
+ const t = getI18n(lang ?? "zh");
7454
+ const disabled = getDisabledServices(modules, lang);
7135
7455
  if (disabled.length === 0) return "";
7136
7456
  const items = disabled.map((svc) => `
7137
7457
  <div style="margin-bottom:12px">
7138
- <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} \u672A\u542F\u7528</div>
7139
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5F71\u54CD\uFF1A${esc(svc.impact)}</div>
7140
- <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>
7141
7461
  </div>`).join("\n");
7142
7462
  return `
7143
7463
  <section>
7144
7464
  <div style="background:#2d1f00;border:1px solid #b45309;border-radius:8px;padding:20px;margin-bottom:32px">
7145
- <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>
7146
7466
  ${items}
7147
- <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>
7148
7468
  </div>
7149
7469
  </section>`;
7150
7470
  }
@@ -7346,12 +7666,12 @@ function donutChart(summary) {
7346
7666
  "</svg>"
7347
7667
  ].join("\n");
7348
7668
  }
7349
- function barChart(modules) {
7669
+ function barChart(modules, allCleanLabel = "All modules clean") {
7350
7670
  const withFindings = modules.filter((m) => m.findingsCount > 0).sort((a, b) => b.findingsCount - a.findingsCount).slice(0, 12);
7351
7671
  if (withFindings.length === 0) {
7352
7672
  return [
7353
7673
  '<svg viewBox="0 0 400 50" width="100%">',
7354
- ' <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>`,
7355
7675
  "</svg>"
7356
7676
  ].join("\n");
7357
7677
  }
@@ -7466,7 +7786,9 @@ function scoreTrendChart(history) {
7466
7786
  "</svg>"
7467
7787
  ].join("\n");
7468
7788
  }
7469
- 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";
7470
7792
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
7471
7793
  const date = scanStart.split("T")[0];
7472
7794
  const duration = formatDuration2(scanStart, scanEnd);
@@ -7484,23 +7806,23 @@ function generateHtmlReport(scanResults, history) {
7484
7806
  <div class="top5-content">
7485
7807
  <span class="badge badge-${esc(f.severity.toLowerCase())}">${esc(f.severity)}</span>
7486
7808
  <div class="top5-title">${esc(f.title)}</div>
7487
- <div class="top5-detail"><strong>Resource:</strong> ${esc(f.resourceId)}</div>
7488
- <div class="top5-detail"><strong>Impact:</strong> ${esc(f.impact)}</div>
7489
- <div class="top5-detail"><strong>Risk Score:</strong> ${f.riskScore}/10</div>
7490
- <h4>Remediation</h4>
7491
- <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>
7492
7814
  </div>
7493
7815
  </div>`
7494
7816
  ).join("\n");
7495
7817
  top5Html = `
7496
7818
  <section>
7497
- <h2>Top ${top5.length} Highest Risk Findings</h2>
7819
+ <h2>${esc(t.topHighestRiskFindings(top5.length))}</h2>
7498
7820
  ${cards}
7499
7821
  </section>`;
7500
7822
  }
7501
7823
  let findingsHtml;
7502
7824
  if (summary.totalFindings === 0) {
7503
- findingsHtml = '<div class="no-findings">No security issues found.</div>';
7825
+ findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
7504
7826
  } else {
7505
7827
  const FOLD_THRESHOLD = 20;
7506
7828
  const renderCard = (f) => {
@@ -7509,10 +7831,10 @@ function generateHtmlReport(scanResults, history) {
7509
7831
  <span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
7510
7832
  <span class="finding-title-text">${esc(f.title)}</span>
7511
7833
  <span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
7512
- <details><summary>Details</summary><div class="finding-card-body">
7834
+ <details><summary>${t.details}</summary><div class="finding-card-body">
7513
7835
  <p>${esc(f.description)}</p>
7514
- <p><strong>Remediation:</strong></p>
7515
- <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>
7516
7838
  </div></details>
7517
7839
  </div>`;
7518
7840
  };
@@ -7523,7 +7845,7 @@ function generateHtmlReport(scanResults, history) {
7523
7845
  const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
7524
7846
  const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
7525
7847
  return `${first}
7526
- <details><summary>Show remaining ${findings.length - FOLD_THRESHOLD} findings...</summary>
7848
+ <details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
7527
7849
  ${rest}
7528
7850
  </details>`;
7529
7851
  };
@@ -7575,13 +7897,13 @@ ${rest}
7575
7897
  if (history && history.length >= 2) {
7576
7898
  trendHtml = `
7577
7899
  <section class="trend-section">
7578
- <h2>30-Day Trends</h2>
7900
+ <h2>${esc(t.trendTitle)}</h2>
7579
7901
  <div class="trend-chart">
7580
- <div class="trend-title">Findings by Severity</div>
7902
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7581
7903
  ${findingsTrendChart(history)}
7582
7904
  </div>
7583
7905
  <div class="trend-chart">
7584
- <div class="trend-title">Security Score</div>
7906
+ <div class="trend-title">${esc(t.securityScore)}</div>
7585
7907
  ${scoreTrendChart(history)}
7586
7908
  </div>
7587
7909
  </section>`;
@@ -7592,16 +7914,57 @@ ${rest}
7592
7914
  let recsHtml = "";
7593
7915
  if (summary.totalFindings > 0) {
7594
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."];
7595
7921
  for (const f of allFindings) {
7596
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
+ }
7597
7948
  const existing = recMap.get(rem);
7598
7949
  if (existing) {
7599
7950
  existing.count++;
7951
+ if (!existing.url && url) existing.url = url;
7600
7952
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7601
7953
  existing.severity = f.severity;
7602
7954
  }
7603
7955
  } else {
7604
- 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;
7605
7968
  }
7606
7969
  }
7607
7970
  const uniqueRecs = [...recMap.values()].sort((a, b) => {
@@ -7612,60 +7975,61 @@ ${rest}
7612
7975
  const renderRec = (r) => {
7613
7976
  const sev = r.severity.toLowerCase();
7614
7977
  const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
7615
- 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>`;
7616
7980
  };
7617
7981
  const TOP_N = 10;
7618
7982
  const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
7619
7983
  const remaining = uniqueRecs.slice(TOP_N);
7620
7984
  const moreHtml = remaining.length > 0 ? `
7621
- <details><summary>Show ${remaining.length} more&hellip;</summary>
7985
+ <details><summary>${t.showMoreCount(remaining.length)}</summary>
7622
7986
  ${remaining.map(renderRec).join("\n")}
7623
7987
  </details>` : "";
7624
7988
  recsHtml = `
7625
7989
  <details class="rec-fold">
7626
- <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>
7627
7991
  <div class="rec-body">
7628
7992
  <ol>${topItems}${moreHtml}</ol>
7629
7993
  </div>
7630
7994
  </details>`;
7631
7995
  }
7632
7996
  return `<!DOCTYPE html>
7633
- <html lang="en">
7997
+ <html lang="${htmlLang}">
7634
7998
  <head>
7635
7999
  <meta charset="UTF-8">
7636
8000
  <meta name="viewport" content="width=device-width,initial-scale=1">
7637
- <title>AWS Security Scan Report &mdash; ${esc(date)}</title>
8001
+ <title>${esc(t.securityReportTitle)} &mdash; ${esc(date)}</title>
7638
8002
  <style>${sharedCss()}</style>
7639
8003
  </head>
7640
8004
  <body>
7641
8005
  <div class="container">
7642
8006
 
7643
8007
  <header>
7644
- <h1>&#128737;&#65039; AWS Security Scan Report</h1>
7645
- <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>
7646
8010
  </header>
7647
8011
 
7648
8012
  <section class="summary">
7649
8013
  <div class="score-card">
7650
8014
  <div class="score-value" style="color:${scoreColor(score)}">${score}</div>
7651
- <div class="score-label">Security Score</div>
8015
+ <div class="score-label">${esc(t.securityScore)}</div>
7652
8016
  </div>
7653
8017
  <div class="severity-stats">
7654
- <div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">Critical</div></div>
7655
- <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">High</div></div>
7656
- <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">Medium</div></div>
7657
- <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>
7658
8022
  </div>
7659
8023
  </section>
7660
8024
 
7661
8025
  <section class="charts">
7662
8026
  <div class="chart-box">
7663
- <div class="chart-title">Severity Distribution</div>
8027
+ <div class="chart-title">${esc(t.severityDistribution)}</div>
7664
8028
  <div style="text-align:center">${donutChart(summary)}</div>
7665
8029
  </div>
7666
8030
  <div class="chart-box">
7667
- <div class="chart-title">Findings by Module</div>
7668
- ${barChart(modules)}
8031
+ <div class="chart-title">${esc(t.findingsByModule)}</div>
8032
+ ${barChart(modules, t.allModulesClean)}
7669
8033
  </div>
7670
8034
  </section>
7671
8035
 
@@ -7673,33 +8037,35 @@ ${trendHtml}
7673
8037
 
7674
8038
  ${top5Html}
7675
8039
 
7676
- ${buildServiceReminderHtml(modules)}
8040
+ ${buildServiceReminderHtml(modules, lang)}
7677
8041
 
7678
8042
  <section>
7679
- <h2>Scan Statistics</h2>
8043
+ <h2>${esc(t.scanStatistics)}</h2>
7680
8044
  <table>
7681
- <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>
7682
8046
  <tbody>${statsRows}</tbody>
7683
8047
  </table>
7684
8048
  </section>
7685
8049
 
7686
8050
  <section>
7687
- <h2>All Findings</h2>
8051
+ <h2>${esc(t.allFindings)}</h2>
7688
8052
  ${findingsHtml}
7689
8053
  </section>
7690
8054
 
7691
8055
  ${recsHtml}
7692
8056
 
7693
8057
  <footer>
7694
- <p>Generated by AWS Security MCP Server v${VERSION}</p>
7695
- <p>This report is for informational purposes only.</p>
8058
+ <p>${esc(t.generatedBy)} v${VERSION}</p>
8059
+ <p>${esc(t.informationalOnly)}</p>
7696
8060
  </footer>
7697
8061
 
7698
8062
  </div>
7699
8063
  </body>
7700
8064
  </html>`;
7701
8065
  }
7702
- 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";
7703
8069
  const { accountId, region, scanStart } = scanResults;
7704
8070
  const date = scanStart.split("T")[0];
7705
8071
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
@@ -7716,17 +8082,21 @@ function generateMlps3HtmlReport(scanResults, history) {
7716
8082
  if (history && history.length >= 2) {
7717
8083
  trendHtml = `
7718
8084
  <section class="trend-section">
7719
- <h2>30\u65E5\u8D8B\u52BF</h2>
8085
+ <h2>${esc(t.trendTitle)}</h2>
7720
8086
  <div class="trend-chart">
7721
- <div class="trend-title">\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0</div>
8087
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7722
8088
  ${findingsTrendChart(history)}
7723
8089
  </div>
7724
8090
  <div class="trend-chart">
7725
- <div class="trend-title">\u5B89\u5168\u8BC4\u5206</div>
8091
+ <div class="trend-title">${esc(t.securityScore)}</div>
7726
8092
  ${scoreTrendChart(history)}
7727
8093
  </div>
7728
8094
  </section>`;
7729
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;
7730
8100
  const categoryMap = /* @__PURE__ */ new Map();
7731
8101
  for (const r of results) {
7732
8102
  if (r.status === "not_applicable") continue;
@@ -7735,7 +8105,7 @@ function generateMlps3HtmlReport(scanResults, history) {
7735
8105
  categoryMap.get(cat).push(r);
7736
8106
  }
7737
8107
  const categorySections = MLPS3_CATEGORY_ORDER.map((category) => {
7738
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
8108
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
7739
8109
  const catResults = categoryMap.get(category);
7740
8110
  if (!catResults || catResults.length === 0) return "";
7741
8111
  const allCloud = catResults.every((r) => r.status === "cloud_provider");
@@ -7743,11 +8113,11 @@ function generateMlps3HtmlReport(scanResults, history) {
7743
8113
  return `<details class="category-fold mlps-cloud-section">
7744
8114
  <summary>
7745
8115
  <span class="category-title">${esc(sectionTitle)}</span>
7746
- <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>
7747
8117
  </summary>
7748
8118
  <div class="category-body">
7749
- <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>
7750
- ${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")}
7751
8121
  </div>
7752
8122
  </details>`;
7753
8123
  }
@@ -7769,31 +8139,34 @@ function generateMlps3HtmlReport(scanResults, history) {
7769
8139
  if (!controlMap.has(key)) controlMap.set(key, []);
7770
8140
  controlMap.get(key).push(r);
7771
8141
  }
7772
- const controlGroups = [...controlMap.entries()].map(([controlName, controlResults]) => {
8142
+ const controlGroups = [...controlMap.entries()].map(([_controlKey, controlResults]) => {
8143
+ const controlName = itemControl(controlResults[0]);
7773
8144
  const cloudItems = controlResults.filter((r) => r.status === "cloud_provider");
7774
8145
  const nonCloudItems = controlResults.filter((r) => r.status !== "cloud_provider");
7775
8146
  let itemsHtml = "";
7776
8147
  for (const r of nonCloudItems) {
7777
8148
  const icon = r.status === "clean" ? "\u{1F7E2}" : r.status === "issues" ? "\u{1F534}" : r.status === "unknown" ? "\u2B1C" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
7778
8149
  const cls = `check-${r.status === "cloud_provider" ? "cloud" : r.status}`;
7779
- 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)}` : "";
7780
8151
  let findingsDetail = "";
7781
8152
  if (r.status === "clean") {
7782
- 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>`;
7783
8154
  } else if (r.status === "issues" && r.relatedFindings.length > 0) {
7784
8155
  const fItems = r.relatedFindings.slice(0, 5).map((f) => `<li>${esc(f.severity)}: ${esc(f.title)}</li>`);
7785
8156
  if (r.relatedFindings.length > 5) {
7786
- fItems.push(`<li>... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 5} \u9879</li>`);
8157
+ fItems.push(`<li>${esc(t.andMore(r.relatedFindings.length - 5))}</li>`);
7787
8158
  }
7788
- 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>` : "";
7789
- 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>`;
7790
8161
  }
7791
- 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>
7792
8164
  ${findingsDetail}`;
7793
8165
  }
7794
8166
  if (cloudItems.length > 0) {
7795
8167
  for (const r of cloudItems) {
7796
- 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>
7797
8170
  `;
7798
8171
  }
7799
8172
  }
@@ -7826,20 +8199,61 @@ ${itemsHtml}
7826
8199
  let remediationHtml = "";
7827
8200
  if (failedResults.length > 0) {
7828
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."];
7829
8206
  for (const r of failedResults) {
7830
8207
  for (const f of r.relatedFindings) {
7831
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
+ }
7832
8234
  const existing = mlpsRecMap.get(rem);
7833
8235
  if (existing) {
7834
8236
  existing.count++;
8237
+ if (!existing.url && url) existing.url = url;
7835
8238
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7836
8239
  existing.severity = f.severity;
7837
8240
  }
7838
8241
  } else {
7839
- mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
8242
+ mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
7840
8243
  }
7841
8244
  }
7842
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
+ }
7843
8257
  const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
7844
8258
  const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
7845
8259
  if (sevDiff !== 0) return sevDiff;
@@ -7849,26 +8263,27 @@ ${itemsHtml}
7849
8263
  const renderMlpsRec = (r) => {
7850
8264
  const sev = r.severity.toLowerCase();
7851
8265
  const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
7852
- 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>`;
7853
8268
  };
7854
8269
  const MLPS_TOP_N = 10;
7855
8270
  const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
7856
8271
  const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
7857
8272
  const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
7858
- <details><summary>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879&hellip;</summary>
8273
+ <details><summary>${esc(t.showRemaining(mlpsRemaining.length))}&hellip;</summary>
7859
8274
  ${mlpsRemaining.map(renderMlpsRec).join("\n")}
7860
8275
  </details>` : "";
7861
8276
  remediationHtml = `
7862
8277
  <details class="rec-fold" open>
7863
- <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>
7864
8279
  <div class="rec-body">
7865
8280
  <ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
7866
8281
  </div>
7867
8282
  </details>`;
7868
8283
  }
7869
8284
  }
7870
- 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>` : "";
7871
- 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>` : "";
7872
8287
  const mlpsCss = `
7873
8288
  .mlps-cloud-section>summary{color:#94a3b8}
7874
8289
  .mlps-cloud-note{color:#94a3b8;font-size:13px;margin-bottom:12px;font-style:italic}
@@ -7890,40 +8305,40 @@ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
7890
8305
  .mlps-summary-card .stat-label{font-size:12px;color:#94a3b8;margin-top:2px}
7891
8306
  `;
7892
8307
  return `<!DOCTYPE html>
7893
- <html lang="zh-CN">
8308
+ <html lang="${htmlLang}">
7894
8309
  <head>
7895
8310
  <meta charset="UTF-8">
7896
8311
  <meta name="viewport" content="width=device-width,initial-scale=1">
7897
- <title>\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A &mdash; ${esc(date)}</title>
8312
+ <title>${esc(t.mlpsTitle)} &mdash; ${esc(date)}</title>
7898
8313
  <style>${sharedCss()}${mlpsCss}</style>
7899
8314
  </head>
7900
8315
  <body>
7901
8316
  <div class="container">
7902
8317
 
7903
8318
  <header>
7904
- <h1>&#128737;&#65039; \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A</h1>
7905
- <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>
7906
- <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>
7907
8322
  </header>
7908
8323
 
7909
8324
  <section class="summary" style="display:block;text-align:center">
7910
8325
  <div style="font-size:36px;font-weight:700;margin-bottom:12px">
7911
- <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>
7912
8327
  <span style="color:#475569;margin:0 16px">/</span>
7913
- <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>
7914
8329
  </div>
7915
8330
  <div class="mlps-summary-cards" style="justify-content:center">
7916
- <div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">\u5DF2\u68C0\u67E5\u9879</div></div>
7917
- <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>
7918
- <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>
7919
- ${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>` : ""}
7920
8335
  </div>
7921
8336
  </section>
7922
8337
  ${unknownNote}
7923
8338
 
7924
8339
  ${trendHtml}
7925
8340
 
7926
- ${buildServiceReminderHtml(scanResults.modules)}
8341
+ ${buildServiceReminderHtml(scanResults.modules, lang)}
7927
8342
 
7928
8343
  ${categorySections}
7929
8344
 
@@ -7932,8 +8347,8 @@ ${remediationHtml}
7932
8347
  ${naNote}
7933
8348
 
7934
8349
  <footer>
7935
- <p>\u7531 AWS Security MCP Server v${VERSION} \u751F\u6210</p>
7936
- <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>
7937
8352
  </footer>
7938
8353
 
7939
8354
  </div>
@@ -8148,16 +8563,14 @@ Aggregates active findings from AWS Security Hub. Replaces individual config sca
8148
8563
  - INFORMATIONAL findings are skipped.
8149
8564
 
8150
8565
  ## 3. GuardDuty Findings (guardduty_findings)
8151
- Aggregates threat detection findings from Amazon GuardDuty.
8152
- - Covers account compromise, instance compromise, and reconnaissance.
8153
- - Severity mapped from GuardDuty 0\u201310 scale: \u22657 \u2192 HIGH, \u22654 \u2192 MEDIUM, <4 \u2192 LOW.
8154
- - 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.
8155
8569
 
8156
8570
  ## 4. Inspector Findings (inspector_findings)
8157
- Aggregates vulnerability findings from Amazon Inspector v2.
8158
- - Covers CVEs in EC2 instances, Lambda functions, and container images.
8159
- - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
8160
- - 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.
8161
8574
 
8162
8575
  ## 5. Trusted Advisor Findings (trusted_advisor_findings)
8163
8576
  Aggregates security checks from AWS Trusted Advisor.
@@ -8193,19 +8606,15 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
8193
8606
  Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
8194
8607
 
8195
8608
  ## 15. Config Rules Findings (config_rules_findings)
8196
- Pulls non-compliant AWS Config Rule evaluation results.
8197
- - Lists all Config Rules and their compliance status.
8198
- - For NON_COMPLIANT rules, retrieves specific non-compliant resources.
8199
- - Security-related rules (encryption, IAM, public access, etc.) mapped to HIGH severity (7.5).
8200
- - 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.
8201
8612
  - Gracefully handles regions where AWS Config is not enabled.
8202
8613
 
8203
8614
  ## 16. IAM Access Analyzer Findings (access_analyzer_findings)
8204
- Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account.
8205
- - Lists active analyzers (ACCOUNT or ORGANIZATION type).
8206
- - Retrieves ACTIVE findings showing external access to resources.
8207
- - Covers S3 buckets, IAM roles, SQS queues, Lambda functions, KMS keys, and more.
8208
- - 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.
8209
8618
  - Returns warning if no analyzer is configured.
8210
8619
 
8211
8620
  ## 17. SSM Patch Compliance (patch_compliance_findings)
@@ -8290,112 +8699,18 @@ var MODULE_DESCRIPTIONS = {
8290
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.",
8291
8700
  disaster_recovery: "Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.",
8292
8701
  security_hub_findings: "Aggregates active findings from AWS Security Hub \u2014 replaces individual config scanners with centralized compliance checks.",
8293
- guardduty_findings: "Aggregates threat detection findings from Amazon GuardDuty \u2014 account compromise, instance compromise, and reconnaissance.",
8294
- 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.",
8295
8704
  trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
8296
- config_rules_findings: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
8297
- 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.",
8298
8707
  patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches.",
8299
8708
  imdsv2_enforcement: "Checks if EC2 instances enforce IMDSv2 (HttpTokens: required) \u2014 IMDSv1 allows credential theft via SSRF.",
8300
8709
  waf_coverage: "Checks if internet-facing ALBs have WAF Web ACL associated for protection against common web exploits."
8301
8710
  };
8302
- var HW_DEFENSE_CHECKLIST = `
8303
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8304
- \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
8305
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8306
-
8307
- \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
8308
-
8309
- \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
8310
- \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
8311
- \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
8312
- \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
8313
- \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
8314
-
8315
- \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
8316
- \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
8317
- \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
8318
- \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
8319
-
8320
- \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
8321
- \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
8322
- \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
8323
- \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
8324
- \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
8325
- \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
8326
-
8327
- \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
8328
- \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
8329
- \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
8330
- \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
8331
-
8332
- \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
8333
- \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
8334
- \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
8335
- \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
8336
-
8337
- \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
8338
- \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
8339
- \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
8340
- \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A"\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0"
8341
-
8342
- \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
8343
- \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
8344
- \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
8345
- \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
8346
- \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
8347
-
8348
- \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
8349
- \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
8350
- \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
8351
- \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
8352
-
8353
- \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
8354
- `;
8355
- var SERVICE_RECOMMENDATIONS2 = {
8356
- security_hub_findings: {
8357
- icon: "\u{1F534}",
8358
- service: "Security Hub",
8359
- impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
8360
- action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
8361
- },
8362
- guardduty_findings: {
8363
- icon: "\u{1F534}",
8364
- service: "GuardDuty",
8365
- 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",
8366
- action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
8367
- },
8368
- inspector_findings: {
8369
- icon: "\u{1F7E1}",
8370
- service: "Inspector",
8371
- impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
8372
- action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
8373
- },
8374
- trusted_advisor_findings: {
8375
- icon: "\u{1F7E1}",
8376
- service: "Trusted Advisor",
8377
- impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
8378
- action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
8379
- },
8380
- config_rules_findings: {
8381
- icon: "\u{1F7E1}",
8382
- service: "AWS Config",
8383
- impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
8384
- action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
8385
- },
8386
- access_analyzer_findings: {
8387
- icon: "\u{1F7E1}",
8388
- service: "IAM Access Analyzer",
8389
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
8390
- action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
8391
- },
8392
- patch_compliance_findings: {
8393
- icon: "\u{1F7E1}",
8394
- service: "SSM Patch Manager",
8395
- impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
8396
- action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
8397
- }
8398
- };
8711
+ function getHwDefenseChecklist(lang) {
8712
+ return getI18n(lang ?? "zh").hwChecklist;
8713
+ }
8399
8714
  var SERVICE_NOT_ENABLED_PATTERNS2 = [
8400
8715
  "not enabled",
8401
8716
  "not found",
@@ -8405,10 +8720,11 @@ var SERVICE_NOT_ENABLED_PATTERNS2 = [
8405
8720
  "not available",
8406
8721
  "is not enabled"
8407
8722
  ];
8408
- function buildServiceReminder(modules) {
8723
+ function buildServiceReminder(modules, lang) {
8724
+ const t = getI18n(lang ?? "zh");
8409
8725
  const disabledServices = [];
8410
8726
  for (const mod of modules) {
8411
- const rec = SERVICE_RECOMMENDATIONS2[mod.module];
8727
+ const rec = t.serviceRecommendations[mod.module];
8412
8728
  if (!rec) continue;
8413
8729
  if (!mod.warnings?.length) continue;
8414
8730
  const hasNotEnabled = mod.warnings.some(
@@ -8421,26 +8737,26 @@ function buildServiceReminder(modules) {
8421
8737
  if (disabledServices.length === 0) return "";
8422
8738
  const lines = [
8423
8739
  "",
8424
- "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
8740
+ t.serviceReminderTitle,
8425
8741
  ""
8426
8742
  ];
8427
8743
  for (const svc of disabledServices) {
8428
- lines.push(`${svc.icon} ${svc.service} \u672A\u542F\u7528`);
8429
- lines.push(` \u5F71\u54CD\uFF1A${svc.impact}`);
8430
- 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}`);
8431
8747
  lines.push("");
8432
8748
  }
8433
- 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);
8434
8750
  return lines.join("\n");
8435
8751
  }
8436
- function summarizeResult(result) {
8752
+ function summarizeResult(result, lang) {
8437
8753
  const { summary } = result;
8438
8754
  const lines = [
8439
8755
  `Scan complete for account ${result.accountId} in ${result.region}.`,
8440
8756
  `Total findings: ${summary.totalFindings} (${summary.critical} Critical, ${summary.high} High, ${summary.medium} Medium, ${summary.low} Low)`,
8441
8757
  `Modules: ${summary.modulesSuccess} succeeded, ${summary.modulesError} errored`
8442
8758
  ];
8443
- const reminder = buildServiceReminder(result.modules);
8759
+ const reminder = buildServiceReminder(result.modules, lang);
8444
8760
  if (reminder) {
8445
8761
  lines.push(reminder);
8446
8762
  }
@@ -8502,9 +8818,10 @@ function createServer(defaultRegion) {
8502
8818
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8503
8819
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8504
8820
  role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
8505
- 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)")
8506
8823
  },
8507
- async ({ region, org_mode, role_name, account_ids }) => {
8824
+ async ({ region, org_mode, role_name, account_ids, lang }) => {
8508
8825
  try {
8509
8826
  const r = region ?? defaultRegion;
8510
8827
  let result;
@@ -8519,7 +8836,7 @@ function createServer(defaultRegion) {
8519
8836
  }
8520
8837
  return {
8521
8838
  content: [
8522
- { type: "text", text: summarizeResult(result) },
8839
+ { type: "text", text: summarizeResult(result, lang ?? "zh") },
8523
8840
  { type: "text", text: JSON.stringify(result, null, 2) }
8524
8841
  ]
8525
8842
  };
@@ -8580,9 +8897,10 @@ function createServer(defaultRegion) {
8580
8897
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8581
8898
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8582
8899
  role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
8583
- 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)")
8584
8902
  },
8585
- async ({ group, region, org_mode, role_name, account_ids }) => {
8903
+ async ({ group, region, org_mode, role_name, account_ids, lang }) => {
8586
8904
  try {
8587
8905
  const groupDef = SCAN_GROUPS[group];
8588
8906
  if (!groupDef) {
@@ -8664,7 +8982,7 @@ function createServer(defaultRegion) {
8664
8982
  `Scan group: ${groupDef.name} (${group})`,
8665
8983
  groupDef.description,
8666
8984
  "",
8667
- summarizeResult(result)
8985
+ summarizeResult(result, lang ?? "zh")
8668
8986
  ];
8669
8987
  if (missingModules.length > 0) {
8670
8988
  lines.push("");
@@ -8677,7 +8995,7 @@ function createServer(defaultRegion) {
8677
8995
  if (group === "hw_defense") {
8678
8996
  const summaryContent = content[0];
8679
8997
  if (summaryContent && summaryContent.type === "text") {
8680
- summaryContent.text += "\n\n" + HW_DEFENSE_CHECKLIST;
8998
+ summaryContent.text += "\n\n" + getHwDefenseChecklist(lang ?? "zh");
8681
8999
  }
8682
9000
  }
8683
9001
  return { content };
@@ -8707,11 +9025,14 @@ function createServer(defaultRegion) {
8707
9025
  server.tool(
8708
9026
  "generate_report",
8709
9027
  "Generate a Markdown security report from scan results. Read-only. Does not modify any AWS resources.",
8710
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
8711
- 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 }) => {
8712
9033
  try {
8713
9034
  const parsed = JSON.parse(scan_results);
8714
- const report = generateMarkdownReport(parsed);
9035
+ const report = generateMarkdownReport(parsed, lang ?? "zh");
8715
9036
  return { content: [{ type: "text", text: report }] };
8716
9037
  } catch (err) {
8717
9038
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8721,11 +9042,14 @@ function createServer(defaultRegion) {
8721
9042
  server.tool(
8722
9043
  "generate_mlps3_report",
8723
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.",
8724
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all") },
8725
- 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 }) => {
8726
9050
  try {
8727
9051
  const parsed = JSON.parse(scan_results);
8728
- const report = generateMlps3Report(parsed);
9052
+ const report = generateMlps3Report(parsed, lang ?? "zh");
8729
9053
  return { content: [{ type: "text", text: report }] };
8730
9054
  } catch (err) {
8731
9055
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8737,13 +9061,14 @@ function createServer(defaultRegion) {
8737
9061
  "Generate a professional HTML security report. Save the output as an .html file.",
8738
9062
  {
8739
9063
  scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
8740
- 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)")
8741
9066
  },
8742
- async ({ scan_results, history }) => {
9067
+ async ({ scan_results, history, lang }) => {
8743
9068
  try {
8744
9069
  const parsed = JSON.parse(scan_results);
8745
9070
  const historyData = history ? JSON.parse(history) : void 0;
8746
- const report = generateHtmlReport(parsed, historyData);
9071
+ const report = generateHtmlReport(parsed, historyData, lang ?? "zh");
8747
9072
  return { content: [{ type: "text", text: report }] };
8748
9073
  } catch (err) {
8749
9074
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8755,13 +9080,14 @@ function createServer(defaultRegion) {
8755
9080
  "Generate a professional HTML MLPS Level 3 compliance report (\u7B49\u4FDD\u4E09\u7EA7). Save as .html file.",
8756
9081
  {
8757
9082
  scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
8758
- 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)")
8759
9085
  },
8760
- async ({ scan_results, history }) => {
9086
+ async ({ scan_results, history, lang }) => {
8761
9087
  try {
8762
9088
  const parsed = JSON.parse(scan_results);
8763
9089
  const historyData = history ? JSON.parse(history) : void 0;
8764
- const report = generateMlps3HtmlReport(parsed, historyData);
9090
+ const report = generateMlps3HtmlReport(parsed, historyData, lang ?? "zh");
8765
9091
  return { content: [{ type: "text", text: report }] };
8766
9092
  } catch (err) {
8767
9093
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8771,7 +9097,10 @@ function createServer(defaultRegion) {
8771
9097
  server.tool(
8772
9098
  "generate_maturity_report",
8773
9099
  "Generate a security maturity assessment report from scan_all results. Requires service_detection module output. Read-only.",
8774
- { 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
+ },
8775
9104
  async ({ scan_results }) => {
8776
9105
  try {
8777
9106
  const parsed = JSON.parse(scan_results);
@@ -9042,7 +9371,7 @@ ${finding}`
9042
9371
  type: "text",
9043
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
9044
9373
 
9045
- ${HW_DEFENSE_CHECKLIST}
9374
+ ${getHwDefenseChecklist("zh")}
9046
9375
 
9047
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`
9048
9377
  }