audit-labs/audit-tools

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

clone: git clone https://gitbay.org/audit-labs/audit-tools.git

v1.0.0: applications/github/audit.py · raw

  1"""
  2GitHub audit CLI.
  3
  4Runs all collectors against a GitHub organization and writes a timestamped
  5audit package to an output directory.
  6
  7Usage:
  8    export GITHUB_TOKEN=your_token
  9    export GITHUB_ORG=your_org
 10
 11    python audit.py
 12    python audit.py --org my-org
 13    python audit.py --org my-org --out ./output
 14    python audit.py --org my-org --branch main
 15
 16Output:
 17    <out>/github_audit_<org>_<date>/
 18        member_roster.csv
 19        two_factor_disabled.csv
 20        outside_collaborators.csv
 21        privileged_access.csv
 22        pending_invitations.csv
 23        team_permissions.csv
 24        permission_matrix.csv
 25        branch_protections.csv
 26        commits.csv
 27        audit_log.csv
 28        summary.txt
 29"""
 30
 31import argparse
 32import os
 33import sys
 34from datetime import date
 35
 36import config
 37from collectors import (
 38    audit_log,
 39    branch_protections,
 40    commits,
 41    deploy_keys,
 42    members,
 43    org_settings,
 44    security_alerts,
 45    webhooks,
 46)
 47from reporters import csv_reporter
 48
 49
 50def parse_args():
 51    parser = argparse.ArgumentParser(
 52        description="Generate a GitHub audit package for an organization."
 53    )
 54    parser.add_argument(
 55        "--org",
 56        help="GitHub organization name. Overrides GITHUB_ORG env var.",
 57    )
 58    parser.add_argument(
 59        "--out",
 60        default="./output",
 61        help="Directory to write the audit package into. Default: ./output",
 62    )
 63    parser.add_argument(
 64        "--branch",
 65        default="main",
 66        help="Branch to collect commits from. Default: main",
 67    )
 68    parser.add_argument(
 69        "--include-audit-log",
 70        action="store_true",
 71        help=argparse.SUPPRESS,
 72    )
 73    return parser.parse_args()
 74
 75
 76def run():
 77    args = parse_args()
 78    cfg = config.load(org_override=args.org)
 79    org = cfg["org"]
 80
 81    output_dir = os.path.join(
 82        args.out, f"github_audit_{org}_{date.today().isoformat()}"
 83    )
 84
 85    print(f"GitHub Audit — {org}")
 86    print(f"Output directory: {output_dir}")
 87    print()
 88
 89    sections = []
 90
 91    def collect(label, fn, filename, *fn_args):
 92        print(f"Collecting: {label}...")
 93        try:
 94            rows = fn(*fn_args)
 95        except Exception as e:
 96            print(f"  Error: {e}", file=sys.stderr)
 97            rows = []
 98        csv_reporter.write(output_dir, filename, rows)
 99        sections.append((label, len(rows)))
100        return rows
101
102    collect("Member roster", members.member_roster, "member_roster.csv", org, cfg)
103    collect(
104        "2FA disabled", members.two_factor_disabled, "two_factor_disabled.csv", org, cfg
105    )
106
107    print("Fetching repo collaborators (shared cache)...")
108    try:
109        repo_collabs = members.fetch_repo_collaborators(org, cfg)
110    except Exception as e:
111        print(f"  Error fetching collaborators: {e}", file=sys.stderr)
112        repo_collabs = []
113
114    collect(
115        "Outside collaborators",
116        members.outside_collaborators,
117        "outside_collaborators.csv",
118        org,
119        cfg,
120        repo_collabs,
121    )
122    collect(
123        "Privileged access",
124        members.privileged_access,
125        "privileged_access.csv",
126        org,
127        cfg,
128        repo_collabs,
129    )
130    collect(
131        "Pending invitations",
132        members.pending_invitations,
133        "pending_invitations.csv",
134        org,
135        cfg,
136    )
137    collect(
138        "Team permissions", members.team_permissions, "team_permissions.csv", org, cfg
139    )
140    collect(
141        "Permission matrix",
142        members.permission_matrix,
143        "permission_matrix.csv",
144        org,
145        cfg,
146        repo_collabs,
147    )
148    collect(
149        "Branch protections",
150        branch_protections.branch_protections,
151        "branch_protections.csv",
152        org,
153        cfg,
154    )
155    collect("Commits", commits.commits, "commits.csv", org, cfg, args.branch)
156    collect(
157        "Org security settings", org_settings.org_security, "org_security.csv", org, cfg
158    )
159    collect("Webhooks", webhooks.webhooks, "webhooks.csv", org, cfg)
160    collect("Deploy keys", deploy_keys.deploy_keys, "deploy_keys.csv", org, cfg)
161    collect(
162        "Secret scanning alerts",
163        security_alerts.secret_scanning,
164        "secret_scanning.csv",
165        org,
166        cfg,
167    )
168    collect(
169        "Dependabot alerts",
170        security_alerts.dependabot_alerts,
171        "dependabot_alerts.csv",
172        org,
173        cfg,
174    )
175    collect(
176        "Audit log branch/ruleset changes",
177        audit_log.audit_log,
178        "audit_log.csv",
179        org,
180        cfg,
181    )
182
183    print()
184    csv_reporter.write_summary(output_dir, org, sections)
185    print()
186    print("Done.")
187
188
189if __name__ == "__main__":
190    run()