Install¶
Veridor needs Python 3.11 or newer and nothing else at runtime. It imports only the standard library, so there is no dependency tree to resolve or audit.
Early pre-release (alpha): nothing is published
No release has been tagged. veridor is not on PyPI, and there is no
container image in any registry, no zipapp download and no published Helm
chart. The source repository,
github.com/Veridor-authz/veridor, is private until the source is published. The names on this site
(pip install veridor==X.Y.Z, ghcr.io/veridor-authz/veridor,
Veridor-authz/veridor@...) are what the release workflow is written to
publish on a first version tag. The source declares version 0.1.0, so a
local build is named 0.1.0; that is not a release.
The name veridor is unclaimed on PyPI, so whatever pip install veridor
finds before the first release is not this project's. Until a release
exists, every route below starts from a copy of the Veridor source tree.
Pick a surface¶
| Surface | Command | Use it when | Needs a network to install |
|---|---|---|---|
| a checkout | python -m pip install . |
you want the CLI, the demo target and the fixtures | yes: pip fetches the build backend |
| a wheel | python -m pip install --no-index veridor-0.1.0-py3-none-any.whl |
you want to install on a machine without index access | no |
| the zipapp | python3 veridor-0.1.0.pyz |
you want one file and no install at all | no (building it needs none either) |
| the container image | docker build -t veridor:local . |
the gate runs in CI that is not Python-shaped, or in Kubernetes | building it does |
| the GitHub Action | uses: Veridor-authz/veridor@<full-commit-sha> |
the gate runs in GitHub Actions | no: it runs from its own checkout |
| PyPI | python -m pip install veridor==X.Y.Z |
after a first release | yes |
From a checkout¶
The demo target, the fixtures and every command in the quickstart live in the repository.
pipx install . works too, if you want the binary on your PATH without
activating anything.
The virtual environment is not ceremony. python3 -m pip install is refused
outright on Homebrew Python and on any Debian or Ubuntu system Python (PEP 668
answers error: externally-managed-environment), and on macOS the bare
python3 is 3.9, which requires-python = ">=3.11" rejects. Both obvious
interpreters fail, so name the version and give it its own environment.
A source install is not offline
The runtime has no dependencies, but building the package does:
pyproject.toml declares requires = ["setuptools>=77"], and pip fetches
it into an isolated build environment. Measured for this page in a fresh
Python 3.12 virtual environment: pip install --no-index --dry-run . fails
with No matching distribution found for setuptools>=77, and the same
command with the index reachable succeeds. For a machine without index
access, build a wheel or the zipapp somewhere
that has one.
A wheel carries the veridor package and nothing else: no fixtures, no demo
server. Pointing an installed veridor at a fixture path it does not have
produces this, which reads like a broken install and is not one:
A wheel¶
Build it once, where a package index is reachable:
Then install it anywhere, with no index:
Measured for this page on an earlier tree (before 38025ed): the wheel built with pip wheel
--no-deps installed with --no-index into a fresh Python 3.12 environment
and imported from site-packages. The release workflow is written to attach the wheel, the sdist,
the zipapp, a CycloneDX SBOM and SHA256SUMS to a GitHub release, and to
publish the wheel and sdist to PyPI; none of that has run.
The zipapp¶
One file, the package plus a __main__, run by any Python 3.11 or newer:
python3.12 scripts/build_zipapp.py # writes dist/veridor-0.1.0.pyz
python3.11 dist/veridor-0.1.0.pyz --version
- It contains
src/veridor(the.pyfiles and the viewer's web assets), a top-level__main__.py,LICENSEandNOTICE. Nothing else: no fixtures, no demo server. - The first line is
#!/usr/bin/env python3, so./veridor-0.1.0.pyzworks when thatpython3is 3.11 or newer. An older interpreter is refused with exit2: measured with macOS/usr/bin/python33.9,veridor needs Python 3.11 or newer; this is 3.9 (...). - The build is reproducible by design: entries are sorted, stored rather than compressed, and stamped 1980-01-01, so two builds of the same tree are byte-identical and a published checksum could be reproduced from a checkout. The Veridor CHANGELOG records the same sha256 on macOS arm64 and on Linux arm64 and amd64. The checksum depends on the tree, so none is quoted here.
- Building it needs only Python; it is not a pip install.
Measured for this page on an earlier tree (before 38025ed): built twice with Python 3.12, the two
files were byte-identical; --version under Python 3.11 exited 0, and under
macOS's /usr/bin/python3 3.9 exited 2. Windows was not tried.
The container image¶
The repository's Dockerfile builds the release image:
- Two stages on
python:3.12-alpine(musl), pinned by digest. The build stage installs one hash-pinned build dependency (setuptools 84.0.0) and builds the wheel with no index; the runtime stage installs that wheel with--no-indexfrom a read-only build mount, so the image holds the interpreter and one package: no tests, docs, fixtures or demo server. The mount needs BuildKit. - pip is removed after the install, and there is no bash. A POSIX
shandpython3are present; CI templates that need a shell clear the entrypoint. USER 10001:10001,HOMEandWORKDIR/work(owned by 10001),ENTRYPOINT ["veridor"],CMD ["--help"].- The CA bundle is Alpine's, which lacks some legacy roots Debian still
carries (DigiCert Global Root CA and GlobalSign Root CA among them). For a
target whose chain ends at one of those, mount a file that holds it and set
SSL_CERT_FILE; it adds to the image's roots rather than replacing them. Veridor has no TLS setting of its own.
The Veridor README records 20.9 MB compressed for linux/amd64 and 21.4 MB for
linux/arm64, from OCI exports on 2026-09-27. The project's CI builds the image
for linux/amd64, checks that its user is uid 10001 and that starting it and a
run compile nothing from source (with two control images that must fail
that check), and gates the demo API with it: vulnerable exits 1, fixed exits
0. No image has been pushed to a registry.
The working directory must be writable by uid 10001¶
The default --output-dir is .veridor under /work. A bind mount replaces
/work with your directory and its ownership, so run as your own user:
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" veridor:local \
run --config veridor.json
Before Veridor checked this itself, a run with /work a root-owned 0755
tmpfs sent its probes to the vulnerable demo target and then exited 2 with
veridor: [Errno 13] Permission denied: '.veridor'. The control, the image's
own /work, exited 1: the finding the first run measured was lost to a
write error.
Now run, map, mutate and verify create each output directory and write
and remove a test file in it before the first request. Measured for this page
on 2026-09-24, image built from the integration branch (5ee6158), same tmpfs,
the target a listener that counts requests:
veridor: cannot write the results of `run` to --output-dir .veridor (resolved: /work/.veridor): could not create it (Permission denied) as uid:gid 10001:10001. Nothing has been sent: this is checked before the first request, because a run whose results cannot be written would still probe the target and then lose what it found. Fix: pass an --output-dir this user can write (for example --output-dir /tmp/veridor), or, in a container, run it as the owner of the mounted directory (docker run --user "$(id -u):$(id -g)").
Exit 2, and the listener counted 0 requests. The control, the image's own
/work, sent 9. Docker Desktop bind mounts on macOS do not reproduce Linux
ownership, which is why both measurements used a tmpfs.
127.0.0.1 in a config means the container's own loopback
A base_url of http://127.0.0.1:8765 resolves inside the container, not
on your host. Every probe then fails, all of them grade inconclusive, and
the run exits 3: not one probe was answered, so nothing was established.
Point base_url at a routable address, or put the container on the same
network as the target.
For Kubernetes, the veridor-gate chart and the Kustomize plain Job
run this image as a Job under the restricted Pod Security profile.
deploy/kind/Dockerfile, the demonstration image¶
Built for the kind demonstration rather than for CI:
It is python:3.12-slim (not pinned by digest, and not the release image's
Alpine base), a pip install . of the
package, a non-root user (uid 10001), and, copied in separately, the fixtures,
scripts/demo_server.py and a seed script. Its default command serves the
local viewer on 0.0.0.0:8787 with --i-understand-this-is-unauthenticated,
because a published container port is only reachable on 0.0.0.0. Publish it
to loopback only:
The viewer has no authentication: every run document it serves (target URLs, finding messages, config paths) is readable by anything that can reach the port.
GitHub Action¶
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- uses: Veridor-authz/veridor@<full-commit-sha> # vX.Y.Z, after the first release
with:
config: veridor.json
The Action installs nothing. It runs python -P -m veridor with PYTHONPATH
set to its own src/, so it needs a Python 3.11+ on the runner and does not
reach a package index. The only network traffic is what Veridor sends to your
target, through a proxy only if the job sets http_proxy or https_proxy for
it. Pin a full commit SHA; there is no moving major tag. The Action has run
through uses: ./ on GitHub-hosted ubuntu-latest and macos-latest runners
in the project's own CI, where the vulnerable demo fails the step and the
fixed one passes. The repository is private, so those runs are not public, and
the Action is not on the Marketplace. Everything else is on
the GitHub Action page.
After a release: PyPI¶
This is the command a first release is meant to make work. Today it does not install Veridor: nothing is on PyPI, and the name is unclaimed, so anything it finds before the first release is not this project's.
Pin the version: there is no moving major tag while the version is 0.x,
because a minor release may change behaviour.
Verifying a release (after the first release)¶
No release exists, so nothing in this section has been run against one. It
describes what the repository's .github/workflows/release.yml is configured
to produce for a tag vX.Y.Z, and how the Veridor repository's SECURITY.md
says to check it. That workflow has never run.
Release files (wheel, sdist, zipapp) are meant to carry GitHub build provenance. Verify it against the workflow and the tag, not only the repository:
gh attestation verify veridor-X.Y.Z-py3-none-any.whl --repo Veridor-authz/veridor \
--cert-identity https://github.com/Veridor-authz/veridor/.github/workflows/release.yml@refs/tags/vX.Y.Z
- Why the pinned identity. With
--repoalone,ghaccepts an attestation made by any workflow in the repository at any ref, so a workflow pushed on a branch could replace a release file and attest the replacement (inferred from GitHub's permission model; there is no release to try it on).--cert-identitymust match the signing certificate exactly, so it accepts only a run ofrelease.ymlfrom the tagvX.Y.Z.--signer-workflowalone is a prefix match and accepts any ref.SECURITY.mdrecords this measured with gh 2.95.0 on another project's real attestation, made onrefs/heads/main:--repoalone accepted it;--cert-identitywith the exact signer accepted it, and the same identity at another ref, or naming another workflow, was refused. - What a pass proves: GitHub Actions signed provenance for this file's
sha256 in a run of
release.ymlas it stood at the tagvX.Y.Z. Not that the tag points at the commit you expect (add--source-digest <commit sha>if you know it), not that a reviewer approved the run, and not that nothing the run fetched while building was tampered with. Anyone allowed to push av*tag can produce this identity.
SHA256SUMS lists the sha256 of every other file attached to the release:
It is downloaded from the same place as the files and is not attested itself,
so it detects a damaged download, not a substituted one. Only the pinned
attestation check above ties a file to release.yml at the tag. The Jenkins,
Azure and CodeBuild templates' zipapp notes point to that check for the same
reason: a downloaded zipapp runs with every VERIDOR_* credential.
The container image and the Helm chart are meant to be signed with cosign's keyless signing by the release workflow. Verify by digest:
cosign verify ghcr.io/veridor-authz/veridor@sha256:<digest> \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github.com/Veridor-authz/veridor/\.github/workflows/release\.yml@refs/tags/v'
The chart is ghcr.io/veridor-authz/charts/veridor-gate@sha256:<digest>, checked
the same way. The release notes are meant to name both digests, and the
published chart's image.digest is set to the image digest, so a verified
chart deploys the signed image by digest rather than by its X.Y.Z tag.
Every job in that workflow that publishes is configured to run in a protected
release environment and wait there for a maintainer's approval. It relies on
repository and PyPI settings the workflow cannot fully check: it checks the
reviewer and the bypass setting before publishing, and cannot show who
approved a job, check the tag rule or ruleset, or check PyPI's side.
Check that it worked¶
python -m veridor is the same entry point, with the same exit codes.
veridor --help prints the three verbs (map, run, mutate) above the
utilities (capsule, init, coverage, new-marker, report, serve).
Each takes its own --help.
Python versions¶
requires-python is >=3.11, and pip enforces it. The project's GitHub
Actions CI runs the fast and slow test tiers on Ubuntu with 3.11, 3.12 and 3.13 and on
macOS with 3.12, and builds, installs and tests the wheel on 3.11, 3.12 and
3.13; every one of those jobs passed on GitHub-hosted runners on 2026-11-08
(workflow run 42508338432, at commit 38025ed). The repository is private, so
the run is not public yet. For this page, the targeted test modules ran on 3.12
and the zipapp ran on 3.11.
Standard library only¶
Veridor imports nothing outside the Python standard library at runtime. What that buys you:
- There is no runtime dependency to audit, only Python itself. Building from
source fetches the build backend, setuptools, which the
Dockerfilepins by version and hash. - The container image has no dependency layer.
- Once installed, it needs no network access except to your target, or to a proxy your environment names for it (proxies).
How it is checked: the Veridor test suite's tests/test_stdlib_only.py (fast
tier) AST-scans every import in src/veridor/**, including ones inside
functions, try blocks and if TYPE_CHECKING, and fails on any module that is
neither in the standard library nor Veridor's own; it also fails if
pyproject.toml declares a runtime dependency. Before it existed, nothing in
the repository scanned the whole package: the release workflow used to, and its
rewrite dropped it.
Measured for this page on the tree at commit 38025ed: that test module
passed; and a scan
of this page's own, walking every import in the 25 files under src/veridor,
found none outside sys.stdlib_module_names, and on a copy with import
requests planted inside a try in a function it reported that one import.
The bound: that is a static scan of the source tree. It says nothing about the interpreter you run Veridor on, or about the tools around it (the test suite, the docs build, the demo target), none of which is the runtime.
Next¶
Your first finding, against the demo target in the Veridor source, then Write your access policy.