Skip to main content

authzlock

CI PyPI

An authorization lockfile for Django, Django REST Framework and FastAPI.

A one-line change to permission_classes or a decorator can open an endpoint to more users, and in review it looks like any other line. authzlock records who may call every route in a committed file, authz.lock, and on each pull request tells you which routes changed and whether access got looser.

Install

pip install authzlock

Install it in the same environment as your project: authzlock imports your settings and URLs to read the rules. Django REST Framework and FastAPI are optional.

Quickstart

From your project root, inside a git repository, with your own settings module:

export DJANGO_SETTINGS_MODULE=mysite.settings
authzlock update
git add authz.lock
git commit -m "Add authz.lock"
authzlock check

update writes authz.lock. check confirms the lockfile matches the code.

Now loosen a rule. Here an action only admins could call is opened to every signed-in user:

-    @action(detail=True, methods=["post"], permission_classes=[IsAdminUser])
+    @action(detail=True, methods=["post"], permission_classes=[IsAuthenticated])
     def archive(self, request: Request, pk: Any = None) -> Response:
authzlock check  # exits 1
authzlock diff --base HEAD  # exits 1

check fails because the code no longer matches the lockfile. diff shows what changed since HEAD; the action has three routes and all three are loosened:

loosened        POST ^invoices/(?P<pk>[^/.]+)/archive/$ -> billing.views.InvoiceViewSet  permission_classes: [rest_framework.permissions.IsAdminUser] -> [rest_framework.permissions.IsAuthenticated]  (R1: strongest built-in rest_framework.permissions.IsAdminUser -> rest_framework.permissions.IsAuthenticated)
loosened        POST ^invoices/(?P<pk>[^/.]+)/archive\.(?P<format>[a-z0-9]+)/?$ -> billing.views.InvoiceViewSet  permission_classes: [rest_framework.permissions.IsAdminUser] -> [rest_framework.permissions.IsAuthenticated]  (R1: strongest built-in rest_framework.permissions.IsAdminUser -> rest_framework.permissions.IsAuthenticated)
loosened        POST v2/^invoices/(?P<pk>[^/.]+)/archive/$ -> billing.views.InvoiceViewSet  permission_classes: [rest_framework.permissions.IsAdminUser] -> [rest_framework.permissions.IsAuthenticated]  (R1: strongest built-in rest_framework.permissions.IsAdminUser -> rest_framework.permissions.IsAuthenticated)

3 loosened, 0 tightened, 0 added, 0 removed, 0 changed-unknown, 0 equivalent

If the change is intended, record it and commit it with the code:

authzlock update
git commit -am "Let signed-in users archive invoices"
authzlock check

Every command exits 0 on success, 1 when the lockfile and the code differ, and 2 on an error. docs/scenario.md walks through a longer example.

The lockfile

authz.lock is sorted YAML, so the same code always gives the same file and review diffs show only real changes. Don't edit it by hand; run authzlock update. One route:

schema_version: 1
routes:
- path: ^invoices/(?P<pk>[^/.]+)/archive/$
  name: invoice-archive
  view: billing.views.InvoiceViewSet
  methods:
  - POST
  actions:
    POST: archive
  permission_classes:
  - rest_framework.permissions.IsAdminUser
  permission_source: action
  authentication_classes:
  - rest_framework.authentication.SessionAuthentication
  authentication_source: settings-default
  django_auth: null
  object_scoping:
    get_object:
      overridden: false
      references_request_user: null
    get_queryset:
      overridden: false
      references_request_user: null
    perform_create:
      overridden: false
      references_request_user: null
custom_permissions: {}

docs/lockfile.md describes every key.

How changes are labelled

authzlock diff gives each changed route one label:

  • loosened: more users can call the route than before.
  • tightened: fewer users can call it.
  • added / removed: the route is new, or gone.
  • changed-unknown: the rules changed, but authzlock can't tell which way, for example a custom permission class was swapped for another.
  • equivalent: the rules were rewritten but every HTTP method allows the same users.

authzlock ranks only the built-in classes (AllowAny < IsAuthenticatedOrReadOnly < IsAuthenticated < IsAdminUser, and login_required < permission_required) and never guesses what custom code does, so it reports changed-unknown rather than a false alarm. docs/classification.md has the full rules, R1 to R10.

Use it in CI

GitHub Action. Comments the diff on each pull request and fails when a route is loosened. Save as .github/workflows/authzlock.yml:

name: authzlock

on:
  pull_request:

permissions:
  contents: read
  pull-requests: write

jobs:
  authzlock:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - uses: smhasan94/authzlock@v1
        with:
          settings-module: mysite.settings
          python-version: "3.12"

Install your project's dependencies first. For FastAPI, pass app: main:app instead of settings-module. More in docs/github-action.md.

pre-commit. Blocks a commit when authz.lock is out of date. List the packages your settings and URLs import under additional_dependencies:

repos:
  - repo: https://github.com/smhasan94/authzlock
    rev: v0.3.1
    hooks:
      - id: authzlock-check
        args: ["--settings", "mysite.settings"]
        additional_dependencies:
          - "django==5.2.*"
          - "djangorestframework==3.16.*"

More in docs/pre-commit.md.

FastAPI

Point authzlock at your app as module:attr instead of a settings module. Everything else works the same:

export AUTHZLOCK_APP=main:app
authzlock update

Each route's dependencies and security schemes are recorded. A changed dependency is changed-unknown; removing every security scheme from a route is loosened. docs/heuristics-and-limits.md covers the details.

What authzlock does not do

  • It does not check that custom permission logic is correct. A class such as IsOwner is recorded by name; its code is never run.
  • It does not test a running app. It reads code and settings and sends no requests.
  • It does not generate tests for signed-in users. authzlock gen-tests only writes tests that anonymous requests to protected routes are refused, for Django projects.

A passing check means the lockfile matches the code, not that the rules are right. docs/heuristics-and-limits.md explains what authzlock can and cannot see.

Compatibility

Python Django 4.2 Django 5.1 Django 5.2 Django 6.0 Django 6.1
3.10 yes yes yes no no
3.11 yes yes yes no no
3.12 yes yes yes yes yes
3.13 no yes yes yes yes

Django REST Framework 3.14 or newer, including drf-nested-routers. FastAPI 0.100 or newer.

Documentation

Contributing

See CONTRIBUTING.md for setup, tests and conventions.

License

MIT. See LICENSE.

Metadata

Release files for authzlock 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for authzlock 0.3.1
File Size Uploaded Provenance
authzlock-0.3.1.tar.gz 55.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for authzlock 0.3.1
File Interpreter ABI Platform
authzlock-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 118.7 kB

Release files / authzlock-0.3.1.tar.gz

Download URL authzlock-0.3.1.tar.gz
Size 55.8 kB
Tags Source
SHA-256 checksum
How to use checksums
1bfe8555b0b592da29edaf6f2ee8d301a60d6bd118340a56e5c1c25096c5007b
BLAKE2b-256 checksum
How to use checksums
03060d3c8c47578cc03f6fff98197572336213a5a9573600b87d9ed5504fa9c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / authzlock-0.3.1-py3-none-any.whl

Download URL authzlock-0.3.1-py3-none-any.whl
Size 62.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
03d0116e224eb2758931f094b52636a86030618411bf5762a2196bf858e06a21
BLAKE2b-256 checksum
How to use checksums
44416af7197e6967ef2cd293cb01363309ccfdeade21d6caa286c6db314d8f9a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Security for authzlock 0.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for authzlock-0.3.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page