aws-security-mcp 0.4.0 → 0.4.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.
@@ -17226,6 +17226,504 @@ var TrustedAdvisorFindingsScanner = class {
17226
17226
  }
17227
17227
  };
17228
17228
 
17229
+ // src/scanners/config-rules-findings.ts
17230
+ import {
17231
+ ConfigServiceClient as ConfigServiceClient2,
17232
+ DescribeComplianceByConfigRuleCommand,
17233
+ GetComplianceDetailsByConfigRuleCommand
17234
+ } from "@aws-sdk/client-config-service";
17235
+ var SECURITY_RULE_PATTERNS = [
17236
+ "securitygroup",
17237
+ "security-group",
17238
+ "encryption",
17239
+ "encrypted",
17240
+ "public",
17241
+ "unrestricted",
17242
+ "mfa",
17243
+ "password",
17244
+ "access-key",
17245
+ "root",
17246
+ "admin",
17247
+ "logging",
17248
+ "cloudtrail",
17249
+ "iam",
17250
+ "kms",
17251
+ "ssl",
17252
+ "tls",
17253
+ "vpc-flow",
17254
+ "guardduty",
17255
+ "securityhub"
17256
+ ];
17257
+ function ruleIsSecurityRelated(ruleName) {
17258
+ const lower = ruleName.toLowerCase();
17259
+ return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
17260
+ }
17261
+ var ConfigRulesFindingsScanner = class {
17262
+ moduleName = "config_rules_findings";
17263
+ async scan(ctx) {
17264
+ const { region, partition, accountId } = ctx;
17265
+ const startMs = Date.now();
17266
+ const findings = [];
17267
+ const warnings = [];
17268
+ let resourcesScanned = 0;
17269
+ try {
17270
+ const client = createClient(ConfigServiceClient2, region, ctx.credentials);
17271
+ let nextToken;
17272
+ const nonCompliantRules = [];
17273
+ do {
17274
+ const resp = await client.send(
17275
+ new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
17276
+ );
17277
+ for (const rule of resp.ComplianceByConfigRules ?? []) {
17278
+ resourcesScanned++;
17279
+ if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
17280
+ nonCompliantRules.push(rule);
17281
+ }
17282
+ }
17283
+ nextToken = resp.NextToken;
17284
+ } while (nextToken);
17285
+ if (resourcesScanned === 0) {
17286
+ warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
17287
+ return {
17288
+ module: this.moduleName,
17289
+ status: "success",
17290
+ warnings,
17291
+ resourcesScanned: 0,
17292
+ findingsCount: 0,
17293
+ scanTimeMs: Date.now() - startMs,
17294
+ findings: []
17295
+ };
17296
+ }
17297
+ for (const rule of nonCompliantRules) {
17298
+ const ruleName = rule.ConfigRuleName ?? "unknown";
17299
+ try {
17300
+ let detailToken;
17301
+ do {
17302
+ const detailResp = await client.send(
17303
+ new GetComplianceDetailsByConfigRuleCommand({
17304
+ ConfigRuleName: ruleName,
17305
+ ComplianceTypes: ["NON_COMPLIANT"],
17306
+ NextToken: detailToken
17307
+ })
17308
+ );
17309
+ for (const evalResult of detailResp.EvaluationResults ?? []) {
17310
+ const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
17311
+ const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
17312
+ const resourceId = qualifier?.ResourceId ?? "unknown";
17313
+ const annotation = evalResult.Annotation;
17314
+ const isSecurityRule = ruleIsSecurityRelated(ruleName);
17315
+ const riskScore = isSecurityRule ? 7.5 : 5.5;
17316
+ const severity = severityFromScore(riskScore);
17317
+ const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
17318
+ if (annotation) descParts.push(`Annotation: ${annotation}`);
17319
+ findings.push({
17320
+ severity,
17321
+ title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
17322
+ resourceType,
17323
+ resourceId,
17324
+ resourceArn: resourceId,
17325
+ region,
17326
+ description: descParts.join(". "),
17327
+ impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
17328
+ riskScore,
17329
+ remediationSteps: [
17330
+ `Review the Config Rule "${ruleName}" in the AWS Config console.`,
17331
+ `Check resource ${resourceId} for compliance violations.`,
17332
+ "Follow the rule's remediation guidance to bring the resource into compliance."
17333
+ ],
17334
+ priority: priorityFromSeverity(severity),
17335
+ module: this.moduleName,
17336
+ accountId
17337
+ });
17338
+ }
17339
+ detailToken = detailResp.NextToken;
17340
+ } while (detailToken);
17341
+ } catch (detailErr) {
17342
+ const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
17343
+ warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
17344
+ }
17345
+ }
17346
+ return {
17347
+ module: this.moduleName,
17348
+ status: "success",
17349
+ warnings: warnings.length > 0 ? warnings : void 0,
17350
+ resourcesScanned,
17351
+ findingsCount: findings.length,
17352
+ scanTimeMs: Date.now() - startMs,
17353
+ findings
17354
+ };
17355
+ } catch (err) {
17356
+ const msg = err instanceof Error ? err.message : String(err);
17357
+ if (msg.includes("NoSuchConfigurationRecorder") || msg.includes("InsufficientDeliveryPolicy") || msg.includes("No Configuration Recorder")) {
17358
+ warnings.push("AWS Config is not enabled in this region.");
17359
+ return {
17360
+ module: this.moduleName,
17361
+ status: "success",
17362
+ warnings,
17363
+ resourcesScanned: 0,
17364
+ findingsCount: 0,
17365
+ scanTimeMs: Date.now() - startMs,
17366
+ findings: []
17367
+ };
17368
+ }
17369
+ return {
17370
+ module: this.moduleName,
17371
+ status: "error",
17372
+ error: `Config Rules scan failed: ${msg}`,
17373
+ warnings: warnings.length > 0 ? warnings : void 0,
17374
+ resourcesScanned,
17375
+ findingsCount: 0,
17376
+ scanTimeMs: Date.now() - startMs,
17377
+ findings: []
17378
+ };
17379
+ }
17380
+ }
17381
+ };
17382
+
17383
+ // src/scanners/access-analyzer-findings.ts
17384
+ import {
17385
+ AccessAnalyzerClient,
17386
+ ListAnalyzersCommand,
17387
+ ListFindingsV2Command
17388
+ } from "@aws-sdk/client-accessanalyzer";
17389
+ function findingTypeToScore(findingType) {
17390
+ const ft = findingType;
17391
+ switch (ft) {
17392
+ case "ExternalAccess":
17393
+ return 8;
17394
+ case "UnusedIAMRole":
17395
+ case "UnusedIAMUserAccessKey":
17396
+ case "UnusedIAMUserPassword":
17397
+ return 5.5;
17398
+ case "UnusedPermission":
17399
+ return 3;
17400
+ default:
17401
+ return 5.5;
17402
+ }
17403
+ }
17404
+ var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
17405
+ "UnusedIAMRole",
17406
+ "UnusedIAMUserAccessKey",
17407
+ "UnusedIAMUserPassword",
17408
+ "UnusedPermission"
17409
+ ]);
17410
+ function isSecurityRelevant(findingType) {
17411
+ const ft = findingType;
17412
+ return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
17413
+ }
17414
+ function isExternalAccess(findingType) {
17415
+ return findingType === "ExternalAccess";
17416
+ }
17417
+ var AccessAnalyzerFindingsScanner = class {
17418
+ moduleName = "access_analyzer_findings";
17419
+ async scan(ctx) {
17420
+ const { region, partition, accountId } = ctx;
17421
+ const startMs = Date.now();
17422
+ const findings = [];
17423
+ const warnings = [];
17424
+ let resourcesScanned = 0;
17425
+ try {
17426
+ const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
17427
+ let analyzerToken;
17428
+ const analyzers = [];
17429
+ do {
17430
+ const resp = await client.send(
17431
+ new ListAnalyzersCommand({ nextToken: analyzerToken })
17432
+ );
17433
+ for (const analyzer of resp.analyzers ?? []) {
17434
+ if (analyzer.status === "ACTIVE") {
17435
+ analyzers.push(analyzer);
17436
+ }
17437
+ }
17438
+ analyzerToken = resp.nextToken;
17439
+ } while (analyzerToken);
17440
+ if (analyzers.length === 0) {
17441
+ warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
17442
+ return {
17443
+ module: this.moduleName,
17444
+ status: "success",
17445
+ warnings,
17446
+ resourcesScanned: 0,
17447
+ findingsCount: 0,
17448
+ scanTimeMs: Date.now() - startMs,
17449
+ findings: []
17450
+ };
17451
+ }
17452
+ for (const analyzer of analyzers) {
17453
+ const analyzerArn = analyzer.arn ?? "unknown";
17454
+ let findingToken;
17455
+ do {
17456
+ const listResp = await client.send(
17457
+ new ListFindingsV2Command({
17458
+ analyzerArn,
17459
+ filter: {
17460
+ status: { eq: ["ACTIVE"] }
17461
+ },
17462
+ nextToken: findingToken
17463
+ })
17464
+ );
17465
+ for (const aaf of listResp.findings ?? []) {
17466
+ if (!isSecurityRelevant(aaf.findingType)) {
17467
+ continue;
17468
+ }
17469
+ resourcesScanned++;
17470
+ const score = findingTypeToScore(aaf.findingType);
17471
+ const severity = severityFromScore(score);
17472
+ const resourceArn = aaf.resource ?? "unknown";
17473
+ const resourceType = aaf.resourceType ?? "AWS::Unknown";
17474
+ const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
17475
+ const external = isExternalAccess(aaf.findingType);
17476
+ const descParts = [`Resource Type: ${resourceType}`];
17477
+ if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
17478
+ if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
17479
+ const title = buildFindingTitle(aaf);
17480
+ 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"}`;
17481
+ const remediationSteps = external ? [
17482
+ "Review the finding in the IAM Access Analyzer console.",
17483
+ `Check resource ${resourceId} for unintended external access.`,
17484
+ "Remove or restrict the resource policy to eliminate external access."
17485
+ ] : [
17486
+ "Review the finding in the IAM Access Analyzer console.",
17487
+ `Check resource ${resourceId} for unused access permissions.`,
17488
+ "Remove unused permissions, roles, or credentials to follow least-privilege."
17489
+ ];
17490
+ findings.push({
17491
+ severity,
17492
+ title,
17493
+ resourceType: mapResourceType(resourceType),
17494
+ resourceId,
17495
+ resourceArn,
17496
+ region,
17497
+ description: descParts.join(". "),
17498
+ impact,
17499
+ riskScore: score,
17500
+ remediationSteps,
17501
+ priority: priorityFromSeverity(severity),
17502
+ module: this.moduleName,
17503
+ accountId: aaf.resourceOwnerAccount ?? accountId
17504
+ });
17505
+ }
17506
+ findingToken = listResp.nextToken;
17507
+ } while (findingToken);
17508
+ }
17509
+ return {
17510
+ module: this.moduleName,
17511
+ status: "success",
17512
+ warnings: warnings.length > 0 ? warnings : void 0,
17513
+ resourcesScanned,
17514
+ findingsCount: findings.length,
17515
+ scanTimeMs: Date.now() - startMs,
17516
+ findings
17517
+ };
17518
+ } catch (err) {
17519
+ const msg = err instanceof Error ? err.message : String(err);
17520
+ return {
17521
+ module: this.moduleName,
17522
+ status: "error",
17523
+ error: `Access Analyzer scan failed: ${msg}`,
17524
+ warnings: warnings.length > 0 ? warnings : void 0,
17525
+ resourcesScanned,
17526
+ findingsCount: 0,
17527
+ scanTimeMs: Date.now() - startMs,
17528
+ findings: []
17529
+ };
17530
+ }
17531
+ }
17532
+ };
17533
+ function buildFindingTitle(finding) {
17534
+ const resourceType = finding.resourceType ?? "Resource";
17535
+ const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
17536
+ const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
17537
+ return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
17538
+ }
17539
+ function mapResourceType(aaType) {
17540
+ const mapping = {
17541
+ "AWS::S3::Bucket": "AWS::S3::Bucket",
17542
+ "AWS::IAM::Role": "AWS::IAM::Role",
17543
+ "AWS::SQS::Queue": "AWS::SQS::Queue",
17544
+ "AWS::Lambda::Function": "AWS::Lambda::Function",
17545
+ "AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
17546
+ "AWS::KMS::Key": "AWS::KMS::Key",
17547
+ "AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
17548
+ "AWS::SNS::Topic": "AWS::SNS::Topic",
17549
+ "AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
17550
+ "AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
17551
+ "AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
17552
+ "AWS::ECR::Repository": "AWS::ECR::Repository"
17553
+ };
17554
+ return mapping[aaType] ?? aaType;
17555
+ }
17556
+
17557
+ // src/scanners/patch-compliance-findings.ts
17558
+ import {
17559
+ SSMClient,
17560
+ DescribeInstanceInformationCommand,
17561
+ DescribeInstancePatchStatesCommand
17562
+ } from "@aws-sdk/client-ssm";
17563
+ var PatchComplianceFindingsScanner = class {
17564
+ moduleName = "patch_compliance_findings";
17565
+ async scan(ctx) {
17566
+ const { region, partition, accountId } = ctx;
17567
+ const startMs = Date.now();
17568
+ const findings = [];
17569
+ const warnings = [];
17570
+ let resourcesScanned = 0;
17571
+ try {
17572
+ const client = createClient(SSMClient, region, ctx.credentials);
17573
+ let nextToken;
17574
+ const instances = [];
17575
+ do {
17576
+ const resp = await client.send(
17577
+ new DescribeInstanceInformationCommand({
17578
+ MaxResults: 50,
17579
+ NextToken: nextToken
17580
+ })
17581
+ );
17582
+ instances.push(...resp.InstanceInformationList ?? []);
17583
+ nextToken = resp.NextToken;
17584
+ } while (nextToken);
17585
+ if (instances.length === 0) {
17586
+ warnings.push("No SSM-managed instances found in this region.");
17587
+ return {
17588
+ module: this.moduleName,
17589
+ status: "success",
17590
+ warnings,
17591
+ resourcesScanned: 0,
17592
+ findingsCount: 0,
17593
+ scanTimeMs: Date.now() - startMs,
17594
+ findings: []
17595
+ };
17596
+ }
17597
+ resourcesScanned = instances.length;
17598
+ const instanceIds = instances.map((i) => i.InstanceId).filter(Boolean);
17599
+ const patchStateMap = /* @__PURE__ */ new Map();
17600
+ for (let i = 0; i < instanceIds.length; i += 50) {
17601
+ const batch = instanceIds.slice(i, i + 50);
17602
+ let patchToken;
17603
+ do {
17604
+ const patchResp = await client.send(
17605
+ new DescribeInstancePatchStatesCommand({
17606
+ InstanceIds: batch,
17607
+ NextToken: patchToken
17608
+ })
17609
+ );
17610
+ for (const ps of patchResp.InstancePatchStates ?? []) {
17611
+ if (ps.InstanceId) {
17612
+ patchStateMap.set(ps.InstanceId, ps);
17613
+ }
17614
+ }
17615
+ patchToken = patchResp.NextToken;
17616
+ } while (patchToken);
17617
+ }
17618
+ for (const instance of instances) {
17619
+ const instanceId = instance.InstanceId ?? "unknown";
17620
+ const platform = instance.PlatformName ?? instance.PlatformType ?? "unknown";
17621
+ const instanceArn = `arn:${partition}:ec2:${region}:${accountId}:instance/${instanceId}`;
17622
+ const patchState = patchStateMap.get(instanceId);
17623
+ if (!patchState) {
17624
+ const riskScore2 = 3;
17625
+ const severity2 = severityFromScore(riskScore2);
17626
+ findings.push({
17627
+ severity: severity2,
17628
+ title: `Instance ${instanceId} has no patch compliance data`,
17629
+ resourceType: "AWS::EC2::Instance",
17630
+ resourceId: instanceId,
17631
+ resourceArn: instanceArn,
17632
+ region,
17633
+ description: `Instance ${instanceId} (${platform}) is managed by SSM but has no patch compliance data. Patch Manager may not be configured for this instance.`,
17634
+ impact: "Patch compliance status is unknown \u2014 vulnerabilities may exist.",
17635
+ riskScore: riskScore2,
17636
+ remediationSteps: [
17637
+ "Configure AWS Systems Manager Patch Manager for this instance.",
17638
+ "Create a patch baseline and maintenance window.",
17639
+ "Run a patch scan to establish compliance status."
17640
+ ],
17641
+ priority: priorityFromSeverity(severity2),
17642
+ module: this.moduleName,
17643
+ accountId
17644
+ });
17645
+ continue;
17646
+ }
17647
+ const missingCount = patchState.MissingCount ?? 0;
17648
+ const failedCount = patchState.FailedCount ?? 0;
17649
+ const criticalNonCompliantCount = patchState.CriticalNonCompliantCount ?? 0;
17650
+ const securityNonCompliantCount = patchState.SecurityNonCompliantCount ?? 0;
17651
+ const otherNonCompliantCount = patchState.OtherNonCompliantCount ?? 0;
17652
+ const lastScanTime = patchState.OperationEndTime?.toISOString() ?? "unknown";
17653
+ if (missingCount === 0 && failedCount === 0 && criticalNonCompliantCount === 0 && securityNonCompliantCount === 0 && otherNonCompliantCount === 0) {
17654
+ continue;
17655
+ }
17656
+ let riskScore;
17657
+ if (criticalNonCompliantCount > 0 || securityNonCompliantCount > 0 || failedCount > 0) {
17658
+ riskScore = 7.5;
17659
+ } else if (otherNonCompliantCount > 0) {
17660
+ riskScore = 5.5;
17661
+ } else {
17662
+ riskScore = 5.5;
17663
+ }
17664
+ const severity = severityFromScore(riskScore);
17665
+ const titleParts = [];
17666
+ if (missingCount > 0) titleParts.push(`${missingCount} missing`);
17667
+ if (failedCount > 0) titleParts.push(`${failedCount} failed`);
17668
+ if (criticalNonCompliantCount > 0) titleParts.push(`${criticalNonCompliantCount} critical non-compliant`);
17669
+ if (securityNonCompliantCount > 0) titleParts.push(`${securityNonCompliantCount} security non-compliant`);
17670
+ if (otherNonCompliantCount > 0) titleParts.push(`${otherNonCompliantCount} other non-compliant`);
17671
+ const descParts = [
17672
+ `Instance: ${instanceId}`,
17673
+ `Platform: ${platform}`,
17674
+ `Missing patches: ${missingCount}`,
17675
+ `Failed patches: ${failedCount}`,
17676
+ `Critical non-compliant: ${criticalNonCompliantCount}`,
17677
+ `Security non-compliant: ${securityNonCompliantCount}`,
17678
+ `Other non-compliant: ${otherNonCompliantCount}`,
17679
+ `Last scan: ${lastScanTime}`
17680
+ ];
17681
+ findings.push({
17682
+ severity,
17683
+ title: `Instance ${instanceId} has ${titleParts.join(", ")} patches`,
17684
+ resourceType: "AWS::EC2::Instance",
17685
+ resourceId: instanceId,
17686
+ resourceArn: instanceArn,
17687
+ region,
17688
+ description: descParts.join(". "),
17689
+ impact: `Instance has ${missingCount} missing and ${failedCount} failed patches \u2014 potential security vulnerabilities.`,
17690
+ riskScore,
17691
+ remediationSteps: [
17692
+ `Review patch compliance for instance ${instanceId} in the Systems Manager console.`,
17693
+ "Apply missing patches using a maintenance window or manual patching.",
17694
+ "Investigate and resolve any failed patch installations.",
17695
+ "Consider enabling automatic patching through Patch Manager."
17696
+ ],
17697
+ priority: priorityFromSeverity(severity),
17698
+ module: this.moduleName,
17699
+ accountId
17700
+ });
17701
+ }
17702
+ return {
17703
+ module: this.moduleName,
17704
+ status: "success",
17705
+ warnings: warnings.length > 0 ? warnings : void 0,
17706
+ resourcesScanned,
17707
+ findingsCount: findings.length,
17708
+ scanTimeMs: Date.now() - startMs,
17709
+ findings
17710
+ };
17711
+ } catch (err) {
17712
+ const msg = err instanceof Error ? err.message : String(err);
17713
+ return {
17714
+ module: this.moduleName,
17715
+ status: "error",
17716
+ error: `Patch compliance scan failed: ${msg}`,
17717
+ warnings: warnings.length > 0 ? warnings : void 0,
17718
+ resourcesScanned,
17719
+ findingsCount: 0,
17720
+ scanTimeMs: Date.now() - startMs,
17721
+ findings: []
17722
+ };
17723
+ }
17724
+ }
17725
+ };
17726
+
17229
17727
  // src/tools/report-tool.ts
