aws-security-mcp 0.6.0 → 0.6.2
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/README.md +5 -7
- package/dist/bin/aws-security-mcp.js +326 -556
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.js +326 -556
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -2
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.6.
|
|
7
|
+
var VERSION = "0.6.2";
|
|
8
8
|
|
|
9
9
|
// src/utils/aws-client.ts
|
|
10
10
|
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
@@ -274,10 +274,6 @@ import {
|
|
|
274
274
|
ConfigServiceClient,
|
|
275
275
|
DescribeConfigurationRecordersCommand
|
|
276
276
|
} from "@aws-sdk/client-config-service";
|
|
277
|
-
import {
|
|
278
|
-
Macie2Client,
|
|
279
|
-
GetMacieSessionCommand
|
|
280
|
-
} from "@aws-sdk/client-macie2";
|
|
281
277
|
import {
|
|
282
278
|
CloudTrailClient,
|
|
283
279
|
DescribeTrailsCommand
|
|
@@ -321,7 +317,7 @@ function isNotEnabled(err) {
|
|
|
321
317
|
err.name === "DisabledException" || err.message.includes("not enabled") || err.message.includes("not subscribed");
|
|
322
318
|
}
|
|
323
319
|
function computeMaturityLevel(enabledCount) {
|
|
324
|
-
if (enabledCount >=
|
|
320
|
+
if (enabledCount >= 5) return "comprehensive";
|
|
325
321
|
if (enabledCount >= 4) return "advanced";
|
|
326
322
|
if (enabledCount >= 2) return "intermediate";
|
|
327
323
|
return "basic";
|
|
@@ -625,53 +621,6 @@ var ServiceDetectionScanner = class {
|
|
|
625
621
|
services.push({ name: "AWS Config", enabled: null, details: "Detection error" });
|
|
626
622
|
}
|
|
627
623
|
}
|
|
628
|
-
if (region.startsWith("cn-")) {
|
|
629
|
-
services.push({ name: "Macie", enabled: null, details: "Not available in China regions" });
|
|
630
|
-
warnings.push("Macie is not available in AWS China regions.");
|
|
631
|
-
} else {
|
|
632
|
-
try {
|
|
633
|
-
const mc = createClient(Macie2Client, region, ctx.credentials);
|
|
634
|
-
await mc.send(new GetMacieSessionCommand({}));
|
|
635
|
-
services.push({
|
|
636
|
-
name: "Macie",
|
|
637
|
-
enabled: true,
|
|
638
|
-
details: "Sensitive data detection active"
|
|
639
|
-
});
|
|
640
|
-
} catch (err) {
|
|
641
|
-
if (isAccessDenied(err)) {
|
|
642
|
-
warnings.push("Macie: insufficient permissions to check status");
|
|
643
|
-
services.push({ name: "Macie", enabled: null, details: "Access denied" });
|
|
644
|
-
} else if (isNotEnabled(err)) {
|
|
645
|
-
services.push({
|
|
646
|
-
name: "Macie",
|
|
647
|
-
enabled: false,
|
|
648
|
-
recommendation: "Enable Macie to detect sensitive data in S3",
|
|
649
|
-
freeTrialAvailable: true
|
|
650
|
-
});
|
|
651
|
-
findings.push(
|
|
652
|
-
makeFinding({
|
|
653
|
-
riskScore: 5,
|
|
654
|
-
title: "Amazon Macie is not enabled",
|
|
655
|
-
resourceType: "AWS::Macie::Session",
|
|
656
|
-
resourceId: "macie",
|
|
657
|
-
resourceArn: `arn:${partition}:macie2:${region}:${accountId}:session`,
|
|
658
|
-
region,
|
|
659
|
-
description: "Amazon Macie is not enabled in this region. Macie uses machine learning to discover and protect sensitive data stored in S3.",
|
|
660
|
-
impact: "Detects sensitive data (PII, credentials, financial data) in S3 buckets. Without it, sensitive data exposure may go unnoticed.",
|
|
661
|
-
remediationSteps: [
|
|
662
|
-
"Open the Amazon Macie console.",
|
|
663
|
-
"Click 'Get Started' and enable Macie.",
|
|
664
|
-
"Macie offers a 30-day free trial for sensitive data discovery.",
|
|
665
|
-
"Configure automated sensitive data discovery jobs."
|
|
666
|
-
]
|
|
667
|
-
})
|
|
668
|
-
);
|
|
669
|
-
} else {
|
|
670
|
-
warnings.push(`Macie detection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
671
|
-
services.push({ name: "Macie", enabled: null, details: "Detection error" });
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
624
|
const knownServices = services.filter((s) => s.enabled !== null);
|
|
676
625
|
const enabledCount = services.filter((s) => s.enabled === true).length;
|
|
677
626
|
const coveragePercent = knownServices.length > 0 ? Math.round(enabledCount / knownServices.length * 100) : 0;
|
|
@@ -2818,126 +2767,37 @@ var SecurityHubFindingsScanner = class {
|
|
|
2818
2767
|
// src/scanners/guardduty-findings.ts
|
|
2819
2768
|
import {
|
|
2820
2769
|
GuardDutyClient as GuardDutyClient2,
|
|
2821
|
-
ListDetectorsCommand as ListDetectorsCommand2
|
|
2822
|
-
ListFindingsCommand,
|
|
2823
|
-
GetFindingsCommand as GetFindingsCommand2
|
|
2770
|
+
ListDetectorsCommand as ListDetectorsCommand2
|
|
2824
2771
|
} from "@aws-sdk/client-guardduty";
|
|
2825
|
-
function gdSeverityToScore(severity) {
|
|
2826
|
-
if (severity >= 7) return 8;
|
|
2827
|
-
if (severity >= 4) return 5.5;
|
|
2828
|
-
return 3;
|
|
2829
|
-
}
|
|
2830
2772
|
var GuardDutyFindingsScanner = class {
|
|
2831
2773
|
moduleName = "guardduty_findings";
|
|
2832
2774
|
async scan(ctx) {
|
|
2833
|
-
const { region
|
|
2775
|
+
const { region } = ctx;
|
|
2834
2776
|
const startMs = Date.now();
|
|
2835
|
-
const findings = [];
|
|
2836
2777
|
const warnings = [];
|
|
2837
|
-
let resourcesScanned = 0;
|
|
2838
2778
|
try {
|
|
2839
2779
|
const client = createClient(GuardDutyClient2, region, ctx.credentials);
|
|
2840
|
-
const
|
|
2841
|
-
const detectorIds =
|
|
2780
|
+
const resp = await client.send(new ListDetectorsCommand2({}));
|
|
2781
|
+
const detectorIds = resp.DetectorIds ?? [];
|
|
2842
2782
|
if (detectorIds.length === 0) {
|
|
2843
2783
|
warnings.push("GuardDuty is not enabled in this region (no detectors found).");
|
|
2844
|
-
return {
|
|
2845
|
-
module: this.moduleName,
|
|
2846
|
-
status: "success",
|
|
2847
|
-
warnings,
|
|
2848
|
-
resourcesScanned: 0,
|
|
2849
|
-
findingsCount: 0,
|
|
2850
|
-
scanTimeMs: Date.now() - startMs,
|
|
2851
|
-
findings: []
|
|
2852
|
-
};
|
|
2853
|
-
}
|
|
2854
|
-
const detectorId = detectorIds[0];
|
|
2855
|
-
let nextToken;
|
|
2856
|
-
const findingIds = [];
|
|
2857
|
-
do {
|
|
2858
|
-
const listResp = await client.send(
|
|
2859
|
-
new ListFindingsCommand({
|
|
2860
|
-
DetectorId: detectorId,
|
|
2861
|
-
FindingCriteria: {
|
|
2862
|
-
Criterion: {
|
|
2863
|
-
"service.archived": {
|
|
2864
|
-
Eq: ["false"]
|
|
2865
|
-
}
|
|
2866
|
-
}
|
|
2867
|
-
},
|
|
2868
|
-
MaxResults: 50,
|
|
2869
|
-
NextToken: nextToken
|
|
2870
|
-
})
|
|
2871
|
-
);
|
|
2872
|
-
findingIds.push(...listResp.FindingIds ?? []);
|
|
2873
|
-
nextToken = listResp.NextToken;
|
|
2874
|
-
} while (nextToken);
|
|
2875
|
-
resourcesScanned = findingIds.length;
|
|
2876
|
-
if (findingIds.length === 0) {
|
|
2877
|
-
return {
|
|
2878
|
-
module: this.moduleName,
|
|
2879
|
-
status: "success",
|
|
2880
|
-
warnings: warnings.length > 0 ? warnings : void 0,
|
|
2881
|
-
resourcesScanned: 0,
|
|
2882
|
-
findingsCount: 0,
|
|
2883
|
-
scanTimeMs: Date.now() - startMs,
|
|
2884
|
-
findings: []
|
|
2885
|
-
};
|
|
2886
|
-
}
|
|
2887
|
-
for (let i = 0; i < findingIds.length; i += 50) {
|
|
2888
|
-
const batch = findingIds.slice(i, i + 50);
|
|
2889
|
-
const detailsResp = await client.send(
|
|
2890
|
-
new GetFindingsCommand2({
|
|
2891
|
-
DetectorId: detectorId,
|
|
2892
|
-
FindingIds: batch
|
|
2893
|
-
})
|
|
2894
|
-
);
|
|
2895
|
-
for (const gdf of detailsResp.Findings ?? []) {
|
|
2896
|
-
const gdSeverity = gdf.Severity ?? 0;
|
|
2897
|
-
const score = gdSeverityToScore(gdSeverity);
|
|
2898
|
-
const severity = severityFromScore(score);
|
|
2899
|
-
const resourceType = gdf.Resource?.ResourceType ?? "AWS::Unknown";
|
|
2900
|
-
const resourceId = gdf.Resource?.InstanceDetails?.InstanceId ?? gdf.Resource?.AccessKeyDetails?.AccessKeyId ?? gdf.Arn ?? "unknown";
|
|
2901
|
-
const resourceArn = gdf.Arn ?? `arn:${partition}:guardduty:${region}:${accountId}:detector/${detectorId}/finding/${gdf.Id ?? "unknown"}`;
|
|
2902
|
-
findings.push({
|
|
2903
|
-
severity,
|
|
2904
|
-
title: `[GuardDuty] ${gdf.Title ?? gdf.Type ?? "Finding"}`,
|
|
2905
|
-
resourceType,
|
|
2906
|
-
resourceId,
|
|
2907
|
-
resourceArn,
|
|
2908
|
-
region: gdf.Region ?? region,
|
|
2909
|
-
description: gdf.Description ?? gdf.Title ?? "No description",
|
|
2910
|
-
impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
|
|
2911
|
-
riskScore: score,
|
|
2912
|
-
remediationSteps: [
|
|
2913
|
-
`Investigate ${gdf.Type ?? "unknown threat"}: ${gdf.Title ?? "threat detected"}`,
|
|
2914
|
-
gdf.Description ? `Details: ${gdf.Description.substring(0, 200)}` : "",
|
|
2915
|
-
"Isolate affected resources if compromise is confirmed.",
|
|
2916
|
-
"Review CloudTrail logs for related suspicious activity."
|
|
2917
|
-
].filter(Boolean),
|
|
2918
|
-
priority: priorityFromSeverity(severity),
|
|
2919
|
-
module: this.moduleName,
|
|
2920
|
-
accountId: gdf.AccountId ?? accountId
|
|
2921
|
-
});
|
|
2922
|
-
}
|
|
2923
2784
|
}
|
|
2924
2785
|
return {
|
|
2925
2786
|
module: this.moduleName,
|
|
2926
2787
|
status: "success",
|
|
2927
2788
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
2928
|
-
resourcesScanned,
|
|
2929
|
-
findingsCount:
|
|
2789
|
+
resourcesScanned: 0,
|
|
2790
|
+
findingsCount: 0,
|
|
2930
2791
|
scanTimeMs: Date.now() - startMs,
|
|
2931
|
-
findings
|
|
2792
|
+
findings: []
|
|
2932
2793
|
};
|
|
2933
2794
|
} catch (err) {
|
|
2934
2795
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2935
2796
|
return {
|
|
2936
2797
|
module: this.moduleName,
|
|
2937
2798
|
status: "error",
|
|
2938
|
-
error: `GuardDuty
|
|
2939
|
-
|
|
2940
|
-
resourcesScanned,
|
|
2799
|
+
error: `GuardDuty detection check failed: ${msg}`,
|
|
2800
|
+
resourcesScanned: 0,
|
|
2941
2801
|
findingsCount: 0,
|
|
2942
2802
|
scanTimeMs: Date.now() - startMs,
|
|
2943
2803
|
findings: []
|
|
@@ -2949,164 +2809,59 @@ var GuardDutyFindingsScanner = class {
|
|
|
2949
2809
|
// src/scanners/inspector-findings.ts
|
|
2950
2810
|
import {
|
|
2951
2811
|
Inspector2Client as Inspector2Client2,
|
|
2952
|
-
|
|
2812
|
+
BatchGetAccountStatusCommand as BatchGetAccountStatusCommand2
|
|
2953
2813
|
} from "@aws-sdk/client-inspector2";
|
|
2954
|
-
function inspectorSeverityToScore(label) {
|
|
2955
|
-
switch (label) {
|
|
2956
|
-
case "CRITICAL":
|
|
2957
|
-
return 9.5;
|
|
2958
|
-
case "HIGH":
|
|
2959
|
-
return 8;
|
|
2960
|
-
case "MEDIUM":
|
|
2961
|
-
return 5.5;
|
|
2962
|
-
case "LOW":
|
|
2963
|
-
return 3;
|
|
2964
|
-
case "INFORMATIONAL":
|
|
2965
|
-
return null;
|
|
2966
|
-
case "UNTRIAGED":
|
|
2967
|
-
return 5.5;
|
|
2968
|
-
default:
|
|
2969
|
-
return null;
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
2814
|
var InspectorFindingsScanner = class {
|
|
2973
2815
|
moduleName = "inspector_findings";
|
|
2974
2816
|
async scan(ctx) {
|
|
2975
|
-
const { region
|
|
2817
|
+
const { region } = ctx;
|
|
2976
2818
|
const startMs = Date.now();
|
|
2977
|
-
const findings = [];
|
|
2978
2819
|
const warnings = [];
|
|
2979
|
-
let resourcesScanned = 0;
|
|
2980
2820
|
try {
|
|
2981
2821
|
const client = createClient(Inspector2Client2, region, ctx.credentials);
|
|
2982
|
-
|
|
2983
|
-
const
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
const
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
}
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
const severity = severityFromScore(score);
|
|
3001
|
-
const cveId = f.packageVulnerabilityDetails?.vulnerabilityId;
|
|
3002
|
-
const titleBase = f.title ?? "Inspector Finding";
|
|
3003
|
-
const title = cveId ? `[${cveId}] ${titleBase}` : titleBase;
|
|
3004
|
-
const resourceId = f.resources?.[0]?.id ?? "unknown";
|
|
3005
|
-
const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
|
|
3006
|
-
const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
|
|
3007
|
-
const remediationSteps = [];
|
|
3008
|
-
const vulnPkgs = f.packageVulnerabilityDetails?.vulnerablePackages;
|
|
3009
|
-
if (vulnPkgs?.length) {
|
|
3010
|
-
for (const pkg of vulnPkgs.slice(0, 3)) {
|
|
3011
|
-
const name = pkg.name ?? "unknown-package";
|
|
3012
|
-
const installed = pkg.version ?? "unknown";
|
|
3013
|
-
const fixed = pkg.fixedInVersion ?? "latest";
|
|
3014
|
-
const cveRef = cveId ? ` to fix ${cveId}` : "";
|
|
3015
|
-
remediationSteps.push(`Update ${name} from ${installed} to ${fixed}${cveRef}`);
|
|
3016
|
-
}
|
|
3017
|
-
} else if (f.remediation?.recommendation?.text) {
|
|
3018
|
-
remediationSteps.push(f.remediation.recommendation.text);
|
|
3019
|
-
}
|
|
3020
|
-
const genericPatterns = ["See References", "None Provided", "Review the finding"];
|
|
3021
|
-
if (remediationSteps.length === 0 || genericPatterns.some((p) => remediationSteps[0]?.startsWith(p))) {
|
|
3022
|
-
remediationSteps.length = 0;
|
|
3023
|
-
const rawTitle = f.title ?? "";
|
|
3024
|
-
if (rawTitle.includes("KB")) {
|
|
3025
|
-
const kbMatch = rawTitle.match(/KB\d+/);
|
|
3026
|
-
const kb = kbMatch ? kbMatch[0] : "patch";
|
|
3027
|
-
remediationSteps.push(`Install Windows patch ${kb} via WSUS or AWS Systems Manager Patch Manager`);
|
|
3028
|
-
remediationSteps.push(`Run: aws ssm send-command --document-name "AWS-InstallWindowsUpdates" --targets "Key=InstanceIds,Values=${resourceId}"`);
|
|
3029
|
-
if (kbMatch) {
|
|
3030
|
-
remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${kb}`);
|
|
3031
|
-
}
|
|
3032
|
-
} else if (rawTitle.includes("CVE-") || cveId) {
|
|
3033
|
-
const cveMatch = rawTitle.match(/CVE-[\d-]+/);
|
|
3034
|
-
const cve = cveMatch ? cveMatch[0] : cveId ?? "vulnerability";
|
|
3035
|
-
remediationSteps.push(`Fix ${cve}: update the affected software package to the latest patched version`);
|
|
3036
|
-
} else {
|
|
3037
|
-
remediationSteps.push(`Review and remediate: ${rawTitle}`);
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
if (f.remediation?.recommendation?.Url) {
|
|
3041
|
-
remediationSteps.push(`Documentation: ${f.remediation.recommendation.Url}`);
|
|
3042
|
-
}
|
|
3043
|
-
if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
|
|
3044
|
-
remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
|
|
3045
|
-
}
|
|
3046
|
-
const description = f.description ?? titleBase;
|
|
3047
|
-
const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
|
|
3048
|
-
findings.push({
|
|
3049
|
-
severity,
|
|
3050
|
-
title,
|
|
3051
|
-
resourceType,
|
|
3052
|
-
resourceId,
|
|
3053
|
-
resourceArn,
|
|
3054
|
-
region,
|
|
3055
|
-
description,
|
|
3056
|
-
impact,
|
|
3057
|
-
riskScore: score,
|
|
3058
|
-
remediationSteps,
|
|
3059
|
-
priority: priorityFromSeverity(severity),
|
|
3060
|
-
module: this.moduleName,
|
|
3061
|
-
accountId: f.awsAccountId ?? accountId
|
|
3062
|
-
});
|
|
2822
|
+
const resp = await client.send(new BatchGetAccountStatusCommand2({ accountIds: [] }));
|
|
2823
|
+
const account = resp.accounts?.[0];
|
|
2824
|
+
if (!account || account.state?.status !== "ENABLED") {
|
|
2825
|
+
warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
|
|
2826
|
+
} else {
|
|
2827
|
+
const rs = account.resourceState;
|
|
2828
|
+
const types = [
|
|
2829
|
+
{ name: "EC2", status: rs?.ec2?.status },
|
|
2830
|
+
{ name: "Lambda", status: rs?.lambda?.status },
|
|
2831
|
+
{ name: "ECR", status: rs?.ecr?.status },
|
|
2832
|
+
{ name: "Lambda Code", status: rs?.lambdaCode?.status },
|
|
2833
|
+
{ name: "Code Repository", status: rs?.codeRepository?.status }
|
|
2834
|
+
];
|
|
2835
|
+
const disabled = types.filter((t) => t.status && t.status !== "ENABLED");
|
|
2836
|
+
if (disabled.length > 0) {
|
|
2837
|
+
warnings.push(
|
|
2838
|
+
`Inspector scan types not enabled: ${disabled.map((t) => t.name).join(", ")}. Enable them for full vulnerability coverage.`
|
|
2839
|
+
);
|
|
3063
2840
|
}
|
|
3064
|
-
|
|
3065
|
-
} while (nextToken);
|
|
2841
|
+
}
|
|
3066
2842
|
return {
|
|
3067
2843
|
module: this.moduleName,
|
|
3068
2844
|
status: "success",
|
|
3069
2845
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3070
|
-
resourcesScanned,
|
|
3071
|
-
findingsCount:
|
|
2846
|
+
resourcesScanned: 0,
|
|
2847
|
+
findingsCount: 0,
|
|
3072
2848
|
scanTimeMs: Date.now() - startMs,
|
|
3073
|
-
findings
|
|
2849
|
+
findings: []
|
|
3074
2850
|
};
|
|
3075
2851
|
} catch (err) {
|
|
3076
2852
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3077
2853
|
const errName = err instanceof Error ? err.name : "";
|
|
3078
2854
|
const isAccessDenied2 = errName === "AccessDeniedException" || msg.includes("AccessDeniedException");
|
|
3079
|
-
const isNotEnabled2 = msg.includes("not enabled") || msg.includes("not subscribed");
|
|
3080
2855
|
if (isAccessDenied2) {
|
|
3081
|
-
warnings.push("Insufficient permissions to access Inspector. Grant inspector2:
|
|
3082
|
-
|
|
3083
|
-
module: this.moduleName,
|
|
3084
|
-
status: "success",
|
|
3085
|
-
warnings,
|
|
3086
|
-
resourcesScanned: 0,
|
|
3087
|
-
findingsCount: 0,
|
|
3088
|
-
scanTimeMs: Date.now() - startMs,
|
|
3089
|
-
findings: []
|
|
3090
|
-
};
|
|
3091
|
-
}
|
|
3092
|
-
if (isNotEnabled2) {
|
|
2856
|
+
warnings.push("Insufficient permissions to access Inspector. Grant inspector2:BatchGetAccountStatus to check enablement.");
|
|
2857
|
+
} else {
|
|
3093
2858
|
warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
|
|
3094
|
-
return {
|
|
3095
|
-
module: this.moduleName,
|
|
3096
|
-
status: "success",
|
|
3097
|
-
warnings,
|
|
3098
|
-
resourcesScanned: 0,
|
|
3099
|
-
findingsCount: 0,
|
|
3100
|
-
scanTimeMs: Date.now() - startMs,
|
|
3101
|
-
findings: []
|
|
3102
|
-
};
|
|
3103
2859
|
}
|
|
3104
2860
|
return {
|
|
3105
2861
|
module: this.moduleName,
|
|
3106
|
-
status: "
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
resourcesScanned,
|
|
2862
|
+
status: "success",
|
|
2863
|
+
warnings,
|
|
2864
|
+
resourcesScanned: 0,
|
|
3110
2865
|
findingsCount: 0,
|
|
3111
2866
|
scanTimeMs: Date.now() - startMs,
|
|
3112
2867
|
findings: []
|
|
@@ -3279,128 +3034,29 @@ var TrustedAdvisorFindingsScanner = class {
|
|
|
3279
3034
|
// src/scanners/config-rules-findings.ts
|
|
3280
3035
|
import {
|
|
3281
3036
|
ConfigServiceClient as ConfigServiceClient2,
|
|
3282
|
-
|
|
3283
|
-
GetComplianceDetailsByConfigRuleCommand
|
|
3037
|
+
DescribeConfigurationRecordersCommand as DescribeConfigurationRecordersCommand2
|
|
3284
3038
|
} from "@aws-sdk/client-config-service";
|
|
3285
|
-
var SECURITY_RULE_PATTERNS = [
|
|
3286
|
-
"securitygroup",
|
|
3287
|
-
"security-group",
|
|
3288
|
-
"encryption",
|
|
3289
|
-
"encrypted",
|
|
3290
|
-
"public",
|
|
3291
|
-
"unrestricted",
|
|
3292
|
-
"mfa",
|
|
3293
|
-
"password",
|
|
3294
|
-
"access-key",
|
|
3295
|
-
"root",
|
|
3296
|
-
"admin",
|
|
3297
|
-
"logging",
|
|
3298
|
-
"cloudtrail",
|
|
3299
|
-
"iam",
|
|
3300
|
-
"kms",
|
|
3301
|
-
"ssl",
|
|
3302
|
-
"tls",
|
|
3303
|
-
"vpc-flow",
|
|
3304
|
-
"guardduty",
|
|
3305
|
-
"securityhub"
|
|
3306
|
-
];
|
|
3307
|
-
function ruleIsSecurityRelated(ruleName) {
|
|
3308
|
-
const lower = ruleName.toLowerCase();
|
|
3309
|
-
return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
|
|
3310
|
-
}
|
|
3311
3039
|
var ConfigRulesFindingsScanner = class {
|
|
3312
3040
|
moduleName = "config_rules_findings";
|
|
3313
3041
|
async scan(ctx) {
|
|
3314
|
-
const { region
|
|
3042
|
+
const { region } = ctx;
|
|
3315
3043
|
const startMs = Date.now();
|
|
3316
|
-
const findings = [];
|
|
3317
3044
|
const warnings = [];
|
|
3318
|
-
let resourcesScanned = 0;
|
|
3319
3045
|
try {
|
|
3320
3046
|
const client = createClient(ConfigServiceClient2, region, ctx.credentials);
|
|
3321
|
-
|
|
3322
|
-
const
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
|
|
3326
|
-
);
|
|
3327
|
-
for (const rule of resp.ComplianceByConfigRules ?? []) {
|
|
3328
|
-
resourcesScanned++;
|
|
3329
|
-
if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
|
|
3330
|
-
nonCompliantRules.push(rule);
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
nextToken = resp.NextToken;
|
|
3334
|
-
} while (nextToken);
|
|
3335
|
-
if (resourcesScanned === 0) {
|
|
3336
|
-
warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
|
|
3337
|
-
return {
|
|
3338
|
-
module: this.moduleName,
|
|
3339
|
-
status: "success",
|
|
3340
|
-
warnings,
|
|
3341
|
-
resourcesScanned: 0,
|
|
3342
|
-
findingsCount: 0,
|
|
3343
|
-
scanTimeMs: Date.now() - startMs,
|
|
3344
|
-
findings: []
|
|
3345
|
-
};
|
|
3346
|
-
}
|
|
3347
|
-
for (const rule of nonCompliantRules) {
|
|
3348
|
-
const ruleName = rule.ConfigRuleName ?? "unknown";
|
|
3349
|
-
try {
|
|
3350
|
-
let detailToken;
|
|
3351
|
-
do {
|
|
3352
|
-
const detailResp = await client.send(
|
|
3353
|
-
new GetComplianceDetailsByConfigRuleCommand({
|
|
3354
|
-
ConfigRuleName: ruleName,
|
|
3355
|
-
ComplianceTypes: ["NON_COMPLIANT"],
|
|
3356
|
-
NextToken: detailToken
|
|
3357
|
-
})
|
|
3358
|
-
);
|
|
3359
|
-
for (const evalResult of detailResp.EvaluationResults ?? []) {
|
|
3360
|
-
const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
|
|
3361
|
-
const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
|
|
3362
|
-
const resourceId = qualifier?.ResourceId ?? "unknown";
|
|
3363
|
-
const annotation = evalResult.Annotation;
|
|
3364
|
-
const isSecurityRule = ruleIsSecurityRelated(ruleName);
|
|
3365
|
-
const riskScore = isSecurityRule ? 7.5 : 5.5;
|
|
3366
|
-
const severity = severityFromScore(riskScore);
|
|
3367
|
-
const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
|
|
3368
|
-
if (annotation) descParts.push(`Annotation: ${annotation}`);
|
|
3369
|
-
findings.push({
|
|
3370
|
-
severity,
|
|
3371
|
-
title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
|
|
3372
|
-
resourceType,
|
|
3373
|
-
resourceId,
|
|
3374
|
-
resourceArn: resourceId,
|
|
3375
|
-
region,
|
|
3376
|
-
description: descParts.join(". "),
|
|
3377
|
-
impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
|
|
3378
|
-
riskScore,
|
|
3379
|
-
remediationSteps: [
|
|
3380
|
-
`Fix Config Rule violation: ${ruleName}`,
|
|
3381
|
-
annotation ? `Details: ${annotation}` : "",
|
|
3382
|
-
`Resource: ${resourceType}/${resourceId}`
|
|
3383
|
-
].filter(Boolean),
|
|
3384
|
-
priority: priorityFromSeverity(severity),
|
|
3385
|
-
module: this.moduleName,
|
|
3386
|
-
accountId
|
|
3387
|
-
});
|
|
3388
|
-
}
|
|
3389
|
-
detailToken = detailResp.NextToken;
|
|
3390
|
-
} while (detailToken);
|
|
3391
|
-
} catch (detailErr) {
|
|
3392
|
-
const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
|
|
3393
|
-
warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
|
|
3394
|
-
}
|
|
3047
|
+
const resp = await client.send(new DescribeConfigurationRecordersCommand2({}));
|
|
3048
|
+
const recorders = resp.ConfigurationRecorders ?? [];
|
|
3049
|
+
if (recorders.length === 0) {
|
|
3050
|
+
warnings.push("AWS Config is not enabled in this region.");
|
|
3395
3051
|
}
|
|
3396
3052
|
return {
|
|
3397
3053
|
module: this.moduleName,
|
|
3398
3054
|
status: "success",
|
|
3399
3055
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3400
|
-
resourcesScanned,
|
|
3401
|
-
findingsCount:
|
|
3056
|
+
resourcesScanned: 0,
|
|
3057
|
+
findingsCount: 0,
|
|
3402
3058
|
scanTimeMs: Date.now() - startMs,
|
|
3403
|
-
findings
|
|
3059
|
+
findings: []
|
|
3404
3060
|
};
|
|
3405
3061
|
} catch (err) {
|
|
3406
3062
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -3419,9 +3075,8 @@ var ConfigRulesFindingsScanner = class {
|
|
|
3419
3075
|
return {
|
|
3420
3076
|
module: this.moduleName,
|
|
3421
3077
|
status: "error",
|
|
3422
|
-
error: `Config Rules
|
|
3423
|
-
|
|
3424
|
-
resourcesScanned,
|
|
3078
|
+
error: `Config Rules detection check failed: ${msg}`,
|
|
3079
|
+
resourcesScanned: 0,
|
|
3425
3080
|
findingsCount: 0,
|
|
3426
3081
|
scanTimeMs: Date.now() - startMs,
|
|
3427
3082
|
findings: []
|
|
@@ -3433,146 +3088,50 @@ var ConfigRulesFindingsScanner = class {
|
|
|
3433
3088
|
// src/scanners/access-analyzer-findings.ts
|
|
3434
3089
|
import {
|
|
3435
3090
|
AccessAnalyzerClient,
|
|
3436
|
-
ListAnalyzersCommand
|
|
3437
|
-
ListFindingsV2Command
|
|
3091
|
+
ListAnalyzersCommand
|
|
3438
3092
|
} from "@aws-sdk/client-accessanalyzer";
|
|
3439
|
-
function findingTypeToScore(findingType) {
|
|
3440
|
-
const ft = findingType;
|
|
3441
|
-
switch (ft) {
|
|
3442
|
-
case "ExternalAccess":
|
|
3443
|
-
return 8;
|
|
3444
|
-
case "UnusedIAMRole":
|
|
3445
|
-
case "UnusedIAMUserAccessKey":
|
|
3446
|
-
case "UnusedIAMUserPassword":
|
|
3447
|
-
return 5.5;
|
|
3448
|
-
case "UnusedPermission":
|
|
3449
|
-
return 3;
|
|
3450
|
-
default:
|
|
3451
|
-
return 5.5;
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3454
|
-
var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
|
|
3455
|
-
"UnusedIAMRole",
|
|
3456
|
-
"UnusedIAMUserAccessKey",
|
|
3457
|
-
"UnusedIAMUserPassword",
|
|
3458
|
-
"UnusedPermission"
|
|
3459
|
-
]);
|
|
3460
|
-
function isSecurityRelevant(findingType) {
|
|
3461
|
-
const ft = findingType;
|
|
3462
|
-
return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
|
|
3463
|
-
}
|
|
3464
|
-
function isExternalAccess(findingType) {
|
|
3465
|
-
return findingType === "ExternalAccess";
|
|
3466
|
-
}
|
|
3467
3093
|
var AccessAnalyzerFindingsScanner = class {
|
|
3468
3094
|
moduleName = "access_analyzer_findings";
|
|
3469
3095
|
async scan(ctx) {
|
|
3470
|
-
const { region
|
|
3096
|
+
const { region } = ctx;
|
|
3471
3097
|
const startMs = Date.now();
|
|
3472
|
-
const findings = [];
|
|
3473
3098
|
const warnings = [];
|
|
3474
|
-
let resourcesScanned = 0;
|
|
3475
3099
|
try {
|
|
3476
3100
|
const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
|
|
3477
3101
|
let analyzerToken;
|
|
3478
|
-
|
|
3102
|
+
let hasActiveAnalyzer = false;
|
|
3479
3103
|
do {
|
|
3480
3104
|
const resp = await client.send(
|
|
3481
3105
|
new ListAnalyzersCommand({ nextToken: analyzerToken })
|
|
3482
3106
|
);
|
|
3483
3107
|
for (const analyzer of resp.analyzers ?? []) {
|
|
3484
3108
|
if (analyzer.status === "ACTIVE") {
|
|
3485
|
-
|
|
3109
|
+
hasActiveAnalyzer = true;
|
|
3110
|
+
break;
|
|
3486
3111
|
}
|
|
3487
3112
|
}
|
|
3113
|
+
if (hasActiveAnalyzer) break;
|
|
3488
3114
|
analyzerToken = resp.nextToken;
|
|
3489
3115
|
} while (analyzerToken);
|
|
3490
|
-
if (
|
|
3116
|
+
if (!hasActiveAnalyzer) {
|
|
3491
3117
|
warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
|
|
3492
|
-
return {
|
|
3493
|
-
module: this.moduleName,
|
|
3494
|
-
status: "success",
|
|
3495
|
-
warnings,
|
|
3496
|
-
resourcesScanned: 0,
|
|
3497
|
-
findingsCount: 0,
|
|
3498
|
-
scanTimeMs: Date.now() - startMs,
|
|
3499
|
-
findings: []
|
|
3500
|
-
};
|
|
3501
|
-
}
|
|
3502
|
-
for (const analyzer of analyzers) {
|
|
3503
|
-
const analyzerArn = analyzer.arn ?? "unknown";
|
|
3504
|
-
let findingToken;
|
|
3505
|
-
do {
|
|
3506
|
-
const listResp = await client.send(
|
|
3507
|
-
new ListFindingsV2Command({
|
|
3508
|
-
analyzerArn,
|
|
3509
|
-
filter: {
|
|
3510
|
-
status: { eq: ["ACTIVE"] }
|
|
3511
|
-
},
|
|
3512
|
-
nextToken: findingToken
|
|
3513
|
-
})
|
|
3514
|
-
);
|
|
3515
|
-
for (const aaf of listResp.findings ?? []) {
|
|
3516
|
-
if (!isSecurityRelevant(aaf.findingType)) {
|
|
3517
|
-
continue;
|
|
3518
|
-
}
|
|
3519
|
-
resourcesScanned++;
|
|
3520
|
-
const score = findingTypeToScore(aaf.findingType);
|
|
3521
|
-
const severity = severityFromScore(score);
|
|
3522
|
-
const resourceArn = aaf.resource ?? "unknown";
|
|
3523
|
-
const resourceType = aaf.resourceType ?? "AWS::Unknown";
|
|
3524
|
-
const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
|
|
3525
|
-
const external = isExternalAccess(aaf.findingType);
|
|
3526
|
-
const descParts = [`Resource Type: ${resourceType}`];
|
|
3527
|
-
if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
|
|
3528
|
-
if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
|
|
3529
|
-
const title = buildFindingTitle(aaf);
|
|
3530
|
-
const impact = external ? `Resource is accessible from outside the account. Type: ${aaf.findingType ?? "unknown"}` : `Unused access detected \u2014 review and remove to follow least-privilege. Type: ${aaf.findingType ?? "unknown"}`;
|
|
3531
|
-
const remediationSteps = external ? [
|
|
3532
|
-
`Restrict external access on ${resourceType} ${resourceId}`,
|
|
3533
|
-
"Remove or narrow the resource policy to eliminate unintended external access.",
|
|
3534
|
-
`Resource ARN: ${resourceArn}`
|
|
3535
|
-
] : [
|
|
3536
|
-
`Remove unused access on ${resourceType} ${resourceId}`,
|
|
3537
|
-
"Remove unused permissions, roles, or credentials to follow least-privilege.",
|
|
3538
|
-
`Resource ARN: ${resourceArn}`
|
|
3539
|
-
];
|
|
3540
|
-
findings.push({
|
|
3541
|
-
severity,
|
|
3542
|
-
title,
|
|
3543
|
-
resourceType: mapResourceType(resourceType),
|
|
3544
|
-
resourceId,
|
|
3545
|
-
resourceArn,
|
|
3546
|
-
region,
|
|
3547
|
-
description: descParts.join(". "),
|
|
3548
|
-
impact,
|
|
3549
|
-
riskScore: score,
|
|
3550
|
-
remediationSteps,
|
|
3551
|
-
priority: priorityFromSeverity(severity),
|
|
3552
|
-
module: this.moduleName,
|
|
3553
|
-
accountId: aaf.resourceOwnerAccount ?? accountId
|
|
3554
|
-
});
|
|
3555
|
-
}
|
|
3556
|
-
findingToken = listResp.nextToken;
|
|
3557
|
-
} while (findingToken);
|
|
3558
3118
|
}
|
|
3559
3119
|
return {
|
|
3560
3120
|
module: this.moduleName,
|
|
3561
3121
|
status: "success",
|
|
3562
3122
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3563
|
-
resourcesScanned,
|
|
3564
|
-
findingsCount:
|
|
3123
|
+
resourcesScanned: 0,
|
|
3124
|
+
findingsCount: 0,
|
|
3565
3125
|
scanTimeMs: Date.now() - startMs,
|
|
3566
|
-
findings
|
|
3126
|
+
findings: []
|
|
3567
3127
|
};
|
|
3568
3128
|
} catch (err) {
|
|
3569
3129
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3570
3130
|
return {
|
|
3571
3131
|
module: this.moduleName,
|
|
3572
3132
|
status: "error",
|
|
3573
|
-
error: `Access Analyzer
|
|
3574
|
-
|
|
3575
|
-
resourcesScanned,
|
|
3133
|
+
error: `Access Analyzer detection check failed: ${msg}`,
|
|
3134
|
+
resourcesScanned: 0,
|
|
3576
3135
|
findingsCount: 0,
|
|
3577
3136
|
scanTimeMs: Date.now() - startMs,
|
|
3578
3137
|
findings: []
|
|
@@ -3580,29 +3139,6 @@ var AccessAnalyzerFindingsScanner = class {
|
|
|
3580
3139
|
}
|
|
3581
3140
|
}
|
|
3582
3141
|
};
|
|
3583
|
-
function buildFindingTitle(finding) {
|
|
3584
|
-
const resourceType = finding.resourceType ?? "Resource";
|
|
3585
|
-
const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
|
|
3586
|
-
const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
|
|
3587
|
-
return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
|
|
3588
|
-
}
|
|
3589
|
-
function mapResourceType(aaType) {
|
|
3590
|
-
const mapping = {
|
|
3591
|
-
"AWS::S3::Bucket": "AWS::S3::Bucket",
|
|
3592
|
-
"AWS::IAM::Role": "AWS::IAM::Role",
|
|
3593
|
-
"AWS::SQS::Queue": "AWS::SQS::Queue",
|
|
3594
|
-
"AWS::Lambda::Function": "AWS::Lambda::Function",
|
|
3595
|
-
"AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
|
|
3596
|
-
"AWS::KMS::Key": "AWS::KMS::Key",
|
|
3597
|
-
"AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
|
|
3598
|
-
"AWS::SNS::Topic": "AWS::SNS::Topic",
|
|
3599
|
-
"AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
|
|
3600
|
-
"AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
|
|
3601
|
-
"AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
|
|
3602
|
-
"AWS::ECR::Repository": "AWS::ECR::Repository"
|
|
3603
|
-
};
|
|
3604
|
-
return mapping[aaType] ?? aaType;
|
|
3605
|
-
}
|
|
3606
3142
|
|
|
3607
3143
|
// src/scanners/patch-compliance-findings.ts
|
|
3608
3144
|
import {
|
|
@@ -4140,6 +3676,44 @@ var zhI18n = {
|
|
|
4140
3676
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "\u56DB\u3001\u5B89\u5168\u8BA1\u7B97\u73AF\u5883",
|
|
4141
3677
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "\u4E94\u3001\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3"
|
|
4142
3678
|
},
|
|
3679
|
+
// Module display names
|
|
3680
|
+
moduleNames: {
|
|
3681
|
+
service_detection: "\u5B89\u5168\u670D\u52A1\u68C0\u6D4B",
|
|
3682
|
+
secret_exposure: "\u5BC6\u94A5\u66B4\u9732",
|
|
3683
|
+
ssl_certificate: "SSL \u8BC1\u4E66",
|
|
3684
|
+
dns_dangling: "\u60AC\u6302 DNS",
|
|
3685
|
+
network_reachability: "\u7F51\u7EDC\u53EF\u8FBE\u6027",
|
|
3686
|
+
iam_privilege_escalation: "IAM \u63D0\u6743\u5206\u6790",
|
|
3687
|
+
public_access_verify: "\u516C\u7F51\u8BBF\u95EE\u9A8C\u8BC1",
|
|
3688
|
+
tag_compliance: "\u6807\u7B7E\u5408\u89C4",
|
|
3689
|
+
idle_resources: "\u95F2\u7F6E\u8D44\u6E90",
|
|
3690
|
+
disaster_recovery: "\u707E\u5907\u8BC4\u4F30",
|
|
3691
|
+
security_hub_findings: "Security Hub",
|
|
3692
|
+
guardduty_findings: "GuardDuty",
|
|
3693
|
+
inspector_findings: "Inspector",
|
|
3694
|
+
trusted_advisor_findings: "Trusted Advisor",
|
|
3695
|
+
config_rules_findings: "Config Rules",
|
|
3696
|
+
access_analyzer_findings: "Access Analyzer",
|
|
3697
|
+
patch_compliance_findings: "\u8865\u4E01\u5408\u89C4",
|
|
3698
|
+
imdsv2_enforcement: "IMDSv2 \u5F3A\u5236",
|
|
3699
|
+
waf_coverage: "WAF \u8986\u76D6",
|
|
3700
|
+
// Security Hub sub-categories
|
|
3701
|
+
"sh:FSBP": "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5",
|
|
3702
|
+
"sh:Inspector": "\u8F6F\u4EF6\u6F0F\u6D1E",
|
|
3703
|
+
"sh:GuardDuty": "\u5A01\u80C1\u68C0\u6D4B",
|
|
3704
|
+
"sh:Config": "\u914D\u7F6E\u5408\u89C4",
|
|
3705
|
+
"sh:Access Analyzer": "\u5916\u90E8\u8BBF\u95EE",
|
|
3706
|
+
"sh:Other": "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0"
|
|
3707
|
+
},
|
|
3708
|
+
// Security Hub sub-categories
|
|
3709
|
+
securityHubSubCategories: {
|
|
3710
|
+
FSBP: { label: "\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5" },
|
|
3711
|
+
Inspector: { label: "\u8F6F\u4EF6\u6F0F\u6D1E" },
|
|
3712
|
+
GuardDuty: { label: "\u5A01\u80C1\u68C0\u6D4B" },
|
|
3713
|
+
Config: { label: "\u914D\u7F6E\u5408\u89C4" },
|
|
3714
|
+
"Access Analyzer": { label: "\u5916\u90E8\u8BBF\u95EE" },
|
|
3715
|
+
Other: { label: "\u5176\u4ED6\u5B89\u5168\u53D1\u73B0" }
|
|
3716
|
+
},
|
|
4143
3717
|
// Service Recommendations
|
|
4144
3718
|
notEnabled: "\u672A\u542F\u7528",
|
|
4145
3719
|
serviceRecommendations: {
|
|
@@ -4372,6 +3946,44 @@ var enI18n = {
|
|
|
4372
3946
|
"\u5B89\u5168\u8BA1\u7B97\u73AF\u5883": "IV. Computing Environment Security",
|
|
4373
3947
|
"\u5B89\u5168\u7BA1\u7406\u4E2D\u5FC3": "V. Security Management Center"
|
|
4374
3948
|
},
|
|
3949
|
+
// Module display names
|
|
3950
|
+
moduleNames: {
|
|
3951
|
+
service_detection: "Security Service Detection",
|
|
3952
|
+
secret_exposure: "Secret Exposure",
|
|
3953
|
+
ssl_certificate: "SSL Certificate",
|
|
3954
|
+
dns_dangling: "Dangling DNS",
|
|
3955
|
+
network_reachability: "Network Reachability",
|
|
3956
|
+
iam_privilege_escalation: "IAM Privilege Escalation",
|
|
3957
|
+
public_access_verify: "Public Access Verification",
|
|
3958
|
+
tag_compliance: "Tag Compliance",
|
|
3959
|
+
idle_resources: "Idle Resources",
|
|
3960
|
+
disaster_recovery: "Disaster Recovery",
|
|
3961
|
+
security_hub_findings: "Security Hub",
|
|
3962
|
+
guardduty_findings: "GuardDuty",
|
|
3963
|
+
inspector_findings: "Inspector",
|
|
3964
|
+
trusted_advisor_findings: "Trusted Advisor",
|
|
3965
|
+
config_rules_findings: "Config Rules",
|
|
3966
|
+
access_analyzer_findings: "Access Analyzer",
|
|
3967
|
+
patch_compliance_findings: "Patch Compliance",
|
|
3968
|
+
imdsv2_enforcement: "IMDSv2 Enforcement",
|
|
3969
|
+
waf_coverage: "WAF Coverage",
|
|
3970
|
+
// Security Hub sub-categories
|
|
3971
|
+
"sh:FSBP": "Security Best Practices",
|
|
3972
|
+
"sh:Inspector": "Software Vulnerabilities",
|
|
3973
|
+
"sh:GuardDuty": "Threat Detection",
|
|
3974
|
+
"sh:Config": "Configuration Compliance",
|
|
3975
|
+
"sh:Access Analyzer": "External Access",
|
|
3976
|
+
"sh:Other": "Other Security Findings"
|
|
3977
|
+
},
|
|
3978
|
+
// Security Hub sub-categories
|
|
3979
|
+
securityHubSubCategories: {
|
|
3980
|
+
FSBP: { label: "Security Best Practices" },
|
|
3981
|
+
Inspector: { label: "Software Vulnerabilities" },
|
|
3982
|
+
GuardDuty: { label: "Threat Detection" },
|
|
3983
|
+
Config: { label: "Configuration Compliance" },
|
|
3984
|
+
"Access Analyzer": { label: "External Access" },
|
|
3985
|
+
Other: { label: "Other Security Findings" }
|
|
3986
|
+
},
|
|
4375
3987
|
// Service Recommendations
|
|
4376
3988
|
notEnabled: "Not Enabled",
|
|
4377
3989
|
serviceRecommendations: {
|
|
@@ -4576,7 +4188,7 @@ function generateMarkdownReport(scanResults, lang) {
|
|
|
4576
4188
|
for (const m of modules) {
|
|
4577
4189
|
const status = m.status === "success" ? "\u2705" : "\u274C";
|
|
4578
4190
|
lines.push(
|
|
4579
|
-
`| ${m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
|
|
4191
|
+
`| ${t.moduleNames[m.module] ?? m.module} | ${m.resourcesScanned} | ${m.findingsCount} | ${status} |`
|
|
4580
4192
|
);
|
|
4581
4193
|
}
|
|
4582
4194
|
lines.push("");
|
|
@@ -7419,6 +7031,22 @@ var SEV_COLOR = {
|
|
|
7419
7031
|
LOW: "#22c55e"
|
|
7420
7032
|
};
|
|
7421
7033
|
var SEVERITY_ORDER2 = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
7034
|
+
function getRecommendationTemplate(rem) {
|
|
7035
|
+
return rem.replace(/\b(i-[0-9a-f]+)\b/g, "{instance}").replace(/\b(vol-[0-9a-f]+)\b/g, "{volume}").replace(/\b(sg-[0-9a-f]+)\b/g, "{sg}").replace(/\b(eipalloc-[0-9a-f]+)\b/g, "{eip}").replace(/\b(arn:aws[-\w]*:[^"\s]+)\b/g, "{arn}").replace(/"[^"]+"/g, "{name}").replace(/bucket \S+/g, "bucket {name}").replace(/instance \S+/g, "instance {id}").replace(/volume \S+/g, "volume {id}").replace(/rule \S+/g, "rule {name}");
|
|
7036
|
+
}
|
|
7037
|
+
function getSecurityHubSource(finding) {
|
|
7038
|
+
const impact = finding.impact ?? "";
|
|
7039
|
+
const match = impact.match(/^Source:\s*([^(]+)/);
|
|
7040
|
+
if (!match) return "Other";
|
|
7041
|
+
const product = match[1].trim();
|
|
7042
|
+
if (product === "Security Hub" || product.includes("Foundational")) return "FSBP";
|
|
7043
|
+
if (product === "Inspector" || product.includes("Inspector")) return "Inspector";
|
|
7044
|
+
if (product === "GuardDuty" || product.includes("GuardDuty")) return "GuardDuty";
|
|
7045
|
+
if (product === "Config" || product.includes("Config")) return "Config";
|
|
7046
|
+
if (product === "IAM Access Analyzer" || product.includes("Access Analyzer")) return "Access Analyzer";
|
|
7047
|
+
return "Other";
|
|
7048
|
+
}
|
|
7049
|
+
var SECURITY_HUB_SUB_CAT_ORDER = ["FSBP", "Inspector", "GuardDuty", "Config", "Access Analyzer", "Other"];
|
|
7422
7050
|
function scoreColor(score) {
|
|
7423
7051
|
if (score >= 80) return "#22c55e";
|
|
7424
7052
|
if (score >= 50) return "#eab308";
|
|
@@ -7796,6 +7424,47 @@ function generateHtmlReport(scanResults, history, lang) {
|
|
|
7796
7424
|
const allFindings = modules.flatMap(
|
|
7797
7425
|
(m) => m.findings.map((f) => ({ ...f, module: f.module ?? m.module }))
|
|
7798
7426
|
);
|
|
7427
|
+
const shModule = modules.find((m) => m.module === "security_hub_findings");
|
|
7428
|
+
const shSubCats = [];
|
|
7429
|
+
if (shModule && shModule.findingsCount > 0) {
|
|
7430
|
+
const catMap = {};
|
|
7431
|
+
for (const f of shModule.findings) {
|
|
7432
|
+
const cat = getSecurityHubSource(f);
|
|
7433
|
+
if (!catMap[cat]) catMap[cat] = [];
|
|
7434
|
+
catMap[cat].push(f);
|
|
7435
|
+
}
|
|
7436
|
+
for (const cat of SECURITY_HUB_SUB_CAT_ORDER) {
|
|
7437
|
+
const catFindings = catMap[cat];
|
|
7438
|
+
if (catFindings && catFindings.length > 0) {
|
|
7439
|
+
const meta = t.securityHubSubCategories[cat];
|
|
7440
|
+
const shLabel = t.moduleNames[`sh:${cat}`] ?? meta?.label ?? cat;
|
|
7441
|
+
shSubCats.push({
|
|
7442
|
+
key: cat,
|
|
7443
|
+
label: shLabel,
|
|
7444
|
+
count: catFindings.length,
|
|
7445
|
+
findings: catFindings
|
|
7446
|
+
});
|
|
7447
|
+
}
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
const DETECTION_ONLY_MODULES = /* @__PURE__ */ new Set([
|
|
7451
|
+
"guardduty_findings",
|
|
7452
|
+
"inspector_findings",
|
|
7453
|
+
"config_rules_findings",
|
|
7454
|
+
"access_analyzer_findings"
|
|
7455
|
+
]);
|
|
7456
|
+
const barChartModules = modules.flatMap((m) => {
|
|
7457
|
+
if (DETECTION_ONLY_MODULES.has(m.module)) return [];
|
|
7458
|
+
if (m.module === "security_hub_findings" && shSubCats.length > 0) {
|
|
7459
|
+
return shSubCats.map((sc) => ({
|
|
7460
|
+
...m,
|
|
7461
|
+
module: t.moduleNames[`sh:${sc.key}`] ?? sc.key,
|
|
7462
|
+
findingsCount: sc.count,
|
|
7463
|
+
findings: sc.findings
|
|
7464
|
+
}));
|
|
7465
|
+
}
|
|
7466
|
+
return [{ ...m, module: t.moduleNames[m.module] ?? m.module }];
|
|
7467
|
+
});
|
|
7799
7468
|
let top5Html = "";
|
|
7800
7469
|
if (allFindings.length > 0) {
|
|
7801
7470
|
const top5 = [...allFindings].sort((a, b) => b.riskScore - a.riskScore).slice(0, 5);
|
|
@@ -7861,34 +7530,51 @@ ${rest}
|
|
|
7861
7530
|
if (!moduleMap.has(mod)) moduleMap.set(mod, []);
|
|
7862
7531
|
moduleMap.get(mod).push(f);
|
|
7863
7532
|
}
|
|
7864
|
-
const
|
|
7533
|
+
const expandedEntries = [];
|
|
7534
|
+
for (const [mod, findings] of moduleMap.entries()) {
|
|
7535
|
+
if (DETECTION_ONLY_MODULES.has(mod)) continue;
|
|
7536
|
+
if (mod === "security_hub_findings" && shSubCats.length > 0) {
|
|
7537
|
+
for (const sc of shSubCats) {
|
|
7538
|
+
expandedEntries.push([sc.key, sc.findings, sc.label]);
|
|
7539
|
+
}
|
|
7540
|
+
} else {
|
|
7541
|
+
expandedEntries.push([mod, findings, null]);
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
const moduleEntries = expandedEntries.sort((a, b) => {
|
|
7865
7545
|
const aHasCritHigh = a[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
7866
7546
|
const bHasCritHigh = b[1].some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
7867
7547
|
if (aHasCritHigh !== bHasCritHigh) return aHasCritHigh ? -1 : 1;
|
|
7868
7548
|
return b[1].length - a[1].length;
|
|
7869
7549
|
});
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
const findings = modFindings.filter((f) => f.severity === sev);
|
|
7876
|
-
if (findings.length === 0) return "";
|
|
7877
|
-
findings.sort((a, b) => b.riskScore - a.riskScore);
|
|
7550
|
+
const renderSeverityGroups = (findings) => {
|
|
7551
|
+
return SEVERITY_ORDER2.map((sev) => {
|
|
7552
|
+
const sevFindings = findings.filter((f) => f.severity === sev);
|
|
7553
|
+
if (sevFindings.length === 0) return "";
|
|
7554
|
+
sevFindings.sort((a, b) => b.riskScore - a.riskScore);
|
|
7878
7555
|
const emoji = SEV_EMOJI[sev] ?? "";
|
|
7879
7556
|
const label = sev.charAt(0) + sev.slice(1).toLowerCase();
|
|
7880
7557
|
return `<details class="severity-group-fold">
|
|
7881
|
-
<summary><h4>${emoji} ${label} (${
|
|
7882
|
-
${renderCards(
|
|
7558
|
+
<summary><h4>${emoji} ${label} (${sevFindings.length})</h4></summary>
|
|
7559
|
+
${renderCards(sevFindings)}
|
|
7883
7560
|
</details>`;
|
|
7884
7561
|
}).filter(Boolean).join("\n");
|
|
7562
|
+
};
|
|
7563
|
+
const renderModuleBadges = (findings) => {
|
|
7564
|
+
const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
7565
|
+
for (const f of findings) sevCounts[f.severity]++;
|
|
7566
|
+
return SEVERITY_ORDER2.filter((sev) => sevCounts[sev] > 0).map((sev) => `<span class="badge badge-${sev.toLowerCase()}">${sevCounts[sev]} ${sev.charAt(0) + sev.slice(1).toLowerCase()}</span>`).join(" ");
|
|
7567
|
+
};
|
|
7568
|
+
findingsHtml = moduleEntries.map(([modName, modFindings, subCatLabel]) => {
|
|
7569
|
+
const badges = renderModuleBadges(modFindings);
|
|
7570
|
+
const displayName = subCatLabel ?? (t.moduleNames[modName] ?? modName);
|
|
7885
7571
|
return `<details class="module-fold">
|
|
7886
7572
|
<summary>
|
|
7887
|
-
<h3>🔒 ${esc(
|
|
7573
|
+
<h3>🔒 ${esc(displayName)} (${modFindings.length})</h3>
|
|
7888
7574
|
<span class="module-badges">${badges}</span>
|
|
7889
7575
|
</summary>
|
|
7890
7576
|
<div class="module-body">
|
|
7891
|
-
${
|
|
7577
|
+
${renderSeverityGroups(modFindings)}
|
|
7892
7578
|
</div>
|
|
7893
7579
|
</details>`;
|
|
7894
7580
|
}).join("\n");
|
|
@@ -7908,8 +7594,29 @@ ${rest}
|
|
|
7908
7594
|
</div>
|
|
7909
7595
|
</section>`;
|
|
7910
7596
|
}
|
|
7911
|
-
const
|
|
7912
|
-
|
|
7597
|
+
const isModuleDisabled = (m) => {
|
|
7598
|
+
if (!m.warnings?.length) return void 0;
|
|
7599
|
+
const w = m.warnings.find(
|
|
7600
|
+
(w2) => SERVICE_NOT_ENABLED_PATTERNS.some((p) => w2.includes(p))
|
|
7601
|
+
);
|
|
7602
|
+
return w;
|
|
7603
|
+
};
|
|
7604
|
+
const statsRows = modules.flatMap(
|
|
7605
|
+
(m) => {
|
|
7606
|
+
if (DETECTION_ONLY_MODULES.has(m.module)) return [];
|
|
7607
|
+
if (m.module === "security_hub_findings" && shSubCats.length > 0) {
|
|
7608
|
+
return shSubCats.map(
|
|
7609
|
+
(sc) => `<tr><td>${esc(sc.label)}</td><td>${m.resourcesScanned}</td><td>${sc.count}</td><td>✓</td></tr>`
|
|
7610
|
+
);
|
|
7611
|
+
}
|
|
7612
|
+
const disabledWarning = isModuleDisabled(m);
|
|
7613
|
+
if (disabledWarning) {
|
|
7614
|
+
const rec = t.serviceRecommendations[m.module];
|
|
7615
|
+
const reason = rec ? rec.action : disabledWarning;
|
|
7616
|
+
return [`<tr><td>${esc(t.moduleNames[m.module] ?? m.module)}</td><td>-</td><td>-</td><td style="color:#eab308">⚠ ${esc(reason)}</td></tr>`];
|
|
7617
|
+
}
|
|
7618
|
+
return [`<tr><td>${esc(t.moduleNames[m.module] ?? m.module)}</td><td>${m.resourcesScanned}</td><td>${m.findingsCount}</td><td>${m.status === "success" ? "✓" : "✗"}</td></tr>`];
|
|
7619
|
+
}
|
|
7913
7620
|
).join("\n");
|
|
7914
7621
|
let recsHtml = "";
|
|
7915
7622
|
if (summary.totalFindings > 0) {
|
|
@@ -7917,6 +7624,9 @@ ${rest}
|
|
|
7917
7624
|
const kbPatches = [];
|
|
7918
7625
|
let kbSeverity = "LOW";
|
|
7919
7626
|
let kbUrl;
|
|
7627
|
+
const cveList = [];
|
|
7628
|
+
let cveSeverity = "LOW";
|
|
7629
|
+
let cveUrl;
|
|
7920
7630
|
const genericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
7921
7631
|
for (const f of allFindings) {
|
|
7922
7632
|
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
@@ -7929,6 +7639,13 @@ ${rest}
|
|
|
7929
7639
|
if (!kbUrl && url) kbUrl = url;
|
|
7930
7640
|
continue;
|
|
7931
7641
|
}
|
|
7642
|
+
const cveMatch = f.title.match(/CVE-[\d-]+/);
|
|
7643
|
+
if (cveMatch && (f.module === "security_hub_findings" || f.module === "inspector_findings")) {
|
|
7644
|
+
cveList.push(cveMatch[0]);
|
|
7645
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(cveSeverity)) cveSeverity = f.severity;
|
|
7646
|
+
if (!cveUrl && url) cveUrl = url;
|
|
7647
|
+
continue;
|
|
7648
|
+
}
|
|
7932
7649
|
if (f.module === "security_hub_findings") {
|
|
7933
7650
|
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
7934
7651
|
if (controlMatch) {
|
|
@@ -7945,6 +7662,23 @@ ${rest}
|
|
|
7945
7662
|
continue;
|
|
7946
7663
|
}
|
|
7947
7664
|
}
|
|
7665
|
+
if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
|
|
7666
|
+
const template = getRecommendationTemplate(rem);
|
|
7667
|
+
if (template !== rem) {
|
|
7668
|
+
const templateKey = `tmpl:${f.module}:${template}`;
|
|
7669
|
+
const existingTmpl = recMap.get(templateKey);
|
|
7670
|
+
if (existingTmpl) {
|
|
7671
|
+
existingTmpl.count++;
|
|
7672
|
+
if (!existingTmpl.url && url) existingTmpl.url = url;
|
|
7673
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
|
|
7674
|
+
existingTmpl.severity = f.severity;
|
|
7675
|
+
}
|
|
7676
|
+
continue;
|
|
7677
|
+
}
|
|
7678
|
+
recMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
|
|
7679
|
+
continue;
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
7948
7682
|
const existing = recMap.get(rem);
|
|
7949
7683
|
if (existing) {
|
|
7950
7684
|
existing.count++;
|
|
@@ -7961,8 +7695,14 @@ ${rest}
|
|
|
7961
7695
|
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
7962
7696
|
recMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: kbSeverity, count: 1, url: kbUrl });
|
|
7963
7697
|
}
|
|
7698
|
+
if (cveList.length > 0) {
|
|
7699
|
+
const unique = [...new Set(cveList)];
|
|
7700
|
+
const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
7701
|
+
const cveText = (lang ?? "zh") === "zh" ? `\u4FEE\u590D ${unique.length} \u4E2A\u8F6F\u4EF6\u6F0F\u6D1E (${cveDisplay})\uFF0C\u66F4\u65B0\u53D7\u5F71\u54CD\u7684\u8F6F\u4EF6\u5305\u5230\u6700\u65B0\u7248\u672C` : `Fix ${unique.length} software vulnerabilities (${cveDisplay}) \u2014 update affected packages to latest patched versions`;
|
|
7702
|
+
recMap.set("__cve__", { text: cveText, severity: cveSeverity, count: 1, url: cveUrl });
|
|
7703
|
+
}
|
|
7964
7704
|
for (const [key, rec] of recMap) {
|
|
7965
|
-
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
7705
|
+
if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
|
|
7966
7706
|
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
7967
7707
|
rec.count = 1;
|
|
7968
7708
|
}
|
|
@@ -8029,7 +7769,7 @@ ${remaining.map(renderRec).join("\n")}
|
|
|
8029
7769
|
</div>
|
|
8030
7770
|
<div class="chart-box">
|
|
8031
7771
|
<div class="chart-title">${esc(t.findingsByModule)}</div>
|
|
8032
|
-
${barChart(
|
|
7772
|
+
${barChart(barChartModules, t.allModulesClean)}
|
|
8033
7773
|
</div>
|
|
8034
7774
|
</section>
|
|
8035
7775
|
|
|
@@ -8202,6 +7942,9 @@ ${itemsHtml}
|
|
|
8202
7942
|
const mlpsKbPatches = [];
|
|
8203
7943
|
let mlpsKbSeverity = "LOW";
|
|
8204
7944
|
let mlpsKbUrl;
|
|
7945
|
+
const mlpsCveList = [];
|
|
7946
|
+
let mlpsCveSeverity = "LOW";
|
|
7947
|
+
let mlpsCveUrl;
|
|
8205
7948
|
const mlpsGenericPatterns = ["See References", "None Provided", "Review the finding", "Review and remediate."];
|
|
8206
7949
|
for (const r of failedResults) {
|
|
8207
7950
|
for (const f of r.relatedFindings) {
|
|
@@ -8215,6 +7958,13 @@ ${itemsHtml}
|
|
|
8215
7958
|
if (!mlpsKbUrl && url) mlpsKbUrl = url;
|
|
8216
7959
|
continue;
|
|
8217
7960
|
}
|
|
7961
|
+
const cveMatch = f.title.match(/CVE-[\d-]+/);
|
|
7962
|
+
if (cveMatch) {
|
|
7963
|
+
mlpsCveList.push(cveMatch[0]);
|
|
7964
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(mlpsCveSeverity)) mlpsCveSeverity = f.severity;
|
|
7965
|
+
if (!mlpsCveUrl && url) mlpsCveUrl = url;
|
|
7966
|
+
continue;
|
|
7967
|
+
}
|
|
8218
7968
|
if (f.module === "security_hub_findings") {
|
|
8219
7969
|
const controlMatch = f.title.match(/^([A-Z][A-Za-z0-9]*\.\d+)\s/);
|
|
8220
7970
|
if (controlMatch) {
|
|
@@ -8231,6 +7981,23 @@ ${itemsHtml}
|
|
|
8231
7981
|
continue;
|
|
8232
7982
|
}
|
|
8233
7983
|
}
|
|
7984
|
+
if (f.module !== "security_hub_findings" && f.module !== "inspector_findings") {
|
|
7985
|
+
const template = getRecommendationTemplate(rem);
|
|
7986
|
+
if (template !== rem) {
|
|
7987
|
+
const templateKey = `tmpl:${f.module}:${template}`;
|
|
7988
|
+
const existingTmpl = mlpsRecMap.get(templateKey);
|
|
7989
|
+
if (existingTmpl) {
|
|
7990
|
+
existingTmpl.count++;
|
|
7991
|
+
if (!existingTmpl.url && url) existingTmpl.url = url;
|
|
7992
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existingTmpl.severity)) {
|
|
7993
|
+
existingTmpl.severity = f.severity;
|
|
7994
|
+
}
|
|
7995
|
+
continue;
|
|
7996
|
+
}
|
|
7997
|
+
mlpsRecMap.set(templateKey, { text: rem, severity: f.severity, count: 1, url });
|
|
7998
|
+
continue;
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8234
8001
|
const existing = mlpsRecMap.get(rem);
|
|
8235
8002
|
if (existing) {
|
|
8236
8003
|
existing.count++;
|
|
@@ -8248,8 +8015,14 @@ ${itemsHtml}
|
|
|
8248
8015
|
const kbList = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
8249
8016
|
mlpsRecMap.set("__kb__", { text: t.installWindowsPatches(unique.length, kbList), severity: mlpsKbSeverity, count: 1, url: mlpsKbUrl });
|
|
8250
8017
|
}
|
|
8018
|
+
if (mlpsCveList.length > 0) {
|
|
8019
|
+
const unique = [...new Set(mlpsCveList)];
|
|
8020
|
+
const cveDisplay = unique.slice(0, 5).join(", ") + (unique.length > 5 ? ", \u2026" : "");
|
|
8021
|
+
const cveText = (lang ?? "zh") === "zh" ? `\u4FEE\u590D ${unique.length} \u4E2A\u8F6F\u4EF6\u6F0F\u6D1E (${cveDisplay})\uFF0C\u66F4\u65B0\u53D7\u5F71\u54CD\u7684\u8F6F\u4EF6\u5305\u5230\u6700\u65B0\u7248\u672C` : `Fix ${unique.length} software vulnerabilities (${cveDisplay}) \u2014 update affected packages to latest patched versions`;
|
|
8022
|
+
mlpsRecMap.set("__cve__", { text: cveText, severity: mlpsCveSeverity, count: 1, url: mlpsCveUrl });
|
|
8023
|
+
}
|
|
8251
8024
|
for (const [key, rec] of mlpsRecMap) {
|
|
8252
|
-
if (key.startsWith("ctrl:") && rec.count > 1) {
|
|
8025
|
+
if ((key.startsWith("ctrl:") || key.startsWith("tmpl:")) && rec.count > 1) {
|
|
8253
8026
|
rec.text += ` \u2014 ${t.affectedResources(rec.count)}`;
|
|
8254
8027
|
rec.count = 1;
|
|
8255
8028
|
}
|
|
@@ -8545,7 +8318,6 @@ Detects which AWS security services are enabled and assesses overall security ma
|
|
|
8545
8318
|
- **GuardDuty not enabled** \u2014 Risk 7.5: Provides continuous threat detection.
|
|
8546
8319
|
- **Inspector not enabled** \u2014 Risk 6.0: Scans for software vulnerabilities.
|
|
8547
8320
|
- **AWS Config not enabled** \u2014 Risk 6.0: Tracks configuration changes.
|
|
8548
|
-
- **Macie not enabled** \u2014 Risk 5.0: Detects sensitive data in S3 (not available in China regions).
|
|
8549
8321
|
- CloudTrail detection is included for coverage metrics.
|
|
8550
8322
|
|
|
8551
8323
|
### Maturity Levels
|
|
@@ -8553,8 +8325,8 @@ Detects which AWS security services are enabled and assesses overall security ma
|
|
|
8553
8325
|
|------------------|-------|
|
|
8554
8326
|
| 0\u20131 | Basic |
|
|
8555
8327
|
| 2\u20133 | Intermediate |
|
|
8556
|
-
| 4
|
|
8557
|
-
|
|
|
8328
|
+
| 4 | Advanced |
|
|
8329
|
+
| 5 | Comprehensive |
|
|
8558
8330
|
|
|
8559
8331
|
## 2. Security Hub Findings (security_hub_findings)
|
|
8560
8332
|
Aggregates active findings from AWS Security Hub. Replaces individual config scanners (SG, S3, IAM, CloudTrail, RDS, EBS, VPC, etc.) with centralized compliance checks from FSBP, CIS, and PCI DSS standards.
|
|
@@ -8688,7 +8460,7 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
8688
8460
|
import { join as join2, dirname } from "path";
|
|
8689
8461
|
import { fileURLToPath } from "url";
|
|
8690
8462
|
var MODULE_DESCRIPTIONS = {
|
|
8691
|
-
service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config
|
|
8463
|
+
service_detection: "Detects which AWS security services (Security Hub, GuardDuty, Inspector, Config) are enabled and assesses security maturity.",
|
|
8692
8464
|
secret_exposure: "Checks Lambda env vars and EC2 userData for exposed secrets (AWS keys, private keys, passwords).",
|
|
8693
8465
|
ssl_certificate: "Checks ACM certificates for expiry, failed status, and upcoming renewals.",
|
|
8694
8466
|
dns_dangling: "Checks Route53 CNAME records for dangling DNS (subdomain takeover risk).",
|
|
@@ -9123,14 +8895,12 @@ function createServer(defaultRegion) {
|
|
|
9123
8895
|
"Security Hub": "+300 security checks",
|
|
9124
8896
|
"GuardDuty": "Threat detection",
|
|
9125
8897
|
"Inspector": "Vulnerability scanning",
|
|
9126
|
-
"AWS Config": "Configuration tracking"
|
|
9127
|
-
"Macie": "Sensitive data detection"
|
|
8898
|
+
"AWS Config": "Configuration tracking"
|
|
9128
8899
|
};
|
|
9129
8900
|
const serviceFreeTrials = {
|
|
9130
8901
|
"Security Hub": true,
|
|
9131
8902
|
"GuardDuty": true,
|
|
9132
|
-
"Inspector": true
|
|
9133
|
-
"Macie": true
|
|
8903
|
+
"Inspector": true
|
|
9134
8904
|
};
|
|
9135
8905
|
const services = detection.services;
|
|
9136
8906
|
const coveragePercent = detection.coveragePercent;
|
|
@@ -9165,7 +8935,7 @@ function createServer(defaultRegion) {
|
|
|
9165
8935
|
lines.push("");
|
|
9166
8936
|
lines.push("### Recommendations (Priority Order)");
|
|
9167
8937
|
lines.push("");
|
|
9168
|
-
const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "
|
|
8938
|
+
const priorityOrder = ["Security Hub", "GuardDuty", "Inspector", "AWS Config", "CloudTrail"];
|
|
9169
8939
|
const sorted = disabled.sort(
|
|
9170
8940
|
(a, b) => priorityOrder.indexOf(a.name) - priorityOrder.indexOf(b.name)
|
|
9171
8941
|
);
|
|
@@ -9188,7 +8958,7 @@ function createServer(defaultRegion) {
|
|
|
9188
8958
|
const nextMilestones = {
|
|
9189
8959
|
basic: { level: "Intermediate", target: 2, suggestions: ["Security Hub", "GuardDuty"] },
|
|
9190
8960
|
intermediate: { level: "Advanced", target: 4, suggestions: ["Inspector", "AWS Config"] },
|
|
9191
|
-
advanced: { level: "Comprehensive", target:
|
|
8961
|
+
advanced: { level: "Comprehensive", target: 5, suggestions: ["CloudTrail"] }
|
|
9192
8962
|
};
|
|
9193
8963
|
const next = nextMilestones[maturityLevel];
|
|
9194
8964
|
if (next) {
|