iam-policy-validator 1.14.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 (106) hide show
  1. iam_policy_validator-1.14.0.dist-info/METADATA +782 -0
  2. iam_policy_validator-1.14.0.dist-info/RECORD +106 -0
  3. iam_policy_validator-1.14.0.dist-info/WHEEL +4 -0
  4. iam_policy_validator-1.14.0.dist-info/entry_points.txt +2 -0
  5. iam_policy_validator-1.14.0.dist-info/licenses/LICENSE +21 -0
  6. iam_validator/__init__.py +27 -0
  7. iam_validator/__main__.py +11 -0
  8. iam_validator/__version__.py +9 -0
  9. iam_validator/checks/__init__.py +45 -0
  10. iam_validator/checks/action_condition_enforcement.py +1442 -0
  11. iam_validator/checks/action_resource_matching.py +472 -0
  12. iam_validator/checks/action_validation.py +67 -0
  13. iam_validator/checks/condition_key_validation.py +88 -0
  14. iam_validator/checks/condition_type_mismatch.py +257 -0
  15. iam_validator/checks/full_wildcard.py +62 -0
  16. iam_validator/checks/mfa_condition_check.py +105 -0
  17. iam_validator/checks/policy_size.py +114 -0
  18. iam_validator/checks/policy_structure.py +556 -0
  19. iam_validator/checks/policy_type_validation.py +331 -0
  20. iam_validator/checks/principal_validation.py +708 -0
  21. iam_validator/checks/resource_validation.py +135 -0
  22. iam_validator/checks/sensitive_action.py +438 -0
  23. iam_validator/checks/service_wildcard.py +98 -0
  24. iam_validator/checks/set_operator_validation.py +153 -0
  25. iam_validator/checks/sid_uniqueness.py +146 -0
  26. iam_validator/checks/trust_policy_validation.py +509 -0
  27. iam_validator/checks/utils/__init__.py +17 -0
  28. iam_validator/checks/utils/action_parser.py +149 -0
  29. iam_validator/checks/utils/policy_level_checks.py +190 -0
  30. iam_validator/checks/utils/sensitive_action_matcher.py +293 -0
  31. iam_validator/checks/utils/wildcard_expansion.py +86 -0
  32. iam_validator/checks/wildcard_action.py +58 -0
  33. iam_validator/checks/wildcard_resource.py +374 -0
  34. iam_validator/commands/__init__.py +31 -0
  35. iam_validator/commands/analyze.py +549 -0
  36. iam_validator/commands/base.py +48 -0
  37. iam_validator/commands/cache.py +393 -0
  38. iam_validator/commands/completion.py +471 -0
  39. iam_validator/commands/download_services.py +255 -0
  40. iam_validator/commands/post_to_pr.py +86 -0
  41. iam_validator/commands/query.py +485 -0
  42. iam_validator/commands/validate.py +830 -0
  43. iam_validator/core/__init__.py +13 -0
  44. iam_validator/core/access_analyzer.py +671 -0
  45. iam_validator/core/access_analyzer_report.py +640 -0
  46. iam_validator/core/aws_fetcher.py +29 -0
  47. iam_validator/core/aws_service/__init__.py +21 -0
  48. iam_validator/core/aws_service/cache.py +108 -0
  49. iam_validator/core/aws_service/client.py +205 -0
  50. iam_validator/core/aws_service/fetcher.py +641 -0
  51. iam_validator/core/aws_service/parsers.py +149 -0
  52. iam_validator/core/aws_service/patterns.py +51 -0
  53. iam_validator/core/aws_service/storage.py +291 -0
  54. iam_validator/core/aws_service/validators.py +380 -0
  55. iam_validator/core/check_registry.py +679 -0
  56. iam_validator/core/cli.py +134 -0
  57. iam_validator/core/codeowners.py +245 -0
  58. iam_validator/core/condition_validators.py +626 -0
  59. iam_validator/core/config/__init__.py +81 -0
  60. iam_validator/core/config/aws_api.py +35 -0
  61. iam_validator/core/config/aws_global_conditions.py +160 -0
  62. iam_validator/core/config/category_suggestions.py +181 -0
  63. iam_validator/core/config/check_documentation.py +390 -0
  64. iam_validator/core/config/condition_requirements.py +258 -0
  65. iam_validator/core/config/config_loader.py +670 -0
  66. iam_validator/core/config/defaults.py +739 -0
  67. iam_validator/core/config/principal_requirements.py +421 -0
  68. iam_validator/core/config/sensitive_actions.py +672 -0
  69. iam_validator/core/config/service_principals.py +132 -0
  70. iam_validator/core/config/wildcards.py +127 -0
  71. iam_validator/core/constants.py +149 -0
  72. iam_validator/core/diff_parser.py +325 -0
  73. iam_validator/core/finding_fingerprint.py +131 -0
  74. iam_validator/core/formatters/__init__.py +27 -0
  75. iam_validator/core/formatters/base.py +147 -0
  76. iam_validator/core/formatters/console.py +68 -0
  77. iam_validator/core/formatters/csv.py +171 -0
  78. iam_validator/core/formatters/enhanced.py +481 -0
  79. iam_validator/core/formatters/html.py +672 -0
  80. iam_validator/core/formatters/json.py +33 -0
  81. iam_validator/core/formatters/markdown.py +64 -0
  82. iam_validator/core/formatters/sarif.py +251 -0
  83. iam_validator/core/ignore_patterns.py +297 -0
  84. iam_validator/core/ignore_processor.py +309 -0
  85. iam_validator/core/ignored_findings.py +400 -0
  86. iam_validator/core/label_manager.py +197 -0
  87. iam_validator/core/models.py +404 -0
  88. iam_validator/core/policy_checks.py +220 -0
  89. iam_validator/core/policy_loader.py +785 -0
  90. iam_validator/core/pr_commenter.py +780 -0
  91. iam_validator/core/report.py +942 -0
  92. iam_validator/integrations/__init__.py +28 -0
  93. iam_validator/integrations/github_integration.py +1821 -0
  94. iam_validator/integrations/ms_teams.py +442 -0
  95. iam_validator/sdk/__init__.py +220 -0
  96. iam_validator/sdk/arn_matching.py +382 -0
  97. iam_validator/sdk/context.py +222 -0
  98. iam_validator/sdk/exceptions.py +48 -0
  99. iam_validator/sdk/helpers.py +177 -0
  100. iam_validator/sdk/policy_utils.py +451 -0
  101. iam_validator/sdk/query_utils.py +454 -0
  102. iam_validator/sdk/shortcuts.py +283 -0
  103. iam_validator/utils/__init__.py +35 -0
  104. iam_validator/utils/cache.py +105 -0
  105. iam_validator/utils/regex.py +205 -0
  106. iam_validator/utils/terminal.py +22 -0
