audit-labs/audit-report
Turn audit-tools evidence packages into control-mapped, auditor-ready reports.
clone: git clone https://gitbay.org/audit-labs/audit-report.git
main:
| 040000 | .github/ | |
| 100644 | .gitignore | 177 |
| 100644 | CHANGELOG.md | 1234 |
| 100644 | CODEOWNERS | 21 |
| 100644 | LICENSE | 35149 |
| 100644 | MAPPING.md | 1940 |
| 100644 | README.md | 7739 |
| 040000 | audit_report/ | |
| 100644 | conftest.py | 347 |
| 040000 | docs/ | |
| 040000 | examples/ | |
| 100644 | pyproject.toml | 869 |
| 100644 | requirements-dev.txt | 34 |
| 100644 | requirements.txt | 12 |
| 100644 | ruff.toml | 709 |
| 040000 | tests/ |
audit-report
Turns an audit-tools evidence package into a control-mapped, auditor-ready report — Markdown, self-contained HTML, or JSON — driven by declarative rulesets.
audit-tools collects raw CSVs (IAM users, branch protections, security
groups…). audit-report reads that package, applies pass/fail rules, maps each
result to SOC 2, ISO 27001, and NIST SP 800-53 controls, and produces a
report you can hand to an auditor or gate a pipeline on.
Like gh-attest, this tool produces evidence, not a compliance verdict. A failing row means a setting is in a state that does not support a control; the final judgment belongs to the organization and its auditor.
Install
pip install audit-report
To hack on it from a clone instead, see Development.
Usage
Point it at a package directory produced by audit-tools:
# Markdown report to stdout (platform + ruleset auto-detected from the dir name)
audit-report ./output/aws_audit_default_2026-07-29
# All three formats into ./report/
audit-report ./output/github_audit_acme_2026-07-29 --format md,html,json --out report/
# Gate CI: exit non-zero if any high-severity check fails
audit-report ./output/aws_audit_prod_2026-07-29 --fail-on high --out report/
Diff mode — compare two packages
Pass --baseline to report how a package drifted from an earlier one. Both
are evaluated with the same ruleset; the report classifies each rule as
regressed, fixed, drifted (an ongoing failure whose evidence rows changed),
changed, or unchanged — and shows exactly which evidence rows appeared or
disappeared.
# How did prod change between two audit runs?
audit-report ./output/aws_audit_prod_2026-07-29 \
--baseline ./output/aws_audit_prod_2026-06-29 \
--format md,html --out drift/
# Fail CI if this change regressed any high-severity control
audit-report ./output/aws_audit_prod_2026-07-29 \
--baseline ./output/aws_audit_prod_2026-06-29 --fail-on high
In diff mode --fail-on gates on regressions at or above the given
severity, and output files are named diff.* instead of report.*.
Trend mode — track controls over time
Pass --trend and point at a folder of dated packages (for example
audit-tools' output/). Every package in the series is evaluated with the same
ruleset and laid out as a timeline — one row per rule, one column per date — so
you can watch a control drift in and out of compliance.
# Heatmap of every control across all retained prod packages
audit-report ./output --trend --format html,json --out trend/
# Disambiguate when the folder holds more than one series
audit-report ./output --trend --subject prod --out trend/
The series must share one platform and subject (use --subject to pick one) and
contain at least two packages. In trend mode --fail-on reflects the latest
package, so it can double as a snapshot gate. Output files are named trend.*.
You can also run it without installing:
python -m audit_report ./output/aws_audit_default_2026-07-29
Options
| Flag | Description |
| --- | --- |
| --baseline PATH | Diff mode: report how PACKAGE drifted from this earlier package. |
| --trend | Trend mode: treat PACKAGE as a folder of dated packages and chart each rule over time. |
| --subject NAME | In trend mode, pick one subject when the folder holds several series. |
| --ruleset PATH | Use a specific ruleset instead of the bundled one for the detected platform. |
| --format md,html,json | One or more output formats (default: md). |
| --out DIR | Write report.<ext> files into DIR. Without it, the first format prints to stdout. |
| --fail-on low\|medium\|high\|none | Exit non-zero when a failing finding — or, in diff mode, a regression — meets this severity (default: none). |
What a report contains
- Summary — failing / passing / not-applicable counts.
- Control coverage matrix — every cited control, its framework, and a worst-wins status rolled up from the rules that reference it. One failing rule marks the control as not fully evidenced.
- Findings — failures first, then by severity, each with the reason, why it matters, remediation, and the exact evidence rows that failed.
Bundled rulesets
| Platform | Package prefix | Checks include |
| --- | --- | --- |
| AWS | aws_audit_* | Console-user MFA, access-key rotation, root MFA & keys, password policy, open SSH, public S3, CloudTrail logging |
| GitHub | github_audit_* | Org 2FA, base permission, secret-scanning push protection, default-branch protection, required reviews |
| GitLab | gitlab_audit_* | Force-push on protected branches, code-owner approval, approval-rule strength, public projects, instance password policy, audit logging |
Writing your own rules
A ruleset is a YAML file: a platform and a list of rules. Each rule names a
CSV table, a check, the controls it maps to, and human-readable text. Point at
one with --ruleset.
platform: aws
rules:
- id: aws.iam.console-mfa
title: Console users have MFA enabled
table: iam_users # <table>.csv inside the package
severity: high # high | medium | low
controls: [SOC2:CC6.1, ISO:A.5.17, NIST:IA-2]
rationale: A console user without a second factor is one stolen password from access.
remediation: Enforce MFA for all console users, or remove their console password.
check:
type: fail_rows_where # every matching row is a failure
when:
all:
- {column: console_password, op: is_true}
- {column: mfa_enabled, op: is_false}
Check types
| Type | Meaning |
| --- | --- |
| fail_rows_where | Each row matching when is a failing finding. |
| fail_if_any_rows | The presence of any row (optionally filtered by when) is a failure. |
| require_any_row | Passes only if at least one row satisfies when. |
| assert_row | Checks each condition in require against the first row of a single-row config table. |
Condition language — a condition is a leaf {column, op, value}, or one of
{all: [...]}, {any: [...]}, {not: ...}. Operators: equals, not_equals,
is_true, is_false, in, not_in, gt, gte, lt, lte, empty,
not_empty. Control codes referenced by a rule must exist in
audit_report/catalog.py.
Continuous integration
Run it on a schedule or in a pipeline to keep evidence current and gate on regressions. See docs/ci.md and the ready-to-copy examples:
The --fail-on exit code (1 = a finding/regression met the threshold, 2 =
usage error) lets a workflow separate "the audit found a problem" from "the job
is misconfigured".
Stability
audit-report is stable as of v1.0.0 and follows semantic versioning.
The JSON report schema — findings carrying their controls and
pass / fail / not_applicable status — is a committed contract that
control-coverage consumes
directly; it will not break without a major-version bump.
Development
pip install -e ".[dev]"
pytest # run from the project root
ruff check .
License
GPL-3.0-or-later. See LICENSE.