Skip to content

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.

python3.12 -m venv .venv && . .venv/bin/activate
python -m pip install .

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:

veridor: configuration not found: fixtures/veridor.demo.json

A wheel

Build it once, where a package index is reachable:

python -m pip wheel --no-deps . -w dist

Then install it anywhere, with no index:

python -m pip install --no-index dist/veridor-0.1.0-py3-none-any.whl

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
veridor 0.1.0
  • It contains src/veridor (the .py files and the viewer's web assets), a top-level __main__.py, LICENSE and NOTICE. Nothing else: no fixtures, no demo server.
  • The first line is #!/usr/bin/env python3, so ./veridor-0.1.0.pyz works when that python3 is 3.11 or newer. An older interpreter is refused with exit 2: measured with macOS /usr/bin/python3 3.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:

docker build -t veridor:local .
docker run --rm veridor:local --version
  • 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-index from 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 sh and python3 are present; CI templates that need a shell clear the entrypoint.
  • USER 10001:10001, HOME and WORKDIR /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:

docker build -f deploy/kind/Dockerfile -t veridor:demo .

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:

docker run -p 127.0.0.1:8787:8787 veridor:demo

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.

python -m pip install veridor==X.Y.Z     # inside a virtual environment

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 --repo alone, gh accepts 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-identity must match the signing certificate exactly, so it accepts only a run of release.yml from the tag vX.Y.Z. --signer-workflow alone is a prefix match and accepts any ref. SECURITY.md records this measured with gh 2.95.0 on another project's real attestation, made on refs/heads/main: --repo alone accepted it; --cert-identity with 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.yml as it stood at the tag vX.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 a v* tag can produce this identity.

SHA256SUMS lists the sha256 of every other file attached to the release:

sha256sum --check --ignore-missing SHA256SUMS

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

veridor --version
veridor 0.1.0

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 Dockerfile pins 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.