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/bin/aws-security-mcp.js +871 -788
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +871 -788
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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
|
-
|
|
2739
|
-
|
|
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(`
|
|
2756
|
+
remediationSteps.push(`Documentation: ${f.Remediation.Recommendation.Url}`);
|
|
2743
2757
|
}
|
|
2744
|
-
|
|
2745
|
-
|
|
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
|
|
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
|
|
2826
|
-
const 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:
|
|
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
|
|
2923
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2967
|
-
const
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
const
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
}
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
3040
|
-
|
|
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: "
|
|
3065
|
-
|
|
3066
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3280
|
-
const
|
|
3281
|
-
|
|
3282
|
-
|
|
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:
|
|
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
|
|
3381
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3160
|
+
hasActiveAnalyzer = true;
|
|
3161
|
+
break;
|
|
3444
3162
|
}
|
|
3445
3163
|
}
|
|
3164
|
+
if (hasActiveAnalyzer) break;
|
|
3446
3165
|
analyzerToken = resp.nextToken;
|
|
3447
3166
|
} while (analyzerToken);
|
|
3448
|
-
if (
|
|
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:
|
|
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
|
|
3532
|
-
|
|
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
|
|
3989
|
-
const
|
|
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(`#
|
|
4115
|
+
lines.push(`# ${t.securityReportTitle} \u2014 ${date}`);
|
|
4007
4116
|
lines.push("");
|
|
4008
|
-
lines.push(
|
|
4009
|
-
lines.push(`-
|
|
4010
|
-
lines.push(`-
|
|
4011
|
-
lines.push(`-
|
|
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
|
-
`-
|
|
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(
|
|
4126
|
+
lines.push(`## ${t.findingsBySeverity}`);
|
|
4018
4127
|
lines.push("");
|
|
4019
|
-
lines.push(
|
|
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(
|
|
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
|
|
4144
|
+
lines.push(`### ${icon} ${sevLabel[sev]}`);
|
|
4036
4145
|
lines.push("");
|
|
4037
4146
|
if (findings.length === 0) {
|
|
4038
|
-
lines.push(
|
|
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(
|
|
4158
|
+
lines.push(`## ${t.scanStatistics}`);
|
|
4050
4159
|
lines.push(
|
|
4051
|
-
|
|
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(
|
|
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(
|
|
6791
|
-
lines.push(
|
|
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(
|
|
6795
|
-
lines.push(`-
|
|
6899
|
+
lines.push(`## ${t.accountInfo}`);
|
|
6900
|
+
lines.push(`- ${t.account}: ${accountId} | ${t.region}: ${region} | ${t.scanTime}: ${scanTime}`);
|
|
6796
6901
|
lines.push("");
|
|
6797
|
-
lines.push(
|
|
6798
|
-
lines.push(`-
|
|
6902
|
+
lines.push(`## ${t.preCheckOverview}`);
|
|
6903
|
+
lines.push(`- ${t.checkedCount(checkedTotal, autoClean, autoIssues)}`);
|
|
6799
6904
|
if (autoUnknown > 0) {
|
|
6800
|
-
lines.push(`-
|
|
6905
|
+
lines.push(`- ${t.uncheckedCount(autoUnknown)}`);
|
|
6801
6906
|
}
|
|
6802
|
-
lines.push(`-
|
|
6803
|
-
lines.push(`-
|
|
6907
|
+
lines.push(`- ${t.cloudProviderCount(cloudCount)}`);
|
|
6908
|
+
lines.push(`- ${t.manualReviewCount(manualCount)}`);
|
|
6804
6909
|
if (naCount > 0) {
|
|
6805
|
-
lines.push(`-
|
|
6910
|
+
lines.push(`- ${t.naCount(naCount)}`);
|
|
6806
6911
|
}
|
|
6807
6912
|
lines.push("");
|
|
6808
6913
|
for (const category of MLPS3_CATEGORY_ORDER) {
|
|
6809
|
-
const sectionTitle =
|
|
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 [
|
|
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" ?
|
|
6827
|
-
|
|
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(` -
|
|
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(
|
|
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(`>
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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 =
|
|
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
|
|
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)}
|
|
6972
|
-
<div style="margin-left:28px;color:#cbd5e1;font-size:13px"
|
|
6973
|
-
<div style="margin-left:28px;color:#cbd5e1;font-size:13px"
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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
|
|
7321
|
-
<div class="top5-detail"><strong
|
|
7322
|
-
<div class="top5-detail"><strong
|
|
7323
|
-
<h4
|
|
7324
|
-
<ol class="top5-remediation">${f.remediationSteps.map((s) => `<li>${
|
|
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
|
|
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 =
|
|
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
|
|
7421
|
+
<details><summary>${t.details}</summary><div class="finding-card-body">
|
|
7346
7422
|
<p>${esc(f.description)}</p>
|
|
7347
|
-
<p><strong
|
|
7348
|
-
<ol>${f.remediationSteps.map((s) => `<li>${
|
|
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
|
|
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
|
|
7487
|
+
<h2>${esc(t.trendTitle)}</h2>
|
|
7412
7488
|
<div class="trend-chart">
|
|
7413
|
-
<div class="trend-title"
|
|
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"
|
|
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 ? ` (× ${r.count})` : "";
|
|
7448
|
-
|
|
7565
|
+
const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">📖</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
|
|
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"
|
|
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="
|
|
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
|
|
7588
|
+
<title>${esc(t.securityReportTitle)} — ${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>🛡️
|
|
7478
|
-
<div class="meta"
|
|
7595
|
+
<h1>🛡️ ${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"
|
|
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"
|
|
7488
|
-
<div class="stat-card stat-high"><div class="stat-count">${summary.high}</div><div class="stat-label"
|
|
7489
|
-
<div class="stat-card stat-medium"><div class="stat-count">${summary.medium}</div><div class="stat-label"
|
|
7490
|
-
<div class="stat-card stat-low"><div class="stat-count">${summary.low}</div><div class="stat-label"
|
|
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"
|
|
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"
|
|
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
|
|
7630
|
+
<h2>${esc(t.scanStatistics)}</h2>
|
|
7513
7631
|
<table>
|
|
7514
|
-
<thead><tr><th
|
|
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
|
|
7638
|
+
<h2>${esc(t.allFindings)}</h2>
|
|
7521
7639
|
${findingsHtml}
|
|
7522
7640
|
</section>
|
|
7523
7641
|
|
|
7524
7642
|
${recsHtml}
|
|
7525
7643
|
|
|
7526
7644
|
<footer>
|
|
7527
|
-
<p
|
|
7528
|
-
<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
|
|
7672
|
+
<h2>${esc(t.trendTitle)}</h2>
|
|
7553
7673
|
<div class="trend-chart">
|
|
7554
|
-
<div class="trend-title"
|
|
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"
|
|
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 =
|
|
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}
|
|
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"
|
|
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
|
|
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(([
|
|
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" ?
|
|
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"
|
|
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
|
|
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"
|
|
7622
|
-
findingsDetail = `<div class="check-findings-wrap"><details><summary
|
|
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
|
-
|
|
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
|
-
|
|
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 ? ` (× ${r.count})` : "";
|
|
7685
|
-
|
|
7853
|
+
const linkHtml = r.url ? ` <a href="${esc(r.url)}" style="color:#60a5fa" target="_blank" rel="noopener">📖</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
|
|
7860
|
+
<details><summary>${esc(t.showRemaining(mlpsRemaining.length))}…</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"
|
|
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"
|
|
7704
|
-
const unknownNote = autoUnknown > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px"
|
|
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="
|
|
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
|
|
7899
|
+
<title>${esc(t.mlpsTitle)} — ${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>🛡️
|
|
7738
|
-
<div class="disclaimer"
|
|
7739
|
-
<div class="meta"
|
|
7906
|
+
<h1>🛡️ ${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"
|
|
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"
|
|
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"
|
|
7750
|
-
<div class="mlps-summary-card"><div class="stat-count" style="color:#94a3b8">${cloudCount}</div><div class="stat-label">\u{1F3E2}
|
|
7751
|
-
<div class="mlps-summary-card"><div class="stat-count" style="color:#eab308">${manualCount}</div><div class="stat-label">\u{1F4CB}
|
|
7752
|
-
${naCount > 0 ? `<div class="mlps-summary-card"><div class="stat-count" style="color:#64748b">${naCount}</div><div class="stat-label">\u2796
|
|
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
|
|
7769
|
-
<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
|
-
|
|
7985
|
-
-
|
|
7986
|
-
-
|
|
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
|
-
|
|
7991
|
-
-
|
|
7992
|
-
-
|
|
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
|
-
|
|
8030
|
-
-
|
|
8031
|
-
-
|
|
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
|
-
|
|
8038
|
-
-
|
|
8039
|
-
-
|
|
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: "
|
|
8127
|
-
inspector_findings: "
|
|
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: "
|
|
8130
|
-
access_analyzer_findings: "
|
|
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
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
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 =
|
|
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
|
-
|
|
8327
|
+
t.serviceReminderTitle,
|
|
8258
8328
|
""
|
|
8259
8329
|
];
|
|
8260
8330
|
for (const svc of disabledServices) {
|
|
8261
|
-
lines.push(`${svc.icon} ${svc.service}
|
|
8262
|
-
lines.push(`
|
|
8263
|
-
lines.push(`
|
|
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(
|
|
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" +
|
|
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
|
-
{
|
|
8544
|
-
|
|
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
|
-
{
|
|
8558
|
-
|
|
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
|
-
{
|
|
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
|
-
${
|
|
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
|
}
|