iam-policy-validator 1.15.2__py3-none-any.whl → 1.15.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iam-policy-validator
3
- Version: 1.15.2
3
+ Version: 1.15.3
4
4
  Summary: Validate AWS IAM policies for correctness and security using AWS Service Reference API
5
5
  Project-URL: Homepage, https://github.com/boogy/iam-policy-validator
6
6
  Project-URL: Documentation, https://boogy.github.io/iam-policy-validator
@@ -1,6 +1,6 @@
1
1
  iam_validator/__init__.py,sha256=xHdUASOxFHwEXfT_GSr_KrkLlnxZ-pAAr1wW1PwAGko,693
2
2
  iam_validator/__main__.py,sha256=to_nz3n_IerJpVVZZ6WSFlFR5s_06J0csfPOTfQZG8g,197
3
- iam_validator/__version__.py,sha256=Bgc0qLeAX9DtaxWp2EJRUbZweOqF_-YeAxjCOs2BIuM,374
3
+ iam_validator/__version__.py,sha256=2oTvvCnwbVqgXWNdEDQWe8-xj2ecUXJtl1jF_CIIGxk,374
4
4
  iam_validator/checks/__init__.py,sha256=wFU5Lz-ZIQBcn2y1u0Kl88B--vEO3btOOaTGPPSjJ74,2106
5
5
  iam_validator/checks/action_condition_enforcement.py,sha256=2-XUMbof9tQ7SHZNmAHMkR1DgbOIzY2eFWlp9S9dwLk,60625
6
6
  iam_validator/checks/action_resource_matching.py,sha256=qND0hfDgNoxFEdLWwrxOPVDfdj3k50nzedT2qF7nK7o,19428
@@ -8,8 +8,8 @@ iam_validator/checks/action_validation.py,sha256=gy9_mujYbojBboLk0WwuJYJjggrY2sR
8
8
  iam_validator/checks/condition_key_validation.py,sha256=5i8LqqV78SjWK6pLrbttWmeMAD4pDC12_FjTjx5dFSU,4024
9
9
  iam_validator/checks/condition_type_mismatch.py,sha256=KJp7zQHDd8VeTcfjcD-ur3S4070cXEDTWkFtxfp7CuE,10652
10
10
  iam_validator/checks/full_wildcard.py,sha256=0TkkHtV0MZ6nZtJRtGdn3wwOMM96TRyGO7l7mmdHNUo,2325
11
- iam_validator/checks/mfa_condition_check.py,sha256=y1LbqcvQ_fL2BPTNaKRQoBYM5hM7JET9cDPUOWKFEVs,4814
12
- iam_validator/checks/not_action_not_resource.py,sha256=WWKOCLCq7yxOG9tgi1n5xPpphTLZ9RfcfPwZ-TP6n9Y,8097
11
+ iam_validator/checks/mfa_condition_check.py,sha256=EYOzESzsZVlxW9Pp6hSqPZkH0UT1h21SoZibBX7k_OU,7705
12
+ iam_validator/checks/not_action_not_resource.py,sha256=18wB1NKaCj4X7o8xcTLmtFjsXFJLXTukzp1FhZzvMa8,9779
13
13
  iam_validator/checks/policy_size.py,sha256=eJd36Nj4gqWLIkQ5imhHR1hGtQ6T-iJsC22Wd1VSUf0,4681
14
14
  iam_validator/checks/policy_structure.py,sha256=LExdm93JeqsyhikWrh9lfJ9sBiZ4818Ts0-N3MwUrtk,22089
15
15
  iam_validator/checks/policy_type_validation.py,sha256=-Q2Yn_sa7Cba_Fb9ESxSL5qNbRpncuC7NQy7R_fdJtY,16521
@@ -112,8 +112,8 @@ iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYu
112
112
  iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
113
113
  iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
114
114
  iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
