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 .gitignore177
100644 CHANGELOG.md1234
100644 CODEOWNERS21
100644 LICENSE35149
100644 MAPPING.md1940
100644 README.md7739
040000 audit_report/
100644 conftest.py347
040000 docs/
040000 examples/
100644 pyproject.toml869
100644 requirements-dev.txt34
100644 requirements.txt12
100644 ruff.toml709
040000 tests/

audit-report

License: GPL v3 Python

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

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.