Skip to main content

control-coverage

License: GPL v3 Python

Control-first coverage and blind-spot analysis over an evidence corpus.

The rest of the Audit Labs toolchain is evidence-first: audit-tools collects raw signals, audit-report maps each finding onto the controls it touches, and evidence-seal proves the package is authentic. That answers "what did I collect, and what does it map to?" — but it can never tell you what you are not looking at, because it has no list of everything a framework requires.

control-coverage supplies that missing list — the denominator. It starts from the complete catalog of a framework's controls and scores your evidence against it, so it can report two numbers nothing else in the pipeline can:

  • Coverage % — of everything the framework requires, how much the evidence corpus addresses at all.
  • Blind spots — the in-scope controls that no finding touches. These are the gaps an auditor finds for you if you don't find them first.

It also produces a Statement of Applicability — the ISO 27001 artifact that lists every Annex A control, whether it applies, and why — derived from your evidence instead of hand-maintained.

Like every Audit Labs tool, this produces evidence, not a verdict. An unaddressed control is a gap in evidence, which may reflect a real gap in controls or simply a signal not yet collected. The final judgment belongs to the organization and its auditor.

Install

pip install control-coverage

Pure standard library plus PyYAML — no other dependencies. To hack on it from a clone instead, see Development.

Usage

The input is one or more JSON reports from audit-report (its --format json output). A corpus is typically one report per platform and date — AWS, GitHub, GitLab — which control-coverage folds into a single per-framework picture.

# Coverage across every framework the corpus cites, Markdown to stdout
control-coverage aws.json github.json

# Just the blind spots — the controls nothing evidences yet
control-coverage aws.json github.json --framework SOC2 --blind-spots

# A whole directory of reports, all formats into ./out/
control-coverage ./reports/ --format md,html,json,soa --out out/

# Gate CI: exit non-zero if any framework's coverage is under 60%
control-coverage ./reports/ --fail-under 60

Trend — how coverage moved

Point --baseline at an earlier corpus (a file or a directory) to see what changed: controls that improved, regressed, and — the two that move the coverage number — were gained (a blind spot became addressed) or lost (an addressed control became a blind spot).

control-coverage ./reports/2026-02/ --baseline ./reports/2026-01/ --framework SOC2

# Gate CI: fail the build if any control regressed or lost coverage
control-coverage ./reports/2026-02/ --baseline ./reports/2026-01/ --fail-on-regression

Trend mode outputs Markdown, HTML, or JSON (--format md,html,json).

Crosswalk — evidence leverage and the minimal set

One check is rarely worth one control: enforced 2FA is evidence for SOC 2 CC6.1, ISO A.5.17, and NIST IA-2 at once. --crosswalk shows that leverage per check and computes the minimal evidence set — the fewest checks that still touch every addressed control, which is what you want when scoping a walkthrough or a sample.

control-coverage ./reports/ --framework SOC2,ISO,NIST --crosswalk

Crosswalk mode outputs Markdown, HTML, or JSON (--format md,html,json).

Scope and the Statement of Applicability

Not every control applies to every organization. A scope file records which controls are excluded and — required, never optional — why:

# soa.yaml
subject: Acme Production
frameworks: [SOC2, ISO]
exclusions:
  - control: ISO:A.7.1
    reason: "Fully cloud-hosted; no physical premises are in scope for the ISMS."
  - control: ISO:A.5.7
    reason: "No formal threat-intelligence program; risk accepted by the CISO for 2026."
owners:
  SOC2:CC6.1: platform-team
# Coverage over in-scope controls, plus a ready-to-file SoA
control-coverage ./reports/ --scope soa.yaml --format md,soa --out out/

Excluded controls are recorded with their justification rather than counted as gaps. An exclusion with no reason is rejected — an unjustified exclusion is the single most common SoA audit finding.

Assurance states

Every in-scope control lands in exactly one state:

State Meaning
supported At least one mapped finding passes, and none fail.
failing At least one mapped finding fails. The worst observation wins.
asserted Findings map here, but their data was absent — evidence attempted, not obtained.
unaddressed No finding maps here at all. The blind spot.
out of scope Excluded by the scope file, with a recorded justification.

coverage % is the share of in-scope controls in any of the first three states; assured % is the share that are supported.

Bundled catalogs

Framework Code Catalog
SOC 2 (Trust Services Criteria) SOC2 Complete — all five categories (Common Criteria + Availability, Confidentiality, Processing Integrity, Privacy), 61 controls
ISO/IEC 27001:2022 Annex A ISO Complete — all 93 controls
NIST SP 800-53 Rev. 5 NIST Moderate baseline — 177 base controls across 18 families

Most SOC 2 reports scope only some categories (Security is near-universal; Privacy and Processing Integrity often are not). Use exclude_families in the scope file to drop a whole category — or an ISO theme, or a NIST family — from the denominator in one line:

exclude_families:
  - {framework: SOC2, family: Privacy, reason: "Privacy category not in the SOC 2 audit scope."}
  - {framework: SOC2, family: Processing Integrity, reason: "PI category not in the SOC 2 audit scope."}

Control codes are written FRAMEWORK:ID (SOC2:CC6.1, ISO:A.5.17), matching the codes audit-report rulesets already cite. A partial catalog is reported honestly as coverage of the shipped subset, never as the whole standard.

If the corpus cites a code whose framework is loaded but the catalog does not define it — a typo or a renamed control — it is surfaced as an unmatched control code rather than silently ignored.

How it fits the pipeline

audit-tools ──► CSV package ──► evidence-seal (seal + verify)
                     │
                     ▼
               audit-report ──► per-package report (--format json)
                                        │
                                        ▼  one or more reports = a corpus
                               control-coverage ──► coverage %, blind spots, SoA,
                                                    trend over time, evidence crosswalk

Stability

control-coverage is stable as of v1.0.0 and follows semantic versioning. It reads audit-report's v1 JSON contract; the bundled catalogs, the coverage / Statement of Applicability / JSON output schemas, and the --fail-under gate are committed within the 1.x line.

Development

pip install -e ".[dev]"
pytest
ruff check .

License

GPL-3.0-or-later. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

control_coverage-1.0.0.tar.gz (51.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

control_coverage-1.0.0-py3-none-any.whl (51.5 kB view details)

Uploaded Python 3

File details

Details for the file control_coverage-1.0.0.tar.gz.

File metadata

  • Download URL: control_coverage-1.0.0.tar.gz
  • Upload date:
  • Size: 51.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for control_coverage-1.0.0.tar.gz
Algorithm Hash digest
SHA256 adfb47d4c38a612b91f455a8004b8bf842adf431ea986fc2f4dd2fc3551c43e5
MD5 78cde9c9169ec3d1051fc6ace56bc897
BLAKE2b-256 34e1f2173d520474ec6e8c1ca7570313e2e85b3cebf6d88774c4162769ab071d

See more details on using hashes here.

Provenance

The following attestation bundles were made for control_coverage-1.0.0.tar.gz:

Publisher: release.yml on audit-labs/control-coverage

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file control_coverage-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for control_coverage-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 663df94a00f1dee30c87f2c70f9e92cf40fec37eb19a877a090bdeb4add8d499
MD5 a5907fc12fe92d149c6258635d815e54
BLAKE2b-256 df07f5b9e1f954da03b3f6b7de6705bc6776d6a942ede8982f17e5fc872919e0

See more details on using hashes here.

Provenance

The following attestation bundles were made for control_coverage-1.0.0-py3-none-any.whl:

Publisher: release.yml on audit-labs/control-coverage

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page