aws-security-mcp 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { z } from "zod";
5
5
 
6
6
  // src/version.ts
7
- var VERSION = "0.5.3";
7
+ var VERSION = "0.6.1";
8
8
 
9
9
  // src/utils/aws-client.ts
10
10
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -88,7 +88,9 @@ async function listOrgAccounts(region) {
88
88
  var AGGREGATION_MODULES = /* @__PURE__ */ new Set([
89
89
  "security_hub_findings",
90
90
  "guardduty_findings",
91
- "inspector_findings"
91
+ "inspector_findings",
92
+ "config_rules_findings",
93
+ "access_analyzer_findings"
92
94
  ]);
93
95
  function buildSummary(modules) {
94
96
  let critical = 0;
@@ -480,9 +482,15 @@ var ServiceDetectionScanner = class {
480
482
  const insp = createClient(Inspector2Client, region, ctx.credentials);
481
483
  const resp = await insp.send(new BatchGetAccountStatusCommand({ accountIds: [accountId] }));
482
484
  const accounts = resp.accounts ?? [];
483
- const active = accounts.some(
484
- (a) => a.state?.status === "ENABLED" || a.state?.status === "ENABLING"
485
- );
485
+ const active = accounts.some((a) => {
486
+ const s = a.state?.status;
487
+ if (s === "ENABLED" || s === "ENABLING") return true;
488
+ const rs = a.resourceState;
489
+ if (!rs) return false;
490
+ return ["ec2", "ecr", "lambda", "lambdaCode", "codeRepository"].some(
491
+ (k) => rs[k]?.status === "ENABLED"
492
+ );
493
+ });
486
494
  if (active) {
487
495
  services.push({
488
496
  name: "Inspector",
@@ -2735,14 +2743,21 @@ var SecurityHubFindingsScanner = class {
2735
2743
  const resourceType = f.Resources?.[0]?.Type ?? "AWS::Unknown";
2736
2744
  const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:securityhub:${region}:${accountId}:finding/${f.Id ?? "unknown"}`;
2737
2745
  const remediationSteps = [];
2738
- if (f.Remediation?.Recommendation?.Text) {
2739
- remediationSteps.push(f.Remediation.Recommendation.Text);
2746
+ const title = f.Title ?? "Security Hub Finding";
2747
+ if (/^KB\d+$/.test(title)) {
2748
+ remediationSteps.push(`Install Windows patch ${title} via WSUS or SSM Patch Manager`);
2749
+ remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${title}`);
2750
+ } else if (/^CVE-/.test(title)) {
2751
+ remediationSteps.push(`Fix vulnerability ${title}: update affected software to patched version`);
2752
+ } else {
2753
+ remediationSteps.push(title);
2740
2754
  }
2741
2755
  if (f.Remediation?.Recommendation?.Url) {
2742
- remediationSteps.push(`Reference: ${f.Remediation.Recommendation.Url}`);
2756
+ remediationSteps.push(`Documentation: ${f.Remediation.Recommendation.Url}`);
2743
2757
  }
2744
- if (remediationSteps.length === 0) {
2745
- remediationSteps.push("Review the finding in the AWS Security Hub console and follow the recommended remediation.");
2758
+ const recText = f.Remediation?.Recommendation?.Text ?? "";
2759
+ if (recText && !["See References", "None Provided", ""].includes(recText.trim())) {
2760
+ remediationSteps.push(recText);
2746
2761
  }
2747
2762
  findings.push({
2748
2763
  severity,
@@ -2803,125 +2818,37 @@ var SecurityHubFindingsScanner = class {
2803
2818
  // src/scanners/guardduty-findings.ts
2804
2819
  import {
2805
2820
  GuardDutyClient as GuardDutyClient2,
2806
- ListDetectorsCommand as ListDetectorsCommand2,
2807
- ListFindingsCommand,
2808
- GetFindingsCommand as GetFindingsCommand2
2821
+ ListDetectorsCommand as ListDetectorsCommand2
2809
2822
  } from "@aws-sdk/client-guardduty";
2810
- function gdSeverityToScore(severity) {
2811
- if (severity >= 7) return 8;
2812
- if (severity >= 4) return 5.5;
2813
- return 3;
2814
- }
2815
2823
  var GuardDutyFindingsScanner = class {
2816
2824
  moduleName = "guardduty_findings";
2817
2825
  async scan(ctx) {
2818
- const { region, partition, accountId } = ctx;
2826
+ const { region } = ctx;
2819
2827
  const startMs = Date.now();
2820
- const findings = [];
2821
2828
  const warnings = [];
2822
- let resourcesScanned = 0;
2823
2829
  try {
2824
2830
  const client = createClient(GuardDutyClient2, region, ctx.credentials);
2825
- const detectorsResp = await client.send(new ListDetectorsCommand2({}));
2826
- const detectorIds = detectorsResp.DetectorIds ?? [];
2831
+ const resp = await client.send(new ListDetectorsCommand2({}));
2832
+ const detectorIds = resp.DetectorIds ?? [];
2827
2833
  if (detectorIds.length === 0) {
2828
2834
  warnings.push("GuardDuty is not enabled in this region (no detectors found).");
2829
- return {
2830
- module: this.moduleName,
2831
- status: "success",
2832
- warnings,
2833
- resourcesScanned: 0,
2834
- findingsCount: 0,
2835
- scanTimeMs: Date.now() - startMs,
2836
- findings: []
2837
- };
2838
- }
2839
- const detectorId = detectorIds[0];
2840
- let nextToken;
2841
- const findingIds = [];
2842
- do {
2843
- const listResp = await client.send(
2844
- new ListFindingsCommand({
2845
- DetectorId: detectorId,
2846
- FindingCriteria: {
2847
- Criterion: {
2848
- "service.archived": {
2849
- Eq: ["false"]
2850
- }
2851
- }
2852
- },
2853
- MaxResults: 50,
2854
- NextToken: nextToken
2855
- })
2856
- );
2857
- findingIds.push(...listResp.FindingIds ?? []);
2858
- nextToken = listResp.NextToken;
2859
- } while (nextToken);
2860
- resourcesScanned = findingIds.length;
2861
- if (findingIds.length === 0) {
2862
- return {
2863
- module: this.moduleName,
2864
- status: "success",
2865
- warnings: warnings.length > 0 ? warnings : void 0,
2866
- resourcesScanned: 0,
2867
- findingsCount: 0,
2868
- scanTimeMs: Date.now() - startMs,
2869
- findings: []
2870
- };
2871
- }
2872
- for (let i = 0; i < findingIds.length; i += 50) {
2873
- const batch = findingIds.slice(i, i + 50);
2874
- const detailsResp = await client.send(
2875
- new GetFindingsCommand2({
2876
- DetectorId: detectorId,
2877
- FindingIds: batch
2878
- })
2879
- );
2880
- for (const gdf of detailsResp.Findings ?? []) {
2881
- const gdSeverity = gdf.Severity ?? 0;
2882
- const score = gdSeverityToScore(gdSeverity);
2883
- const severity = severityFromScore(score);
2884
- const resourceType = gdf.Resource?.ResourceType ?? "AWS::Unknown";
2885
- const resourceId = gdf.Resource?.InstanceDetails?.InstanceId ?? gdf.Resource?.AccessKeyDetails?.AccessKeyId ?? gdf.Arn ?? "unknown";
2886
- const resourceArn = gdf.Arn ?? `arn:${partition}:guardduty:${region}:${accountId}:detector/${detectorId}/finding/${gdf.Id ?? "unknown"}`;
2887
- findings.push({
2888
- severity,
2889
- title: `[GuardDuty] ${gdf.Title ?? gdf.Type ?? "Finding"}`,
2890
- resourceType,
2891
- resourceId,
2892
- resourceArn,
2893
- region: gdf.Region ?? region,
2894
- description: gdf.Description ?? gdf.Title ?? "No description",
2895
- impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
2896
- riskScore: score,
2897
- remediationSteps: [
2898
- "Review the finding in the Amazon GuardDuty console.",
2899
- `Finding type: ${gdf.Type ?? "unknown"}`,
2900
- "Follow the recommended remediation in the GuardDuty documentation."
2901
- ],
2902
- priority: priorityFromSeverity(severity),
2903
- module: this.moduleName,
2904
- accountId: gdf.AccountId ?? accountId
2905
- });
2906
- }
2907
2835
  }
2908
2836
  return {
2909
2837
  module: this.moduleName,
2910
2838
  status: "success",
2911
2839
  warnings: warnings.length > 0 ? warnings : void 0,
2912
- resourcesScanned,
2913
- findingsCount: findings.length,
2840
+ resourcesScanned: 0,
2841
+ findingsCount: 0,
2914
2842
  scanTimeMs: Date.now() - startMs,
2915
- findings
2843
+ findings: []
2916
2844
  };
2917
2845
  } catch (err) {
2918
2846
  const msg = err instanceof Error ? err.message : String(err);
2919
2847
  return {
2920
2848
  module: this.moduleName,
2921
2849
  status: "error",
2922
- error: `GuardDuty findings scan failed: ${msg}`,
2923
- warnings: warnings.length > 0 ? warnings : void 0,
2924
- resourcesScanned,
2850
+ error: `GuardDuty detection check failed: ${msg}`,
2851
+ resourcesScanned: 0,
2925
2852
  findingsCount: 0,
2926
2853
  scanTimeMs: Date.now() - startMs,
2927
2854
  findings: []
@@ -2933,138 +2860,59 @@ var GuardDutyFindingsScanner = class {
2933
2860
  // src/scanners/inspector-findings.ts
2934
2861
  import {
2935
2862
  Inspector2Client as Inspector2Client2,
2936
- ListFindingsCommand as ListFindingsCommand2
2863
+ BatchGetAccountStatusCommand as BatchGetAccountStatusCommand2
2937
2864
  } from "@aws-sdk/client-inspector2";
2938
- function inspectorSeverityToScore(label) {
2939
- switch (label) {
2940
- case "CRITICAL":
2941
- return 9.5;
2942
- case "HIGH":
2943
- return 8;
2944
- case "MEDIUM":
2945
- return 5.5;
2946
- case "LOW":
2947
- return 3;
2948
- case "INFORMATIONAL":
2949
- return null;
2950
- case "UNTRIAGED":
2951
- return 5.5;
2952
- default:
2953
- return null;
2954
- }
2955
- }
2956
2865
  var InspectorFindingsScanner = class {
2957
2866
  moduleName = "inspector_findings";
2958
2867
  async scan(ctx) {
2959
- const { region, partition, accountId } = ctx;
2868
+ const { region } = ctx;
2960
2869
  const startMs = Date.now();
2961
- const findings = [];
2962
2870
  const warnings = [];
2963
- let resourcesScanned = 0;
2964
2871
  try {
2965
2872
  const client = createClient(Inspector2Client2, region, ctx.credentials);
2966
- let nextToken;
2967
- const filterCriteria = {
2968
- findingStatus: [{ comparison: "EQUALS", value: "ACTIVE" }]
2969
- };
2970
- do {
2971
- const resp = await client.send(
2972
- new ListFindingsCommand2({
2973
- filterCriteria,
2974
- maxResults: 100,
2975
- nextToken
2976
- })
2977
- );
2978
- const inspFindings = resp.findings ?? [];
2979
- resourcesScanned += inspFindings.length;
2980
- for (const f of inspFindings) {
2981
- const severityLabel = f.severity ?? "INFORMATIONAL";
2982
- const score = inspectorSeverityToScore(severityLabel);
2983
- if (score === null) continue;
2984
- const severity = severityFromScore(score);
2985
- const cveId = f.packageVulnerabilityDetails?.vulnerabilityId;
2986
- const titleBase = f.title ?? "Inspector Finding";
2987
- const title = cveId ? `[${cveId}] ${titleBase}` : titleBase;
2988
- const resourceId = f.resources?.[0]?.id ?? "unknown";
2989
- const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
2990
- const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
2991
- const remediationSteps = [];
2992
- if (f.remediation?.recommendation?.text) {
2993
- remediationSteps.push(f.remediation.recommendation.text);
2994
- }
2995
- if (f.remediation?.recommendation?.Url) {
2996
- remediationSteps.push(`Reference: ${f.remediation.recommendation.Url}`);
2997
- }
2998
- if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
2999
- remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
3000
- }
3001
- if (remediationSteps.length === 0) {
3002
- remediationSteps.push("Review the finding in the Amazon Inspector console and apply the recommended patch or update.");
3003
- }
3004
- const description = f.description ?? titleBase;
3005
- const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
3006
- findings.push({
3007
- severity,
3008
- title,
3009
- resourceType,
3010
- resourceId,
3011
- resourceArn,
3012
- region,
3013
- description,
3014
- impact,
3015
- riskScore: score,
3016
- remediationSteps,
3017
- priority: priorityFromSeverity(severity),
3018
- module: this.moduleName,
3019
- accountId: f.awsAccountId ?? accountId
3020
- });
2873
+ const resp = await client.send(new BatchGetAccountStatusCommand2({ accountIds: [] }));
2874
+ const account = resp.accounts?.[0];
2875
+ if (!account || account.state?.status !== "ENABLED") {
2876
+ warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
2877
+ } else {
2878
+ const rs = account.resourceState;
2879
+ const types = [
2880
+ { name: "EC2", status: rs?.ec2?.status },
2881
+ { name: "Lambda", status: rs?.lambda?.status },
2882
+ { name: "ECR", status: rs?.ecr?.status },
2883
+ { name: "Lambda Code", status: rs?.lambdaCode?.status },
2884
+ { name: "Code Repository", status: rs?.codeRepository?.status }
2885
+ ];
2886
+ const disabled = types.filter((t) => t.status && t.status !== "ENABLED");
2887
+ if (disabled.length > 0) {
2888
+ warnings.push(
2889
+ `Inspector scan types not enabled: ${disabled.map((t) => t.name).join(", ")}. Enable them for full vulnerability coverage.`
2890
+ );
3021
2891
  }
3022
- nextToken = resp.nextToken;
3023
- } while (nextToken);
2892
+ }
3024
2893
  return {
3025
2894
  module: this.moduleName,
3026
2895
  status: "success",
3027
2896
  warnings: warnings.length > 0 ? warnings : void 0,
3028
- resourcesScanned,
3029
- findingsCount: findings.length,
2897
+ resourcesScanned: 0,
2898
+ findingsCount: 0,
3030
2899
  scanTimeMs: Date.now() - startMs,
3031
- findings
2900
+ findings: []
3032
2901
  };
3033
2902
  } catch (err) {
3034
2903
  const msg = err instanceof Error ? err.message : String(err);
3035
2904
  const errName = err instanceof Error ? err.name : "";
3036
2905
  const isAccessDenied2 = errName === "AccessDeniedException" || msg.includes("AccessDeniedException");
3037
- const isNotEnabled2 = msg.includes("not enabled") || msg.includes("not subscribed");
3038
2906
  if (isAccessDenied2) {
3039
- warnings.push("Insufficient permissions to access Inspector. Grant inspector2:ListFindings to scan for vulnerabilities.");
3040
- return {
3041
- module: this.moduleName,
3042
- status: "success",
3043
- warnings,
3044
- resourcesScanned: 0,
3045
- findingsCount: 0,
3046
- scanTimeMs: Date.now() - startMs,
3047
- findings: []
3048
- };
3049
- }
3050
- if (isNotEnabled2) {
2907
+ warnings.push("Insufficient permissions to access Inspector. Grant inspector2:BatchGetAccountStatus to check enablement.");
2908
+ } else {
3051
2909
  warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
3052
- return {
3053
- module: this.moduleName,
3054
- status: "success",
3055
- warnings,
3056
- resourcesScanned: 0,
3057
- findingsCount: 0,
3058
- scanTimeMs: Date.now() - startMs,
3059
- findings: []
3060
- };
3061
2910
  }
3062
2911
  return {
3063
2912
  module: this.moduleName,
3064
- status: "error",
3065
- error: `Inspector findings scan failed: ${msg}`,
3066
- warnings: warnings.length > 0 ? warnings : void 0,
3067
- resourcesScanned,
2913
+ status: "success",
2914
+ warnings,
2915
+ resourcesScanned: 0,
3068
2916
  findingsCount: 0,
3069
2917
  scanTimeMs: Date.now() - startMs,
3070
2918
  findings: []
@@ -3237,128 +3085,29 @@ var TrustedAdvisorFindingsScanner = class {
3237
3085
  // src/scanners/config-rules-findings.ts
3238
3086
  import {
3239
3087
  ConfigServiceClient as ConfigServiceClient2,
3240
- DescribeComplianceByConfigRuleCommand,
3241
- GetComplianceDetailsByConfigRuleCommand
3088
+ DescribeConfigurationRecordersCommand as DescribeConfigurationRecordersCommand2
3242
3089
  } from "@aws-sdk/client-config-service";
3243
- var SECURITY_RULE_PATTERNS = [
3244
- "securitygroup",
3245
- "security-group",
3246
- "encryption",
3247
- "encrypted",
3248
- "public",
3249
- "unrestricted",
3250
- "mfa",
3251
- "password",
3252
- "access-key",
3253
- "root",
3254
- "admin",
3255
- "logging",
3256
- "cloudtrail",
3257
- "iam",
3258
- "kms",
3259
- "ssl",
3260
- "tls",
3261
- "vpc-flow",
3262
- "guardduty",
3263
- "securityhub"
3264
- ];
3265
- function ruleIsSecurityRelated(ruleName) {
3266
- const lower = ruleName.toLowerCase();
3267
- return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
3268
- }
3269
3090
  var ConfigRulesFindingsScanner = class {
3270
3091
  moduleName = "config_rules_findings";
3271
3092
  async scan(ctx) {
3272
- const { region, partition, accountId } = ctx;
3093
+ const { region } = ctx;
3273
3094
  const startMs = Date.now();
3274
- const findings = [];
3275
3095
  const warnings = [];
3276
- let resourcesScanned = 0;
3277
3096
  try {
3278
3097
  const client = createClient(ConfigServiceClient2, region, ctx.credentials);
3279
- let nextToken;
3280
- const nonCompliantRules = [];
3281
- do {
3282
- const resp = await client.send(
3283
- new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
3284
- );
3285
- for (const rule of resp.ComplianceByConfigRules ?? []) {
3286
- resourcesScanned++;
3287
- if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
3288
- nonCompliantRules.push(rule);
3289
- }
3290
- }
3291
- nextToken = resp.NextToken;
3292
- } while (nextToken);
3293
- if (resourcesScanned === 0) {
3294
- warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
3295
- return {
3296
- module: this.moduleName,
3297
- status: "success",
3298
- warnings,
3299
- resourcesScanned: 0,
3300
- findingsCount: 0,
3301
- scanTimeMs: Date.now() - startMs,
3302
- findings: []
3303
- };
3304
- }
3305
- for (const rule of nonCompliantRules) {
3306
- const ruleName = rule.ConfigRuleName ?? "unknown";
3307
- try {
3308
- let detailToken;
3309
- do {
3310
- const detailResp = await client.send(
3311
- new GetComplianceDetailsByConfigRuleCommand({
3312
- ConfigRuleName: ruleName,
3313
- ComplianceTypes: ["NON_COMPLIANT"],
3314
- NextToken: detailToken
3315
- })
3316
- );
3317
- for (const evalResult of detailResp.EvaluationResults ?? []) {
3318
- const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
3319
- const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
3320
- const resourceId = qualifier?.ResourceId ?? "unknown";
3321
- const annotation = evalResult.Annotation;
3322
- const isSecurityRule = ruleIsSecurityRelated(ruleName);
3323
- const riskScore = isSecurityRule ? 7.5 : 5.5;
3324
- const severity = severityFromScore(riskScore);
3325
- const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
3326
- if (annotation) descParts.push(`Annotation: ${annotation}`);
3327
- findings.push({
3328
- severity,
3329
- title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
3330
- resourceType,
3331
- resourceId,
3332
- resourceArn: resourceId,
3333
- region,
3334
- description: descParts.join(". "),
3335
- impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
3336
- riskScore,
3337
- remediationSteps: [
3338
- `Review the Config Rule "${ruleName}" in the AWS Config console.`,
3339
- `Check resource ${resourceId} for compliance violations.`,
3340
- "Follow the rule's remediation guidance to bring the resource into compliance."
3341
- ],
3342
- priority: priorityFromSeverity(severity),
3343
- module: this.moduleName,
3344
- accountId
3345
- });
3346
- }
3347
- detailToken = detailResp.NextToken;
3348
- } while (detailToken);
3349
- } catch (detailErr) {
3350
- const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
3351
- warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
3352
- }
3098
+ const resp = await client.send(new DescribeConfigurationRecordersCommand2({}));
3099
+ const recorders = resp.ConfigurationRecorders ?? [];
3100
+ if (recorders.length === 0) {
3101
+ warnings.push("AWS Config is not enabled in this region.");
3353
3102
  }
3354
3103
  return {
3355
3104
  module: this.moduleName,
3356
3105
  status: "success",
3357
3106
  warnings: warnings.length > 0 ? warnings : void 0,
3358
- resourcesScanned,
3359
- findingsCount: findings.length,
3107
+ resourcesScanned: 0,
3108
+ findingsCount: 0,
3360
3109
  scanTimeMs: Date.now() - startMs,
3361
- findings
3110
+ findings: []
3362
3111
  };
3363
3112
  } catch (err) {
3364
3113
  const msg = err instanceof Error ? err.message : String(err);
@@ -3377,9 +3126,8 @@ var ConfigRulesFindingsScanner = class {
3377
3126
  return {
3378
3127
  module: this.moduleName,
3379
3128
  status: "error",
3380
- error: `Config Rules scan failed: ${msg}`,
3381
- warnings: warnings.length > 0 ? warnings : void 0,
3382
- resourcesScanned,
3129
+ error: `Config Rules detection check failed: ${msg}`,
3130
+ resourcesScanned: 0,
3383
3131
  findingsCount: 0,
3384
3132
  scanTimeMs: Date.now() - startMs,
3385
3133
  findings: []
@@ -3391,146 +3139,50 @@ var ConfigRulesFindingsScanner = class {
3391
3139
  // src/scanners/access-analyzer-findings.ts
3392
3140
  import {
3393
3141
  AccessAnalyzerClient,
3394
- ListAnalyzersCommand,
3395
- ListFindingsV2Command
3142
+ ListAnalyzersCommand
3396
3143
  } from "@aws-sdk/client-accessanalyzer";
3397
- function findingTypeToScore(findingType) {
3398
- const ft = findingType;
3399
- switch (ft) {
3400
- case "ExternalAccess":
3401
- return 8;
3402
- case "UnusedIAMRole":
3403
- case "UnusedIAMUserAccessKey":
3404
- case "UnusedIAMUserPassword":
3405
- return 5.5;
3406
- case "UnusedPermission":
3407
- return 3;
3408
- default:
3409
- return 5.5;
3410
- }
3411
- }
3412
- var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
3413
- "UnusedIAMRole",
3414
- "UnusedIAMUserAccessKey",
3415
- "UnusedIAMUserPassword",
3416
- "UnusedPermission"
3417
- ]);
3418
- function isSecurityRelevant(findingType) {
3419
- const ft = findingType;
3420
- return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
3421
- }
3422
- function isExternalAccess(findingType) {
3423
- return findingType === "ExternalAccess";
3424
- }
3425
3144
  var AccessAnalyzerFindingsScanner = class {
3426
3145
  moduleName = "access_analyzer_findings";
3427
3146
  async scan(ctx) {
3428
- const { region, partition, accountId } = ctx;
3147
+ const { region } = ctx;
3429
3148
  const startMs = Date.now();
3430
- const findings = [];
3431
3149
  const warnings = [];
3432
- let resourcesScanned = 0;
3433
3150
  try {
3434
3151
  const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
3435
3152
  let analyzerToken;
3436
- const analyzers = [];
3153
+ let hasActiveAnalyzer = false;
3437
3154
  do {
3438
3155
  const resp = await client.send(
3439
3156
  new ListAnalyzersCommand({ nextToken: analyzerToken })
3440
3157
  );
3441
3158
  for (const analyzer of resp.analyzers ?? []) {
3442
3159
  if (analyzer.status === "ACTIVE") {
3443
- analyzers.push(analyzer);
3160
+ hasActiveAnalyzer = true;
3161
+ break;
3444
3162
  }
3445
3163
  }
3164
+ if (hasActiveAnalyzer) break;
3446
3165
  analyzerToken = resp.nextToken;
3447
3166
  } while (analyzerToken);
3448
- if (analyzers.length === 0) {
3167
+ if (!hasActiveAnalyzer) {
3449
3168
  warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
3450
- return {
3451
- module: this.moduleName,
3452
- status: "success",
3453
- warnings,
3454
- resourcesScanned: 0,
3455
- findingsCount: 0,
3456
- scanTimeMs: Date.now() - startMs,
3457
- findings: []
3458
- };
3459
- }
3460
- for (const analyzer of analyzers) {
3461
- const analyzerArn = analyzer.arn ?? "unknown";
3462
- let findingToken;
3463
- do {
3464
- const listResp = await client.send(
3465
- new ListFindingsV2Command({
3466
- analyzerArn,
3467
- filter: {
3468
- status: { eq: ["ACTIVE"] }
3469
- },
3470
- nextToken: findingToken
3471
- })
3472
- );
3473
- for (const aaf of listResp.findings ?? []) {
3474
- if (!isSecurityRelevant(aaf.findingType)) {
3475
- continue;
3476
- }
3477
- resourcesScanned++;
3478
- const score = findingTypeToScore(aaf.findingType);
3479
- const severity = severityFromScore(score);
3480
- const resourceArn = aaf.resource ?? "unknown";
3481
- const resourceType = aaf.resourceType ?? "AWS::Unknown";
3482
- const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
3483
- const external = isExternalAccess(aaf.findingType);
3484
- const descParts = [`Resource Type: ${resourceType}`];
3485
- if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
3486
- if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
3487
- const title = buildFindingTitle(aaf);
3488
- const impact = external ? `Resource is accessible from outside the account. Type: ${aaf.findingType ?? "unknown"}` : `Unused access detected \u2014 review and remove to follow least-privilege. Type: ${aaf.findingType ?? "unknown"}`;
3489
- const remediationSteps = external ? [
3490
- "Review the finding in the IAM Access Analyzer console.",
3491
- `Check resource ${resourceId} for unintended external access.`,
3492
- "Remove or restrict the resource policy to eliminate external access."
3493
- ] : [
3494
- "Review the finding in the IAM Access Analyzer console.",
3495
- `Check resource ${resourceId} for unused access permissions.`,
3496
- "Remove unused permissions, roles, or credentials to follow least-privilege."
3497
- ];
3498
- findings.push({
3499
- severity,
3500
- title,
3501
- resourceType: mapResourceType(resourceType),
3502
- resourceId,
3503
- resourceArn,
3504
- region,
3505
- description: descParts.join(". "),
3506
- impact,
3507
- riskScore: score,
3508
- remediationSteps,
3509
- priority: priorityFromSeverity(severity),
3510
- module: this.moduleName,
3511
- accountId: aaf.resourceOwnerAccount ?? accountId
3512
- });
3513
- }
3514
- findingToken = listResp.nextToken;
3515
- } while (findingToken);
3516
3169
  }
3517
3170
  return {
3518
3171
  module: this.moduleName,
3519
3172
  status: "success",
3520
3173
  warnings: warnings.length > 0 ? warnings : void 0,
3521
- resourcesScanned,
3522
- findingsCount: findings.length,
3174
+ resourcesScanned: 0,
3175
+ findingsCount: 0,
3523
3176
  scanTimeMs: Date.now() - startMs,
3524
- findings
3177
+ findings: []
3525
3178
  };
3526
3179
  } catch (err) {
3527
3180
  const msg = err instanceof Error ? err.message : String(err);
3528
3181
  return {
3529
3182
  module: this.moduleName,
3530
3183
  status: "error",
3531
- error: `Access Analyzer scan failed: ${msg}`,
3532
- warnings: warnings.length > 0 ? warnings : void 0,
3533
- resourcesScanned,
3184
+ error: `Access Analyzer detection check failed: ${msg}`,
3185
+ resourcesScanned: 0,
3534
3186
  findingsCount: 0,
3535
3187
  scanTimeMs: Date.now() - startMs,
3536
3188
  findings: []
@@ -3538,29 +3190,6 @@ var AccessAnalyzerFindingsScanner = class {
3538
3190
  }
3539
3191
  }
3540
3192
  };
3541
- function buildFindingTitle(finding) {
3542
- const resourceType = finding.resourceType ?? "Resource";
3543
- const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
3544
- const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
3545
- return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
3546
- }
3547
- function mapResourceType(aaType) {
3548
- const mapping = {
3549
- "AWS::S3::Bucket": "AWS::S3::Bucket",
3550
- "AWS::IAM::Role": "AWS::IAM::Role",
3551
- "AWS::SQS::Queue": "AWS::SQS::Queue",
3552
- "AWS::Lambda::Function": "AWS::Lambda::Function",
3553
- "AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
3554
- "AWS::KMS::Key": "AWS::KMS::Key",
3555
- "AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
3556
- "AWS::SNS::Topic": "AWS::SNS::Topic",
3557
- "AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
3558
- "AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
3559
- "AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
3560
- "AWS::ECR::Repository": "AWS::ECR::Repository"
3561
- };
3562
- return mapping[aaType] ?? aaType;
3563
- }
3564
3193
 
3565
3194
  // src/scanners/patch-compliance-findings.ts
3566
3195
  import {
@@ -3968,6 +3597,479 @@ var WafCoverageScanner = class {
3968
3597
  }
3969
3598
  };
3970
3599
 
3600
+ // src/i18n/zh.ts
3601
+ var zhI18n = {
3602
+ // HTML Security Report
3603
+ securityReportTitle: "AWS \u5B89\u5168\u626B\u63CF\u62A5\u544A",
3604
+ securityScore: "\u5B89\u5168\u8BC4\u5206",
3605
+ critical: "\u4E25\u91CD",
3606
+ high: "\u9AD8",
3607
+ medium: "\u4E2D",
3608
+ low: "\u4F4E",
3609
+ scanStatistics: "\u626B\u63CF\u7EDF\u8BA1",
3610
+ module: "\u6A21\u5757",
3611
+ resources: "\u8D44\u6E90",
3612
+ findings: "\u53D1\u73B0",
3613
+ status: "\u72B6\u6001",
3614
+ allFindings: "\u6240\u6709\u53D1\u73B0",
3615
+ recommendations: "\u5EFA\u8BAE",
3616
+ unique: "\u53BB\u91CD",
3617
+ showMore: "\u663E\u793A\u66F4\u591A",
3618
+ noIssuesFound: "\u672A\u53D1\u73B0\u5B89\u5168\u95EE\u9898\u3002",
3619
+ allModulesClean: "\u6240\u6709\u6A21\u5757\u6B63\u5E38",
3620
+ generatedBy: "\u7531 AWS Security MCP Server \u751F\u6210",
3621
+ informationalOnly: "\u672C\u62A5\u544A\u4EC5\u4F9B\u53C2\u8003\u3002",
3622
+ // MLPS Report
3623
+ mlpsTitle: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A",
3624
+ 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",
3625
+ checkedItems: "\u5DF2\u68C0\u67E5\u9879",
3626
+ noIssues: "\u672A\u53D1\u73B0\u95EE\u9898",
3627
+ issuesFound: "\u53D1\u73B0\u95EE\u9898",
3628
+ notChecked: "\u672A\u68C0\u67E5",
3629
+ cloudProvider: "\u4E91\u5E73\u53F0\u8D1F\u8D23",
3630
+ manualReview: "\u9700\u4EBA\u5DE5\u8BC4\u4F30",
3631
+ notApplicable: "\u4E0D\u9002\u7528",
3632
+ checkResult: "\u68C0\u67E5\u7ED3\u679C",
3633
+ noRelatedIssues: "\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898",
3634
+ issuesFoundCount: (n) => `\u68C0\u67E5\u7ED3\u679C\uFF1A\u53D1\u73B0 ${n} \u4E2A\u76F8\u5173\u95EE\u9898`,
3635
+ remediation: "\u5EFA\u8BAE",
3636
+ remediationItems: (n) => `\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${n} \u9879\u53BB\u91CD\uFF09`,
3637
+ showRemaining: (n) => `\u663E\u793A\u5176\u4F59 ${n} \u9879`,
3638
+ // HW Defense Checklist
3639
+ hwChecklistTitle: "\u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09",
3640
+ hwChecklistSubtitle: "\u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A",
3641
+ hwEmergencyIsolation: `\u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
3642
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
3643
+ \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
3644
+ \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
3645
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F`,
3646
+ hwTestEnvShutdown: `\u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
3647
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
3648
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
3649
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563`,
3650
+ hwDutyTeam: `\u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
3651
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
3652
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
3653
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
3654
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
3655
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09`,
3656
+ hwNetworkDiagram: `\u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
3657
+ \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
3658
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
3659
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3`,
3660
+ hwPentest: `\u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
3661
+ \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
3662
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
3663
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09`,
3664
+ hwWarRoom: `\u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
3665
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
3666
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
3667
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D`,
3668
+ hwCredentials: `\u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
3669
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
3670
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
3671
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
3672
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801`,
3673
+ hwPostOptimization: `\u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
3674
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
3675
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
3676
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669`,
3677
+ hwReference: "\u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)",
3678
+ // Service Reminders
3679
+ serviceReminderTitle: "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
3680
+ serviceReminderFooter: "\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002",
3681
+ serviceImpact: "\u5F71\u54CD",
3682
+ serviceAction: "\u5EFA\u8BAE",
3683
+ // Common
3684
+ account: "\u8D26\u6237",
3685
+ region: "\u533A\u57DF",
3686
+ scanTime: "\u626B\u63CF\u65F6\u95F4",
3687
+ duration: "\u8017\u65F6",
3688
+ severityDistribution: "\u4E25\u91CD\u6027\u5206\u5E03",
3689
+ findingsByModule: "\u6309\u6A21\u5757\u5206\u7C7B\u7684\u53D1\u73B0",
3690
+ details: "\u8BE6\u60C5",
3691
+ // Extended — HTML Security Report extras
3692
+ topHighestRiskFindings: (n) => `\u524D ${n} \u9879\u6700\u9AD8\u98CE\u9669\u53D1\u73B0`,
3693
+ resource: "\u8D44\u6E90",
3694
+ impact: "\u5F71\u54CD",
3695
+ riskScore: "\u98CE\u9669\u8BC4\u5206",
3696
+ showRemainingFindings: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u53D1\u73B0\u2026`,
3697
+ trendTitle: "30\u65E5\u8D8B\u52BF",
3698
+ findingsBySeverity: "\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0",
3699
+ showMoreCount: (n) => `\u663E\u793A\u5269\u4F59 ${n} \u9879\u2026`,
3700
+ // Extended — MLPS extras
3701
+ // Markdown report
3702
+ executiveSummary: "\u6267\u884C\u6458\u8981",
3703
+ totalFindingsLabel: "\u53D1\u73B0\u603B\u6570",
3704
+ description: "\u63CF\u8FF0",
3705
+ priority: "\u4F18\u5148\u7EA7",
3706
+ noFindingsForSeverity: (severity) => `\u65E0${severity}\u53D1\u73B0\u3002`,
3707
+ preCheckOverview: "\u9884\u68C0\u603B\u89C8",
3708
+ accountInfo: "\u8D26\u6237\u4FE1\u606F",
3709
+ checkedCount: (total, clean, issues) => `\u5DF2\u68C0\u67E5: ${total} \u9879\uFF08\u672A\u53D1\u73B0\u95EE\u9898: ${clean} \u9879 | \u53D1\u73B0\u95EE\u9898: ${issues} \u9879\uFF09`,
3710
+ uncheckedCount: (n) => `\u672A\u68C0\u67E5: ${n} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
3711
+ cloudProviderCount: (n) => `\u4E91\u5E73\u53F0\u8D1F\u8D23: ${n} \u9879`,
3712
+ manualReviewCount: (n) => `\u9700\u4EBA\u5DE5\u8BC4\u4F30: ${n} \u9879`,
3713
+ naCount: (n) => `\u4E0D\u9002\u7528: ${n} \u9879`,
3714
+ 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`,
3715
+ unknownNote: (n) => `\uFF08${n} \u9879\u672A\u68C0\u67E5\uFF0C\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`,
3716
+ 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`,
3717
+ mlpsFooterGenerated: (version) => `\u7531 AWS Security MCP Server v${version} \u751F\u6210`,
3718
+ 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",
3719
+ andMore: (n) => `... \u53CA\u5176\u4ED6 ${n} \u9879`,
3720
+ remediationByPriority: "\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09",
3721
+ affectedResources: (n) => `\u6D89\u53CA ${n} \u4E2A\u8D44\u6E90`,
3722
+ installWindowsPatches: (n, kbs) => `\u5B89\u88C5 ${n} \u4E2A Windows \u8865\u4E01 (${kbs})`,
3723
+ mlpsCategorySection: {
3724
+ "\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
3725
+ "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
3726
+ "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
3727
+ "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
3728
+ "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
3729
+ },
3730
+ // Service Recommendations
3731
+ notEnabled: "\u672A\u542F\u7528",
3732
+ serviceRecommendations: {
3733
+ security_hub_findings: {
3734
+ icon: "\u{1F534}",
3735
+ service: "Security Hub",
3736
+ impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
3737
+ action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
3738
+ },
3739
+ guardduty_findings: {
3740
+ icon: "\u{1F534}",
3741
+ service: "GuardDuty",
3742
+ 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",
3743
+ action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
3744
+ },
3745
+ inspector_findings: {
3746
+ icon: "\u{1F7E1}",
3747
+ service: "Inspector",
3748
+ impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
3749
+ action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
3750
+ },
3751
+ trusted_advisor_findings: {
3752
+ icon: "\u{1F7E1}",
3753
+ service: "Trusted Advisor",
3754
+ impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
3755
+ action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
3756
+ },
3757
+ config_rules_findings: {
3758
+ icon: "\u{1F7E1}",
3759
+ service: "AWS Config",
3760
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
3761
+ action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
3762
+ },
3763
+ access_analyzer_findings: {
3764
+ icon: "\u{1F7E1}",
3765
+ service: "IAM Access Analyzer",
3766
+ impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
3767
+ action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
3768
+ },
3769
+ patch_compliance_findings: {
3770
+ icon: "\u{1F7E1}",
3771
+ service: "SSM Patch Manager",
3772
+ impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
3773
+ action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
3774
+ }
3775
+ },
3776
+ // HW Checklist (full composite)
3777
+ hwChecklist: `
3778
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
3779
+ \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
3780
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
3781
+
3782
+ \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
3783
+
3784
+ \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
3785
+ \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
3786
+ \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
3787
+ \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
3788
+ \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
3789
+
3790
+ \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
3791
+ \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
3792
+ \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
3793
+ \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
3794
+
3795
+ \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
3796
+ \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
3797
+ \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
3798
+ \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
3799
+ \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
3800
+ \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
3801
+
3802
+ \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
3803
+ \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
3804
+ \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
3805
+ \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
3806
+
3807
+ \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
3808
+ \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
3809
+ \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
3810
+ \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
3811
+
3812
+ \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
3813
+ \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
3814
+ \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
3815
+ \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A\u201C\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0\u201D
3816
+
3817
+ \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
3818
+ \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
3819
+ \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
3820
+ \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
3821
+ \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
3822
+
3823
+ \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
3824
+ \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
3825
+ \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
3826
+ \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
3827
+
3828
+ \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
3829
+ `
3830
+ };
3831
+
3832
+ // src/i18n/en.ts
3833
+ var enI18n = {
3834
+ // HTML Security Report
3835
+ securityReportTitle: "AWS Security Scan Report",
3836
+ securityScore: "Security Score",
3837
+ critical: "Critical",
3838
+ high: "High",
3839
+ medium: "Medium",
3840
+ low: "Low",
3841
+ scanStatistics: "Scan Statistics",
3842
+ module: "Module",
3843
+ resources: "Resources",
3844
+ findings: "Findings",
3845
+ status: "Status",
3846
+ allFindings: "All Findings",
3847
+ recommendations: "Recommendations",
3848
+ unique: "unique",
3849
+ showMore: "Show more",
3850
+ noIssuesFound: "No security issues found.",
3851
+ allModulesClean: "All modules clean",
3852
+ generatedBy: "Generated by AWS Security MCP Server",
3853
+ informationalOnly: "This report is for informational purposes only.",
3854
+ // MLPS Report
3855
+ mlpsTitle: "MLPS Level 3 Pre-Check Report",
3856
+ 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)",
3857
+ checkedItems: "Checked Items",
3858
+ noIssues: "No Issues Found",
3859
+ issuesFound: "Issues Found",
3860
+ notChecked: "Not Checked",
3861
+ cloudProvider: "Cloud Provider Responsible",
3862
+ manualReview: "Manual Review Required",
3863
+ notApplicable: "Not Applicable",
3864
+ checkResult: "Check Result",
3865
+ noRelatedIssues: "Check Result: No related issues found",
3866
+ issuesFoundCount: (n) => `Check Result: Found ${n} related issue${n === 1 ? "" : "s"}`,
3867
+ remediation: "Remediation",
3868
+ remediationItems: (n) => `Remediation Items (${n} unique)`,
3869
+ showRemaining: (n) => `Show remaining ${n} items`,
3870
+ // HW Defense Checklist
3871
+ hwChecklistTitle: "\u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)",
3872
+ hwChecklistSubtitle: "The following items require manual verification and execution:",
3873
+ hwEmergencyIsolation: `\u26A0\uFE0F Emergency Isolation / Incident Response Plan
3874
+ \u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
3875
+ \u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
3876
+ \u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
3877
+ \u25A1 Identify responsible personnel and contacts for each project account and resource`,
3878
+ hwTestEnvShutdown: `\u26A0\uFE0F Test/Development Environment Handling
3879
+ \u25A1 Shut down non-critical systems during the drill period
3880
+ \u25A1 Shut down test/dev environments or maintain same security baseline as production
3881
+ \u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation`,
3882
+ hwDutyTeam: `\u26A0\uFE0F On-Duty Team Formation
3883
+ \u25A1 7\xD724 monitoring and rapid response team
3884
+ \u25A1 Technical and risk analysis team
3885
+ \u25A1 Security policy deployment team
3886
+ \u25A1 Business response team
3887
+ \u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)`,
3888
+ hwNetworkDiagram: `\u26A0\uFE0F Ingress/Egress Path Architecture Diagram
3889
+ \u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
3890
+ \u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
3891
+ \u25A1 Identify all internet-facing data interaction interfaces`,
3892
+ hwPentest: `\u26A0\uFE0F Proactive Penetration Testing
3893
+ \u25A1 Contact security vendors for simulated attack drills before the exercise
3894
+ \u25A1 Conduct security hardening based on penetration test reports
3895
+ \u25A1 Monitor AWS security advisories (known vulnerabilities and patches)`,
3896
+ hwWarRoom: `\u26A0\uFE0F WAR-ROOM Real-Time Communication
3897
+ \u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
3898
+ \u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
3899
+ \u25A1 Standardize case title format: "[CyberDrill] + Issue Description"`,
3900
+ hwCredentials: `\u26A0\uFE0F Password & Credential Management
3901
+ \u25A1 All IAM users must have MFA enabled
3902
+ \u25A1 Access key rotation cycle \u2264 90 days
3903
+ \u25A1 Avoid shared account usage
3904
+ \u25A1 No plaintext passwords in S3/Lambda/application code`,
3905
+ hwPostOptimization: `\u26A0\uFE0F Post-Drill Optimization
3906
+ \u25A1 Address and remediate each item from the attack report
3907
+ \u25A1 Establish periodic security maintenance processes with the security team
3908
+ \u25A1 Continuously fill security risk gaps`,
3909
+ hwReference: "Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)",
3910
+ // Service Reminders
3911
+ serviceReminderTitle: "\u26A1 The following security services are not enabled; some checks cannot be performed:",
3912
+ serviceReminderFooter: "Re-scan after enabling the above services for a more complete security assessment.",
3913
+ serviceImpact: "Impact",
3914
+ serviceAction: "Action",
3915
+ // Common
3916
+ account: "Account",
3917
+ region: "Region",
3918
+ scanTime: "Scan Time",
3919
+ duration: "Duration",
3920
+ severityDistribution: "Severity Distribution",
3921
+ findingsByModule: "Findings by Module",
3922
+ details: "Details",
3923
+ // Extended \u2014 HTML Security Report extras
3924
+ topHighestRiskFindings: (n) => `Top ${n} Highest Risk Findings`,
3925
+ resource: "Resource",
3926
+ impact: "Impact",
3927
+ riskScore: "Risk Score",
3928
+ showRemainingFindings: (n) => `Show remaining ${n} findings\u2026`,
3929
+ trendTitle: "30-Day Trends",
3930
+ findingsBySeverity: "Findings by Severity",
3931
+ showMoreCount: (n) => `Show ${n} more\u2026`,
3932
+ // Extended \u2014 MLPS extras
3933
+ // Markdown report
3934
+ executiveSummary: "Executive Summary",
3935
+ totalFindingsLabel: "Total Findings",
3936
+ description: "Description",
3937
+ priority: "Priority",
3938
+ noFindingsForSeverity: (severity) => `No ${severity.toLowerCase()} findings.`,
3939
+ preCheckOverview: "Pre-Check Overview",
3940
+ accountInfo: "Account Information",
3941
+ checkedCount: (total, clean, issues) => `Checked: ${total} items (No issues: ${clean} | Issues found: ${issues})`,
3942
+ uncheckedCount: (n) => `Not checked: ${n} items (corresponding scan modules not run)`,
3943
+ cloudProviderCount: (n) => `Cloud provider responsible: ${n} items`,
3944
+ manualReviewCount: (n) => `Manual review required: ${n} items`,
3945
+ naCount: (n) => `Not applicable: ${n} items`,
3946
+ naNote: (n) => `Not applicable: ${n} items (IoT/wireless networks/mobile terminals/ICS/trusted verification, etc.)`,
3947
+ unknownNote: (n) => `(${n} items not checked \u2014 corresponding scan modules not run)`,
3948
+ 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.`,
3949
+ mlpsFooterGenerated: (version) => `Generated by AWS Security MCP Server v${version}`,
3950
+ 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.",
3951
+ andMore: (n) => `\u2026 and ${n} more`,
3952
+ remediationByPriority: "Remediation Items (by Priority)",
3953
+ affectedResources: (n) => `${n} resource${n === 1 ? "" : "s"} affected`,
3954
+ installWindowsPatches: (n, kbs) => `Install ${n} Windows patch${n === 1 ? "" : "es"} (${kbs})`,
3955
+ mlpsCategorySection: {
3956
+ "\u5B89\u5168\u7269\u7406\u73AF\u5883": "I. Physical Environment Security",
3957
+ "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "II. Communication Network Security",
3958
+ "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "III. Area Boundary Security",
3959
+ "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
3960
+ "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
3961
+ },
3962
+ // Service Recommendations
3963
+ notEnabled: "Not Enabled",
3964
+ serviceRecommendations: {
3965
+ security_hub_findings: {
3966
+ icon: "\u{1F534}",
3967
+ service: "Security Hub",
3968
+ impact: "Cannot obtain 300+ automated security checks (FSBP/CIS/PCI DSS standards)",
3969
+ action: "Enable Security Hub for the most comprehensive security posture assessment"
3970
+ },
3971
+ guardduty_findings: {
3972
+ icon: "\u{1F534}",
3973
+ service: "GuardDuty",
3974
+ impact: "Cannot detect threat activity (malicious IPs, anomalous API calls, crypto mining, etc.)",
3975
+ action: "Enable GuardDuty for continuous threat detection"
3976
+ },
3977
+ inspector_findings: {
3978
+ icon: "\u{1F7E1}",
3979
+ service: "Inspector",
3980
+ impact: "Cannot scan EC2/Lambda/container software vulnerabilities (CVEs)",
3981
+ action: "Enable Inspector to discover known security vulnerabilities"
3982
+ },
3983
+ trusted_advisor_findings: {
3984
+ icon: "\u{1F7E1}",
3985
+ service: "Trusted Advisor",
3986
+ impact: "Cannot obtain AWS best practice security checks",
3987
+ action: "Upgrade to Business/Enterprise Support plan to use Trusted Advisor security checks"
3988
+ },
3989
+ config_rules_findings: {
3990
+ icon: "\u{1F7E1}",
3991
+ service: "AWS Config",
3992
+ impact: "Cannot check resource configuration compliance status",
3993
+ action: "Enable AWS Config and configure Config Rules"
3994
+ },
3995
+ access_analyzer_findings: {
3996
+ icon: "\u{1F7E1}",
3997
+ service: "IAM Access Analyzer",
3998
+ impact: "Cannot detect whether resources are accessed by external accounts or public networks",
3999
+ action: "Create IAM Access Analyzer (account-level or organization-level)"
4000
+ },
4001
+ patch_compliance_findings: {
4002
+ icon: "\u{1F7E1}",
4003
+ service: "SSM Patch Manager",
4004
+ impact: "Cannot check instance patch compliance status",
4005
+ action: "Install SSM Agent and configure Patch Manager"
4006
+ }
4007
+ },
4008
+ // HW Checklist (full composite)
4009
+ hwChecklist: `
4010
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4011
+ \u{1F4CB} Cyber Defense Drill Supplementary Reminders (Beyond Automated Scanning)
4012
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
4013
+
4014
+ The following items require manual verification and execution:
4015
+
4016
+ \u26A0\uFE0F Emergency Isolation / Incident Response Plan
4017
+ \u25A1 Prepare dedicated isolation security groups (no Inbound/Outbound rules)
4018
+ \u25A1 Establish instance isolation SOP: Alert \u2192 Investigate \u2192 Block attacker IP \u2192 Network isolation \u2192 Security response \u2192 Log attack details
4019
+ \u25A1 Define emergency response procedures for each system (production core/non-core/test/dev)
4020
+ \u25A1 Identify responsible personnel and contacts for each project account and resource
4021
+
4022
+ \u26A0\uFE0F Test/Development Environment Handling
4023
+ \u25A1 Shut down non-critical systems during the drill period
4024
+ \u25A1 Shut down test/dev environments or maintain same security baseline as production
4025
+ \u25A1 Confirm which environments can be emergency-stopped to prevent attack propagation
4026
+
4027
+ \u26A0\uFE0F On-Duty Team Formation
4028
+ \u25A1 7\xD724 monitoring and rapid response team
4029
+ \u25A1 Technical and risk analysis team
4030
+ \u25A1 Security policy deployment team
4031
+ \u25A1 Business response team
4032
+ \u25A1 Confirm AWS TAM/Support contact information (ES/EOP customers)
4033
+
4034
+ \u26A0\uFE0F Ingress/Egress Path Architecture Diagram
4035
+ \u25A1 Ensure all Internet/DX dedicated line ingress/egress paths are clearly marked in architecture diagrams
4036
+ \u25A1 Clarify data flow for each ELB/Public EC2/S3/DX
4037
+ \u25A1 Identify all internet-facing data interaction interfaces
4038
+
4039
+ \u26A0\uFE0F Proactive Penetration Testing
4040
+ \u25A1 Contact security vendors for simulated attack drills before the exercise
4041
+ \u25A1 Conduct security hardening based on penetration test reports
4042
+ \u25A1 Monitor AWS security advisories (known vulnerabilities and patches)
4043
+
4044
+ \u26A0\uFE0F WAR-ROOM Real-Time Communication
4045
+ \u25A1 Create dedicated communication channels for the drill period (Teams/Slack/Chime)
4046
+ \u25A1 Establish WAR-ROOM connection with AWS TAM (Enterprise Support customers)
4047
+ \u25A1 Standardize case title format: "[CyberDrill] + Issue Description"
4048
+
4049
+ \u26A0\uFE0F Password & Credential Management
4050
+ \u25A1 All IAM users must have MFA enabled
4051
+ \u25A1 Access key rotation cycle \u2264 90 days
4052
+ \u25A1 Avoid shared account usage
4053
+ \u25A1 No plaintext passwords in S3/Lambda/application code
4054
+
4055
+ \u26A0\uFE0F Post-Drill Optimization
4056
+ \u25A1 Address and remediate each item from the attack report
4057
+ \u25A1 Establish periodic security maintenance processes with the security team
4058
+ \u25A1 Continuously fill security risk gaps
4059
+
4060
+ Reference: AWS Cyber Defense Drill Standard Operation Procedure (Compliance IEM)
4061
+ `
4062
+ };
4063
+
4064
+ // src/i18n/index.ts
4065
+ var translations = {
4066
+ zh: zhI18n,
4067
+ en: enI18n
4068
+ };
4069
+ function getI18n(lang = "zh") {
4070
+ return translations[lang] ?? translations.zh;
4071
+ }
4072
+
3971
4073
  // src/tools/report-tool.ts
3972
4074
  var SEVERITY_ICON = {
3973
4075
  CRITICAL: "\u{1F534}",
@@ -3985,38 +4087,45 @@ function formatDuration(start, end) {
3985
4087
  const remainSecs = secs % 60;
3986
4088
  return `${mins}m ${remainSecs}s`;
3987
4089
  }
3988
- function renderFinding(f) {
3989
- const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
3990
- return [
3991
- `#### ${f.title}`,
3992
- `- **Resource:** ${f.resourceId} (\`${f.resourceArn}\`)`,
3993
- `- **Description:** ${f.description}`,
3994
- `- **Impact:** ${f.impact}`,
3995
- `- **Risk Score:** ${f.riskScore}/10`,
3996
- `- **Remediation:**`,
3997
- steps,
3998
- `- **Priority:** ${f.priority}`
3999
- ].join("\n");
4000
- }
4001
- function generateMarkdownReport(scanResults) {
4090
+ function generateMarkdownReport(scanResults, lang) {
4091
+ const t = getI18n(lang ?? "zh");
4002
4092
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
4003
4093
  const date = scanStart.split("T")[0];
4004
4094
  const duration = formatDuration(scanStart, scanEnd);
4095
+ const sevLabel = {
4096
+ CRITICAL: t.critical,
4097
+ HIGH: t.high,
4098
+ MEDIUM: t.medium,
4099
+ LOW: t.low
4100
+ };
4101
+ function renderFinding(f) {
4102
+ const steps = f.remediationSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
4103
+ return [
4104
+ `#### ${f.title}`,
4105
+ `- **${t.resource}:** ${f.resourceId} (\`${f.resourceArn}\`)`,
4106
+ `- **${t.description}:** ${f.description}`,
4107
+ `- **${t.impact}:** ${f.impact}`,
4108
+ `- **${t.riskScore}:** ${f.riskScore}/10`,
4109
+ `- **${t.remediation}:**`,
4110
+ steps,
4111
+ `- **${t.priority}:** ${f.priority}`
4112
+ ].join("\n");
4113
+ }
4005
4114
  const lines = [];
4006
- lines.push(`# AWS Security Scan Report \u2014 ${date}`);
4115
+ lines.push(`# ${t.securityReportTitle} \u2014 ${date}`);
4007
4116
  lines.push("");
4008
- lines.push("## Executive Summary");
4009
- lines.push(`- **Account:** ${accountId}`);
4010
- lines.push(`- **Region:** ${region}`);
4011
- lines.push(`- **Scan Duration:** ${duration}`);
4117
+ lines.push(`## ${t.executiveSummary}`);
4118
+ lines.push(`- **${t.account}:** ${accountId}`);
4119
+ lines.push(`- **${t.region}:** ${region}`);
4120
+ lines.push(`- **${t.duration}:** ${duration}`);
4012
4121
  lines.push(
4013
- `- **Total Findings:** ${summary.totalFindings} (\u{1F534} ${summary.critical} Critical | \u{1F7E0} ${summary.high} High | \u{1F7E1} ${summary.medium} Medium | \u{1F7E2} ${summary.low} Low)`
4122
+ `- **${t.totalFindingsLabel}:** ${summary.totalFindings} (${SEVERITY_ICON.CRITICAL} ${summary.critical} ${t.critical} | ${SEVERITY_ICON.HIGH} ${summary.high} ${t.high} | ${SEVERITY_ICON.MEDIUM} ${summary.medium} ${t.medium} | ${SEVERITY_ICON.LOW} ${summary.low} ${t.low})`
4014
4123
  );
4015
4124
  lines.push("");
4016
4125
  if (summary.totalFindings === 0) {
4017
- lines.push("## Findings by Severity");
4126
+ lines.push(`## ${t.findingsBySeverity}`);
4018
4127
  lines.push("");
4019
- lines.push("\u2705 No security issues found.");
4128
+ lines.push(`\u2705 ${t.noIssuesFound}`);
4020
4129
  lines.push("");
4021
4130
  } else {
4022
4131
  const allFindings = modules.flatMap((m) => m.findings);
@@ -4027,15 +4136,15 @@ function generateMarkdownReport(scanResults) {
4027
4136
  for (const f of allFindings) {
4028
4137
  grouped.get(f.severity).push(f);
4029
4138
  }
4030
- lines.push("## Findings by Severity");
4139
+ lines.push(`## ${t.findingsBySeverity}`);
4031
4140
  lines.push("");
4032
4141
  for (const sev of SEVERITY_ORDER) {
4033
4142
  const findings = grouped.get(sev);
4034
4143
  const icon = SEVERITY_ICON[sev];
4035
- lines.push(`### ${icon} ${sev.charAt(0)}${sev.slice(1).toLowerCase()}`);
4144
+ lines.push(`### ${icon} ${sevLabel[sev]}`);
4036
4145
  lines.push("");
4037
4146
  if (findings.length === 0) {
4038
- lines.push(`No ${sev.toLowerCase()} findings.`);
4147
+ lines.push(t.noFindingsForSeverity(sevLabel[sev]));
4039
4148
  lines.push("");
4040
4149
  continue;
4041
4150
  }
@@ -4046,9 +4155,9 @@ function generateMarkdownReport(scanResults) {
4046
4155
  }
4047
4156
  }
4048
4157
  }
4049
- lines.push("## Scan Statistics");
4158
+ lines.push(`## ${t.scanStatistics}`);
4050
4159
  lines.push(
4051
- "| Module | Resources Scanned | Findings | Status |"
4160
+ `| ${t.module} | ${t.resources} | ${t.findings} | ${t.status} |`
4052
4161
  );
4053
4162
  lines.push("|--------|------------------|----------|--------|");
4054
4163
  for (const m of modules) {
@@ -4061,7 +4170,7 @@ function generateMarkdownReport(scanResults) {
4061
4170
  if (summary.totalFindings > 0) {
4062
4171
  const allFindings = modules.flatMap((m) => m.findings);
4063
4172
  allFindings.sort((a, b) => b.riskScore - a.riskScore);
4064
- lines.push("## Recommendations (Priority Order)");
4173
+ lines.push(`## ${t.recommendations}`);
4065
4174
  for (let i = 0; i < allFindings.length; i++) {
4066
4175
  const f = allFindings[i];
4067
4176
  lines.push(`${i + 1}. [${f.priority}] ${f.title}: ${f.remediationSteps[0] ?? "Review and remediate."}`);
@@ -6108,13 +6217,6 @@ var MLPS3_CATEGORY_ORDER = [
6108
6217
  "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6109
6218
  "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6110
6219
  ];
6111
- var MLPS3_CATEGORY_SECTION = {
6112
- "\u5B89\u5168\u7269\u7406\u73AF\u5883": "\u4E00\u3001\u5B89\u5168\u7269\u7406\u73AF\u5883",
6113
- "\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC": "\u4E8C\u3001\u5B89\u5168\u901A\u4FE1\u7F51\u7EDC",
6114
- "\u5B89\u5168\u533A\u57DF\u8FB9\u754C": "\u4E09\u3001\u5B89\u5168\u533A\u57DF\u8FB9\u754C",
6115
- "\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
6116
- "\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
6117
- };
6118
6220
  var MLPS3_CHECK_MAPPING = [
6119
6221
  // =========================================================================
6120
6222
  // 安全物理环境 — L3-PES1-* (22 items) → cloud_provider
@@ -6774,7 +6876,9 @@ function evaluateAllFullChecks(scanResults) {
6774
6876
  return evaluateFullCheck(item, mapping, allFindings, scanModules);
6775
6877
  });
6776
6878
  }
6777
- function generateMlps3Report(scanResults) {
6879
+ function generateMlps3Report(scanResults, lang) {
6880
+ const t = getI18n(lang ?? "zh");
6881
+ const isEn = (lang ?? "zh") === "en";
6778
6882
  const { accountId, region, scanStart } = scanResults;
6779
6883
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
6780
6884
  const results = evaluateAllFullChecks(scanResults);
@@ -6786,27 +6890,28 @@ function generateMlps3Report(scanResults) {
6786
6890
  const cloudCount = results.filter((r) => r.status === "cloud_provider").length;
6787
6891
  const manualCount = results.filter((r) => r.status === "manual").length;
6788
6892
  const naCount = results.filter((r) => r.status === "not_applicable").length;
6893
+ const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
6894
+ const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
6789
6895
  const lines = [];
6790
- lines.push("# \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A");
6791
- lines.push("> **\u672C\u62A5\u544A\u4E3A\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u53C2\u8003\uFF0C\u63D0\u4F9B\u4E91\u5E73\u53F0\u914D\u7F6E\u68C0\u67E5\u6570\u636E\u4E0E\u5EFA\u8BAE\u3002\u5408\u89C4\u5224\u5B9A\uFF08\u7B26\u5408/\u90E8\u5206\u7B26\u5408/\u4E0D\u7B26\u5408\uFF09\u9700\u7531\u6301\u8BC1\u6D4B\u8BC4\u673A\u6784\u6839\u636E\u5B9E\u9645\u60C5\u51B5\u786E\u8BA4\u3002**");
6792
- lines.push("> **\uFF08GB/T 22239-2019 \u5B8C\u6574\u68C0\u67E5\u6E05\u5355 184 \u9879\uFF09**");
6896
+ lines.push(`# ${t.mlpsTitle}`);
6897
+ lines.push(`> **${t.mlpsDisclaimer}**`);
6793
6898
  lines.push("");
6794
- lines.push("## \u8D26\u6237\u4FE1\u606F");
6795
- lines.push(`- Account: ${accountId} | Region: ${region} | \u626B\u63CF\u65F6\u95F4: ${scanTime}`);
6899
+ lines.push(`## ${t.accountInfo}`);
6900
+ lines.push(`- ${t.account}: ${accountId} | ${t.region}: ${region} | ${t.scanTime}: ${scanTime}`);
6796
6901
  lines.push("");
6797
- lines.push("## \u9884\u68C0\u603B\u89C8");
6798
- lines.push(`- \u5DF2\u68C0\u67E5: ${checkedTotal} \u9879\uFF08\u672A\u53D1\u73B0\u95EE\u9898: ${autoClean} \u9879 | \u53D1\u73B0\u95EE\u9898: ${autoIssues} \u9879\uFF09`);
6902
+ lines.push(`## ${t.preCheckOverview}`);
6903
+ lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
6799
6904
  if (autoUnknown > 0) {
6800
- lines.push(`- \u672A\u68C0\u67E5: ${autoUnknown} \u9879\uFF08\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09`);
6905
+ lines.push(`- ${t.uncheckedCount(autoUnknown)}`);
6801
6906
  }
6802
- lines.push(`- \u4E91\u5E73\u53F0\u8D1F\u8D23: ${cloudCount} \u9879`);
6803
- lines.push(`- \u9700\u4EBA\u5DE5\u8BC4\u4F30: ${manualCount} \u9879`);
6907
+ lines.push(`- ${t.cloudProviderCount(cloudCount)}`);
6908
+ lines.push(`- ${t.manualReviewCount(manualCount)}`);
6804
6909
  if (naCount > 0) {
6805
- lines.push(`- \u4E0D\u9002\u7528: ${naCount} \u9879`);
6910
+ lines.push(`- ${t.naCount(naCount)}`);
6806
6911
  }
6807
6912
  lines.push("");
6808
6913
  for (const category of MLPS3_CATEGORY_ORDER) {
6809
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
6914
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
6810
6915
  const catResults = results.filter(
6811
6916
  (r) => r.item.categoryCn === category && r.status !== "not_applicable"
6812
6917
  );
@@ -6819,18 +6924,20 @@ function generateMlps3Report(scanResults) {
6819
6924
  if (!controlMap.has(key)) controlMap.set(key, []);
6820
6925
  controlMap.get(key).push(r);
6821
6926
  }
6822
- for (const [controlName, controlResults] of controlMap) {
6927
+ for (const [_controlKey, controlResults] of controlMap) {
6928
+ const controlName = itemControl(controlResults[0]);
6823
6929
  lines.push(`### ${controlName}`);
6824
6930
  for (const r of controlResults) {
6825
6931
  const icon = r.status === "clean" ? "\u2705" : r.status === "issues" ? "\u274C" : r.status === "unknown" ? "\u26A0\uFE0F" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
6826
- const suffix = r.status === "unknown" ? " \u2014 \u672A\u68C0\u67E5" : r.status === "manual" ? ` \u2014 ${r.mapping.guidance ?? "\u9700\u4EBA\u5DE5\u8BC4\u4F30"}` : r.status === "cloud_provider" ? ` \u2014 ${r.mapping.note ?? "\u4E91\u5E73\u53F0\u8D1F\u8D23"}` : r.status === "clean" ? " \u672A\u53D1\u73B0\u95EE\u9898" : " \u53D1\u73B0\u95EE\u9898";
6827
- lines.push(`- [${icon}] ${r.item.id} ${r.item.requirementCn.slice(0, 60)}${r.item.requirementCn.length > 60 ? "\u2026" : ""}${suffix}`);
6932
+ 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}`;
6933
+ const reqText = itemReq(r);
6934
+ lines.push(`- [${icon}] ${r.item.id} ${reqText.slice(0, 60)}${reqText.length > 60 ? "\u2026" : ""}${suffix}`);
6828
6935
  if (r.status === "issues" && r.relatedFindings.length > 0) {
6829
6936
  for (const f of r.relatedFindings.slice(0, 3)) {
6830
6937
  lines.push(` - ${f.severity}: ${f.title}`);
6831
6938
  }
6832
6939
  if (r.relatedFindings.length > 3) {
6833
- lines.push(` - ... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 3} \u9879`);
6940
+ lines.push(` - ${t.andMore(r.relatedFindings.length - 3)}`);
6834
6941
  }
6835
6942
  }
6836
6943
  }
@@ -6839,7 +6946,7 @@ function generateMlps3Report(scanResults) {
6839
6946
  }
6840
6947
  const failedResults = results.filter((r) => r.status === "issues");
6841
6948
  if (failedResults.length > 0) {
6842
- lines.push("## \u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09");
6949
+ lines.push(`## ${t.remediationByPriority}`);
6843
6950
  lines.push("");
6844
6951
  const allFailedFindings = /* @__PURE__ */ new Map();
6845
6952
  for (const r of failedResults) {
@@ -6862,7 +6969,7 @@ function generateMlps3Report(scanResults) {
6862
6969
  lines.push("");
6863
6970
  }
6864
6971
  if (naCount > 0) {
6865
- lines.push(`> \u4E0D\u9002\u7528\u9879: ${naCount} \u9879\uFF08\u7269\u8054\u7F51/\u65E0\u7EBF\u7F51\u7EDC/\u79FB\u52A8\u7EC8\u7AEF/\u5DE5\u63A7\u7CFB\u7EDF/\u53EF\u4FE1\u9A8C\u8BC1\u7B49\uFF09`);
6972
+ lines.push(`> ${t.naNote(naCount)}`);
6866
6973
  lines.push("");
6867
6974
  }
6868
6975
  return lines.join("\n");
@@ -6872,6 +6979,15 @@ function generateMlps3Report(scanResults) {
6872
6979
  function esc(s) {
6873
6980
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
6874
6981
  }
6982
+ function escWithLinks(s) {
6983
+ const parts = s.split(/(https?:\/\/\S+)/);
6984
+ return parts.map((part, i) => {
6985
+ if (i % 2 === 1) {
6986
+ return `<a href="${esc(part)}" style="color:#60a5fa" target="_blank" rel="noopener">${esc(part)}</a>`;
6987
+ }
6988
+ return esc(part);
6989
+ }).join("");
6990
+ }
6875
6991
  function calcScore(summary) {
6876
6992
  const raw = 100 - summary.critical * 15 - summary.high * 5 - summary.medium * 2 - summary.low * 0.5;
6877
6993
  return Math.max(0, Math.min(100, Math.round(raw)));
@@ -6895,50 +7011,6 @@ function scoreColor(score) {
6895
7011
  if (score >= 50) return "#eab308";
6896
7012
  return "#ef4444";
6897
7013
  }
6898
- var SERVICE_RECOMMENDATIONS = {
6899
- security_hub_findings: {
6900
- icon: "\u{1F534}",
6901
- service: "Security Hub",
6902
- impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
6903
- action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
6904
- },
6905
- guardduty_findings: {
6906
- icon: "\u{1F534}",
6907
- service: "GuardDuty",
6908
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u5A01\u80C1\u6D3B\u52A8\uFF08\u6076\u610F IP\u3001\u5F02\u5E38 API \u8C03\u7528\u3001\u52A0\u5BC6\u8D27\u5E01\u6316\u77FF\u7B49\uFF09",
6909
- action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
6910
- },
6911
- inspector_findings: {
6912
- icon: "\u{1F7E1}",
6913
- service: "Inspector",
6914
- impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
6915
- action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
6916
- },
6917
- trusted_advisor_findings: {
6918
- icon: "\u{1F7E1}",
6919
- service: "Trusted Advisor",
6920
- impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
6921
- action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
6922
- },
6923
- config_rules_findings: {
6924
- icon: "\u{1F7E1}",
6925
- service: "AWS Config",
6926
- impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
6927
- action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
6928
- },
6929
- access_analyzer_findings: {
6930
- icon: "\u{1F7E1}",
6931
- service: "IAM Access Analyzer",
6932
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
6933
- action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
6934
- },
6935
- patch_compliance_findings: {
6936
- icon: "\u{1F7E1}",
6937
- service: "SSM Patch Manager",
6938
- impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
6939
- action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
6940
- }
6941
- };
6942
7014
  var SERVICE_NOT_ENABLED_PATTERNS = [
6943
7015
  "not enabled",
6944
7016
  "not found",
@@ -6948,10 +7020,11 @@ var SERVICE_NOT_ENABLED_PATTERNS = [
6948
7020
  "not available",
6949
7021
  "is not enabled"
6950
7022
  ];
6951
- function getDisabledServices(modules) {
7023
+ function getDisabledServices(modules, lang) {
7024
+ const t = getI18n(lang ?? "zh");
6952
7025
  const disabled = [];
6953
7026
  for (const mod of modules) {
6954
- const rec = SERVICE_RECOMMENDATIONS[mod.module];
7027
+ const rec = t.serviceRecommendations[mod.module];
6955
7028
  if (!rec) continue;
6956
7029
  if (!mod.warnings?.length) continue;
6957
7030
  const hasNotEnabled = mod.warnings.some(
@@ -6963,21 +7036,22 @@ function getDisabledServices(modules) {
6963
7036
  }
6964
7037
  return disabled;
6965
7038
  }
6966
- function buildServiceReminderHtml(modules) {
6967
- const disabled = getDisabledServices(modules);
7039
+ function buildServiceReminderHtml(modules, lang) {
7040
+ const t = getI18n(lang ?? "zh");
7041
+ const disabled = getDisabledServices(modules, lang);
6968
7042
  if (disabled.length === 0) return "";
6969
7043
  const items = disabled.map((svc) => `
6970
7044
  <div style="margin-bottom:12px">
6971
- <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} \u672A\u542F\u7528</div>
6972
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5F71\u54CD\uFF1A${esc(svc.impact)}</div>
6973
- <div style="margin-left:28px;color:#cbd5e1;font-size:13px">\u5EFA\u8BAE\uFF1A${esc(svc.action)}</div>
7045
+ <div style="font-weight:600;font-size:15px">${esc(svc.icon)} ${esc(svc.service)} ${esc(t.notEnabled)}</div>
7046
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceImpact)}\uFF1A${esc(svc.impact)}</div>
7047
+ <div style="margin-left:28px;color:#cbd5e1;font-size:13px">${esc(t.serviceAction)}\uFF1A${esc(svc.action)}</div>
6974
7048
  </div>`).join("\n");
6975
7049
  return `
6976
7050
  <section>
6977
7051
  <div style="background:#2d1f00;border:1px solid #b45309;border-radius:8px;padding:20px;margin-bottom:32px">
6978
- <div style="font-size:17px;font-weight:700;margin-bottom:12px">&#9889; \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A</div>
7052
+ <div style="font-size:17px;font-weight:700;margin-bottom:12px">${esc(t.serviceReminderTitle)}</div>
6979
7053
  ${items}
6980
- <div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500">\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002</div>
7054
+ <div style="margin-top:12px;font-size:13px;color:#fbbf24;font-weight:500">${esc(t.serviceReminderFooter)}</div>
6981
7055
  </div>
6982
7056
  </section>`;
6983
7057
  }
@@ -7179,12 +7253,12 @@ function donutChart(summary) {
7179
7253
  "</svg>"
7180
7254
  ].join("\n");
7181
7255
  }
7182
- function barChart(modules) {
7256
+ function barChart(modules, allCleanLabel = "All modules clean") {
7183
7257
  const withFindings = modules.filter((m) => m.findingsCount > 0).sort((a, b) => b.findingsCount - a.findingsCount).slice(0, 12);
7184
7258
  if (withFindings.length === 0) {
7185
7259
  return [
7186
7260
  '<svg viewBox="0 0 400 50" width="100%">',
7187
- ' <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">All modules clean</text>',
7261
+ ` <text x="200" y="30" text-anchor="middle" fill="#22c55e" font-size="14" font-weight="600">${esc(allCleanLabel)}</text>`,
7188
7262
  "</svg>"
7189
7263
  ].join("\n");
7190
7264
  }
@@ -7299,7 +7373,9 @@ function scoreTrendChart(history) {
7299
7373
  "</svg>"
7300
7374
  ].join("\n");
7301
7375
  }
7302
- function generateHtmlReport(scanResults, history) {
7376
+ function generateHtmlReport(scanResults, history, lang) {
7377
+ const t = getI18n(lang ?? "zh");
7378
+ const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
7303
7379
  const { summary, modules, accountId, region, scanStart, scanEnd } = scanResults;
7304
7380
  const date = scanStart.split("T")[0];
7305
7381
  const duration = formatDuration2(scanStart, scanEnd);
@@ -7317,23 +7393,23 @@ function generateHtmlReport(scanResults, history) {
7317
7393
  <div class="top5-content">
7318
7394
  <span class="badge badge-${esc(f.severity.toLowerCase())}">${esc(f.severity)}</span>
7319
7395
  <div class="top5-title">${esc(f.title)}</div>
7320
- <div class="top5-detail"><strong>Resource:</strong> ${esc(f.resourceId)}</div>
7321
- <div class="top5-detail"><strong>Impact:</strong> ${esc(f.impact)}</div>
7322
- <div class="top5-detail"><strong>Risk Score:</strong> ${f.riskScore}/10</div>
7323
- <h4>Remediation</h4>
7324
- <ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
7396
+ <div class="top5-detail"><strong>${t.resource}:</strong> ${esc(f.resourceId)}</div>
7397
+ <div class="top5-detail"><strong>${t.impact}:</strong> ${esc(f.impact)}</div>
7398
+ <div class="top5-detail"><strong>${t.riskScore}:</strong> ${f.riskScore}/10</div>
7399
+ <h4>${t.remediation}</h4>
7400
+ <ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
7325
7401
  </div>
7326
7402
  </div>`
7327
7403
  ).join("\n");
7328
7404
  top5Html = `
7329
7405
  <section>
7330
- <h2>Top ${top5.length} Highest Risk Findings</h2>
7406
+ <h2>${esc(t.topHighestRiskFindings(top5.length))}</h2>
7331
7407
  ${cards}
7332
7408
  </section>`;
7333
7409
  }
7334
7410
  let findingsHtml;
7335
7411
  if (summary.totalFindings === 0) {
7336
- findingsHtml = '<div class="no-findings">No security issues found.</div>';
7412
+ findingsHtml = `<div class="no-findings">${esc(t.noIssuesFound)}</div>`;
7337
7413
  } else {
7338
7414
  const FOLD_THRESHOLD = 20;
7339
7415
  const renderCard = (f) => {
@@ -7342,10 +7418,10 @@ function generateHtmlReport(scanResults, history) {
7342
7418
  <span class="badge badge-${esc(sev)}">${esc(f.severity)}</span>
7343
7419
  <span class="finding-title-text">${esc(f.title)}</span>
7344
7420
  <span class="finding-resource">${esc(f.resourceArn || f.resourceId)}</span>
7345
- <details><summary>Details</summary><div class="finding-card-body">
7421
+ <details><summary>${t.details}</summary><div class="finding-card-body">
7346
7422
  <p>${esc(f.description)}</p>
7347
- <p><strong>Remediation:</strong></p>
7348
- <ol>${f.remediationSteps.map((s) => `<li>${esc(s)}</li>`).join("")}</ol>
7423
+ <p><strong>${t.remediation}:</strong></p>
7424
+ <ol>${f.remediationSteps.map((s) => `<li>${escWithLinks(s)}</li>`).join("")}</ol>
7349
7425
  </div></details>
7350
7426
  </div>`;
7351
7427
  };
@@ -7356,7 +7432,7 @@ function generateHtmlReport(scanResults, history) {
7356
7432
  const first = findings.slice(0, FOLD_THRESHOLD).map(renderCard).join("\n");
7357
7433
  const rest = findings.slice(FOLD_THRESHOLD).map(renderCard).join("\n");
7358
7434
  return `${first}
7359
- <details><summary>Show remaining ${findings.length - FOLD_THRESHOLD} findings...</summary>
7435
+ <details><summary>${t.showRemainingFindings(findings.length - FOLD_THRESHOLD)}</summary>
7360
7436
  ${rest}
7361
7437
  </details>`;
7362
7438
  };
@@ -7408,13 +7484,13 @@ ${rest}
7408
7484
  if (history && history.length >= 2) {
7409
7485
  trendHtml = `
7410
7486
  <section class="trend-section">
7411
- <h2>30-Day Trends</h2>
7487
+ <h2>${esc(t.trendTitle)}</h2>
7412
7488
  <div class="trend-chart">
7413
- <div class="trend-title">Findings by Severity</div>
7489
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7414
7490
  ${findingsTrendChart(history)}
7415
7491
  </div>
7416
7492
  <div class="trend-chart">
7417
- <div class="trend-title">Security Score</div>
7493
+ <div class="trend-title">${esc(t.securityScore)}</div>
7418
7494
  ${scoreTrendChart(history)}
7419
7495
  </div>
7420
7496
  </section>`;
@@ -7425,16 +7501,57 @@ ${rest}
7425
7501
  let recsHtml = "";
7426
7502
  if (summary.totalFindings > 0) {
7427
7503
  const recMap = /* @__PURE__ */ new Map();
7504
+ const kbPatches = [];
7505
+ let kbSeverity = "LOW";
7506
+ let kbUrl;
7507
+ const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7428
7508
  for (const f of allFindings) {
7429
7509
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
7510
+ const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
7511
+ if (genericPatterns.some((p) => rem.startsWith(p))) continue;
7512
+ const kbMatch = f.title.match(/KB\d+/);
7513
+ if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
7514
+ kbPatches.push(kbMatch[0]);
7515
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(kbSeverity)) kbSeverity = f.severity;
7516
+ if (!kbUrl && url) kbUrl = url;
7517
+ continue;
7518
+ }
7519
+ if (f.module === "security_hub_findings") {
7520
+ const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
7521
+ if (controlMatch) {
7522
+ const controlId = controlMatch[1];
7523
+ const key = `ctrl:${controlId}`;
7524
+ const existing2 = recMap.get(key);
7525
+ if (existing2) {
7526
+ existing2.count++;
7527
+ if (!existing2.url && url) existing2.url = url;
7528
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
7529
+ } else {
7530
+ recMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
7531
+ }
7532
+ continue;
7533
+ }
7534
+ }
7430
7535
  const existing = recMap.get(rem);
7431
7536
  if (existing) {
7432
7537
  existing.count++;
7538
+ if (!existing.url && url) existing.url = url;
7433
7539
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7434
7540
  existing.severity = f.severity;
7435
7541
  }
7436
7542
  } else {
7437
- recMap.set(rem, { text: rem, severity: f.severity, count: 1 });
7543
+ recMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
7544
+ }
7545
+ }
7546
+ if (kbPatches.length > 0) {
7547
+ const unique = [...new Set(kbPatches)];
7548
+ const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
7549
+ recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
7550
+ }
7551
+ for (const [key, rec] of recMap) {
7552
+ if (key.startsWith("ctrl:") && rec.count > 1) {
7553
+ rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
7554
+ rec.count = 1;
7438
7555
  }
7439
7556
  }
7440
7557
  const uniqueRecs = [...recMap.values()].sort((a, b) => {
@@ -7445,60 +7562,61 @@ ${rest}
7445
7562
  const renderRec = (r) => {
7446
7563
  const sev = r.severity.toLowerCase();
7447
7564
  const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
7448
- return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}</li>`;
7565
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</a>` : "";
7566
+ return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
7449
7567
  };
7450
7568
  const TOP_N = 10;
7451
7569
  const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
7452
7570
  const remaining = uniqueRecs.slice(TOP_N);
7453
7571
  const moreHtml = remaining.length > 0 ? `
7454
- <details><summary>Show ${remaining.length} more&hellip;</summary>
7572
+ <details><summary>${t.showMoreCount(remaining.length)}</summary>
7455
7573
  ${remaining.map(renderRec).join("\n")}
7456
7574
  </details>` : "";
7457
7575
  recsHtml = `
7458
7576
  <details class="rec-fold">
7459
- <summary><h2 style="margin:0;border:0;display:inline">Recommendations (${uniqueRecs.length} unique)</h2></summary>
7577
+ <summary><h2 style="margin:0;border:0;display:inline">${esc(t.recommendations)} (${uniqueRecs.length} ${esc(t.unique)})</h2></summary>
7460
7578
  <div class="rec-body">
7461
7579
  <ol>${topItems}${moreHtml}</ol>
7462
7580
  </div>
7463
7581
  </details>`;
7464
7582
  }
7465
7583
  return `<!DOCTYPE html>
7466
- <html lang="en">
7584
+ <html lang="${htmlLang}">
7467
7585
  <head>
7468
7586
  <meta charset="UTF-8">
7469
7587
  <meta name="viewport" content="width=device-width,initial-scale=1">
7470
- <title>AWS Security Scan Report &mdash; ${esc(date)}</title>
7588
+ <title>${esc(t.securityReportTitle)} &mdash; ${esc(date)}</title>
7471
7589
  <style>${sharedCss()}</style>
7472
7590
  </head>
7473
7591
  <body>
7474
7592
  <div class="container">
7475
7593
 
7476
7594
  <header>
7477
- <h1>&#128737;&#65039; AWS Security Scan Report</h1>
7478
- <div class="meta">Account: ${esc(accountId)} | Region: ${esc(region)} | ${esc(date)} | Duration: ${esc(duration)}</div>
7595
+ <h1>&#128737;&#65039; ${esc(t.securityReportTitle)}</h1>
7596
+ <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(date)} | ${esc(t.duration)}: ${esc(duration)}</div>
7479
7597
  </header>
7480
7598
 
7481
7599
  <section class="summary">
7482
7600
  <div class="score-card">
7483
7601
  <div class="score-value" style="color:${scoreColor(score)}">${score}</div>
7484
- <div class="score-label">Security Score</div>
7602
+ <div class="score-label">${esc(t.securityScore)}</div>
7485
7603
  </div>
7486
7604
  <div class="severity-stats">
7487
- <div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">Critical</div></div>
7488
- <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">High</div></div>
7489
- <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">Medium</div></div>
7490
- <div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">Low</div></div>
7605
+ <div class="stat-card stat-critical"><div class="stat-count">${summary.critical}</div><div class="stat-label">${esc(t.critical)}</div></div>
7606
+ <div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label">${esc(t.high)}</div></div>
7607
+ <div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label">${esc(t.medium)}</div></div>
7608
+ <div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label">${esc(t.low)}</div></div>
7491
7609
  </div>
7492
7610
  </section>
7493
7611
 
7494
7612
  <section class="charts">
7495
7613
  <div class="chart-box">
7496
- <div class="chart-title">Severity Distribution</div>
7614
+ <div class="chart-title">${esc(t.severityDistribution)}</div>
7497
7615
  <div style="text-align:center">${donutChart(summary)}</div>
7498
7616
  </div>
7499
7617
  <div class="chart-box">
7500
- <div class="chart-title">Findings by Module</div>
7501
- ${barChart(modules)}
7618
+ <div class="chart-title">${esc(t.findingsByModule)}</div>
7619
+ ${barChart(modules, t.allModulesClean)}
7502
7620
  </div>
7503
7621
  </section>
7504
7622
 
@@ -7506,33 +7624,35 @@ ${trendHtml}
7506
7624
 
7507
7625
  ${top5Html}
7508
7626
 
7509
- ${buildServiceReminderHtml(modules)}
7627
+ ${buildServiceReminderHtml(modules, lang)}
7510
7628
 
7511
7629
  <section>
7512
- <h2>Scan Statistics</h2>
7630
+ <h2>${esc(t.scanStatistics)}</h2>
7513
7631
  <table>
7514
- <thead><tr><th>Module</th><th>Resources</th><th>Findings</th><th>Status</th></tr></thead>
7632
+ <thead><tr><th>${esc(t.module)}</th><th>${esc(t.resources)}</th><th>${esc(t.findings)}</th><th>${esc(t.status)}</th></tr></thead>
7515
7633
  <tbody>${statsRows}</tbody>
7516
7634
  </table>
7517
7635
  </section>
7518
7636
 
7519
7637
  <section>
7520
- <h2>All Findings</h2>
7638
+ <h2>${esc(t.allFindings)}</h2>
7521
7639
  ${findingsHtml}
7522
7640
  </section>
7523
7641
 
7524
7642
  ${recsHtml}
7525
7643
 
7526
7644
  <footer>
7527
- <p>Generated by AWS Security MCP Server v${VERSION}</p>
7528
- <p>This report is for informational purposes only.</p>
7645
+ <p>${esc(t.generatedBy)} v${VERSION}</p>
7646
+ <p>${esc(t.informationalOnly)}</p>
7529
7647
  </footer>
7530
7648
 
7531
7649
  </div>
7532
7650
  </body>
7533
7651
  </html>`;
7534
7652
  }
7535
- function generateMlps3HtmlReport(scanResults, history) {
7653
+ function generateMlps3HtmlReport(scanResults, history, lang) {
7654
+ const t = getI18n(lang ?? "zh");
7655
+ const htmlLang = (lang ?? "zh") === "zh" ? "zh-CN" : "en";
7536
7656
  const { accountId, region, scanStart } = scanResults;
7537
7657
  const date = scanStart.split("T")[0];
7538
7658
  const scanTime = scanStart.replace("T", " ").replace(/\.\d+Z$/, " UTC");
@@ -7549,17 +7669,21 @@ function generateMlps3HtmlReport(scanResults, history) {
7549
7669
  if (history && history.length >= 2) {
7550
7670
  trendHtml = `
7551
7671
  <section class="trend-section">
7552
- <h2>30\u65E5\u8D8B\u52BF</h2>
7672
+ <h2>${esc(t.trendTitle)}</h2>
7553
7673
  <div class="trend-chart">
7554
- <div class="trend-title">\u6309\u4E25\u91CD\u6027\u5206\u7C7B\u7684\u53D1\u73B0</div>
7674
+ <div class="trend-title">${esc(t.findingsBySeverity)}</div>
7555
7675
  ${findingsTrendChart(history)}
7556
7676
  </div>
7557
7677
  <div class="trend-chart">
7558
- <div class="trend-title">\u5B89\u5168\u8BC4\u5206</div>
7678
+ <div class="trend-title">${esc(t.securityScore)}</div>
7559
7679
  ${scoreTrendChart(history)}
7560
7680
  </div>
7561
7681
  </section>`;
7562
7682
  }
7683
+ const isEn = (lang ?? "zh") === "en";
7684
+ const itemCat = (r) => isEn ? r.item.categoryEn : r.item.categoryCn;
7685
+ const itemControl = (r) => isEn ? r.item.controlEn : r.item.controlCn;
7686
+ const itemReq = (r) => isEn ? r.item.requirementEn : r.item.requirementCn;
7563
7687
  const categoryMap = /* @__PURE__ */ new Map();
7564
7688
  for (const r of results) {
7565
7689
  if (r.status === "not_applicable") continue;
@@ -7568,7 +7692,7 @@ function generateMlps3HtmlReport(scanResults, history) {
7568
7692
  categoryMap.get(cat).push(r);
7569
7693
  }
7570
7694
  const categorySections = MLPS3_CATEGORY_ORDER.map((category) => {
7571
- const sectionTitle = MLPS3_CATEGORY_SECTION[category];
7695
+ const sectionTitle = t.mlpsCategorySection[category] ?? category;
7572
7696
  const catResults = categoryMap.get(category);
7573
7697
  if (!catResults || catResults.length === 0) return "";
7574
7698
  const allCloud = catResults.every((r) => r.status === "cloud_provider");
@@ -7576,11 +7700,11 @@ function generateMlps3HtmlReport(scanResults, history) {
7576
7700
  return `<details class="category-fold mlps-cloud-section">
7577
7701
  <summary>
7578
7702
  <span class="category-title">${esc(sectionTitle)}</span>
7579
- <span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length} \u9879\u4E91\u5E73\u53F0\u8D1F\u8D23</span></span>
7703
+ <span class="category-stats"><span class="category-stat-cloud">\u{1F3E2} ${catResults.length} ${esc(t.cloudProvider)}</span></span>
7580
7704
  </summary>
7581
7705
  <div class="category-body">
7582
- <div class="mlps-cloud-note">\u4EE5\u4E0B ${catResults.length} \u9879\u7531 AWS \u4E91\u5E73\u53F0\u8D1F\u8D23\uFF0C\u6839\u636E\u5B89\u5168\u8D23\u4EFB\u5171\u62C5\u6A21\u578B\u4E0D\u5728\u672C\u62A5\u544A\u68C0\u67E5\u8303\u56F4\u5185\u3002</div>
7583
- ${catResults.map((r) => `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(r.item.controlCn)}</span><span class="check-note">${esc(r.mapping.note ?? "")}</span></div>`).join("\n")}
7706
+ <div class="mlps-cloud-note">${esc(t.cloudItemsNote(catResults.length))}</div>
7707
+ ${catResults.map((r) => `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(itemControl(r))}</span><span class="check-note">${esc(r.mapping.note ?? "")}</span></div>`).join("\n")}
7584
7708
  </div>
7585
7709
  </details>`;
7586
7710
  }
@@ -7602,31 +7726,34 @@ function generateMlps3HtmlReport(scanResults, history) {
7602
7726
  if (!controlMap.has(key)) controlMap.set(key, []);
7603
7727
  controlMap.get(key).push(r);
7604
7728
  }
7605
- const controlGroups = [...controlMap.entries()].map(([controlName, controlResults]) => {
7729
+ const controlGroups = [...controlMap.entries()].map(([_controlKey, controlResults]) => {
7730
+ const controlName = itemControl(controlResults[0]);
7606
7731
  const cloudItems = controlResults.filter((r) => r.status === "cloud_provider");
7607
7732
  const nonCloudItems = controlResults.filter((r) => r.status !== "cloud_provider");
7608
7733
  let itemsHtml = "";
7609
7734
  for (const r of nonCloudItems) {
7610
7735
  const icon = r.status === "clean" ? "\u{1F7E2}" : r.status === "issues" ? "\u{1F534}" : r.status === "unknown" ? "\u2B1C" : r.status === "manual" ? "\u{1F4CB}" : "\u{1F3E2}";
7611
7736
  const cls = `check-${r.status === "cloud_provider" ? "cloud" : r.status}`;
7612
- const suffix = r.status === "unknown" ? " \u2014 \u672A\u68C0\u67E5" : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? "\u9700\u4EBA\u5DE5\u8BC4\u4F30")}` : "";
7737
+ const suffix = r.status === "unknown" ? ` \u2014 ${esc(t.notChecked)}` : r.status === "manual" ? ` \u2014 ${esc(r.mapping.guidance ?? t.manualReview)}` : "";
7613
7738
  let findingsDetail = "";
7614
7739
  if (r.status === "clean") {
7615
- findingsDetail = `<div class="check-detail">\u68C0\u67E5\u7ED3\u679C\uFF1A\u672A\u53D1\u73B0\u76F8\u5173\u95EE\u9898</div>`;
7740
+ findingsDetail = `<div class="check-detail">${esc(t.noRelatedIssues)}</div>`;
7616
7741
  } else if (r.status === "issues" && r.relatedFindings.length > 0) {
7617
7742
  const fItems = r.relatedFindings.slice(0, 5).map((f) => `<li>${esc(f.severity)}: ${esc(f.title)}</li>`);
7618
7743
  if (r.relatedFindings.length > 5) {
7619
- fItems.push(`<li>... \u53CA\u5176\u4ED6 ${r.relatedFindings.length - 5} \u9879</li>`);
7744
+ fItems.push(`<li>${esc(t.andMore(r.relatedFindings.length - 5))}</li>`);
7620
7745
  }
7621
- const remediationHint = r.relatedFindings[0]?.remediationSteps?.[0] ? `<p style="color:#fbbf24;font-size:12px;margin-top:4px">\u5EFA\u8BAE\uFF1A${esc(r.relatedFindings[0].remediationSteps[0])}</p>` : "";
7622
- findingsDetail = `<div class="check-findings-wrap"><details><summary>\u68C0\u67E5\u7ED3\u679C\uFF1A\u53D1\u73B0 ${r.relatedFindings.length} \u4E2A\u76F8\u5173\u95EE\u9898</summary><ul class="check-findings">${fItems.join("")}</ul>${remediationHint}</details></div>`;
7746
+ 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>` : "";
7747
+ findingsDetail = `<div class="check-findings-wrap"><details><summary>${esc(t.issuesFoundCount(r.relatedFindings.length))}</summary><ul class="check-findings">${fItems.join("")}</ul>${remediationHint}</details></div>`;
7623
7748
  }
7624
- itemsHtml += `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc(r.item.id)} ${esc(r.item.requirementCn.slice(0, 60))}${r.item.requirementCn.length > 60 ? "\u2026" : ""}${suffix}</span></div>
7749
+ const reqText = itemReq(r);
7750
+ itemsHtml += `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc(r.item.id)} ${esc(reqText.slice(0, 60))}${reqText.length > 60 ? "\u2026" : ""}${suffix}</span></div>
7625
7751
  ${findingsDetail}`;
7626
7752
  }
7627
7753
  if (cloudItems.length > 0) {
7628
7754
  for (const r of cloudItems) {
7629
- itemsHtml += `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(r.item.requirementCn.slice(0, 50))}${r.item.requirementCn.length > 50 ? "\u2026" : ""}</span><span class="check-note">\u4E91\u5E73\u53F0\u8D1F\u8D23</span></div>
7755
+ const reqText = itemReq(r);
7756
+ itemsHtml += `<div class="check-item check-cloud"><span class="check-icon">\u{1F3E2}</span><span class="check-name">${esc(r.item.id)} ${esc(reqText.slice(0, 50))}${reqText.length > 50 ? "\u2026" : ""}</span><span class="check-note">${esc(t.cloudProvider)}</span></div>
7630
7757
  `;
7631
7758
  }
7632
7759
  }
@@ -7659,20 +7786,61 @@ ${itemsHtml}
7659
7786
  let remediationHtml = "";
7660
7787
  if (failedResults.length > 0) {
7661
7788
  const mlpsRecMap = /* @__PURE__ */ new Map();
7789
+ const mlpsKbPatches = [];
7790
+ let mlpsKbSeverity = "LOW";
7791
+ let mlpsKbUrl;
7792
+ const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
7662
7793
  for (const r of failedResults) {
7663
7794
  for (const f of r.relatedFindings) {
7664
7795
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
7796
+ const url = f.remediationSteps.find((s) => s.startsWith("Documentation:"))?.replace("Documentation: ", "");
7797
+ if (mlpsGenericPatterns.some((p) => rem.startsWith(p))) continue;
7798
+ const kbMatch = f.title.match(/KB\d+/);
7799
+ if (kbMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
7800
+ mlpsKbPatches.push(kbMatch[0]);
7801
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsKbSeverity)) mlpsKbSeverity = f.severity;
7802
+ if (!mlpsKbUrl && url) mlpsKbUrl = url;
7803
+ continue;
7804
+ }
7805
+ if (f.module === "security_hub_findings") {
7806
+ const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
7807
+ if (controlMatch) {
7808
+ const controlId = controlMatch[1];
7809
+ const key = `ctrl:${controlId}`;
7810
+ const existing2 = mlpsRecMap.get(key);
7811
+ if (existing2) {
7812
+ existing2.count++;
7813
+ if (!existing2.url && url) existing2.url = url;
7814
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing2.severity)) existing2.severity = f.severity;
7815
+ } else {
7816
+ mlpsRecMap.set(key, { text: `[${controlId}] ${rem}`, severity: f.severity, count: 1, url });
7817
+ }
7818
+ continue;
7819
+ }
7820
+ }
7665
7821
  const existing = mlpsRecMap.get(rem);
7666
7822
  if (existing) {
7667
7823
  existing.count++;
7824
+ if (!existing.url && url) existing.url = url;
7668
7825
  if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
7669
7826
  existing.severity = f.severity;
7670
7827
  }
7671
7828
  } else {
7672
- mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
7829
+ mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1, url });
7673
7830
  }
7674
7831
  }
7675
7832
  }
7833
+ if (mlpsKbPatches.length > 0) {
7834
+ const unique = [...new Set(mlpsKbPatches)];
7835
+ const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
7836
+ mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
7837
+ }
7838
+ for (const [key, rec] of mlpsRecMap) {
7839
+ if (key.startsWith("ctrl:") && rec.count > 1) {
7840
+ rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
7841
+ rec.count = 1;
7842
+ }
7843
+ }
7676
7844
  const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
7677
7845
  const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
7678
7846
  if (sevDiff !== 0) return sevDiff;
@@ -7682,26 +7850,27 @@ ${itemsHtml}
7682
7850
  const renderMlpsRec = (r) => {
7683
7851
  const sev = r.severity.toLowerCase();
7684
7852
  const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
7685
- return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}</li>`;
7853
+ const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">&#128214;</a>` : "";
7854
+ return `<li><span class="badge badge-${esc(sev)}">${esc(r.severity)}</span> ${esc(r.text)}${countLabel}${linkHtml}</li>`;
7686
7855
  };
7687
7856
  const MLPS_TOP_N = 10;
7688
7857
  const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
7689
7858
  const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
7690
7859
  const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
7691
- <details><summary>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879&hellip;</summary>
7860
+ <details><summary>${esc(t.showRemaining(mlpsRemaining.length))}&hellip;</summary>
7692
7861
  ${mlpsRemaining.map(renderMlpsRec).join("\n")}
7693
7862
  </details>` : "";
7694
7863
  remediationHtml = `
7695
7864
  <details class="rec-fold" open>
7696
- <summary><h2 style="margin:0;border:0;display:inline">\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${mlpsUniqueRecs.length} \u9879\u53BB\u91CD\uFF09</h2></summary>
7865
+ <summary><h2 style="margin:0;border:0;display:inline">${esc(t.remediationItems(mlpsUniqueRecs.length))}</h2></summary>
7697
7866
  <div class="rec-body">
7698
7867
  <ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
7699
7868
  </div>
7700
7869
  </details>`;
7701
7870
  }
7702
7871
  }
7703
- const naNote = naCount > 0 ? `<p style="color:#64748b;font-size:13px;margin-top:24px">\u4E0D\u9002\u7528\u9879: ${naCount} \u9879\uFF08\u7269\u8054\u7F51/\u65E0\u7EBF\u7F51\u7EDC/\u79FB\u52A8\u7EC8\u7AEF/\u5DE5\u63A7\u7CFB\u7EDF/\u53EF\u4FE1\u9A8C\u8BC1\u7B49\uFF09</p>` : "";
7704
- const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">\uFF08${autoUnknown} \u9879\u672A\u68C0\u67E5\uFF0C\u5BF9\u5E94\u626B\u63CF\u6A21\u5757\u672A\u8FD0\u884C\uFF09</div>` : "";
7872
+ const naNote = naCount > 0 ? `<p style="color:#64748b;font-size:13px;margin-top:24px">${esc(t.naNote(naCount))}</p>` : "";
7873
+ const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">${esc(t.unknownNote(autoUnknown))}</div>` : "";
7705
7874
  const mlpsCss = `
7706
7875
  .mlps-cloud-section>summary{color:#94a3b8}
7707
7876
  .mlps-cloud-note{color:#94a3b8;font-size:13px;margin-bottom:12px;font-style:italic}
@@ -7723,40 +7892,40 @@ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
7723
7892
  .mlps-summary-card .stat-label{font-size:12px;color:#94a3b8;margin-top:2px}
7724
7893
  `;
7725
7894
  return `<!DOCTYPE html>
7726
- <html lang="zh-CN">
7895
+ <html lang="${htmlLang}">
7727
7896
  <head>
7728
7897
  <meta charset="UTF-8">
7729
7898
  <meta name="viewport" content="width=device-width,initial-scale=1">
7730
- <title>\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A &mdash; ${esc(date)}</title>
7899
+ <title>${esc(t.mlpsTitle)} &mdash; ${esc(date)}</title>
7731
7900
  <style>${sharedCss()}${mlpsCss}</style>
7732
7901
  </head>
7733
7902
  <body>
7734
7903
  <div class="container">
7735
7904
 
7736
7905
  <header>
7737
- <h1>&#128737;&#65039; \u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u62A5\u544A</h1>
7738
- <div class="disclaimer">\u672C\u62A5\u544A\u4E3A\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0\u53C2\u8003\uFF0C\u63D0\u4F9B\u4E91\u5E73\u53F0\u914D\u7F6E\u68C0\u67E5\u6570\u636E\u4E0E\u5EFA\u8BAE\u3002\u5408\u89C4\u5224\u5B9A\uFF08\u7B26\u5408/\u90E8\u5206\u7B26\u5408/\u4E0D\u7B26\u5408\uFF09\u9700\u7531\u6301\u8BC1\u6D4B\u8BC4\u673A\u6784\u6839\u636E\u5B9E\u9645\u60C5\u51B5\u786E\u8BA4\u3002\uFF08GB/T 22239-2019 \u5B8C\u6574\u68C0\u67E5\u6E05\u5355 184 \u9879\uFF09</div>
7739
- <div class="meta">\u8D26\u6237: ${esc(accountId)} | \u533A\u57DF: ${esc(region)} | \u626B\u63CF\u65F6\u95F4: ${esc(scanTime)}</div>
7906
+ <h1>&#128737;&#65039; ${esc(t.mlpsTitle)}</h1>
7907
+ <div class="disclaimer">${esc(t.mlpsDisclaimer)}</div>
7908
+ <div class="meta">${esc(t.account)}: ${esc(accountId)} | ${esc(t.region)}: ${esc(region)} | ${esc(t.scanTime)}: ${esc(scanTime)}</div>
7740
7909
  </header>
7741
7910
 
7742
7911
  <section class="summary" style="display:block;text-align:center">
7743
7912
  <div style="font-size:36px;font-weight:700;margin-bottom:12px">
7744
- <span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">\u672A\u53D1\u73B0\u95EE\u9898</span>
7913
+ <span style="color:#22c55e">${autoClean}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.noIssues)}</span>
7745
7914
  <span style="color:#475569;margin:0 16px">/</span>
7746
- <span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px">\u53D1\u73B0\u95EE\u9898</span>
7915
+ <span style="color:#ef4444">${autoIssues}</span> <span style="color:#94a3b8;font-size:18px">${esc(t.issuesFound)}</span>
7747
7916
  </div>
7748
7917
  <div class="mlps-summary-cards" style="justify-content:center">
7749
- <div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">\u5DF2\u68C0\u67E5\u9879</div></div>
7750
- <div class="mlps-summary-card"><div class="stat-count" style="color:#94a3b8">${cloudCount}</div><div class="stat-label">\u{1F3E2} \u4E91\u5E73\u53F0\u8D1F\u8D23</div></div>
7751
- <div class="mlps-summary-card"><div class="stat-count" style="color:#eab308">${manualCount}</div><div class="stat-label">\u{1F4CB} \u9700\u4EBA\u5DE5\u8BC4\u4F30</div></div>
7752
- ${naCount > 0 ? `<div class="mlps-summary-card"><div class="stat-count" style="color:#64748b">${naCount}</div><div class="stat-label">\u2796 \u4E0D\u9002\u7528</div></div>` : ""}
7918
+ <div class="mlps-summary-card"><div class="stat-count" style="color:#60a5fa">${checkedTotal}</div><div class="stat-label">${esc(t.checkedItems)}</div></div>
7919
+ <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>
7920
+ <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>
7921
+ ${naCount > 0 ? `<div class="mlps-summary-card"><div class="stat-count" style="color:#64748b">${naCount}</div><div class="stat-label">\u2796 ${esc(t.notApplicable)}</div></div>` : ""}
7753
7922
  </div>
7754
7923
  </section>
7755
7924
  ${unknownNote}
7756
7925
 
7757
7926
  ${trendHtml}
7758
7927
 
7759
- ${buildServiceReminderHtml(scanResults.modules)}
7928
+ ${buildServiceReminderHtml(scanResults.modules, lang)}
7760
7929
 
7761
7930
  ${categorySections}
7762
7931
 
@@ -7765,8 +7934,8 @@ ${remediationHtml}
7765
7934
  ${naNote}
7766
7935
 
7767
7936
  <footer>
7768
- <p>\u7531 AWS Security MCP Server v${VERSION} \u751F\u6210</p>
7769
- <p>\u672C\u62A5\u544A\u4E3A\u8BC1\u636E\u6536\u96C6\u53C2\u8003\uFF0C\u4E0D\u5305\u542B\u5408\u89C4\u5224\u5B9A\u3002\u5B8C\u6574\u7B49\u4FDD\u6D4B\u8BC4\u9700\u7531\u6301\u8BC1\u6D4B\u8BC4\u673A\u6784\u6267\u884C\u3002</p>
7937
+ <p>${esc(t.mlpsFooterGenerated(VERSION))}</p>
7938
+ <p>${esc(t.mlpsFooterDisclaimer)}</p>
7770
7939
  </footer>
7771
7940
 
7772
7941
  </div>
@@ -7981,16 +8150,14 @@ Aggregates active findings from AWS Security Hub. Replaces individual config sca
7981
8150
  - INFORMATIONAL findings are skipped.
7982
8151
 
7983
8152
  ## 3. GuardDuty Findings (guardduty_findings)
7984
- Aggregates threat detection findings from Amazon GuardDuty.
7985
- - Covers account compromise, instance compromise, and reconnaissance.
7986
- - Severity mapped from GuardDuty 0\u201310 scale: \u22657 \u2192 HIGH, \u22654 \u2192 MEDIUM, <4 \u2192 LOW.
7987
- - Only non-archived findings are included.
8153
+ Detection-only: checks if GuardDuty is enabled in the region.
8154
+ - GuardDuty findings are aggregated via Security Hub (security_hub_findings module).
8155
+ - Reports whether GuardDuty detectors are active.
7988
8156
 
7989
8157
  ## 4. Inspector Findings (inspector_findings)
7990
- Aggregates vulnerability findings from Amazon Inspector v2.
7991
- - Covers CVEs in EC2 instances, Lambda functions, and container images.
7992
- - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
7993
- - CVE IDs are included in finding titles when available.
8158
+ Detection-only: checks if Inspector is enabled in the region.
8159
+ - Inspector findings are aggregated via Security Hub (security_hub_findings module).
8160
+ - Reports whether Inspector scanning (EC2/Lambda) is active.
7994
8161
 
7995
8162
  ## 5. Trusted Advisor Findings (trusted_advisor_findings)
7996
8163
  Aggregates security checks from AWS Trusted Advisor.
@@ -8026,19 +8193,15 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
8026
8193
  Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
8027
8194
 
8028
8195
  ## 15. Config Rules Findings (config_rules_findings)
8029
- Pulls non-compliant AWS Config Rule evaluation results.
8030
- - Lists all Config Rules and their compliance status.
8031
- - For NON_COMPLIANT rules, retrieves specific non-compliant resources.
8032
- - Security-related rules (encryption, IAM, public access, etc.) mapped to HIGH severity (7.5).
8033
- - Other non-compliant rules mapped to MEDIUM severity (5.5).
8196
+ Detection-only: checks if AWS Config Rules are configured.
8197
+ - Config Rule compliance findings are aggregated via Security Hub (security_hub_findings module).
8198
+ - Reports whether Config is enabled and counts active rules.
8034
8199
  - Gracefully handles regions where AWS Config is not enabled.
8035
8200
 
8036
8201
  ## 16. IAM Access Analyzer Findings (access_analyzer_findings)
8037
- Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account.
8038
- - Lists active analyzers (ACCOUNT or ORGANIZATION type).
8039
- - Retrieves ACTIVE findings showing external access to resources.
8040
- - Covers S3 buckets, IAM roles, SQS queues, Lambda functions, KMS keys, and more.
8041
- - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
8202
+ Detection-only: checks if IAM Access Analyzer is configured.
8203
+ - Access Analyzer findings are aggregated via Security Hub (security_hub_findings module).
8204
+ - Reports whether active analyzers exist.
8042
8205
  - Returns warning if no analyzer is configured.
8043
8206
 
8044
8207
  ## 17. SSM Patch Compliance (patch_compliance_findings)
@@ -8123,112 +8286,18 @@ var MODULE_DESCRIPTIONS = {
8123
8286
  idle_resources: "Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped instances, unused security groups) that waste money and increase attack surface.",
8124
8287
  disaster_recovery: "Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.",
8125
8288
  security_hub_findings: "Aggregates active findings from AWS Security Hub \u2014 replaces individual config scanners with centralized compliance checks.",
8126
- guardduty_findings: "Aggregates threat detection findings from Amazon GuardDuty \u2014 account compromise, instance compromise, and reconnaissance.",
8127
- inspector_findings: "Aggregates vulnerability findings from Amazon Inspector \u2014 CVEs in EC2, Lambda, and container images.",
8289
+ guardduty_findings: "Checks if GuardDuty is enabled. Findings are aggregated via Security Hub.",
8290
+ inspector_findings: "Checks if Inspector is enabled. Findings are aggregated via Security Hub.",
8128
8291
  trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
8129
- config_rules_findings: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
8130
- access_analyzer_findings: "Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account (external principals, public access).",
8292
+ config_rules_findings: "Checks if AWS Config Rules are configured. Findings are aggregated via Security Hub.",
8293
+ access_analyzer_findings: "Checks if IAM Access Analyzer is configured. Findings are aggregated via Security Hub.",
8131
8294
  patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches.",
8132
8295
  imdsv2_enforcement: "Checks if EC2 instances enforce IMDSv2 (HttpTokens: required) \u2014 IMDSv1 allows credential theft via SSRF.",
8133
8296
  waf_coverage: "Checks if internet-facing ALBs have WAF Web ACL associated for protection against common web exploits."
8134
8297
  };
8135
- var HW_DEFENSE_CHECKLIST = `
8136
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8137
- \u{1F4CB} \u62A4\u7F51\u884C\u52A8\u8865\u5145\u63D0\u9192\uFF08\u8D85\u51FA\u81EA\u52A8\u5316\u626B\u63CF\u8303\u56F4\uFF09
8138
- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8139
-
8140
- \u4EE5\u4E0B\u4E8B\u9879\u9700\u8981\u4EBA\u5DE5\u786E\u8BA4\u548C\u6267\u884C\uFF1A
8141
-
8142
- \u26A0\uFE0F \u5E94\u6025\u9694\u79BB/\u6B62\u8840\u65B9\u6848
8143
- \u25A1 \u51C6\u5907\u4E13\u7528\u9694\u79BB\u5B89\u5168\u7EC4\uFF08\u65E0 Inbound/Outbound \u89C4\u5219\uFF09
8144
- \u25A1 \u5236\u5B9A\u5B9E\u4F8B\u9694\u79BB SOP\uFF1A\u544A\u8B66 \u2192 \u6392\u67E5 \u2192 \u5C01\u9501\u653B\u51FBIP \u2192 \u7F51\u7EDC\u9694\u79BB \u2192 \u5B89\u5168\u5904\u7F6E \u2192 \u8BB0\u5F55\u653B\u51FB\u9879
8145
- \u25A1 \u660E\u786E\u5404\u7CFB\u7EDF\uFF08\u751F\u4EA7\u6838\u5FC3/\u751F\u4EA7\u975E\u6838\u5FC3/\u6D4B\u8BD5/\u5F00\u53D1\uFF09\u7684\u5E94\u6025\u5904\u7F6E\u65B9\u5F0F
8146
- \u25A1 \u660E\u786E\u5404\u9879\u76EE\u8D26\u6237\u53CA\u8D44\u6E90\u7684\u8D1F\u8D23\u4EBA\u4E0E\u8054\u7CFB\u65B9\u5F0F
8147
-
8148
- \u26A0\uFE0F \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5904\u7F6E
8149
- \u25A1 \u975E\u6838\u5FC3\u7CFB\u7EDF\u5728\u62A4\u7F51\u671F\u95F4\u5173\u95ED
8150
- \u25A1 \u6D4B\u8BD5/\u5F00\u53D1\u73AF\u5883\u5173\u95ED\u6216\u4E0E\u751F\u4EA7\u4FDD\u6301\u540C\u7B49\u5B89\u5168\u57FA\u7EBF
8151
- \u25A1 \u786E\u8BA4\u54EA\u4E9B\u73AF\u5883\u53EF\u4EE5\u7D27\u6025\u5173\u505C\uFF0C\u907F\u514D\u653B\u51FB\u6269\u6563
8152
-
8153
- \u26A0\uFE0F \u503C\u5B88\u56E2\u961F\u7EC4\u5EFA
8154
- \u25A1 7\xD724 \u76D1\u63A7\u5FEB\u901F\u54CD\u5E94\u56E2\u961F
8155
- \u25A1 \u6280\u672F\u4E0E\u98CE\u9669\u5206\u6790\u7EC4
8156
- \u25A1 \u5B89\u5168\u7B56\u7565\u4E0B\u53D1\u7EC4
8157
- \u25A1 \u4E1A\u52A1\u54CD\u5E94\u7EC4
8158
- \u25A1 \u660E\u786E AWS TAM/Support \u8054\u7CFB\u65B9\u5F0F\uFF08ES/EOP \u5BA2\u6237\uFF09
8159
-
8160
- \u26A0\uFE0F \u51FA\u5165\u7AD9\u8DEF\u5F84\u67B6\u6784\u56FE
8161
- \u25A1 \u786E\u4FDD\u6240\u6709\u4E92\u8054\u7F51/DX \u4E13\u7EBF\u51FA\u5165\u7AD9\u8DEF\u5F84\u5728\u67B6\u6784\u56FE\u4E2D\u6E05\u6670\u6807\u6CE8
8162
- \u25A1 \u660E\u786E\u5404 ELB/Public EC2/S3/DX \u7684\u6570\u636E\u6D41\u5411
8163
- \u25A1 \u8BC6\u522B\u6240\u6709\u9762\u5411\u4E92\u8054\u7F51\u7684\u6570\u636E\u4EA4\u4E92\u63A5\u53E3
8164
-
8165
- \u26A0\uFE0F \u4E3B\u52A8\u5F0F\u6E17\u900F\u6D4B\u8BD5
8166
- \u25A1 \u62A4\u7F51\u524D\u8054\u7CFB\u5B89\u5168\u5382\u5546\uFF08\u9752\u85E4/\u957F\u4EAD/\u5FAE\u6B65\u7B49\uFF09\u8FDB\u884C\u6A21\u62DF\u653B\u51FB\u6F14\u7EC3
8167
- \u25A1 \u57FA\u4E8E\u6E17\u900F\u6D4B\u8BD5\u62A5\u544A\u8FDB\u884C\u6B63\u5F0F\u62A4\u7F51\u524D\u7684\u5B89\u5168\u52A0\u56FA
8168
- \u25A1 \u5173\u6CE8 AWS \u5B89\u5168\u516C\u544A\uFF08\u5DF2\u77E5\u6F0F\u6D1E\u4E0E\u8865\u4E01\uFF09
8169
-
8170
- \u26A0\uFE0F WAR-ROOM \u5B9E\u65F6\u6C9F\u901A
8171
- \u25A1 \u521B\u5EFA\u62A4\u7F51\u671F\u95F4\u4E13\u7528\u6C9F\u901A\u6E20\u9053\uFF08\u4F01\u5FAE/\u9489\u9489/\u98DE\u4E66/Chime\uFF09
8172
- \u25A1 \u4E0E AWS TAM \u5EFA\u7ACB WAR-ROOM \u8054\u7CFB\uFF08\u4F01\u4E1A\u7EA7\u652F\u6301\u5BA2\u6237\uFF09
8173
- \u25A1 \u7EDF\u4E00\u6848\u4F8B\u6807\u9898\u683C\u5F0F\uFF1A"\u3010\u62A4\u7F51\u3011+ \u95EE\u9898\u63CF\u8FF0"
8174
-
8175
- \u26A0\uFE0F \u5BC6\u7801\u4E0E\u51ED\u8BC1\u7BA1\u7406
8176
- \u25A1 \u6240\u6709 IAM \u7528\u6237\u7ED1\u5B9A MFA
8177
- \u25A1 AKSK \u8F6E\u8F6C\u5468\u671F \u2264 90 \u5929
8178
- \u25A1 \u907F\u514D\u5171\u4EAB\u8D26\u6237\u4F7F\u7528
8179
- \u25A1 S3/Lambda/\u5E94\u7528\u4EE3\u7801\u4E2D\u65E0\u660E\u6587\u5BC6\u7801
8180
-
8181
- \u26A0\uFE0F \u62A4\u7F51\u540E\u4F18\u5316
8182
- \u25A1 \u9488\u5BF9\u653B\u51FB\u62A5\u544A\u9010\u9879\u5E94\u7B54\u4E0E\u4FEE\u590D
8183
- \u25A1 \u4E0E\u5B89\u5168\u56E2\u961F\u5EFA\u7ACB\u5468\u671F\u6027\u5B89\u5168\u7EF4\u62A4\u6D41\u7A0B
8184
- \u25A1 \u6301\u7EED\u8865\u5168\u5B89\u5168\u98CE\u9669
8185
-
8186
- \u53C2\u8003\uFF1AAWS \u62A4\u7F51\u884C\u52A8 Standard Operation Procedure (Compliance IEM)
8187
- `;
8188
- var SERVICE_RECOMMENDATIONS2 = {
8189
- security_hub_findings: {
8190
- icon: "\u{1F534}",
8191
- service: "Security Hub",
8192
- impact: "\u65E0\u6CD5\u83B7\u53D6 300+ \u9879\u81EA\u52A8\u5316\u5B89\u5168\u68C0\u67E5\uFF08FSBP/CIS/PCI DSS \u6807\u51C6\uFF09",
8193
- action: "\u542F\u7528 Security Hub \u83B7\u5F97\u6700\u5168\u9762\u7684\u5B89\u5168\u6001\u52BF\u8BC4\u4F30"
8194
- },
8195
- guardduty_findings: {
8196
- icon: "\u{1F534}",
8197
- service: "GuardDuty",
8198
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u5A01\u80C1\u6D3B\u52A8\uFF08\u6076\u610F IP\u3001\u5F02\u5E38 API \u8C03\u7528\u3001\u52A0\u5BC6\u8D27\u5E01\u6316\u77FF\u7B49\uFF09",
8199
- action: "\u542F\u7528 GuardDuty \u83B7\u5F97\u6301\u7EED\u5A01\u80C1\u68C0\u6D4B\u80FD\u529B"
8200
- },
8201
- inspector_findings: {
8202
- icon: "\u{1F7E1}",
8203
- service: "Inspector",
8204
- impact: "\u65E0\u6CD5\u626B\u63CF EC2/Lambda/\u5BB9\u5668\u7684\u8F6F\u4EF6\u6F0F\u6D1E\uFF08CVE\uFF09",
8205
- action: "\u542F\u7528 Inspector \u53D1\u73B0\u5DF2\u77E5\u5B89\u5168\u6F0F\u6D1E"
8206
- },
8207
- trusted_advisor_findings: {
8208
- icon: "\u{1F7E1}",
8209
- service: "Trusted Advisor",
8210
- impact: "\u65E0\u6CD5\u83B7\u53D6 AWS \u6700\u4F73\u5B9E\u8DF5\u5B89\u5168\u68C0\u67E5",
8211
- action: "\u5347\u7EA7\u81F3 Business/Enterprise Support \u8BA1\u5212\u4EE5\u4F7F\u7528 Trusted Advisor \u5B89\u5168\u68C0\u67E5"
8212
- },
8213
- config_rules_findings: {
8214
- icon: "\u{1F7E1}",
8215
- service: "AWS Config",
8216
- impact: "\u65E0\u6CD5\u68C0\u67E5\u8D44\u6E90\u914D\u7F6E\u5408\u89C4\u72B6\u6001",
8217
- action: "\u542F\u7528 AWS Config \u5E76\u914D\u7F6E Config Rules"
8218
- },
8219
- access_analyzer_findings: {
8220
- icon: "\u{1F7E1}",
8221
- service: "IAM Access Analyzer",
8222
- impact: "\u65E0\u6CD5\u68C0\u6D4B\u8D44\u6E90\u662F\u5426\u88AB\u5916\u90E8\u8D26\u53F7\u6216\u516C\u7F51\u8BBF\u95EE",
8223
- action: "\u521B\u5EFA IAM Access Analyzer\uFF08\u8D26\u6237\u7EA7\u6216\u7EC4\u7EC7\u7EA7\uFF09"
8224
- },
8225
- patch_compliance_findings: {
8226
- icon: "\u{1F7E1}",
8227
- service: "SSM Patch Manager",
8228
- impact: "\u65E0\u6CD5\u68C0\u67E5\u5B9E\u4F8B\u8865\u4E01\u5408\u89C4\u72B6\u6001",
8229
- action: "\u5B89\u88C5 SSM Agent \u5E76\u914D\u7F6E Patch Manager"
8230
- }
8231
- };
8298
+ function getHwDefenseChecklist(lang) {
8299
+ return getI18n(lang ?? "zh").hwChecklist;
8300
+ }
8232
8301
  var SERVICE_NOT_ENABLED_PATTERNS2 = [
8233
8302
  "not enabled",
8234
8303
  "not found",
@@ -8238,10 +8307,11 @@ var SERVICE_NOT_ENABLED_PATTERNS2 = [
8238
8307
  "not available",
8239
8308
  "is not enabled"
8240
8309
  ];
8241
- function buildServiceReminder(modules) {
8310
+ function buildServiceReminder(modules, lang) {
8311
+ const t = getI18n(lang ?? "zh");
8242
8312
  const disabledServices = [];
8243
8313
  for (const mod of modules) {
8244
- const rec = SERVICE_RECOMMENDATIONS2[mod.module];
8314
+ const rec = t.serviceRecommendations[mod.module];
8245
8315
  if (!rec) continue;
8246
8316
  if (!mod.warnings?.length) continue;
8247
8317
  const hasNotEnabled = mod.warnings.some(
@@ -8254,26 +8324,26 @@ function buildServiceReminder(modules) {
8254
8324
  if (disabledServices.length === 0) return "";
8255
8325
  const lines = [
8256
8326
  "",
8257
- "\u26A1 \u4EE5\u4E0B\u5B89\u5168\u670D\u52A1\u672A\u542F\u7528\uFF0C\u90E8\u5206\u68C0\u67E5\u65E0\u6CD5\u6267\u884C\uFF1A",
8327
+ t.serviceReminderTitle,
8258
8328
  ""
8259
8329
  ];
8260
8330
  for (const svc of disabledServices) {
8261
- lines.push(`${svc.icon} ${svc.service} \u672A\u542F\u7528`);
8262
- lines.push(` \u5F71\u54CD\uFF1A${svc.impact}`);
8263
- lines.push(` \u5EFA\u8BAE\uFF1A${svc.action}`);
8331
+ lines.push(`${svc.icon} ${svc.service} ${t.notEnabled}`);
8332
+ lines.push(` ${t.serviceImpact}: ${svc.impact}`);
8333
+ lines.push(` ${t.serviceAction}: ${svc.action}`);
8264
8334
  lines.push("");
8265
8335
  }
8266
- lines.push("\u542F\u7528\u4EE5\u4E0A\u670D\u52A1\u540E\u91CD\u65B0\u626B\u63CF\u53EF\u83B7\u5F97\u66F4\u5B8C\u6574\u7684\u5B89\u5168\u8BC4\u4F30\u3002");
8336
+ lines.push(t.serviceReminderFooter);
8267
8337
  return lines.join("\n");
8268
8338
  }
8269
- function summarizeResult(result) {
8339
+ function summarizeResult(result, lang) {
8270
8340
  const { summary } = result;
8271
8341
  const lines = [
8272
8342
  `Scan complete for account ${result.accountId} in ${result.region}.`,
8273
8343
  `Total findings: ${summary.totalFindings} (${summary.critical} Critical, ${summary.high} High, ${summary.medium} Medium, ${summary.low} Low)`,
8274
8344
  `Modules: ${summary.modulesSuccess} succeeded, ${summary.modulesError} errored`
8275
8345
  ];
8276
- const reminder = buildServiceReminder(result.modules);
8346
+ const reminder = buildServiceReminder(result.modules, lang);
8277
8347
  if (reminder) {
8278
8348
  lines.push(reminder);
8279
8349
  }
@@ -8335,9 +8405,10 @@ function createServer(defaultRegion) {
8335
8405
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8336
8406
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8337
8407
  role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
8338
- account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)")
8408
+ account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
8409
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8339
8410
  },
8340
- async ({ region, org_mode, role_name, account_ids }) => {
8411
+ async ({ region, org_mode, role_name, account_ids, lang }) => {
8341
8412
  try {
8342
8413
  const r = region ?? defaultRegion;
8343
8414
  let result;
@@ -8352,7 +8423,7 @@ function createServer(defaultRegion) {
8352
8423
  }
8353
8424
  return {
8354
8425
  content: [
8355
- { type: "text", text: summarizeResult(result) },
8426
+ { type: "text", text: summarizeResult(result, lang ?? "zh") },
8356
8427
  { type: "text", text: JSON.stringify(result, null, 2) }
8357
8428
  ]
8358
8429
  };
@@ -8413,9 +8484,10 @@ function createServer(defaultRegion) {
8413
8484
  region: z.string().optional().describe("AWS region to scan (default: server region)"),
8414
8485
  org_mode: z.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
8415
8486
  role_name: z.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
8416
- account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)")
8487
+ account_ids: z.array(z.string()).optional().describe("Specific account IDs to scan (default: all org accounts)"),
8488
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8417
8489
  },
8418
- async ({ group, region, org_mode, role_name, account_ids }) => {
8490
+ async ({ group, region, org_mode, role_name, account_ids, lang }) => {
8419
8491
  try {
8420
8492
  const groupDef = SCAN_GROUPS[group];
8421
8493
  if (!groupDef) {
@@ -8497,7 +8569,7 @@ function createServer(defaultRegion) {
8497
8569
  `Scan group: ${groupDef.name} (${group})`,
8498
8570
  groupDef.description,
8499
8571
  "",
8500
- summarizeResult(result)
8572
+ summarizeResult(result, lang ?? "zh")
8501
8573
  ];
8502
8574
  if (missingModules.length > 0) {
8503
8575
  lines.push("");
@@ -8510,7 +8582,7 @@ function createServer(defaultRegion) {
8510
8582
  if (group === "hw_defense") {
8511
8583
  const summaryContent = content[0];
8512
8584
  if (summaryContent && summaryContent.type === "text") {
8513
- summaryContent.text += "\n\n" + HW_DEFENSE_CHECKLIST;
8585
+ summaryContent.text += "\n\n" + getHwDefenseChecklist(lang ?? "zh");
8514
8586
  }
8515
8587
  }
8516
8588
  return { content };
@@ -8540,11 +8612,14 @@ function createServer(defaultRegion) {
8540
8612
  server.tool(
8541
8613
  "generate_report",
8542
8614
  "Generate a Markdown security report from scan results. Read-only. Does not modify any AWS resources.",
8543
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
8544
- async ({ scan_results }) => {
8615
+ {
8616
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
8617
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8618
+ },
8619
+ async ({ scan_results, lang }) => {
8545
8620
  try {
8546
8621
  const parsed = JSON.parse(scan_results);
8547
- const report = generateMarkdownReport(parsed);
8622
+ const report = generateMarkdownReport(parsed, lang ?? "zh");
8548
8623
  return { content: [{ type: "text", text: report }] };
8549
8624
  } catch (err) {
8550
8625
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8554,11 +8629,14 @@ function createServer(defaultRegion) {
8554
8629
  server.tool(
8555
8630
  "generate_mlps3_report",
8556
8631
  "Generate a GB/T 22239-2019 \u7B49\u4FDD\u4E09\u7EA7 compliance pre-check report from scan results. Best used with scan_group mlps3_precheck results. Read-only.",
8557
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all") },
8558
- async ({ scan_results }) => {
8632
+ {
8633
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
8634
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8635
+ },
8636
+ async ({ scan_results, lang }) => {
8559
8637
  try {
8560
8638
  const parsed = JSON.parse(scan_results);
8561
- const report = generateMlps3Report(parsed);
8639
+ const report = generateMlps3Report(parsed, lang ?? "zh");
8562
8640
  return { content: [{ type: "text", text: report }] };
8563
8641
  } catch (err) {
8564
8642
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8570,13 +8648,14 @@ function createServer(defaultRegion) {
8570
8648
  "Generate a professional HTML security report. Save the output as an .html file.",
8571
8649
  {
8572
8650
  scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
8573
- history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts")
8651
+ history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
8652
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8574
8653
  },
8575
- async ({ scan_results, history }) => {
8654
+ async ({ scan_results, history, lang }) => {
8576
8655
  try {
8577
8656
  const parsed = JSON.parse(scan_results);
8578
8657
  const historyData = history ? JSON.parse(history) : void 0;
8579
- const report = generateHtmlReport(parsed, historyData);
8658
+ const report = generateHtmlReport(parsed, historyData, lang ?? "zh");
8580
8659
  return { content: [{ type: "text", text: report }] };
8581
8660
  } catch (err) {
8582
8661
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8588,13 +8667,14 @@ function createServer(defaultRegion) {
8588
8667
  "Generate a professional HTML MLPS Level 3 compliance report (\u7B49\u4FDD\u4E09\u7EA7). Save as .html file.",
8589
8668
  {
8590
8669
  scan_results: z.string().describe("JSON string of FullScanResult from scan_group mlps3_precheck or scan_all"),
8591
- history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts")
8670
+ history: z.string().optional().describe("JSON string of DashboardHistoryEntry[] from dashboard data.json for 30-day trend charts"),
8671
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8592
8672
  },
8593
- async ({ scan_results, history }) => {
8673
+ async ({ scan_results, history, lang }) => {
8594
8674
  try {
8595
8675
  const parsed = JSON.parse(scan_results);
8596
8676
  const historyData = history ? JSON.parse(history) : void 0;
8597
- const report = generateMlps3HtmlReport(parsed, historyData);
8677
+ const report = generateMlps3HtmlReport(parsed, historyData, lang ?? "zh");
8598
8678
  return { content: [{ type: "text", text: report }] };
8599
8679
  } catch (err) {
8600
8680
  return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
@@ -8604,7 +8684,10 @@ function createServer(defaultRegion) {
8604
8684
  server.tool(
8605
8685
  "generate_maturity_report",
8606
8686
  "Generate a security maturity assessment report from scan_all results. Requires service_detection module output. Read-only.",
8607
- { scan_results: z.string().describe("JSON string of FullScanResult from scan_all") },
8687
+ {
8688
+ scan_results: z.string().describe("JSON string of FullScanResult from scan_all"),
8689
+ lang: z.enum(["zh", "en"]).optional().describe("Report language (default: zh)")
8690
+ },
8608
8691
  async ({ scan_results }) => {
8609
8692
  try {
8610
8693
  const parsed = JSON.parse(scan_results);
@@ -8875,7 +8958,7 @@ ${finding}`
8875
8958
  type: "text",
8876
8959
  text: `\u8BF7\u57FA\u4E8E\u4EE5\u4E0B\u62A4\u7F51\u884C\u52A8\u68C0\u67E5\u6E05\u5355\uFF0C\u5E2E\u52A9\u6211\u5236\u5B9A\u62A4\u7F51\u51C6\u5907\u8BA1\u5212\uFF1A
8877
8960
 
8878
- ${HW_DEFENSE_CHECKLIST}
8961
+ ${getHwDefenseChecklist("zh")}
8879
8962
 
8880
8963
  \u81EA\u52A8\u5316\u626B\u63CF\u90E8\u5206\u8BF7\u4F7F\u7528 scan_group hw_defense \u6267\u884C\u3002\u4EE5\u4E0A\u4EBA\u5DE5\u68C0\u67E5\u9879\u8BF7\u9010\u9879\u786E\u8BA4\u5E76\u63D0\u4F9B\u5177\u4F53\u5EFA\u8BAE\u3002`
8881
8964
  }