Skip to main content

Vozern Attest — AI development governance: inventory AI agents/MCP servers and unmanaged developer software, evaluate against an approved catalogue and advisories, emit signed audit-grade evidence

Project description

Attest, by Vozern — workstation unmanaged-software & AI tooling attestation

Vozern is the company (vozern.com); Attest is the product. The Python distribution is published as vozern (pipx install vozern); the command and import package are attest.

Inventories software installed outside centrally-managed channels on a macOS workstation — Homebrew, global npm, pipx, VS Code extensions, AI tools and MCP servers — matches it against known advisories via OSV.dev, evaluates it against an approved AI-tool catalogue, and produces an audit-grade attestation report mapped to selectable control frameworks (NIST SP 800-53, CIS Controls v8, organization policy, or your own internal control set).

The report is the product. The scanner exists to generate a credible one.

No telemetry, ever. attest makes exactly two kinds of network calls, both explicit and both optional: the OSV.dev advisory lookup (--no-osv disables) and the VS Code Marketplace publisher-trust lookup (--no-trust disables). Nothing about your machine leaves it; reports are written to local files only. Run it fully offline and it still produces a valid inventory attestation. (Connecting to the Attest control plane — see PLATFORM.md — is explicit, opt-in, and sends signed control evidence only: outcomes, counts, and hashes, never the inventory itself.)

Run it

Zero-install (bare Python 3.10+, nothing to pip install):

python3 attest.py            # scan this machine + match advisories
python3 attest.py --open     # ...and open the report
python3 attest.py --demo     # render a sample report (illustrative data)
python3 attest.py --no-osv   # inventory only, fully offline

Or install it:

pipx install .               # console command: attest
attest --demo
attest --version

Output formats

attest --format html                      # default; print-clean, self-contained
attest --format json --format sarif      # repeatable
attest --format pdf                      # true A4 PDF (optional extra, below)
attest -o quarterly-scan --format html --format sarif
# -> quarterly-scan.html, quarterly-scan.sarif
  • HTML — the human artefact: self-contained, no external assets, prints to a filable A4 document.
  • JSON — documented shape below, for arbitrary downstream tooling.
  • SARIF 2.1.0 — validates against the official schema; loads into GitHub code scanning and standard SARIF viewers. Accepted risks map to native suppressions.
  • PDF — a real A4 PDF rendered from the same HTML: pip install 'attest[pdf]' (needs brew install pango; if the libraries aren't found at run time, prefix with DYLD_FALLBACK_LIBRARY_PATH="$(brew --prefix)/lib").

Control frameworks (--framework, repeatable)

attest --framework nist --framework cis    # the default pair
attest --framework internal                # generic internal-control template

Frameworks are data, not code: each is a YAML file (attest/frameworks/*.yaml) defining controls — id, name, assertion, an evidence template over a fixed metric namespace, and declarative status rules (first match wins). Copy internal.yaml, rename the ids to your control register, and the report speaks your auditor's language. Files load with PyYAML when installed, otherwise through a vendored strict-subset parser, so the zero-install path keeps working.

Accepted risk (exceptions.yaml)

A finding you have consciously accepted moves out of "attention" and into a documented accepted-risk section — visible justification, owner, optional expiry. Place exceptions.yaml next to where you run attest (or pass --exceptions PATH):

exceptions:
  - finding: "npm-global:lodash:GHSA-jf85-cpcp-j695"   # channel:package:advisory
    justification: "Dev-only tool; not exposed to untrusted input."
    owner: "name@example.com"
    expires: "2026-12-31"        # optional; past expiry it reverts to open

Expired acceptances reopen with a visible ACCEPTANCE EXPIRED tag. In SARIF, accepted findings carry a suppression with the justification.

What it checks — and how honestly

Channel Inventoried Advisory matching Confidence label
global npm yes OSV npm, native exact
pipx yes OSV PyPI, native exact
Homebrew formulae yes curated upstream map, else name-based heuristic high / low
Homebrew casks yes never matched (names too generic to be honest)
VS Code extensions yes no feed exists; trust signal instead
AI tools (CLIs + apps) yes no feed exists; approval catalogue via --policy
MCP servers yes no feed exists; approval catalogue via --policy

AI tools & MCP servers are discovered from the client configurations that Claude Code/Desktop, Cursor, Windsurf, VS Code, Codex, and Gemini CLI actually read, plus known AI binaries and macOS app bundles. With --policy policy.yaml (see policy.example.yaml), anything outside the organization's approved catalogue becomes an ATTEST-UNAPPROVED finding that flows through findings, SARIF, exceptions, and --fail-on like any advisory.

Homebrew has no OSV ecosystem, so matching is best-effort by design: a curated map (attest/data/brew-map.yaml) ties well-known formulae to advisory sources that track upstream version numbers (Bitnami, Go modules, PyPI, crates.io, npm) at high confidence; unmapped formulae fall back to a name-only lookup filtered to those same upstream-semantics ecosystems at low confidence, labelled "verify before acting" in the report. Distro feeds (Ubuntu/Alpine/Debian/...) are never accepted — their version ranges describe distro builds, not your Homebrew install. Formulae with no upstream feed (openssl, curl, ...) are shown as unmatched, not as clean. Control it with --brew-match off|curated|full (default full; the heuristic pass queries OSV once per unmapped formula, so curated is much faster on formula-heavy machines).

VS Code extensions carry a trust signal: marketplace publisher verification, staleness (>2 years without an update), and a curated offline denylist of publicly documented malicious extensions — a denylist hit is a CRITICAL finding.

CI gating

attest --fail-on critical    # exit 2 if any OPEN critical advisory exists
attest --fail-on high        # critical or high

Off by default; accepted risks never trip the gate.

JSON shape (attest-report/1)

{
  "schema": "attest-report/1",
  "tool": {"name": "attest", "version": "1.0.0"},
  "demo": false,
  "context": {"hostname", "user", "platform", "scanned_at"},
  "match_meta": {"osv_available", "queryable", "unqueryable", "error",
                 "pages_fetched", "truncated", "cache": {"hits", "misses"}},
  "trust_meta": {"checked", "available", "error"},
  "summary": { /* the full metric namespace: components, channels,
                  open_advisories, accepted_advisories, critical_open, ... */ },
  "controls": [{"id", "framework", "name", "status", "assertion", "evidence"}],
  "records": [{
    "channel": "npm-global", "ecosystem": "npm",
    "name": "lodash", "version": "4.17.4",
    "coverage": "native",                  // native|curated|heuristic|none
    "vulns": [{
      "id": "GHSA-...", "cve": "CVE-...", "severity": "HIGH",
      "summary": "...", "url": "...",
      "confidence": "exact",               // exact|high|low
      "accepted": {"justification", "owner", "expires", "expired"}  // optional
    }],
    "trust": { /* extensions only: available, verified, last_updated,
                  stale, denylisted, denylist_reason */ }
  }]
}

