From 8443ecea692adb004e6b97dbfd872a1fdca3bcad Mon Sep 17 00:00:00 2001 From: Austin Schaefer Date: Fri, 11 Sep 2026 09:10:06 +0200 Subject: [PATCH] Narrow scope: personal middle ground, not a security framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an explicit Scope/Non-Goals section (curated personal package list, not adversarial-config or compromised-vendor-pipeline defense) and a post-build version sanity check as its own pipeline stage, distinct from the trust tiers — a correctness gate (does the build report the version we expected), not a security control. Trims the next-steps list to match. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01A2FEut5tVMNjeVjqhgVZbr --- SPEC.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/SPEC.md b/SPEC.md index 1e2d231..05ff57f 100644 --- a/SPEC.md +++ b/SPEC.md @@ -20,6 +20,37 @@ package X" once, and have that declaration stay live — checked periodically, re-verified on every new upstream release, and fed into a normal `pacman`-based workflow without hand-editing a PKGBUILD each time. +## Scope + +This is a personal tool for a small, curated list of non-critical packages +— not a general-purpose supply-chain-security framework. The concrete +motivating case: software (especially fast-moving AI/ML tooling) that +Manjaro's `extra` repo lags weeks behind upstream on, where raw AUR or a +vendor's curl|sh script are the only faster alternatives today. + +**Goal**: a middle ground — fresher than Manjaro's lag, with real +verification where the vendor actually offers something to check, safer +than blindly piping an install script to `sh`. + +**Non-goals**: + +- Defending against a fully compromised vendor signing/release pipeline. + If upstream's CI or signing key is itself compromised, pkgwatch cannot + and does not try to catch that. Tiers 1–3 (below) raise the bar from + "trust the domain" to "trust the vendor's actual release process"; they + are not a guarantee against that process being subverted. +- Defending against a malicious downgrade specifically. A compromised + version-check source reporting an older version as "latest" is a + downstream/distro-security problem, out of scope here. (See "post-build + version check" below for a related but distinct sanity check — it is + not a security control.) +- Supporting an adversarial or multi-user config. The tracked-package list + is curated by the one person running the daemon on their own machine; + config/state file integrity relies on normal filesystem permissions, not + a hardened trust boundary. Key-pinning friction on rotation (see tiers + below) is acceptable, even desirable, at this scale — it's a personal, + low-volume list, not something that needs to scale painlessly. + ## Vision A Rust binary, run as a systemd service (service + timer, periodic not @@ -44,7 +75,10 @@ that those tools don't provide. ## Verification trust tiers -The central design problem: from the install UX, a cryptographically strong +Per the Scope above, the goal is to raise the bar above curl|sh where the +vendor gives us something to check — not to build airtight supply-chain +defense. The central design problem within that goal: from the install UX, +a cryptographically strong verification and a "trust-me-bro" same-domain checksum look identical. The tool's job is to make that difference legible instead of laundering every package into an undifferentiated "verified" bucket. @@ -82,6 +116,10 @@ This is the load-bearing decision, not just a cosmetic label: adds at these tiers is diff-and-alert (notice something changed, surface the new hash for a human to look at), not verify-and-trust. +The post-build version sanity check (see Architecture below) runs +regardless of tier — it's a correctness gate on the build itself, not part +of the trust-tier judgment, and doesn't change this tiering. + ### Surfacing trust, not just gating on it - Every tracked package carries an explicit tier + one-line justification @@ -106,6 +144,13 @@ tier = 4 method = "same-origin-sha256" checksum_asset_pattern = "uv-x86_64-unknown-linux-gnu.tar.gz.sha256" +# Post-build sanity check — correctness only, not a security control. +# Runs the built binary and confirms it reports the version pkgwatch +# believes it just built; mismatch blocks publish. +[package.uv.sanity_check] +command = "uv --version" +version_regex = 'uv (\d+\.\d+\.\d+)' + # Tier 1 example: [package.somepkg] source = "url-with-version-regex" @@ -144,11 +189,22 @@ Open questions on the schema: attestation companion) for a resolved version. - **Verifier**: tier-specific verification implementations behind a common trait; returns a tier + pass/fail + justification string. -- **Publisher**: for tiers 1–3 on pass, generates/updates the PKGBUILD, - runs `makepkg`, runs `repo-add` against the local repo. +- **Builder**: for tiers 1–3 on pass, generates/updates the PKGBUILD + (strict validation on any upstream-controlled string — version, filename + — before it touches generated shell content; never unescaped + interpolation) and runs `makepkg`. +- **Sanity checker**: after a successful build, runs the package's + declared `sanity_check.command` against the built artifact and confirms + the reported version matches what pkgwatch believes it just built. + Mismatch = fail loud, do not publish. This is a correctness check, not a + security control — it catches checker bugs and mangled/wrong-artifact + downloads, not malicious releases. +- **Publisher**: runs `repo-add` against the local repo, only after the + sanity check passes. - **Reviewer queue**: for tiers 4–6, records the detected change instead of - auto-publishing; a separate `pkgwatch review` command lets a human - approve/reject, which then triggers the publish step. + auto-building; a separate `pkgwatch review` command lets a human + approve/reject, which then triggers the build → sanity-check → publish + steps above. - **Scheduling**: systemd `.service` (oneshot) + `.timer` running it periodically, matching the pattern already used for other periodic tasks on this box. @@ -166,11 +222,19 @@ Open questions on the schema: ## Status / next steps -- [ ] Refine config schema further (see open questions above). +- [x] Scope decided: personal middle-ground tool for a curated package + list, not a general supply-chain-security framework (see Scope + above). Downgrade attacks and compromised-vendor-pipeline defense + are explicit non-goals. +- [ ] Refine config schema further (see open questions above), including + the `sanity_check` block per package. - [ ] Decide version-check strategy: shell out to `nvchecker` vs. own implementation, for the PoC. +- [ ] Implement PKGBUILD generation with strict upstream-string validation + from day one (see Builder, above) — cheap to do right up front, + expensive to retrofit. - [ ] PoC scope: single tier-4 package (e.g. `uv`, ironically) end-to-end — check, fetch, same-origin-checksum verify, flag-for-review, manual - approve, PKGBUILD generation, local repo publish. + approve, build, post-build version sanity check, local repo publish. - [ ] Decide on project home: local-only for now, or push to code.austinschaefer.com (Forgejo) once the spec settles.