115
- iam_policy_validator-1.15.2.dist-info/METADATA,sha256=3YWLi7WFFLI4s-vCyjfkxJ8WB9QJCQthXl1-_isP9wE,34939
116
- iam_policy_validator-1.15.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
117
- iam_policy_validator-1.15.2.dist-info/entry_points.txt,sha256=VXAcx1evo9fuxX0Gtj3J2HnzWcBHSXugiZwBtQ1BXE0,162
118
- iam_policy_validator-1.15.2.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
119
- iam_policy_validator-1.15.2.dist-info/RECORD,,
115
+ iam_policy_validator-1.15.3.dist-info/METADATA,sha256=r1dXILWhINVsEMyiTTC3IJpAEAoS0usdkBkt7lVe1og,34939
116
+ iam_policy_validator-1.15.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
117
+ iam_policy_validator-1.15.3.dist-info/entry_points.txt,sha256=VXAcx1evo9fuxX0Gtj3J2HnzWcBHSXugiZwBtQ1BXE0,162
118
+ iam_policy_validator-1.15.3.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
119
+ iam_policy_validator-1.15.3.dist-info/RECORD,,
@@ -3,7 +3,7 @@
3
3
  This file is the single source of truth for the package version.
4
4
  """
5
5
 
6
- __version__ = "1.15.2"
6
+ __version__ = "1.15.3"
7
7
  # Parse version, handling pre-release suffixes like -rc, -alpha, -beta
8
8
  _version_base = __version__.split("-", maxsplit=1)[0] # Remove pre-release suffix if present
9
9
  __version_info__ = tuple(int(part) for part in _version_base.split("."))
@@ -75,7 +75,36 @@ class MFAConditionCheck(PolicyCheck):
75
75
  )
76
76
  )
77
77
 
78
- # Check for anti-pattern #2: Null with aws:MultiFactorAuthPresent = false
78
+ # Check for anti-pattern #2: BoolIfExists with aws:MultiFactorAuthPresent = false
79
+ # This is MORE dangerous than Bool because it also matches when the key is missing
80
+ bool_if_exists_conditions = statement.condition.get("BoolIfExists", {})
81
+ for key, value in bool_if_exists_conditions.items():
82
+ if key.lower() == "aws:multifactorauthpresent":
83
+ # Normalize value to list
84
+ values = value if isinstance(value, list) else [value]
85
+ # Convert to lowercase strings for comparison
86
+ values_lower = [str(v).lower() for v in values]
87
+
88
+ if "false" in values_lower or False in values:
89
+ issues.append(
90
+ ValidationIssue(
91
+ severity="high", # Higher than default - this is worse than Bool
92
+ message=(
93
+ "**DANGEROUS MFA condition pattern detected.** "
94
+ 'Using `{"BoolIfExists": {"aws:MultiFactorAuthPresent": "false"}}` '
95
+ "is MORE dangerous than using `Bool` because it also matches when "
96
+ "the key is missing entirely (no MFA context in the request). "
97
+ "This effectively allows access without any MFA verification."
98
+ ),
99
+ statement_sid=statement_sid,
100
+ statement_index=statement_idx,
101
+ issue_type="mfa_antipattern_boolif_exists_false",
102
+ line_number=line_number,
103
+ field_name="condition",
104
+ )
105
+ )
106
+
107
+ # Check for anti-pattern #3: Null with aws:MultiFactorAuthPresent = false
79
108
  null_conditions = statement.condition.get("Null", {})
80
109
  for key, value in null_conditions.items():
81
110
  if key.lower() == "aws:multifactorauthpresent":
@@ -102,4 +131,24 @@ class MFAConditionCheck(PolicyCheck):
102
131
  )
103
132
  )
104
133
 
134
+ # Check for anti-pattern #4: Null with aws:MultiFactorAuthPresent = true
135
+ # This means "key does NOT exist" = no MFA was used
136
+ if "true" in values_lower or True in values:
137
+ issues.append(
138
+ ValidationIssue(
139
+ severity=self.get_severity(config),
140
+ message=(
141
+ "**Dangerous MFA condition pattern detected.** "
142
+ 'Using `{"Null": {"aws:MultiFactorAuthPresent": "true"}}` checks if the key '
143
+ "does NOT exist, which means no MFA was provided in the request context. "
144
+ "This condition allows access when MFA is absent."
145
+ ),
146
+ statement_sid=statement_sid,
147
+ statement_index=statement_idx,
148
+ issue_type="mfa_antipattern_null_true",
149
+ line_number=line_number,
150
+ field_name="condition",
151
+ )
152
+ )
153
+
105
154
  return issues
@@ -12,6 +12,13 @@ from iam_validator.core.check_registry import CheckConfig, PolicyCheck
12
12
  from iam_validator.core.models import Statement, ValidationIssue
13
13
 
14
14
 
15
+ def _format_list_with_backticks(items: list[str], max_items: int = 3) -> str:
16
+ """Format a list of items with backticks for markdown rendering."""
17
+ formatted = [f"`{item}`" for item in items[:max_items]]
18
+ suffix = "..." if len(items) > max_items else ""
19
+ return ", ".join(formatted) + suffix
20
+
21
+
15
22
  class NotActionNotResourceCheck(PolicyCheck):
16
23
  """Checks for dangerous NotAction/NotResource patterns.