17230
17728
  var SEVERITY_ICON = {
17231
17729
  CRITICAL: "\u{1F534}",
@@ -17737,10 +18235,21 @@ function sharedCss() {
17737
18235
  .finding-card-body p{color:#cbd5e1;font-size:13px;margin-bottom:4px}
17738
18236
  .finding-card-body ol{padding-left:20px}
17739
18237
  .finding-card-body li{color:#cbd5e1;font-size:13px;margin-bottom:2px}
18238
+ .rec-fold{background:#1e293b;border:1px solid #334155;border-radius:8px;margin-bottom:16px;overflow:hidden}
18239
+ .rec-fold>summary{cursor:pointer;padding:16px 20px;display:flex;align-items:center;gap:12px;list-style:none;user-select:none}
18240
+ .rec-fold>summary::-webkit-details-marker{display:none}
18241
+ .rec-fold>summary::marker{content:""}
18242
+ .rec-fold>summary::after{content:"\\25B6";font-size:12px;color:#64748b;flex-shrink:0;transition:transform 0.2s;margin-left:auto}
18243
+ .rec-fold[open]>summary::after{transform:rotate(90deg)}
18244
+ .rec-fold[open]>summary{border-bottom:1px solid #334155}
18245
+ .rec-body{padding:12px 20px 16px}
18246
+ .rec-body ol{padding-left:24px}
18247
+ .rec-body li{margin-bottom:8px;color:#cbd5e1;font-size:13px}
18248
+ .rec-body .badge{margin-right:6px;vertical-align:middle}
17740
18249
  @media print{
17741
18250
  body{background:#fff;color:#1e293b;-webkit-print-color-adjust:exact;print-color-adjust:exact}
17742
18251
  .container{max-width:100%;padding:20px}
17743
- .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}
18252
+ .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}
17744
18253
  .badge{border:1px solid}
17745
18254
  header{border-bottom-color:#e2e8f0}
17746
18255
  h2{border-bottom-color:#e2e8f0}
@@ -17758,10 +18267,10 @@ function sharedCss() {
17758
18267
  .finding-title-text{color:#1e293b}
17759
18268
  .finding-resource{color:#64748b}
17760
18269
  .check-findings li{color:#64748b}
17761
- .finding-fold,.top5-card,.category-fold,.module-fold,.finding-card{break-inside:avoid}
18270
+ .finding-fold,.top5-card,.category-fold,.module-fold,.finding-card,.rec-fold{break-inside:avoid}
17762
18271
  .check-item{break-inside:avoid}
17763
18272
  svg text{fill:#1e293b !important}
17764
- .finding-fold[open]>summary,.category-fold[open]>summary,.module-fold[open]>summary{border-bottom-color:#e2e8f0}
18273
+ .finding-fold[open]>summary,.category-fold[open]>summary,.module-fold[open]>summary,.rec-fold[open]>summary{border-bottom-color:#e2e8f0}
17765
18274
  details{display:block}
17766
18275
  details>summary{display:block}
17767
18276
  details>:not(summary){display:block !important}
@@ -18000,8 +18509,6 @@ ${rest}
18000
18509
  return b[1].length - a[1].length;
18001
18510
  });
18002
18511
  findingsHtml = moduleEntries.map(([modName, modFindings]) => {
18003
- const hasCritHigh = modFindings.some((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
18004
- const openAttr = hasCritHigh ? " open" : "";
18005
18512
  const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
18006
18513
  for (const f of modFindings) sevCounts[f.severity]++;
18007
18514
  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(" ");
@@ -18011,19 +18518,12 @@ ${rest}
18011
18518
  findings.sort((a, b) => b.riskScore - a.riskScore);
18012
18519
  const emoji3 = SEV_EMOJI[sev] ?? "";
18013
18520
  const label = sev.charAt(0) + sev.slice(1).toLowerCase();
18014
- const isCritHigh = sev === "CRITICAL" || sev === "HIGH";
18015
- if (isCritHigh) {
18016
- return `<div class="severity-group">
18017
- <h4>${emoji3} ${label} (${findings.length})</h4>
18018
- ${renderCards(findings)}
18019
- </div>`;
18020
- }
18021
18521
  return `<details class="severity-group-fold">
18022
- <summary><h4>${emoji3} ${label} (${findings.length}) &mdash; click to expand</h4></summary>
18522
+ <summary><h4>${emoji3} ${label} (${findings.length})</h4></summary>
18023
18523
  ${renderCards(findings)}
18024
18524
  </details>`;
18025
18525
  }).filter(Boolean).join("\n");
18026
- return `<details class="module-fold"${openAttr}>
18526
+ return `<details class="module-fold">
18027
18527
  <summary>
18028
18528
  <h3>&#128274; ${esc2(modName)} (${modFindings.length})</h3>
18029
18529
  <span class="module-badges">${badges}</span>
@@ -18054,17 +18554,43 @@ ${rest}
18054
18554
  ).join("\n");
18055
18555
  let recsHtml = "";
18056
18556
  if (summary.totalFindings > 0) {
18057
- const sorted = [...allFindings].sort((a, b) => b.riskScore - a.riskScore);
18058
- const items = sorted.map((f) => {
18059
- const pc = f.priority.toLowerCase();
18557
+ const recMap = /* @__PURE__ */ new Map();
18558
+ for (const f of allFindings) {
18060
18559
  const rem = f.remediationSteps[0] ?? "Review and remediate.";
18061
- return `<li><span class="priority-${esc2(pc)}">[${esc2(f.priority)}]</span> ${esc2(f.title)}: ${esc2(rem)}</li>`;
18062
- }).join("\n");
18560
+ const existing = recMap.get(rem);
18561
+ if (existing) {
18562
+ existing.count++;
18563
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
18564
+ existing.severity = f.severity;
18565
+ }
18566
+ } else {
18567
+ recMap.set(rem, { text: rem, severity: f.severity, count: 1 });
18568
+ }
18569
+ }
18570
+ const uniqueRecs = [...recMap.values()].sort((a, b) => {
18571
+ const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
18572
+ if (sevDiff !== 0) return sevDiff;
18573
+ return b.count - a.count;
18574
+ });
18575
+ const renderRec = (r) => {
18576
+ const sev = r.severity.toLowerCase();
18577
+ const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
18578
+ return `<li><span class="badge badge-${esc2(sev)}">${esc2(r.severity)}</span> ${esc2(r.text)}${countLabel}</li>`;
18579
+ };
18580
+ const TOP_N = 10;
18581
+ const topItems = uniqueRecs.slice(0, TOP_N).map(renderRec).join("\n");
18582
+ const remaining = uniqueRecs.slice(TOP_N);
18583
+ const moreHtml = remaining.length > 0 ? `
18584
+ <details><summary>Show ${remaining.length} more&hellip;</summary>
18585
+ ${remaining.map(renderRec).join("\n")}
18586
+ </details>` : "";
18063
18587
  recsHtml = `
18064
- <section class="recommendations">
18065
- <h2>Recommendations (Priority Order)</h2>
18066
- <ol>${items}</ol>
18067
- </section>`;
18588
+ <details class="rec-fold">
18589
+ <summary><h2 style="margin:0;border:0;display:inline">Recommendations (${uniqueRecs.length} unique)</h2></summary>
18590
+ <div class="rec-body">
18591
+ <ol>${topItems}${moreHtml}</ol>
18592
+ </div>
18593
+ </details>`;
18068
18594
  }
18069
18595
  return `<!DOCTYPE html>
18070
18596
  <html lang="en">
@@ -18179,8 +18705,6 @@ function generateMlps3HtmlReport(scanResults, history) {
18179
18705
  const catUnknown = categoryResults.filter(
18180
18706
  (r) => r.status === "unknown"
18181
18707
  ).length;
18182
- const hasFailure = catFail > 0;
18183
- const openAttr = hasFailure ? " open" : "";
18184
18708
  const byId = /* @__PURE__ */ new Map();
18185
18709
  for (const r of categoryResults) {
18186
18710
  const existing = byId.get(r.check.id) ?? [];
@@ -18188,6 +18712,9 @@ function generateMlps3HtmlReport(scanResults, history) {
18188
18712
  byId.set(r.check.id, existing);
18189
18713
  }
18190
18714
  const groups = [...byId.entries()].map(([checkId, checkResults]) => {
18715
+ const grpPass = checkResults.filter((r) => r.status === "pass").length;
18716
+ const grpFail = checkResults.filter((r) => r.status === "fail").length;
18717
+ const grpUnknown = checkResults.filter((r) => r.status === "unknown").length;
18191
18718
  const items = checkResults.map((r) => {
18192
18719
  const icon = r.status === "pass" ? "&#10004;" : r.status === "fail" ? "&#10008;" : "&#9888;";
18193
18720
  const cls = `check-${r.status}`;
@@ -18206,15 +18733,21 @@ function generateMlps3HtmlReport(scanResults, history) {
18206
18733
  }
18207
18734
  return `<div class="check-item ${cls}"><span class="check-icon">${icon}</span><span class="check-name">${esc2(r.check.name)}${label}</span></div>${findingsHtml}`;
18208
18735
  }).join("\n");
18209
- return `<h3>${esc2(checkId)} ${esc2(checkResults[0].check.name)}</h3>
18210
- ${items}`;
18736
+ const statusBadges = [
18737
+ grpPass > 0 ? `<span class="category-stat-pass">&#10003; ${grpPass}</span>` : "",
18738
+ grpFail > 0 ? `<span class="category-stat-fail">&#10007; ${grpFail}</span>` : "",
18739
+ grpUnknown > 0 ? `<span class="category-stat-unknown">? ${grpUnknown}</span>` : ""
18740
+ ].filter(Boolean).join(" ");
18741
+ return `<details class="severity-group-fold"><summary><h4>${esc2(checkId)} ${esc2(checkResults[0].check.name)} <span class="category-stats">${statusBadges}</span></h4></summary>
18742
+ ${items}
18743
+ </details>`;
18211
18744
  }).join("\n");
18212
18745
  const statsHtml = [
18213
18746
  catPass > 0 ? `<span class="category-stat-pass">&#10003; ${catPass}</span>` : "",
18214
18747
  catFail > 0 ? `<span class="category-stat-fail">&#10007; ${catFail}</span>` : "",
18215
18748
  catUnknown > 0 ? `<span class="category-stat-unknown">? ${catUnknown}</span>` : ""
18216
18749
  ].filter(Boolean).join("");
18217
- return `<details class="category-fold"${openAttr}>
18750
+ return `<details class="category-fold">
18218
18751
  <summary>
18219
18752
  <span class="category-title">${esc2(sectionTitle)}</span>
18220
18753
  <span class="category-stats">${statsHtml}</span>
@@ -18225,28 +18758,45 @@ ${items}`;
18225
18758
  const failedResults = results.filter((r) => r.status === "fail");
18226
18759
  let remediationHtml = "";
18227
18760
  if (failedResults.length > 0) {
18228
- const allFailedFindings = /* @__PURE__ */ new Map();
18761
+ const mlpsRecMap = /* @__PURE__ */ new Map();
18229
18762
  for (const r of failedResults) {
18230
18763
  for (const f of r.relatedFindings) {
18231
- const key = `${f.resourceId}:${f.title}`;
18232
- if (!allFailedFindings.has(key)) {
18233
- allFailedFindings.set(key, f);
18764
+ const rem = f.remediationSteps[0] ?? "Review and remediate.";
18765
+ const existing = mlpsRecMap.get(rem);
18766
+ if (existing) {
18767
+ existing.count++;
18768
+ if (SEVERITY_ORDER2.indexOf(f.severity) < SEVERITY_ORDER2.indexOf(existing.severity)) {
18769
+ existing.severity = f.severity;
18770
+ }
18771
+ } else {
18772
+ mlpsRecMap.set(rem, { text: rem, severity: f.severity, count: 1 });
18234
18773
  }
18235
18774
  }
18236
18775
  }
18237
- const sorted = [...allFailedFindings.values()].sort(
18238
- (a, b) => b.riskScore - a.riskScore
18239
- );
18240
- const items = sorted.map((f) => {
18241
- const p = f.riskScore >= 9 ? "P0" : f.riskScore >= 7 ? "P1" : f.riskScore >= 4 ? "P2" : "P3";
18242
- const rem = f.remediationSteps[0] ?? "Review and remediate.";
18243
- return `<li><span class="priority-${p.toLowerCase()}">[${p}]</span> ${esc2(f.title)} &mdash; ${esc2(rem)}</li>`;
18244
- }).join("\n");
18776
+ const mlpsUniqueRecs = [...mlpsRecMap.values()].sort((a, b) => {
18777
+ const sevDiff = SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity);
18778
+ if (sevDiff !== 0) return sevDiff;
18779
+ return b.count - a.count;
18780
+ });
18781
+ const renderMlpsRec = (r) => {
18782
+ const sev = r.severity.toLowerCase();
18783
+ const countLabel = r.count > 1 ? ` (&times; ${r.count})` : "";
18784
+ return `<li><span class="badge badge-${esc2(sev)}">${esc2(r.severity)}</span> ${esc2(r.text)}${countLabel}</li>`;
18785
+ };
18786
+ const MLPS_TOP_N = 10;
18787
+ const mlpsTopItems = mlpsUniqueRecs.slice(0, MLPS_TOP_N).map(renderMlpsRec).join("\n");
18788
+ const mlpsRemaining = mlpsUniqueRecs.slice(MLPS_TOP_N);
18789
+ const mlpsMoreHtml = mlpsRemaining.length > 0 ? `
18790
+ <details><summary>\u663E\u793A\u5176\u4F59 ${mlpsRemaining.length} \u9879&hellip;</summary>
18791
+ ${mlpsRemaining.map(renderMlpsRec).join("\n")}
18792
+ </details>` : "";
18245
18793
  remediationHtml = `
18246
- <section class="recommendations">
18247
- <h2>\u5EFA\u8BAE\u6574\u6539\u9879\uFF08\u6309\u4F18\u5148\u7EA7\uFF09</h2>
18248
- <ol>${items}</ol>
18249
- </section>`;
18794
+ <details class="rec-fold">
18795
+ <summary><h2 style="margin:0;border:0;display:inline">\u5EFA\u8BAE\u6574\u6539\u9879\uFF08${mlpsUniqueRecs.length} \u9879\u53BB\u91CD\uFF09</h2></summary>
18796
+ <div class="rec-body">
18797
+ <ol>${mlpsTopItems}${mlpsMoreHtml}</ol>
18798
+ </div>
18799
+ </details>`;
18250
18800
  }
18251
18801
  const passRateColor = percent >= 80 ? "#22c55e" : percent >= 50 ? "#eab308" : "#ef4444";
18252
18802
  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>` : "";
@@ -18405,13 +18955,13 @@ var SCAN_GROUPS = {
18405
18955
  mlps3_precheck: {
18406
18956
  name: "\u7B49\u4FDD\u4E09\u7EA7\u9884\u68C0",
18407
18957
  description: "GB/T 22239-2019 \u7B49\u4FDD\u4E09\u7EA7 AWS \u4E91\u79DF\u6237\u5C42\u914D\u7F6E\u68C0\u67E5",
18408
- 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"],
18958
+ 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"],
18409
18959
  reportType: "mlps3"
18410
18960
  },
18411
18961
  hw_defense: {
18412
18962
  name: "\u62A4\u7F51\u84DD\u961F\u52A0\u56FA",
18413
18963
  description: "\u62A4\u7F51\u524D\u5B89\u5168\u81EA\u67E5 \u2014 \u653B\u51FB\u9762+\u5F31\u70B9\u8BC4\u4F30",
18414
- modules: ["service_detection", "secret_exposure", "network_reachability", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "inspector_findings"],
18964
+ 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"],
18415
18965
  findingsFilter: {
18416
18966
  guardDutyTypes: ["Backdoor", "Trojan", "PenTest", "CryptoCurrency"],
18417
18967
  minSeverity: "MEDIUM"
@@ -18420,16 +18970,11 @@ var SCAN_GROUPS = {
18420
18970
  exposure: {
18421
18971
  name: "\u516C\u7F51\u66B4\u9732\u9762\u8BC4\u4F30",
18422
18972
  description: "\u8BC4\u4F30\u516C\u7F51\u53EF\u8FBE\u7684\u8D44\u6E90\u548C\u7AEF\u53E3",
18423
- modules: ["network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "security_hub_findings"],
18973
+ modules: ["network_reachability", "dns_dangling", "public_access_verify", "ssl_certificate", "security_hub_findings", "access_analyzer_findings"],
18424
18974
  findingsFilter: {
18425
18975
  securityHubCategories: ["network", "public", "exposure", "port"]
18426
18976
  }
18427
18977
  },
18428
- pre_launch: {
18429
- name: "\u751F\u4EA7\u4E0A\u7EBF\u524D\u68C0\u67E5",
18430
- description: "\u4E0A\u7EBF\u524D\u5168\u9762\u5B89\u5168\u8BC4\u4F30",
18431
- modules: ["ALL"]
18432
- },
18433
18978
  data_encryption: {
18434
18979
  name: "\u6570\u636E\u52A0\u5BC6\u5BA1\u8BA1",
18435
18980
  description: "\u5168\u9762\u68C0\u67E5\u5B58\u50A8\u548C\u4F20\u8F93\u52A0\u5BC6\u72B6\u6001",
@@ -18441,7 +18986,7 @@ var SCAN_GROUPS = {
18441
18986
  least_privilege: {
18442
18987
  name: "\u6700\u5C0F\u6743\u9650\u5BA1\u8BA1",
18443
18988
  description: "IAM \u6743\u9650\u6700\u5C0F\u5316\u8BC4\u4F30",
18444
- modules: ["iam_privilege_escalation", "security_hub_findings"],
18989
+ modules: ["iam_privilege_escalation", "security_hub_findings", "access_analyzer_findings"],
18445
18990
  findingsFilter: {
18446
18991
  securityHubCategories: ["IAM", "iam", "access", "privilege"]
18447
18992
  }
@@ -18469,25 +19014,20 @@ var SCAN_GROUPS = {
18469
19014
  description: "\u68C0\u67E5\u5FC5\u9700\u6807\u7B7E",
18470
19015
  modules: ["tag_compliance"]
18471
19016
  },
18472
- public_access_verify: {
18473
- name: "\u516C\u7F51\u53EF\u8FBE\u6027\u9A8C\u8BC1",
18474
- description: "\u9A8C\u8BC1\u6807\u8BB0\u4E3A\u516C\u5F00\u7684\u8D44\u6E90\u662F\u5426\u771F\u6B63\u53EF\u4ECE\u4E92\u8054\u7F51\u8BBF\u95EE",
18475
- modules: ["public_access_verify"]
18476
- },
18477
19017
  new_account_baseline: {
18478
19018
  name: "\u65B0\u8D26\u6237\u57FA\u7EBF\u68C0\u67E5",
18479
19019
  description: "\u65B0 AWS \u8D26\u6237\u5B89\u5168\u57FA\u7EBF",
18480
- modules: ["service_detection", "secret_exposure", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings"]
19020
+ modules: ["service_detection", "secret_exposure", "iam_privilege_escalation", "security_hub_findings", "guardduty_findings", "access_analyzer_findings"]
18481
19021
  },
18482
19022
  aggregation: {
18483
19023
  name: "\u5B89\u5168\u670D\u52A1\u805A\u5408",
18484
19024
  description: "\u4ECE Security Hub / GuardDuty / Inspector / Trusted Advisor \u805A\u5408\u6240\u6709\u5B89\u5168\u53D1\u73B0",
18485
- modules: ["security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings"]
19025
+ modules: ["security_hub_findings", "guardduty_findings", "inspector_findings", "trusted_advisor_findings", "config_rules_findings", "access_analyzer_findings", "patch_compliance_findings"]
18486
19026
  }
18487
19027
  };
18488
19028
 
18489
19029
  // src/resources/index.ts
18490
- var SECURITY_RULES_CONTENT = `# AWS Security Scan Modules & Rules
19030
+ var SECURITY_RULES_CONTENT = `# AWS Security Scan Modules & Rules (17 modules)
18491
19031
 
18492
19032
  ## 1. Service Detection (service_detection)
18493
19033
  Detects which AWS security services are enabled and assesses overall security maturity.
@@ -18556,6 +19096,31 @@ Finds unused/idle AWS resources (unattached EBS volumes, unused EIPs, stopped in
18556
19096
 
18557
19097
  ## 14. Disaster Recovery (disaster_recovery)
18558
19098
  Assesses disaster recovery readiness \u2014 RDS Multi-AZ & backups, EBS snapshot coverage, S3 versioning & cross-region replication.
19099
+
19100
+ ## 15. Config Rules Findings (config_rules_findings)
19101
+ Pulls non-compliant AWS Config Rule evaluation results.
19102
+ - Lists all Config Rules and their compliance status.
19103
+ - For NON_COMPLIANT rules, retrieves specific non-compliant resources.
19104
+ - Security-related rules (encryption, IAM, public access, etc.) mapped to HIGH severity (7.5).
19105
+ - Other non-compliant rules mapped to MEDIUM severity (5.5).
19106
+ - Gracefully handles regions where AWS Config is not enabled.
19107
+
19108
+ ## 16. IAM Access Analyzer Findings (access_analyzer_findings)
19109
+ Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account.
19110
+ - Lists active analyzers (ACCOUNT or ORGANIZATION type).
19111
+ - Retrieves ACTIVE findings showing external access to resources.
19112
+ - Covers S3 buckets, IAM roles, SQS queues, Lambda functions, KMS keys, and more.
19113
+ - Severity mapped: CRITICAL \u2192 9.5, HIGH \u2192 8.0, MEDIUM \u2192 5.5, LOW \u2192 3.0.
19114
+ - Returns warning if no analyzer is configured.
19115
+
19116
+ ## 17. SSM Patch Compliance (patch_compliance_findings)
19117
+ Checks patch compliance status for SSM-managed instances.
19118
+ - Lists all managed instances via SSM.
19119
+ - Retrieves patch compliance state for each instance.
19120
+ - Missing security patches or failed patches \u2192 HIGH (7.5).
19121
+ - Missing non-security patches \u2192 MEDIUM (5.5).
19122
+ - Instances without patch data flagged as LOW (3.0) for visibility.
19123
+ - Includes platform info, missing/failed counts, and last scan time.
18559
19124
  `;
18560
19125
  var RISK_SCORING_CONTENT = `# Risk Scoring Model
18561
19126
 
@@ -18617,7 +19182,10 @@ var MODULE_DESCRIPTIONS = {
18617
19182
  security_hub_findings: "Aggregates active findings from AWS Security Hub \u2014 replaces individual config scanners with centralized compliance checks.",
18618
19183
  guardduty_findings: "Aggregates threat detection findings from Amazon GuardDuty \u2014 account compromise, instance compromise, and reconnaissance.",
18619
19184
  inspector_findings: "Aggregates vulnerability findings from Amazon Inspector \u2014 CVEs in EC2, Lambda, and container images.",
18620
- trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan."
19185
+ trusted_advisor_findings: "Aggregates security checks from AWS Trusted Advisor \u2014 requires Business or Enterprise Support plan.",
19186
+ config_rules_findings: "Pulls non-compliant AWS Config Rule evaluation results \u2014 configuration compliance violations across all resource types.",
19187
+ access_analyzer_findings: "Pulls active IAM Access Analyzer findings \u2014 resources accessible from outside the account (external principals, public access).",
19188
+ patch_compliance_findings: "Checks SSM Patch Manager compliance \u2014 managed instances with missing or failed security and system patches."
18621
19189
  };
18622
19190
  function summarizeResult(result) {
18623
19191
  const { summary } = result;
@@ -18666,7 +19234,10 @@ function createServer(defaultRegion) {
18666
19234
  new SecurityHubFindingsScanner(),
18667
19235
  new GuardDutyFindingsScanner(),
18668
19236
  new InspectorFindingsScanner(),
18669
- new TrustedAdvisorFindingsScanner()
19237
+ new TrustedAdvisorFindingsScanner(),
19238
+ new ConfigRulesFindingsScanner(),
19239
+ new AccessAnalyzerFindingsScanner(),
19240
+ new PatchComplianceFindingsScanner()
18670
19241
  ];
18671
19242
  const scannerMap = /* @__PURE__ */ new Map();
18672
19243
  for (const s of allScanners) {
@@ -18719,7 +19290,10 @@ function createServer(defaultRegion) {
18719
19290
  { toolName: "scan_security_hub_findings", moduleName: "security_hub_findings", label: "Security Hub Findings" },
18720
19291
  { toolName: "scan_guardduty_findings", moduleName: "guardduty_findings", label: "GuardDuty Findings" },
18721
19292
  { toolName: "scan_inspector_findings", moduleName: "inspector_findings", label: "Inspector Findings" },
18722
- { toolName: "scan_trusted_advisor_findings", moduleName: "trusted_advisor_findings", label: "Trusted Advisor Findings" }
19293
+ { toolName: "scan_trusted_advisor_findings", moduleName: "trusted_advisor_findings", label: "Trusted Advisor Findings" },
19294
+ { toolName: "scan_config_rules_findings", moduleName: "config_rules_findings", label: "Config Rules Findings" },
19295
+ { toolName: "scan_access_analyzer_findings", moduleName: "access_analyzer_findings", label: "Access Analyzer Findings" },
19296
+ { toolName: "scan_patch_compliance_findings", moduleName: "patch_compliance_findings", label: "Patch Compliance Findings" }
18723
19297
  ];
18724
19298
  for (const { toolName, moduleName, label } of individualScanners) {
18725
19299
  server.tool(
@@ -18748,7 +19322,7 @@ function createServer(defaultRegion) {
18748
19322
  "scan_group",
18749
19323
  "Run a predefined group of security scanners for a specific scenario (e.g., MLPS compliance, network defense). Read-only. Supports multi-account org scanning.",
18750
19324
  {
18751
- group: external_exports.string().describe("Scan group ID: mlps3_precheck, hw_defense, exposure, pre_launch, data_encryption, least_privilege, log_integrity, disaster_recovery, idle_resources, tag_compliance, new_account_baseline, public_access_verify, aggregation"),
19325
+ group: external_exports.string().describe("Scan group ID: mlps3_precheck, hw_defense, exposure, data_encryption, least_privilege, log_integrity, disaster_recovery, idle_resources, tag_compliance, new_account_baseline, aggregation"),
18752
19326
  region: external_exports.string().optional().describe("AWS region to scan (default: server region)"),
18753
19327
  org_mode: external_exports.boolean().optional().describe("Enable multi-account scanning via AWS Organizations"),
18754
19328
  role_name: external_exports.string().optional().describe("IAM role name to assume in child accounts (default: AWSSecurityMCPAudit)"),
@@ -19151,7 +19725,7 @@ Deploy this as a StackSet from your Management Account to all member accounts.`
19151
19725
  server.resource(
19152
19726
  "security-rules",
19153
19727
  "security://rules",
19154
- { description: "Describes all 14 scan modules and their check rules", mimeType: "text/markdown" },
19728
+ { description: "Describes all 17 scan modules and their check rules", mimeType: "text/markdown" },
19155
19729
  async () => ({
19156
19730
  contents: [{ uri: "security://rules", text: SECURITY_RULES_CONTENT, mimeType: "text/markdown" }]
19157
19731
  })