iam-policy-validator 1.13.1__py3-none-any.whl → 1.14.1__py3-none-any.whl

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.
Files changed (45) hide show
  1. {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/METADATA +1 -1
  2. {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/RECORD +45 -39
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/action_condition_enforcement.py +6 -0
  5. iam_validator/checks/action_resource_matching.py +12 -12
  6. iam_validator/checks/action_validation.py +1 -0
  7. iam_validator/checks/condition_key_validation.py +2 -0
  8. iam_validator/checks/condition_type_mismatch.py +3 -0
  9. iam_validator/checks/full_wildcard.py +1 -0
  10. iam_validator/checks/mfa_condition_check.py +2 -0
  11. iam_validator/checks/policy_structure.py +9 -0
  12. iam_validator/checks/policy_type_validation.py +11 -0
  13. iam_validator/checks/principal_validation.py +5 -0
  14. iam_validator/checks/resource_validation.py +4 -0
  15. iam_validator/checks/sensitive_action.py +1 -0
  16. iam_validator/checks/service_wildcard.py +6 -3
  17. iam_validator/checks/set_operator_validation.py +3 -0
  18. iam_validator/checks/sid_uniqueness.py +2 -0
  19. iam_validator/checks/trust_policy_validation.py +3 -0
  20. iam_validator/checks/utils/__init__.py +16 -0
  21. iam_validator/checks/utils/action_parser.py +149 -0
  22. iam_validator/checks/wildcard_action.py +1 -0
  23. iam_validator/checks/wildcard_resource.py +231 -4
  24. iam_validator/commands/analyze.py +19 -1
  25. iam_validator/commands/completion.py +6 -2
  26. iam_validator/commands/validate.py +231 -12
  27. iam_validator/core/aws_service/fetcher.py +21 -9
  28. iam_validator/core/codeowners.py +245 -0
  29. iam_validator/core/config/check_documentation.py +390 -0
  30. iam_validator/core/config/config_loader.py +199 -0
  31. iam_validator/core/config/defaults.py +25 -0
  32. iam_validator/core/constants.py +1 -0
  33. iam_validator/core/diff_parser.py +8 -4
  34. iam_validator/core/finding_fingerprint.py +131 -0
  35. iam_validator/core/formatters/sarif.py +370 -128
  36. iam_validator/core/ignore_processor.py +309 -0
  37. iam_validator/core/ignored_findings.py +400 -0
  38. iam_validator/core/models.py +54 -4
  39. iam_validator/core/policy_loader.py +313 -4
  40. iam_validator/core/pr_commenter.py +223 -22
  41. iam_validator/core/report.py +22 -6
  42. iam_validator/integrations/github_integration.py +881 -123
  43. {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/WHEEL +0 -0
  44. {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/entry_points.txt +0 -0
  45. {iam_policy_validator-1.13.1.dist-info → iam_policy_validator-1.14.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,15 @@
1
- """SARIF (Static Analysis Results Interchange Format) formatter for GitHub integration."""
1
+ """SARIF (Static Analysis Results Interchange Format) formatter for GitHub integration.
2
+
3
+ This formatter produces SARIF 2.1.0 output that integrates with GitHub Code Scanning,
4
+ providing rich issue details including:
5
+ - Risk explanations and remediation guidance
6
+ - Suggested fixes and code examples
7
+ - Links to documentation
8
+ - Affected policy fields (action, resource, condition)
9
+
10
+ The output appears in GitHub's Security tab as code scanning alerts with inline
11
+ annotations on affected lines.
12
+ """
2
13
 
3
14
  import json
4
15
  from datetime import datetime, timezone
@@ -9,7 +20,14 @@ from iam_validator.core.models import ValidationIssue, ValidationReport
9
20
 
10
21
 
11
22
  class SARIFFormatter(OutputFormatter):
12
- """Formats validation results in SARIF format for GitHub code scanning."""
23
+ """Formats validation results in SARIF format for GitHub code scanning.
24
+
25
+ Produces rich SARIF output with:
26
+ - Dynamic rule definitions based on check IDs
27
+ - Full issue context (risk, remediation, examples)
28
+ - Suggested fixes as SARIF fix objects
29
+ - Related locations for affected fields
30
+ """
13
31
 
14
32
  @property
15
33
  def format_id(self) -> str:
@@ -55,6 +73,11 @@ class SARIFFormatter(OutputFormatter):
55
73
  "low": "note",
56
74
  }
57
75
 
76
+ # Collect all unique check_ids from issues for dynamic rule generation
77
+ all_issues: list[ValidationIssue] = []
78
+ for policy_result in report.results:
79
+ all_issues.extend(policy_result.issues)
80
+
58
81
  # Create SARIF structure
59
82
  sarif = {
60
83
  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
@@ -66,7 +89,7 @@ class SARIFFormatter(OutputFormatter):
66
89
  "name": "IAM Validator",
67
90
  "version": tool_version,
68
91
  "informationUri": "https://github.com/boogy/iam-validator",
69
- "rules": self._create_rules(),
92
+ "rules": self._create_rules_from_issues(all_issues),
70
93
  }
71
94
  },
72
95
  "results": self._create_results(report, severity_map),
@@ -83,90 +106,190 @@ class SARIFFormatter(OutputFormatter):
83
106
 
84
107
  return sarif
85
108
 
86
- def _create_rules(self) -> list[dict[str, Any]]:
87
- """Create SARIF rules definitions."""
88
- return [
89
- {
90
- "id": "invalid-action",
91
- "shortDescription": {"text": "Invalid IAM Action"},
92
- "fullDescription": {"text": "The specified IAM action does not exist in AWS"},
93
- "helpUri": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html",
94
- "defaultConfiguration": {"level": "error"},
95
- },
96
- {
97
- "id": "invalid-condition-key",
98
- "shortDescription": {"text": "Invalid Condition Key"},
99
- "fullDescription": {
100
- "text": "The specified condition key is not valid for this action"
101
- },
102
- "helpUri": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html",
103
- "defaultConfiguration": {"level": "error"},
104
- },
105
- {
106
- "id": "invalid-resource",
107
- "shortDescription": {"text": "Invalid Resource ARN"},
108
- "fullDescription": {"text": "The resource ARN format is invalid"},
109
- "helpUri": "https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html",
110
- "defaultConfiguration": {"level": "error"},
111
- },
112
- {
113
- "id": "duplicate-sid",
114
- "shortDescription": {"text": "Duplicate Statement ID"},
115
- "fullDescription": {
116
- "text": "Multiple statements use the same Statement ID (Sid), which can cause confusion"
117
- },
118
- "helpUri": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_sid.html",
119
- "defaultConfiguration": {"level": "error"},
120
- },
121
- {
122
- "id": "overly-permissive",
123
- "shortDescription": {"text": "Overly Permissive Policy"},
124
- "fullDescription": {
125
- "text": "Policy grants overly broad permissions using wildcards in actions or resources"
126
- },
127
- "helpUri": "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege",
128
- "defaultConfiguration": {"level": "warning"},
129
- },
130
- {
131
- "id": "missing-condition",
132
- "shortDescription": {"text": "Missing Condition Restrictions"},
133
- "fullDescription": {
134
- "text": "Sensitive actions should include condition restrictions to limit when they can be used"
135
- },
136
- "helpUri": "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#use-policy-conditions",
137
- "defaultConfiguration": {"level": "warning"},
138
- },
139
- {
140
- "id": "missing-required-condition",
141
- "shortDescription": {"text": "Missing Required Condition"},
142
- "fullDescription": {
143
- "text": "Specific actions require certain conditions to prevent privilege escalation or security issues"
144
- },
145
- "helpUri": "https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html",
146
- "defaultConfiguration": {"level": "error"},
147
- },
148
- {
149
- "id": "invalid-principal",
150
- "shortDescription": {"text": "Invalid Principal"},
151
- "fullDescription": {
152
- "text": "The specified principal is invalid or improperly formatted"
153
- },
154
- "helpUri": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html",
155
- "defaultConfiguration": {"level": "error"},
156
- },
157
- {
158
- "id": "general-issue",
159
- "shortDescription": {"text": "IAM Policy Issue"},
160
- "fullDescription": {"text": "General IAM policy validation issue"},
161
- "helpUri": "https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html",
162
- "defaultConfiguration": {"level": "warning"},
163
- },
164
- ]
109
+ def _create_rules_from_issues(self, issues: list[ValidationIssue]) -> list[dict[str, Any]]:
110
+ """Create SARIF rules dynamically from actual issues found.
111
+
112
+ This generates rules based on the check_id and issue_type of actual findings,
113
+ ensuring all rules referenced by results are defined.
114
+
115
+ Args:
116
+ issues: List of all validation issues
117
+
118
+ Returns:
119
+ List of SARIF rule definitions
120
+ """
121
+ # Track unique rules by check_id (or issue_type as fallback)
122
+ rules_map: dict[str, dict[str, Any]] = {}
123
+
124
+ # Severity to SARIF level mapping
125
+ severity_to_level = {
126
+ "error": "error",
127
+ "critical": "error",
128
+ "high": "error",
129
+ "warning": "warning",
130
+ "medium": "warning",
131
+ "info": "note",
132
+ "low": "note",
133
+ }
134
+
135
+ for issue in issues:
136
+ rule_id = self._get_rule_id(issue)
137
+
138
+ # Skip if already defined
139
+ if rule_id in rules_map:
140
+ continue
141
+
142
+ # Build rule from issue metadata
143
+ rule: dict[str, Any] = {
144
+ "id": rule_id,
145
+ "shortDescription": {"text": self._get_rule_short_description(issue)},
146
+ "fullDescription": {"text": self._get_rule_full_description(issue)},
147
+ "defaultConfiguration": {"level": severity_to_level.get(issue.severity, "warning")},
148
+ }
149
+
150
+ # Add help URI if available
151
+ if issue.documentation_url:
152
+ rule["helpUri"] = issue.documentation_url
153
+ else:
154
+ # Use default AWS docs based on issue type
155
+ rule["helpUri"] = self._get_default_help_uri(issue)
156
+
157
+ # Add rich help text with risk explanation and remediation
158
+ help_text = self._build_help_markdown(issue)
159
+ if help_text:
160
+ rule["help"] = {"text": help_text, "markdown": help_text}
161
+
162
+ rules_map[rule_id] = rule
163
+
164
+ # Return rules sorted by ID for consistent output
165
+ return list(sorted(rules_map.values(), key=lambda r: r["id"]))
166
+
167
+ def _get_rule_short_description(self, issue: ValidationIssue) -> str:
168
+ """Get a short description for the rule based on check_id or issue_type."""
169
+ # Map check_id to human-readable short descriptions
170
+ descriptions = {
171
+ "action_validation": "Invalid IAM Action",
172
+ "condition_key_validation": "Invalid Condition Key",
173
+ "condition_type_mismatch": "Condition Type Mismatch",
174
+ "resource_validation": "Invalid Resource ARN",
175
+ "sid_uniqueness": "Duplicate Statement ID",
176
+ "policy_size": "Policy Size Exceeded",
177
+ "policy_structure": "Invalid Policy Structure",
178
+ "set_operator_validation": "Invalid Set Operator",
179
+ "mfa_condition_check": "Missing MFA Condition",
180
+ "principal_validation": "Invalid Principal",
181
+ "policy_type_validation": "Policy Type Mismatch",
182
+ "action_resource_matching": "Action-Resource Mismatch",
183
+ "trust_policy_validation": "Trust Policy Issue",
184
+ "wildcard_action": "Wildcard Action",
185
+ "wildcard_resource": "Wildcard Resource",
186
+ "full_wildcard": "Full Wildcard Permission",
187
+ "service_wildcard": "Service-Wide Wildcard",
188
+ "sensitive_action": "Sensitive Action",
189
+ "action_condition_enforcement": "Missing Required Condition",
190
+ }
191
+
192
+ if issue.check_id and issue.check_id in descriptions:
193
+ return descriptions[issue.check_id]
194
+
195
+ # Fall back to formatting issue_type
196
+ return issue.issue_type.replace("_", " ").title()
197
+
198
+ def _get_rule_full_description(self, issue: ValidationIssue) -> str:
199
+ """Get a full description for the rule, using risk_explanation if available."""
200
+ if issue.risk_explanation:
201
+ return issue.risk_explanation
202
+
203
+ # Default descriptions based on check_id
204
+ descriptions = {
205
+ "action_validation": "The specified IAM action does not exist in AWS or is incorrectly formatted.",
206
+ "condition_key_validation": "The specified condition key is not valid for this action or service.",
207
+ "condition_type_mismatch": "The condition operator does not match the expected type for the condition key.",
208
+ "resource_validation": "The resource ARN format is invalid or does not match the expected pattern.",
209
+ "sid_uniqueness": "Multiple statements use the same Statement ID (Sid), which can cause confusion and policy conflicts.",
210
+ "policy_size": "The policy exceeds AWS size limits and may fail to apply.",
211
+ "policy_structure": "The policy structure is invalid and will be rejected by AWS.",
212
+ "wildcard_action": "Using wildcard (*) in actions grants broader permissions than necessary, violating least privilege.",
213
+ "wildcard_resource": "Using wildcard (*) in resources allows actions on all resources of that type.",
214
+ "full_wildcard": "Using Action: '*' with Resource: '*' grants full administrative access.",
215
+ "service_wildcard": "Using service:* grants all permissions for a service, which is overly permissive.",
216
+ "sensitive_action": "This action can modify security-critical resources and should be carefully restricted.",
217
+ "action_condition_enforcement": "Sensitive actions require specific conditions to prevent security issues.",
218
+ }
219
+
220
+ if issue.check_id and issue.check_id in descriptions:
221
+ return descriptions[issue.check_id]
222
+
223
+ return issue.message
224
+
225
+ def _get_default_help_uri(self, issue: ValidationIssue) -> str:
226
+ """Get default AWS documentation URL based on issue type."""
227
+ uri_map = {
228
+ "action_validation": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html",
229
+ "condition_key_validation": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html",
230
+ "resource_validation": "https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html",
231
+ "sid_uniqueness": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_sid.html",
232
+ "principal_validation": "https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html",
233
+ "wildcard_action": "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege",
234
+ "wildcard_resource": "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege",
235
+ "sensitive_action": "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#use-policy-conditions",
236
+ }
237
+
238
+ if issue.check_id and issue.check_id in uri_map:
239
+ return uri_map[issue.check_id]
240
+
241
+ return "https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html"
242
+
243
+ def _build_help_markdown(self, issue: ValidationIssue) -> str:
244
+ """Build markdown help text with remediation guidance.
245
+
246
+ Args:
247
+ issue: The validation issue
248
+
249
+ Returns:
250
+ Markdown-formatted help text
251
+ """
252
+ parts: list[str] = []
253
+
254
+ # Add risk explanation
255
+ if issue.risk_explanation:
256
+ parts.append(f"**Why this matters:** {issue.risk_explanation}")
257
+ parts.append("")
258
+
259
+ # Add remediation steps
260
+ if issue.remediation_steps:
261
+ parts.append("**How to fix:**")
262
+ for i, step in enumerate(issue.remediation_steps, 1):
263
+ parts.append(f"{i}. {step}")
264
+ parts.append("")
265
+
266
+ # Add suggestion
267
+ if issue.suggestion:
268
+ parts.append(f"**Suggestion:** {issue.suggestion}")
269
+ parts.append("")
270
+
271
+ # Add example
272
+ if issue.example:
273
+ parts.append("**Example:**")
274
+ parts.append("```json")
275
+ parts.append(issue.example)
276
+ parts.append("```")
277
+
278
+ return "\n".join(parts) if parts else ""
165
279
 
166
280
  def _create_results(
167
281
  self, report: ValidationReport, severity_map: dict[str, str]
168
282
  ) -> list[dict[str, Any]]:
169
- """Create SARIF results from validation issues."""
283
+ """Create SARIF results from validation issues with full context.
284
+
285
+ Each result includes:
286
+ - Rule reference and severity level
287
+ - Full message with risk explanation
288
+ - Location with line number
289
+ - Related locations for affected fields
290
+ - Fix suggestions with examples
291
+ - Properties with additional metadata
292
+ """
170
293
  results = []
171
294
 
172
295
  for policy_result in report.results:
@@ -177,7 +300,7 @@ class SARIFFormatter(OutputFormatter):
177
300
  result = {
178
301
  "ruleId": self._get_rule_id(issue),
179
302
  "level": severity_map.get(issue.severity, "note"),
180
- "message": {"text": issue.message},
303
+ "message": {"text": self._build_result_message(issue)},
181
304
  "locations": [
182
305
  {
183
306
  "physicalLocation": {
@@ -195,57 +318,176 @@ class SARIFFormatter(OutputFormatter):
195
318
  }
196
319
 
197
320
  # Add fix suggestions if available
198
- if issue.suggestion:
199
- result["fixes"] = [
200
- {
201
- "description": {"text": issue.suggestion},
202
- }
203
- ]
321
+ fixes = self._build_fixes(issue)
322
+ if fixes:
323
+ result["fixes"] = fixes
324
+
325
+ # Add related locations for affected fields
326
+ related = self._build_related_locations(issue, policy_result.policy_file)
327
+ if related:
328
+ result["relatedLocations"] = related
329
+
330
+ # Add properties with additional metadata
331
+ properties = self._build_properties(issue)
332
+ if properties:
333
+ result["properties"] = properties
204
334
 
205
335
  results.append(result)
206
336
 
207
337
  return results
208
338
 
339
+ def _build_result_message(self, issue: ValidationIssue) -> str:
340
+ """Build a comprehensive result message including context.
341
+
342
+ Args:
343
+ issue: The validation issue
344
+
345
+ Returns:
346
+ Formatted message string
347
+ """
348
+ parts = [issue.message]
349
+
350
+ # Add risk explanation if present
351
+ if issue.risk_explanation:
352
+ parts.append(f"\n\nWhy this matters: {issue.risk_explanation}")
353
+
354
+ # Add affected fields context
355
+ affected = []
356
+ if issue.action:
357
+ affected.append(f"Action: {issue.action}")
358
+ if issue.resource:
359
+ affected.append(f"Resource: {issue.resource}")
360
+ if issue.condition_key:
361
+ affected.append(f"Condition Key: {issue.condition_key}")
362
+
363
+ if affected:
364
+ parts.append(f"\n\nAffected: {', '.join(affected)}")
365
+
366
+ return "".join(parts)
367
+
368
+ def _build_fixes(self, issue: ValidationIssue) -> list[dict[str, Any]]:
369
+ """Build SARIF fix objects from issue suggestions.
370
+
371
+ Args:
372
+ issue: The validation issue
373
+
374
+ Returns:
375
+ List of SARIF fix objects
376
+ """
377
+ fixes = []
378
+
379
+ # Add suggestion as a fix
380
+ if issue.suggestion:
381
+ fix: dict[str, Any] = {"description": {"text": issue.suggestion}}
382
+
383
+ # If we have an example, include it as replacement text
384
+ if issue.example:
385
+ fix["description"]["text"] += f"\n\nExample:\n{issue.example}"
386
+
387
+ fixes.append(fix)
388
+
389
+ # Add remediation steps as a separate fix entry
390
+ if issue.remediation_steps:
391
+ remediation_text = "How to fix:\n" + "\n".join(
392
+ f"{i}. {step}" for i, step in enumerate(issue.remediation_steps, 1)
393
+ )
394
+ fixes.append({"description": {"text": remediation_text}})
395
+
396
+ return fixes
397
+
398
+ def _build_related_locations(
399
+ self, issue: ValidationIssue, policy_file: str
400
+ ) -> list[dict[str, Any]]:
401
+ """Build related locations for affected fields.
402
+
403
+ Args:
404
+ issue: The validation issue
405
+ policy_file: Path to the policy file
406
+
407
+ Returns:
408
+ List of SARIF related location objects
409
+ """
410
+ related = []
411
+
412
+ # Add statement context
413
+ if issue.statement_sid:
414
+ related.append(
415
+ {
416
+ "id": 0,
417
+ "message": {"text": f"Statement: {issue.statement_sid}"},
418
+ "physicalLocation": {
419
+ "artifactLocation": {"uri": policy_file, "uriBaseId": "SRCROOT"},
420
+ "region": {"startLine": issue.line_number or 1},
421
+ },
422
+ }
423
+ )
424
+
425
+ return related
426
+
427
+ def _build_properties(self, issue: ValidationIssue) -> dict[str, Any]:
428
+ """Build SARIF properties with additional metadata.
429
+
430
+ Args:
431
+ issue: The validation issue
432
+
433
+ Returns:
434
+ Dictionary of custom properties
435
+ """
436
+ properties: dict[str, Any] = {}
437
+
438
+ # Add check ID
439
+ if issue.check_id:
440
+ properties["checkId"] = issue.check_id
441
+
442
+ # Add issue type
443
+ properties["issueType"] = issue.issue_type
444
+
445
+ # Add statement info
446
+ properties["statementIndex"] = issue.statement_index
447
+ if issue.statement_sid:
448
+ properties["statementSid"] = issue.statement_sid
449
+
450
+ # Add severity category
451
+ if issue.is_security_severity():
452
+ properties["severityCategory"] = "security"
453
+ else:
454
+ properties["severityCategory"] = "validity"
455
+
456
+ # Add affected fields
457
+ if issue.action:
458
+ properties["action"] = issue.action
459
+ if issue.resource:
460
+ properties["resource"] = issue.resource
461
+ if issue.condition_key:
462
+ properties["conditionKey"] = issue.condition_key
463
+ if issue.field_name:
464
+ properties["fieldName"] = issue.field_name
465
+
466
+ # Add documentation URL
467
+ if issue.documentation_url:
468
+ properties["documentationUrl"] = issue.documentation_url
469
+
470
+ # Add remediation steps as array
471
+ if issue.remediation_steps:
472
+ properties["remediationSteps"] = issue.remediation_steps
473
+
474
+ return properties
475
+
209
476
  def _get_rule_id(self, issue: ValidationIssue) -> str:
210
477
  """Map issue to SARIF rule ID.
211
478
 
212
- Uses the issue_type field directly, converting underscores to hyphens
213
- for SARIF rule ID format. Falls back to heuristic matching for unknown types.
479
+ Uses check_id as the primary identifier (matches dynamically generated rules).
480
+ Falls back to issue_type if check_id is not available.
481
+
482
+ Args:
483
+ issue: The validation issue
484
+
485
+ Returns:
486
+ SARIF rule ID string
214
487
  """
215
- # Map common issue types directly
216
- issue_type_map = {
217
- "invalid_action": "invalid-action",
218
- "invalid_condition_key": "invalid-condition-key",
219
- "invalid_resource": "invalid-resource",
220
- "duplicate_sid": "duplicate-sid",
221
- "overly_permissive": "overly-permissive",
222
- "missing_condition": "missing-condition",
223
- "missing_required_condition": "missing-required-condition",
224
- "invalid_principal": "invalid-principal",
225
- }
488
+ # Prefer check_id as it's more specific and matches the check that raised it
489
+ if issue.check_id:
490
+ return issue.check_id
226
491
 
227
- # Try direct mapping from issue_type
228
- if issue.issue_type in issue_type_map:
229
- return issue_type_map[issue.issue_type]
230
-
231
- # Fallback: heuristic matching based on message
232
- message_lower = issue.message.lower()
233
-
234
- if "action" in message_lower and "not found" in message_lower:
235
- return "invalid-action"
236
- elif "condition key" in message_lower:
237
- return "invalid-condition-key"
238
- elif "duplicate" in message_lower and "sid" in message_lower:
239
- return "duplicate-sid"
240
- elif "wildcard" in message_lower or "overly permissive" in message_lower:
241
- return "overly-permissive"
242
- elif "missing" in message_lower and "condition" in message_lower:
243
- if "required" in message_lower:
244
- return "missing-required-condition"
245
- return "missing-condition"
246
- elif "principal" in message_lower:
247
- return "invalid-principal"
248
- elif "resource" in message_lower or "arn" in message_lower:
249
- return "invalid-resource"
250
- else:
251
- return "general-issue"
492
+ # Fall back to issue_type
493
+ return issue.issue_type