Metadata-Version: 2.4
Name: vozern
Version: 1.0.0
Summary: 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
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: PyYAML>=6
Provides-Extra: pdf
Requires-Dist: weasyprint>=60; extra == "pdf"
Provides-Extra: publish
Requires-Dist: cryptography>=42; extra == "publish"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: jsonschema>=4; extra == "dev"

# 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](https://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):

```bash
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:

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

## Output formats

```bash
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)

```bash
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`):

```yaml
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

```bash
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`)

```jsonc
{
  "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:

```bash
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

```bash
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.