17
24
 
@@ -59,18 +66,18 @@ class NotActionNotResourceCheck(PolicyCheck):
59
66
  statement_index=statement_idx,
60
67
  issue_type="not_action_allow",
61
68
  message=(
62
- "Statement uses NotAction with Allow effect. "
69
+ "Statement uses `NotAction` with `Allow` effect. "
63
70
  "This grants ALL actions except the listed ones. "
64
71
  "While conditions are present, this pattern is still risky."
65
72
  ),
66
73
  suggestion=(
67
- "Consider using explicit Action lists instead of NotAction. "
68
- "If NotAction is required, ensure conditions are comprehensive."
74
+ "Consider using explicit `Action` lists instead of `NotAction`. "
75
+ "If `NotAction` is required, ensure conditions are comprehensive."
69
76
  ),
70
77
  example='{\n "Effect": "Allow",\n "Action": ["s3:GetObject", "s3:ListBucket"],\n "Resource": "*"\n}',
71
78
  line_number=statement.line_number,
72
79
  field_name="action",
73
- action=", ".join(not_actions[:3]) + ("..." if len(not_actions) > 3 else ""),
80
+ action=_format_list_with_backticks(not_actions, 3),
74
81
  )
75
82
  )
76
83
  else:
@@ -82,20 +89,19 @@ class NotActionNotResourceCheck(PolicyCheck):
82
89
  statement_index=statement_idx,
83
90
  issue_type="not_action_allow_no_condition",
84
91
  message=(
85
- "Statement uses NotAction with Allow effect and NO conditions. "
86
- f"This grants ALL AWS actions except: {', '.join(not_actions[:5])}"
87
- f"{'...' if len(not_actions) > 5 else ''}. "
92
+ "Statement uses `NotAction` with `Allow` effect and NO conditions. "
93
+ f"This grants ALL AWS actions except: {_format_list_with_backticks(not_actions, 5)}. "
88
94
  "This is equivalent to granting near-administrator access."
89
95
  ),
90
96
  suggestion=(
91
- "Replace NotAction with explicit Action list. "
92
- "If NotAction is required, add strict conditions like "
93
- "aws:SourceIp, aws:PrincipalArn, or aws:MultiFactorAuthPresent."
97
+ "Replace `NotAction` with explicit `Action` list. "
98
+ "If `NotAction` is required, add strict conditions like "
99
+ "`aws:SourceIp`, `aws:PrincipalArn`, or `aws:MultiFactorAuthPresent`."
94
100
  ),
95
101
  example='{\n "Effect": "Allow",\n "Action": ["specific:Action"],\n "Resource": "*",\n "Condition": {\n "Bool": {"aws:MultiFactorAuthPresent": "true"}\n }\n}',
96
102
  line_number=statement.line_number,
97
103
  field_name="action",
98
- action=", ".join(not_actions[:3]) + ("..." if len(not_actions) > 3 else ""),
104
+ action=_format_list_with_backticks(not_actions, 3),
99
105
  )
100
106
  )
101
107
 
@@ -113,24 +119,49 @@ class NotActionNotResourceCheck(PolicyCheck):
113
119
  statement_index=statement_idx,
114
120
  issue_type="not_resource_broad",