@@ -0,0 +1,257 @@
1
+ """Condition Type Mismatch Check.
2
+
3
+ Validates that condition operators match the expected types for condition keys and values.
4
+ """
5
+
6
+ from typing import ClassVar
7
+
8
+ from iam_validator.core.aws_service import AWSServiceFetcher
9
+ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
10
+ from iam_validator.core.condition_validators import (
11
+ normalize_operator,
12
+ translate_type,
13
+ validate_value_for_type,
14
+ )
15
+ from iam_validator.core.models import Statement, ValidationIssue
16
+
17
+
18
+ class ConditionTypeMismatchCheck(PolicyCheck):
19
+ """Check for type mismatches between operators, keys, and values."""
20
+
21
+ check_id: ClassVar[str] = "condition_type_mismatch"
22
+ description: ClassVar[str] = (
23
+ "Validates condition operator types match key types and value formats"
24
+ )
25
+ default_severity: ClassVar[str] = "error"
26
+
27
+ async def execute(
28
+ self,
29
+ statement: Statement,
30
+ statement_idx: int,
31
+ fetcher: AWSServiceFetcher,
32
+ config: CheckConfig,
33
+ ) -> list[ValidationIssue]:
34
+ """
35
+ Execute the condition type mismatch check.
36
+
37
+ Validates:
38
+ 1. Operator type matches condition key type
39
+ 2. Condition values match the expected type format
40
+
41
+ Args:
42
+ statement: The IAM statement to check
43
+ statement_idx: Index of this statement in the policy
44
+ fetcher: AWS service fetcher for looking up condition key types
45
+ config: Check configuration
46
+
47
+ Returns:
48
+ List of validation issues found
49
+ """
50
+ issues = []
51
+
52
+ # Only check statements with conditions
53
+ if not statement.condition:
54
+ return issues
55
+
56
+ # Skip Null operator - it's special and doesn't need type validation
57
+ # (Null just checks if a key exists or doesn't exist)
58
+ skip_operators = {"Null"}
59
+
60
+ statement_sid = statement.sid
61
+ line_number = statement.line_number
62
+ actions = statement.get_actions()
63
+ resources = statement.get_resources()
64
+
65
+ # Check each condition operator and its keys/values
66
+ for operator, conditions in statement.condition.items():
67
+ # Normalize the operator and get its expected type
68
+ base_operator, operator_type, _set_prefix = normalize_operator(operator)
69
+
70
+ if operator_type is None:
71
+ # Unknown operator - this will be caught by another check
72
+ continue
73
+
74
+ if base_operator in skip_operators:
75
+ continue
76
+
77
+ # Check each condition key
78
+ for condition_key, condition_values in conditions.items():
79
+ # Normalize values to a list
80
+ values = (
81
+ condition_values if isinstance(condition_values, list) else [condition_values]
82
+ )
83
+
84
+ # Get the expected type for this condition key
85
+ key_type = await self._get_condition_key_type(
86
+ fetcher, condition_key, actions, resources
87
+ )
88
+
89
+ if key_type is None:
90
+ # Unknown condition key - will be caught by condition_key_validation check
91
+ continue
92
+
93
+ # Normalize the key type
94
+ key_type = translate_type(key_type)
95
+ operator_type = translate_type(operator_type)
96
+
97
+ # Special case: String operators with ARN types (usable but not recommended)
98
+ if operator_type == "String" and key_type == "ARN":
99
+ issues.append(
100
+ ValidationIssue(
101
+ severity="warning",
102
+ message=(
103
+ f"Type mismatch (usable but not recommended): Operator `{operator}` expects "
104
+ f"`{operator_type}` values, but condition key `{condition_key}` is type `{key_type}`. "
105
+ f"Consider using an ARN-specific operator like `ArnEquals` or `ArnLike` instead."
106
+ ),
107
+ statement_sid=statement_sid,
108
+ statement_index=statement_idx,
109
+ issue_type="type_mismatch_usable",
110
+ line_number=line_number,
111
+ field_name="condition",
112
+ )
113
+ )
114
+ # Check if operator type matches key type
115
+ elif not self._types_compatible(operator_type, key_type):
116
+ issues.append(
117
+ ValidationIssue(
118
+ severity=self.get_severity(config),
119
+ message=(
120
+ f"Type mismatch: Operator `{operator}` expects `{operator_type}` values, "
121
+ f"but condition key `{condition_key}` is type `{key_type}`."
122
+ ),
123
+ statement_sid=statement_sid,
124
+ statement_index=statement_idx,
125
+ issue_type="type_mismatch",
126
+ condition_key=condition_key,
127
+ line_number=line_number,
128
+ field_name="condition",
129
+ )
130
+ )
131
+
132
+ # Validate that the values match the expected type format
133
+ is_valid, error_msg = validate_value_for_type(key_type, values)
134
+ if not is_valid:
135
+ issues.append(
136
+ ValidationIssue(
137
+ severity=self.get_severity(config),
138
+ message=(
139
+ f"Invalid value format for condition key `{condition_key}`: {error_msg}"
140
+ ),
141
+ statement_sid=statement_sid,
142
+ statement_index=statement_idx,
143
+ issue_type="invalid_value_format",
144
+ condition_key=condition_key,
145
+ line_number=line_number,
146
+ field_name="condition",
147
+ )
148
+ )
149
+
150
+ return issues
151
+
152
+ async def _get_condition_key_type(
153
+ self,
154
+ fetcher: AWSServiceFetcher,
155
+ condition_key: str,
156
+ actions: list[str],
157
+ resources: list[str],
158
+ ) -> str | None:
159
+ """
160
+ Get the expected type for a condition key by checking global keys and service definitions.
161
+
162
+ Args:
163
+ fetcher: AWS service fetcher
164
+ condition_key: The condition key to look up
165
+ actions: List of actions from the statement
166
+ resources: List of resources from the statement
167
+
168
+ Returns:
169
+ Type string or None if not found
170
+ """
171
+ from iam_validator.core.config.aws_global_conditions import ( # pylint: disable=import-outside-toplevel
172
+ get_global_conditions,
173
+ )
174
+
175
+ # Check if it's a global condition key
176
+ global_conditions = get_global_conditions()
177
+ key_type = global_conditions.get_key_type(condition_key)
178
+ if key_type:
179
+ return key_type
180
+
181
+ # Check service-specific and action-specific condition keys
182
+ for action in actions:
183
+ if action == "*":
184
+ continue
185
+
186
+ try:
187
+ service_prefix, action_name = fetcher.parse_action(action)
188
+ service_detail = await fetcher.fetch_service_by_name(service_prefix)
189
+
190
+ # Check service-level condition keys
191
+ if condition_key in service_detail.condition_keys:
192
+ condition_key_obj = service_detail.condition_keys[condition_key]
193
+ if condition_key_obj.types:
194
+ return condition_key_obj.types[0]
195
+
196
+ # Check action-level condition keys
197
+ if action_name in service_detail.actions:
198
+ action_detail = service_detail.actions[action_name]
199
+
200
+ # For action-specific keys, we need to check the service condition keys list
201
+ if (
202
+ action_detail.action_condition_keys
203
+ and condition_key in action_detail.action_condition_keys
204
+ ):
205
+ if condition_key in service_detail.condition_keys:
206
+ condition_key_obj = service_detail.condition_keys[condition_key]
207
+ if condition_key_obj.types:
208
+ return condition_key_obj.types[0]
209
+
210
+ # Check resource-specific condition keys
211
+ if resources and action_detail.resources:
212
+ for res_req in action_detail.resources:
213
+ resource_name = res_req.get("Name", "")
214
+ if not resource_name:
215
+ continue
216
+
217
+ resource_type = service_detail.resources.get(resource_name)
218
+ if resource_type and resource_type.condition_keys:
219
+ if condition_key in resource_type.condition_keys:
220
+ # Resource condition keys reference service condition keys
221
+ if condition_key in service_detail.condition_keys:
222
+ condition_key_obj = service_detail.condition_keys[
223
+ condition_key
224
+ ]
225
+ if condition_key_obj.types:
226
+ return condition_key_obj.types[0]
227
+
228
+ except Exception: # pylint: disable=broad-exception-caught
229
+ # If we can't look up the action, skip it
230
+ continue
231
+
232
+ return None
233
+
234
+ def _types_compatible(self, operator_type: str, key_type: str) -> bool:
235
+ """
236
+ Check if an operator type is compatible with a key type.
237
+
238
+ Note: String/ARN compatibility is handled separately with a warning,
239
+ so this method returns False for that combination.
240
+
241
+ Args:
242
+ operator_type: Type expected by the operator
243
+ key_type: Type of the condition key
244
+
245
+ Returns:
246
+ True if compatible
247
+ """
248
+ # Exact match
249
+ if operator_type == key_type:
250
+ return True
251
+
252
+ # EpochTime can accept both Date and Numeric
253
+ # (this is a special case mentioned in Parliament)
254
+ if key_type == "Date" and operator_type == "Numeric":
255
+ return True
256
+
257
+ return False
@@ -0,0 +1,62 @@
1
+ """Full wildcard check - detects Action: '*' AND Resource: '*' together (critical security risk)."""
2
+
3
+ from typing import ClassVar
4
+
5
+ from iam_validator.core.aws_service import AWSServiceFetcher
6
+ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
7
+ from iam_validator.core.models import Statement, ValidationIssue
8
+
9
+
10
+ class FullWildcardCheck(PolicyCheck):
11
+ """Checks for both Action: '*' AND Resource: '*' which grants full administrative access."""
12
+
13
+ check_id: ClassVar[str] = "full_wildcard"
14
+ description: ClassVar[str] = (
15
+ "Checks for both action and resource wildcards together (critical risk)"
16
+ )
17
+ default_severity: ClassVar[str] = "critical"
18
+
19
+ async def execute(
20
+ self,
21
+ statement: Statement,
22
+ statement_idx: int,
23
+ fetcher: AWSServiceFetcher,
24
+ config: CheckConfig,
25
+ ) -> list[ValidationIssue]:
26
+ """Execute full wildcard check on a statement."""
27
+ issues = []
28
+
29
+ # Only check Allow statements
30
+ if statement.effect != "Allow":
31
+ return issues
32
+
33
+ actions = statement.get_actions()
34
+ resources = statement.get_resources()
35
+
36
+ # Check for both wildcards together (CRITICAL)
37
+ if "*" in actions and "*" in resources:
38
+ message = config.config.get(
39
+ "message",
40
+ "Statement allows all actions on all resources - **CRITICAL SECURITY RISK**",
41
+ )
42
+ suggestion = config.config.get(
43
+ "suggestion",
44
+ "This grants full administrative access. Replace both wildcards with specific actions and resources to follow least-privilege principle",
45
+ )
46
+ example = config.config.get("example", "")
47
+
48
+ issues.append(
49
+ ValidationIssue(
50
+ severity=self.get_severity(config),
51
+ statement_sid=statement.sid,
52
+ statement_index=statement_idx,
53
+ issue_type="security_risk",
54
+ message=message,
55
+ suggestion=suggestion,
56
+ example=example if example else None,
57
+ line_number=statement.line_number,
58
+ field_name="action", # Action is primary concern in full wildcard
59
+ )
60
+ )
61
+
62
+ return issues
@@ -0,0 +1,105 @@
1
+ """MFA Condition Anti-Pattern Check.
2
+
3
+ Detects dangerous MFA-related condition patterns that may not enforce MFA as intended.
4
+ """
5
+
6
+ from typing import ClassVar
7
+
8
+ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
9
+ from iam_validator.core.models import Statement, ValidationIssue
10
+
11
+
12
+ class MFAConditionCheck(PolicyCheck):
13
+ """Check for MFA condition anti-patterns."""
14
+
15
+ check_id: ClassVar[str] = "mfa_condition_antipattern"
16
+ description: ClassVar[str] = "Detects dangerous MFA-related condition patterns"
17
+ default_severity: ClassVar[str] = "warning"
18
+
19
+ async def execute(
20
+ self, statement: Statement, statement_idx: int, fetcher, config: CheckConfig
21
+ ) -> list[ValidationIssue]:
22
+ """
23
+ Execute the MFA condition anti-pattern check.
24
+
25
+ Common anti-patterns:
26
+ 1. Using Bool with aws:MultiFactorAuthPresent = false
27
+ Problem: The key may not exist in the request, so condition doesn't enforce anything
28
+
29
+ 2. Using Null with aws:MultiFactorAuthPresent = false
30
+ Problem: This only checks if the key exists, not if MFA was used
31
+
32
+ Args:
33
+ statement: The IAM statement to check
34
+ statement_idx: Index of this statement in the policy
35
+ fetcher: AWS service fetcher (not used in this check)
36
+ config: Check configuration
37
+
38
+ Returns:
39
+ List of validation issues found
40
+ """
41
+ issues = []
42
+
43
+ # Only check statements with conditions
44
+ if not statement.condition:
45
+ return issues
46
+
47
+ statement_sid = statement.sid
48
+ line_number = statement.line_number
49
+
50
+ # Check for anti-pattern #1: Bool with aws:MultiFactorAuthPresent = false
51
+ bool_conditions = statement.condition.get("Bool", {})
52
+ for key, value in bool_conditions.items():
53
+ if key.lower() == "aws:multifactorauthpresent":
54
+ # Normalize value to list
55
+ values = value if isinstance(value, list) else [value]
56
+ # Convert to lowercase strings for comparison
57
+ values_lower = [str(v).lower() for v in values]
58
+
59
+ if "false" in values_lower or False in values:
60
+ issues.append(
61
+ ValidationIssue(
62
+ severity=self.get_severity(config),
63
+ message=(
64
+ "**Dangerous MFA condition pattern detected.** "
65
+ 'Using `{"Bool": {"aws:MultiFactorAuthPresent": "false"}}` does not enforce MFA '
66
+ "because `aws:MultiFactorAuthPresent` may not exist in the request context. "
67
+ 'Consider using `{"Bool": {"aws:MultiFactorAuthPresent": "true"}}` in an `Allow` statement, '
68
+ "or use `BoolIfExists` in a `Deny` statement."
69
+ ),
70
+ statement_sid=statement_sid,
71
+ statement_index=statement_idx,
72
+ issue_type="mfa_antipattern_bool_false",
73
+ line_number=line_number,
74
+ field_name="condition",
75
+ )
76
+ )
77
+
78
+ # Check for anti-pattern #2: Null with aws:MultiFactorAuthPresent = false
79
+ null_conditions = statement.condition.get("Null", {})
80
+ for key, value in null_conditions.items():
81
+ if key.lower() == "aws:multifactorauthpresent":
82
+ # Normalize value to list
83
+ values = value if isinstance(value, list) else [value]
84
+ # Convert to lowercase strings for comparison
85
+ values_lower = [str(v).lower() for v in values]
86
+
87
+ if "false" in values_lower or False in values:
88
+ issues.append(
89
+ ValidationIssue(
90
+ severity=self.get_severity(config),
91
+ message=(
92
+ "**Dangerous MFA condition pattern detected.** "
93
+ 'Using `{"Null": {"aws:MultiFactorAuthPresent": "false"}}` only checks if the key exists, '
94
+ "not whether MFA was actually used. This does not enforce MFA. "
95
+ 'Consider using `{"Bool": {"aws:MultiFactorAuthPresent": "true"}}` in an `Allow` statement instead.'
96
+ ),
97
+ statement_sid=statement_sid,
98
+ statement_index=statement_idx,
99
+ issue_type="mfa_antipattern_null_false",
100
+ line_number=line_number,
101
+ field_name="condition",
102
+ )
103
+ )
104
+
105
+ return issues
@@ -0,0 +1,114 @@
1
+ """Policy size validation check.
2
+
3
+ This check validates that IAM policies don't exceed AWS's maximum size limits.
4
+ AWS enforces different size limits based on policy type:
5
+ - Managed policies: 6,144 characters maximum
6
+ - Inline policies for users: 2,048 characters maximum
7
+ - Inline policies for groups: 5,120 characters maximum
8
+ - Inline policies for roles: 10,240 characters maximum
9
+
10
+ Note: AWS does not count whitespace when calculating policy size.
11
+ """
12
+
13
+ import json
14
+ import re
15
+ from typing import TYPE_CHECKING, ClassVar
16
+
17
+ from iam_validator.core.aws_service import AWSServiceFetcher
18
+ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
19
+ from iam_validator.core.constants import AWS_POLICY_SIZE_LIMITS
20
+ from iam_validator.core.models import ValidationIssue
21
+
22
+ if TYPE_CHECKING:
23
+ from iam_validator.core.models import IAMPolicy
24
+
25
+
26
+ class PolicySizeCheck(PolicyCheck):
27
+ """Validates that IAM policies don't exceed AWS size limits."""
28
+
29
+ # AWS IAM policy size limits (loaded from constants module)
30
+ DEFAULT_LIMITS = AWS_POLICY_SIZE_LIMITS
31
+
32
+ check_id: ClassVar[str] = "policy_size"
33
+ description: ClassVar[str] = "Validates that IAM policies don't exceed AWS size limits"
34
+ default_severity: ClassVar[str] = "error"
35
+
36
+ async def execute_policy(
37
+ self,
38
+ policy: "IAMPolicy",
39
+ policy_file: str,
40
+ fetcher: AWSServiceFetcher,
41
+ config: CheckConfig,
42
+ **kwargs,
43
+ ) -> list[ValidationIssue]:
44
+ """Execute the policy size check on the entire policy.
45
+
46
+ This method calculates the policy size (excluding whitespace) and validates
47
+ it against AWS limits based on the configured policy type.
48
+
49
+ Args:
50
+ policy: The complete IAM policy to validate
51
+ policy_file: Path to the policy file (for context/reporting)
52
+ fetcher: AWS service fetcher (unused for this check)
53
+ config: Configuration for this check instance
54
+
55
+ Returns:
56
+ List of ValidationIssue objects if policy exceeds size limits
57
+ """
58
+ del policy_file, fetcher # Unused
59
+ issues = []
60
+
61
+ # Get the policy type from config (default to managed)
62
+ policy_type = config.config.get("policy_type", "managed")
63
+
64
+ # Get custom limits if provided in config, otherwise use defaults
65
+ size_limits = config.config.get("size_limits", self.DEFAULT_LIMITS.copy())
66
+
67
+ # Determine the applicable limit
68
+ limit_key = policy_type
69
+ if limit_key not in size_limits:
70
+ # If custom policy_type not found, default to managed
71
+ limit_key = "managed"
72
+
73
+ max_size = size_limits[limit_key]
74
+
75
+ # Convert policy to JSON and calculate size (excluding whitespace)
76
+ policy_json = policy.model_dump(by_alias=True, exclude_none=True)
77
+ policy_string = json.dumps(policy_json, separators=(",", ":"))
78
+
79
+ # Remove all whitespace as AWS doesn't count it
80
+ policy_size = len(re.sub(r"\s+", "", policy_string))
81
+
82
+ # Check if policy exceeds the limit
83
+ if policy_size > max_size:
84
+ severity = self.get_severity(config)
85
+
86
+ # Calculate percentage over limit
87
+ percentage_over = ((policy_size - max_size) / max_size) * 100
88
+
89
+ # Determine policy type description
90
+ policy_type_desc = {
91
+ "managed": "managed policy",
92
+ "inline_user": "inline policy for users",
93
+ "inline_group": "inline policy for groups",
94
+ "inline_role": "inline policy for roles",
95
+ }.get(policy_type, policy_type)
96
+
97
+ issues.append(
98
+ ValidationIssue(
99
+ severity=severity,
100
+ statement_sid=None, # Policy-level issue
101
+ statement_index=-1, # -1 indicates policy-level issue
102
+ issue_type="policy_size_exceeded",
103
+ message=f"Policy size ({policy_size:,} characters) exceeds AWS limit for {policy_type_desc} ({max_size:,} characters)",
104
+ suggestion=f"The policy is {policy_size - max_size:,} characters over the limit ({percentage_over:.1f}% too large). Consider:\n"
105
+ f" 1. Splitting the policy into multiple smaller policies\n"
106
+ f" 2. Using more concise action/resource patterns with wildcards\n"
107
+ f" 3. Removing unnecessary statements or conditions\n"
108
+ f" 4. For inline policies, consider using managed policies instead\n"
109
+ f"\nNote: AWS does not count whitespace in the size calculation.",
110
+ line_number=None,
111
+ )
112
+ )
113
+
114
+ return issues