Skip to main content

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_TOKEN environment 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-key flow (0600 perms).
  • OS user config dir ($XDG_CONFIG_HOME/avaril/token, else ~/.config/avaril/token) — the --token enforcement 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)

Source distribution for avaril-cli 0.1.0
File Size Uploaded
avaril_cli-0.1.0.tar.gz 115.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for avaril-cli 0.1.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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