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/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}) &mdash; click to expand</h4></summary>
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"${openAttr}>
18305
+ return `<details class="module-fold">
17806
18306
  <summary>
17807
18307
  <h3>&#128274; ${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 sorted = [...allFindings].sort((a, b) => b.riskScore - a.riskScore);
17837
- const items = sorted.map((f) => {
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
- return `<li><span class="priority-${esc2(pc)}">[${esc2(f.priority)}]</span> ${esc2(f.title)}: ${esc2(rem)}</li>`;
17841
- }).join("\n");
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 ? ` (&times; ${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&hellip;</summary>
18364
+ ${remaining.map(renderRec).join("\n")}
18365
+ </details>` : "";
17842
18366
  recsHtml = `
17843
- <section class="recommendations">
17844
- <h2>Recommendations (Priority Order)</h2>
17845
- <ol>${items}</ol>
17846
- </section>`;
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" ? "&#10004;" : r.status === "fail" ? "&#10008;" : "&#9888;";
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
- return `<h3>${esc2(checkId)} ${esc2(checkResults[0].check.name)}</h3>
17989
- ${items}`;
18515
+ const statusBadges = [
18516
+ grpPass > 0 ? `<span class="category-stat-pass">&#10003; ${grpPass}</span>` : "",
18517
+ grpFail > 0 ? `<span class="category-stat-fail">&#10007; ${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">&#10003; ${catPass}</span>` : "",
17993
18526
  catFail > 0 ? `<span class="category-stat-fail">&#10007; ${catFail}</span>` : "",
17994
18527
  catUnknown > 0 ? `<span class="category-stat-unknown">? ${catUnknown}</span>` : ""
17995
18528
  ].filter(Boolean).join("");
17996
- return `<details class="category-fold"${openAttr}>
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 allFailedFindings = /* @__PURE__ */ new Map();
18540
+ const mlpsRecMap = /* @__PURE__ */ new Map();
18008
18541
  for (const r of failedResults) {
18009
18542
  for (const f of r.relatedFindings) {
18010
- const key = `${f.resourceId}:${f.title}`;
18011
- if (!allFailedFindings.has(key)) {
18012
- allFailedFindings.set(key, f);
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 sorted = [...allFailedFindings.values()].sort(
18017
- (a, b) => b.riskScore - a.riskScore
18018
- );
18019
- const items = sorted.map((f) => {
18020
- const p = f.riskScore >= 9 ? "P0" : f.riskScore >= 7 ? "P1" : f.riskScore >= 4 ? "P2" : "P3";
18021
- const rem = f.remediationSteps[0] ?? "Review and remediate.";
18022
- return `<li><span class="priority-${p.toLowerCase()}">[${p}]</span> ${esc2(f.title)} &mdash; ${esc2(rem)}</li>`;
18023
- }).join("\n");
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 ? ` (&times; ${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&hellip;</summary>
18570
+ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
18571
+ </details>` : "";
18024
18572
  remediationHtml = `
18025
- <section class="recommendations">
18026
- <h2>\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09</h2>
18027
- <ol>${items}</ol>
18028
- </section>`;
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 14 scan modules and their check rules", mimeType: "text/markdown" },
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
  })