115
121
  message=(
116
- "Statement uses NotResource with Allow effect and broad Resource. "
117
- f"This grants access to ALL resources except: {', '.join(not_resources[:3])}"
118
- f"{'...' if len(not_resources) > 3 else ''}."
122
+ "Statement uses `NotResource` with `Allow` effect and broad `Resource`. "
123
+ f"This grants access to ALL resources except: {_format_list_with_backticks(not_resources, 3)}."
119
124
  ),
120
125
  suggestion=(
121
- "Replace NotResource with explicit Resource ARNs. "
122
- "Using NotResource grants access to all current and future resources "
126
+ "Replace `NotResource` with explicit `Resource` ARNs. "
127
+ "Using `NotResource` grants access to all current and future resources "
123
128
  "except those explicitly excluded."
124
129
  ),
125
130
  example='{\n "Effect": "Allow",\n "Action": ["s3:GetObject"],\n "Resource": "arn:aws:s3:::my-bucket/*"\n}',
126
131
  line_number=statement.line_number,
127
132
  field_name="resource",
128
- resource=", ".join(not_resources[:3])
129
- + ("..." if len(not_resources) > 3 else ""),
133
+ resource=_format_list_with_backticks(not_resources, 3),
130
134
  )
131
135
  )
132
136
 
133
- # Check 3: NotAction with Deny - less dangerous but worth noting
137
+ # Check 3: Combined NotAction AND NotResource with Allow
138
+ # This is the most dangerous pattern - grants nearly all actions on nearly all resources
139
+ if not_actions and not_resources and effect == "Allow":
140
+ issues.append(
141
+ ValidationIssue(
142
+ severity="critical",
143
+ statement_sid=statement.sid,
144
+ statement_index=statement_idx,
145
+ issue_type="combined_not_action_not_resource",
146
+ message=(
147
+ "**CRITICAL:** Policy uses both `NotAction` AND `NotResource` with `Allow` effect. "
148
+ f"This grants ALL actions except [{_format_list_with_backticks(not_actions, 3)}] "
149
+ f"on ALL resources except [{_format_list_with_backticks(not_resources, 3)}]. "
150
+ "This is equivalent to near-administrator access."
151
+ ),
152
+ suggestion=(
153
+ "Rewrite using explicit `Action` and `Resource` lists instead of negations. "
154
+ "The combination of `NotAction` + `NotResource` is almost always a mistake."
155
+ ),
156
+ example='{\n "Effect": "Allow",\n "Action": ["specific:Action"],\n "Resource": "arn:aws:service:::specific-resource"\n}',
157
+ line_number=statement.line_number,
158
+ field_name="action",
159
+ action=_format_list_with_backticks(not_actions, 3),
160
+ resource=_format_list_with_backticks(not_resources, 3),
161
+ )
162
+ )
163
+
164
+ # Check 4: NotAction with Deny - less dangerous but worth noting
134
165
  # NotAction with Deny means "deny everything except these actions"
135
166
  # which is actually a valid deny pattern but should be reviewed
136
167
  if not_actions and effect == "Deny":
@@ -145,18 +176,17 @@ class NotActionNotResourceCheck(PolicyCheck):
145
176
  statement_index=statement_idx,
146
177
  issue_type="not_action_deny_review",
147
178
  message=(
148
- "Statement uses NotAction with Deny effect on all resources. "
149
- f"This denies everything except: {', '.join(not_actions[:5])}"
150
- f"{'...' if len(not_actions) > 5 else ''}. "
179
+ "Statement uses `NotAction` with `Deny` effect on all resources. "
180
+ f"This denies everything except: {_format_list_with_backticks(not_actions, 5)}. "
151
181
  "Review to ensure this is the intended behavior."
152
182
  ),
153
183
  suggestion=(
154
184
  "Verify that allowing only these specific actions is intended. "
155
- "Consider if an explicit Allow list would be clearer."
185
+ "Consider if an explicit `Allow` list would be clearer."
156
186
  ),
157
187
  line_number=statement.line_number,
158
188
  field_name="action",
159
- action=", ".join(not_actions[:3]) + ("..." if len(not_actions) > 3 else ""),
189
+ action=_format_list_with_backticks(not_actions, 3),
160
190
  )
161
191
  )
162
192