#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.12" # dependencies = ["pyyaml==6.0.3", "rich==15.0.0", "typer==0.25.0"] # /// #MISE description="Review the most stale compensating control" #USAGE flag "--limit " default="10" help="Number of controls to show in the table" """Review compensating controls by staleness. Reads ``compensating-controls.yaml`` and sorts by ``last-reviewed``. Shows a staleness table, then displays the most stale control with all references found in the codebase. After reviewing, update the control entry: last-reviewed: YYYY-MM-DD Usage: mise run review-compensating-controls [--limit 10] """ import subprocess import sys from datetime import date from pathlib import Path from typing import Annotated import typer import yaml from rich.console import Console from rich.panel import Panel from rich.table import Table CONTROLS_FILE = Path(__file__).parent.parent / "compensating-controls.yaml" REPO_ROOT = Path(__file__).parent.parent def load_controls(path: Path) -> list[dict]: data = yaml.safe_load(path.read_text()) return data.get("controls", []) def parse_date(raw) -> date | None: if raw is None: return None if isinstance(raw, date): return raw try: return date.fromisoformat(str(raw)) except ValueError: return None def find_references(control_id: str) -> list[str]: """Find all files referencing a control ID using ripgrep.""" try: result = subprocess.run( ["rg", "--no-heading", "-n", control_id, str(REPO_ROOT)], capture_output=True, text=True, timeout=10, ) lines = result.stdout.strip().splitlines() # Exclude the controls file itself and this script return [ ln for ln in lines if "compensating-controls.yaml" not in ln and "review-compensating-controls" not in ln ] except (FileNotFoundError, subprocess.TimeoutExpired): return [] def main( limit: Annotated[ int, typer.Option(help="Number of controls to show in the table") ] = 10, ) -> None: console = Console() today = date.today() if not CONTROLS_FILE.exists(): console.print( f"[bold red]Controls file not found:[/bold red] {CONTROLS_FILE}" ) raise typer.Exit(code=1) controls = load_controls(CONTROLS_FILE) # Parse dates and build sortable entries entries: list[tuple[dict, date | None]] = [] for ctrl in controls: reviewed = parse_date(ctrl.get("last-reviewed")) entries.append((ctrl, reviewed)) # Sort: never-reviewed first, then oldest entries.sort(key=lambda e: (e[1] is not None, e[1] or date.min)) never_reviewed = sum(1 for _, r in entries if r is None) # --- Summary panel --- console.print() console.print( Panel( f"[bold]{len(entries)}[/bold] compensating controls, " f"[bold red]{never_reviewed}[/bold red] never reviewed", title="[bold]Compensating Control Review Queue[/bold]", border_style="cyan", ) ) console.print() # --- Staleness table --- table = Table(show_header=True, header_style="bold") table.add_column("#", justify="right") table.add_column("Control ID") table.add_column("Last Reviewed", justify="right") table.add_column("Age (days)", justify="right") table.add_column("Refs", justify="right") for i, (ctrl, reviewed) in enumerate(entries[:limit], 1): control_id = ctrl["id"] refs = len(find_references(control_id)) if reviewed is None: table.add_row( str(i), f"[red]{control_id}[/red]", "[red]never[/red]", "[red]—[/red]", str(refs), ) else: age = (today - reviewed).days style = "yellow" if age > 90 else "" id_str = f"[{style}]{control_id}[/{style}]" if style else control_id date_str = f"[{style}]{reviewed}[/{style}]" if style else str(reviewed) age_str = f"[{style}]{age}[/{style}]" if style else str(age) table.add_row(str(i), id_str, date_str, age_str, str(refs)) remaining = len(entries) - limit if remaining > 0: table.add_row("", f"[dim]… {remaining} more[/dim]", "", "", "") console.print(table) console.print() # --- Most stale control detail --- if not entries: console.print("[bold red]No controls found![/bold red]") raise typer.Exit(code=1) top_ctrl, top_reviewed = entries[0] control_id = top_ctrl["id"] refs = find_references(control_id) detail_lines = [ f"[bold cyan]{control_id}[/bold cyan]", f"[dim]Last reviewed: {top_reviewed or 'never'}[/dim]", "", f"[bold]Description:[/bold] {top_ctrl.get('description', '').strip()}", ] notes = top_ctrl.get("notes", "").strip() if notes: detail_lines.append(f"[bold]Notes:[/bold] {notes}") console.print( Panel( "\n".join(detail_lines), title="[bold]Up For Review[/bold]", border_style="green", ) ) console.print() # --- References --- if refs: ref_table = Table( show_header=True, header_style="bold", title="References in codebase" ) ref_table.add_column("File", style="cyan") ref_table.add_column("Line") for ref in refs: # rg output: file:line:content parts = ref.split(":", 2) if len(parts) >= 3: filepath = parts[0].replace(str(REPO_ROOT) + "/", "") line_no = parts[1] content = parts[2].strip() ref_table.add_row(f"{filepath}:{line_no}", content) else: ref_table.add_row(ref, "") console.print(ref_table) else: console.print( f"[yellow]No references to '{control_id}' found in the codebase.[/yellow]" ) console.print() # --- Review checklist --- checklist = [ "[bold]Verification:[/bold]\n", f"• {notes}\n" if notes else "", "\n[bold]Review each reference:[/bold]\n", "• For each muted finding referencing this control, confirm:\n", " 1. The risk the original check guards against\n", " 2. That this control actually mitigates that risk\n", " 3. That the control is still in effect (not degraded or bypassed)\n", "\n[bold]After review:[/bold]\n", f"• Update compensating-controls.yaml: [cyan]last-reviewed: {today}[/cyan]\n", "• If the control is no longer valid, either:\n", " - Fix the underlying finding and remove the mute, or\n", " - Document a new/updated compensating control\n", "• Commit the change", ] console.print( Panel( "".join(checklist), title="[bold yellow]Review Guidance[/bold yellow]", border_style="yellow", ) ) if __name__ == "__main__": typer.run(main)