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.
@@ -237,7 +237,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
237
237
  import { z } from "zod";
238
238
 
239
239
  // src/version.ts
240
- var VERSION = "0.5.2";
240
+ var VERSION = "0.6.0";
241
241
 
242
242
  // src/utils/aws-client.ts
243
243
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -316,7 +316,9 @@ async function listOrgAccounts(region) {
316
316
  var AGGREGATION_MODULES = /* @__PURE__ */ new Set([
317
317
  "security_hub_findings",
318
318
  "guardduty_findings",
319
- "inspector_findings"
319
+ "inspector_findings",
320
+ "config_rules_findings",
321
+ "access_analyzer_findings"
320
322
  ]);
321
323
  function buildSummary(modules) {
322
324
  let critical = 0;
@@ -708,9 +710,15 @@ var ServiceDetectionScanner = class {
708
710
  const insp = createClient(Inspector2Client, region, ctx.credentials);
709
711
  const resp = await insp.send(new BatchGetAccountStatusCommand({ accountIds: [accountId] }));
710
712
  const accounts = resp.accounts ?? [];
711
- const active = accounts.some(
712
- (a) => a.state?.status === "ENABLED" || a.state?.status === "ENABLING"
713
- );
713
+ const active = accounts.some((a) => {
714
+ const s = a.state?.status;
715
+ if (s === "ENABLED" || s === "ENABLING") return true;
716
+ const rs = a.resourceState;
717
+ if (!rs) return false;
718
+ return ["ec2", "ecr", "lambda", "lambdaCode", "codeRepository"].some(
719
+ (k) => rs[k]?.status === "ENABLED"
720
+ );
721
+ });
714
722
  if (active) {
715
723
  services.push({
716
724
  name: "Inspector",
@@ -2439,7 +2447,6 @@ import {
2439
2447
  DescribeNetworkInterfacesCommand,
2440
2448
  DescribeSecurityGroupsCommand as DescribeSecurityGroupsCommand2
2441
2449
  } from "@aws-sdk/client-ec2";
2442
- var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
2443
2450
  function makeFinding9(opts) {
2444
2451
  const severity = severityFromScore(opts.riskScore);
2445
2452
  return { ...opts, severity, priority: priorityFromSeverity(severity) };
@@ -2964,14 +2971,21 @@ var SecurityHubFindingsScanner = class {
2964
2971
  const resourceType = f.Resources?.[0]?.Type ?? "AWS::Unknown";
2965
2972
  const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:securityhub:${region}:${accountId}:finding/${f.Id ?? "unknown"}`;
2966
2973
  const remediationSteps = [];
2967
- if (f.Remediation?.Recommendation?.Text) {
2968
- remediationSteps.push(f.Remediation.Recommendation.Text);
2974
+ const title = f.Title ?? "Security Hub Finding";
2975
+ if (/^KB\d+$/.test(title)) {
2976
+ remediationSteps.push(`Install Windows patch ${title} via WSUS or SSM Patch Manager`);
2977
+ remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${title}`);
2978
+ } else if (/^CVE-/.test(title)) {
2979
+ remediationSteps.push(`Fix vulnerability ${title}: update affected software to patched version`);
2980
+ } else {
2981
+ remediationSteps.push(title);
2969
2982
  }
2970
2983
  if (f.Remediation?.Recommendation?.Url) {
2971
- remediationSteps.push(`Reference: ${f.Remediation.Recommendation.Url}`);
2984
+ remediationSteps.push(`Documentation: ${f.Remediation.Recommendation.Url}`);
2972
2985
  }
2973
- if (remediationSteps.length === 0) {
2974
- remediationSteps.push("Review the finding in the AWS Security Hub console and follow the recommended remediation.");
2986
+ const recText = f.Remediation?.Recommendation?.Text ?? "";
2987
+ if (recText && !["See References", "None Provided", ""].includes(recText.trim())) {
2988
+ remediationSteps.push(recText);
2975
2989
  }
2976
2990
  findings.push({
2977
2991
  severity,
@@ -3124,10 +3138,11 @@ var GuardDutyFindingsScanner = class {
3124
3138
  impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
3125
3139
  riskScore: score,
3126
3140
  remediationSteps: [
3127
- "Review the finding in the Amazon GuardDuty console.",
3128
- `Finding type: ${gdf.Type ?? "unknown"}`,
3129
- "Follow the recommended remediation in the GuardDuty documentation."
3130
- ],
3141
+ `Investigate ${gdf.Type ?? "unknown threat"}: ${gdf.Title ?? "threat detected"}`,
3142
+ gdf.Description ? `Details: ${gdf.Description.substring(0, 200)}` : "",
3143
+ "Isolate affected resources if compromise is confirmed.",
3144
+ "Review CloudTrail logs for related suspicious activity."
3145
+ ].filter(Boolean),
3131
3146
  priority: priorityFromSeverity(severity),
3132
3147
  module: this.moduleName,
3133
3148
  accountId: gdf.AccountId ?? accountId
@@ -3218,18 +3233,44 @@ var InspectorFindingsScanner = class {
3218
3233
  const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
3219
3234
  const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
3220
3235
  const remediationSteps = [];
3221
- if (f.remediation?.recommendation?.text) {
3236
+ const vulnPkgs = f.packageVulnerabilityDetails?.vulnerablePackages;
3237
+ if (vulnPkgs?.length) {
3238
+ for (const pkg of vulnPkgs.slice(0, 3)) {
3239
+ const name = pkg.name ?? "unknown-package";
3240
+ const installed = pkg.version ?? "unknown";
3241
+ const fixed = pkg.fixedInVersion ?? "latest";
3242
+ const cveRef = cveId ? ` to fix ${cveId}` : "";
3243
+ remediationSteps.push(`Update ${name} from ${installed} to ${fixed}${cveRef}`);
3244
+ }
3245
+ } else if (f.remediation?.recommendation?.text) {
3222
3246
  remediationSteps.push(f.remediation.recommendation.text);
3223
3247
  }
3248
+ const genericPatterns = ["See References", "None Provided", "Review the finding"];
3249
+ if (remediationSteps.length === 0 || genericPatterns.some((p) => remediationSteps[0]?.startsWith(p))) {
3250
+ remediationSteps.length = 0;
3251
+ const rawTitle = f.title ?? "";
3252
+ if (rawTitle.includes("KB")) {
3253
+ const kbMatch = rawTitle.match(/KB\d+/);
3254
+ const kb = kbMatch ? kbMatch[0] : "patch";
3255
+ remediationSteps.push(`Install Windows patch ${kb} via WSUS or AWS Systems Manager Patch Manager`);
3256
+ remediationSteps.push(`Run: aws ssm send-command --document-name "AWS-InstallWindowsUpdates" --targets "Key=InstanceIds,Values=${resourceId}"`);
3257
+ if (kbMatch) {
3258
+ remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${kb}`);
3259
+ }
3260
+ } else if (rawTitle.includes("CVE-") || cveId) {
3261
+ const cveMatch = rawTitle.match(/CVE-[\d-]+/);
3262
+ const cve = cveMatch ? cveMatch[0] : cveId ?? "vulnerability";
3263
+ remediationSteps.push(`Fix ${cve}: update the affected software package to the latest patched version`);
3264
+ } else {
3265
+ remediationSteps.push(`Review and remediate: ${rawTitle}`);
3266
+ }
3267
+ }
3224
3268
  if (f.remediation?.recommendation?.Url) {
3225
- remediationSteps.push(`Reference: ${f.remediation.recommendation.Url}`);
3269
+ remediationSteps.push(`Documentation: ${f.remediation.recommendation.Url}`);
3226
3270
  }
3227
3271
  if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
3228
3272
  remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
3229
3273
  }
3230
- if (remediationSteps.length === 0) {
3231
- remediationSteps.push("Review the finding in the Amazon Inspector console and apply the recommended patch or update.");
3232
- }
3233
3274
  const description = f.description ?? titleBase;
3234
3275
  const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
3235
3276
  findings.push({
@@ -3564,10 +3605,10 @@ var ConfigRulesFindingsScanner = class {
3564
3605
  impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
3565
3606
  riskScore,
3566
3607
  remediationSteps: [
3567
- `Review the Config Rule "${ruleName}" in the AWS Config console.`,
3568
- `Check resource ${resourceId} for compliance violations.`,
3569
- "Follow the rule's remediation guidance to bring the resource into compliance."
3570
- ],
3608
+ `Fix Config Rule violation: ${ruleName}`,
3609
+ annotation ? `Details: ${annotation}` : "",
3610
+ `Resource: ${resourceType}/${resourceId}`
3611
+ ].filter(Boolean),
3571
3612
  priority: priorityFromSeverity(severity),
3572
3613
  module: this.moduleName,
3573
3614
  accountId
@@ -3716,13 +3757,13 @@ var AccessAnalyzerFindingsScanner = class {
3716
3757
  const title = buildFindingTitle(aaf);
3717
3758
  const impact = external ? `Resource is accessible from outside the account. Type: ${aaf.findingType ?? "unknown"}` : `Unused access detected \u2014 review and remove to follow least-privilege. Type: ${aaf.findingType ?? "unknown"}`;
3718
3759
  const remediationSteps = external ? [
3719
- "Review the finding in the IAM Access Analyzer console.",
3720
- `Check resource ${resourceId} for unintended external access.`,
3721
- "Remove or restrict the resource policy to eliminate external access."
3760
+ `Restrict external access on ${resourceType} ${resourceId}`,
3761
+ "Remove or narrow the resource policy to eliminate unintended external access.",
3762
+ `Resource ARN: ${resourceArn}`
3722
3763
  ] : [
3723
- "Review the finding in the IAM Access Analyzer console.",
3724
- `Check resource ${resourceId} for unused access permissions.`,
3725
- "Remove unused permissions, roles, or credentials to follow least-privilege."
3764
+ `Remove unused access on ${resourceType} ${resourceId}`,
3765
+ "Remove unused permissions, roles, or credentials to follow least-privilege.",
3766
+ `Resource ARN: ${resourceArn}`
3726
3767
  ];
3727
3768
  findings.push({
3728
3769
  severity,
@@ -4197,6 +4238,479 @@ var WafCoverageScanner = class {
4197
4238
  }
4198
4239
  };
4199
4240
 
4241
+ // src/i18n/zh.ts
4242
+ var zhI18n = {
4243
+ // HTML Security Report
4244
+ securityReportTitle: "AWS \u5B89\u5168\u626B\u63CF\u62A5\u544A",
4245
+ securityScore: "\u5B89\u5168\u8BC4\u5206",
4246
+ critical: "\u4E25\u91CD",
4247
+ high: "\u9AD8",
4248
+ medium: "\u4E2D",
4249
+ low: "\u4F4E",
4250
+ scanStatistics: "\u626B\u63CF\u7EDF\u8BA1",
4251
+ module: "\u6A21\u5757",
4252
+ resources: "\u8D44\u6E90",
4253
+ findings: "\u53D1\u73B0",
4254
+ status: "\u72B6\u6001",
4255
+ allFindings: "\u6240\u6709\u53D1\u73B0",
4256
+ recommendations: "\u5EFA\u8BAE",
4257
+ unique: "\u53BB\u91CD",
4258
+ showMore: "\u663E\u793A\u66F4\u591A",
4259
+ noIssuesFound: "\u672A\u53D1\u73B0\u5B89\u5168\u95EE\u9898\u3002",
4260
+ allModulesClean: "\u6240\u6709\u6A21\u5757\u6B63\u5E38",
4261
+ generatedBy: "\u7531 AWS Security MCP Server \u751F\u6210",
4262
+ informationalOnly: "\u672C\u62A5\u544A\u4EC5\u4F9B\u53C2\u8003\u3002",
4263
+ // MLPS Report
4264
+ mlpsTitle: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A",
4265
+ mlpsDisclaimer: "\u672C\u62A5\u544A\u4E3A\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u53C2\u8003\uFF0C\u63D0\u4F9B\u4E91\u5E73\u53F0\u914D\u7F6E\u68C0\u67E5\u6570\u636E\u4E0E\u5EFA\u8BAE\u3002\u5408\u89C4\u5224\u5B9A\uFF08\u7B26\u5408/\u90E8\u5206\u7B26\u5408/\u4E0D\u7B26\u5408\uFF09\u9700\u7531\u6301\u8BC1\u6D4B\u8BC4\u673A\u6784\u6839\u636E\u5B9E\u9645\u60C5\u51B5\u786E\u8BA4\u3002\uFF08GB/T 22239-2019 \u5B8C\u6574\u68C0\u67E5\u6E05\u5355 184 \u9879\uFF09",
4266
+ checkedItems: "\u5DF2\u68C0\u67E5\u9879",
4267
+ noIssues: "\u672A\u53D1\u73B0\u95EE\u9898",
4268
+ issuesFound: "\u53D1\u73B0\u95EE\u9898",
4269
+ notChecked: "\u672A\u68C0\u67E5",
4270
+ cloudProvider: "\u4E91\u5E73\u53F0\u8D1F\u8D23",
4271
+ manualReview: "\u9700\u4EBA\u5DE5\u8BC4\u4F30",
4272
+ notApplicable: "\u4E0D\u9002\u7528",
4273
+ checkResult: "\u68C0\u67E5\u7ED3\u679C",
4274
+ noRelatedIssues: "\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898",
4275
+ issuesFoundCount: (n) => `\u68C0\u67E5\u7ED3\u679C\uFF1A\u53D1\u73B0 ${n} \u4E2A\u76F8\u5173\u95EE\u9898`,
4276
+ remediation: "\u5EFA\u8BAE",
4277
+ remediationItems: (n) => `\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${n} \u9879\u53BB\u91CD\uFF09`,
4278
+ showRemaining: (n) => `\u663E\u793A\u5176\u4F59 ${n} \u9879`,
4279
+ // HW Defense Checklist
4280
+ hwChecklistTitle: "\u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09",
4281
+ hwChecklistSubtitle: "\u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A",
4282
+ hwEmergencyIsolation: `\u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
4283
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
4284
+ \u25A1 \u5236\u5B9A\u5B9E\u4F8B\u9694\u79BB SOP\uFF1A\u544A\u8B66 \u2192 \u6392\u67E5 \u2192 \u5C01\u9501\u653B\u51FBIP \u2192 \u7F51\u7EDC\u9694\u79BB \u2192 \u5B89\u5168\u5904\u7F6E \u2192 \u8BB0\u5F55\u653B\u51FB\u9879
4285
+ \u25A1 \u660E\u786E\u5404\u7CFB\u7EDF\uFF08\u751F\u4EA7\u6838\u5FC3/\u751F\u4EA7\u975E\u6838\u5FC3/\u6D4B\u8BD5/\u5F00\u53D1\uFF09\u7684\u5E94\u6025\u5904\u7F6E\u65B9\u5F0F
4286
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F`,
4287
+ hwTestEnvShutdown: `\u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
4288
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
4289
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
4290
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563`,
4291
+ hwDutyTeam: `\u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
4292
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
4293
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
4294
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
4295
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
4296
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09`,
4297
+ hwNetworkDiagram: `\u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
4298
+ \u25A1 \u786E\u4FDD\u6240\u6709\u4E92\u8054\u7F51/DX \u4E13\u7EBF\u51FA\u5165\u7AD9\u8DEF\u5F84\u5728\u67B6\u6784\u56FE\u4E2D\u6E05\u6670\u6807\u6CE8
4299
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
4300
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3`,
4301
+ hwPentest: `\u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
4302
+ \u25A1 \u62A4\u7F51\u524D\u8054\u7CFB\u5B89\u5168\u5382\u5546\uFF08\u9752\u85E4/\u957F\u4EAD/\u5FAE\u6B65\u7B49\uFF09\u8FDB\u884C\u6A21\u62DF\u653B\u51FB\u6F14\u7EC3
4303
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
4304
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09`,
4305
+ hwWarRoom: `\u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
4306
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
4307
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
4308
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D`,
4309
+ hwCredentials: `\u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
4310
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
4311
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
4312
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
4313
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801`,
4314
+ hwPostOptimization: `\u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
4315
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
4316
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
4317
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669`,
4318
+ hwReference: "\u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)",
4319
+ // Service Reminders
4320
+ serviceReminderTitle: "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
4321
+ serviceReminderFooter: "\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002",
4322
+ serviceImpact: "\u5F71\u54CD",
4323
+ serviceAction: "\u5EFA\u8BAE",
4324
+ // Common
4325
+ account: "\u8D26\u6237",
4326
+ region: "\u533A\u57DF",
4327
+ scanTime: "\u626B\u63CF\u65F6\u95F4",
4328
+ duration: "\u8017\u65F6",
4329
+ severityDistribution: "\u4E25\u91CD\u6027\u5206\u5E03",
4330
+ findingsByModule: "\u6309\u6A21\u5757\u5206\u7C7B\u7684\u53D1\u73B0",
4331
+ details: "\u8BE6\u60C5",
4332
+ // Extended — HTML Security Report extras
4333
+ topHighestRiskFindings: (n) => `\u524D ${n} \u9879\u6700\u9AD8\u98CE\u9669\u53D1\u73B0`,
4334
+ resource: "\u8D44\u6E90",
4335
+ impact: "\u5F71\u54CD",
4336
+ riskScore: "\u98CE\u9669\u8BC4\u5206",
4337
+ showRemainingFindings: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u53D1\u73B0\u2026`,
4338
+ trendTitle: "30\u65E5\u8D8B\u52BF",
4339
+ findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
4340
+ showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
4341
+ // Extended — MLPS extras
4342
+ // Markdown report
4343
+ executiveSummary: "\u6267\u884C\u6458\u8981",
4344
+ totalFindingsLabel: "\u53D1\u73B0\u603B\u6570",
4345
+ description: "\u63CF\u8FF0",
4346
+ priority: "\u4F18\u5148\u7EA7",
4347
+ noFindingsForSeverity: (severity) => `\u65E0${severity}\u53D1\u73B0\u3002`,
4348
+ preCheckOverview: "\u9884\u68C0\u603B\u89C8",
4349
+ accountInfo: "\u8D26\u6237\u4FE1\u606F",
4350
+ checkedCount: (total, clean, issues) => `\u5DF2\u68C0\u67E5: ${total} \u9879\uFF08\u672A\u53D1\u73B0\u95EE\u9898: ${clean} \u9879 | \u53D1\u73B0\u95EE\u9898: ${issues} \u9879\uFF09`,
4351
+ uncheckedCount: (n) => `\u672A\u68C0\u67E5: ${n} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
4352
+ cloudProviderCount: (n) => `\u4E91\u5E73\u53F0\u8D1F\u8D23: ${n} \u9879`,
4353
+ manualReviewCount: (n) => `\u9700\u4EBA\u5DE5\u8BC4\u4F30: ${n} \u9879`,
4354
+ naCount: (n) => `\u4E0D\u9002\u7528: ${n} \u9879`,
4355
+ naNote: (n) => `\u4E0D\u9002\u7528\u9879: ${n} \u9879\uFF08\u7269\u8054\u7F51/\u65E0\u7EBF\u7F51\u7EDC/\u79FB\u52A8\u7EC8\u7AEF/\u5DE5\u63A7\u7CFB\u7EDF/\u53EF\u4FE1\u9A8C\u8BC1\u7B49\uFF09`,
4356
+ unknownNote: (n) => `\uFF08${n} \u9879\u672A\u68C0\u67E5\uFF0C\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
4357
+ cloudItemsNote: (n) => `\u4EE5\u4E0B ${n} \u9879\u7531 AWS \u4E91\u5E73\u53F0\u8D1F\u8D23\uFF0C\u6839\u636E\u5B89\u5168\u8D23\u4EFB\u5171\u62C5\u6A21\u578B\u4E0D\u5728\u672C\u62A5\u544A\u68C0\u67E5\u8303\u56F4\u5185\u3002`,
4358
+ mlpsFooterGenerated: (version) => `\u7531 AWS Security MCP Server v${version} \u751F\u6210`,
4359
+ mlpsFooterDisclaimer: "\u672C\u62A5\u544A\u4E3A\u8BC1\u636E\u6536\u96C6\u53C2\u8003\uFF0C\u4E0D\u5305\u542B\u5408\u89C4\u5224\u5B9A\u3002\u5B8C\u6574\u7B49\u4FDD\u6D4B\u8BC4\u9700\u7531\u6301\u8BC1\u6D4B\u8BC4\u673A\u6784\u6267\u884C\u3002",
4360
+ andMore: (n) => `... \u53CA\u5176\u4ED6 ${n} \u9879`,
4361
+ remediationByPriority: "\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09",
4362
+ affectedResources: (n) => `\u6D89\u53CA ${n} \u4E2A\u8D44\u6E90`,
4363
+ installWindowsPatches: (n, kbs) => `\u5B89\u88C5 ${n} \u4E2A Windows \u8865\u4E01 (${kbs})`,
4364
+ mlpsCategorySection: {
4365
+ "\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
4366
+ "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
4367
+ "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
4368
+ "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
4369
+ "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
4370
+ },
4371
+ // Service Recommendations
4372
+ notEnabled: "\u672A\u542F\u7528",
4373
+ serviceRecommendations: {
4374
+ security_hub_findings: {
4375
+ icon: "\u{1F534}",
4376
+ service: "Security Hub",
4377
+ impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
4378
+ action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
4379
+ },
4380
+ guardduty_findings: {
4381
+ icon: "\u{1F534}",
4382
+ service: "GuardDuty",
4383
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u5A01\u80C1\u6D3B\u52A8\uFF08\u6076\u610F IP\u3001\u5F02\u5E38 API \u8C03\u7528\u3001\u52A0\u5BC6\u8D27\u5E01\u6316\u77FF\u7B49\uFF09",
4384
+ action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
4385
+ },
4386
+ inspector_findings: {
4387
+ icon: "\u{1F7E1}",
4388
+ service: "Inspector",
4389
+ impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
4390
+ action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
4391
+ },
4392
+ trusted_advisor_findings: {
4393
+ icon: "\u{1F7E1}",
4394
+ service: "Trusted Advisor",
4395
+ impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
4396
+ action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
4397
+ },
4398
+ config_rules_findings: {
4399
+ icon: "\u{1F7E1}",
4400
+ service: "AWS Config",
4401
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
4402
+ action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
4403
+ },
4404
+ access_analyzer_findings: {
4405
+ icon: "\u{1F7E1}",
4406
+ service: "IAM Access Analyzer",
4407
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
4408
+ action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
4409
+ },
4410
+ patch_compliance_findings: {
4411
+ icon: "\u{1F7E1}",
4412
+ service: "SSM Patch Manager",
4413
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
4414
+ action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
4415
+ }
4416
+ },
4417
+ // HW Checklist (full composite)
4418
+ hwChecklist: `
4419
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4420
+ \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
4421
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4422
+
4423
+ \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
4424
+
4425
+ \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
4426
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
4427
+ \u25A1 \u5236\u5B9A\u5B9E\u4F8B\u9694\u79BB SOP\uFF1A\u544A\u8B66 \u2192 \u6392\u67E5 \u2192 \u5C01\u9501\u653B\u51FBIP \u2192 \u7F51\u7EDC\u9694\u79BB \u2192 \u5B89\u5168\u5904\u7F6E \u2192 \u8BB0\u5F55\u653B\u51FB\u9879
4428
+ \u25A1 \u660E\u786E\u5404\u7CFB\u7EDF\uFF08\u751F\u4EA7\u6838\u5FC3/\u751F\u4EA7\u975E\u6838\u5FC3/\u6D4B\u8BD5/\u5F00\u53D1\uFF09\u7684\u5E94\u6025\u5904\u7F6E\u65B9\u5F0F
4429
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
4430
+
4431
+ \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
4432
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
4433
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
4434
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
4435
+
4436
+ \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
4437
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
4438
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
4439
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
4440
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
4441
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
4442
+
4443
+ \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
4444
+ \u25A1 \u786E\u4FDD\u6240\u6709\u4E92\u8054\u7F51/DX \u4E13\u7EBF\u51FA\u5165\u7AD9\u8DEF\u5F84\u5728\u67B6\u6784\u56FE\u4E2D\u6E05\u6670\u6807\u6CE8
4445
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
4446
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
4447
+
4448
+ \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
4449
+ \u25A1 \u62A4\u7F51\u524D\u8054\u7CFB\u5B89\u5168\u5382\u5546\uFF08\u9752\u85E4/\u957F\u4EAD/\u5FAE\u6B65\u7B49\uFF09\u8FDB\u884C\u6A21\u62DF\u653B\u51FB\u6F14\u7EC3
4450
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
4451
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
4452
+
4453
+ \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
4454
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
4455
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
4456
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D
4457
+
4458
+ \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
4459
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
4460
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
4461
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
4462
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
4463
+
4464
+ \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
4465
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
4466
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
4467
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
4468
+
4469
+ \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
4470
+ `
4471
+ };
4472
+
4473
+ // src/i18n/en.ts
4474
+ var enI18n = {
4475
+ // HTML Security Report
4476
+ securityReportTitle: "AWS Security Scan Report",
4477
+ securityScore: "Security Score",
4478
+ critical: "Critical",
4479
+ high: "High",
4480
+ medium: "Medium",
4481
+ low: "Low",
4482
+ scanStatistics: "Scan Statistics",
4483
+ module: "Module",
4484
+ resources: "Resources",
4485
+ findings: "Findings",
4486
+ status: "Status",
4487
+ allFindings: "All Findings",
4488
+ recommendations: "Recommendations",
4489
+ unique: "unique",
4490
+ showMore: "Show more",
4491
+ noIssuesFound: "No security issues found.",
4492
+ allModulesClean: "All modules clean",
4493
+ generatedBy: "Generated by AWS Security MCP Server",
4494
+ informationalOnly: "This report is for informational purposes only.",
4495
+ // MLPS Report
4496
+ mlpsTitle: "MLPS Level 3 Pre-Check Report",
4497
+ mlpsDisclaimer: "This report is for MLPS Level 3 pre-check reference, providing cloud platform configuration check data and recommendations. Compliance determination (compliant/partially compliant/non-compliant) must be confirmed by a certified assessment institution. (GB/T 22239-2019 full checklist: 184 items)",
4498
+ checkedItems: "Checked Items",
4499
+ noIssues: "No Issues Found",
4500
+ issuesFound: "Issues Found",
4501
+ notChecked: "Not Checked",
4502
+ cloudProvider: "Cloud Provider Responsible",
4503
+ manualReview: "Manual Review Required",
4504
+ notApplicable: "Not Applicable",
4505
+ checkResult: "Check Result",
4506
+ noRelatedIssues: "Check Result: No related issues found",
4507
+ issuesFoundCount: (n) => `Check Result: Found ${n} related issue${n === 1 ? "" : "s"}`,
4508
+ remediation: "Remediation",
4509
+ remediationItems: (n) => `Remediation Items (${n} unique)`,
4510
+ showRemaining: (n) => `Show remaining ${n} items`,
4511
+ // HW Defense Checklist
4512
+ hwChecklistTitle: "\u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)",
4513
+ hwChecklistSubtitle: "The following items require manual verification and execution:",
4514
+ hwEmergencyIsolation: `\u26A0\uFE0F Emergency Isolation / Incident Response Plan
4515
+ \u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
4516
+ \u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
4517
+ \u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
4518
+ \u25A1 Identify responsible personnel and contacts for each project account and resource`,
4519
+ hwTestEnvShutdown: `\u26A0\uFE0F Test/Development Environment Handling
4520
+ \u25A1 Shut down non-critical systems during the drill period
4521
+ \u25A1 Shut down test/dev environments or maintain same security baseline as production
4522
+ \u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation`,
4523
+ hwDutyTeam: `\u26A0\uFE0F On-Duty Team Formation
4524
+ \u25A1 7\xD724 monitoring and rapid response team
4525
+ \u25A1 Technical and risk analysis team
4526
+ \u25A1 Security policy deployment team
4527
+ \u25A1 Business response team
4528
+ \u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)`,
4529
+ hwNetworkDiagram: `\u26A0\uFE0F Ingress/Egress Path Architecture Diagram
4530
+ \u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
4531
+ \u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
4532
+ \u25A1 Identify all internet-facing data interaction interfaces`,
4533
+ hwPentest: `\u26A0\uFE0F Proactive Penetration Testing
4534
+ \u25A1 Contact security vendors for simulated attack drills before the exercise
4535
+ \u25A1 Conduct security hardening based on penetration test reports
4536
+ \u25A1 Monitor AWS security advisories (known vulnerabilities and patches)`,
4537
+ hwWarRoom: `\u26A0\uFE0F WAR-ROOM Real-Time Communication
4538
+ \u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
4539
+ \u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
4540
+ \u25A1 Standardize case title format: "[CyberDrill] + Issue Description"`,
4541
+ hwCredentials: `\u26A0\uFE0F Password & Credential Management
4542
+ \u25A1 All IAM users must have MFA enabled
4543
+ \u25A1 Access key rotation cycle \u2264 90 days
4544
+ \u25A1 Avoid shared account usage
4545
+ \u25A1 No plaintext passwords in S3/Lambda/application code`,
4546
+ hwPostOptimization: `\u26A0\uFE0F Post-Drill Optimization
4547
+ \u25A1 Address and remediate each item from the attack report
4548
+ \u25A1 Establish periodic security maintenance processes with the security team
4549
+ \u25A1 Continuously fill security risk gaps`,
4550
+ hwReference: "Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)",
4551
+ // Service Reminders
4552
+ serviceReminderTitle: "\u26A1 The following security services are not enabled; some checks cannot be performed:",
4553
+ serviceReminderFooter: "Re-scan after enabling the above services for a more complete security assessment.",
4554
+ serviceImpact: "Impact",
4555
+ serviceAction: "Action",
4556
+ // Common
4557
+ account: "Account",
4558
+ region: "Region",
4559
+ scanTime: "Scan Time",
4560
+ duration: "Duration",
4561
+ severityDistribution: "Severity Distribution",
4562
+ findingsByModule: "Findings by Module",
4563
+ details: "Details",
4564
+ // Extended \u2014 HTML Security Report extras
4565
+ topHighestRiskFindings: (n) => `Top ${n} Highest Risk Findings`,
4566
+ resource: "Resource",
4567
+ impact: "Impact",
4568
+ riskScore: "Risk Score",
4569
+ showRemainingFindings: (n) => `Show remaining ${n} findings\u2026`,
4570
+ trendTitle: "30-Day Trends",
4571
+ findingsBySeverity: "Findings by Severity",
4572
+ showMoreCount: (n) => `Show ${n} more\u2026`,
4573
+ // Extended \u2014 MLPS extras
4574
+ // Markdown report
4575
+ executiveSummary: "Executive Summary",
4576
+ totalFindingsLabel: "Total Findings",
4577
+ description: "Description",
4578
+ priority: "Priority",
4579
+ noFindingsForSeverity: (severity) => `No ${severity.toLowerCase()} findings.`,
4580
+ preCheckOverview: "Pre-Check Overview",
4581
+ accountInfo: "Account Information",
4582
+ checkedCount: (total, clean, issues) => `Checked: ${total} items (No issues: ${clean} | Issues found: ${issues})`,
4583
+ uncheckedCount: (n) => `Not checked: ${n} items (corresponding scan modules not run)`,
4584
+ cloudProviderCount: (n) => `Cloud provider responsible: ${n} items`,
4585
+ manualReviewCount: (n) => `Manual review required: ${n} items`,
4586
+ naCount: (n) => `Not applicable: ${n} items`,
4587
+ naNote: (n) => `Not applicable: ${n} items (IoT/wireless networks/mobile terminals/ICS/trusted verification, etc.)`,
4588
+ unknownNote: (n) => `(${n} items not checked \u2014 corresponding scan modules not run)`,
4589
+ cloudItemsNote: (n) => `The following ${n} items are the responsibility of the AWS cloud platform and are outside the scope of this report per the shared responsibility model.`,
4590
+ mlpsFooterGenerated: (version) => `Generated by AWS Security MCP Server v${version}`,
4591
+ mlpsFooterDisclaimer: "This report is for evidence collection reference and does not include compliance determination. A complete MLPS assessment must be conducted by a certified assessment institution.",
4592
+ andMore: (n) => `\u2026 and ${n} more`,
4593
+ remediationByPriority: "Remediation Items (by Priority)",
4594
+ affectedResources: (n) => `${n} resource${n === 1 ? "" : "s"} affected`,
4595
+ installWindowsPatches: (n, kbs) => `Install ${n} Windows patch${n === 1 ? "" : "es"} (${kbs})`,
4596
+ mlpsCategorySection: {
4597
+ "\u5B89\u5168\u7269\u7406\u73AF\u5883": "I. Physical Environment Security",
4598
+ "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "II. Communication Network Security",
4599
+ "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "III. Area Boundary Security",
4600
+ "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
4601
+ "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
4602
+ },
4603
+ // Service Recommendations
4604
+ notEnabled: "Not Enabled",
4605
+ serviceRecommendations: {
4606
+ security_hub_findings: {
4607
+ icon: "\u{1F534}",
4608
+ service: "Security Hub",
4609
+ impact: "Cannot obtain 300+ automated security checks (FSBP/CIS/PCI DSS standards)",
4610
+ action: "Enable Security Hub for the most comprehensive security posture assessment"
4611
+ },
4612
+ guardduty_findings: {
4613
+ icon: "\u{1F534}",
4614
+ service: "GuardDuty",
4615
+ impact: "Cannot detect threat activity (malicious IPs, anomalous API calls, crypto mining, etc.)",
4616
+ action: "Enable GuardDuty for continuous threat detection"
4617
+ },
4618
+ inspector_findings: {
4619
+ icon: "\u{1F7E1}",
4620
+ service: "Inspector",
4621
+ impact: "Cannot scan EC2/Lambda/container software vulnerabilities (CVEs)",
4622
+ action: "Enable Inspector to discover known security vulnerabilities"
4623
+ },
4624
+ trusted_advisor_findings: {
4625
+ icon: "\u{1F7E1}",
4626
+ service: "Trusted Advisor",
4627
+ impact: "Cannot obtain AWS best practice security checks",
4628
+ action: "Upgrade to Business/Enterprise Support plan to use Trusted Advisor security checks"
4629
+ },
4630
+ config_rules_findings: {
4631
+ icon: "\u{1F7E1}",
4632
+ service: "AWS Config",
4633
+ impact: "Cannot check resource configuration compliance status",
4634
+ action: "Enable AWS Config and configure Config Rules"
4635
+ },
4636
+ access_analyzer_findings: {
4637
+ icon: "\u{1F7E1}",
4638
+ service: "IAM Access Analyzer",
4639
+ impact: "Cannot detect whether resources are accessed by external accounts or public networks",
4640
+ action: "Create IAM Access Analyzer (account-level or organization-level)"
4641
+ },
4642
+ patch_compliance_findings: {
4643
+ icon: "\u{1F7E1}",
4644
+ service: "SSM Patch Manager",
4645
+ impact: "Cannot check instance patch compliance status",
4646
+ action: "Install SSM Agent and configure Patch Manager"
4647
+ }
4648
+ },
4649
+ // HW Checklist (full composite)
4650
+ hwChecklist: `
4651
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4652
+ \u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)
4653
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4654
+
4655
+ The following items require manual verification and execution:
4656
+
4657
+ \u26A0\uFE0F Emergency Isolation / Incident Response Plan
4658
+ \u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
4659
+ \u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
4660
+ \u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
4661
+ \u25A1 Identify responsible personnel and contacts for each project account and resource
4662
+
4663
+ \u26A0\uFE0F Test/Development Environment Handling
4664
+ \u25A1 Shut down non-critical systems during the drill period
4665
+ \u25A1 Shut down test/dev environments or maintain same security baseline as production
4666
+ \u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation
4667
+
4668
+ \u26A0\uFE0F On-Duty Team Formation
4669
+ \u25A1 7\xD724 monitoring and rapid response team
4670
+ \u25A1 Technical and risk analysis team
4671
+ \u25A1 Security policy deployment team
4672
+ \u25A1 Business response team
4673
+ \u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)
4674
+
4675
+ \u26A0\uFE0F Ingress/Egress Path Architecture Diagram
4676
+ \u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
4677
+ \u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
4678
+ \u25A1 Identify all internet-facing data interaction interfaces
4679
+
4680
+ \u26A0\uFE0F Proactive Penetration Testing
4681
+ \u25A1 Contact security vendors for simulated attack drills before the exercise
4682
+ \u25A1 Conduct security hardening based on penetration test reports
4683
+ \u25A1 Monitor AWS security advisories (known vulnerabilities and patches)
4684
+
4685
+ \u26A0\uFE0F WAR-ROOM Real-Time Communication
4686
+ \u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
4687
+ \u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
4688
+ \u25A1 Standardize case title format: "[CyberDrill] + Issue Description"
4689
+
4690
+ \u26A0\uFE0F Password & Credential Management
4691
+ \u25A1 All IAM users must have MFA enabled
4692
+ \u25A1 Access key rotation cycle \u2264 90 days
4693
+ \u25A1 Avoid shared account usage
4694
+ \u25A1 No plaintext passwords in S3/Lambda/application code
4695
+
4696
+ \u26A0\uFE0F Post-Drill Optimization
4697
+ \u25A1 Address and remediate each item from the attack report
4698
+ \u25A1 Establish periodic security maintenance processes with the security team
4699
+ \u25A1 Continuously fill security risk gaps
4700
+
4701
+ Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)
4702
+ `
4703
+ };
4704
+
4705
+ // src/i18n/index.ts
4706
+ var translations = {
4707
+ zh: zhI18n,
4708
+ en: enI18n
4709
+ };
4710
+ function getI18n(lang = "zh") {
4711
+ return translations[lang] ?? translations.zh;
4712
+ }
4713
+
4200
4714
  // src/tools/report-tool.ts
4201
4715
  var SEVERITY_ICON = {
4202
4716
  CRITICAL: "\u{1F534}",
@@ -4214,38 +4728,45 @@ function formatDuration(start, end) {
4214
4728
  const remainSecs = secs % 60;
4215
4729
  return `${mins}m ${remainSecs}s`;
4216
4730
  }
4217
- function renderFinding(f) {
4218
- const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
4219
- return [
4220
- `#### ${f.title}`,
4221
- `- **Resource:** ${f.resourceId} (\`${f.resourceArn}\`)`,
4222
- `- **Description:** ${f.description}`,
4223
- `- **Impact:** ${f.impact}`,
4224
- `- **Risk Score:** ${f.riskScore}/10`,
4225
- `- **Remediation:**`,
4226
- steps,
4227
- `- **Priority:** ${f.priority}`
4228
- ].join("\n");
4229
- }
4230
- function generateMarkdownReport(scanResults) {
4731
+ function generateMarkdownReport(scanResults, lang) {
4732
+ const t = getI18n(lang ?? "zh");
4231
4733
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
4232
4734
  const date = scanStart.split("T")[0];
4233
4735
  const duration = formatDuration(scanStart, scanEnd);
4736
+ const sevLabel = {
4737
+ CRITICAL: t.critical,
4738
+ HIGH: t.high,
4739
+ MEDIUM: t.medium,
4740
+ LOW: t.low
4741
+ };
4742
+ function renderFinding(f) {
4743
+ const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
4744
+ return [
4745
+ `#### ${f.title}`,
4746
+ `- **${t.resource}:** ${f.resourceId} (\`${f.resourceArn}\`)`,
4747
+ `- **${t.description}:** ${f.description}`,
4748
+ `- **${t.impact}:** ${f.impact}`,
4749
+ `- **${t.riskScore}:** ${f.riskScore}/10`,
4750
+ `- **${t.remediation}:**`,
4751
+ steps,
4752
+ `- **${t.priority}:** ${f.priority}`
4753
+ ].join("\n");
4754
+ }
4234
4755
  const lines = [];
4235
- lines.push(`# AWS Security Scan Report \u2014 ${date}`);
4756
+ lines.push(`# ${t.securityReportTitle} \u2014 ${date}`);
4236
4757
  lines.push("");
4237
- lines.push("## Executive Summary");
4238
- lines.push(`- **Account:** ${accountId}`);
4239
- lines.push(`- **Region:** ${region}`);
4240
- lines.push(`- **Scan Duration:** ${duration}`);
4758
+ lines.push(`## ${t.executiveSummary}`);
4759
+ lines.push(`- **${t.account}:** ${accountId}`);
4760
+ lines.push(`- **${t.region}:** ${region}`);
4761
+ lines.push(`- **${t.duration}:** ${duration}`);
4241
4762
  lines.push(
4242
- `- **Total Findings:** ${summary.totalFindings} (\u{1F534} ${summary.critical} Critical | \u{1F7E0} ${summary.high} High | \u{1F7E1} ${summary.medium} Medium | \u{1F7E2} ${summary.low} Low)`
4763
+ `- **${t.totalFindingsLabel}:** ${summary.totalFindings} (${SEVERITY_ICON.CRITICAL} ${summary.critical} ${t.critical} | ${SEVERITY_ICON.HIGH} ${summary.high} ${t.high} | ${SEVERITY_ICON.MEDIUM} ${summary.medium} ${t.medium} | ${SEVERITY_ICON.LOW} ${summary.low} ${t.low})`
4243
4764
  );
4244
4765
  lines.push("");
4245
4766
  if (summary.totalFindings === 0) {
4246
- lines.push("## Findings by Severity");
4767
+ lines.push(`## ${t.findingsBySeverity}`);
4247
4768
  lines.push("");
4248
- lines.push("\u2705 No security issues found.");
4769
+ lines.push(`\u2705 ${t.noIssuesFound}`);
4249
4770
  lines.push("");
4250
4771
  } else {
4251
4772
  const allFindings = modules.flatMap((m) => m.findings);
@@ -4256,15 +4777,15 @@ function generateMarkdownReport(scanResults) {
4256
4777
  for (const f of allFindings) {
4257
4778
  grouped.get(f.severity).push(f);
4258
4779
  }
4259
- lines.push("## Findings by Severity");
4780
+ lines.push(`## ${t.findingsBySeverity}`);
4260
4781
  lines.push("");
4261
4782
  for (const sev of SEVERITY_ORDER) {
4262
4783
  const findings = grouped.get(sev);
4263
4784
  const icon = SEVERITY_ICON[sev];
4264
- lines.push(`### ${icon} ${sev.charAt(0)}${sev.slice(1).toLowerCase()}`);
4785
+ lines.push(`### ${icon} ${sevLabel[sev]}`);
4265
4786
  lines.push("");
4266
4787
  if (findings.length === 0) {
4267
- lines.push(`No ${sev.toLowerCase()} findings.`);
4788
+ lines.push(t.noFindingsForSeverity(sevLabel[sev]));
4268
4789
  lines.push("");
4269
4790
  continue;
4270
4791
  }
@@ -4275,9 +4796,9 @@ function generateMarkdownReport(scanResults) {
4275
4796
  }
4276
4797
  }
4277
4798
  }
4278
- lines.push("## Scan Statistics");
4799
+ lines.push(`## ${t.scanStatistics}`);
4279
4800
  lines.push(
4280
- "| Module | Resources Scanned | Findings | Status |"
4801
+ `| ${t.module} | ${t.resources} | ${t.findings} | ${t.status} |`
4281
4802
  );
4282
4803
  lines.push("|--------|------------------|----------|--------|");
4283
4804
  for (const m of modules) {
@@ -4290,7 +4811,7 @@ function generateMarkdownReport(scanResults) {
4290
4811
  if (summary.totalFindings > 0) {
4291
4812
  const allFindings = modules.flatMap((m) => m.findings);
4292
4813
  allFindings.sort((a, b) => b.riskScore - a.riskScore);
4293
- lines.push("## Recommendations (Priority Order)");
4814
+ lines.push(`## ${t.recommendations}`);
4294
4815
  for (let i = 0; i < allFindings.length; i++) {
4295
4816
  const f = allFindings[i];
4296
4817
  lines.push(`${i + 1}. [${f.priority}] ${f.title}: ${f.remediationSteps[0] ?? "Review and remediate."}`);
@@ -6337,13 +6858,6 @@ var MLPS3_CATEGORY_ORDER = [
6337
6858
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6338
6859
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6339
6860
  ];
6340
- var MLPS3_CATEGORY_SECTION = {
6341
- "\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
6342
- "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
6343
- "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
6344
- "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6345
- "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6346
- };
6347
6861
  var MLPS3_CHECK_MAPPING = [
6348
6862
  // =========================================================================
6349
6863
  // 安全物理环境 — L3-PES1-* (22 items) → cloud_provider
@@ -6908,9 +7422,8 @@ var MLPS3_CHECK_MAPPING = [
6908
7422
  },
6909
7423
  {
6910
7424
  id: "L3-SMC1-09",
6911
- type: "auto",
6912
- modules: ["service_detection"],
6913
- findingPatterns: ["CloudWatch"]
7425
+ type: "manual",
7426
+ guidance: "\u9700\u914D\u7F6E CloudWatch \u96C6\u4E2D\u76D1\u63A7\u5E73\u53F0\uFF0C\u7ED3\u5408 SNS \u8FDB\u884C\u544A\u8B66\u901A\u77E5"
6914
7427
  },
6915
7428
  {
6916
7429
  id: "L3-SMC1-10",
@@ -7004,232 +7517,68 @@ function evaluateAllFullChecks(scanResults) {
7004
7517
  return evaluateFullCheck(item, mapping, allFindings, scanModules);
7005
7518
  });
7006
7519
  }
7007
- var MLPS_CHECKS = [
7008
- // 一、身份鉴别
7009
- {
7010
- id: "8.1.4.1a",
7011
- category: "\u8EAB\u4EFD\u9274\u522B",
7012
- name: "\u5BC6\u7801\u7B56\u7565",
7013
- modules: ["security_hub_findings"],
7014
- findingPatterns: ["password policy", "password length", "complexity", "password expiry", "reuse prevention", "IAM.7", "IAM.10"]
7015
- },
7016
- {
7017
- id: "8.1.4.1a",
7018
- category: "\u8EAB\u4EFD\u9274\u522B",
7019
- name: "\u5BC6\u94A5\u8F6E\u6362",
7020
- modules: ["security_hub_findings"],
7021
- findingPatterns: ["access key older", "access key rotated", "IAM.3", "IAM.4"]
7022
- },
7023
- {
7024
- id: "8.1.4.1d",
7025
- category: "\u8EAB\u4EFD\u9274\u522B",
7026
- name: "\u53CC\u56E0\u7D20\u8BA4\u8BC1",
7027
- modules: ["security_hub_findings"],
7028
- findingPatterns: ["MFA", "IAM.5", "IAM.6"]
7029
- },
7030
- // 二、访问控制
7031
- {
7032
- id: "8.1.4.2c",
7033
- category: "\u8BBF\u95EE\u63A7\u5236",
7034
- name: "\u6700\u5C0F\u6743\u9650",
7035
- modules: ["iam_privilege_escalation", "security_hub_findings"],
7036
- findingPatterns: [
7037
- "AdministratorAccess",
7038
- "PowerUserAccess",
7039
- "IAMFullAccess",
7040
- "over-permissive",
7041
- "privilege escalation",
7042
- "self-grant",
7043
- "iam:*",
7044
- "create admin",
7045
- "Lambda role passing",
7046
- "CreateAccessKey",
7047
- "AssumeRole"
7048
- ]
7049
- },
7050
- {
7051
- id: "8.1.4.2",
7052
- category: "\u8BBF\u95EE\u63A7\u5236",
7053
- name: "\u5B89\u5168\u7EC4",
7054
- modules: ["network_reachability", "security_hub_findings"],
7055
- findingPatterns: ["allows all ports", "allows SSH", "allows RDP", "MySQL", "PostgreSQL", "MongoDB", "Redis", "high-risk port", "security group", "EC2.18", "EC2.19"]
7056
- },
7057
- // 三、安全审计
7058
- {
7059
- id: "8.1.4.3a",
7060
- category: "\u5B89\u5168\u5BA1\u8BA1",
7061
- name: "\u5BA1\u8BA1\u529F\u80FD",
7062
- modules: ["security_hub_findings"],
7063
- findingPatterns: ["CloudTrail", "not enabled", "multi-region", "not logging", "CloudTrail.1"]
7064
- },
7065
- {
7066
- id: "8.1.4.3b",
7067
- category: "\u5B89\u5168\u5BA1\u8BA1",
7068
- name: "\u5BA1\u8BA1\u5B8C\u6574\u6027",
7069
- modules: ["security_hub_findings"],
7070
- findingPatterns: ["log file validation", "log integrity", "log validation", "CloudTrail.4", "CloudTrail.5"]
7071
- },
7072
- {
7073
- id: "8.1.4.3c",
7074
- category: "\u5B89\u5168\u5BA1\u8BA1",
7075
- name: "\u5BA1\u8BA1\u4FDD\u62A4",
7076
- modules: ["security_hub_findings"],
7077
- findingPatterns: ["CloudTrail", "S3 bucket", "encryption", "versioning", "Block Public Access", "CloudTrail.6", "CloudTrail.7"]
7078
- },
7079
- // 四、入侵防范
7080
- {
7081
- id: "8.1.4.4a",
7082
- category: "\u5165\u4FB5\u9632\u8303",
7083
- name: "GuardDuty \u5A01\u80C1\u68C0\u6D4B",
7084
- modules: ["service_detection", "guardduty_findings"],
7085
- findingPatterns: ["GuardDuty"]
7086
- },
7087
- {
7088
- id: "8.1.4.4a",
7089
- category: "\u5165\u4FB5\u9632\u8303",
7090
- name: "Inspector \u6F0F\u6D1E\u626B\u63CF",
7091
- modules: ["service_detection", "inspector_findings"],
7092
- findingPatterns: ["Inspector", "CVE-"]
7093
- },
7094
- // 五、数据安全
7095
- {
7096
- id: "8.1.4.5a",
7097
- category: "\u6570\u636E\u5B89\u5168",
7098
- name: "\u4F20\u8F93\u52A0\u5BC6",
7099
- modules: ["ssl_certificate", "security_hub_findings"],
7100
- findingPatterns: ["HTTPS", "TLS", "HTTP listener", "certificate", "ELB.1"]
7101
- },
7102
- {
7103
- id: "8.1.4.5b",
7104
- category: "\u6570\u636E\u5B89\u5168",
7105
- name: "S3 \u5B58\u50A8\u52A0\u5BC6",
7106
- modules: ["security_hub_findings"],
7107
- findingPatterns: ["no default encryption", "not encrypted", "S3.4"]
7108
- },
7109
- {
7110
- id: "8.1.4.5b",
7111
- category: "\u6570\u636E\u5B89\u5168",
7112
- name: "EBS \u9ED8\u8BA4\u52A0\u5BC6",
7113
- modules: ["security_hub_findings"],
7114
- findingPatterns: ["EBS default encryption", "EC2.7"]
7115
- },
7116
- {
7117
- id: "8.1.4.5b",
7118
- category: "\u6570\u636E\u5B89\u5168",
7119
- name: "RDS \u5B58\u50A8\u52A0\u5BC6",
7120
- modules: ["security_hub_findings"],
7121
- findingPatterns: ["storage is not encrypted", "RDS.3"]
7122
- },
7123
- // 六、网络安全
7124
- {
7125
- id: "8.1.3.1a",
7126
- category: "\u7F51\u7EDC\u5B89\u5168",
7127
- name: "\u7F51\u7EDC\u67B6\u6784",
7128
- modules: ["security_hub_findings"],
7129
- findingPatterns: ["default VPC", "EC2.2"]
7130
- },
7131
- {
7132
- id: "8.1.3.2a",
7133
- category: "\u7F51\u7EDC\u5B89\u5168",
7134
- name: "\u8FB9\u754C\u9632\u62A4",
7135
- modules: ["network_reachability", "security_hub_findings"],
7136
- findingPatterns: ["allows all ports", "allows SSH", "allows RDP", "security group", "EC2.18", "EC2.19"]
7137
- }
7138
- ];
7139
- var CATEGORY_ORDER = [
7140
- "\u8EAB\u4EFD\u9274\u522B",
7141
- "\u8BBF\u95EE\u63A7\u5236",
7142
- "\u5B89\u5168\u5BA1\u8BA1",
7143
- "\u5165\u4FB5\u9632\u8303",
7144
- "\u6570\u636E\u5B89\u5168",
7145
- "\u7F51\u7EDC\u5B89\u5168"
7146
- ];
7147
- var CATEGORY_SECTION = {
7148
- "\u8EAB\u4EFD\u9274\u522B": "\u4E00\u3001\u8EAB\u4EFD\u9274\u522B",
7149
- "\u8BBF\u95EE\u63A7\u5236": "\u4E8C\u3001\u8BBF\u95EE\u63A7\u5236",
7150
- "\u5B89\u5168\u5BA1\u8BA1": "\u4E09\u3001\u5B89\u5168\u5BA1\u8BA1",
7151
- "\u5165\u4FB5\u9632\u8303": "\u56DB\u3001\u5165\u4FB5\u9632\u8303",
7152
- "\u6570\u636E\u5B89\u5168": "\u4E94\u3001\u6570\u636E\u5B89\u5168",
7153
- "\u7F51\u7EDC\u5B89\u5168": "\u516D\u3001\u7F51\u7EDC\u5B89\u5168"
7154
- };
7155
- function evaluateCheck(check, allFindings, scanModules) {
7156
- const allModulesPresent = check.modules.every(
7157
- (mod) => scanModules.some((m) => m.module === mod && m.status === "success")
7158
- );
7159
- if (!allModulesPresent) {
7160
- return { check, status: "unknown", relatedFindings: [] };
7161
- }
7162
- const relatedFindings = allFindings.filter((f) => {
7163
- const moduleMatch = check.modules.some((mod) => f.module === mod);
7164
- if (!moduleMatch) return false;
7165
- const text = `${f.title} ${f.description}`.toLowerCase();
7166
- return check.findingPatterns.some(
7167
- (pattern) => text.includes(pattern.toLowerCase())
7168
- );
7169
- });
7170
- return {
7171
- check,
7172
- status: relatedFindings.length === 0 ? "clean" : "issues",
7173
- relatedFindings
7174
- };
7175
- }
7176
- function generateMlps3Report(scanResults) {
7520
+ function generateMlps3Report(scanResults, lang) {
7521
+ const t = getI18n(lang ?? "zh");
7522
+ const isEn = (lang ?? "zh") === "en";
7177
7523
  const { accountId, region, scanStart } = scanResults;
7178
7524
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
7179
- const allFindings = scanResults.modules.flatMap(
7180
- (m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
7181
- );
7182
- const scanModules = scanResults.modules.map((m) => ({
7183
- module: m.module,
7184
- status: m.status
7185
- }));
7186
- const results = MLPS_CHECKS.map(
7187
- (check) => evaluateCheck(check, allFindings, scanModules)
7188
- );
7189
- const cleanCount = results.filter((r) => r.status === "clean").length;
7190
- const issuesCount = results.filter((r) => r.status === "issues").length;
7191
- const unknownCount = results.filter((r) => r.status === "unknown").length;
7192
- const checkedTotal = cleanCount + issuesCount;
7193
- const total = results.length;
7525
+ const results = evaluateAllFullChecks(scanResults);
7526
+ const autoResults = results.filter((r) => r.mapping.type === "auto");
7527
+ const autoClean = autoResults.filter((r) => r.status === "clean").length;
7528
+ const autoIssues = autoResults.filter((r) => r.status === "issues").length;
7529
+ const autoUnknown = autoResults.filter((r) => r.status === "unknown").length;
7530
+ const checkedTotal = autoClean + autoIssues;
7531
+ const cloudCount = results.filter((r) => r.status === "cloud_provider").length;
7532
+ const manualCount = results.filter((r) => r.status === "manual").length;
7533
+ const naCount = results.filter((r) => r.status === "not_applicable").length;
7534
+ const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
7535
+ const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
7194
7536
  const lines = [];
7195
- lines.push("# \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A");
7196
- 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**");
7537
+ lines.push(`# ${t.mlpsTitle}`);
7538
+ lines.push(`> **${t.mlpsDisclaimer}**`);
7197
7539
  lines.push("");
7198
- lines.push("## \u8D26\u6237\u4FE1\u606F");
7199
- lines.push(`- Account: ${accountId} | Region: ${region} | \u626B\u63CF\u65F6\u95F4: ${scanTime}`);
7540
+ lines.push(`## ${t.accountInfo}`);
7541
+ lines.push(`- ${t.account}: ${accountId} | ${t.region}: ${region} | ${t.scanTime}: ${scanTime}`);
7200
7542
  lines.push("");
7201
- lines.push("## \u9884\u68C0\u603B\u89C8");
7202
- lines.push(`- \u5DF2\u68C0\u67E5 ${checkedTotal} \u9879 / \u5171 ${total} \u9879`);
7203
- lines.push(` - \u672A\u53D1\u73B0\u95EE\u9898: ${cleanCount} \u9879`);
7204
- lines.push(` - \u53D1\u73B0\u95EE\u9898: ${issuesCount} \u9879`);
7205
- if (unknownCount > 0) {
7206
- lines.push(` - \u672A\u68C0\u67E5: ${unknownCount} \u9879`);
7543
+ lines.push(`## ${t.preCheckOverview}`);
7544
+ lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
7545
+ if (autoUnknown > 0) {
7546
+ lines.push(`- ${t.uncheckedCount(autoUnknown)}`);
7547
+ }
7548
+ lines.push(`- ${t.cloudProviderCount(cloudCount)}`);
7549
+ lines.push(`- ${t.manualReviewCount(manualCount)}`);
7550
+ if (naCount > 0) {
7551
+ lines.push(`- ${t.naCount(naCount)}`);
7207
7552
  }
7208
7553
  lines.push("");
7209
- for (const category of CATEGORY_ORDER) {
7210
- const sectionTitle = CATEGORY_SECTION[category];
7211
- const categoryResults = results.filter((r) => r.check.category === category);
7212
- if (categoryResults.length === 0) continue;
7554
+ for (const category of MLPS3_CATEGORY_ORDER) {
7555
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
7556
+ const catResults = results.filter(
7557
+ (r) => r.item.categoryCn === category && r.status !== "not_applicable"
7558
+ );
7559
+ if (catResults.length === 0) continue;
7213
7560
  lines.push(`## ${sectionTitle}`);
7214
7561
  lines.push("");
7215
- const byId = /* @__PURE__ */ new Map();
7216
- for (const r of categoryResults) {
7217
- const existing = byId.get(r.check.id) ?? [];
7218
- existing.push(r);
7219
- byId.set(r.check.id, existing);
7562
+ const controlMap = /* @__PURE__ */ new Map();
7563
+ for (const r of catResults) {
7564
+ const key = r.item.controlCn;
7565
+ if (!controlMap.has(key)) controlMap.set(key, []);
7566
+ controlMap.get(key).push(r);
7220
7567
  }
7221
- for (const [checkId, checkResults] of byId) {
7222
- lines.push(`### ${checkId} ${checkResults[0].check.name}`);
7223
- for (const r of checkResults) {
7224
- const icon = r.status === "clean" ? "\u2705" : r.status === "issues" ? "\u274C" : "\u26A0\uFE0F";
7225
- const label = r.status === "unknown" ? " \u672A\u68C0\u67E5" : r.status === "clean" ? " \u672A\u53D1\u73B0\u95EE\u9898" : r.status === "issues" ? " \u53D1\u73B0\u95EE\u9898" : "";
7226
- lines.push(`- [${icon}] ${r.check.name}${label}`);
7568
+ for (const [_controlKey, controlResults] of controlMap) {
7569
+ const controlName = itemControl(controlResults[0]);
7570
+ lines.push(`### ${controlName}`);
7571
+ for (const r of controlResults) {
7572
+ const icon = r.status === "clean" ? "\u2705" : r.status === "issues" ? "\u274C" : r.status === "unknown" ? "\u26A0\uFE0F" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
7573
+ const suffix = r.status === "unknown" ? ` \u2014 ${t.notChecked}` : r.status === "manual" ? ` \u2014 ${r.mapping.guidance ?? t.manualReview}` : r.status === "cloud_provider" ? ` \u2014 ${r.mapping.note ?? t.cloudProvider}` : r.status === "clean" ? ` ${t.noIssues}` : ` ${t.issuesFound}`;
7574
+ const reqText = itemReq(r);
7575
+ lines.push(`- [${icon}] ${r.item.id} ${reqText.slice(0, 60)}${reqText.length > 60 ? "\u2026" : ""}${suffix}`);
7227
7576
  if (r.status === "issues" && r.relatedFindings.length > 0) {
7228
7577
  for (const f of r.relatedFindings.slice(0, 3)) {
7229
7578
  lines.push(` - ${f.severity}: ${f.title}`);
7230
7579
  }
7231
7580
  if (r.relatedFindings.length > 3) {
7232
- lines.push(` - ... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 3} \u9879`);
7581
+ lines.push(` - ${t.andMore(r.relatedFindings.length - 3)}`);
7233
7582
  }
7234
7583
  }
7235
7584
  }
@@ -7238,7 +7587,7 @@ function generateMlps3Report(scanResults) {
7238
7587
  }
7239
7588
  const failedResults = results.filter((r) => r.status === "issues");
7240
7589
  if (failedResults.length > 0) {
7241
- lines.push("## \u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09");
7590
+ lines.push(`## ${t.remediationByPriority}`);
7242
7591
  lines.push("");
7243
7592
  const allFailedFindings = /* @__PURE__ */ new Map();
7244
7593
  for (const r of failedResults) {
@@ -7260,6 +7609,10 @@ function generateMlps3Report(scanResults) {
7260
7609
  }
7261
7610
  lines.push("");
7262
7611
  }
7612
+ if (naCount > 0) {
7613
+ lines.push(`> ${t.naNote(naCount)}`);
7614
+ lines.push("");
7615
+ }
7263
7616
  return lines.join("\n");
7264
7617
  }
7265
7618
 
@@ -7267,6 +7620,15 @@ function generateMlps3Report(scanResults) {
7267
7620
  function esc(s) {
7268
7621
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
7269
7622
  }
7623
+ function escWithLinks(s) {
7624
+ const parts = s.split(/(https?:\/\/\S+)/);
7625
+ return parts.map((part, i) => {
7626
+ if (i % 2 === 1) {
7627
+ return `<a href="${esc(part)}" style="color:#60a5fa" target="_blank" rel="noopener">${esc(part)}</a>`;
7628
+ }
7629
+ return esc(part);
7630
+ }).join("");
7631
+ }
7270
7632
  function calcScore(summary) {
7271
7633
  const raw = 100 - summary.critical * 15 - summary.high * 5 - summary.medium * 2 - summary.low * 0.5;
7272
7634
  return Math.max(0, Math.min(100, Math.round(raw)));
@@ -7290,50 +7652,6 @@ function scoreColor(score) {
7290
7652
  if (score >= 50) return "#eab308";
7291
7653
  return "#ef4444";
7292
7654
  }
7293
- var SERVICE_RECOMMENDATIONS = {
7294
- security_hub_findings: {
7295
- icon: "\u{1F534}",
7296
- service: "Security Hub",
7297
- impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
7298
- action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
7299
- },
7300
- guardduty_findings: {
7301
- icon: "\u{1F534}",
7302
- service: "GuardDuty",
7303
- 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",
7304
- action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
7305
- },
7306
- inspector_findings: {
7307
- icon: "\u{1F7E1}",
7308
- service: "Inspector",
7309
- impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
7310
- action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
7311
- },
7312
- trusted_advisor_findings: {
7313
- icon: "\u{1F7E1}",
7314
- service: "Trusted Advisor",
7315
- impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
7316
- action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
7317
- },
7318
- config_rules_findings: {
7319
- icon: "\u{1F7E1}",
7320
- service: "AWS Config",
7321
- impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
7322
- action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
7323
- },
7324
- access_analyzer_findings: {
7325
- icon: "\u{1F7E1}",
7326
- service: "IAM Access Analyzer",
7327
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
7328
- action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
7329
- },
7330
- patch_compliance_findings: {
7331
- icon: "\u{1F7E1}",
7332
- service: "SSM Patch Manager",
7333
- impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
7334
- action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
7335
- }
7336
- };
7337
7655
  var SERVICE_NOT_ENABLED_PATTERNS = [
7338
7656
  "not enabled",
7339
7657
  "not found",
@@ -7343,10 +7661,11 @@ var SERVICE_NOT_ENABLED_PATTERNS = [
7343
7661
  "not available",
7344
7662
  "is not enabled"
7345
7663
  ];
7346
- function getDisabledServices(modules) {
7664
+ function getDisabledServices(modules, lang) {
7665
+ const t = getI18n(lang ?? "zh");
7347
7666
  const disabled = [];
7348
7667
  for (const mod of modules) {
7349
- const rec = SERVICE_RECOMMENDATIONS[mod.module];
7668
+ const rec = t.serviceRecommendations[mod.module];
7350
7669
  if (!rec) continue;
7351
7670
  if (!mod.warnings?.length) continue;
7352
7671
  const hasNotEnabled = mod.warnings.some(
@@ -7358,21 +7677,22 @@ function getDisabledServices(modules) {
7358
7677
  }
7359
7678
  return disabled;
7360
7679
  }
7361
- function buildServiceReminderHtml(modules) {
7362
- const disabled = getDisabledServices(modules);
7680
+ function buildServiceReminderHtml(modules, lang) {
7681
+ const t = getI18n(lang ?? "zh");
7682
+ const disabled = getDisabledServices(modules, lang);
7363
7683
  if (disabled.length === 0) return "";
7364
7684
  const items = disabled.map((svc) => `
7365
7685
  <div style="margin-bottom:12px">
7366
- <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} \u672A\u542F\u7528</div>
7367
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5F71\u54CD\uFF1A${esc(svc.impact)}</div>
7368
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5EFA\u8BAE\uFF1A${esc(svc.action)}</div>
7686
+ <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} ${esc(t.notEnabled)}</div>
7687
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceImpact)}\uFF1A${esc(svc.impact)}</div>
7688
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceAction)}\uFF1A${esc(svc.action)}</div>
7369
7689
  </div>`).join("\n");
7370
7690
  return `
7371
7691
  <section>
7372
7692
  <div style="background:#2d1f00;border:1px solid #b45309;border-radius:8px;padding:20px;margin-bottom:32px">
7373
- <div style="font-size:17px;font-weight:700;margin-bottom:12px">&#9889; \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A</div>
7693
+ <div style="font-size:17px;font-weight:700;margin-bottom:12px">${esc(t.serviceReminderTitle)}</div>
7374
7694
  ${items}
7375
- <div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500">\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002</div>
7695
+ <div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500">${esc(t.serviceReminderFooter)}</div>
7376
7696
  </div>
7377
7697
  </section>`;
7378
7698
  }
@@ -7574,12 +7894,12 @@ function donutChart(summary) {
7574
7894
  "</svg>"
7575
7895
  ].join("\n");
7576
7896
  }
7577
- function barChart(modules) {
7897
+ function barChart(modules, allCleanLabel = "All modules clean") {
7578
7898
  const withFindings = modules.filter((m) => m.findingsCount > 0).sort((a, b) => b.findingsCount - a.findingsCount).slice(0, 12);
7579
7899
  if (withFindings.length === 0) {
7580
7900
  return [
7581
7901
  '<svg viewBox="0 0 400 50" width="100%">',
7582
- ' <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">All modules clean</text>',
7902
+ ` <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">${esc(allCleanLabel)}</text>`,
7583
7903
  "</svg>"
7584
7904
  ].join("\n");
7585
7905
  }
@@ -7694,7 +8014,9 @@ function scoreTrendChart(history) {
7694
8014
  "</svg>"
7695
8015
  ].join("\n");
7696
8016
  }
7697
- function generateHtmlReport(scanResults, history) {
8017
+ function generateHtmlReport(scanResults, history, lang) {
8018
+ const t = getI18n(lang ?? "zh");
8019
+ const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
7698
8020
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
7699
8021
  const date = scanStart.split("T")[0];
7700
8022
  const duration = formatDuration2(scanStart, scanEnd);
@@ -7712,23 +8034,23 @@ function generateHtmlReport(scanResults, history) {
7712
8034
  <div class="top5-content">
7713
8035
  <span class="badge badge-${esc(f.severity.toLowerCase())}">${esc(f.severity)}</span>
7714
8036
  <div class="top5-title">${esc(f.title)}</div>
7715
- <div class="top5-detail"><strong>Resource:</strong> ${esc(f.resourceId)}</div>
7716
- <div class="top5-detail"><strong>Impact:</strong> ${esc(f.impact)}</div>
7717
- <div class="top5-detail"><strong>Risk Score:</strong> ${f.riskScore}/10</div>
7718
- <h4>Remediation</h4>
7719
- <ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
8037
+ <div class="top5-detail"><strong>${t.resource}:</strong> ${esc(f.resourceId)}</div>
8038
+ <div class="top5-detail"><strong>${t.impact}:</strong> ${esc(f.impact)}</div>
8039
+ <div class="top5-detail"><strong>${t.riskScore}:</strong> ${f.riskScore}/10</div>
8040
+ <h4>${t.remediation}</h4>
8041
+ <ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
7720
8042
  </div>
7721
8043
  </div>`
7722
8044
  ).join("\n");
7723
8045
  top5Html = `
7724
8046
  <section>
7725
- <h2>Top ${top5.length} Highest Risk Findings</h2>
8047
+ <h2>${esc(t.topHighestRiskFindings(top5.length))}</h2>
7726
8048
  ${cards}
7727
8049
  </section>`;
7728
8050
  }
7729
8051
  let findingsHtml;
7730
8052
  if (summary.totalFindings === 0) {
7731
- findingsHtml = '<div class="no-findings">No security issues found.</div>';
8053
+ findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
7732
8054
  } else {
7733
8055
  const FOLD_THRESHOLD = 20;
7734
8056
  const renderCard = (f) => {
@@ -7737,10 +8059,10 @@ function generateHtmlReport(scanResults, history) {
7737
8059
  <span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
7738
8060
  <span class="finding-title-text">${esc(f.title)}</span>
7739
8061
  <span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
7740
- <details><summary>Details</summary><div class="finding-card-body">
8062
+ <details><summary>${t.details}</summary><div class="finding-card-body">
7741
8063
  <p>${esc(f.description)}</p>
7742
- <p><strong>Remediation:</strong></p>
7743
- <ol>${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
8064
+ <p><strong>${t.remediation}:</strong></p>
8065
+ <ol>${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
7744
8066
  </div></details>
7745
8067
  </div>`;
7746
8068
  };
@@ -7751,7 +8073,7 @@ function generateHtmlReport(scanResults, history) {
7751
8073
  const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
7752
8074
  const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
7753
8075
  return `${first}
7754
- <details><summary>Show remaining ${findings.length - FOLD_THRESHOLD} findings...</summary>
8076
+ <details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
7755
8077
  ${rest}
7756
8078
  </details>`;
7757
8079
  };
@@ -7803,13 +8125,13 @@ ${rest}
7803
8125
  if (history && history.length >= 2) {
7804
8126
  trendHtml = `
7805
8127
  <section class="trend-section">
7806
- <h2>30-Day Trends</h2>
8128
+ <h2>${esc(t.trendTitle)}</h2>
7807
8129
  <div class="trend-chart">
7808
- <div class="trend-title">Findings by Severity</div>
8130
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7809
8131
  ${findingsTrendChart(history)}
7810
8132
  </div>
7811
8133
  <div class="trend-chart">
7812
- <div class="trend-title">Security Score</div>
8134
+ <div class="trend-title">${esc(t.securityScore)}</div>
7813
8135
  ${scoreTrendChart(history)}
7814
8136
  </div>
7815
8137
  </section>`;
@@ -7820,16 +8142,57 @@ ${rest}
7820
8142
  let recsHtml = "";
7821
8143
  if (summary.totalFindings > 0) {
7822
8144
  const recMap = /* @__PURE__ */ new Map();
8145
+ const kbPatches = [];
8146
+ let kbSeverity = "LOW";
8147
+ let kbUrl;
8148
+ const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7823
8149
  for (const f of allFindings) {
7824
8150
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
8151
+ const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
8152
+ if (genericPatterns.some((p) => rem.startsWith(p))) continue;
8153
+ const kbMatch = f.title.match(/KB\d+/);
8154
+ if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
8155
+ kbPatches.push(kbMatch[0]);
8156
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(kbSeverity)) kbSeverity = f.severity;
8157
+ if (!kbUrl && url) kbUrl = url;
8158
+ continue;
8159
+ }
8160
+ if (f.module === "security_hub_findings") {
8161
+ const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
8162
+ if (controlMatch) {
8163
+ const controlId = controlMatch[1];
8164
+ const key = `ctrl:${controlId}`;
8165
+ const existing2 = recMap.get(key);
8166
+ if (existing2) {
8167
+ existing2.count++;
8168
+ if (!existing2.url && url) existing2.url = url;
8169
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
8170
+ } else {
8171
+ recMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
8172
+ }
8173
+ continue;
8174
+ }
8175
+ }
7825
8176
  const existing = recMap.get(rem);
7826
8177
  if (existing) {
7827
8178
  existing.count++;
8179
+ if (!existing.url && url) existing.url = url;
7828
8180
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7829
8181
  existing.severity = f.severity;
7830
8182
  }
7831
8183
  } else {
7832
- recMap.set(rem, { text: rem, severity: f.severity, count: 1 });
8184
+ recMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
8185
+ }
8186
+ }
8187
+ if (kbPatches.length > 0) {
8188
+ const unique = [...new Set(kbPatches)];
8189
+ const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
8190
+ recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
8191
+ }
8192
+ for (const [key, rec] of recMap) {
8193
+ if (key.startsWith("ctrl:") && rec.count > 1) {
8194
+ rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
8195
+ rec.count = 1;
7833
8196
  }
7834
8197
  }
7835
8198
  const uniqueRecs = [...recMap.values()].sort((a, b) => {
@@ -7840,60 +8203,61 @@ ${rest}
7840
8203
  const renderRec = (r) => {
7841
8204
  const sev = r.severity.toLowerCase();
7842
8205
  const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
7843
- return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}</li>`;
8206
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</a>` : "";
8207
+ return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
7844
8208
  };
7845
8209
  const TOP_N = 10;
7846
8210
  const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
7847
8211
  const remaining = uniqueRecs.slice(TOP_N);
7848
8212
  const moreHtml = remaining.length > 0 ? `
7849
- <details><summary>Show ${remaining.length} more&hellip;</summary>
8213
+ <details><summary>${t.showMoreCount(remaining.length)}</summary>
7850
8214
  ${remaining.map(renderRec).join("\n")}
7851
8215
  </details>` : "";
7852
8216
  recsHtml = `
7853
8217
  <details class="rec-fold">
7854
- <summary><h2 style="margin:0;border:0;display:inline">Recommendations (${uniqueRecs.length} unique)</h2></summary>
8218
+ <summary><h2 style="margin:0;border:0;display:inline">${esc(t.recommendations)} (${uniqueRecs.length} ${esc(t.unique)})</h2></summary>
7855
8219
  <div class="rec-body">
7856
8220
  <ol>${topItems}${moreHtml}</ol>
7857
8221
  </div>
7858
8222
  </details>`;
7859
8223
  }
7860
8224
  return `<!DOCTYPE html>
7861
- <html lang="en">
8225
+ <html lang="${htmlLang}">
7862
8226
  <head>
7863
8227
  <meta charset="UTF-8">
7864
8228
  <meta name="viewport" content="width=device-width,initial-scale=1">
7865
- <title>AWS Security Scan Report &mdash; ${esc(date)}</title>
8229
+ <title>${esc(t.securityReportTitle)} &mdash; ${esc(date)}</title>
7866
8230
  <style>${sharedCss()}</style>
7867
8231
  </head>
7868
8232
  <body>
7869
8233
  <div class="container">
7870
8234
 
7871
8235
  <header>
7872
- <h1>&#128737;&#65039; AWS Security Scan Report</h1>
7873
- <div class="meta">Account: ${esc(accountId)} | Region: ${esc(region)} | ${esc(date)} | Duration: ${esc(duration)}</div>
8236
+ <h1>&#128737;&#65039; ${esc(t.securityReportTitle)}</h1>
8237
+ <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(date)} | ${esc(t.duration)}: ${esc(duration)}</div>
7874
8238
  </header>
7875
8239
 
7876
8240
  <section class="summary">
7877
8241
  <div class="score-card">
7878
8242
  <div class="score-value" style="color:${scoreColor(score)}">${score}</div>
7879
- <div class="score-label">Security Score</div>
8243
+ <div class="score-label">${esc(t.securityScore)}</div>
7880
8244
  </div>
7881
8245
  <div class="severity-stats">
7882
- <div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">Critical</div></div>
7883
- <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">High</div></div>
7884
- <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">Medium</div></div>
7885
- <div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">Low</div></div>
8246
+ <div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">${esc(t.critical)}</div></div>
8247
+ <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">${esc(t.high)}</div></div>
8248
+ <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">${esc(t.medium)}</div></div>
8249
+ <div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">${esc(t.low)}</div></div>
7886
8250
  </div>
7887
8251
  </section>
7888
8252
 
7889
8253
  <section class="charts">
7890
8254
  <div class="chart-box">
7891
- <div class="chart-title">Severity Distribution</div>
8255
+ <div class="chart-title">${esc(t.severityDistribution)}</div>
7892
8256
  <div style="text-align:center">${donutChart(summary)}</div>
7893
8257
  </div>
7894
8258
  <div class="chart-box">
7895
- <div class="chart-title">Findings by Module</div>
7896
- ${barChart(modules)}
8259
+ <div class="chart-title">${esc(t.findingsByModule)}</div>
8260
+ ${barChart(modules, t.allModulesClean)}
7897
8261
  </div>
7898
8262
  </section>
7899
8263
 
@@ -7901,33 +8265,35 @@ ${trendHtml}
7901
8265
 
7902
8266
  ${top5Html}
7903
8267
 
7904
- ${buildServiceReminderHtml(modules)}
8268
+ ${buildServiceReminderHtml(modules, lang)}
7905
8269
 
7906
8270
  <section>
7907
- <h2>Scan Statistics</h2>
8271
+ <h2>${esc(t.scanStatistics)}</h2>
7908
8272
  <table>
7909
- <thead><tr><th>Module</th><th>Resources</th><th>Findings</th><th>Status</th></tr></thead>
8273
+ <thead><tr><th>${esc(t.module)}</th><th>${esc(t.resources)}</th><th>${esc(t.findings)}</th><th>${esc(t.status)}</th></tr></thead>
7910
8274
  <tbody>${statsRows}</tbody>
7911
8275
  </table>
7912
8276
  </section>
7913
8277
 
7914
8278
  <section>
7915
- <h2>All Findings</h2>
8279
+ <h2>${esc(t.allFindings)}</h2>
7916
8280
  ${findingsHtml}
7917
8281
  </section>
7918
8282
 
7919
8283
  ${recsHtml}
7920
8284
 
7921
8285
  <footer>
7922
- <p>Generated by AWS Security MCP Server v${VERSION}</p>
7923
- <p>This report is for informational purposes only.</p>
8286
+ <p>${esc(t.generatedBy)} v${VERSION}</p>
8287
+ <p>${esc(t.informationalOnly)}</p>
7924
8288
  </footer>
7925
8289
 
7926
8290
  </div>
7927
8291
  </body>
7928
8292
  </html>`;
7929
8293
  }
7930
- function generateMlps3HtmlReport(scanResults, history) {
8294
+ function generateMlps3HtmlReport(scanResults, history, lang) {
8295
+ const t = getI18n(lang ?? "zh");
8296
+ const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
7931
8297
  const { accountId, region, scanStart } = scanResults;
7932
8298
  const date = scanStart.split("T")[0];
7933
8299
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
@@ -7944,17 +8310,21 @@ function generateMlps3HtmlReport(scanResults, history) {
7944
8310
  if (history && history.length >= 2) {
7945
8311
  trendHtml = `
7946
8312
  <section class="trend-section">
7947
- <h2>30\u65E5\u8D8B\u52BF</h2>
8313
+ <h2>${esc(t.trendTitle)}</h2>
7948
8314
  <div class="trend-chart">
7949
- <div class="trend-title">\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0</div>
8315
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7950
8316
  ${findingsTrendChart(history)}
7951
8317
  </div>
7952
8318
  <div class="trend-chart">
7953
- <div class="trend-title">\u5B89\u5168\u8BC4\u5206</div>
8319
+ <div class="trend-title">${esc(t.securityScore)}</div>
7954
8320
  ${scoreTrendChart(history)}
7955
8321
  </div>
7956
8322
  </section>`;
7957
8323
  }
8324
+ const isEn = (lang ?? "zh") === "en";
8325
+ const itemCat = (r) => isEn ? r.item.categoryEn : r.item.categoryCn;
8326
+ const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
8327
+ const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
7958
8328
  const categoryMap = /* @__PURE__ */ new Map();
7959
8329
  for (const r of results) {
7960
8330
  if (r.status === "not_applicable") continue;
@@ -7963,7 +8333,7 @@ function generateMlps3HtmlReport(scanResults, history) {
7963
8333
  categoryMap.get(cat).push(r);
7964
8334
  }
7965
8335
  const categorySections = MLPS3_CATEGORY_ORDER.map((category) => {
7966
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
8336
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
7967
8337
  const catResults = categoryMap.get(category);
7968
8338
  if (!catResults || catResults.length === 0) return "";
7969
8339
  const allCloud = catResults.every((r) => r.status === "cloud_provider");
@@ -7971,11 +8341,11 @@ function generateMlps3HtmlReport(scanResults, history) {
7971
8341
  return `<details class="category-fold mlps-cloud-section">
7972
8342
  <summary>
7973
8343
  <span class="category-title">${esc(sectionTitle)}</span>
7974
- <span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length} \u9879\u4E91\u5E73\u53F0\u8D1F\u8D23</span></span>
8344
+ <span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length} ${esc(t.cloudProvider)}</span></span>
7975
8345
  </summary>
7976
8346
  <div class="category-body">
7977
- <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>
7978
- ${catResults.map((r) => `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(r.item.controlCn)}</span><span class="check-note">${esc(r.mapping.note ?? "")}</span></div>`).join("\n")}
8347
+ <div class="mlps-cloud-note">${esc(t.cloudItemsNote(catResults.length))}</div>
8348
+ ${catResults.map((r) => `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(itemControl(r))}</span><span class="check-note">${esc(r.mapping.note ?? "")}</span></div>`).join("\n")}
7979
8349
  </div>
7980
8350
  </details>`;
7981
8351
  }
@@ -7997,31 +8367,34 @@ function generateMlps3HtmlReport(scanResults, history) {
7997
8367
  if (!controlMap.has(key)) controlMap.set(key, []);
7998
8368
  controlMap.get(key).push(r);
7999
8369
  }
8000
- const controlGroups = [...controlMap.entries()].map(([controlName, controlResults]) => {
8370
+ const controlGroups = [...controlMap.entries()].map(([_controlKey, controlResults]) => {
8371
+ const controlName = itemControl(controlResults[0]);
8001
8372
  const cloudItems = controlResults.filter((r) => r.status === "cloud_provider");
8002
8373
  const nonCloudItems = controlResults.filter((r) => r.status !== "cloud_provider");
8003
8374
  let itemsHtml = "";
8004
8375
  for (const r of nonCloudItems) {
8005
8376
  const icon = r.status === "clean" ? "\u{1F7E2}" : r.status === "issues" ? "\u{1F534}" : r.status === "unknown" ? "\u2B1C" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
8006
8377
  const cls = `check-${r.status === "cloud_provider" ? "cloud" : r.status}`;
8007
- const suffix = r.status === "unknown" ? " \u2014 \u672A\u68C0\u67E5" : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? "\u9700\u4EBA\u5DE5\u8BC4\u4F30")}` : "";
8378
+ const suffix = r.status === "unknown" ? ` \u2014 ${esc(t.notChecked)}` : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? t.manualReview)}` : "";
8008
8379
  let findingsDetail = "";
8009
8380
  if (r.status === "clean") {
8010
- findingsDetail = `<div class="check-detail">\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898</div>`;
8381
+ findingsDetail = `<div class="check-detail">${esc(t.noRelatedIssues)}</div>`;
8011
8382
  } else if (r.status === "issues" && r.relatedFindings.length > 0) {
8012
8383
  const fItems = r.relatedFindings.slice(0, 5).map((f) => `<li>${esc(f.severity)}: ${esc(f.title)}</li>`);
8013
8384
  if (r.relatedFindings.length > 5) {
8014
- fItems.push(`<li>... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 5} \u9879</li>`);
8385
+ fItems.push(`<li>${esc(t.andMore(r.relatedFindings.length - 5))}</li>`);
8015
8386
  }
8016
- 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>` : "";
8017
- findingsDetail = `<div class="check-findings-wrap"><details><summary>\u68C0\u67E5\u7ED3\u679C\uFF1A\u53D1\u73B0 ${r.relatedFindings.length} \u4E2A\u76F8\u5173\u95EE\u9898</summary><ul class="check-findings">${fItems.join("")}</ul>${remediationHint}</details></div>`;
8387
+ const remediationHint = r.relatedFindings[0]?.remediationSteps?.[0] ? `<p style="color:#fbbf24;font-size:12px;margin-top:4px">${esc(t.remediation)}\uFF1A${escWithLinks(r.relatedFindings[0].remediationSteps[0])}</p>` : "";
8388
+ findingsDetail = `<div class="check-findings-wrap"><details><summary>${esc(t.issuesFoundCount(r.relatedFindings.length))}</summary><ul class="check-findings">${fItems.join("")}</ul>${remediationHint}</details></div>`;
8018
8389
  }
8019
- itemsHtml += `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc(r.item.id)} ${esc(r.item.requirementCn.slice(0, 60))}${r.item.requirementCn.length > 60 ? "\u2026" : ""}${suffix}</span></div>
8390
+ const reqText = itemReq(r);
8391
+ itemsHtml += `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc(r.item.id)} ${esc(reqText.slice(0, 60))}${reqText.length > 60 ? "\u2026" : ""}${suffix}</span></div>
8020
8392
  ${findingsDetail}`;
8021
8393
  }
8022
8394
  if (cloudItems.length > 0) {
8023
8395
  for (const r of cloudItems) {
8024
- itemsHtml += `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(r.item.requirementCn.slice(0, 50))}${r.item.requirementCn.length > 50 ? "\u2026" : ""}</span><span class="check-note">\u4E91\u5E73\u53F0\u8D1F\u8D23</span></div>
8396
+ const reqText = itemReq(r);
8397
+ itemsHtml += `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(reqText.slice(0, 50))}${reqText.length > 50 ? "\u2026" : ""}</span><span class="check-note">${esc(t.cloudProvider)}</span></div>
8025
8398
  `;
8026
8399
  }
8027
8400
  }
@@ -8054,20 +8427,61 @@ ${itemsHtml}
8054
8427
  let remediationHtml = "";
8055
8428
  if (failedResults.length > 0) {
8056
8429
  const mlpsRecMap = /* @__PURE__ */ new Map();
8430
+ const mlpsKbPatches = [];
8431
+ let mlpsKbSeverity = "LOW";
8432
+ let mlpsKbUrl;
8433
+ const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
8057
8434
  for (const r of failedResults) {
8058
8435
  for (const f of r.relatedFindings) {
8059
8436
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
8437
+ const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
8438
+ if (mlpsGenericPatterns.some((p) => rem.startsWith(p))) continue;
8439
+ const kbMatch = f.title.match(/KB\d+/);
8440
+ if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
8441
+ mlpsKbPatches.push(kbMatch[0]);
8442
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsKbSeverity)) mlpsKbSeverity = f.severity;
8443
+ if (!mlpsKbUrl && url) mlpsKbUrl = url;
8444
+ continue;
8445
+ }
8446
+ if (f.module === "security_hub_findings") {
8447
+ const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
8448
+ if (controlMatch) {
8449
+ const controlId = controlMatch[1];
8450
+ const key = `ctrl:${controlId}`;
8451
+ const existing2 = mlpsRecMap.get(key);
8452
+ if (existing2) {
8453
+ existing2.count++;
8454
+ if (!existing2.url && url) existing2.url = url;
8455
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
8456
+ } else {
8457
+ mlpsRecMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
8458
+ }
8459
+ continue;
8460
+ }
8461
+ }
8060
8462
  const existing = mlpsRecMap.get(rem);
8061
8463
  if (existing) {
8062
8464
  existing.count++;
8465
+ if (!existing.url && url) existing.url = url;
8063
8466
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
8064
8467
  existing.severity = f.severity;
8065
8468
  }
8066
8469
  } else {
8067
- mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
8470
+ mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
8068
8471
  }
8069
8472
  }
8070
8473
  }
8474
+ if (mlpsKbPatches.length > 0) {
8475
+ const unique = [...new Set(mlpsKbPatches)];
8476
+ const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
8477
+ mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
8478
+ }
8479
+ for (const [key, rec] of mlpsRecMap) {
8480
+ if (key.startsWith("ctrl:") && rec.count > 1) {
8481
+ rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
8482
+ rec.count = 1;
8483
+ }
8484
+ }
8071
8485
  const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
8072
8486
  const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
8073
8487
  if (sevDiff !== 0) return sevDiff;
@@ -8077,26 +8491,27 @@ ${itemsHtml}
8077
8491
  const renderMlpsRec = (r) => {
8078
8492
  const sev = r.severity.toLowerCase();
8079
8493
  const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
8080
- return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}</li>`;
8494
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</a>` : "";
8495
+ return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
8081
8496
  };
8082
8497
  const MLPS_TOP_N = 10;
8083
8498
  const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
8084
8499
  const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
8085
8500
  const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
8086
- <details><summary>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879&hellip;</summary>
8501
+ <details><summary>${esc(t.showRemaining(mlpsRemaining.length))}&hellip;</summary>
8087
8502
  ${mlpsRemaining.map(renderMlpsRec).join("\n")}
8088
8503
  </details>` : "";
8089
8504
  remediationHtml = `
8090
8505
  <details class="rec-fold" open>
8091
- <summary><h2 style="margin:0;border:0;display:inline">\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${mlpsUniqueRecs.length} \u9879\u53BB\u91CD\uFF09</h2></summary>
8506
+ <summary><h2 style="margin:0;border:0;display:inline">${esc(t.remediationItems(mlpsUniqueRecs.length))}</h2></summary>
8092
8507
  <div class="rec-body">
8093
8508
  <ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
8094
8509
  </div>
8095
8510
  </details>`;
8096
8511
  }
8097
8512
  }
8098
- 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>` : "";
8099
- const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">\uFF08${autoUnknown} \u9879\u672A\u68C0\u67E5\uFF0C\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09</div>` : "";
8513
+ const naNote = naCount > 0 ? `<p style="color:#64748b;font-size:13px;margin-top:24px">${esc(t.naNote(naCount))}</p>` : "";
8514
+ const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">${esc(t.unknownNote(autoUnknown))}</div>` : "";
8100
8515
  const mlpsCss = `
8101
8516
  .mlps-cloud-section>summary{color:#94a3b8}
8102
8517
  .mlps-cloud-note{color:#94a3b8;font-size:13px;margin-bottom:12px;font-style:italic}
@@ -8118,40 +8533,40 @@ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
8118
8533
  .mlps-summary-card .stat-label{font-size:12px;color:#94a3b8;margin-top:2px}
8119
8534
  `;
8120
8535
  return `<!DOCTYPE html>
8121
- <html lang="zh-CN">
8536
+ <html lang="${htmlLang}">
8122
8537
  <head>
8123
8538
  <meta charset="UTF-8">
8124
8539
  <meta name="viewport" content="width=device-width,initial-scale=1">
8125
- <title>\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A &mdash; ${esc(date)}</title>
8540
+ <title>${esc(t.mlpsTitle)} &mdash; ${esc(date)}</title>
8126
8541
  <style>${sharedCss()}${mlpsCss}</style>
8127
8542
  </head>
8128
8543
  <body>
8129
8544
  <div class="container">
8130
8545
 
8131
8546
  <header>
8132
- <h1>&#128737;&#65039; \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A</h1>
8133
- <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>
8134
- <div class="meta">\u8D26\u6237: ${esc(accountId)} | \u533A\u57DF: ${esc(region)} | \u626B\u63CF\u65F6\u95F4: ${esc(scanTime)}</div>
8547
+ <h1>&#128737;&#65039; ${esc(t.mlpsTitle)}</h1>
8548
+ <div class="disclaimer">${esc(t.mlpsDisclaimer)}</div>
8549
+ <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(t.scanTime)}: ${esc(scanTime)}</div>
8135
8550
  </header>
8136
8551
 
8137
8552
  <section class="summary" style="display:block;text-align:center">
8138
8553
  <div style="font-size:36px;font-weight:700;margin-bottom:12px">
8139
- <span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">\u672A\u53D1\u73B0\u95EE\u9898</span>
8554
+ <span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.noIssues)}</span>
8140
8555
  <span style="color:#475569;margin:0 16px">/</span>
8141
- <span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px">\u53D1\u73B0\u95EE\u9898</span>
8556
+ <span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.issuesFound)}</span>
8142
8557
  </div>
8143
8558
  <div class="mlps-summary-cards" style="justify-content:center">
8144
- <div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">\u5DF2\u68C0\u67E5\u9879</div></div>
8145
- <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>
8146
- <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>
8147
- ${naCount > 0 ? `<div class="mlps-summary-card"><div class="stat-count" style="color:#64748b">${naCount}</div><div class="stat-label">\u2796 \u4E0D\u9002\u7528</div></div>` : ""}
8559
+ <div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">${esc(t.checkedItems)}</div></div>
8560
+ <div class="mlps-summary-card"><div class="stat-count" style="color:#94a3b8">${cloudCount}</div><div class="stat-label">\u{1F3E2} ${esc(t.cloudProvider)}</div></div>
8561
+ <div class="mlps-summary-card"><div class="stat-count" style="color:#eab308">${manualCount}</div><div class="stat-label">\u{1F4CB} ${esc(t.manualReview)}</div></div>
8562
+ ${naCount > 0 ? `<div class="mlps-summary-card"><div class="stat-count" style="color:#64748b">${naCount}</div><div class="stat-label">\u2796 ${esc(t.notApplicable)}</div></div>` : ""}
8148
8563
  </div>
8149
8564
  </section>
8150
8565
  ${unknownNote}
8151
8566
 
8152
8567
  ${trendHtml}
8153
8568
 
8154
- ${buildServiceReminderHtml(scanResults.modules)}
8569
+ ${buildServiceReminderHtml(scanResults.modules, lang)}
8155
8570
 
8156
8571
  ${categorySections}
8157
8572
 
@@ -8160,8 +8575,8 @@ ${remediationHtml}
8160
8575
  ${naNote}
8161
8576
 
8162
8577
  <footer>
8163
- <p>\u7531 AWS Security MCP Server v${VERSION} \u751F\u6210</p>
8164
- <p>\u672C\u62A5\u544A\u4E3A\u8BC1\u636E\u6536\u96C6\u53C2\u8003\uFF0C\u4E0D\u5305\u542B\u5408\u89C4\u5224\u5B9A\u3002\u5B8C\u6574\u7B49\u4FDD\u6D4B\u8BC4\u9700\u7531\u6301\u8BC1\u6D4B\u8BC4\u673A\u6784\u6267\u884C\u3002</p>
8578
+ <p>${esc(t.mlpsFooterGenerated(VERSION))}</p>
8579
+ <p>${esc(t.mlpsFooterDisclaimer)}</p>
8165
8580
  </footer>
8166
8581
 
8167
8582
  </div>
@@ -8376,16 +8791,14 @@ Aggregates active findings from AWS Security Hub. Replaces individual config sca
8376
8791
  - INFORMATIONAL findings are skipped.
8377
8792
 
8378
8793
  ## 3. GuardDuty Findings (guardduty_findings)
8379
- Aggregates threat detection findings from Amazon GuardDuty.
8380
- - Covers account compromise, instance compromise, and reconnaissance.
8381
- - Severity mapped from GuardDuty 0\u201310 scale: \u22657 \u2192 HIGH, \u22654 \u2192 MEDIUM, <4 \u2192 LOW.
8382
- - Only non-archived findings are included.
8794
+ Detection-only: checks if GuardDuty is enabled in the region.
8795
+ - GuardDuty findings are aggregated via Security Hub (security_hub_findings module).
8796
+ - Reports whether GuardDuty detectors are active.
8383
8797
 
8384
8798
  ## 4. Inspector Findings (inspector_findings)
8385
- Aggregates vulnerability findings from Amazon Inspector v2.
8386
- - Covers CVEs in EC2 instances, Lambda functions, and container images.
8387
- - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
8388
- - CVE IDs are included in finding titles when available.
8799
+ Detection-only: checks if Inspector is enabled in the region.
8800
+ - Inspector findings are aggregated via Security Hub (security_hub_findings module).
8801
+ - Reports whether Inspector scanning (EC2/Lambda) is active.
8389
8802
 
8390
8803
  ## 5. Trusted Advisor Findings (trusted_advisor_findings)
8391
8804
  Aggregates security checks from AWS Trusted Advisor.
@@ -8421,19 +8834,15 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
8421
8834
  Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
8422
8835
 
8423
8836
  ## 15. Config Rules Findings (config_rules_findings)
8424
- Pulls non-compliant AWS Config Rule evaluation results.
8425
- - Lists all Config Rules and their compliance status.
8426
- - For NON_COMPLIANT rules, retrieves specific non-compliant resources.
8427
- - Security-related rules (encryption, IAM, public access, etc.) mapped to HIGH severity (7.5).
8428
- - Other non-compliant rules mapped to MEDIUM severity (5.5).
8837
+ Detection-only: checks if AWS Config Rules are configured.
8838
+ - Config Rule compliance findings are aggregated via Security Hub (security_hub_findings module).
8839
+ - Reports whether Config is enabled and counts active rules.
8429
8840
  - Gracefully handles regions where AWS Config is not enabled.
8430
8841
 
8431
8842
  ## 16. IAM Access Analyzer Findings (access_analyzer_findings)
8432
- Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account.
8433
- - Lists active analyzers (ACCOUNT or ORGANIZATION type).
8434
- - Retrieves ACTIVE findings showing external access to resources.
8435
- - Covers S3 buckets, IAM roles, SQS queues, Lambda functions, KMS keys, and more.
8436
- - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
8843
+ Detection-only: checks if IAM Access Analyzer is configured.
8844
+ - Access Analyzer findings are aggregated via Security Hub (security_hub_findings module).
8845
+ - Reports whether active analyzers exist.
8437
8846
  - Returns warning if no analyzer is configured.
8438
8847
 
8439
8848
  ## 17. SSM Patch Compliance (patch_compliance_findings)
@@ -8518,112 +8927,18 @@ var MODULE_DESCRIPTIONS = {
8518
8927
  idle_resources: "Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped instances, unused security groups) that waste money and increase attack surface.",
8519
8928
  disaster_recovery: "Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.",
8520
8929
  security_hub_findings: "Aggregates active findings from AWS Security Hub \u2014 replaces individual config scanners with centralized compliance checks.",
8521
- guardduty_findings: "Aggregates threat detection findings from Amazon GuardDuty \u2014 account compromise, instance compromise, and reconnaissance.",
8522
- inspector_findings: "Aggregates vulnerability findings from Amazon Inspector \u2014 CVEs in EC2, Lambda, and container images.",
8930
+ guardduty_findings: "Checks if GuardDuty is enabled. Findings are aggregated via Security Hub.",
8931
+ inspector_findings: "Checks if Inspector is enabled. Findings are aggregated via Security Hub.",
8523
8932
  trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
8524
- config_rules_findings: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
8525
- access_analyzer_findings: "Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account (external principals, public access).",
8933
+ config_rules_findings: "Checks if AWS Config Rules are configured. Findings are aggregated via Security Hub.",
8934
+ access_analyzer_findings: "Checks if IAM Access Analyzer is configured. Findings are aggregated via Security Hub.",
8526
8935
  patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches.",
8527
8936
  imdsv2_enforcement: "Checks if EC2 instances enforce IMDSv2 (HttpTokens: required) \u2014 IMDSv1 allows credential theft via SSRF.",
8528
8937
  waf_coverage: "Checks if internet-facing ALBs have WAF Web ACL associated for protection against common web exploits."
8529
8938
  };
8530
- var HW_DEFENSE_CHECKLIST = `
8531
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8532
- \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
8533
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8534
-
8535
- \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
8536
-
8537
- \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
8538
- \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
8539
- \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
8540
- \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
8541
- \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
8542
-
8543
- \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
8544
- \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
8545
- \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
8546
- \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
8547
-
8548
- \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
8549
- \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
8550
- \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
8551
- \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
8552
- \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
8553
- \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
8554
-
8555
- \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
8556
- \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
8557
- \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
8558
- \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
8559
-
8560
- \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
8561
- \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
8562
- \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
8563
- \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
8564
-
8565
- \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
8566
- \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
8567
- \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
8568
- \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A"\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0"
8569
-
8570
- \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
8571
- \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
8572
- \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
8573
- \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
8574
- \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
8575
-
8576
- \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
8577
- \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
8578
- \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
8579
- \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
8580
-
8581
- \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
8582
- `;
8583
- var SERVICE_RECOMMENDATIONS2 = {
8584
- security_hub_findings: {
8585
- icon: "\u{1F534}",
8586
- service: "Security Hub",
8587
- impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
8588
- action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
8589
- },
8590
- guardduty_findings: {
8591
- icon: "\u{1F534}",
8592
- service: "GuardDuty",
8593
- 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",
8594
- action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
8595
- },
8596
- inspector_findings: {
8597
- icon: "\u{1F7E1}",
8598
- service: "Inspector",
8599
- impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
8600
- action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
8601
- },
8602
- trusted_advisor_findings: {
8603
- icon: "\u{1F7E1}",
8604
- service: "Trusted Advisor",
8605
- impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
8606
- action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
8607
- },
8608
- config_rules_findings: {
8609
- icon: "\u{1F7E1}",
8610
- service: "AWS Config",
8611
- impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
8612
- action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
8613
- },
8614
- access_analyzer_findings: {
8615
- icon: "\u{1F7E1}",
8616
- service: "IAM Access Analyzer",
8617
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
8618
- action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
8619
- },
8620
- patch_compliance_findings: {
8621
- icon: "\u{1F7E1}",
8622
- service: "SSM Patch Manager",
8623
- impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
8624
- action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
8625
- }
8626
- };
8939
+ function getHwDefenseChecklist(lang) {
8940
+ return getI18n(lang ?? "zh").hwChecklist;
8941
+ }
8627
8942
  var SERVICE_NOT_ENABLED_PATTERNS2 = [
8628
8943
  "not enabled",
8629
8944
  "not found",
@@ -8633,10 +8948,11 @@ var SERVICE_NOT_ENABLED_PATTERNS2 = [
8633
8948
  "not available",
8634
8949
  "is not enabled"
8635
8950
  ];
8636
- function buildServiceReminder(modules) {
8951
+ function buildServiceReminder(modules, lang) {
8952
+ const t = getI18n(lang ?? "zh");
8637
8953
  const disabledServices = [];
8638
8954
  for (const mod of modules) {
8639
- const rec = SERVICE_RECOMMENDATIONS2[mod.module];
8955
+ const rec = t.serviceRecommendations[mod.module];
8640
8956
  if (!rec) continue;
8641
8957
  if (!mod.warnings?.length) continue;
8642
8958
  const hasNotEnabled = mod.warnings.some(
@@ -8649,26 +8965,26 @@ function buildServiceReminder(modules) {
8649
8965
  if (disabledServices.length === 0) return "";
8650
8966
  const lines = [
8651
8967
  "",
8652
- "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
8968
+ t.serviceReminderTitle,
8653
8969
  ""
8654
8970
  ];
8655
8971
  for (const svc of disabledServices) {
8656
- lines.push(`${svc.icon} ${svc.service} \u672A\u542F\u7528`);
8657
- lines.push(` \u5F71\u54CD\uFF1A${svc.impact}`);
8658
- lines.push(` \u5EFA\u8BAE\uFF1A${svc.action}`);
8972
+ lines.push(`${svc.icon} ${svc.service} ${t.notEnabled}`);
8973
+ lines.push(` ${t.serviceImpact}: ${svc.impact}`);
8974
+ lines.push(` ${t.serviceAction}: ${svc.action}`);
8659
8975
  lines.push("");
8660
8976
  }
8661
- lines.push("\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002");
8977
+ lines.push(t.serviceReminderFooter);
8662
8978
  return lines.join("\n");
8663
8979
  }
8664
- function summarizeResult(result) {
8980
+ function summarizeResult(result, lang) {
8665
8981
  const { summary } = result;
8666
8982
  const lines = [
8667
8983
  `Scan complete for account ${result.accountId} in ${result.region}.`,
8668
8984
  `Total findings: ${summary.totalFindings} (${summary.critical} Critical, ${summary.high} High, ${summary.medium} Medium, ${summary.low} Low)`,
8669
8985
  `Modules: ${summary.modulesSuccess} succeeded, ${summary.modulesError} errored`
8670
8986
  ];
8671
- const reminder = buildServiceReminder(result.modules);
8987
+ const reminder = buildServiceReminder(result.modules, lang);
8672
8988
  if (reminder) {
8673
8989
  lines.push(reminder);
8674
8990
  }
@@ -8730,9 +9046,10 @@ function createServer(defaultRegion) {
8730
9046
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8731
9047
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8732
9048
  role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
8733
- account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)")
9049
+ account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
9050
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8734
9051
  },
8735
- async ({ region, org_mode, role_name, account_ids }) => {
9052
+ async ({ region, org_mode, role_name, account_ids, lang }) => {
8736
9053
  try {
8737
9054
  const r = region ?? defaultRegion;
8738
9055
  let result;
@@ -8747,7 +9064,7 @@ function createServer(defaultRegion) {
8747
9064
  }
8748
9065
  return {
8749
9066
  content: [
8750
- { type: "text", text: summarizeResult(result) },
9067
+ { type: "text", text: summarizeResult(result, lang ?? "zh") },
8751
9068
  { type: "text", text: JSON.stringify(result, null, 2) }
8752
9069
  ]
8753
9070
  };
@@ -8808,9 +9125,10 @@ function createServer(defaultRegion) {
8808
9125
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8809
9126
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8810
9127
  role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
8811
- account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)")
9128
+ account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
9129
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8812
9130
  },
8813
- async ({ group, region, org_mode, role_name, account_ids }) => {
9131
+ async ({ group, region, org_mode, role_name, account_ids, lang }) => {
8814
9132
  try {
8815
9133
  const groupDef = SCAN_GROUPS[group];
8816
9134
  if (!groupDef) {
@@ -8892,7 +9210,7 @@ function createServer(defaultRegion) {
8892
9210
  `Scan group: ${groupDef.name} (${group})`,
8893
9211
  groupDef.description,
8894
9212
  "",
8895
- summarizeResult(result)
9213
+ summarizeResult(result, lang ?? "zh")
8896
9214
  ];
8897
9215
  if (missingModules.length > 0) {
8898
9216
  lines.push("");
@@ -8905,7 +9223,7 @@ function createServer(defaultRegion) {
8905
9223
  if (group === "hw_defense") {
8906
9224
  const summaryContent = content[0];
8907
9225
  if (summaryContent && summaryContent.type === "text") {
8908
- summaryContent.text += "\n\n" + HW_DEFENSE_CHECKLIST;
9226
+ summaryContent.text += "\n\n" + getHwDefenseChecklist(lang ?? "zh");
8909
9227
  }
8910
9228
  }
8911
9229
  return { content };
@@ -8935,11 +9253,14 @@ function createServer(defaultRegion) {
8935
9253
  server.tool(
8936
9254
  "generate_report",
8937
9255
  "Generate a Markdown security report from scan results. Read-only. Does not modify any AWS resources.",
8938
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
8939
- async ({ scan_results }) => {
9256
+ {
9257
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9258
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9259
+ },
9260
+ async ({ scan_results, lang }) => {
8940
9261
  try {
8941
9262
  const parsed = JSON.parse(scan_results);
8942
- const report = generateMarkdownReport(parsed);
9263
+ const report = generateMarkdownReport(parsed, lang ?? "zh");
8943
9264
  return { content: [{ type: "text", text: report }] };
8944
9265
  } catch (err) {
8945
9266
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8949,11 +9270,14 @@ function createServer(defaultRegion) {
8949
9270
  server.tool(
8950
9271
  "generate_mlps3_report",
8951
9272
  "Generate a GB/T 22239-2019 \u7B49\u4FDD\u4E09\u7EA7 compliance pre-check report from scan results. Best used with scan_group mlps3_precheck results. Read-only.",
8952
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all") },
8953
- async ({ scan_results }) => {
9273
+ {
9274
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
9275
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9276
+ },
9277
+ async ({ scan_results, lang }) => {
8954
9278
  try {
8955
9279
  const parsed = JSON.parse(scan_results);
8956
- const report = generateMlps3Report(parsed);
9280
+ const report = generateMlps3Report(parsed, lang ?? "zh");
8957
9281
  return { content: [{ type: "text", text: report }] };
8958
9282
  } catch (err) {
8959
9283
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8965,13 +9289,14 @@ function createServer(defaultRegion) {
8965
9289
  "Generate a professional HTML security report. Save the output as an .html file.",
8966
9290
  {
8967
9291
  scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
8968
- history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts")
9292
+ history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
9293
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8969
9294
  },
8970
- async ({ scan_results, history }) => {
9295
+ async ({ scan_results, history, lang }) => {
8971
9296
  try {
8972
9297
  const parsed = JSON.parse(scan_results);
8973
9298
  const historyData = history ? JSON.parse(history) : void 0;
8974
- const report = generateHtmlReport(parsed, historyData);
9299
+ const report = generateHtmlReport(parsed, historyData, lang ?? "zh");
8975
9300
  return { content: [{ type: "text", text: report }] };
8976
9301
  } catch (err) {
8977
9302
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8983,13 +9308,14 @@ function createServer(defaultRegion) {
8983
9308
  "Generate a professional HTML MLPS Level 3 compliance report (\u7B49\u4FDD\u4E09\u7EA7). Save as .html file.",
8984
9309
  {
8985
9310
  scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
8986
- history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts")
9311
+ history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
9312
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8987
9313
  },
8988
- async ({ scan_results, history }) => {
9314
+ async ({ scan_results, history, lang }) => {
8989
9315
  try {
8990
9316
  const parsed = JSON.parse(scan_results);
8991
9317
  const historyData = history ? JSON.parse(history) : void 0;
8992
- const report = generateMlps3HtmlReport(parsed, historyData);
9318
+ const report = generateMlps3HtmlReport(parsed, historyData, lang ?? "zh");
8993
9319
  return { content: [{ type: "text", text: report }] };
8994
9320
  } catch (err) {
8995
9321
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8999,7 +9325,10 @@ function createServer(defaultRegion) {
8999
9325
  server.tool(
9000
9326
  "generate_maturity_report",
9001
9327
  "Generate a security maturity assessment report from scan_all results. Requires service_detection module output. Read-only.",
9002
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
9328
+ {
9329
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
9330
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
9331
+ },
9003
9332
  async ({ scan_results }) => {
9004
9333
  try {
9005
9334
  const parsed = JSON.parse(scan_results);
@@ -9270,7 +9599,7 @@ ${finding}`
9270
9599
  type: "text",
9271
9600
  text: `\u8BF7\u57FA\u4E8E\u4EE5\u4E0B\u62A4\u7F51\u884C\u52A8\u68C0\u67E5\u6E05\u5355\uFF0C\u5E2E\u52A9\u6211\u5236\u5B9A\u62A4\u7F51\u51C6\u5907\u8BA1\u5212\uFF1A
9272
9601
 
9273
- ${HW_DEFENSE_CHECKLIST}
9602
+ ${getHwDefenseChecklist("zh")}
9274
9603
 
9275
9604
  \u81EA\u52A8\u5316\u626B\u63CF\u90E8\u5206\u8BF7\u4F7F\u7528 scan_group hw_defense \u6267\u884C\u3002\u4EE5\u4E0A\u4EBA\u5DE5\u68C0\u67E5\u9879\u8BF7\u9010\u9879\u786E\u8BA4\u5E76\u63D0\u4F9B\u5177\u4F53\u5EFA\u8BAE\u3002`
9276
9605
  }