Skip to main content

Zero-dependency linter for Python, Terraform, Dockerfiles, Kubernetes, and GitHub Actions — catches AI slop and security misconfigurations pre-commit

Project description

proofctl

Zero-dependency Python linter and infrastructure policy checker for pre-commit hooks. Detects AI-generated slop, security misconfigurations, and quality regressions across Python, Terraform (AWS/GCP/Azure), Dockerfiles, Kubernetes manifests, and GitHub Actions workflows.

proofctl check .
PROOFCTL-S-001  module/auth.py:42    SQL injection via string format in .execute()          ERROR
PROOFCTL-Q-001  module/models.py:17  Mutable default argument — list assigned at def time   ERROR
PROOFCTL-P-001  module/tasks.py:89   Unimplemented function body (pass / ...)               ERROR
PROOFCTL-TF-G004 infra/main.tf:14   GKE node pool has auto-repair disabled                 WARNING
──────────────────────────────────────────────────────────────────
4 findings  (3 ERROR, 1 WARNING)  exit 2

Why proofctl

AI coding assistants generate plausible-looking code that is frequently:

  • Insecure — SQL/command injection, hardcoded secrets, weak crypto, JWT bypass
  • Broken — hallucinated imports, phantom methods, pass bodies shipped as real functions
  • Fragile — mutable defaults, broad except:, no timeouts, untested paths
  • Wasteful — near-duplicate files, single-implementation abstractions, TODO bombs

proofctl is a pre-commit linter purpose-built to catch all of this before it merges. It runs in under a second on a typical repo, requires no internet access, and adds zero runtime dependencies to your project.


Installation

pip install proofctl

Requires: Python 3.11+


Quick start

# Scan the current directory
proofctl check .

# Scan a single file
proofctl check src/api/auth.py

# Only report ERROR findings
proofctl check . --min-severity ERROR

# Exit non-zero only on ERROR (useful for CI gate)
proofctl check . --fail-on ERROR

# Scan only files changed in this branch
proofctl check . --changed-only

# Run only security and quality families
proofctl check . --families S,Q

# Generate an HTML report
proofctl check . --format html --output report.html

# List all rules
proofctl rules

Pre-commit integration

Add to .pre-commit-config.yaml:

repos:
  - repo: local
    hooks:
      - id: proofctl
        name: proofctl
        language: system
        entry: proofctl check
        args: [--no-pypi, --fail-on, ERROR]
        types: [python]
        pass_filenames: false

Or pin from the published GitHub repo:

repos:
  - repo: https://github.com/kolawoluu/proofctl
    rev: v0.1.0
    hooks:
      - id: proofctl

CI integration (GitHub Actions)

name: proofctl

on: [push, pull_request]

jobs:
  proofctl:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install proofctl

      # --new-only compares against the committed baseline snapshot —
      # CI fails only on regressions introduced in this PR.
      - run: proofctl check . --no-pypi --fail-on ERROR --new-only

      - name: Generate HTML report
        if: failure()
        run: proofctl check . --no-pypi --format html --output proofctl-report.html || true

      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: proofctl-report
          path: proofctl-report.html
          retention-days: 14

Create a baseline snapshot to suppress pre-existing findings:

proofctl baseline .
git add .proofctl-baseline.json
git commit -m "chore: add proofctl baseline"

Rules

Python — Placeholder (P)

Rule Severity Description
PROOFCTL-P-001 ERROR Unimplemented function body (pass, ..., raise NotImplementedError)
PROOFCTL-P-002 WARNING TODO / FIXME / HACK / XXX comment present
PROOFCTL-P-003 INFO Commented-out code block

Python — Quality (Q)

Rule Severity Description
PROOFCTL-Q-001 ERROR Mutable default argument (list, dict, set at def time)
PROOFCTL-Q-002 ERROR Bare except: or broad except Exception with no re-raise
PROOFCTL-Q-003 WARNING Excessive Any usage or unexplained # type: ignore
PROOFCTL-Q-004 INFO Single-implementation abstraction (YAGNI violation)
PROOFCTL-Q-005 WARNING/ERROR High cyclomatic complexity (warn >10, error >20)
PROOFCTL-Q-006 WARNING/ERROR Function body too long (warn >50 lines, error >100 lines)
PROOFCTL-Q-007 WARNING/ERROR Too many parameters (warn >5, error >7)
PROOFCTL-Q-008 WARNING Boolean literal as positional argument (flag argument)
PROOFCTL-Q-009 INFO print() in non-test code instead of logging

Python — Security (S)

