authzlock
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
IsOwneris 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-testsonly 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
- docs/cli.md: every command, option and output format.
- docs/lockfile.md: the lockfile format.
- docs/classification.md: how changes are labelled.
- docs/heuristics-and-limits.md: what authzlock can and cannot see.
- docs/index.md: all pages.
- CHANGELOG.md: changes by release.
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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| authzlock-0.3.1.tar.gz | 55.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.