avaril-cli
Avaril is a privacy governance layer for building software that meets its legal obligations by design. It gives teams a privacy framework that fits into the software development lifecycle.
Verifies that a codebase actually enforces the privacy rules a company claims it enforces: reads a manifest of declared data entities/purposes/ retention/processors, runs detectors against the code, and produces one signed, structured result — the evidence behind an Enforcement Score that feeds a larger PIA score computed server-side.
Also still manages the separate, older AVRL offline-process-manifest
flow (avaril manifest pull/verify/inspect) consumed by avaril.process's
enforce_manifest gating in the Python SDK — see the "Two manifests"
note below before touching anything named manifest.
Beta
This package is pre-1.0 and not yet published to PyPI. There is no
CI-specific auth mode (scoped CI tokens via OIDC exchange are not
available yet — see "Authentication in CI" below). avaril pull's
backend endpoint now exists and works, but no constraint pack has been
published anywhere yet, so it returns pack_not_found until one is.
Everything that works entirely client-side — init, doctor, scan
against an already-cached pack, report, explain, verify, and
manifest pull/verify/inspect — works today. avaril push also works
end-to-end against a real server.
Authentication in CI
There is no scoped, short-lived CI token yet. Use a long-lived enforcement token as a CI secret, supplied either way:
AVARIL_TOKENenvironment variable (checked first, never written to disk — the preferred path for CI).avaril login --token <token>, which writes it to the OS user config directory for the duration of the job.
Install
uv tool install avaril-cli
# or
pipx install avaril-cli
(Not on PyPI yet — see the beta notice above and
PUBLISH-CHECKLIST.md. Once published, both of the above resolve
avaril and its own dependencies automatically; there is no manual step
for the end user.)
Quickstart
cd your-repo
avaril init # creates .avaril/
avaril doctor # confirms it's set up correctly
avaril pull --pack-id <id> --pack-version <v> # not live yet -- see Beta above
avaril scan
avaril report
Commands
| Command | Flags | Notes |
|---|---|---|
avaril login |
--api-key <key> [--base-url <url>] or --token <token> |
Two unrelated auth paths on one command, picked by flag: --api-key is the original flow (~/.avaril/credentials, used by manifest pull); --base-url applies only to --api-key. --token writes an enforcement token to the OS user config dir (used by pull/scan/push). Never writes a token under .avaril/. |
avaril init |
--force, --path <repo> |
Creates .avaril/{config.yaml,manifest.yaml,.gitignore}. Idempotent — skips existing files unless --force. |
avaril doctor |
--path <repo> |
Reports config validity, .avaril/ writability, auth presence, supported languages. |
avaril pull |
--pack-id <id> --pack-version <version> [--offline] [--path <repo>] |
Fetches and checksum-verifies a constraint pack into .avaril/packs/<id>/<version>/, pins it in config.yaml. --offline uses the cache only, exit 2 if missing. |
avaril scan |
--paths <p> (repeatable), --fail-on <severity>, --max-unverified <pct>, --path <repo> |
Runs the pinned pack's constraints, writes .avaril/scans/<scan_id>/{result.json,result.sig} and scans/latest.json. Warns (doesn't fail) on a dirty working tree. |
avaril report |
--format table|json|sarif, --scan-id <id>, --fail-on <severity>, --path <repo> |
Prints the latest (or named) scan's result. sarif validates against the real OASIS SARIF 2.1.0 schema, for code-scanning integration. Respects NO_COLOR and TTY detection. |
avaril explain <constraint_id> |
--path <repo> |
Shows a constraint's detectors, expected outcomes, and fix guidance. |
avaril verify <result.json> |
— | Checks schema, canonical form, and signature against the sibling result.sig. Non-zero exit on any mismatch (tamper-evident). |
avaril push |
--scan-id <id>, --path <repo> |
Uploads a result with retries, idempotent on scan_id. Payload is checked against a hard-coded allowlist before it's ever sent — see Privacy below. |
avaril manifest pull |
--environment <env>, --out <path> |
AVRL manifest, unrelated to the above — fetches the signed process-registry snapshot, default .avaril/manifest.avrl (cwd-relative, pre-dates repo-root detection). |
avaril manifest verify <path> |
— | Verifies an AVRL manifest's signature and expiry. |
avaril manifest inspect <path> |
— | Human-readable dump of an AVRL manifest's entries. |
Two manifests, same directory name
.avaril/manifest.yaml (declarative, plain YAML, written by init or by
hand — what entities/purposes/retention a scan checks the code against)
and the AVRL binary manifest avaril manifest pull fetches (a signed
process-registry snapshot the Python SDK's enforce_manifest reads at
runtime) are two different things that happen to share the word
"manifest." See avaril_cli/schemas/README.md for the full comparison
table. This was a known open question going into Phase 2 and is still
unresolved at the product level — don't assume one is a stand-in for
the other.
.avaril/ layout
Every file any command writes into a scanned repo lives here — nothing
else in that repo is ever touched (enforced by a dedicated test suite,
tests/test_e2e.py/tests/test_paths.py, that snapshots the repo
before/after each command and asserts every new path starts with
.avaril/):
.avaril/
config.yaml # pinned pack, thresholds, ignore globs, sink_allowlist (non-secret)
manifest.yaml # declared entities/purposes/retention/processors
.gitignore # ignores cache/, scans/ by default; config/manifest stay committable
packs/<pack_id>/<version>/
pack.json
constraints/*.yaml
scans/<scan_id>/
result.json
result.sig
scans/latest.json # pointer: { "scan_id": "..." }
cache/ # call-site AST cache, content-hash keyed, invalidated on CLI-version change
logs/ # reserved — path exists (AvarilPaths.logs_dir), no logger writes here yet
Credentials and tokens are deliberately outside this tree, on purpose — a credential inside a scanned repo could end up committed by accident:
~/.avaril/credentials— the original--api-keyflow (0600perms).- OS user config dir (
$XDG_CONFIG_HOME/avaril/token, else~/.config/avaril/token) — the--tokenenforcement token, and a lazily-generated local Ed25519 self-signing key (.../avaril/signing_key).
AVARIL_TOKEN (env var) is checked before either on-disk token path, so
CI can supply one without ever writing to disk.
Exit codes
| Code | Constant | Meaning |
|---|---|---|
| 0 | EXIT_OK |
Clean — no failures at/above --fail-on, coverage at/above --max-unverified. |
| 1 | EXIT_FAIL |
At least one constraint failed at or above the --fail-on severity. |
| 2 | EXIT_USAGE |
Config/usage error (e.g. no pack pinned, --offline with nothing cached). |
| 3 | EXIT_COVERAGE |
Verification coverage fell below --max-unverified. |
| 4 | EXIT_AUTH |
Auth or network failure talking to the backend. |
These are a tested contract (avaril_cli/errors.py, avaril_cli/gate.py)
for every command added from Phase 0 onward. The two original AVRL
commands (manifest verify/manifest inspect) predate this contract and
still use their own literal sys.exit calls — and disagree with each
other on which code means "corrupt signature" (2 vs. 1). Known,
documented, not fixed in this pass (compatibility — see hard rule 9).
Privacy guarantees
avaril push never uploads source code or snippets — only constraint
id, outcome, severity, file path, line range, evidence hash, counts, and
versions. This isn't just a convention: avaril_cli/privacy_check.py's
check_payload is a default-deny allowlist over every key anywhere
in the push payload (not a blocklist for "looks like code" names) — an
unrecognized key refuses the push outright. None of the result schemas
have a snippet/source-text field to begin with (Phase 1 design choice);
this check guards against one quietly being added later, or a tampered
result.json being pushed. Finding records file/lines/
evidence_hash only — the matched source text itself is hashed, never
stored, on disk or in any payload (tests/test_call_site_e2e.py's
test_result_json_never_contains_fixture_source_text greps for it).
CI example (GitHub Actions)
- name: Avaril enforcement scan
env:
AVARIL_TOKEN: ${{ secrets.AVARIL_TOKEN }}
run: |
avaril pull --pack-id dpdpa-core --pack-version 1.0.0
avaril scan --fail-on high --max-unverified 20
avaril report --format sarif > avaril-results.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: avaril-results.sarif
- name: Push findings
if: always()
env:
AVARIL_TOKEN: ${{ secrets.AVARIL_TOKEN }}
run: avaril push
Running inside GitHub Actions also upgrades the scan's attestation
from self-reported to ci-attested automatically (scan.py's
detect_ci_attestation, keyed off GITHUB_ACTIONS=true), binding the
result to the run's commit/repo/actor — no extra flag needed.
Development
pip install -e ".[dev]"
pytest
scripts/benchmark_scan.py times the call-site/sink detectors'
file-scanning core on a synthetic large repo — see BENCHMARKS.md for
the last recorded numbers and why it's a process pool, not a thread pool.
manifest_commands.py imports avaril (the sdk/avaril-py package)
directly for the AVRL binary format and Ed25519 signing/verification —
signing.py (used by scan/verify/push) reuses the same primitive
rather than building a second signing mechanism.
Metadata
Release files for avaril-cli 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| avaril_cli-0.1.0.tar.gz | 115.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| avaril_cli-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 212.7 kB
Release files / avaril_cli-0.1.0.tar.gz
| Download URL | avaril_cli-0.1.0.tar.gz |
|---|---|
| Size | 115.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9c8e85de387286bc1e1cecc3e0770ccde9e164a07eacec2c9036bbc86870ce3f
|
|
BLAKE2b-256 checksum How to use checksums |
ef97f0c82bb9a0520f4492042b7c4527daeb5c5d1f92510a73428e3feb235f95
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Release files / avaril_cli-0.1.0-py3-none-any.whl
| Download URL | avaril_cli-0.1.0-py3-none-any.whl |
|---|---|
| Size | 97.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d00ad7793a861b4e4c141d63cf1baab0cb36977c3f8d7652f29778cf9a2ebf0d
|
|
BLAKE2b-256 checksum How to use checksums |
233ba9867270d3779efc743a2ca14286cacc9d4c150bb42f699d20ef0688a2b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|