Rule Severity Description Authority
PROOFCTL-S-001 ERROR SQL injection via string formatting in .execute() OWASP A03, CWE-89
PROOFCTL-S-002 ERROR Command injection via shell=True or os.system() OWASP A03, CWE-78
PROOFCTL-S-003 ERROR Unsafe deserialization (pickle.loads, yaml.load without SafeLoader) OWASP A08, CWE-502
PROOFCTL-S-004 ERROR Weak cryptographic primitive (md5/sha1 for auth, random for secrets) OWASP A02, CWE-327
PROOFCTL-S-005 ERROR eval() / exec() on non-literal input CWE-95
PROOFCTL-S-006 WARNING Missing timeout= on HTTP calls (requests, httpx) CWE-400
PROOFCTL-S-007 ERROR JWT signature bypass (verify_signature: False, algorithms: ["none"]) OWASP A07
PROOFCTL-S-008 ERROR Path traversal — open() with user-supplied path CWE-22
PROOFCTL-S-009 ERROR Insecure UUID for security token (uuid1/uuid3 for token/secret/key) CWE-338
PROOFCTL-S-010 WARNING Secret value leaked into logs (password/token in logging.* call) OWASP A09
PROOFCTL-S-011 ERROR/WARNING Insecure cipher mode (ECB, ARC4, TripleDES) OWASP A02
PROOFCTL-S-012 WARNING Regex injection — non-literal pattern passed to re.compile/re.search CWE-625
PROOFCTL-S-013 WARNING Open redirect — redirect() with user-supplied URL CWE-601

Python — Leakage (L)

Rule Severity Description
PROOFCTL-L-001 ERROR Hardcoded secret or credential in source
PROOFCTL-L-002 WARNING PII pattern in source (email, SSN, phone number)
PROOFCTL-L-003 WARNING Cross-language idiom (JS/Java/Go pattern in Python)

Python — Imports (I)

Rule Severity Description
PROOFCTL-I-001 WARNING Import from unknown or unresolvable package (hallucination)
PROOFCTL-I-002 ERROR High-risk or deprecated package (telnetlib, pickle, etc.)

Python — Methods (M)

Rule Severity Description
PROOFCTL-M-001 WARNING Method too complex — abstraction opportunity

Python — Variants (V)

Rule Severity Description
PROOFCTL-V-001 WARNING Near-duplicate function bodies (DRY violation)
PROOFCTL-V-002 INFO Near-duplicate file (Jaccard similarity above threshold)

Python — LLM Guardrails (LLM)

Rule Severity Description Authority
PROOFCTL-LLM-001 ERROR Unsanitised user input in LLM prompt (f-string in content=) OWASP LLM01
PROOFCTL-LLM-002 WARNING Missing max_tokens on LLM API call OWASP LLM10
PROOFCTL-LLM-003 WARNING LLM API call inside a loop (unbounded cost) OWASP LLM10
PROOFCTL-LLM-004 WARNING PII variable name in LLM prompt OWASP LLM02
PROOFCTL-LLM-005 ERROR while True: agentic loop with LLM call and no iteration guard OWASP LLM06

Terraform — General (TF-T)

Rule Severity Description
PROOFCTL-TF-T001 ERROR Empty or null resource block
PROOFCTL-TF-T002 WARNING count = 0 resource
PROOFCTL-TF-T003 ERROR Hardcoded secret in resource attribute
PROOFCTL-TF-T004 WARNING Wildcard IAM permission (*)
PROOFCTL-TF-T005 WARNING lifecycle { ignore_changes = all }
PROOFCTL-TF-T006 WARNING Local-only module source (no registry or git ref)
PROOFCTL-TF-T007 ERROR Mutable git ref in module source (branch / HEAD)
PROOFCTL-TF-T008 WARNING Missing description on variable
PROOFCTL-TF-T009 WARNING Missing description on output
PROOFCTL-TF-T010 WARNING Null-default variable without validation block
PROOFCTL-TF-T011 ERROR Remote state without encryption
PROOFCTL-TF-T012 WARNING Provisioner block (prefer cloud-init or user_data)
PROOFCTL-TF-T013 ERROR Missing required resource labels
PROOFCTL-TF-T014 WARNING No terraform { required_version } constraint
PROOFCTL-TF-T015 WARNING Sensitive-named output without sensitive = true

Terraform — GCP (TF-G)

25 rules covering GCP compute, Cloud SQL, GKE, Cloud Storage, IAM, KMS, VPC, Cloud Run, BigQuery, Pub/Sub, and Cloud Functions. Highlights:

  • PROOFCTL-TF-G001 — GKE node pool with auto-repair disabled
  • PROOFCTL-TF-G004 — Cloud SQL with public IP
  • PROOFCTL-TF-G008 — Storage bucket with allUsers ACL
  • PROOFCTL-TF-G015 — GCS bucket without versioning
  • PROOFCTL-TF-G026 — IAM binding with allUsers / allAuthenticatedUsers
  • PROOFCTL-TF-G030 — BigQuery dataset accessible to allAuthenticatedUsers

