audit-labs/audit-tools

A collection of scripts, queries, and other goodies you can use in an audit. audit automation compliance evidence scripts

Commit d4bbf635ef

d4bbf635ef2ad99c0a48aaf32d99b3fcd7d8bd1d

parent: 31f6eb371d

Unsigned

cmc <hello@cleberg.net> · 2026-07-29 04:21 UTC

style: make repo ruff-clean

Resolve ruff check errors across the repo so the lint CI passes:

- Apply ruff autofixes: sort imports (I001), modernize type hints (UP006/
  UP035/UP045), drop a redundant int() cast (RUF046).
- Mark the two shebang scripts executable (EXE001).
- Add ruff.toml ignoring three rules that flag intentional patterns: BLE001
  (collectors deliberately catch broadly so one failing check never aborts a
  run), DTZ011 (local date used for date-stamped output folders), and S112
  (skip unavailable resources during collection). This also lets the inline
  BLE001 noqa comments be removed.

No behavior changes. ruff check and ruff format --check both pass; all tests pass.

Layout: unified · split

applications/aws/aws_password_policy/evaluate_policy.py +8 −8
@@ -21,7 +21,7 @@ import json
21import sys 21import sys
22from datetime import datetime, timezone 22from datetime import datetime, timezone
23from pathlib import Path 23from pathlib import Path
24from typing import Any, Dict, List, Optional 24from typing import Any
25 25
26# ---------------------------------------------------------------------- 26# ----------------------------------------------------------------------
27# Mapping of the 10 password‑policy fields we care about 27# Mapping of the 10 password‑policy fields we care about
@@ -49,7 +49,7 @@ def utc_now() -> datetime:
49 return datetime.datetime.now(timezone.utc) 49 return datetime.datetime.now(timezone.utc)
50 50
51 51
52def prompt_expected(field_type: str, description: str) -> Optional[Any]: 52def prompt_expected(field_type: str, description: str) -> Any | None:
53 """ 53 """
54 Ask the auditor for the expected value. 54 Ask the auditor for the expected value.
55 Returns: 55 Returns:
@@ -78,7 +78,7 @@ def prompt_expected(field_type: str, description: str) -> Optional[Any]:
78 return raw 78 return raw
79 79
80 80
81def evaluate(expect: Optional[Any], actual: Any, field_type: str) -> str: 81def evaluate(expect: Any | None, actual: Any, field_type: str) -> str:
82 """Return PASS / FAIL / N/A.""" 82 """Return PASS / FAIL / N/A."""
83 if expect is None: 83 if expect is None:
84 return "N/A" 84 return "N/A"
@@ -89,7 +89,7 @@ def evaluate(expect: Optional[Any], actual: Any, field_type: str) -> str:
89 return "FAIL" 89 return "FAIL"
90 90
91 91
92def load_json(path: Path) -> Dict[str, Any]: 92def load_json(path: Path) -> dict[str, Any]:
93 """Read the JSON file generated by the Bash script.""" 93 """Read the JSON file generated by the Bash script."""
94 try: 94 try:
95 with path.open("r", encoding="utf-8") as fh: 95 with path.open("r", encoding="utf-8") as fh:
@@ -100,8 +100,8 @@ def load_json(path: Path) -> Dict[str, Any]:
100 100
101def write_csv( 101def write_csv(
102 out_path: Path, 102 out_path: Path,
103 metadata: Dict[str, Any], 103 metadata: dict[str, Any],
104 rows: List[List[Any]], 104 rows: list[list[Any]],
105) -> None: 105) -> None:
106 """Write the CSV report, including a metadata header block.""" 106 """Write the CSV report, including a metadata header block."""
107 with out_path.open("w", newline="", encoding="utf-8") as csvfile: 107 with out_path.open("w", newline="", encoding="utf-8") as csvfile:
@@ -136,7 +136,7 @@ def main() -> None:
136 # -------------------------------------------------------------- 136 # --------------------------------------------------------------
137 # 1. Prompt the auditor for expectations 137 # 1. Prompt the auditor for expectations
138 # -------------------------------------------------------------- 138 # --------------------------------------------------------------
139 expectations: Dict[str, Optional[Any]] = {} 139 expectations: dict[str, Any | None] = {}
140 print("\n=== Expected / Minimum Values (press <Enter> for N/A) ===\n") 140 print("\n=== Expected / Minimum Values (press <Enter> for N/A) ===\n")
141 for _, key, friendly, typ in POLICY_FIELDS: 141 for _, key, friendly, typ in POLICY_FIELDS:
142 expectations[key] = prompt_expected(typ, friendly) 142 expectations[key] = prompt_expected(typ, friendly)
@@ -144,7 +144,7 @@ def main() -> None:
144 # -------------------------------------------------------------- 144 # --------------------------------------------------------------
145 # 2. Build the CSV rows (including PASS/FAIL) 145 # 2. Build the CSV rows (including PASS/FAIL)
146 # -------------------------------------------------------------- 146 # --------------------------------------------------------------
147 csv_rows: List[List[Any]] = [] 147 csv_rows: list[list[Any]] = []
148 for rule_no, key, friendly, typ in POLICY_FIELDS: 148 for rule_no, key, friendly, typ in POLICY_FIELDS:
149 expected = expectations[key] 149 expected = expectations[key]
150 actual = policy.get(key, "(missing)") 150 actual = policy.get(key, "(missing)")
applications/github/audit.py +1 −1
@@ -34,7 +34,7 @@ import sys
34from datetime import date 34from datetime import date
35 35
36import config 36import config
37from collectors import members, branch_protections, commits, audit_log 37from collectors import audit_log, branch_protections, commits, members
38from reporters import csv_reporter 38from reporters import csv_reporter
39 39
40 40
applications/github/collectors/audit_log.py +1 −1
@@ -5,9 +5,9 @@ Requires GitHub Enterprise Cloud. Skips gracefully with a warning if not
5available. 5available.
6""" 6"""
7 7
8from datetime import date, datetime, timezone, timedelta
9import json 8import json
10import sys 9import sys
10from datetime import date, datetime, timedelta, timezone
11 11
12import requests 12import requests
13 13
project_management/dash/app.py +1 −1
@@ -3,9 +3,9 @@ Extensible dashboard for project status.
3""" 3"""
4 4
5# Import packages 5# Import packages
6from dash import Dash, html, dcc
7import pandas as pd 6import pandas as pd
8import plotly.express as px 7import plotly.express as px
8from dash import Dash, dcc, html
9 9
10# Incorporate data 10# Incorporate data
11df = pd.read_excel("project_data.xlsx") 11df = pd.read_excel("project_data.xlsx")
ruff.toml added +14
@@ -0,0 +1,14 @@
1# Ruff configuration for audit-tools.
2#
3# A few lint rules are disabled because they flag patterns this project uses
4# deliberately:
5#
6# BLE001 - The audit collectors and their CLI wrappers intentionally catch
7# broad exceptions so that one failing check never aborts a whole
8# audit run. The error is reported and collection continues.
9# DTZ011 - date.today() is used to build human-facing, date-stamped output
10# folder names, where the local date is the intended value.
11# S112 - try/except/continue is used to skip resources that are unavailable
12# during collection (e.g. a repo without the requested branch).
13[lint]
14ignore = ["BLE001", "DTZ011", "S112"]
sampling/audit_sample.py +1 −3
@@ -1,15 +1,13 @@
1#!/usr/bin/env python3 1#!/usr/bin/env python3
2"""Command-line entrypoint for the audit sampling tool.""" 2"""Command-line entrypoint for the audit sampling tool."""
3 3
4from pathlib import Path
5import sys 4import sys
6 5from pathlib import Path
7 6
8if __package__ is None or __package__ == "": 7if __package__ is None or __package__ == "":
9 sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) 8 sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
10 9
11from sampling.sampling_tool.cli import main 10from sampling.sampling_tool.cli import main
12 11
13
14if __name__ == "__main__": 12if __name__ == "__main__":
15 raise SystemExit(main()) 13 raise SystemExit(main())
sampling/sampling_tool/cli.py +1 −1
@@ -2,11 +2,11 @@
2 2
3from __future__ import annotations 3from __future__ import annotations
4 4
5import sys
5from argparse import ArgumentParser, Namespace 6from argparse import ArgumentParser, Namespace
6from datetime import datetime, timezone 7from datetime import datetime, timezone
7from pathlib import Path 8from pathlib import Path
8from types import SimpleNamespace 9from types import SimpleNamespace
9import sys
10 10
11import pandas as pd 11import pandas as pd
12 12
sampling/sampling_tool/io.py +1 −2
@@ -2,12 +2,11 @@
2 2
3from __future__ import annotations 3from __future__ import annotations
4 4
5from pathlib import Path
6import hashlib 5import hashlib
6from pathlib import Path
7 7
8import pandas as pd 8import pandas as pd
9 9
10
11SUPPORTED_EXCEL_SUFFIXES = {".xlsx", ".xls", ".xlsm"} 10SUPPORTED_EXCEL_SUFFIXES = {".xlsx", ".xls", ".xlsm"}
12 11
13 12
sampling/sampling_tool/manifest.py +1 −1
@@ -2,8 +2,8 @@
2 2
3from __future__ import annotations 3from __future__ import annotations
4 4
5from pathlib import Path
6import json 5import json
6from pathlib import Path
7 7
8from . import __version__ 8from . import __version__
9 9
sampling/sampling_tool/validation.py +1 −1
@@ -69,7 +69,7 @@ def validate_and_prepare(population: pd.DataFrame, options) -> ValidationResult:
69 keep=False 69 keep=False
70 ) 70 )
71 duplicate_rows = working.loc[nonblank_ids].loc[duplicate_mask].copy() 71 duplicate_rows = working.loc[nonblank_ids].loc[duplicate_mask].copy()
72 duplicate_id_count = int(len(duplicate_rows)) 72 duplicate_id_count = len(duplicate_rows)
73 if duplicate_id_count: 73 if duplicate_id_count:
74 if options.dedupe_id == "fail": 74 if options.dedupe_id == "fail":
75 raise AuditSamplingError( 75 raise AuditSamplingError(
sampling/stratified_sample.py +2 −1
@@ -1,7 +1,8 @@
1# Import packages 1# Import packages
2import pandas as pd
3import math 2import math
4 3
4import pandas as pd
5
5# Load data 6# Load data
6df = pd.read_csv("FILENAME_GOES_HERE.csv") 7df = pd.read_csv("FILENAME_GOES_HERE.csv")
7 8
tui/app.py +1 −1
@@ -226,7 +226,7 @@ class RunScreen(Screen):
226 keys, 226 keys,
227 lambda ev: self.app.call_from_thread(self._handle_event, ev), 227 lambda ev: self.app.call_from_thread(self._handle_event, ev),
228 ) 228 )
229 except Exception as e: # noqa: BLE001 - report unexpected failures in the UI 229 except Exception as e:
230 self.app.call_from_thread(self._log, f"[red]Run failed:[/] {e}") 230 self.app.call_from_thread(self._log, f"[red]Run failed:[/] {e}")
231 finally: 231 finally:
232 self.app.call_from_thread(self._finish) 232 self.app.call_from_thread(self._finish)
tui/github_runner.py +2 −2
@@ -147,7 +147,7 @@ def run_audit(
147 on_event(ProgressEvent("fetch", "Repo collaborators (shared cache)")) 147 on_event(ProgressEvent("fetch", "Repo collaborators (shared cache)"))
148 try: 148 try:
149 repo_collabs = members.fetch_repo_collaborators(org, cfg) 149 repo_collabs = members.fetch_repo_collaborators(org, cfg)
150 except Exception as e: # noqa: BLE001 - surface, keep going 150 except Exception as e:
151 on_event( 151 on_event(
152 ProgressEvent( 152 ProgressEvent(
153 "error", "Repo collaborators (shared cache)", message=str(e) 153 "error", "Repo collaborators (shared cache)", message=str(e)
@@ -165,7 +165,7 @@ def run_audit(
165 rows = c.fn(org, cfg, branch) 165 rows = c.fn(org, cfg, branch)
166 else: 166 else:
167 rows = c.fn(org, cfg) 167 rows = c.fn(org, cfg)
168 except Exception as e: # noqa: BLE001 - one bad check shouldn't kill the run 168 except Exception as e:
169 on_event(ProgressEvent("error", c.label, message=str(e))) 169 on_event(ProgressEvent("error", c.label, message=str(e)))
170 sections.append((c.label, 0)) 170 sections.append((c.label, 0))
171 continue 171 continue
tui/gitlab_runner.py +2 −2
@@ -136,7 +136,7 @@ def run_audit(
136 on_event(ProgressEvent("fetch", "Projects (shared cache)")) 136 on_event(ProgressEvent("fetch", "Projects (shared cache)"))
137 try: 137 try:
138 project_cache = projects.fetch_projects(group, cfg) 138 project_cache = projects.fetch_projects(group, cfg)
139 except Exception as e: # noqa: BLE001 - surface, keep going 139 except Exception as e:
140 on_event(ProgressEvent("error", "Projects (shared cache)", message=str(e))) 140 on_event(ProgressEvent("error", "Projects (shared cache)", message=str(e)))
141 project_cache = [] 141 project_cache = []
142 142
@@ -148,7 +148,7 @@ def run_audit(
148 rows = c.fn(group, cfg, project_cache or []) 148 rows = c.fn(group, cfg, project_cache or [])
149 else: 149 else:
150 rows = c.fn(group, cfg) 150 rows = c.fn(group, cfg)
151 except Exception as e: # noqa: BLE001 - one bad check shouldn't kill the run 151 except Exception as e:
152 on_event(ProgressEvent("error", c.label, message=str(e))) 152 on_event(ProgressEvent("error", c.label, message=str(e)))
153 sections.append((c.label, 0)) 153 sections.append((c.label, 0))
154 continue 154 continue