iam-policy-validator 1.7.2__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 (38) hide show
  1. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/METADATA +22 -6
  2. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/RECORD +38 -35
  3. iam_validator/__version__.py +1 -1
  4. iam_validator/checks/__init__.py +5 -3
  5. iam_validator/checks/action_condition_enforcement.py +61 -23
  6. iam_validator/checks/action_resource_matching.py +6 -2
  7. iam_validator/checks/action_validation.py +1 -1
  8. iam_validator/checks/condition_key_validation.py +1 -1
  9. iam_validator/checks/condition_type_mismatch.py +6 -6
  10. iam_validator/checks/policy_structure.py +577 -0
  11. iam_validator/checks/policy_type_validation.py +48 -32
  12. iam_validator/checks/principal_validation.py +65 -133
  13. iam_validator/checks/resource_validation.py +8 -8
  14. iam_validator/checks/sensitive_action.py +7 -3
  15. iam_validator/checks/service_wildcard.py +2 -2
  16. iam_validator/checks/set_operator_validation.py +11 -11
  17. iam_validator/checks/sid_uniqueness.py +8 -4
  18. iam_validator/checks/trust_policy_validation.py +512 -0
  19. iam_validator/checks/utils/sensitive_action_matcher.py +26 -26
  20. iam_validator/checks/utils/wildcard_expansion.py +1 -1
  21. iam_validator/checks/wildcard_action.py +3 -1
  22. iam_validator/checks/wildcard_resource.py +3 -1
  23. iam_validator/commands/validate.py +6 -12
  24. iam_validator/core/__init__.py +1 -2
  25. iam_validator/core/access_analyzer.py +1 -1
  26. iam_validator/core/access_analyzer_report.py +2 -2
  27. iam_validator/core/aws_fetcher.py +45 -43
  28. iam_validator/core/check_registry.py +83 -79
  29. iam_validator/core/config/condition_requirements.py +69 -17
  30. iam_validator/core/config/defaults.py +58 -52
  31. iam_validator/core/config/service_principals.py +40 -3
  32. iam_validator/core/ignore_patterns.py +297 -0
  33. iam_validator/core/models.py +15 -5
  34. iam_validator/core/policy_checks.py +31 -472
  35. iam_validator/core/policy_loader.py +27 -4
  36. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/WHEEL +0 -0
  37. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/entry_points.txt +0 -0
  38. {iam_policy_validator-1.7.2.dist-info → iam_policy_validator-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,15 +5,13 @@ configurations, supporting exact matches, regex patterns, and any_of/all_of logi
5
5
 
6
6
  Performance optimizations:
7
7
  - Uses frozenset for O(1) lookups
8
- - LRU cache for compiled regex patterns
8
+ - Centralized LRU cache for compiled regex patterns (from ignore_patterns module)
9
9
  - Lazy loading of default actions from modular data structure
10
10
  """
11
11
 
12
- import re
13
- from functools import lru_cache
14
-
15
12
  from iam_validator.core.check_registry import CheckConfig
16
13
  from iam_validator.core.config.sensitive_actions import get_sensitive_actions
14
+ from iam_validator.core.ignore_patterns import compile_pattern
17
15
 
18
16
  # Lazy-loaded default set of sensitive actions
19
17
  # This will be loaded only when first accessed
@@ -37,7 +35,9 @@ def _get_default_sensitive_actions() -> frozenset[str]:
37
35
  return _DEFAULT_SENSITIVE_ACTIONS_CACHE
38
36
 
39
37
 
40
- def get_sensitive_actions_by_categories(categories: list[str] | None = None) -> frozenset[str]:
38
+ def get_sensitive_actions_by_categories(
39
+ categories: list[str] | None = None,
40
+ ) -> frozenset[str]:
41
41
  """
42
42
  Get sensitive actions filtered by categories.
43
43
 
@@ -66,25 +66,10 @@ def get_sensitive_actions_by_categories(categories: list[str] | None = None) ->
66
66
  DEFAULT_SENSITIVE_ACTIONS = _get_default_sensitive_actions()
67
67
 
68
68
 
69
- # Global regex pattern cache for performance
70
- @lru_cache(maxsize=256)
71
- def compile_pattern(pattern: str) -> re.Pattern[str] | None:
72
- """Compile and cache regex patterns.
73
-
74
- Args:
75
- pattern: Regex pattern string
76
-
77
- Returns:
78
- Compiled pattern or None if invalid
79
- """
80
- try:
81
- return re.compile(pattern)
82
- except re.error:
83
- return None
84
-
85
-
86
69
  def check_sensitive_actions(
87
- actions: list[str], config: CheckConfig, default_actions: frozenset[str] | None = None
70
+ actions: list[str],
71
+ config: CheckConfig,
72
+ default_actions: frozenset[str] | None = None,
88
73
  ) -> tuple[bool, list[str]]:
89
74
  """
90
75
  Check if actions match sensitive action criteria with any_of/all_of support.
@@ -115,6 +100,10 @@ def check_sensitive_actions(
115
100
  # Use all categories if no specific categories configured
116
101
  default_actions = _get_default_sensitive_actions()
117
102
 
103
+ # Apply ignore_patterns to filter out default actions
104
+ # This allows users to exclude specific actions from the default 490 actions
105
+ default_actions = config.filter_actions(default_actions)
106
+
118
107
  # Filter out wildcards
119
108
  filtered_actions = [a for a in actions if a != "*"]
120
109
  if not filtered_actions:
@@ -141,6 +130,17 @@ def check_sensitive_actions(
141
130
  matched_set = set(actions_matched) | set(patterns_matched)
142
131
  matched_actions = list(matched_set)
143
132
 
133
+ # Apply ignore_patterns to filter the final matched actions
134
+ # This ensures ignore_patterns work for:
135
+ # 1. Default actions (490 actions from Python modules)
136
+ # 2. Custom sensitive_actions configuration
137
+ # 3. Custom sensitive_action_patterns configuration
138
+ if matched_actions and config.ignore_patterns:
139
+ filtered_matched = config.filter_actions(frozenset(matched_actions))
140
+ matched_actions = list(filtered_matched)
141
+ # Update is_sensitive based on filtered results
142
+ is_sensitive = len(matched_actions) > 0
143
+
144
144
  return is_sensitive, matched_actions
145
145
 
146
146
 
@@ -243,7 +243,7 @@ def check_patterns_config(actions: list[str], config) -> tuple[bool, list[str]]:
243
243
  # Each item can be a string pattern, or a dict with any_of/all_of
244
244
  if isinstance(item, str):
245
245
  # Simple string pattern - check if any action matches
246
- # Use cached compiled pattern
246
+ # Use cached compiled pattern from centralized ignore_patterns module
247
247
  compiled = compile_pattern(item)
248
248
  if compiled:
249
249
  for action in actions:
@@ -262,7 +262,7 @@ def check_patterns_config(actions: list[str], config) -> tuple[bool, list[str]]:
262
262
  # any_of: at least one action must match at least one pattern
263
263
  if "any_of" in config:
264
264
  matched = set()
265
- # Pre-compile all patterns
265
+ # Pre-compile all patterns using centralized cache
266
266
  compiled_patterns = [compile_pattern(p) for p in config["any_of"]]
267
267
 
268
268
  for action in actions:
@@ -274,7 +274,7 @@ def check_patterns_config(actions: list[str], config) -> tuple[bool, list[str]]:
274
274
 
275
275
  # all_of: at least one action must match ALL patterns
276
276
  if "all_of" in config:
277
- # Pre-compile all patterns
277
+ # Pre-compile all patterns using centralized cache
278
278
  compiled_patterns = [compile_pattern(p) for p in config["all_of"]]
279
279
  # Filter out invalid patterns
280
280
  compiled_patterns = [p for p in compiled_patterns if p]
@@ -72,7 +72,7 @@ async def expand_wildcard_actions(actions: list[str], fetcher: AWSServiceFetcher
72
72
  available_actions = list(service_detail.actions.keys())
73
73
 
74
74
  # Match wildcard pattern against available actions
75
- _, matched_actions = fetcher._match_wildcard_action(action_name, available_actions)
75
+ _, matched_actions = fetcher.match_wildcard_action(action_name, available_actions)
76
76
 
77
77
  # Add expanded actions with service prefix
78
78
  for matched_action in matched_actions:
@@ -38,7 +38,9 @@ class WildcardActionCheck(PolicyCheck):
38
38
 
39
39
  # Check for wildcard action (Action: "*")
40
40
  if "*" in actions:
41
- message = config.config.get("message", "Statement allows all actions (*)")
41
+ message = config.config.get(
42
+ "message", 'Statement allows all actions `"*"` (wildcard action).'
43
+ )
42
44
  suggestion = config.config.get(
43
45
  "suggestion",
44
46
  "Replace wildcard with specific actions needed for your use case",
@@ -62,7 +62,9 @@ class WildcardResourceCheck(PolicyCheck):
62
62
  return issues
63
63
 
64
64
  # Flag the issue if actions are not all allowed or no allowed_wildcards configured
65
- message = config.config.get("message", "Statement applies to all resources (*)")
65
+ message = config.config.get(
66
+ "message", 'Statement applies to all resources `"*"` (wildcard resource).'
67
+ )
66
68
  suggestion = config.config.get(
67
69
  "suggestion", "Replace wildcard with specific resource ARNs"
68
70
  )
@@ -114,13 +114,17 @@ Examples:
114
114
  choices=[
115
115
  "IDENTITY_POLICY",
116
116
  "RESOURCE_POLICY",
117
+ "TRUST_POLICY",
117
118
  "SERVICE_CONTROL_POLICY",
118
119
  "RESOURCE_CONTROL_POLICY",
119
120
  ],
120
121
  default="IDENTITY_POLICY",
121
122
  help="Type of IAM policy being validated (default: IDENTITY_POLICY). "
122
- "Enables policy-type-specific validation (e.g., requiring Principal for resource policies, "
123
- "strict RCP requirements for resource control policies)",
123
+ "IDENTITY_POLICY: Attached to users/groups/roles | "
124
+ "RESOURCE_POLICY: S3/SNS/SQS policies | "
125
+ "TRUST_POLICY: Role assumption policies | "
126
+ "SERVICE_CONTROL_POLICY: AWS Orgs SCPs | "
127
+ "RESOURCE_CONTROL_POLICY: AWS Orgs RCPs",
124
128
  )
125
129
 
126
130
  parser.add_argument(
@@ -159,12 +163,6 @@ Examples:
159
163
  help="Path to directory containing custom checks for auto-discovery",
160
164
  )
161
165
 
162
- parser.add_argument(
163
- "--no-registry",
164
- action="store_true",
165
- help="Use legacy validation (disable check registry system)",
166
- )
167
-
168
166
  parser.add_argument(
169
167
  "--stream",
170
168
  action="store_true",
@@ -242,14 +240,12 @@ Examples:
242
240
  logging.info(f"Loaded {len(policies)} policies from {len(args.paths)} path(s)")
243
241
 
244
242
  # Validate policies
245
- use_registry = not getattr(args, "no_registry", False)
246
243
  config_path = getattr(args, "config", None)
247
244
  custom_checks_dir = getattr(args, "custom_checks_dir", None)
248
245
  policy_type = cast(PolicyType, getattr(args, "policy_type", "IDENTITY_POLICY"))
249
246
  results = await validate_policies(
250
247
  policies,
251
248
  config_path=config_path,
252
- use_registry=use_registry,
253
249
  custom_checks_dir=custom_checks_dir,
254
250
  policy_type=policy_type,
255
251
  )
@@ -329,7 +325,6 @@ Examples:
329
325
  """
330
326
  loader = PolicyLoader()
331
327
  generator = ReportGenerator()
332
- use_registry = not getattr(args, "no_registry", False)
333
328
  config_path = getattr(args, "config", None)
334
329
  custom_checks_dir = getattr(args, "custom_checks_dir", None)
335
330
  policy_type = cast(PolicyType, getattr(args, "policy_type", "IDENTITY_POLICY"))
@@ -354,7 +349,6 @@ Examples:
354
349
  results = await validate_policies(
355
350
  [(file_path, policy)],
356
351
  config_path=config_path,
357
- use_registry=use_registry,
358
352
  custom_checks_dir=custom_checks_dir,
359
353
  policy_type=policy_type,
360
354
  )
@@ -1,13 +1,12 @@
1
1
  """Core validation modules."""
2
2
 
3
3
  from iam_validator.core.aws_fetcher import AWSServiceFetcher
4
- from iam_validator.core.policy_checks import PolicyValidator, validate_policies
4
+ from iam_validator.core.policy_checks import validate_policies
5
5
  from iam_validator.core.policy_loader import PolicyLoader
6
6
  from iam_validator.core.report import ReportGenerator
7
7
 
8
8
  __all__ = [
9
9
  "AWSServiceFetcher",
10
- "PolicyValidator",
11
10
  "validate_policies",
12
11
  "PolicyLoader",
13
12
  "ReportGenerator",
@@ -577,7 +577,7 @@ class AccessAnalyzerValidator:
577
577
  )
578
578
  results.append(result)
579
579
 
580
- except Exception as e:
580
+ except Exception as e: # pylint: disable=broad-exception-caught
581
581
  self.logger.error(f"Failed to validate {policy_file}: {e}")
582
582
  result = AccessAnalyzerResult(
583
583
  policy_file=policy_file,
@@ -259,7 +259,7 @@ class AccessAnalyzerReportFormatter:
259
259
  file_path: Path to save JSON report
260
260
  """
261
261
  json_content = self.generate_json_report(report)
262
- with open(file_path, "w") as f:
262
+ with open(file_path, "w", encoding="utf-8") as f:
263
263
  f.write(json_content)
264
264
 
265
265
  def generate_markdown_report(
@@ -636,5 +636,5 @@ class AccessAnalyzerReportFormatter:
636
636
  file_path: Path to save Markdown report
637
637
  """
638
638
  markdown_content = self.generate_markdown_report(report)
639
- with open(file_path, "w") as f:
639
+ with open(file_path, "w", encoding="utf-8") as f:
640
640
  f.write(markdown_content)
@@ -160,7 +160,7 @@ class AWSServiceFetcher:
160
160
 
161
161
  Public API - Parsing:
162
162
  - parse_action: Split action into service and name
163
- - _match_wildcard_action: Match wildcard patterns
163
+ - match_wildcard_action: Match wildcard patterns
164
164
 
165
165
  Utilities:
166
166
  - get_stats: Get cache statistics
@@ -352,7 +352,7 @@ class AWSServiceFetcher:
352
352
  try:
353
353
  await self.fetch_service_by_name(name)
354
354
  self._prefetched_services.add(name)
355
- except Exception as e:
355
+ except Exception as e: # pylint: disable=broad-exception-caught
356
356
  logger.warning(f"Failed to prefetch service {name}: {e}")
357
357
 
358
358
  # Fetch in batches to avoid overwhelming the API
@@ -400,7 +400,7 @@ class AWSServiceFetcher:
400
400
  logger.debug(f"Disk cache hit for {url}")
401
401
  return data
402
402
 
403
- except Exception as e:
403
+ except Exception as e: # pylint: disable=broad-exception-caught
404
404
  logger.warning(f"Failed to read cache for {url}: {e}")
405
405
  return None
406
406
 
@@ -415,7 +415,7 @@ class AWSServiceFetcher:
415
415
  with open(cache_path, "w", encoding="utf-8") as f:
416
416
  json.dump(data, f, indent=2)
417
417
  logger.debug(f"Written to disk cache: {url}")
418
- except Exception as e:
418
+ except Exception as e: # pylint: disable=broad-exception-caught
419
419
  logger.warning(f"Failed to write cache for {url}: {e}")
420
420
 
421
421
  async def _make_request_with_batching(self, url: str) -> Any:
@@ -459,7 +459,7 @@ class AWSServiceFetcher:
459
459
  if not future.done():
460
460
  future.set_result(result)
461
461
  return result
462
- except Exception as e:
462
+ except Exception as e: # pylint: disable=broad-exception-caught
463
463
  if not future.done():
464
464
  future.set_exception(e)
465
465
  raise
@@ -504,21 +504,23 @@ class AWSServiceFetcher:
504
504
 
505
505
  return data
506
506
 
507
- except Exception as json_error:
507
+ except Exception as json_error: # pylint: disable=broad-exception-caught
508
508
  logger.error(f"Failed to parse response as JSON: {json_error}")
509
- raise ValueError(f"Invalid JSON response from {url}: {json_error}")
509
+ raise ValueError(
510
+ f"Invalid JSON response from {url}: {json_error}"
511
+ ) from json_error
510
512
 
511
513
  except httpx.HTTPStatusError as e:
512
514
  logger.error(f"HTTP error {e.response.status_code} for {url}")
513
515
  if e.response.status_code == 404:
514
- raise ValueError(f"Service not found: {url}")
516
+ raise ValueError(f"Service not found: {url}") from e
515
517
  last_exception = e
516
518
 
517
519
  except httpx.RequestError as e:
518
520
  logger.error(f"Request error for {url}: {e}")
519
521
  last_exception = e
520
522
 
521
- except Exception as e:
523
+ except Exception as e: # pylint: disable=broad-exception-caught
522
524
  logger.error(f"Unexpected error for {url}: {e}")
523
525
  last_exception = e
524
526
 
@@ -547,7 +549,7 @@ class AWSServiceFetcher:
547
549
  raise FileNotFoundError(f"_services.json not found in {self.aws_services_dir}")
548
550
 
549
551
  try:
550
- with open(services_file) as f:
552
+ with open(services_file, encoding="utf-8") as f:
551
553
  data = json.load(f)
552
554
 
553
555
  if not isinstance(data, list):
@@ -565,7 +567,7 @@ class AWSServiceFetcher:
565
567
  return services
566
568
 
567
569
  except json.JSONDecodeError as e:
568
- raise ValueError(f"Invalid JSON in services.json: {e}")
570
+ raise ValueError(f"Invalid JSON in services.json: {e}") from e
569
571
 
570
572
  def _load_service_from_file(self, service_name: str) -> ServiceDetail:
571
573
  """Load service detail from local JSON file.
@@ -591,7 +593,7 @@ class AWSServiceFetcher:
591
593
  raise FileNotFoundError(f"Service file not found: {service_file}")
592
594
 
593
595
  try:
594
- with open(service_file) as f:
596
+ with open(service_file, encoding="utf-8") as f:
595
597
  data = json.load(f)
596
598
 
597
599
  service_detail = ServiceDetail.model_validate(data)
@@ -599,7 +601,7 @@ class AWSServiceFetcher:
599
601
  return service_detail
600
602
 
601
603
  except json.JSONDecodeError as e:
602
- raise ValueError(f"Invalid JSON in {service_file}: {e}")
604
+ raise ValueError(f"Invalid JSON in {service_file}: {e}") from e
603
605
 
604
606
  async def fetch_services(self) -> list[ServiceInfo]:
605
607
  """Fetch list of AWS services with caching.
@@ -677,7 +679,9 @@ class AWSServiceFetcher:
677
679
  return service_detail
678
680
  except FileNotFoundError:
679
681
  pass
680
- raise ValueError(f"Service `{service_name}` not found in {self.aws_services_dir}")
682
+ raise ValueError(
683
+ f"Service `{service_name}` not found in {self.aws_services_dir}"
684
+ ) from FileNotFoundError
681
685
 
682
686
  # Fetch service list and find URL from API
683
687
  services = await self.fetch_services()
@@ -704,7 +708,7 @@ class AWSServiceFetcher:
704
708
  try:
705
709
  detail = await self.fetch_service_by_name(name)
706
710
  return name, detail
707
- except Exception as e:
711
+ except Exception as e: # pylint: disable=broad-exception-caught
708
712
  logger.error(f"Failed to fetch service {name}: {e}")
709
713
  raise
710
714
 
@@ -717,7 +721,7 @@ class AWSServiceFetcher:
717
721
  if isinstance(result, Exception):
718
722
  logger.error(f"Failed to fetch service {service_names[i]}: {result}")
719
723
  raise result
720
- elif isinstance(result, tuple):
724
+ if isinstance(result, tuple):
721
725
  name, detail = result
722
726
  services[name] = detail
723
727
 
@@ -731,7 +735,7 @@ class AWSServiceFetcher:
731
735
 
732
736
  return match.group("service").lower(), match.group("action")
733
737
 
734
- def _match_wildcard_action(self, pattern: str, actions: list[str]) -> tuple[bool, list[str]]:
738
+ def match_wildcard_action(self, pattern: str, actions: list[str]) -> tuple[bool, list[str]]:
735
739
  """Match wildcard pattern against list of actions.
736
740
 
737
741
  Args:
@@ -774,8 +778,7 @@ class AWSServiceFetcher:
774
778
  # Just verify service exists
775
779
  await self.fetch_service_by_name(service_prefix)
776
780
  return True, None, True
777
- else:
778
- return False, "Wildcard actions are not allowed", True
781
+ return False, "Wildcard actions are not allowed", True
779
782
 
780
783
  # Fetch service details (will use cache)
781
784
  service_detail = await self.fetch_service_by_name(service_prefix)
@@ -786,7 +789,7 @@ class AWSServiceFetcher:
786
789
  if not allow_wildcards:
787
790
  return False, "Wildcard actions are not allowed", True
788
791
 
789
- has_matches, matched_actions = self._match_wildcard_action(
792
+ has_matches, matched_actions = self.match_wildcard_action(
790
793
  action_name, available_actions
791
794
  )
792
795
 
@@ -799,33 +802,32 @@ class AWSServiceFetcher:
799
802
  examples += f", ... ({match_count - 5} more)"
800
803
 
801
804
  return True, None, True
802
- else:
803
- # Wildcard doesn't match any actions
804
- return (
805
- False,
806
- f"Action pattern '{action_name}' does not match any actions in service '{service_prefix}'",
807
- True,
808
- )
805
+ # Wildcard doesn't match any actions
806
+ return (
807
+ False,
808
+ f"Action pattern `{action_name}` does not match any actions in service `{service_prefix}`",
809
+ True,
810
+ )
809
811
 
810
812
  # Check if exact action exists (case-insensitive)
811
813
  action_exists = any(a.lower() == action_name.lower() for a in available_actions)
812
814
 
813
815
  if action_exists:
814
816
  return True, None, False
815
- else:
816
- # Suggest similar actions
817
- similar = [a for a in available_actions if action_name.lower() in a.lower()][:3]
818
817
 
819
- suggestion = f" Did you mean: {', '.join(similar)}?" if similar else ""
820
- return (
821
- False,
822
- f"Action '{action_name}' not found in service '{service_prefix}'.{suggestion}",
823
- False,
824
- )
818
+ # Suggest similar actions
819
+ similar = [f"`{a}`" for a in available_actions if action_name.lower() in a.lower()][:3]
820
+
821
+ suggestion = f" Did you mean: {', '.join(similar)}?" if similar else ""
822
+ return (
823
+ False,
824
+ f"Action `{action_name}` not found in service `{service_prefix}`.{suggestion}",
825
+ False,
826
+ )
825
827
 
826
828
  except ValueError as e:
827
829
  return False, str(e), False
828
- except Exception as e:
830
+ except Exception as e: # pylint: disable=broad-exception-caught
829
831
  logger.error(f"Error validating action {action}: {e}")
830
832
  return False, f"Failed to validate action: {str(e)}", False
831
833
 
@@ -859,7 +861,7 @@ class AWSServiceFetcher:
859
861
  - suggestion: Detailed suggestion with valid keys (shown in collapsible section)
860
862
  """
861
863
  try:
862
- from iam_validator.core.config.aws_global_conditions import (
864
+ from iam_validator.core.config.aws_global_conditions import ( # pylint: disable=import-outside-toplevel
863
865
  get_global_conditions,
864
866
  )
865
867
 
@@ -911,11 +913,11 @@ class AWSServiceFetcher:
911
913
  # AWS allows it but the key may not be available in every request context
912
914
  if is_global_key and action_detail.action_condition_keys is not None:
913
915
  warning_msg = (
914
- f"Global condition key '{condition_key}' is used with action '{action}'. "
916
+ f"Global condition key `{condition_key}` is used with action `{action}`. "
915
917
  f"While global condition keys can be used across all AWS services, "
916
918
  f"the key may not be available in every request context. "
917
- f"Verify that '{condition_key}' is available for this specific action's request context. "
918
- f"Consider using '*IfExists' operators (e.g., StringEqualsIfExists) if the key might be missing."
919
+ f"Verify that `{condition_key}` is available for this specific action's request context. "
920
+ f"Consider using `*IfExists` operators (e.g., `StringEqualsIfExists`) if the key might be missing."
919
921
  )
920
922
  return ConditionKeyValidationResult(is_valid=True, warning_message=warning_msg)
921
923
 
@@ -1000,7 +1002,7 @@ class AWSServiceFetcher:
1000
1002
  suggestion=suggestion,
1001
1003
  )
1002
1004
 
1003
- except Exception as e:
1005
+ except Exception as e: # pylint: disable=broad-exception-caught
1004
1006
  logger.error(f"Error validating condition key {condition_key} for {action}: {e}")
1005
1007
  return ConditionKeyValidationResult(
1006
1008
  is_valid=False,
@@ -1017,7 +1019,7 @@ class AWSServiceFetcher:
1017
1019
  for cache_file in self._cache_dir.glob("*.json"):
1018
1020
  try:
1019
1021
  cache_file.unlink()
1020
- except Exception as e:
1022
+ except Exception as e: # pylint: disable=broad-exception-caught
1021
1023
  logger.warning(f"Failed to delete cache file {cache_file}: {e}")
1022
1024
 
1023
1025
  logger.info("Cleared all caches")