Terraform — AWS (TF-A)

14 rules covering EC2 IMDSv2, RDS, EKS, EBS, Lambda, ElastiCache, S3, ECR, CloudTrail, and VPC flow logs. Highlights:

  • PROOFCTL-TF-A001 — EC2 without IMDSv2 enforced
  • PROOFCTL-TF-A003 — RDS with public access
  • PROOFCTL-TF-A011 — No CloudTrail resource in file
  • PROOFCTL-TF-A014 — Lambda function with hardcoded secret in env vars

Terraform — Azure (TF-AZ)

12 rules covering Azure storage, SQL/Postgres/MySQL, AKS, Key Vault, Monitor, RBAC, App Service, and VMs. Highlights:

  • PROOFCTL-TF-AZ001 — Storage account with allow_blob_public_access = true
  • PROOFCTL-TF-AZ003 — SQL server with public_network_access_enabled = true
  • PROOFCTL-TF-AZ005 — AKS cluster with unrestricted API server access
  • PROOFCTL-TF-AZ007 — Key Vault without purge protection
  • PROOFCTL-TF-AZ011 — App Service without https_only = true

Terraform — Mechanics (TF-M) and Lifecycle (TF-V)

7 + 4 rules covering security group mixing, ignore_changes = all, external provisioners, force_destroy, prevent_destroy, and mutable module refs.

Terragrunt (TG)

6 rules covering var.* in HCL inputs, missing mock_outputs, invalid if_exists values, and unencrypted remote state.

Dockerfile (DF)

Rule Severity Description
PROOFCTL-DF-001 WARNING FROM uses latest tag
PROOFCTL-DF-002 ERROR Running as root (USER root or no USER directive)
PROOFCTL-DF-003 WARNING apt-get install without --no-install-recommends
PROOFCTL-DF-004 ERROR Secret in ARG or ENV (password/token/key name)
PROOFCTL-DF-005 WARNING ADD used where COPY is sufficient
PROOFCTL-DF-006 INFO Multiple RUN commands that could be merged
PROOFCTL-DF-007 ERROR curl/wget output piped directly to shell
PROOFCTL-DF-008 WARNING ADD from URL without checksum verification
PROOFCTL-DF-009 WARNING COPY of dependency manifest without pinned install
PROOFCTL-DF-010 INFO Missing OCI image labels (org.opencontainers.image.*)

YAML / Kubernetes (YAML-K8S)

13 rules based on Kubernetes Pod Security Standards. Highlights:

Rule Severity Description
PROOFCTL-YAML-009 ERROR Container missing runAsNonRoot: true
PROOFCTL-YAML-010 ERROR Container missing allowPrivilegeEscalation: false
PROOFCTL-YAML-011 WARNING Container missing readOnlyRootFilesystem: true
PROOFCTL-YAML-012 WARNING Container missing drop: [ALL] capabilities
PROOFCTL-YAML-013 WARNING Pod missing seccompProfile annotation
PROOFCTL-YAML-014 WARNING automountServiceAccountToken: true on pod or service account
PROOFCTL-YAML-015 ERROR ClusterRoleBinding to cluster-admin
PROOFCTL-YAML-016 WARNING RBAC rule with wildcard verb or resource (*)
PROOFCTL-YAML-017 ERROR RBAC rule with escalate/impersonate/bind verb
PROOFCTL-YAML-018 WARNING Deployment/DaemonSet container missing liveness or readiness probe
PROOFCTL-YAML-019 WARNING Ingress without TLS configuration
PROOFCTL-YAML-020 ERROR Plaintext secret in environment variable (value: on secret-named var)
PROOFCTL-YAML-021 WARNING Namespace-scoped RoleBinding using cluster-admin

YAML / GitHub Actions (YAML-GHA)

Rule Severity Description
PROOFCTL-YAML-007 ERROR Expression injection — ${{ github.event.* }} in run: step
PROOFCTL-YAML-GHA-001 ERROR pull_request_target with actions/checkout of PR head ref
PROOFCTL-YAML-GHA-002 WARNING Workflow missing top-level permissions: block
PROOFCTL-YAML-GHA-003 WARNING Secret passed directly to environment variable in workflow
PROOFCTL-YAML-GHA-004 ERROR ACTIONS_ALLOW_UNSECURE_COMMANDS: true in workflow
PROOFCTL-YAML-GHA-005 WARNING Job missing timeout-minutes

