iam-policy-validator 1.5.0__py3-none-any.whl → 1.6.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 (42) hide show
  1. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/METADATA +89 -60
  2. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/RECORD +40 -25
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/__init__.py +9 -3
  5. iam_validator/checks/action_condition_enforcement.py +164 -2
  6. iam_validator/checks/action_resource_matching.py +424 -0
  7. iam_validator/checks/condition_key_validation.py +3 -1
  8. iam_validator/checks/condition_type_mismatch.py +259 -0
  9. iam_validator/checks/mfa_condition_check.py +112 -0
  10. iam_validator/checks/sensitive_action.py +78 -6
  11. iam_validator/checks/set_operator_validation.py +157 -0
  12. iam_validator/checks/utils/sensitive_action_matcher.py +35 -1
  13. iam_validator/commands/cache.py +1 -1
  14. iam_validator/commands/validate.py +44 -11
  15. iam_validator/core/aws_fetcher.py +89 -52
  16. iam_validator/core/check_registry.py +165 -21
  17. iam_validator/core/condition_validators.py +626 -0
  18. iam_validator/core/config/__init__.py +13 -15
  19. iam_validator/core/config/aws_global_conditions.py +160 -0
  20. iam_validator/core/config/category_suggestions.py +104 -0
  21. iam_validator/core/config/condition_requirements.py +5 -385
  22. iam_validator/core/{config_loader.py → config/config_loader.py} +3 -0
  23. iam_validator/core/config/defaults.py +187 -54
  24. iam_validator/core/config/sensitive_actions.py +620 -81
  25. iam_validator/core/models.py +14 -1
  26. iam_validator/core/policy_checks.py +4 -4
  27. iam_validator/core/pr_commenter.py +1 -1
  28. iam_validator/sdk/__init__.py +187 -0
  29. iam_validator/sdk/arn_matching.py +274 -0
  30. iam_validator/sdk/context.py +222 -0
  31. iam_validator/sdk/exceptions.py +48 -0
  32. iam_validator/sdk/helpers.py +177 -0
  33. iam_validator/sdk/policy_utils.py +425 -0
  34. iam_validator/sdk/shortcuts.py +283 -0
  35. iam_validator/utils/__init__.py +31 -0
  36. iam_validator/utils/cache.py +105 -0
  37. iam_validator/utils/regex.py +206 -0
  38. iam_validator/checks/action_resource_constraint.py +0 -151
  39. iam_validator/core/aws_global_conditions.py +0 -137
  40. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/WHEEL +0 -0
  41. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/entry_points.txt +0 -0
  42. {iam_policy_validator-1.5.0.dist-info → iam_policy_validator-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -38,6 +38,31 @@ def _get_default_sensitive_actions() -> frozenset[str]:
38
38
  return _DEFAULT_SENSITIVE_ACTIONS_CACHE
39
39
 
40
40
 
41
+ def get_sensitive_actions_by_categories(categories: list[str] | None = None) -> frozenset[str]:
42
+ """
43
+ Get sensitive actions filtered by categories.
44
+
45
+ Args:
46
+ categories: List of category IDs to include. If None, returns all actions.
47
+ Valid categories: 'credential_exposure', 'data_access',
48
+ 'priv_esc', 'resource_exposure'
49
+
50
+ Returns:
51
+ Frozenset of sensitive actions matching the specified categories
52
+
53
+ Examples:
54
+ >>> # Get all sensitive actions (default behavior)
55
+ >>> all_actions = get_sensitive_actions_by_categories()
56
+
57
+ >>> # Get only privilege escalation actions
58
+ >>> priv_esc = get_sensitive_actions_by_categories(['priv_esc'])
59
+
60
+ >>> # Get credential exposure and data access actions
61
+ >>> sensitive = get_sensitive_actions_by_categories(['credential_exposure', 'data_access'])
62
+ """
63
+ return get_sensitive_actions(categories)
64
+
65
+
41
66
  # Export for backward compatibility
42
67
  DEFAULT_SENSITIVE_ACTIONS = _get_default_sensitive_actions()
43
68
 
@@ -79,7 +104,16 @@ def check_sensitive_actions(
79
104
  - Uses lazy-loaded defaults (only loaded on first use)
80
105
  - O(1) frozenset lookups for action matching
81
106
  """
82
- if default_actions is None:
107
+ # Check if categories are specified in config
108
+ categories = config.config.get("categories")
109
+ if categories is not None:
110
+ # If categories is an empty list, disable the check
111
+ if len(categories) == 0:
112
+ return False, []
113
+ # Get sensitive actions filtered by categories
114
+ default_actions = get_sensitive_actions_by_categories(categories)
115
+ elif default_actions is None:
116
+ # Use all categories if no specific categories configured
83
117
  default_actions = _get_default_sensitive_actions()
84
118
 
85
119
  # Filter out wildcards
@@ -9,7 +9,7 @@ from rich.table import Table
9
9
 
10
10
  from iam_validator.commands.base import Command
11
11
  from iam_validator.core.aws_fetcher import AWSServiceFetcher
12
- from iam_validator.core.config_loader import ConfigLoader
12
+ from iam_validator.core.config.config_loader import ConfigLoader
13
13
 
14
14
  logger = logging.getLogger(__name__)
15
15
  console = Console()
@@ -37,6 +37,10 @@ Examples:
37
37
  # Validate multiple paths (files and directories)
38
38
  iam-validator validate --path policy1.json --path ./policies/ --path ./more-policies/
39
39
 
40
+ # Read policy from stdin
41
+ cat policy.json | iam-validator validate --stdin
42
+ echo '{"Version":"2012-10-17","Statement":[...]}' | iam-validator validate --stdin
43
+
40
44
  # Use custom checks from a directory
41
45
  iam-validator validate --path ./policies/ --custom-checks-dir ./my-checks
42
46
 
@@ -61,15 +65,23 @@ Examples:
61
65
 
62
66
  def add_arguments(self, parser: argparse.ArgumentParser) -> None:
63
67
  """Add validate command arguments."""
64
- parser.add_argument(
68
+ # Create mutually exclusive group for input sources
69
+ input_group = parser.add_mutually_exclusive_group(required=True)
70
+
71
+ input_group.add_argument(
65
72
  "--path",
66
73
  "-p",
67
- required=True,
68
74
  action="append",
69
75
  dest="paths",
70
76
  help="Path to IAM policy file or directory (can be specified multiple times)",
71
77
  )
72
78
 
79
+ input_group.add_argument(
80
+ "--stdin",
81
+ action="store_true",
82
+ help="Read policy from stdin (JSON format)",
83
+ )
84
+
73
85
  parser.add_argument(
74
86
  "--format",
75
87
  "-f",
@@ -198,15 +210,36 @@ Examples:
198
210
 
199
211
  async def _execute_batch(self, args: argparse.Namespace) -> int:
200
212
  """Execute validation by loading all policies at once (original behavior)."""
201
- # Load policies from all specified paths
213
+ # Load policies from all specified paths or stdin
202
214
  loader = PolicyLoader()
203
- policies = loader.load_from_paths(args.paths, recursive=not args.no_recursive)
204
215
 
205
- if not policies:
206
- logging.error(f"No valid IAM policies found in: {', '.join(args.paths)}")
207
- return 1
216
+ if args.stdin:
217
+ # Read from stdin
218
+ import json
219
+ import sys
220
+
221
+ stdin_content = sys.stdin.read()
222
+ if not stdin_content.strip():
223
+ logging.error("No policy data provided on stdin")
224
+ return 1
225
+
226
+ try:
227
+ policy_data = json.loads(stdin_content)
228
+ # Create a synthetic policy entry
229
+ policies = [("stdin", policy_data)]
230
+ logging.info("Loaded policy from stdin")
231
+ except json.JSONDecodeError as e:
232
+ logging.error(f"Invalid JSON from stdin: {e}")
233
+ return 1
234
+ else:
235
+ # Load from paths
236
+ policies = loader.load_from_paths(args.paths, recursive=not args.no_recursive)
237
+
238
+ if not policies:
239
+ logging.error(f"No valid IAM policies found in: {', '.join(args.paths)}")
240
+ return 1
208
241
 
209
- logging.info(f"Loaded {len(policies)} policies from {len(args.paths)} path(s)")
242
+ logging.info(f"Loaded {len(policies)} policies from {len(args.paths)} path(s)")
210
243
 
211
244
  # Validate policies
212
245
  use_registry = not getattr(args, "no_registry", False)
@@ -258,7 +291,7 @@ Examples:
258
291
 
259
292
  # Post to GitHub if configured
260
293
  if args.github_comment or getattr(args, "github_review", False):
261
- from iam_validator.core.config_loader import ConfigLoader
294
+ from iam_validator.core.config.config_loader import ConfigLoader
262
295
  from iam_validator.core.pr_commenter import PRCommenter
263
296
 
264
297
  # Load config to get fail_on_severity setting
@@ -384,7 +417,7 @@ Examples:
384
417
 
385
418
  # Post summary comment to GitHub (if requested and not already posted per-file reviews)
386
419
  if args.github_comment:
387
- from iam_validator.core.config_loader import ConfigLoader
420
+ from iam_validator.core.config.config_loader import ConfigLoader
388
421
  from iam_validator.core.pr_commenter import PRCommenter
389
422
 
390
423
  # Load config to get fail_on_severity setting
@@ -436,7 +469,7 @@ Examples:
436
469
  This provides progressive feedback in PRs as files are processed.
437
470
  """
438
471
  try:
439
- from iam_validator.core.config_loader import ConfigLoader
472
+ from iam_validator.core.config.config_loader import ConfigLoader
440
473
  from iam_validator.core.pr_commenter import PRCommenter
441
474
 
442
475
  async with GitHubIntegration() as github:
@@ -27,7 +27,6 @@ import os
27
27
  import re
28
28
  import sys
29
29
  import time
30
- from collections import OrderedDict
31
30
  from pathlib import Path
32
31
  from typing import Any
33
32
 
@@ -35,57 +34,11 @@ import httpx
35
34
 
36
35
  from iam_validator.core.config import AWS_SERVICE_REFERENCE_BASE_URL
37
36
  from iam_validator.core.models import ServiceDetail, ServiceInfo
37
+ from iam_validator.utils.cache import LRUCache
38
38
 
39
39
  logger = logging.getLogger(__name__)
40
40
 
41
41
 
42
- class LRUCache:
43
- """Thread-safe LRU cache implementation with TTL support."""
44
-
45
- def __init__(self, maxsize: int = 128, ttl: int = 3600):
46
- """Initialize LRU cache.
47
-
48
- Args:
49
- maxsize: Maximum number of items in cache
50
- ttl: Time to live in seconds (default: 1 hour)
51
- """
52
- self.cache: OrderedDict[str, tuple[Any, float]] = OrderedDict()
53
- self.maxsize = maxsize
54
- self.ttl = ttl
55
- self._lock = asyncio.Lock()
56
-
57
- async def get(self, key: str) -> Any | None:
58
- """Get item from cache if not expired."""
59
- async with self._lock:
60
- if key in self.cache:
61
- value, timestamp = self.cache[key]
62
- if time.time() - timestamp < self.ttl:
63
- # Move to end (most recently used)
64
- self.cache.move_to_end(key)
65
- return value
66
- else:
67
- # Expired, remove it
68
- del self.cache[key]
69
- return None
70
-
71
- async def set(self, key: str, value: Any) -> None:
72
- """Set item in cache with current timestamp."""
73
- async with self._lock:
74
- if key in self.cache:
75
- # Move to end if exists
76
- self.cache.move_to_end(key)
77
- elif len(self.cache) >= self.maxsize:
78
- # Remove least recently used
79
- self.cache.popitem(last=False)
80
-
81
- self.cache[key] = (value, time.time())
82
-
83
- async def clear(self) -> None:
84
- """Clear the cache."""
85
- async with self._lock:
86
- self.cache.clear()
87
-
88
-
89
42
  class CompiledPatterns:
90
43
  """Pre-compiled regex patterns for validation."""
91
44
 
@@ -120,7 +73,69 @@ class CompiledPatterns:
120
73
 
121
74
 
122
75
  class AWSServiceFetcher:
123
- """Fetches AWS service information from the AWS service reference API with enhanced performance features."""
76
+ """Fetches AWS service information from the AWS service reference API with enhanced performance features.
77
+
78
+ This class provides a comprehensive interface for retrieving AWS service metadata,
79
+ including actions, resources, and condition keys. It includes multiple layers of
80
+ caching and optimization for high-performance policy validation.
81
+
82
+ Features:
83
+ - Multi-layer caching (memory LRU + disk with TTL)
84
+ - Service pre-fetching for common AWS services
85
+ - Request batching and coalescing
86
+ - Offline mode support with local AWS service files
87
+ - HTTP/2 connection pooling
88
+ - Automatic retry with exponential backoff
89
+
90
+ Example:
91
+ >>> async with AWSServiceFetcher() as fetcher:
92
+ ... # Fetch service list
93
+ ... services = await fetcher.fetch_services()
94
+ ...
95
+ ... # Fetch specific service details
96
+ ... s3_service = await fetcher.fetch_service_by_name("s3")
97
+ ...
98
+ ... # Validate actions
99
+ ... is_valid = await fetcher.validate_action("s3:GetObject", s3_service)
100
+
101
+ Method Organization:
102
+ Lifecycle Management:
103
+ - __init__: Initialize fetcher with configuration
104
+ - __aenter__, __aexit__: Context manager support
105
+
106
+ Caching (Private):
107
+ - _get_cache_directory: Determine cache location
108
+ - _get_cache_path: Generate cache file path
109
+ - _read_from_cache: Read from disk cache
110
+ - _write_to_cache: Write to disk cache
111
+ - clear_caches: Clear all caches
112
+
113
+ HTTP Operations (Private):
114
+ - _make_request: Core HTTP request handler
115
+ - _make_request_with_batching: Request coalescing
116
+ - _prefetch_common_services: Pre-load common services
117
+
118
+ File I/O (Private):
119
+ - _load_services_from_file: Load service list from local file
120
+ - _load_service_from_file: Load service details from local file
121
+
122
+ Public API - Fetching:
123
+ - fetch_services: Get list of all AWS services
124
+ - fetch_service_by_name: Get details for one service
125
+ - fetch_multiple_services: Batch fetch multiple services
126
+
127
+ Public API - Validation:
128
+ - validate_action: Check if action exists in service
129
+ - validate_arn: Validate ARN format
130
+ - validate_condition_key: Check condition key validity
131
+
132
+ Public API - Parsing:
133
+ - parse_action: Split action into service and name
134
+ - _match_wildcard_action: Match wildcard patterns
135
+
136
+ Utilities:
137
+ - get_stats: Get cache statistics
138
+ """
124
139
 
125
140
  BASE_URL = AWS_SERVICE_REFERENCE_BASE_URL
126
141
 
@@ -797,9 +812,15 @@ class AWSServiceFetcher:
797
812
  return True, None
798
813
 
799
814
  async def validate_condition_key(
800
- self, action: str, condition_key: str
815
+ self, action: str, condition_key: str, resources: list[str] | None = None
801
816
  ) -> tuple[bool, str | None, str | None]:
802
- """Validate condition key with optimized caching.
817
+ """
818
+ Validate condition key against action and optionally resource types.
819
+
820
+ Args:
821
+ action: IAM action (e.g., "s3:GetObject")
822
+ condition_key: Condition key to validate (e.g., "s3:prefix")
823
+ resources: Optional list of resource ARNs to validate against
803
824
 
804
825
  Returns:
805
826
  Tuple of (is_valid, error_message, warning_message)
@@ -808,7 +829,9 @@ class AWSServiceFetcher:
808
829
  - warning_message: Warning message if valid but not recommended
809
830
  """
810
831
  try:
811
- from iam_validator.core.aws_global_conditions import get_global_conditions
832
+ from iam_validator.core.config.aws_global_conditions import (
833
+ get_global_conditions,
834
+ )
812
835
 
813
836
  service_prefix, action_name = self.parse_action(action)
814
837
 
@@ -841,6 +864,20 @@ class AWSServiceFetcher:
841
864
  ):
842
865
  return True, None, None
843
866
 
867
+ # Check resource-specific condition keys
868
+ # Get resource types required by this action
869
+ if resources and action_detail.resources:
870
+ for res_req in action_detail.resources:
871
+ resource_name = res_req.get("Name", "")
872
+ if not resource_name:
873
+ continue
874
+
875
+ # Look up resource type definition
876
+ resource_type = service_detail.resources.get(resource_name)
877
+ if resource_type and resource_type.condition_keys:
878
+ if condition_key in resource_type.condition_keys:
879
+ return True, None, None
880
+
844
881
  # If it's a global key but the action has specific condition keys defined,
845
882
  # AWS allows it but the key may not be available in every request context
846
883
  if is_global_key and action_detail.action_condition_keys is not None:
@@ -31,6 +31,112 @@ class CheckConfig:
31
31
  config: dict[str, Any] = field(default_factory=dict) # Check-specific config
32
32
  description: str = ""
33
33
  root_config: dict[str, Any] = field(default_factory=dict) # Full config for cross-check access
34
+ ignore_patterns: list[dict[str, Any]] = field(default_factory=list) # NEW: Ignore patterns
35
+ """
36
+ List of patterns to ignore findings.
37
+
38
+ Each pattern is a dict with optional fields:
39
+ - filepath_regex: Regex to match file path
40
+ - action_matches: Regex to match action name
41
+ - resource_matches: Regex to match resource
42
+ - statement_sid: Exact SID to match (or regex if ends with .*)
43
+ - condition_key_matches: Regex to match condition key
44
+
45
+ Multiple fields in one pattern = AND logic
46
+ Multiple patterns = OR logic (any pattern matches → ignore)
47
+
48
+ Example:
49
+ ignore_patterns:
50
+ - filepath_regex: "test/.*|examples/.*"
51
+ - filepath_regex: "policies/readonly-.*"
52
+ action_matches: ".*:(Get|List|Describe).*"
53
+ - statement_sid: "AllowReadOnlyAccess"
54
+ """
55
+
56
+ def should_ignore(self, issue: ValidationIssue, filepath: str = "") -> bool:
57
+ """
58
+ Check if issue should be ignored based on ignore patterns.
59
+
60
+ Args:
61
+ issue: The validation issue to check
62
+ filepath: Path to the policy file
63
+
64
+ Returns:
65
+ True if the issue should be ignored
66
+ """
67
+ if not self.ignore_patterns:
68
+ return False
69
+
70
+ import re
71
+
72
+ for pattern in self.ignore_patterns:
73
+ if self._matches_pattern(pattern, issue, filepath, re):
74
+ return True
75
+
76
+ return False
77
+
78
+ def _matches_pattern(
79
+ self,
80
+ pattern: dict[str, Any],
81
+ issue: ValidationIssue,
82
+ filepath: str,
83
+ re_module: Any,
84
+ ) -> bool:
85
+ """
86
+ Check if issue matches a single ignore pattern.
87
+
88
+ All fields in pattern must match (AND logic).
89
+
90
+ Args:
91
+ pattern: Pattern dict with optional fields
92
+ issue: ValidationIssue to check
93
+ filepath: Path to policy file
94
+ re_module: re module for regex matching
95
+
96
+ Returns:
97
+ True if all fields in pattern match the issue
98
+ """
99
+ for field_name, regex_pattern in pattern.items():
100
+ actual_value = None
101
+
102
+ if field_name == "filepath_regex":
103
+ actual_value = filepath
104
+ elif field_name == "action_matches":
105
+ actual_value = issue.action or ""
106
+ elif field_name == "resource_matches":
107
+ actual_value = issue.resource or ""
108
+ elif field_name == "statement_sid":
109
+ # For SID, support both exact match and regex
110
+ if isinstance(regex_pattern, str) and "*" in regex_pattern:
111
+ # Treat as regex if contains wildcard
112
+ actual_value = issue.statement_sid or ""
113
+ else:
114
+ # Exact match
115
+ if issue.statement_sid != regex_pattern:
116
+ return False
117
+ continue
118
+ elif field_name == "condition_key_matches":
119
+ actual_value = issue.condition_key or ""
120
+ else:
121
+ # Unknown field, skip
122
+ continue
123
+
124
+ # Check regex match (case-insensitive)
125
+ if actual_value is None:
126
+ return False
127
+
128
+ try:
129
+ if not re_module.search(
130
+ str(regex_pattern),
131
+ str(actual_value),
132
+ re_module.IGNORECASE,
133
+ ):
134
+ return False
135
+ except re_module.error:
136
+ # Invalid regex, don't match
137
+ return False
138
+
139
+ return True # All fields matched
34
140
 
35
141
 
36
142
  class PolicyCheck(ABC):
@@ -264,6 +370,7 @@ class CheckRegistry:
264
370
  statement: Statement,
265
371
  statement_idx: int,
266
372
  fetcher: AWSServiceFetcher,
373
+ filepath: str = "",
267
374
  ) -> list[ValidationIssue]:
268
375
  """
269
376
  Execute all enabled checks in parallel for maximum performance.
@@ -275,9 +382,10 @@ class CheckRegistry:
275
382
  statement: The IAM policy statement to validate
276
383
  statement_idx: Index of the statement in the policy
277
384
  fetcher: AWS service fetcher for API calls
385
+ filepath: Path to the policy file (for ignore_patterns filtering)
278
386
 
279
387
  Returns:
280
- List of all ValidationIssue objects from all checks
388
+ List of all ValidationIssue objects from all checks (filtered by ignore_patterns)
281
389
  """
282
390
  enabled_checks = self.get_enabled_checks()
283
391
 
@@ -291,21 +399,27 @@ class CheckRegistry:
291
399
  config = self.get_config(check.check_id)
292
400
  if config:
293
401
  issues = await check.execute(statement, statement_idx, fetcher, config)
294
- all_issues.extend(issues)
402
+ # Filter issues based on ignore_patterns
403
+ filtered_issues = [
404
+ issue for issue in issues if not config.should_ignore(issue, filepath)
405
+ ]
406
+ all_issues.extend(filtered_issues)
295
407
  return all_issues
296
408
 
297
409
  # Execute all checks in parallel
298
410
  tasks = []
411
+ configs = []
299
412
  for check in enabled_checks:
300
413
  config = self.get_config(check.check_id)
301
414
  if config:
302
415
  task = check.execute(statement, statement_idx, fetcher, config)
303
416
  tasks.append(task)
417
+ configs.append(config)
304
418
 
305
419
  # Wait for all checks to complete
306
420
  results = await asyncio.gather(*tasks, return_exceptions=True)
307
421
 
308
- # Collect all issues, handling any exceptions
422
+ # Collect all issues, handling any exceptions and applying ignore_patterns
309
423
  all_issues = []
310
424
  for idx, result in enumerate(results):
311
425
  if isinstance(result, Exception):
@@ -313,7 +427,12 @@ class CheckRegistry:
313
427
  check = enabled_checks[idx]
314
428
  print(f"Warning: Check '{check.check_id}' failed: {result}")
315
429
  elif isinstance(result, list):
316
- all_issues.extend(result)
430
+ config = configs[idx]
431
+ # Filter issues based on ignore_patterns
432
+ filtered_issues = [
433
+ issue for issue in result if not config.should_ignore(issue, filepath)
434
+ ]
435
+ all_issues.extend(filtered_issues)
317
436
 
318
437
  return all_issues
319
438
 
@@ -442,22 +561,47 @@ def create_default_registry(
442
561
  # Import and register built-in checks
443
562
  from iam_validator import checks
444
563
 
445
- registry.register(checks.ActionValidationCheck())
446
- registry.register(checks.ConditionKeyValidationCheck())
447
- registry.register(checks.ResourceValidationCheck())
448
- registry.register(checks.WildcardActionCheck())
449
- registry.register(checks.WildcardResourceCheck())
450
- registry.register(checks.FullWildcardCheck())
451
- registry.register(checks.ServiceWildcardCheck())
452
- registry.register(checks.SensitiveActionCheck())
453
- registry.register(checks.ActionConditionEnforcementCheck())
454
- registry.register(checks.ActionResourceConstraintCheck())
455
- registry.register(checks.SidUniquenessCheck())
456
- registry.register(checks.PolicySizeCheck())
457
- registry.register(checks.PrincipalValidationCheck())
458
-
459
- # Note: SID uniqueness check is registered above but its actual execution
460
- # happens at the policy level in _validate_policy_with_registry() since it
461
- # needs to see all statements together to find duplicates
564
+ # 1. POLICY STRUCTURE (Checks that examine the entire policy, not individual statements)
565
+ registry.register(
566
+ checks.SidUniquenessCheck()
567
+ ) # Policy-level: Duplicate SID detection across statements
568
+ registry.register(checks.PolicySizeCheck()) # Policy-level: Size limit validation
569
+
570
+ # 2. IAM VALIDITY (AWS syntax validation - must pass before deeper checks)
571
+ registry.register(checks.ActionValidationCheck()) # Validate actions against AWS API
572
+ registry.register(checks.ResourceValidationCheck()) # Validate resource ARNs
573
+ registry.register(checks.ConditionKeyValidationCheck()) # Validate condition keys
574
+
575
+ # 3. TYPE VALIDATION (Condition operator type checking)
576
+ registry.register(checks.ConditionTypeMismatchCheck()) # Operator-value type compatibility
577
+ registry.register(checks.SetOperatorValidationCheck()) # ForAllValues/ForAnyValue usage
578
+
579
+ # 4. RESOURCE MATCHING (Action-resource relationship validation)
580
+ registry.register(
581
+ checks.ActionResourceMatchingCheck()
582
+ ) # ARN type matching and resource constraints
583
+
584
+ # 5. SECURITY - WILDCARDS (Security best practices for wildcards)
585
+ registry.register(checks.WildcardActionCheck()) # Wildcard action detection
586
+ registry.register(checks.WildcardResourceCheck()) # Wildcard resource detection
587
+ registry.register(checks.FullWildcardCheck()) # Full wildcard (*) detection
588
+ registry.register(checks.ServiceWildcardCheck()) # Service-level wildcard detection
589
+
590
+ # 6. SECURITY - ADVANCED (Sensitive actions and condition enforcement)
591
+ registry.register(
592
+ checks.SensitiveActionCheck()
593
+ ) # Policy-level: Privilege escalation detection (all_of across statements)
594
+ registry.register(
595
+ checks.ActionConditionEnforcementCheck()
596
+ ) # Statement + Policy-level: Condition enforcement (any_of/all_of/none_of)
597
+ registry.register(checks.MFAConditionCheck()) # MFA anti-pattern detection
598
+
599
+ # 7. PRINCIPAL VALIDATION (Resource policy specific)
600
+ registry.register(
601
+ checks.PrincipalValidationCheck()
602
+ ) # Principal validation (resource policies)
603
+
604
+ # Note: policy_type_validation is a standalone function (not a class-based check)
605
+ # and is called separately in the validation flow
462
606
 
463
607
  return registry