iam-policy-validator 1.0.4__py3-none-any.whl → 1.1.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.

Potentially problematic release.


This version of iam-policy-validator might be problematic. Click here for more details.

@@ -43,10 +43,18 @@ class MarkdownFormatter(OutputFormatter):
43
43
 
44
44
  output = [
45
45
  "# IAM Policy Validation Report\n",
46
+ "## Summary",
46
47
  f"**Total Policies:** {report.total_policies}",
47
- f"**Valid Policies:** {report.valid_policies}",
48
- f"**Invalid Policies:** {report.invalid_policies}",
48
+ f"**Valid (IAM):** {report.valid_policies}",
49
+ f"**Invalid (IAM):** {report.invalid_policies}",
50
+ f"**Security Findings:** {report.policies_with_security_issues} ⚠️",
51
+ "",
52
+ "## Issue Breakdown",
49
53
  f"**Total Issues:** {report.total_issues}",
54
+ f"**Validity Issues:** {report.validity_issues} (error/warning/info)",
55
+ f"**Security Issues:** {report.security_issues} (critical/high/medium/low)",
56
+ "",
57
+ "## Legacy Severity Counts",
50
58
  f"**Errors:** {errors}",
51
59
  f"**Warnings:** {warnings}",
52
60
  f"**Info:** {infos}\n",
@@ -251,14 +251,38 @@ class ValidationReport(BaseModel):
251
251
 
252
252
  total_policies: int
253
253
  valid_policies: int
254
- invalid_policies: int
254
+ invalid_policies: int # Policies with IAM validity issues (error/warning)
255
+ policies_with_security_issues: int = (
256
+ 0 # Policies with security findings (critical/high/medium/low)
257
+ )
255
258
  total_issues: int
259
+ validity_issues: int = 0 # Count of IAM validity issues (error/warning/info)
260
+ security_issues: int = 0 # Count of security issues (critical/high/medium/low)
256
261
  results: list[PolicyValidationResult] = Field(default_factory=list)
257
262
 
258
263
  def get_summary(self) -> str:
259
264
  """Generate a human-readable summary."""
260
- return (
261
- f"Validated {self.total_policies} policies: "
262
- f"{self.valid_policies} valid, {self.invalid_policies} invalid, "
263
- f"{self.total_issues} total issues found"
264
- )
265
+ parts = []
266
+ parts.append(f"Validated {self.total_policies} policies:")
267
+
268
+ # Always show valid/invalid counts
269
+ parts.append(f"{self.valid_policies} valid")
270
+
271
+ if self.invalid_policies > 0:
272
+ parts.append(f"{self.invalid_policies} invalid (IAM validity)")
273
+
274
+ if self.policies_with_security_issues > 0:
275
+ parts.append(f"{self.policies_with_security_issues} with security findings")
276
+
277
+ parts.append(f"{self.total_issues} total issues")
278
+
279
+ # Show breakdown if there are issues
280
+ if self.total_issues > 0 and (self.validity_issues > 0 or self.security_issues > 0):
281
+ breakdown_parts = []
282
+ if self.validity_issues > 0:
283
+ breakdown_parts.append(f"{self.validity_issues} validity")
284
+ if self.security_issues > 0:
285
+ breakdown_parts.append(f"{self.security_issues} security")
286
+ parts.append(f"({', '.join(breakdown_parts)})")
287
+
288
+ return " ".join(parts)
@@ -14,6 +14,7 @@ from rich.text import Text
14
14
  from iam_validator.core.formatters import (
15
15
  ConsoleFormatter,
16
16
  CSVFormatter,
17
+ EnhancedFormatter,
17
18
  HTMLFormatter,
18
19
  JSONFormatter,
19
20
  MarkdownFormatter,
@@ -43,6 +44,8 @@ class ReportGenerator:
43
44
  # Register all built-in formatters
44
45
  if not self.formatter_registry.get_formatter("console"):
45
46
  self.formatter_registry.register(ConsoleFormatter())
47
+ if not self.formatter_registry.get_formatter("enhanced"):
48
+ self.formatter_registry.register(EnhancedFormatter())
46
49
  if not self.formatter_registry.get_formatter("json"):
47
50
  self.formatter_registry.register(JSONFormatter())
48
51
  if not self.formatter_registry.get_formatter("markdown"):
@@ -80,11 +83,27 @@ class ReportGenerator:
80
83
  invalid_count = len(results) - valid_count
81
84
  total_issues = sum(len(r.issues) for r in results)
82
85
 
86
+ # Count policies with security issues (separate from validity issues)
87
+ policies_with_security_issues = sum(
88
+ 1 for r in results if any(issue.is_security_severity() for issue in r.issues)
89
+ )
90
+
91
+ # Count validity vs security issues
92
+ validity_issues = sum(
93
+ sum(1 for issue in r.issues if issue.is_validity_severity()) for r in results
94
+ )
95
+ security_issues = sum(
96
+ sum(1 for issue in r.issues if issue.is_security_severity()) for r in results
97
+ )
98
+
83
99
  return ValidationReport(
84
100
  total_policies=len(results),
85
101
  valid_policies=valid_count,
86
102
  invalid_policies=invalid_count,
103
+ policies_with_security_issues=policies_with_security_issues,
87
104
  total_issues=total_issues,
105
+ validity_issues=validity_issues,
106
+ security_issues=security_issues,
88
107
  results=results,
89
108
  )
90
109
 
@@ -98,8 +117,31 @@ class ReportGenerator:
98
117
  summary_text = Text()
99
118
  summary_text.append(f"Total Policies: {report.total_policies}\n")
100
119
  summary_text.append(f"Valid: {report.valid_policies} ", style="green")
101
- summary_text.append(f"Invalid: {report.invalid_policies}\n", style="red")
102
- summary_text.append(f"Total Issues: {report.total_issues}\n")
120
+
121
+ # Show invalid policies (IAM validity issues)
122
+ if report.invalid_policies > 0:
123
+ summary_text.append(f"Invalid: {report.invalid_policies} ", style="red")
124
+
125
+ # Show policies with security findings (separate from validity)
126
+ if report.policies_with_security_issues > 0:
127
+ summary_text.append(
128
+ f"Security Findings: {report.policies_with_security_issues} ", style="yellow"
129
+ )
130
+
131
+ summary_text.append("\n")
132
+
133
+ # Breakdown of issue types
134
+ summary_text.append(f"Total Issues: {report.total_issues}")
135
+ if report.validity_issues > 0 or report.security_issues > 0:
136
+ summary_text.append(" (")
137
+ if report.validity_issues > 0:
138
+ summary_text.append(f"{report.validity_issues} validity", style="red")
139
+ if report.validity_issues > 0 and report.security_issues > 0:
140
+ summary_text.append(", ")
141
+ if report.security_issues > 0:
142
+ summary_text.append(f"{report.security_issues} security", style="yellow")
143
+ summary_text.append(")")
144
+ summary_text.append("\n")
103
145
 
104
146
  self.console.print(Panel(summary_text, title="Validation Summary", border_style="blue"))
105
147
 
@@ -127,11 +169,11 @@ class ReportGenerator:
127
169
  self.console.print(" [dim]No issues found[/dim]")
128
170
  return
129
171
 
130
- # Create issues table
172
+ # Create issues table with adjusted column widths for better readability
131
173
  table = Table(show_header=True, header_style="bold", box=None, padding=(0, 1))
132
- table.add_column("Severity", style="cyan", width=10)
133
- table.add_column("Type", style="magenta", width=20)
134
- table.add_column("Message", style="white")
174
+ table.add_column("Severity", style="cyan", width=12, no_wrap=False)
175
+ table.add_column("Type", style="magenta", width=25, no_wrap=False)
176
+ table.add_column("Message", style="white", no_wrap=False)
135
177
 
136
178
  for issue in result.issues:
137
179
  severity_style = {
@@ -149,6 +191,8 @@ class ReportGenerator:
149
191
  location = f"Statement {issue.statement_index}"
150
192
  if issue.statement_sid:
151
193
  location += f" ({issue.statement_sid})"
194
+ if issue.line_number is not None:
195
+ location += f" @L{issue.line_number}"
152
196
 
153
197
  message = f"{location}: {issue.message}"
154
198
  if issue.suggestion:
@@ -580,22 +624,35 @@ class ReportGenerator:
580
624
  continue
581
625
 
582
626
  policy_lines = []
583
- policy_lines.append("<details open>")
584
- policy_lines.append(
585
- f"<summary><b>{idx}. <code>{result.policy_file}</code></b> - {len(result.issues)} issue(s) found</summary>"
586
- )
587
- policy_lines.append("")
588
627
 
589
628
  # Group issues by severity - support both IAM validity and security severities
590
629
  errors = [i for i in result.issues if i.severity in ("error", "critical", "high")]
591
630
  warnings = [i for i in result.issues if i.severity in ("warning", "medium")]
592
631
  infos = [i for i in result.issues if i.severity in ("info", "low")]
593
632
 
633
+ # Build severity summary for header
634
+ severity_parts = []
635
+ if errors:
636
+ severity_parts.append(f"🔴 {len(errors)}")
637
+ if warnings:
638
+ severity_parts.append(f"🟡 {len(warnings)}")
639
+ if infos:
640
+ severity_parts.append(f"🔵 {len(infos)}")
641
+ severity_summary = " · ".join(severity_parts)
642
+
643
+ # Only open first 3 policy details by default to avoid wall of text
644
+ is_open = " open" if policies_shown < 3 else ""
645
+ policy_lines.append(f"<details{is_open}>")
646
+ policy_lines.append(
647
+ f"<summary><b>{idx}. <code>{result.policy_file}</code></b> - {severity_summary}</summary>"
648
+ )
649
+ policy_lines.append("")
650
+
594
651
  # Add errors (prioritized)
595
652
  if errors:
596
653
  policy_lines.append("### 🔴 Errors")
597
654
  policy_lines.append("")
598
- for issue in errors:
655
+ for i, issue in enumerate(errors):
599
656
  issue_content = self._format_issue_markdown(issue)
600
657
  test_length = len("\n".join(details_lines + policy_lines)) + len(
601
658
  issue_content
@@ -605,6 +662,10 @@ class ReportGenerator:
605
662
  break
606
663
  policy_lines.append(issue_content)
607
664
  issues_shown += 1
665
+ # Add separator between issues within same severity
666
+ if i < len(errors) - 1:
667
+ policy_lines.append("---")
668
+ policy_lines.append("")
608
669
  policy_lines.append("")
609
670
 
610
671
  if truncated:
@@ -614,7 +675,7 @@ class ReportGenerator:
614
675
  if warnings:
615
676
  policy_lines.append("### 🟡 Warnings")
616
677
  policy_lines.append("")
617
- for issue in warnings:
678
+ for i, issue in enumerate(warnings):
618
679
  issue_content = self._format_issue_markdown(issue)
619
680
  test_length = len("\n".join(details_lines + policy_lines)) + len(
620
681
  issue_content
@@ -624,6 +685,10 @@ class ReportGenerator:
624
685
  break
625
686
  policy_lines.append(issue_content)
626
687
  issues_shown += 1
688
+ # Add separator between issues within same severity
689
+ if i < len(warnings) - 1:
690
+ policy_lines.append("---")
691
+ policy_lines.append("")
627
692
  policy_lines.append("")
628
693
 
629
694
  if truncated:
@@ -633,7 +698,7 @@ class ReportGenerator:
633
698
  if infos:
634
699
  policy_lines.append("### 🔵 Info")
635
700
  policy_lines.append("")
636
- for issue in infos:
701
+ for i, issue in enumerate(infos):
637
702
  issue_content = self._format_issue_markdown(issue)
638
703
  test_length = len("\n".join(details_lines + policy_lines)) + len(
639
704
  issue_content
@@ -643,6 +708,10 @@ class ReportGenerator:
643
708
  break
644
709
  policy_lines.append(issue_content)
645
710
  issues_shown += 1
711
+ # Add separator between issues within same severity
712
+ if i < len(infos) - 1:
713
+ policy_lines.append("---")
714
+ policy_lines.append("")
646
715
  policy_lines.append("")
647
716
 
648
717
  if truncated:
@@ -650,6 +719,8 @@ class ReportGenerator:
650
719
 
651
720
  policy_lines.append("</details>")
652
721
  policy_lines.append("")
722
+ policy_lines.append("---")
723
+ policy_lines.append("")
653
724
 
654
725
  # Check if adding this policy would exceed limit
655
726
  test_length = len("\n".join(details_lines + policy_lines))
@@ -712,7 +783,7 @@ class ReportGenerator:
712
783
  parts.append(f"> {issue.message}")
713
784
  parts.append("")
714
785
 
715
- # Details section
786
+ # Details section - inline format
716
787
  details = []
717
788
  if issue.action:
718
789
  details.append(f"**Action:** `{issue.action}`")
@@ -722,19 +793,27 @@ class ReportGenerator:
722
793
  details.append(f"**Condition Key:** `{issue.condition_key}`")
723
794
 
724
795
  if details:
725
- parts.append("<table>")
726
- parts.append("<tr><td>")
727
- parts.append("")
728
- parts.extend(details)
729
- parts.append("")
730
- parts.append("</td></tr>")
731
- parts.append("</table>")
796
+ parts.append(" · ".join(details))
732
797
  parts.append("")
733
798
 
734
- # Suggestion in highlighted box
799
+ # Suggestion in highlighted box with code examples
735
800
  if issue.suggestion:
736
- parts.append(f"> 💡 **Suggestion:** {issue.suggestion}")
737
- parts.append("")
801
+ # Check if suggestion contains "Example:" section
802
+ if "\nExample:\n" in issue.suggestion:
803
+ text_part, code_part = issue.suggestion.split("\nExample:\n", 1)
804
+ parts.append(f"> 💡 **Suggestion:** {text_part}")
805
+ parts.append("")
806
+ parts.append("<details>")
807
+ parts.append("<summary>📖 View Example</summary>")
808
+ parts.append("")
809
+ parts.append("```json")
810
+ parts.append(code_part)
811
+ parts.append("```")
812
+ parts.append("</details>")
813
+ parts.append("")
814
+ else:
815
+ parts.append(f"> 💡 **Suggestion:** {issue.suggestion}")
816
+ parts.append("")
738
817
 
739
818
  return "\n".join(parts)
740
819