aws-security-mcp 0.4.0 → 0.4.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 +639 -55
- package/dist/bin/aws-security-mcp.js.map +1 -1
- package/dist/src/index.js +639 -55
- package/dist/src/index.js.map +1 -1
- package/package.json +4 -2
package/dist/src/index.js
CHANGED
|
@@ -17005,6 +17005,504 @@ var TrustedAdvisorFindingsScanner = class {
|
|
|
17005
17005
|
}
|
|
17006
17006
|
};
|
|
17007
17007
|
|
|
17008
|
+
// src/scanners/config-rules-findings.ts
|
|
17009
|
+
import {
|
|
17010
|
+
ConfigServiceClient as ConfigServiceClient2,
|
|
17011
|
+
DescribeComplianceByConfigRuleCommand,
|
|
17012
|
+
GetComplianceDetailsByConfigRuleCommand
|
|
17013
|
+
} from "@aws-sdk/client-config-service";
|
|
17014
|
+
var SECURITY_RULE_PATTERNS = [
|
|
17015
|
+
"securitygroup",
|
|
17016
|
+
"security-group",
|
|
17017
|
+
"encryption",
|
|
17018
|
+
"encrypted",
|
|
17019
|
+
"public",
|
|
17020
|
+
"unrestricted",
|
|
17021
|
+
"mfa",
|
|
17022
|
+
"password",
|
|
17023
|
+
"access-key",
|
|
17024
|
+
"root",
|
|
17025
|
+
"admin",
|
|
17026
|
+
"logging",
|
|
17027
|
+
"cloudtrail",
|
|
17028
|
+
"iam",
|
|
17029
|
+
"kms",
|
|
17030
|
+
"ssl",
|
|
17031
|
+
"tls",
|
|
17032
|
+
"vpc-flow",
|
|
17033
|
+
"guardduty",
|
|
17034
|
+
"securityhub"
|
|
17035
|
+
];
|
|
17036
|
+
function ruleIsSecurityRelated(ruleName) {
|
|
17037
|
+
const lower = ruleName.toLowerCase();
|
|
17038
|
+
return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
|
|
17039
|
+
}
|
|
17040
|
+
var ConfigRulesFindingsScanner = class {
|
|
17041
|
+
moduleName = "config_rules_findings";
|
|
17042
|
+
async scan(ctx) {
|
|
17043
|
+
const { region, partition, accountId } = ctx;
|
|
17044
|
+
const startMs = Date.now();
|
|
17045
|
+
const findings = [];
|
|
17046
|
+
const warnings = [];
|
|
17047
|
+
let resourcesScanned = 0;
|
|
17048
|
+
try {
|
|
17049
|
+
const client = createClient(ConfigServiceClient2, region, ctx.credentials);
|
|
17050
|
+
let nextToken;
|
|
17051
|
+
const nonCompliantRules = [];
|
|
17052
|
+
do {
|
|
17053
|
+
const resp = await client.send(
|
|
17054
|
+
new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
|
|
17055
|
+
);
|
|
17056
|
+
for (const rule of resp.ComplianceByConfigRules ?? []) {
|
|
17057
|
+
resourcesScanned++;
|
|
17058
|
+
if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
|
|
17059
|
+
nonCompliantRules.push(rule);
|
|
17060
|
+
}
|
|
17061
|
+
}
|
|
17062
|
+
nextToken = resp.NextToken;
|
|
17063
|
+
} while (nextToken);
|
|
17064
|
+
if (resourcesScanned === 0) {
|
|
17065
|
+
warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
|
|
17066
|
+
return {
|
|
17067
|
+
module: this.moduleName,
|
|
17068
|
+
status: "success",
|
|
17069
|
+
warnings,
|
|
17070
|
+
resourcesScanned: 0,
|
|
17071
|
+
findingsCount: 0,
|
|
17072
|
+
scanTimeMs: Date.now() - startMs,
|
|
17073
|
+
findings: []
|
|
17074
|
+
};
|
|
17075
|
+
}
|
|
17076
|
+
for (const rule of nonCompliantRules) {
|
|
17077
|
+
const ruleName = rule.ConfigRuleName ?? "unknown";
|
|
17078
|
+
try {
|
|
17079
|
+
let detailToken;
|
|
17080
|
+
do {
|
|
17081
|
+
const detailResp = await client.send(
|
|
17082
|
+
new GetComplianceDetailsByConfigRuleCommand({
|
|
17083
|
+
ConfigRuleName: ruleName,
|
|
17084
|
+
ComplianceTypes: ["NON_COMPLIANT"],
|
|
17085
|
+
NextToken: detailToken
|
|
17086
|
+
})
|
|
17087
|
+
);
|
|
17088
|
+
for (const evalResult of detailResp.EvaluationResults ?? []) {
|
|
17089
|
+
const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
|
|
17090
|
+
const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
|
|
17091
|
+
const resourceId = qualifier?.ResourceId ?? "unknown";
|
|
17092
|
+
const annotation = evalResult.Annotation;
|
|
17093
|
+
const isSecurityRule = ruleIsSecurityRelated(ruleName);
|
|
17094
|
+
const riskScore = isSecurityRule ? 7.5 : 5.5;
|
|
17095
|
+
const severity = severityFromScore(riskScore);
|
|
17096
|
+
const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
|
|
17097
|
+
if (annotation) descParts.push(`Annotation: ${annotation}`);
|
|
17098
|
+
findings.push({
|
|
17099
|
+
severity,
|
|
17100
|
+
title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
|
|
17101
|
+
resourceType,
|
|
17102
|
+
resourceId,
|
|
17103
|
+
resourceArn: resourceId,
|
|
17104
|
+
region,
|
|
17105
|
+
description: descParts.join(". "),
|
|
17106
|
+
impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
|
|
17107
|
+
riskScore,
|
|
17108
|
+
remediationSteps: [
|
|
17109
|
+
`Review the Config Rule "${ruleName}" in the AWS Config console.`,
|
|
17110
|
+
`Check resource ${resourceId} for compliance violations.`,
|
|
17111
|
+
"Follow the rule's remediation guidance to bring the resource into compliance."
|
|
17112
|
+
],
|
|
17113
|
+
priority: priorityFromSeverity(severity),
|
|
17114
|
+
module: this.moduleName,
|
|
17115
|
+
accountId
|
|
17116
|
+
});
|
|
17117
|
+
}
|
|
17118
|
+
detailToken = detailResp.NextToken;
|
|
17119
|
+
} while (detailToken);
|
|
17120
|
+
} catch (detailErr) {
|
|
17121
|
+
const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
|
|
17122
|
+
warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
|
|
17123
|
+
}
|
|
17124
|
+
}
|
|
17125
|
+
return {
|
|
17126
|
+
module: this.moduleName,
|
|
17127
|
+
status: "success",
|
|
17128
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17129
|
+
resourcesScanned,
|
|
17130
|
+
findingsCount: findings.length,
|
|
17131
|
+
scanTimeMs: Date.now() - startMs,
|
|
17132
|
+
findings
|
|
17133
|
+
};
|
|
17134
|
+
} catch (err) {
|
|
17135
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17136
|
+
if (msg.includes("NoSuchConfigurationRecorder") || msg.includes("InsufficientDeliveryPolicy") || msg.includes("No Configuration Recorder")) {
|
|
17137
|
+
warnings.push("AWS Config is not enabled in this region.");
|
|
17138
|
+
return {
|
|
17139
|
+
module: this.moduleName,
|
|
17140
|
+
status: "success",
|
|
17141
|
+
warnings,
|
|
17142
|
+
resourcesScanned: 0,
|
|
17143
|
+
findingsCount: 0,
|
|
17144
|
+
scanTimeMs: Date.now() - startMs,
|
|
17145
|
+
findings: []
|
|
17146
|
+
};
|
|
17147
|
+
}
|
|
17148
|
+
return {
|
|
17149
|
+
module: this.moduleName,
|
|
17150
|
+
status: "error",
|
|
17151
|
+
error: `Config Rules scan failed: ${msg}`,
|
|
17152
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17153
|
+
resourcesScanned,
|
|
17154
|
+
findingsCount: 0,
|
|
17155
|
+
scanTimeMs: Date.now() - startMs,
|
|
17156
|
+
findings: []
|
|
17157
|
+
};
|
|
17158
|
+
}
|
|
17159
|
+
}
|
|
17160
|
+
};
|
|
17161
|
+
|
|
17162
|
+
// src/scanners/access-analyzer-findings.ts
|
|
17163
|
+
import {
|
|
17164
|
+
AccessAnalyzerClient,
|
|
17165
|
+
ListAnalyzersCommand,
|
|
17166
|
+
ListFindingsV2Command
|
|
17167
|
+
} from "@aws-sdk/client-accessanalyzer";
|
|
17168
|
+
function findingTypeToScore(findingType) {
|
|
17169
|
+
const ft = findingType;
|
|
17170
|
+
switch (ft) {
|
|
17171
|
+
case "ExternalAccess":
|
|
17172
|
+
return 8;
|
|
17173
|
+
case "UnusedIAMRole":
|
|
17174
|
+
case "UnusedIAMUserAccessKey":
|
|
17175
|
+
case "UnusedIAMUserPassword":
|
|
17176
|
+
return 5.5;
|
|
17177
|
+
case "UnusedPermission":
|
|
17178
|
+
return 3;
|
|
17179
|
+
default:
|
|
17180
|
+
return 5.5;
|
|
17181
|
+
}
|
|
17182
|
+
}
|
|
17183
|
+
var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
|
|
17184
|
+
"UnusedIAMRole",
|
|
17185
|
+
"UnusedIAMUserAccessKey",
|
|
17186
|
+
"UnusedIAMUserPassword",
|
|
17187
|
+
"UnusedPermission"
|
|
17188
|
+
]);
|
|
17189
|
+
function isSecurityRelevant(findingType) {
|
|
17190
|
+
const ft = findingType;
|
|
17191
|
+
return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
|
|
17192
|
+
}
|
|
17193
|
+
function isExternalAccess(findingType) {
|
|
17194
|
+
return findingType === "ExternalAccess";
|
|
17195
|
+
}
|
|
17196
|
+
var AccessAnalyzerFindingsScanner = class {
|
|
17197
|
+
moduleName = "access_analyzer_findings";
|
|
17198
|
+
async scan(ctx) {
|
|
17199
|
+
const { region, partition, accountId } = ctx;
|
|
17200
|
+
const startMs = Date.now();
|
|
17201
|
+
const findings = [];
|
|
17202
|
+
const warnings = [];
|
|
17203
|
+
let resourcesScanned = 0;
|
|
17204
|
+
try {
|
|
17205
|
+
const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
|
|
17206
|
+
let analyzerToken;
|
|
17207
|
+
const analyzers = [];
|
|
17208
|
+
do {
|
|
17209
|
+
const resp = await client.send(
|
|
17210
|
+
new ListAnalyzersCommand({ nextToken: analyzerToken })
|
|
17211
|
+
);
|
|
17212
|
+
for (const analyzer of resp.analyzers ?? []) {
|
|
17213
|
+
if (analyzer.status === "ACTIVE") {
|
|
17214
|
+
analyzers.push(analyzer);
|
|
17215
|
+
}
|
|
17216
|
+
}
|
|
17217
|
+
analyzerToken = resp.nextToken;
|
|
17218
|
+
} while (analyzerToken);
|
|
17219
|
+
if (analyzers.length === 0) {
|
|
17220
|
+
warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
|
|
17221
|
+
return {
|
|
17222
|
+
module: this.moduleName,
|
|
17223
|
+
status: "success",
|
|
17224
|
+
warnings,
|
|
17225
|
+
resourcesScanned: 0,
|
|
17226
|
+
findingsCount: 0,
|
|
17227
|
+
scanTimeMs: Date.now() - startMs,
|
|
17228
|
+
findings: []
|
|
17229
|
+
};
|
|
17230
|
+
}
|
|
17231
|
+
for (const analyzer of analyzers) {
|
|
17232
|
+
const analyzerArn = analyzer.arn ?? "unknown";
|
|
17233
|
+
let findingToken;
|
|
17234
|
+
do {
|
|
17235
|
+
const listResp = await client.send(
|
|
17236
|
+
new ListFindingsV2Command({
|
|
17237
|
+
analyzerArn,
|
|
17238
|
+
filter: {
|
|
17239
|
+
status: { eq: ["ACTIVE"] }
|
|
17240
|
+
},
|
|
17241
|
+
nextToken: findingToken
|
|
17242
|
+
})
|
|
17243
|
+
);
|
|
17244
|
+
for (const aaf of listResp.findings ?? []) {
|
|
17245
|
+
if (!isSecurityRelevant(aaf.findingType)) {
|
|
17246
|
+
continue;
|
|
17247
|
+
}
|
|
17248
|
+
resourcesScanned++;
|
|
17249
|
+
const score = findingTypeToScore(aaf.findingType);
|
|
17250
|
+
const severity = severityFromScore(score);
|
|
17251
|
+
const resourceArn = aaf.resource ?? "unknown";
|
|
17252
|
+
const resourceType = aaf.resourceType ?? "AWS::Unknown";
|
|
17253
|
+
const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
|
|
17254
|
+
const external = isExternalAccess(aaf.findingType);
|
|
17255
|
+
const descParts = [`Resource Type: ${resourceType}`];
|
|
17256
|
+
if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
|
|
17257
|
+
if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
|
|
17258
|
+
const title = buildFindingTitle(aaf);
|
|
17259
|
+
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"}`;
|
|
17260
|
+
const remediationSteps = external ? [
|
|
17261
|
+
"Review the finding in the IAM Access Analyzer console.",
|
|
17262
|
+
`Check resource ${resourceId} for unintended external access.`,
|
|
17263
|
+
"Remove or restrict the resource policy to eliminate external access."
|
|
17264
|
+
] : [
|
|
17265
|
+
"Review the finding in the IAM Access Analyzer console.",
|
|
17266
|
+
`Check resource ${resourceId} for unused access permissions.`,
|
|
17267
|
+
"Remove unused permissions, roles, or credentials to follow least-privilege."
|
|
17268
|
+
];
|
|
17269
|
+
findings.push({
|
|
17270
|
+
severity,
|
|
17271
|
+
title,
|
|
17272
|
+
resourceType: mapResourceType(resourceType),
|
|
17273
|
+
resourceId,
|
|
17274
|
+
resourceArn,
|
|
17275
|
+
region,
|
|
17276
|
+
description: descParts.join(". "),
|
|
17277
|
+
impact,
|
|
17278
|
+
riskScore: score,
|
|
17279
|
+
remediationSteps,
|
|
17280
|
+
priority: priorityFromSeverity(severity),
|
|
17281
|
+
module: this.moduleName,
|
|
17282
|
+
accountId: aaf.resourceOwnerAccount ?? accountId
|
|
17283
|
+
});
|
|
17284
|
+
}
|
|
17285
|
+
findingToken = listResp.nextToken;
|
|
17286
|
+
} while (findingToken);
|
|
17287
|
+
}
|
|
17288
|
+
return {
|
|
17289
|
+
module: this.moduleName,
|
|
17290
|
+
status: "success",
|
|
17291
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17292
|
+
resourcesScanned,
|
|
17293
|
+
findingsCount: findings.length,
|
|
17294
|
+
scanTimeMs: Date.now() - startMs,
|
|
17295
|
+
findings
|
|
17296
|
+
};
|
|
17297
|
+
} catch (err) {
|
|
17298
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17299
|
+
return {
|
|
17300
|
+
module: this.moduleName,
|
|
17301
|
+
status: "error",
|
|
17302
|
+
error: `Access Analyzer scan failed: ${msg}`,
|
|
17303
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17304
|
+
resourcesScanned,
|
|
17305
|
+
findingsCount: 0,
|
|
17306
|
+
scanTimeMs: Date.now() - startMs,
|
|
17307
|
+
findings: []
|
|
17308
|
+
};
|
|
17309
|
+
}
|
|
17310
|
+
}
|
|
17311
|
+
};
|
|
17312
|
+
function buildFindingTitle(finding) {
|
|
17313
|
+
const resourceType = finding.resourceType ?? "Resource";
|
|
17314
|
+
const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
|
|
17315
|
+
const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
|
|
17316
|
+
return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
|
|
17317
|
+
}
|
|
17318
|
+
function mapResourceType(aaType) {
|
|
17319
|
+
const mapping = {
|
|
17320
|
+
"AWS::S3::Bucket": "AWS::S3::Bucket",
|
|
17321
|
+
"AWS::IAM::Role": "AWS::IAM::Role",
|
|
17322
|
+
"AWS::SQS::Queue": "AWS::SQS::Queue",
|
|
17323
|
+
"AWS::Lambda::Function": "AWS::Lambda::Function",
|
|
17324
|
+
"AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
|
|
17325
|
+
"AWS::KMS::Key": "AWS::KMS::Key",
|
|
17326
|
+
"AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
|
|
17327
|
+
"AWS::SNS::Topic": "AWS::SNS::Topic",
|
|
17328
|
+
"AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
|
|
17329
|
+
"AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
|
|
17330
|
+
"AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
|
|
17331
|
+
"AWS::ECR::Repository": "AWS::ECR::Repository"
|
|
17332
|
+
};
|
|
17333
|
+
return mapping[aaType] ?? aaType;
|
|
17334
|
+
}
|
|
17335
|
+
|
|
17336
|
+
// src/scanners/patch-compliance-findings.ts
|
|
17337
|
+
import {
|
|
17338
|
+
SSMClient,
|
|
17339
|
+
DescribeInstanceInformationCommand,
|
|
17340
|
+
DescribeInstancePatchStatesCommand
|
|
17341
|
+
} from "@aws-sdk/client-ssm";
|
|
17342
|
+
var PatchComplianceFindingsScanner = class {
|
|
17343
|
+
moduleName = "patch_compliance_findings";
|
|
17344
|
+
async scan(ctx) {
|
|
17345
|
+
const { region, partition, accountId } = ctx;
|
|
17346
|
+
const startMs = Date.now();
|
|
17347
|
+
const findings = [];
|
|
17348
|
+
const warnings = [];
|
|
17349
|
+
let resourcesScanned = 0;
|
|
17350
|
+
try {
|
|
17351
|
+
const client = createClient(SSMClient, region, ctx.credentials);
|
|
17352
|
+
let nextToken;
|
|
17353
|
+
const instances = [];
|
|
17354
|
+
do {
|
|
17355
|
+
const resp = await client.send(
|
|
17356
|
+
new DescribeInstanceInformationCommand({
|
|
17357
|
+
MaxResults: 50,
|
|
17358
|
+
NextToken: nextToken
|
|
17359
|
+
})
|
|
17360
|
+
);
|
|
17361
|
+
instances.push(...resp.InstanceInformationList ?? []);
|
|
17362
|
+
nextToken = resp.NextToken;
|
|
17363
|
+
} while (nextToken);
|
|
17364
|
+
if (instances.length === 0) {
|
|
17365
|
+
warnings.push("No SSM-managed instances found in this region.");
|
|
17366
|
+
return {
|
|
17367
|
+
module: this.moduleName,
|
|
17368
|
+
status: "success",
|
|
17369
|
+
warnings,
|
|
17370
|
+
resourcesScanned: 0,
|
|
17371
|
+
findingsCount: 0,
|
|
17372
|
+
scanTimeMs: Date.now() - startMs,
|
|
17373
|
+
findings: []
|
|
17374
|
+
};
|
|
17375
|
+
}
|
|
17376
|
+
resourcesScanned = instances.length;
|
|
17377
|
+
const instanceIds = instances.map((i) => i.InstanceId).filter(Boolean);
|
|
17378
|
+
const patchStateMap = /* @__PURE__ */ new Map();
|
|
17379
|
+
for (let i = 0; i < instanceIds.length; i += 50) {
|
|
17380
|
+
const batch = instanceIds.slice(i, i + 50);
|
|
17381
|
+
let patchToken;
|
|
17382
|
+
do {
|
|
17383
|
+
const patchResp = await client.send(
|
|
17384
|
+
new DescribeInstancePatchStatesCommand({
|
|
17385
|
+
InstanceIds: batch,
|
|
17386
|
+
NextToken: patchToken
|
|
17387
|
+
})
|
|
17388
|
+
);
|
|
17389
|
+
for (const ps of patchResp.InstancePatchStates ?? []) {
|
|
17390
|
+
if (ps.InstanceId) {
|
|
17391
|
+
patchStateMap.set(ps.InstanceId, ps);
|
|
17392
|
+
}
|
|
17393
|
+
}
|
|
17394
|
+
patchToken = patchResp.NextToken;
|
|
17395
|
+
} while (patchToken);
|
|
17396
|
+
}
|
|
17397
|
+
for (const instance of instances) {
|
|
17398
|
+
const instanceId = instance.InstanceId ?? "unknown";
|
|
17399
|
+
const platform = instance.PlatformName ?? instance.PlatformType ?? "unknown";
|
|
17400
|
+
const instanceArn = `arn:${partition}:ec2:${region}:${accountId}:instance/${instanceId}`;
|
|
17401
|
+
const patchState = patchStateMap.get(instanceId);
|
|
17402
|
+
if (!patchState) {
|
|
17403
|
+
const riskScore2 = 3;
|
|
17404
|
+
const severity2 = severityFromScore(riskScore2);
|
|
17405
|
+
findings.push({
|
|
17406
|
+
severity: severity2,
|
|
17407
|
+
title: `Instance ${instanceId} has no patch compliance data`,
|
|
17408
|
+
resourceType: "AWS::EC2::Instance",
|
|
17409
|
+
resourceId: instanceId,
|
|
17410
|
+
resourceArn: instanceArn,
|
|
17411
|
+
region,
|
|
17412
|
+
description: `Instance ${instanceId} (${platform}) is managed by SSM but has no patch compliance data. Patch Manager may not be configured for this instance.`,
|
|
17413
|
+
impact: "Patch compliance status is unknown \u2014 vulnerabilities may exist.",
|
|
17414
|
+
riskScore: riskScore2,
|
|
17415
|
+
remediationSteps: [
|
|
17416
|
+
"Configure AWS Systems Manager Patch Manager for this instance.",
|
|
17417
|
+
"Create a patch baseline and maintenance window.",
|
|
17418
|
+
"Run a patch scan to establish compliance status."
|
|
17419
|
+
],
|
|
17420
|
+
priority: priorityFromSeverity(severity2),
|
|
17421
|
+
module: this.moduleName,
|
|
17422
|
+
accountId
|
|
17423
|
+
});
|
|
17424
|
+
continue;
|
|
17425
|
+
}
|
|
17426
|
+
const missingCount = patchState.MissingCount ?? 0;
|
|
17427
|
+
const failedCount = patchState.FailedCount ?? 0;
|
|
17428
|
+
const criticalNonCompliantCount = patchState.CriticalNonCompliantCount ?? 0;
|
|
17429
|
+
const securityNonCompliantCount = patchState.SecurityNonCompliantCount ?? 0;
|
|
17430
|
+
const otherNonCompliantCount = patchState.OtherNonCompliantCount ?? 0;
|
|
17431
|
+
const lastScanTime = patchState.OperationEndTime?.toISOString() ?? "unknown";
|
|
17432
|
+
if (missingCount === 0 && failedCount === 0 && criticalNonCompliantCount === 0 && securityNonCompliantCount === 0 && otherNonCompliantCount === 0) {
|
|
17433
|
+
continue;
|
|
17434
|
+
}
|
|
17435
|
+
let riskScore;
|
|
17436
|
+
if (criticalNonCompliantCount > 0 || securityNonCompliantCount > 0 || failedCount > 0) {
|
|
17437
|
+
riskScore = 7.5;
|
|
17438
|
+
} else if (otherNonCompliantCount > 0) {
|
|
17439
|
+
riskScore = 5.5;
|
|
17440
|
+
} else {
|
|
17441
|
+
riskScore = 5.5;
|
|
17442
|
+
}
|
|
17443
|
+
const severity = severityFromScore(riskScore);
|
|
17444
|
+
const titleParts = [];
|
|
17445
|
+
if (missingCount > 0) titleParts.push(`${missingCount} missing`);
|
|
17446
|
+
if (failedCount > 0) titleParts.push(`${failedCount} failed`);
|
|
17447
|
+
if (criticalNonCompliantCount > 0) titleParts.push(`${criticalNonCompliantCount} critical non-compliant`);
|
|
17448
|
+
if (securityNonCompliantCount > 0) titleParts.push(`${securityNonCompliantCount} security non-compliant`);
|
|
17449
|
+
if (otherNonCompliantCount > 0) titleParts.push(`${otherNonCompliantCount} other non-compliant`);
|
|
17450
|
+
const descParts = [
|
|
17451
|
+
`Instance: ${instanceId}`,
|
|
17452
|
+
`Platform: ${platform}`,
|
|
17453
|
+
`Missing patches: ${missingCount}`,
|
|
17454
|
+
`Failed patches: ${failedCount}`,
|
|
17455
|
+
`Critical non-compliant: ${criticalNonCompliantCount}`,
|
|
17456
|
+
`Security non-compliant: ${securityNonCompliantCount}`,
|
|
17457
|
+
`Other non-compliant: ${otherNonCompliantCount}`,
|
|
17458
|
+
`Last scan: ${lastScanTime}`
|
|
17459
|
+
];
|
|
17460
|
+
findings.push({
|
|
17461
|
+
severity,
|
|
17462
|
+
title: `Instance ${instanceId} has ${titleParts.join(", ")} patches`,
|
|
17463
|
+
resourceType: "AWS::EC2::Instance",
|
|
17464
|
+
resourceId: instanceId,
|
|
17465
|
+
resourceArn: instanceArn,
|
|
17466
|
+
region,
|
|
17467
|
+
description: descParts.join(". "),
|
|
17468
|
+
impact: `Instance has ${missingCount} missing and ${failedCount} failed patches \u2014 potential security vulnerabilities.`,
|
|
17469
|
+
riskScore,
|
|
17470
|
+
remediationSteps: [
|
|
17471
|
+
`Review patch compliance for instance ${instanceId} in the Systems Manager console.`,
|
|
17472
|
+
"Apply missing patches using a maintenance window or manual patching.",
|
|
17473
|
+
"Investigate and resolve any failed patch installations.",
|
|
17474
|
+
"Consider enabling automatic patching through Patch Manager."
|
|
17475
|
+
],
|
|
17476
|
+
priority: priorityFromSeverity(severity),
|
|
17477
|
+
module: this.moduleName,
|
|
17478
|
+
accountId
|
|
17479
|
+
});
|
|
17480
|
+
}
|
|
17481
|
+
return {
|
|
17482
|
+
module: this.moduleName,
|
|
17483
|
+
status: "success",
|
|
17484
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17485
|
+
resourcesScanned,
|
|
17486
|
+
findingsCount: findings.length,
|
|
17487
|
+
scanTimeMs: Date.now() - startMs,
|
|
17488
|
+
findings
|
|
17489
|
+
};
|
|
17490
|
+
} catch (err) {
|
|
17491
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17492
|
+
return {
|
|
17493
|
+
module: this.moduleName,
|
|
17494
|
+
status: "error",
|
|
17495
|
+
error: `Patch compliance scan failed: ${msg}`,
|
|
17496
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
17497
|
+
resourcesScanned,
|
|
17498
|
+
findingsCount: 0,
|
|
17499
|
+
scanTimeMs: Date.now() - startMs,
|
|
17500
|
+
findings: []
|
|
17501
|
+
};
|
|
17502
|
+
}
|
|
17503
|
+
}
|
|
17504
|
+
};
|
|
17505
|
+
|
|
17008
17506
|
// src/tools/report-tool.ts
|
|
17009
17507
|
var SEVERITY_ICON = {
|
|
17010
17508
|
CRITICAL: "\u{1F534}",
|
|
@@ -17516,10 +18014,21 @@ function sharedCss() {
|
|
|
17516
18014
|
.finding-card-body p{color:#cbd5e1;font-size:13px;margin-bottom:4px}
|
|
17517
18015
|
.finding-card-body ol{padding-left:20px}
|
|
17518
18016
|
.finding-card-body li{color:#cbd5e1;font-size:13px;margin-bottom:2px}
|
|
18017
|
+
.rec-fold{background:#1e293b;border:1px solid #334155;border-radius:8px;margin-bottom:16px;overflow:hidden}
|
|
18018
|
+
.rec-fold>summary{cursor:pointer;padding:16px 20px;display:flex;align-items:center;gap:12px;list-style:none;user-select:none}
|
|
18019
|
+
.rec-fold>summary::-webkit-details-marker{display:none}
|
|
18020
|
+
.rec-fold>summary::marker{content:""}
|
|
18021
|
+
.rec-fold>summary::after{content:"\\25B6";font-size:12px;color:#64748b;flex-shrink:0;transition:transform 0.2s;margin-left:auto}
|
|
18022
|
+
.rec-fold[open]>summary::after{transform:rotate(90deg)}
|
|
18023
|
+
.rec-fold[open]>summary{border-bottom:1px solid #334155}
|
|
18024
|
+
.rec-body{padding:12px 20px 16px}
|
|
18025
|
+
.rec-body ol{padding-left:24px}
|
|
18026
|
+
.rec-body li{margin-bottom:8px;color:#cbd5e1;font-size:13px}
|
|
18027
|
+
.rec-body .badge{margin-right:6px;vertical-align:middle}
|
|
17519
18028
|
@media print{
|
|
17520
18029
|
body{background:#fff;color:#1e293b;-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
|
17521
18030
|
.container{max-width:100%;padding:20px}
|
|
17522
|
-
.card,.score-card,.stat-card,.chart-box,.finding-fold,.top5-card,.trend-chart,.category-fold,.module-fold,.finding-card{background:#fff;border:1px solid #e2e8f0}
|
|
18031
|
+
.card,.score-card,.stat-card,.chart-box,.finding-fold,.top5-card,.trend-chart,.category-fold,.module-fold,.finding-card,.rec-fold{background:#fff;border:1px solid #e2e8f0}
|
|
17523
18032
|
.badge{border:1px solid}
|
|
17524
18033
|
header{border-bottom-color:#e2e8f0}
|
|
17525
18034
|
h2{border-bottom-color:#e2e8f0}
|
|
@@ -17537,10 +18046,10 @@ function sharedCss() {
|
|
|
17537
18046
|
.finding-title-text{color:#1e293b}
|
|
17538
18047
|
.finding-resource{color:#64748b}
|
|
17539
18048
|
.check-findings li{color:#64748b}
|
|
17540
|
-
.finding-fold,.top5-card,.category-fold,.module-fold,.finding-card{break-inside:avoid}
|
|
18049
|
+
.finding-fold,.top5-card,.category-fold,.module-fold,.finding-card,.rec-fold{break-inside:avoid}
|
|
17541
18050
|
.check-item{break-inside:avoid}
|
|
17542
18051
|
svg text{fill:#1e293b !important}
|
|
17543
|
-
.finding-fold[open]>summary,.category-fold[open]>summary,.module-fold[open]>summary{border-bottom-color:#e2e8f0}
|
|
18052
|
+
.finding-fold[open]>summary,.category-fold[open]>summary,.module-fold[open]>summary,.rec-fold[open]>summary{border-bottom-color:#e2e8f0}
|
|
17544
18053
|
details{display:block}
|
|
17545
18054
|
details>summary{display:block}
|
|
17546
18055
|
details>:not(summary){display:block !important}
|
|
@@ -17779,8 +18288,6 @@ ${rest}
|
|
|
17779
18288
|
return b[1].length - a[1].length;
|
|
17780
18289
|
});
|
|
17781
18290
|
findingsHtml = moduleEntries.map(([modName, modFindings]) => {
|
|
17782
|
-
const hasCritHigh = modFindings.some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
17783
|
-
const openAttr = hasCritHigh ? " open" : "";
|
|
17784
18291
|
const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
17785
18292
|
for (const f of modFindings) sevCounts[f.severity]++;
|
|
17786
18293
|
const badges = 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(" ");
|
|
@@ -17790,19 +18297,12 @@ ${rest}
|
|
|
17790
18297
|
findings.sort((a, b) => b.riskScore - a.riskScore);
|
|
17791
18298
|
const emoji3 = SEV_EMOJI[sev] ?? "";
|
|
17792
18299
|
const label = sev.charAt(0) + sev.slice(1).toLowerCase();
|
|
17793
|
-
const isCritHigh = sev === "CRITICAL" || sev === "HIGH";
|
|
17794
|
-
if (isCritHigh) {
|
|
17795
|
-
return `<div class="severity-group">
|
|
17796
|
-
<h4>${emoji3} ${label} (${findings.length})</h4>
|
|
17797
|
-
${renderCards(findings)}
|
|
17798
|
-
</div>`;
|
|
17799
|
-
}
|
|
17800
18300
|
return `<details class="severity-group-fold">
|
|
17801
|
-
<summary><h4>${emoji3} ${label} (${findings.length})
|
|
18301
|
+
<summary><h4>${emoji3} ${label} (${findings.length})</h4></summary>
|
|
17802
18302
|
${renderCards(findings)}
|
|
17803
18303
|
</details>`;
|
|
17804
18304
|
}).filter(Boolean).join("\n");
|
|
17805
|
-
return `<details class="module-fold"
|
|
18305
|
+
return `<details class="module-fold">
|
|
17806
18306
|
<summary>
|
|
17807
18307
|
<h3>🔒 ${esc2(modName)} (${modFindings.length})</h3>
|
|
17808
18308
|
<span class="module-badges">${badges}</span>
|
|
@@ -17833,17 +18333,43 @@ ${rest}
|
|
|
17833
18333
|
).join("\n");
|
|
17834
18334
|
let recsHtml = "";
|
|
17835
18335
|
if (summary.totalFindings > 0) {
|
|
17836
|
-
const
|
|
17837
|
-
const
|
|
17838
|
-
const pc = f.priority.toLowerCase();
|
|
18336
|
+
const recMap = /* @__PURE__ */ new Map();
|
|
18337
|
+
for (const f of allFindings) {
|
|
17839
18338
|
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
17840
|
-
|
|
17841
|
-
|
|
18339
|
+
const existing = recMap.get(rem);
|
|
18340
|
+
if (existing) {
|
|
18341
|
+
existing.count++;
|
|
18342
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
|
|
18343
|
+
existing.severity = f.severity;
|
|
18344
|
+
}
|
|
18345
|
+
} else {
|
|
18346
|
+
recMap.set(rem, { text: rem, severity: f.severity, count: 1 });
|
|
18347
|
+
}
|
|
18348
|
+
}
|
|
18349
|
+
const uniqueRecs = [...recMap.values()].sort((a, b) => {
|
|
18350
|
+
const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
|
|
18351
|
+
if (sevDiff !== 0) return sevDiff;
|
|
18352
|
+
return b.count - a.count;
|
|
18353
|
+
});
|
|
18354
|
+
const renderRec = (r) => {
|
|
18355
|
+
const sev = r.severity.toLowerCase();
|
|
18356
|
+
const countLabel = r.count > 1 ? ` (× ${r.count})` : "";
|
|
18357
|
+
return `<li><span class="badge badge-${esc2(sev)}">${esc2(r.severity)}</span> ${esc2(r.text)}${countLabel}</li>`;
|
|
18358
|
+
};
|
|
18359
|
+
const TOP_N = 10;
|
|
18360
|
+
const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
|
|
18361
|
+
const remaining = uniqueRecs.slice(TOP_N);
|
|
18362
|
+
const moreHtml = remaining.length > 0 ? `
|
|
18363
|
+
<details><summary>Show ${remaining.length} more…</summary>
|
|
18364
|
+
${remaining.map(renderRec).join("\n")}
|
|
18365
|
+
</details>` : "";
|
|
17842
18366
|
recsHtml = `
|
|
17843
|
-
<
|
|
17844
|
-
<h2>Recommendations (
|
|
17845
|
-
<
|
|
17846
|
-
|
|
18367
|
+
<details class="rec-fold">
|
|
18368
|
+
<summary><h2 style="margin:0;border:0;display:inline">Recommendations (${uniqueRecs.length} unique)</h2></summary>
|
|
18369
|
+
<div class="rec-body">
|
|
18370
|
+
<ol>${topItems}${moreHtml}</ol>
|
|
18371
|
+
</div>
|
|
18372
|
+
</details>`;
|
|
17847
18373
|
}
|
|
17848
18374
|
return `<!DOCTYPE html>
|
|
17849
18375
|
<html lang="en">
|
|
@@ -17958,8 +18484,6 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
17958
18484
|
const catUnknown = categoryResults.filter(
|
|
17959
18485
|
(r) => r.status === "unknown"
|
|
17960
18486
|
).length;
|
|
17961
|
-
const hasFailure = catFail > 0;
|
|
17962
|
-
const openAttr = hasFailure ? " open" : "";
|
|
17963
18487
|
const byId = /* @__PURE__ */ new Map();
|
|
17964
18488
|
for (const r of categoryResults) {
|
|
17965
18489
|
const existing = byId.get(r.check.id) ?? [];
|
|
@@ -17967,6 +18491,9 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
17967
18491
|
byId.set(r.check.id, existing);
|
|
17968
18492
|
}
|
|
17969
18493
|
const groups = [...byId.entries()].map(([checkId, checkResults]) => {
|
|
18494
|
+
const grpPass = checkResults.filter((r) => r.status === "pass").length;
|
|
18495
|
+
const grpFail = checkResults.filter((r) => r.status === "fail").length;
|
|
18496
|
+
const grpUnknown = checkResults.filter((r) => r.status === "unknown").length;
|
|
17970
18497
|
const items = checkResults.map((r) => {
|
|
17971
18498
|
const icon = r.status === "pass" ? "✔" : r.status === "fail" ? "✘" : "⚠";
|
|
17972
18499
|
const cls = `check-${r.status}`;
|
|
@@ -17985,15 +18512,21 @@ function generateMlps3HtmlReport(scanResults, history) {
|
|
|
17985
18512
|
}
|
|
17986
18513
|
return `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc2(r.check.name)}${label}</span></div>${findingsHtml}`;
|
|
17987
18514
|
}).join("\n");
|
|
17988
|
-
|
|
17989
|
-
${
|
|
18515
|
+
const statusBadges = [
|
|
18516
|
+
grpPass > 0 ? `<span class="category-stat-pass">✓ ${grpPass}</span>` : "",
|
|
18517
|
+
grpFail > 0 ? `<span class="category-stat-fail">✗ ${grpFail}</span>` : "",
|
|
18518
|
+
grpUnknown > 0 ? `<span class="category-stat-unknown">? ${grpUnknown}</span>` : ""
|
|
18519
|
+
].filter(Boolean).join(" ");
|
|
18520
|
+
return `<details class="severity-group-fold"><summary><h4>${esc2(checkId)} ${esc2(checkResults[0].check.name)} <span class="category-stats">${statusBadges}</span></h4></summary>
|
|
18521
|
+
${items}
|
|
18522
|
+
</details>`;
|
|
17990
18523
|
}).join("\n");
|
|
17991
18524
|
const statsHtml = [
|
|
17992
18525
|
catPass > 0 ? `<span class="category-stat-pass">✓ ${catPass}</span>` : "",
|
|
17993
18526
|
catFail > 0 ? `<span class="category-stat-fail">✗ ${catFail}</span>` : "",
|
|
17994
18527
|
catUnknown > 0 ? `<span class="category-stat-unknown">? ${catUnknown}</span>` : ""
|
|
17995
18528
|
].filter(Boolean).join("");
|
|
17996
|
-
return `<details class="category-fold"
|
|
18529
|
+
return `<details class="category-fold">
|
|
17997
18530
|
<summary>
|
|
17998
18531
|
<span class="category-title">${esc2(sectionTitle)}</span>
|
|
17999
18532
|
<span class="category-stats">${statsHtml}</span>
|
|
@@ -18004,28 +18537,45 @@ ${items}`;
|
|
|
18004
18537
|
const failedResults = results.filter((r) => r.status === "fail");
|
|
18005
18538
|
let remediationHtml = "";
|
|
18006
18539
|
if (failedResults.length > 0) {
|
|
18007
|
-
const
|
|
18540
|
+
const mlpsRecMap = /* @__PURE__ */ new Map();
|
|
18008
18541
|
for (const r of failedResults) {
|
|
18009
18542
|
for (const f of r.relatedFindings) {
|
|
18010
|
-
const
|
|
18011
|
-
|
|
18012
|
-
|
|
18543
|
+
const rem = f.remediationSteps[0] ?? "Review and remediate.";
|
|
18544
|
+
const existing = mlpsRecMap.get(rem);
|
|
18545
|
+
if (existing) {
|
|
18546
|
+
existing.count++;
|
|
18547
|
+
if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
|
|
18548
|
+
existing.severity = f.severity;
|
|
18549
|
+
}
|
|
18550
|
+
} else {
|
|
18551
|
+
mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
|
|
18013
18552
|
}
|
|
18014
18553
|
}
|
|
18015
18554
|
}
|
|
18016
|
-
const
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
|
|
18555
|
+
const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
|
|
18556
|
+
const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
|
|
18557
|
+
if (sevDiff !== 0) return sevDiff;
|
|
18558
|
+
return b.count - a.count;
|
|
18559
|
+
});
|
|
18560
|
+
const renderMlpsRec = (r) => {
|
|
18561
|
+
const sev = r.severity.toLowerCase();
|
|
18562
|
+
const countLabel = r.count > 1 ? ` (× ${r.count})` : "";
|
|
18563
|
+
return `<li><span class="badge badge-${esc2(sev)}">${esc2(r.severity)}</span> ${esc2(r.text)}${countLabel}</li>`;
|
|
18564
|
+
};
|
|
18565
|
+
const MLPS_TOP_N = 10;
|
|
18566
|
+
const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
|
|
18567
|
+
const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
|
|
18568
|
+
const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
|
|
18569
|
+
<details><summary>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879…</summary>
|
|
18570
|
+
${mlpsRemaining.map(renderMlpsRec).join("\n")}
|
|
18571
|
+
</details>` : "";
|
|
18024
18572
|
remediationHtml = `
|
|
18025
|
-
<
|
|
18026
|
-
<h2>\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\
|
|
18027
|
-
<
|
|
18028
|
-
|
|
18573
|
+
<details class="rec-fold">
|
|
18574
|
+
<summary><h2 style="margin:0;border:0;display:inline">\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${mlpsUniqueRecs.length} \u9879\u53BB\u91CD\uFF09</h2></summary>
|
|
18575
|
+
<div class="rec-body">
|
|
18576
|
+
<ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
|
|
18577
|
+
</div>
|
|
18578
|
+
</details>`;
|
|
18029
18579
|
}
|
|
18030
18580
|
const passRateColor = percent >= 80 ? "#22c55e" : percent >= 50 ? "#eab308" : "#ef4444";
|
|
18031
18581
|
const unknownNote = unknownCount > 0 ? `<div style="color:#94a3b8;font-size:12px;margin-top:8px">\uFF08\u672A\u68C0\u67E5\u9879\u4E0D\u8BA1\u5165\u901A\u8FC7\u7387\uFF09</div>` : "";
|
|
@@ -18184,13 +18734,13 @@ var SCAN_GROUPS = {
|
|
|
18184
18734
|
mlps3_precheck: {
|
|
18185
18735
|
name: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0",
|
|
18186
18736
|
description: "GB/T 22239-2019 \u7B49\u4FDD\u4E09\u7EA7 AWS \u4E91\u79DF\u6237\u5C42\u914D\u7F6E\u68C0\u67E5",
|
|
18187
|
-
modules: ["service_detection", "secret_exposure", "ssl_certificate", "dns_dangling", "network_reachability", "iam_privilege_escalation", "tag_compliance", "disaster_recovery", "security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings"],
|
|
18737
|
+
modules: ["service_detection", "secret_exposure", "ssl_certificate", "dns_dangling", "network_reachability", "iam_privilege_escalation", "tag_compliance", "disaster_recovery", "security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings"],
|
|
18188
18738
|
reportType: "mlps3"
|
|
18189
18739
|
},
|
|
18190
18740
|
hw_defense: {
|
|
18191
18741
|
name: "\u62A4\u7F51\u84DD\u961F\u52A0\u56FA",
|
|
18192
18742
|
description: "\u62A4\u7F51\u524D\u5B89\u5168\u81EA\u67E5 \u2014 \u653B\u51FB\u9762+\u5F31\u70B9\u8BC4\u4F30",
|
|
18193
|
-
modules: ["service_detection", "secret_exposure", "network_reachability", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "inspector_findings"],
|
|
18743
|
+
modules: ["service_detection", "secret_exposure", "network_reachability", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "inspector_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings"],
|
|
18194
18744
|
findingsFilter: {
|
|
18195
18745
|
guardDutyTypes: ["Backdoor", "Trojan", "PenTest", "CryptoCurrency"],
|
|
18196
18746
|
minSeverity: "MEDIUM"
|
|
@@ -18199,7 +18749,7 @@ var SCAN_GROUPS = {
|
|
|
18199
18749
|
exposure: {
|
|
18200
18750
|
name: "\u516C\u7F51\u66B4\u9732\u9762\u8BC4\u4F30",
|
|
18201
18751
|
description: "\u8BC4\u4F30\u516C\u7F51\u53EF\u8FBE\u7684\u8D44\u6E90\u548C\u7AEF\u53E3",
|
|
18202
|
-
modules: ["network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "security_hub_findings"],
|
|
18752
|
+
modules: ["network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "security_hub_findings", "access_analyzer_findings"],
|
|
18203
18753
|
findingsFilter: {
|
|
18204
18754
|
securityHubCategories: ["network", "public", "exposure", "port"]
|
|
18205
18755
|
}
|
|
@@ -18220,7 +18770,7 @@ var SCAN_GROUPS = {
|
|
|
18220
18770
|
least_privilege: {
|
|
18221
18771
|
name: "\u6700\u5C0F\u6743\u9650\u5BA1\u8BA1",
|
|
18222
18772
|
description: "IAM \u6743\u9650\u6700\u5C0F\u5316\u8BC4\u4F30",
|
|
18223
|
-
modules: ["iam_privilege_escalation", "security_hub_findings"],
|
|
18773
|
+
modules: ["iam_privilege_escalation", "security_hub_findings", "access_analyzer_findings"],
|
|
18224
18774
|
findingsFilter: {
|
|
18225
18775
|
securityHubCategories: ["IAM", "iam", "access", "privilege"]
|
|
18226
18776
|
}
|
|
@@ -18256,17 +18806,17 @@ var SCAN_GROUPS = {
|
|
|
18256
18806
|
new_account_baseline: {
|
|
18257
18807
|
name: "\u65B0\u8D26\u6237\u57FA\u7EBF\u68C0\u67E5",
|
|
18258
18808
|
description: "\u65B0 AWS \u8D26\u6237\u5B89\u5168\u57FA\u7EBF",
|
|
18259
|
-
modules: ["service_detection", "secret_exposure", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings"]
|
|
18809
|
+
modules: ["service_detection", "secret_exposure", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "access_analyzer_findings"]
|
|
18260
18810
|
},
|
|
18261
18811
|
aggregation: {
|
|
18262
18812
|
name: "\u5B89\u5168\u670D\u52A1\u805A\u5408",
|
|
18263
18813
|
description: "\u4ECE Security Hub / GuardDuty / Inspector / Trusted Advisor \u805A\u5408\u6240\u6709\u5B89\u5168\u53D1\u73B0",
|
|
18264
|
-
modules: ["security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings"]
|
|
18814
|
+
modules: ["security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings"]
|
|
18265
18815
|
}
|
|
18266
18816
|
};
|
|
18267
18817
|
|
|
18268
18818
|
// src/resources/index.ts
|
|
18269
|
-
var SECURITY_RULES_CONTENT = `# AWS Security Scan Modules & Rules
|
|
18819
|
+
var SECURITY_RULES_CONTENT = `# AWS Security Scan Modules & Rules (17 modules)
|
|
18270
18820
|
|
|
18271
18821
|
## 1. Service Detection (service_detection)
|
|
18272
18822
|
Detects which AWS security services are enabled and assesses overall security maturity.
|
|
@@ -18335,6 +18885,31 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
|
|
|
18335
18885
|
|
|
18336
18886
|
## 14. Disaster Recovery (disaster_recovery)
|
|
18337
18887
|
Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
|
|
18888
|
+
|
|
18889
|
+
## 15. Config Rules Findings (config_rules_findings)
|
|
18890
|
+
Pulls non-compliant AWS Config Rule evaluation results.
|
|
18891
|
+
- Lists all Config Rules and their compliance status.
|
|
18892
|
+
- For NON_COMPLIANT rules, retrieves specific non-compliant resources.
|
|
18893
|
+
- Security-related rules (encryption, IAM, public access, etc.) mapped to HIGH severity (7.5).
|
|
18894
|
+
- Other non-compliant rules mapped to MEDIUM severity (5.5).
|
|
18895
|
+
- Gracefully handles regions where AWS Config is not enabled.
|
|
18896
|
+
|
|
18897
|
+
## 16. IAM Access Analyzer Findings (access_analyzer_findings)
|
|
18898
|
+
Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account.
|
|
18899
|
+
- Lists active analyzers (ACCOUNT or ORGANIZATION type).
|
|
18900
|
+
- Retrieves ACTIVE findings showing external access to resources.
|
|
18901
|
+
- Covers S3 buckets, IAM roles, SQS queues, Lambda functions, KMS keys, and more.
|
|
18902
|
+
- Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
|
|
18903
|
+
- Returns warning if no analyzer is configured.
|
|
18904
|
+
|
|
18905
|
+
## 17. SSM Patch Compliance (patch_compliance_findings)
|
|
18906
|
+
Checks patch compliance status for SSM-managed instances.
|
|
18907
|
+
- Lists all managed instances via SSM.
|
|
18908
|
+
- Retrieves patch compliance state for each instance.
|
|
18909
|
+
- Missing security patches or failed patches \u2192 HIGH (7.5).
|
|
18910
|
+
- Missing non-security patches \u2192 MEDIUM (5.5).
|
|
18911
|
+
- Instances without patch data flagged as LOW (3.0) for visibility.
|
|
18912
|
+
- Includes platform info, missing/failed counts, and last scan time.
|
|
18338
18913
|
`;
|
|
18339
18914
|
var RISK_SCORING_CONTENT = `# Risk Scoring Model
|
|
18340
18915
|
|
|
@@ -18396,7 +18971,10 @@ var MODULE_DESCRIPTIONS = {
|
|
|
18396
18971
|
security_hub_findings: "Aggregates active findings from AWS Security Hub \u2014 replaces individual config scanners with centralized compliance checks.",
|
|
18397
18972
|
guardduty_findings: "Aggregates threat detection findings from Amazon GuardDuty \u2014 account compromise, instance compromise, and reconnaissance.",
|
|
18398
18973
|
inspector_findings: "Aggregates vulnerability findings from Amazon Inspector \u2014 CVEs in EC2, Lambda, and container images.",
|
|
18399
|
-
trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan."
|
|
18974
|
+
trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
|
|
18975
|
+
config_rules_findings: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
|
|
18976
|
+
access_analyzer_findings: "Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account (external principals, public access).",
|
|
18977
|
+
patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches."
|
|
18400
18978
|
};
|
|
18401
18979
|
function summarizeResult(result) {
|
|
18402
18980
|
const { summary } = result;
|
|
@@ -18445,7 +19023,10 @@ function createServer(defaultRegion) {
|
|
|
18445
19023
|
new SecurityHubFindingsScanner(),
|
|
18446
19024
|
new GuardDutyFindingsScanner(),
|
|
18447
19025
|
new InspectorFindingsScanner(),
|
|
18448
|
-
new TrustedAdvisorFindingsScanner()
|
|
19026
|
+
new TrustedAdvisorFindingsScanner(),
|
|
19027
|
+
new ConfigRulesFindingsScanner(),
|
|
19028
|
+
new AccessAnalyzerFindingsScanner(),
|
|
19029
|
+
new PatchComplianceFindingsScanner()
|
|
18449
19030
|
];
|
|
18450
19031
|
const scannerMap = /* @__PURE__ */ new Map();
|
|
18451
19032
|
for (const s of allScanners) {
|
|
@@ -18498,7 +19079,10 @@ function createServer(defaultRegion) {
|
|
|
18498
19079
|
{ toolName: "scan_security_hub_findings", moduleName: "security_hub_findings", label: "Security Hub Findings" },
|
|
18499
19080
|
{ toolName: "scan_guardduty_findings", moduleName: "guardduty_findings", label: "GuardDuty Findings" },
|
|
18500
19081
|
{ toolName: "scan_inspector_findings", moduleName: "inspector_findings", label: "Inspector Findings" },
|
|
18501
|
-
{ toolName: "scan_trusted_advisor_findings", moduleName: "trusted_advisor_findings", label: "Trusted Advisor Findings" }
|
|
19082
|
+
{ toolName: "scan_trusted_advisor_findings", moduleName: "trusted_advisor_findings", label: "Trusted Advisor Findings" },
|
|
19083
|
+
{ toolName: "scan_config_rules_findings", moduleName: "config_rules_findings", label: "Config Rules Findings" },
|
|
19084
|
+
{ toolName: "scan_access_analyzer_findings", moduleName: "access_analyzer_findings", label: "Access Analyzer Findings" },
|
|
19085
|
+
{ toolName: "scan_patch_compliance_findings", moduleName: "patch_compliance_findings", label: "Patch Compliance Findings" }
|
|
18502
19086
|
];
|
|
18503
19087
|
for (const { toolName, moduleName, label } of individualScanners) {
|
|
18504
19088
|
server.tool(
|
|
@@ -18930,7 +19514,7 @@ Deploy this as a StackSet from your Management Account to all member accounts.`
|
|
|
18930
19514
|
server.resource(
|
|
18931
19515
|
"security-rules",
|
|
18932
19516
|
"security://rules",
|
|
18933
|
-
{ description: "Describes all
|
|
19517
|
+
{ description: "Describes all 17 scan modules and their check rules", mimeType: "text/markdown" },
|
|
18934
19518
|
async () => ({
|
|
18935
19519
|
contents: [{ uri: "security://rules", text: SECURITY_RULES_CONTENT, mimeType: "text/markdown" }]
|
|
18936
19520
|
})
|