iam-policy-validator 1.7.1__py3-none-any.whl → 1.8.0__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 (51) hide show
  1. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -7
  2. iam_policy_validator-1.8.0.dist-info/RECORD +87 -0
  3. iam_validator/__version__.py +4 -2
  4. iam_validator/checks/__init__.py +5 -3
  5. iam_validator/checks/action_condition_enforcement.py +81 -36
  6. iam_validator/checks/action_resource_matching.py +75 -37
  7. iam_validator/checks/action_validation.py +1 -1
  8. iam_validator/checks/condition_key_validation.py +7 -7
  9. iam_validator/checks/condition_type_mismatch.py +10 -8
  10. iam_validator/checks/full_wildcard.py +2 -8
  11. iam_validator/checks/mfa_condition_check.py +8 -8
  12. iam_validator/checks/policy_structure.py +577 -0
  13. iam_validator/checks/policy_type_validation.py +48 -32
  14. iam_validator/checks/principal_validation.py +86 -150
  15. iam_validator/checks/resource_validation.py +8 -8
  16. iam_validator/checks/sensitive_action.py +9 -11
  17. iam_validator/checks/service_wildcard.py +4 -10
  18. iam_validator/checks/set_operator_validation.py +11 -11
  19. iam_validator/checks/sid_uniqueness.py +8 -4
  20. iam_validator/checks/trust_policy_validation.py +512 -0
  21. iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
  22. iam_validator/checks/utils/wildcard_expansion.py +1 -1
  23. iam_validator/checks/wildcard_action.py +5 -9
  24. iam_validator/checks/wildcard_resource.py +5 -9
  25. iam_validator/commands/validate.py +8 -14
  26. iam_validator/core/__init__.py +1 -2
  27. iam_validator/core/access_analyzer.py +1 -1
  28. iam_validator/core/access_analyzer_report.py +2 -2
  29. iam_validator/core/aws_fetcher.py +159 -64
  30. iam_validator/core/check_registry.py +83 -79
  31. iam_validator/core/config/condition_requirements.py +69 -17
  32. iam_validator/core/config/config_loader.py +1 -2
  33. iam_validator/core/config/defaults.py +74 -59
  34. iam_validator/core/config/service_principals.py +40 -3
  35. iam_validator/core/constants.py +57 -0
  36. iam_validator/core/formatters/console.py +10 -1
  37. iam_validator/core/formatters/csv.py +2 -1
  38. iam_validator/core/formatters/enhanced.py +42 -8
  39. iam_validator/core/formatters/markdown.py +2 -1
  40. iam_validator/core/ignore_patterns.py +297 -0
  41. iam_validator/core/models.py +35 -10
  42. iam_validator/core/policy_checks.py +34 -474
  43. iam_validator/core/policy_loader.py +98 -18
  44. iam_validator/core/report.py +65 -24
  45. iam_validator/integrations/github_integration.py +4 -5
  46. iam_validator/utils/__init__.py +4 -0
  47. iam_validator/utils/terminal.py +22 -0
  48. iam_policy_validator-1.7.1.dist-info/RECORD +0 -83
  49. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
  50. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
  51. {iam_policy_validator-1.7.1.dist-info → iam_policy_validator-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,15 @@
1
1
  """
2
- Default service principals for resource policy validation.
2
+ Service principals utilities for resource policy validation.
3
3
 
4
- These are AWS service principals that are commonly used and considered safe
5
- in resource-based policies (S3 bucket policies, SNS topic policies, etc.).
4
+ This module provides:
5
+ - Default list of common AWS service principals
6
+ - Utility to check if a principal is any AWS service principal
7
+ - Functions to categorize service principals by type
8
+
9
+ Configuration:
10
+ - Use "*" in allowed_service_principals to allow ALL AWS service principals
11
+ - Use explicit list to restrict to specific services only
12
+ - AWS service principals end with .amazonaws.com or .amazonaws.com.cn
6
13
  """
7
14
 
8
15
  from typing import Final
@@ -58,6 +65,36 @@ def is_allowed_service_principal(principal: str) -> bool:
58
65
  return principal in DEFAULT_SERVICE_PRINCIPALS
59
66
 
60
67
 
68
+ def is_aws_service_principal(principal: str) -> bool:
69
+ """
70
+ Check if a principal is an AWS service principal (any AWS service).
71
+
72
+ This checks if the principal matches the AWS service principal pattern.
73
+ AWS service principals typically end with ".amazonaws.com" or ".amazonaws.com.cn"
74
+
75
+ Args:
76
+ principal: Principal to check (e.g., "lambda.amazonaws.com", "s3.amazonaws.com.cn")
77
+
78
+ Returns:
79
+ True if principal matches AWS service principal pattern
80
+
81
+ Examples:
82
+ >>> is_aws_service_principal("lambda.amazonaws.com")
83
+ True
84
+ >>> is_aws_service_principal("s3.amazonaws.com.cn")
85
+ True
86
+ >>> is_aws_service_principal("arn:aws:iam::123456789012:root")
87
+ False
88
+ >>> is_aws_service_principal("*")
89
+ False
90
+ """
91
+ if not isinstance(principal, str):
92
+ return False
93
+
94
+ # AWS service principals end with .amazonaws.com or .amazonaws.com.cn
95
+ return principal.endswith(".amazonaws.com") or principal.endswith(".amazonaws.com.cn")
96
+
97
+
61
98
  def get_service_principals_by_category() -> dict[str, tuple[str, ...]]:
62
99
  """
63
100
  Get service principals organized by service category.
@@ -62,6 +62,21 @@ DEFAULT_CONFIG_FILENAMES = [
62
62
  ".iam-validator.yml",
63
63
  ]
64
64
 
65
+ # ============================================================================
66
+ # Severity Levels
67
+ # ============================================================================
68
+ # Severity level groupings for filtering and categorization
69
+ # Used across formatters and report generation
70
+
71
+ # High severity issues that typically fail validation
72
+ HIGH_SEVERITY_LEVELS = ("error", "critical", "high")
73
+
74
+ # Medium severity issues (warnings)
75
+ MEDIUM_SEVERITY_LEVELS = ("warning", "medium")
76
+
77
+ # Low severity issues (informational)
78
+ LOW_SEVERITY_LEVELS = ("info", "low")
79
+
65
80
  # ============================================================================
66
81
  # GitHub Integration
67
82
  # ============================================================================
@@ -72,3 +87,45 @@ BOT_IDENTIFIER = "🤖 IAM Policy Validator"
72
87
  # HTML comment markers for identifying bot-generated content (for cleanup/updates)
73
88
  SUMMARY_IDENTIFIER = "<!-- iam-policy-validator-summary -->"
74
89
  REVIEW_IDENTIFIER = "<!-- iam-policy-validator-review -->"
90
+
91
+ # GitHub comment size limits
92
+ # GitHub's actual limit is 65536 characters, but we use a smaller limit for safety
93
+ GITHUB_MAX_COMMENT_LENGTH = 65000 # Maximum single comment length
94
+ GITHUB_COMMENT_SPLIT_LIMIT = 60000 # Target size when splitting into multiple parts
95
+
96
+ # Comment size estimation parameters (used for multi-part comment splitting)
97
+ COMMENT_BASE_OVERHEAD_CHARS = 2000 # Base overhead for headers/footers
98
+ COMMENT_CHARS_PER_ISSUE_ESTIMATE = 500 # Average characters per issue
99
+ COMMENT_CONTINUATION_OVERHEAD_CHARS = 200 # Overhead for continuation markers
100
+ FORMATTING_SAFETY_BUFFER = 100 # Safety buffer for formatting calculations
101
+
102
+ # ============================================================================
103
+ # Console Display Settings
104
+ # ============================================================================
105
+
106
+ # Panel width for formatted console output
107
+ CONSOLE_PANEL_WIDTH = 100
108
+
109
+ # Rich console color styles
110
+ CONSOLE_HEADER_COLOR = "bright_blue"
111
+
112
+ # ============================================================================
113
+ # Cache and Timeout Settings
114
+ # ============================================================================
115
+
116
+ # Cache TTL (Time To Live) - 7 days
117
+ DEFAULT_CACHE_TTL_HOURS = 168 # 7 days in hours
118
+ DEFAULT_CACHE_TTL_SECONDS = 604800 # 7 days in seconds (168 * 3600)
119
+
120
+ # HTTP request timeout in seconds
121
+ DEFAULT_HTTP_TIMEOUT_SECONDS = 30.0
122
+
123
+ # Time conversion constants
124
+ SECONDS_PER_HOUR = 3600
125
+
126
+ # ============================================================================
127
+ # AWS Documentation URLs
128
+ # ============================================================================
129
+
130
+ # AWS Service Authorization Reference (for finding valid actions, resources, and condition keys)
131
+ AWS_SERVICE_AUTH_REF_URL = "https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html"
@@ -39,8 +39,17 @@ class ConsoleFormatter(OutputFormatter):
39
39
  color = kwargs.get("color", True)
40
40
 
41
41
  # Capture the output from print_console_report
42
+ from iam_validator.utils import get_terminal_width
43
+
42
44
  string_buffer = StringIO()
43
- console = Console(file=string_buffer, force_terminal=color, width=120)
45
+ # Get terminal width for proper table column spacing
46
+ terminal_width = get_terminal_width()
47
+ console = Console(
48
+ file=string_buffer,
49
+ force_terminal=color,
50
+ width=terminal_width,
51
+ legacy_windows=False,
52
+ )
44
53
 
45
54
  # Create a generator instance with our custom console
46
55
  generator = ReportGenerator()
@@ -4,6 +4,7 @@ import csv
4
4
  import io
5
5
  from typing import Any
6
6
 
7
+ from iam_validator.core import constants
7
8
  from iam_validator.core.formatters.base import OutputFormatter
8
9
  from iam_validator.core.models import ValidationReport
9
10
 
@@ -58,7 +59,7 @@ class CSVFormatter(OutputFormatter):
58
59
  1
59
60
  for r in report.results
60
61
  for i in r.issues
61
- if i.severity in ("error", "critical", "high")
62
+ if i.severity in constants.HIGH_SEVERITY_LEVELS
62
63
  )
63
64
  warnings = sum(
64
65
  1 for r in report.results for i in r.issues if i.severity in ("warning", "medium")
@@ -10,6 +10,7 @@ from rich.text import Text
10
10
  from rich.tree import Tree
11
11
 
12
12
  from iam_validator.__version__ import __version__
13
+ from iam_validator.core import constants
13
14
  from iam_validator.core.formatters.base import OutputFormatter
14
15
  from iam_validator.core.models import PolicyValidationResult, ValidationReport
15
16
 
@@ -50,8 +51,14 @@ class EnhancedFormatter(OutputFormatter):
50
51
  show_severity_breakdown = kwargs.get("show_severity_breakdown", True)
51
52
 
52
53
  # Use StringIO to capture Rich console output
54
+ from iam_validator.utils import get_terminal_width
55
+
53
56
  string_buffer = StringIO()
54
- console = Console(file=string_buffer, force_terminal=color, width=120)
57
+ # Get terminal width for proper text wrapping
58
+ terminal_width = get_terminal_width()
59
+ console = Console(
60
+ file=string_buffer, force_terminal=color, width=terminal_width, legacy_windows=False
61
+ )
55
62
 
56
63
  # Header with title
57
64
  console.print()
@@ -60,7 +67,14 @@ class EnhancedFormatter(OutputFormatter):
60
67
  style="bold cyan",
61
68
  justify="center",
62
69
  )
63
- console.print(Panel(title, border_style="bright_blue", padding=(1, 0)))
70
+ console.print(
71
+ Panel(
72
+ title,
73
+ border_style=constants.CONSOLE_HEADER_COLOR,
74
+ padding=(1, 0),
75
+ width=constants.CONSOLE_PANEL_WIDTH,
76
+ )
77
+ )
64
78
  console.print()
65
79
 
66
80
  # Executive Summary with progress bars (optional)
@@ -73,7 +87,7 @@ class EnhancedFormatter(OutputFormatter):
73
87
  self._print_severity_breakdown(console, report)
74
88
 
75
89
  console.print()
76
- console.print(Rule(title="[bold]Detailed Results", style="bright_blue"))
90
+ console.print(Rule(title="[bold]Detailed Results", style=constants.CONSOLE_HEADER_COLOR))
77
91
  console.print()
78
92
 
79
93
  # Detailed results using tree structure
@@ -140,8 +154,9 @@ class EnhancedFormatter(OutputFormatter):
140
154
  Panel(
141
155
  metrics_table,
142
156
  title="📊 Executive Summary",
143
- border_style="bright_blue",
157
+ border_style=constants.CONSOLE_HEADER_COLOR,
144
158
  padding=(1, 2),
159
+ width=constants.CONSOLE_PANEL_WIDTH,
145
160
  )
146
161
  )
147
162
 
@@ -225,7 +240,12 @@ class EnhancedFormatter(OutputFormatter):
225
240
  )
226
241
 
227
242
  console.print(
228
- Panel(severity_table, title="🎯 Issue Severity Breakdown", border_style="bright_blue")
243
+ Panel(
244
+ severity_table,
245
+ title="🎯 Issue Severity Breakdown",
246
+ border_style=constants.CONSOLE_HEADER_COLOR,
247
+ width=constants.CONSOLE_PANEL_WIDTH,
248
+ )
229
249
  )
230
250
 
231
251
  def _format_policy_result_modern(
@@ -247,7 +267,7 @@ class EnhancedFormatter(OutputFormatter):
247
267
  elif result.is_valid and result.issues:
248
268
  # Valid IAM policy but has security findings
249
269
  # Check severity to determine the appropriate status
250
- has_critical = any(i.severity in ("error", "critical", "high") for i in result.issues)
270
+ has_critical = any(i.severity in constants.HIGH_SEVERITY_LEVELS for i in result.issues)
251
271
  if has_critical:
252
272
  icon = "⚠️"
253
273
  color = "red"
@@ -386,6 +406,13 @@ class EnhancedFormatter(OutputFormatter):
386
406
  suggestion_text.append(issue.suggestion, style="italic yellow")
387
407
  msg_node.add(suggestion_text)
388
408
 
409
+ # Example (if present, show with indentation)
410
+ if issue.example:
411
+ msg_node.add(Text("Example:", style="bold cyan"))
412
+ # Show example code with syntax highlighting
413
+ example_text = Text(issue.example, style="dim")
414
+ msg_node.add(example_text)
415
+
389
416
  def _print_final_status(self, console: Console, report: ValidationReport) -> None:
390
417
  """Print final status panel."""
391
418
  if report.invalid_policies == 0 and report.total_issues == 0:
@@ -400,7 +427,7 @@ class EnhancedFormatter(OutputFormatter):
400
427
  # Valid IAM policies but may have security findings
401
428
  # Check if there are critical/high security issues
402
429
  has_critical = any(
403
- i.severity in ("error", "critical", "high")
430
+ i.severity in constants.HIGH_SEVERITY_LEVELS
404
431
  for r in report.results
405
432
  for i in r.issues
406
433
  )
@@ -437,4 +464,11 @@ class EnhancedFormatter(OutputFormatter):
437
464
  final_text.append("\n\n")
438
465
  final_text.append(message)
439
466
 
440
- console.print(Panel(final_text, border_style=border_color, padding=(1, 2)))
467
+ console.print(
468
+ Panel(
469
+ final_text,
470
+ border_style=border_color,
471
+ padding=(1, 2),
472
+ width=constants.CONSOLE_PANEL_WIDTH,
473
+ )
474
+ )
@@ -1,5 +1,6 @@
1
1
  """Markdown formatter - placeholder for existing functionality."""
2
2
 
3
+ from iam_validator.core import constants
3
4
  from iam_validator.core.formatters.base import OutputFormatter
4
5
  from iam_validator.core.models import ValidationReport
5
6
 
@@ -34,7 +35,7 @@ class MarkdownFormatter(OutputFormatter):
34
35
  1
35
36
  for r in report.results
36
37
  for i in r.issues
37
- if i.severity in ("error", "critical", "high")
38
+ if i.severity in constants.HIGH_SEVERITY_LEVELS
38
39
  )
39
40
  warnings = sum(
40
41
  1 for r in report.results for i in r.issues if i.severity in ("warning", "medium")
@@ -0,0 +1,297 @@
1
+ """
2
+ Centralized ignore patterns utility with caching and performance optimization.
3
+
4
+ This module provides high-performance pattern matching for ignore_patterns across
5
+ all checks. Uses LRU caching and compiled regex patterns for optimal performance.
6
+ """
7
+
8
+ import re
9
+ from functools import lru_cache
10
+ from typing import Any
11
+
12
+ from iam_validator.core.models import ValidationIssue
13
+
14
+
15
+ # Global regex pattern cache (shared across all checks for maximum efficiency)
16
+ @lru_cache(maxsize=512)
17
+ def compile_pattern(pattern: str) -> re.Pattern[str] | None:
18
+ """
19
+ Compile and cache regex patterns.
20
+
21
+ Uses LRU cache to avoid recompiling the same patterns across multiple calls.
22
+ This is critical for performance when the same patterns are used repeatedly.
23
+
24
+ This is a public API function used across multiple modules for consistent
25
+ regex caching.
26
+
27
+ Args:
28
+ pattern: Regex pattern string
29
+
30
+ Returns:
31
+ Compiled pattern or None if invalid
32
+
33
+ Performance:
34
+ - First call: O(n) compile time
35
+ - Cached calls: O(1) lookup
36
+ """
37
+ try:
38
+ return re.compile(str(pattern), re.IGNORECASE)
39
+ except re.error:
40
+ return None
41
+
42
+
43
+ class IgnorePatternMatcher:
44
+ """
45
+ High-performance pattern matcher for ignore_patterns.
46
+
47
+ Features:
48
+ - Cached compiled regex patterns (LRU cache)
49
+ - Support for new (simple) and old (verbose) field names
50
+ - Efficient filtering with early exit optimization
51
+ - Field-specific validation logic
52
+
53
+ Thread-safe: Yes (regex compilation is cached globally)
54
+ """
55
+
56
+ # Supported field name mappings (new -> old for backward compatibility)
57
+ FIELD_ALIASES = {
58
+ "filepath": "filepath_regex",
59
+ "action": "action_matches",
60
+ "resource": "resource_matches",
61
+ "sid": "statement_sid",
62
+ "condition_key": "condition_key_matches",
63
+ }
64
+
65
+ @staticmethod
66
+ def should_ignore_issue(
67
+ issue: ValidationIssue,
68
+ filepath: str,
69
+ ignore_patterns: list[dict[str, Any]],
70
+ ) -> bool:
71
+ """
72
+ Check if a ValidationIssue should be ignored based on patterns.
73
+
74
+ Pattern Matching Logic:
75
+ - Multiple fields in ONE pattern = AND logic (all must match)
76
+ - Multiple patterns = OR logic (any pattern matches → ignore)
77
+
78
+ Args:
79
+ issue: The validation issue to check
80
+ filepath: Path to the policy file
81
+ ignore_patterns: List of pattern dictionaries
82
+
83
+ Returns:
84
+ True if the issue should be ignored
85
+
86
+ Performance:
87
+ - Early exit on first match (OR logic)
88
+ - Cached regex compilation
89
+ - O(p * f) where p=patterns, f=fields per pattern
90
+ """
91
+ if not ignore_patterns:
92
+ return False
93
+
94
+ for pattern in ignore_patterns:
95
+ if IgnorePatternMatcher._matches_pattern(pattern, issue, filepath):
96
+ return True # Early exit on first match
97
+
98
+ return False
99
+
100
+ @staticmethod
101
+ def filter_actions(
102
+ actions: frozenset[str],
103
+ ignore_patterns: list[dict[str, Any]],
104
+ ) -> frozenset[str]:
105
+ """
106
+ Filter actions based on action ignore patterns.
107
+
108
+ Only considers patterns that contain an "action" or "action_matches" field.
109
+ This is optimized for the sensitive_action check which needs to filter
110
+ actions before creating ValidationIssues.
111
+
112
+ Supports both single action patterns and lists:
113
+ - action: "s3:.*" # Single regex pattern
114
+ - action: ["s3:GetObject", "s3:PutObject"] # List of patterns
115
+
116
+ Args:
117
+ actions: Set of actions to filter
118
+ ignore_patterns: List of pattern dictionaries
119
+
120
+ Returns:
121
+ Filtered set of actions (actions matching patterns removed)
122
+
123
+ Performance:
124
+ - Extracts action patterns once: O(p) where p=patterns
125
+ - Filters with cached regex: O(a * p) where a=actions, p=patterns
126
+ - Early exit per action when match found
127
+ """
128
+ if not ignore_patterns:
129
+ return actions
130
+
131
+ # Extract action patterns once (cache-friendly)
132
+ action_patterns = []
133
+ for pattern in ignore_patterns:
134
+ # Support both new and old field names
135
+ action_regex = pattern.get("action") or pattern.get("action_matches")
136
+ if action_regex:
137
+ # Support both single string and list of strings
138
+ if isinstance(action_regex, list):
139
+ action_patterns.extend(action_regex)
140
+ else:
141
+ action_patterns.append(action_regex)
142
+
143
+ if not action_patterns:
144
+ return actions
145
+
146
+ # Filter actions with compiled patterns (cached)
147
+ filtered = set()
148
+ for action in actions:
149
+ should_keep = True
150
+ for pattern_str in action_patterns:
151
+ compiled = compile_pattern(pattern_str)
152
+ if compiled and compiled.search(str(action)):
153
+ should_keep = False
154
+ break # Early exit on first match
155
+
156
+ if should_keep:
157
+ filtered.add(action)
158
+
159
+ return frozenset(filtered)
160
+
161
+ @staticmethod
162
+ def _matches_pattern(
163
+ pattern: dict[str, Any],
164
+ issue: ValidationIssue,
165
+ filepath: str,
166
+ ) -> bool:
167
+ """
168
+ Check if issue matches a single ignore pattern.
169
+
170
+ All fields in pattern must match (AND logic).
171
+ For list-based fields (like action), ANY match from the list counts (OR logic).
172
+
173
+ Args:
174
+ pattern: Pattern dict with optional fields
175
+ issue: ValidationIssue to check
176
+ filepath: Path to policy file
177
+
178
+ Returns:
179
+ True if all fields in pattern match the issue
180
+
181
+ Performance:
182
+ - Early exit on first non-match (AND logic)
183
+ - Uses cached compiled patterns
184
+ """
185
+ for field_name, regex_pattern in pattern.items():
186
+ # Get actual value from issue based on field name
187
+ actual_value = IgnorePatternMatcher._get_field_value(field_name, issue, filepath)
188
+
189
+ # Handle special case: SID with exact match (no regex)
190
+ if field_name in ("sid", "statement_sid"):
191
+ # Support both single string and list of strings
192
+ if isinstance(regex_pattern, list):
193
+ # List of SIDs - exact match or regex
194
+ matched = False
195
+ for single_sid in regex_pattern:
196
+ if isinstance(single_sid, str) and "*" not in single_sid:
197
+ # Exact match
198
+ if issue.statement_sid == single_sid:
199
+ matched = True
200
+ break
201
+ else:
202
+ # Regex match
203
+ compiled = compile_pattern(str(single_sid))
204
+ if compiled and compiled.search(str(issue.statement_sid or "")):
205
+ matched = True
206
+ break
207
+ if not matched:
208
+ return False
209
+ continue
210
+ elif isinstance(regex_pattern, str) and "*" not in regex_pattern:
211
+ # Single SID - exact match (not a regex)
212
+ if issue.statement_sid != regex_pattern:
213
+ return False # Early exit on non-match
214
+ continue
215
+
216
+ # Regex match for all other cases
217
+ if actual_value is None:
218
+ return False # Early exit on missing value
219
+
220
+ # Support list of patterns (OR logic - any match succeeds)
221
+ if isinstance(regex_pattern, list):
222
+ matched = False
223
+ for single_pattern in regex_pattern:
224
+ compiled = compile_pattern(str(single_pattern))
225
+ if compiled and compiled.search(str(actual_value)):
226
+ matched = True
227
+ break # Found a match in the list
228
+ if not matched:
229
+ return False # None of the patterns matched
230
+ else:
231
+ # Single pattern
232
+ compiled = compile_pattern(str(regex_pattern))
233
+ if not compiled or not compiled.search(str(actual_value)):
234
+ return False # Early exit on non-match
235
+
236
+ return True # All fields matched
237
+
238
+ @staticmethod
239
+ def _get_field_value(
240
+ field_name: str,
241
+ issue: ValidationIssue,
242
+ filepath: str,
243
+ ) -> str | None:
244
+ """
245
+ Extract field value from issue or filepath.
246
+
247
+ Supports both new (simple) and old (verbose) field names for
248
+ backward compatibility.
249
+
250
+ Args:
251
+ field_name: Name of the field to extract
252
+ issue: ValidationIssue to extract from
253
+ filepath: Policy file path
254
+
255
+ Returns:
256
+ Field value as string, or None if field not recognized
257
+ """
258
+ # Normalize field name (support both old and new names)
259
+ if field_name in ("filepath", "filepath_regex"):
260
+ return filepath
261
+ elif field_name in ("action", "action_matches"):
262
+ return issue.action or ""
263
+ elif field_name in ("resource", "resource_matches"):
264
+ return issue.resource or ""
265
+ elif field_name in ("sid", "statement_sid"):
266
+ return issue.statement_sid or ""
267
+ elif field_name in ("condition_key", "condition_key_matches"):
268
+ return issue.condition_key or ""
269
+ else:
270
+ # Unknown field - skip (don't fail)
271
+ return None
272
+
273
+
274
+ # Convenience functions for backward compatibility
275
+ def should_ignore_issue(
276
+ issue: ValidationIssue,
277
+ filepath: str,
278
+ ignore_patterns: list[dict[str, Any]],
279
+ ) -> bool:
280
+ """
281
+ Convenience function for checking if an issue should be ignored.
282
+
283
+ See IgnorePatternMatcher.should_ignore_issue() for details.
284
+ """
285
+ return IgnorePatternMatcher.should_ignore_issue(issue, filepath, ignore_patterns)
286
+
287
+
288
+ def filter_actions(
289
+ actions: frozenset[str],
290
+ ignore_patterns: list[dict[str, Any]],
291
+ ) -> frozenset[str]:
292
+ """
293
+ Convenience function for filtering actions.
294
+
295
+ See IgnorePatternMatcher.filter_actions() for details.
296
+ """
297
+ return IgnorePatternMatcher.filter_actions(actions, ignore_patterns)