Additive changes keep /1; breaking changes bump it.

Publish to a control plane (opt-in)

The platform layer (see PLATFORM.md). A pilot control plane ships in the package — stdlib only, no extra dependencies:

python3 -m attest.server --enroll-token SECRET     # admin box; dashboard on :8788
attest --enroll http://plane.example:8788 --token SECRET   # once per device
attest --publish --disclosure outcomes             # after any scan

What publishes is a signed attest-attestation/1 envelope — control outcomes, an evidence hash, and (at higher disclosure tiers) counts or failing items. Never the inventory. Ed25519 signing needs the optional extra: pip install 'attest[publish]'; without it envelopes publish unsigned and the server marks them unverified. The dashboard shows fleet control posture, device freshness, the unapproved-AI-tooling approval queue, the accepted-risk register with expiry alerts, and a downloadable evidence pack. Enrolled devices fetch their group's approved-tool catalogue from the plane automatically at scan time. Enterprise rollout (SSO proxy, MDM, scheduling): see DEPLOY.md.

Other flags

--input inv.json (render from a saved inventory) · --save-inventory PATH · --no-cache / --refresh (the OSV hydration cache lives in ~/.cache/attest/, TTL 7 days) · --open (macOS).

Everything degrades gracefully offline: network failure annotates the report, it never crashes a run.

Development

pip install -e '.[dev]'
pytest                       # the suite is fully offline
ATTEST_UPDATE_GOLDEN=1 pytest tests/test_export.py  # regenerate HTML snapshot

Deliberately out of scope: fleet/multi-machine, agents/daemons, dashboards, auto-remediation, policy enforcement, auth, Windows/Linux.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vozern-1.0.0.tar.gz (81.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vozern-1.0.0-py3-none-any.whl (69.0 kB view details)

Uploaded Python 3

File details

Details for the file vozern-1.0.0.tar.gz.

File metadata

  • Download URL: vozern-1.0.0.tar.gz
  • Upload date:
  • Size: 81.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.0

File hashes

Hashes for vozern-1.0.0.tar.gz
Algorithm Hash digest
SHA256 370fd49fa53bb1b3b8dc3990bfcf8f6554cd5e62f54b73b1a27f905f48717933
MD5 48918094c824a4acdf2a4959b7608bc0
BLAKE2b-256 32b3d8724834225f03574b34cc533b0d0c5eccddefa7717026e4a8cb90400b9d

See more details on using hashes here.

File details

Details for the file vozern-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: vozern-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 69.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.0

File hashes

Hashes for vozern-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a531aa057378b5dcd332e49974496b0c19f18f75573f3eb746cad14ff8ce44f0
MD5 bc634fe0cb214eaf10c2e284b14bdf9e
BLAKE2b-256 d6fc814382470ff8a9fdb2ab65689cec908398d4909794c036ecf288efa045e8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page