Narrow scope: personal middle ground, not a security framework

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2FEut5tVMNjeVjqhgVZbr
This commit is contained in:
Austin Schaefer 2026-09-11 09:10:06 +02:00
parent 0fa994f60a
commit 8443ecea69

78
SPEC.md
View file

@ -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 13 (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 13 on pass, generates/updates the PKGBUILD,
runs `makepkg`, runs `repo-add` against the local repo.
- **Builder**: for tiers 13 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 46, 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.