Suppressing findings

Inline suppression for a single line:

result = subprocess.run(cmd, shell=True)  # proofctl: ignore[PROOFCTL-S-002]

Project-wide suppression in .proofctl.yaml:

disable:
  - PROOFCTL-Q-004   # YAGNI check too noisy for this repo

Configuration

Place .proofctl.yaml in the project root:

exclude_paths:
  - "**/.venv/**"
  - "**/migrations/**"
  - "**/.terraform/**"

disable:
  - PROOFCTL-Q-004

severity_overrides:
  PROOFCTL-P-002: ERROR   # promote TODOs to ERROR

rules:
  PROOFCTL-P-002:
    allowed_formats:
      - 'TODO\(#\d+\)'    # allow TODO(#123) format
    exclude_paths:
      - tests/

  PROOFCTL-Q-003:
    any_threshold: 5

  PROOFCTL-V-002:
    similarity_threshold: 0.85
    min_file_lines: 30

  PROOFCTL-I-001:
    local_namespaces:
      - mycompany_
      - internal_

  PROOFCTL-TF-T013:
    required_labels:
      - environment
      - team
      - cost_center

Commands

proofctl check

proofctl check [PATH] [OPTIONS]

Options:
  --format / -f        terminal | json | html           (default: terminal)
  --output / -o        Write report to file
  --changed-only       Only scan git-changed files
  --families           Comma-separated families: P,Q,S,L,I,M,V,LLM,TF,TG,DF,YAML
  --min-severity       INFO | WARNING | ERROR
  --fail-on            Exit non-zero at this severity (default: WARNING)
  --no-pypi            Skip PyPI lookups (faster, works offline)
  --new-only           Suppress findings present in .proofctl-baseline.json
  --fix                Auto-fix fixable findings (Q-001 mutable defaults)

proofctl baseline

Snapshot the current findings so future --new-only runs only surface regressions.

proofctl baseline .
git add .proofctl-baseline.json && git commit -m "chore: proofctl baseline"

proofctl rules

Print all rule IDs, names, and severities.


Exit codes

Code Meaning
0 No findings
1 Findings present, none at or above --fail-on threshold
2 One or more findings at or above --fail-on threshold

Output formats

Terminal (default) — rich-coloured table with file:line, rule, severity, and hint.

JSON — machine-readable array for CI integrations:

{
  "summary": { "total": 4, "ERROR": 3, "WARNING": 1 },
  "findings": [
    {
      "file": "module/auth.py",
      "line": 42,
      "col": 8,
      "rule_id": "PROOFCTL-S-001",
      "severity": "ERROR",
      "message": "SQL injection via string format in .execute()",
      "hint": "Use parameterised queries: cursor.execute(sql, params)",
      "authority": "OWASP A03:2021, CWE-89"
    }
  ]
}

HTML — self-contained single-file dashboard with severity summary cards and a filterable table.


License

MIT

Project details


Download files

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

Source Distribution

proofctl-0.1.8.tar.gz (149.1 kB view details)

Uploaded Source

Built Distribution

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

proofctl-0.1.8-py3-none-any.whl (116.4 kB view details)

Uploaded Python 3

File details

Details for the file proofctl-0.1.8.tar.gz.

File metadata

  • Download URL: proofctl-0.1.8.tar.gz
  • Upload date:
  • Size: 149.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for proofctl-0.1.8.tar.gz
Algorithm Hash digest
SHA256 b6ec0fcbc75bb54b8a501d6f63fe03d0a6ebcc62b1374658c8996be1237d2ef6
MD5 d211bb3a864de372fdf4aa6d76ef3a8c
BLAKE2b-256 2916de8af8571be2d4c7f46ff734c090d031cf8ea8c3e1878d4a85a52541c18a

See more details on using hashes here.

Provenance

The following attestation bundles were made for proofctl-0.1.8.tar.gz:

Publisher: publish.yml on kolawoluu/proofctl

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

File details

Details for the file proofctl-0.1.8-py3-none-any.whl.

File metadata

  • Download URL: proofctl-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 116.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for proofctl-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 3a5ae85d48ada0aee91c6a0b3ecdaed973bb52d802b12df49907a6add09c247a
MD5 13c461169450503f7ef5f74ba0c17610
BLAKE2b-256 c38a11d02f34a0a1991fac1d063467a04db373b85c32c0cced7b0643c81ae339

See more details on using hashes here.

Provenance

The following attestation bundles were made for proofctl-0.1.8-py3-none-any.whl:

Publisher: publish.yml on kolawoluu/proofctl

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 Pingdom Monitoring Sentry Error logging StatusPage Status page