aws-security-mcp 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -237,7 +237,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
237
237
  import { z } from "zod";
238
238
 
239
239
  // src/version.ts
240
- var VERSION = "0.6.0";
240
+ var VERSION = "0.6.1";
241
241
 
242
242
  // src/utils/aws-client.ts
243
243
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -3046,126 +3046,37 @@ var SecurityHubFindingsScanner = class {
3046
3046
  // src/scanners/guardduty-findings.ts
3047
3047
  import {
3048
3048
  GuardDutyClient as GuardDutyClient2,
3049
- ListDetectorsCommand as ListDetectorsCommand2,
3050
- ListFindingsCommand,
3051
- GetFindingsCommand as GetFindingsCommand2
3049
+ ListDetectorsCommand as ListDetectorsCommand2
3052
3050
  } from "@aws-sdk/client-guardduty";
3053
- function gdSeverityToScore(severity) {
3054
- if (severity >= 7) return 8;
3055
- if (severity >= 4) return 5.5;
3056
- return 3;
3057
- }
3058
3051
  var GuardDutyFindingsScanner = class {
3059
3052
  moduleName = "guardduty_findings";
3060
3053
  async scan(ctx) {
3061
- const { region, partition, accountId } = ctx;
3054
+ const { region } = ctx;
3062
3055
  const startMs = Date.now();
3063
- const findings = [];
3064
3056
  const warnings = [];
3065
- let resourcesScanned = 0;
3066
3057
  try {
3067
3058
  const client = createClient(GuardDutyClient2, region, ctx.credentials);
3068
- const detectorsResp = await client.send(new ListDetectorsCommand2({}));
3069
- const detectorIds = detectorsResp.DetectorIds ?? [];
3059
+ const resp = await client.send(new ListDetectorsCommand2({}));
3060
+ const detectorIds = resp.DetectorIds ?? [];
3070
3061
  if (detectorIds.length === 0) {
3071
3062
  warnings.push("GuardDuty is not enabled in this region (no detectors found).");
3072
- return {
3073
- module: this.moduleName,
3074
- status: "success",
3075
- warnings,
3076
- resourcesScanned: 0,
3077
- findingsCount: 0,
3078
- scanTimeMs: Date.now() - startMs,
3079
- findings: []
3080
- };
3081
- }
3082
- const detectorId = detectorIds[0];
3083
- let nextToken;
3084
- const findingIds = [];
3085
- do {
3086
- const listResp = await client.send(
3087
- new ListFindingsCommand({
3088
- DetectorId: detectorId,
3089
- FindingCriteria: {
3090
- Criterion: {
3091
- "service.archived": {
3092
- Eq: ["false"]
3093
- }
3094
- }
3095
- },
3096
- MaxResults: 50,
3097
- NextToken: nextToken
3098
- })
3099
- );
3100
- findingIds.push(...listResp.FindingIds ?? []);
3101
- nextToken = listResp.NextToken;
3102
- } while (nextToken);
3103
- resourcesScanned = findingIds.length;
3104
- if (findingIds.length === 0) {
3105
- return {
3106
- module: this.moduleName,
3107
- status: "success",
3108
- warnings: warnings.length > 0 ? warnings : void 0,
3109
- resourcesScanned: 0,
3110
- findingsCount: 0,
3111
- scanTimeMs: Date.now() - startMs,
3112
- findings: []
3113
- };
3114
- }
3115
- for (let i = 0; i < findingIds.length; i += 50) {
3116
- const batch = findingIds.slice(i, i + 50);
3117
- const detailsResp = await client.send(
3118
- new GetFindingsCommand2({
3119
- DetectorId: detectorId,
3120
- FindingIds: batch
3121
- })
3122
- );
3123
- for (const gdf of detailsResp.Findings ?? []) {
3124
- const gdSeverity = gdf.Severity ?? 0;
3125
- const score = gdSeverityToScore(gdSeverity);
3126
- const severity = severityFromScore(score);
3127
- const resourceType = gdf.Resource?.ResourceType ?? "AWS::Unknown";
3128
- const resourceId = gdf.Resource?.InstanceDetails?.InstanceId ?? gdf.Resource?.AccessKeyDetails?.AccessKeyId ?? gdf.Arn ?? "unknown";
3129
- const resourceArn = gdf.Arn ?? `arn:${partition}:guardduty:${region}:${accountId}:detector/${detectorId}/finding/${gdf.Id ?? "unknown"}`;
3130
- findings.push({
3131
- severity,
3132
- title: `[GuardDuty] ${gdf.Title ?? gdf.Type ?? "Finding"}`,
3133
- resourceType,
3134
- resourceId,
3135
- resourceArn,
3136
- region: gdf.Region ?? region,
3137
- description: gdf.Description ?? gdf.Title ?? "No description",
3138
- impact: `GuardDuty threat type: ${gdf.Type ?? "unknown"} (severity ${gdSeverity})`,
3139
- riskScore: score,
3140
- remediationSteps: [
3141
- `Investigate ${gdf.Type ?? "unknown threat"}: ${gdf.Title ?? "threat detected"}`,
3142
- gdf.Description ? `Details: ${gdf.Description.substring(0, 200)}` : "",
3143
- "Isolate affected resources if compromise is confirmed.",
3144
- "Review CloudTrail logs for related suspicious activity."
3145
- ].filter(Boolean),
3146
- priority: priorityFromSeverity(severity),
3147
- module: this.moduleName,
3148
- accountId: gdf.AccountId ?? accountId
3149
- });
3150
- }
3151
3063
  }
3152
3064
  return {
3153
3065
  module: this.moduleName,
3154
3066
  status: "success",
3155
3067
  warnings: warnings.length > 0 ? warnings : void 0,
3156
- resourcesScanned,
3157
- findingsCount: findings.length,
3068
+ resourcesScanned: 0,
3069
+ findingsCount: 0,
3158
3070
  scanTimeMs: Date.now() - startMs,
3159
- findings
3071
+ findings: []
3160
3072
  };
3161
3073
  } catch (err) {
3162
3074
  const msg = err instanceof Error ? err.message : String(err);
3163
3075
  return {
3164
3076
  module: this.moduleName,
3165
3077
  status: "error",
3166
- error: `GuardDuty findings scan failed: ${msg}`,
3167
- warnings: warnings.length > 0 ? warnings : void 0,
3168
- resourcesScanned,
3078
+ error: `GuardDuty detection check failed: ${msg}`,
3079
+ resourcesScanned: 0,
3169
3080
  findingsCount: 0,
3170
3081
  scanTimeMs: Date.now() - startMs,
3171
3082
  findings: []
@@ -3177,164 +3088,59 @@ var GuardDutyFindingsScanner = class {
3177
3088
  // src/scanners/inspector-findings.ts
3178
3089
  import {
3179
3090
  Inspector2Client as Inspector2Client2,
3180
- ListFindingsCommand as ListFindingsCommand2
3091
+ BatchGetAccountStatusCommand as BatchGetAccountStatusCommand2
3181
3092
  } from "@aws-sdk/client-inspector2";
3182
- function inspectorSeverityToScore(label) {
3183
- switch (label) {
3184
- case "CRITICAL":
3185
- return 9.5;
3186
- case "HIGH":
3187
- return 8;
3188
- case "MEDIUM":
3189
- return 5.5;
3190
- case "LOW":
3191
- return 3;
3192
- case "INFORMATIONAL":
3193
- return null;
3194
- case "UNTRIAGED":
3195
- return 5.5;
3196
- default:
3197
- return null;
3198
- }
3199
- }
3200
3093
  var InspectorFindingsScanner = class {
3201
3094
  moduleName = "inspector_findings";
3202
3095
  async scan(ctx) {
3203
- const { region, partition, accountId } = ctx;
3096
+ const { region } = ctx;
3204
3097
  const startMs = Date.now();
3205
- const findings = [];
3206
3098
  const warnings = [];
3207
- let resourcesScanned = 0;
3208
3099
  try {
3209
3100
  const client = createClient(Inspector2Client2, region, ctx.credentials);
3210
- let nextToken;
3211
- const filterCriteria = {
3212
- findingStatus: [{ comparison: "EQUALS", value: "ACTIVE" }]
3213
- };
3214
- do {
3215
- const resp = await client.send(
3216
- new ListFindingsCommand2({
3217
- filterCriteria,
3218
- maxResults: 100,
3219
- nextToken
3220
- })
3221
- );
3222
- const inspFindings = resp.findings ?? [];
3223
- resourcesScanned += inspFindings.length;
3224
- for (const f of inspFindings) {
3225
- const severityLabel = f.severity ?? "INFORMATIONAL";
3226
- const score = inspectorSeverityToScore(severityLabel);
3227
- if (score === null) continue;
3228
- const severity = severityFromScore(score);
3229
- const cveId = f.packageVulnerabilityDetails?.vulnerabilityId;
3230
- const titleBase = f.title ?? "Inspector Finding";
3231
- const title = cveId ? `[${cveId}] ${titleBase}` : titleBase;
3232
- const resourceId = f.resources?.[0]?.id ?? "unknown";
3233
- const resourceType = f.resources?.[0]?.type ?? "AWS::Unknown";
3234
- const resourceArn = resourceId.startsWith("arn:") ? resourceId : `arn:${partition}:inspector2:${region}:${accountId}:finding/${f.findingArn ?? "unknown"}`;
3235
- const remediationSteps = [];
3236
- const vulnPkgs = f.packageVulnerabilityDetails?.vulnerablePackages;
3237
- if (vulnPkgs?.length) {
3238
- for (const pkg of vulnPkgs.slice(0, 3)) {
3239
- const name = pkg.name ?? "unknown-package";
3240
- const installed = pkg.version ?? "unknown";
3241
- const fixed = pkg.fixedInVersion ?? "latest";
3242
- const cveRef = cveId ? ` to fix ${cveId}` : "";
3243
- remediationSteps.push(`Update ${name} from ${installed} to ${fixed}${cveRef}`);
3244
- }
3245
- } else if (f.remediation?.recommendation?.text) {
3246
- remediationSteps.push(f.remediation.recommendation.text);
3247
- }
3248
- const genericPatterns = ["See References", "None Provided", "Review the finding"];
3249
- if (remediationSteps.length === 0 || genericPatterns.some((p) => remediationSteps[0]?.startsWith(p))) {
3250
- remediationSteps.length = 0;
3251
- const rawTitle = f.title ?? "";
3252
- if (rawTitle.includes("KB")) {
3253
- const kbMatch = rawTitle.match(/KB\d+/);
3254
- const kb = kbMatch ? kbMatch[0] : "patch";
3255
- remediationSteps.push(`Install Windows patch ${kb} via WSUS or AWS Systems Manager Patch Manager`);
3256
- remediationSteps.push(`Run: aws ssm send-command --document-name "AWS-InstallWindowsUpdates" --targets "Key=InstanceIds,Values=${resourceId}"`);
3257
- if (kbMatch) {
3258
- remediationSteps.push(`Microsoft KB article: https://support.microsoft.com/help/${kb}`);
3259
- }
3260
- } else if (rawTitle.includes("CVE-") || cveId) {
3261
- const cveMatch = rawTitle.match(/CVE-[\d-]+/);
3262
- const cve = cveMatch ? cveMatch[0] : cveId ?? "vulnerability";
3263
- remediationSteps.push(`Fix ${cve}: update the affected software package to the latest patched version`);
3264
- } else {
3265
- remediationSteps.push(`Review and remediate: ${rawTitle}`);
3266
- }
3267
- }
3268
- if (f.remediation?.recommendation?.Url) {
3269
- remediationSteps.push(`Documentation: ${f.remediation.recommendation.Url}`);
3270
- }
3271
- if (f.packageVulnerabilityDetails?.referenceUrls?.length) {
3272
- remediationSteps.push(`CVE references: ${f.packageVulnerabilityDetails.referenceUrls.slice(0, 3).join(", ")}`);
3273
- }
3274
- const description = f.description ?? titleBase;
3275
- const impact = cveId ? `Vulnerability ${cveId} \u2014 CVSS: ${f.packageVulnerabilityDetails?.cvss?.[0]?.baseScore ?? "N/A"}` : `Inspector finding type: ${f.type ?? "unknown"}`;
3276
- findings.push({
3277
- severity,
3278
- title,
3279
- resourceType,
3280
- resourceId,
3281
- resourceArn,
3282
- region,
3283
- description,
3284
- impact,
3285
- riskScore: score,
3286
- remediationSteps,
3287
- priority: priorityFromSeverity(severity),
3288
- module: this.moduleName,
3289
- accountId: f.awsAccountId ?? accountId
3290
- });
3101
+ const resp = await client.send(new BatchGetAccountStatusCommand2({ accountIds: [] }));
3102
+ const account = resp.accounts?.[0];
3103
+ if (!account || account.state?.status !== "ENABLED") {
3104
+ warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
3105
+ } else {
3106
+ const rs = account.resourceState;
3107
+ const types = [
3108
+ { name: "EC2", status: rs?.ec2?.status },
3109
+ { name: "Lambda", status: rs?.lambda?.status },
3110
+ { name: "ECR", status: rs?.ecr?.status },
3111
+ { name: "Lambda Code", status: rs?.lambdaCode?.status },
3112
+ { name: "Code Repository", status: rs?.codeRepository?.status }
3113
+ ];
3114
+ const disabled = types.filter((t) => t.status && t.status !== "ENABLED");
3115
+ if (disabled.length > 0) {
3116
+ warnings.push(
3117
+ `Inspector scan types not enabled: ${disabled.map((t) => t.name).join(", ")}. Enable them for full vulnerability coverage.`
3118
+ );
3291
3119
  }
3292
- nextToken = resp.nextToken;
3293
- } while (nextToken);
3120
+ }
3294
3121
  return {
3295
3122
  module: this.moduleName,
3296
3123
  status: "success",
3297
3124
  warnings: warnings.length > 0 ? warnings : void 0,
3298
- resourcesScanned,
3299
- findingsCount: findings.length,
3125
+ resourcesScanned: 0,
3126
+ findingsCount: 0,
3300
3127
  scanTimeMs: Date.now() - startMs,
3301
- findings
3128
+ findings: []
3302
3129
  };
3303
3130
  } catch (err) {
3304
3131
  const msg = err instanceof Error ? err.message : String(err);
3305
3132
  const errName = err instanceof Error ? err.name : "";
3306
3133
  const isAccessDenied2 = errName === "AccessDeniedException" || msg.includes("AccessDeniedException");
3307
- const isNotEnabled2 = msg.includes("not enabled") || msg.includes("not subscribed");
3308
3134
  if (isAccessDenied2) {
3309
- warnings.push("Insufficient permissions to access Inspector. Grant inspector2:ListFindings to scan for vulnerabilities.");
3310
- return {
3311
- module: this.moduleName,
3312
- status: "success",
3313
- warnings,
3314
- resourcesScanned: 0,
3315
- findingsCount: 0,
3316
- scanTimeMs: Date.now() - startMs,
3317
- findings: []
3318
- };
3319
- }
3320
- if (isNotEnabled2) {
3135
+ warnings.push("Insufficient permissions to access Inspector. Grant inspector2:BatchGetAccountStatus to check enablement.");
3136
+ } else {
3321
3137
  warnings.push("Inspector is not enabled in this region. Enable it to scan for software vulnerabilities.");
3322
- return {
3323
- module: this.moduleName,
3324
- status: "success",
3325
- warnings,
3326
- resourcesScanned: 0,
3327
- findingsCount: 0,
3328
- scanTimeMs: Date.now() - startMs,
3329
- findings: []
3330
- };
3331
3138
  }
3332
3139
  return {
3333
3140
  module: this.moduleName,
3334
- status: "error",
3335
- error: `Inspector findings scan failed: ${msg}`,
3336
- warnings: warnings.length > 0 ? warnings : void 0,
3337
- resourcesScanned,
3141
+ status: "success",
3142
+ warnings,
3143
+ resourcesScanned: 0,
3338
3144
  findingsCount: 0,
3339
3145
  scanTimeMs: Date.now() - startMs,
3340
3146
  findings: []
@@ -3507,128 +3313,29 @@ var TrustedAdvisorFindingsScanner = class {
3507
3313
  // src/scanners/config-rules-findings.ts
3508
3314
  import {
3509
3315
  ConfigServiceClient as ConfigServiceClient2,
3510
- DescribeComplianceByConfigRuleCommand,
3511
- GetComplianceDetailsByConfigRuleCommand
3316
+ DescribeConfigurationRecordersCommand as DescribeConfigurationRecordersCommand2
3512
3317
  } from "@aws-sdk/client-config-service";
3513
- var SECURITY_RULE_PATTERNS = [
3514
- "securitygroup",
3515
- "security-group",
3516
- "encryption",
3517
- "encrypted",
3518
- "public",
3519
- "unrestricted",
3520
- "mfa",
3521
- "password",
3522
- "access-key",
3523
- "root",
3524
- "admin",
3525
- "logging",
3526
- "cloudtrail",
3527
- "iam",
3528
- "kms",
3529
- "ssl",
3530
- "tls",
3531
- "vpc-flow",
3532
- "guardduty",
3533
- "securityhub"
3534
- ];
3535
- function ruleIsSecurityRelated(ruleName) {
3536
- const lower = ruleName.toLowerCase();
3537
- return SECURITY_RULE_PATTERNS.some((pat) => lower.includes(pat));
3538
- }
3539
3318
  var ConfigRulesFindingsScanner = class {
3540
3319
  moduleName = "config_rules_findings";
3541
3320
  async scan(ctx) {
3542
- const { region, partition, accountId } = ctx;
3321
+ const { region } = ctx;
3543
3322
  const startMs = Date.now();
3544
- const findings = [];
3545
3323
  const warnings = [];
3546
- let resourcesScanned = 0;
3547
3324
  try {
3548
3325
  const client = createClient(ConfigServiceClient2, region, ctx.credentials);
3549
- let nextToken;
3550
- const nonCompliantRules = [];
3551
- do {
3552
- const resp = await client.send(
3553
- new DescribeComplianceByConfigRuleCommand({ NextToken: nextToken })
3554
- );
3555
- for (const rule of resp.ComplianceByConfigRules ?? []) {
3556
- resourcesScanned++;
3557
- if (rule.Compliance?.ComplianceType === "NON_COMPLIANT") {
3558
- nonCompliantRules.push(rule);
3559
- }
3560
- }
3561
- nextToken = resp.NextToken;
3562
- } while (nextToken);
3563
- if (resourcesScanned === 0) {
3564
- warnings.push("AWS Config is not enabled in this region or no Config Rules are defined.");
3565
- return {
3566
- module: this.moduleName,
3567
- status: "success",
3568
- warnings,
3569
- resourcesScanned: 0,
3570
- findingsCount: 0,
3571
- scanTimeMs: Date.now() - startMs,
3572
- findings: []
3573
- };
3574
- }
3575
- for (const rule of nonCompliantRules) {
3576
- const ruleName = rule.ConfigRuleName ?? "unknown";
3577
- try {
3578
- let detailToken;
3579
- do {
3580
- const detailResp = await client.send(
3581
- new GetComplianceDetailsByConfigRuleCommand({
3582
- ConfigRuleName: ruleName,
3583
- ComplianceTypes: ["NON_COMPLIANT"],
3584
- NextToken: detailToken
3585
- })
3586
- );
3587
- for (const evalResult of detailResp.EvaluationResults ?? []) {
3588
- const qualifier = evalResult.EvaluationResultIdentifier?.EvaluationResultQualifier;
3589
- const resourceType = qualifier?.ResourceType ?? "AWS::Unknown";
3590
- const resourceId = qualifier?.ResourceId ?? "unknown";
3591
- const annotation = evalResult.Annotation;
3592
- const isSecurityRule = ruleIsSecurityRelated(ruleName);
3593
- const riskScore = isSecurityRule ? 7.5 : 5.5;
3594
- const severity = severityFromScore(riskScore);
3595
- const descParts = [`Config Rule: ${ruleName}`, `Resource Type: ${resourceType}`];
3596
- if (annotation) descParts.push(`Annotation: ${annotation}`);
3597
- findings.push({
3598
- severity,
3599
- title: `Config Rule: ${ruleName} - ${resourceType}/${resourceId} Non-Compliant`,
3600
- resourceType,
3601
- resourceId,
3602
- resourceArn: resourceId,
3603
- region,
3604
- description: descParts.join(". "),
3605
- impact: `Resource is non-compliant with Config Rule: ${ruleName}`,
3606
- riskScore,
3607
- remediationSteps: [
3608
- `Fix Config Rule violation: ${ruleName}`,
3609
- annotation ? `Details: ${annotation}` : "",
3610
- `Resource: ${resourceType}/${resourceId}`
3611
- ].filter(Boolean),
3612
- priority: priorityFromSeverity(severity),
3613
- module: this.moduleName,
3614
- accountId
3615
- });
3616
- }
3617
- detailToken = detailResp.NextToken;
3618
- } while (detailToken);
3619
- } catch (detailErr) {
3620
- const msg = detailErr instanceof Error ? detailErr.message : String(detailErr);
3621
- warnings.push(`Failed to get details for rule ${ruleName}: ${msg}`);
3622
- }
3326
+ const resp = await client.send(new DescribeConfigurationRecordersCommand2({}));
3327
+ const recorders = resp.ConfigurationRecorders ?? [];
3328
+ if (recorders.length === 0) {
3329
+ warnings.push("AWS Config is not enabled in this region.");
3623
3330
  }
3624
3331
  return {
3625
3332
  module: this.moduleName,
3626
3333
  status: "success",
3627
3334
  warnings: warnings.length > 0 ? warnings : void 0,
3628
- resourcesScanned,
3629
- findingsCount: findings.length,
3335
+ resourcesScanned: 0,
3336
+ findingsCount: 0,
3630
3337
  scanTimeMs: Date.now() - startMs,
3631
- findings
3338
+ findings: []
3632
3339
  };
3633
3340
  } catch (err) {
3634
3341
  const msg = err instanceof Error ? err.message : String(err);
@@ -3647,9 +3354,8 @@ var ConfigRulesFindingsScanner = class {
3647
3354
  return {
3648
3355
  module: this.moduleName,
3649
3356
  status: "error",
3650
- error: `Config Rules scan failed: ${msg}`,
3651
- warnings: warnings.length > 0 ? warnings : void 0,
3652
- resourcesScanned,
3357
+ error: `Config Rules detection check failed: ${msg}`,
3358
+ resourcesScanned: 0,
3653
3359
  findingsCount: 0,
3654
3360
  scanTimeMs: Date.now() - startMs,
3655
3361
  findings: []
@@ -3661,146 +3367,50 @@ var ConfigRulesFindingsScanner = class {
3661
3367
  // src/scanners/access-analyzer-findings.ts
3662
3368
  import {
3663
3369
  AccessAnalyzerClient,
3664
- ListAnalyzersCommand,
3665
- ListFindingsV2Command
3370
+ ListAnalyzersCommand
3666
3371
  } from "@aws-sdk/client-accessanalyzer";
3667
- function findingTypeToScore(findingType) {
3668
- const ft = findingType;
3669
- switch (ft) {
3670
- case "ExternalAccess":
3671
- return 8;
3672
- case "UnusedIAMRole":
3673
- case "UnusedIAMUserAccessKey":
3674
- case "UnusedIAMUserPassword":
3675
- return 5.5;
3676
- case "UnusedPermission":
3677
- return 3;
3678
- default:
3679
- return 5.5;
3680
- }
3681
- }
3682
- var UNUSED_FINDING_TYPES = /* @__PURE__ */ new Set([
3683
- "UnusedIAMRole",
3684
- "UnusedIAMUserAccessKey",
3685
- "UnusedIAMUserPassword",
3686
- "UnusedPermission"
3687
- ]);
3688
- function isSecurityRelevant(findingType) {
3689
- const ft = findingType;
3690
- return ft === "ExternalAccess" || UNUSED_FINDING_TYPES.has(ft ?? "");
3691
- }
3692
- function isExternalAccess(findingType) {
3693
- return findingType === "ExternalAccess";
3694
- }
3695
3372
  var AccessAnalyzerFindingsScanner = class {
3696
3373
  moduleName = "access_analyzer_findings";
3697
3374
  async scan(ctx) {
3698
- const { region, partition, accountId } = ctx;
3375
+ const { region } = ctx;
3699
3376
  const startMs = Date.now();
3700
- const findings = [];
3701
3377
  const warnings = [];
3702
- let resourcesScanned = 0;
3703
3378
  try {
3704
3379
  const client = createClient(AccessAnalyzerClient, region, ctx.credentials);
3705
3380
  let analyzerToken;
3706
- const analyzers = [];
3381
+ let hasActiveAnalyzer = false;
3707
3382
  do {
3708
3383
  const resp = await client.send(
3709
3384
  new ListAnalyzersCommand({ nextToken: analyzerToken })
3710
3385
  );
3711
3386
  for (const analyzer of resp.analyzers ?? []) {
3712
3387
  if (analyzer.status === "ACTIVE") {
3713
- analyzers.push(analyzer);
3388
+ hasActiveAnalyzer = true;
3389
+ break;
3714
3390
  }
3715
3391
  }
3392
+ if (hasActiveAnalyzer) break;
3716
3393
  analyzerToken = resp.nextToken;
3717
3394
  } while (analyzerToken);
3718
- if (analyzers.length === 0) {
3395
+ if (!hasActiveAnalyzer) {
3719
3396
  warnings.push("No IAM Access Analyzer found. Create an analyzer to detect external access to your resources.");
3720
- return {
3721
- module: this.moduleName,
3722
- status: "success",
3723
- warnings,
3724
- resourcesScanned: 0,
3725
- findingsCount: 0,
3726
- scanTimeMs: Date.now() - startMs,
3727
- findings: []
3728
- };
3729
- }
3730
- for (const analyzer of analyzers) {
3731
- const analyzerArn = analyzer.arn ?? "unknown";
3732
- let findingToken;
3733
- do {
3734
- const listResp = await client.send(
3735
- new ListFindingsV2Command({
3736
- analyzerArn,
3737
- filter: {
3738
- status: { eq: ["ACTIVE"] }
3739
- },
3740
- nextToken: findingToken
3741
- })
3742
- );
3743
- for (const aaf of listResp.findings ?? []) {
3744
- if (!isSecurityRelevant(aaf.findingType)) {
3745
- continue;
3746
- }
3747
- resourcesScanned++;
3748
- const score = findingTypeToScore(aaf.findingType);
3749
- const severity = severityFromScore(score);
3750
- const resourceArn = aaf.resource ?? "unknown";
3751
- const resourceType = aaf.resourceType ?? "AWS::Unknown";
3752
- const resourceId = resourceArn.split("/").pop() ?? resourceArn.split(":").pop() ?? "unknown";
3753
- const external = isExternalAccess(aaf.findingType);
3754
- const descParts = [`Resource Type: ${resourceType}`];
3755
- if (aaf.resourceOwnerAccount) descParts.push(`Owner Account: ${aaf.resourceOwnerAccount}`);
3756
- if (aaf.findingType) descParts.push(`Finding Type: ${aaf.findingType}`);
3757
- const title = buildFindingTitle(aaf);
3758
- 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"}`;
3759
- const remediationSteps = external ? [
3760
- `Restrict external access on ${resourceType} ${resourceId}`,
3761
- "Remove or narrow the resource policy to eliminate unintended external access.",
3762
- `Resource ARN: ${resourceArn}`
3763
- ] : [
3764
- `Remove unused access on ${resourceType} ${resourceId}`,
3765
- "Remove unused permissions, roles, or credentials to follow least-privilege.",
3766
- `Resource ARN: ${resourceArn}`
3767
- ];
3768
- findings.push({
3769
- severity,
3770
- title,
3771
- resourceType: mapResourceType(resourceType),
3772
- resourceId,
3773
- resourceArn,
3774
- region,
3775
- description: descParts.join(". "),
3776
- impact,
3777
- riskScore: score,
3778
- remediationSteps,
3779
- priority: priorityFromSeverity(severity),
3780
- module: this.moduleName,
3781
- accountId: aaf.resourceOwnerAccount ?? accountId
3782
- });
3783
- }
3784
- findingToken = listResp.nextToken;
3785
- } while (findingToken);
3786
3397
  }
3787
3398
  return {
3788
3399
  module: this.moduleName,
3789
3400
  status: "success",
3790
3401
  warnings: warnings.length > 0 ? warnings : void 0,
3791
- resourcesScanned,
3792
- findingsCount: findings.length,
3402
+ resourcesScanned: 0,
3403
+ findingsCount: 0,
3793
3404
  scanTimeMs: Date.now() - startMs,
3794
- findings
3405
+ findings: []
3795
3406
  };
3796
3407
  } catch (err) {
3797
3408
  const msg = err instanceof Error ? err.message : String(err);
3798
3409
  return {
3799
3410
  module: this.moduleName,
3800
3411
  status: "error",
3801
- error: `Access Analyzer scan failed: ${msg}`,
3802
- warnings: warnings.length > 0 ? warnings : void 0,
3803
- resourcesScanned,
3412
+ error: `Access Analyzer detection check failed: ${msg}`,
3413
+ resourcesScanned: 0,
3804
3414
  findingsCount: 0,
3805
3415
  scanTimeMs: Date.now() - startMs,
3806
3416
  findings: []
@@ -3808,29 +3418,6 @@ var AccessAnalyzerFindingsScanner = class {
3808
3418
  }
3809
3419
  }
3810
3420
  };
3811
- function buildFindingTitle(finding) {
3812
- const resourceType = finding.resourceType ?? "Resource";
3813
- const resource = finding.resource ? finding.resource.split("/").pop() ?? finding.resource.split(":").pop() ?? finding.resource : "unknown";
3814
- const label = isExternalAccess(finding.findingType) ? "external access detected" : "unused access detected";
3815
- return `[Access Analyzer] ${resourceType} ${resource} \u2014 ${label}`;
3816
- }
3817
- function mapResourceType(aaType) {
3818
- const mapping = {
3819
- "AWS::S3::Bucket": "AWS::S3::Bucket",
3820
- "AWS::IAM::Role": "AWS::IAM::Role",
3821
- "AWS::SQS::Queue": "AWS::SQS::Queue",
3822
- "AWS::Lambda::Function": "AWS::Lambda::Function",
3823
- "AWS::Lambda::LayerVersion": "AWS::Lambda::LayerVersion",
3824
- "AWS::KMS::Key": "AWS::KMS::Key",
3825
- "AWS::SecretsManager::Secret": "AWS::SecretsManager::Secret",
3826
- "AWS::SNS::Topic": "AWS::SNS::Topic",
3827
- "AWS::EFS::FileSystem": "AWS::EFS::FileSystem",
3828
- "AWS::RDS::DBSnapshot": "AWS::RDS::DBSnapshot",
3829
- "AWS::RDS::DBClusterSnapshot": "AWS::RDS::DBClusterSnapshot",
3830
- "AWS::ECR::Repository": "AWS::ECR::Repository"
3831
- };
3832
- return mapping[aaType] ?? aaType;
3833
- }
3834
3421
 
3835
3422
  // src/scanners/patch-compliance-findings.ts
3836
3423
  import {