kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 623643c4f49b8639938e03cdb045fe85d049ec91
parent 618ab868d81712aeb501c84a0e9128bf38983d33
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 16 Jul 2026 10:41:20 -0700

dist: require explicit release trust

Diffstat:
Mdoc/RELEASE.md | 24++++++++++++++----------
Mdoc/RELEASE_AUDIT_2026_6_0.md | 8++++----
Mdriver/cmd/update.c | 11+++++++++++
Mdriver/env.h | 6+++---
Mmk/dist.mk | 41++++++++++++++++++++++++-----------------
Mscripts/release.sh | 14++++++--------
Mtest/dist/keys/README.md | 17++++++++++-------
Mtest/dist/run.sh | 47+++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 119 insertions(+), 49 deletions(-)

diff --git a/doc/RELEASE.md b/doc/RELEASE.md @@ -40,7 +40,9 @@ embeds the same bytes and the self-host byte-identity gate is preserved. ## Building release artifacts -Two layers, so the everyday target needs no secret key. +Both artifact paths require explicit release signing and update-channel trust +configuration. Hermetic tests opt into the checked-in non-release key; no +release target selects it implicitly. ### `make dist` — native host @@ -55,13 +57,14 @@ build/dist/ kit-2026.6.0-aarch64-macos.tar.gz.minisig detached sig over the tarball (bootstrap) ``` -The staging tree is `bin/kit`, `lib/libkit.a`, `include/`, `support/rt/{include,lib}/`, -`VERSION`, and README/license. Signing uses `KIT_SIGN_KEY=<path>` if set, -otherwise the in-tree **NON-RELEASE** test key (`test/dist/keys/nonrelease.key`) -with a loud warning — so the full `make dist` → verify → `kit update` pipeline -runs hermetically in CI. The `.kpkg` is the canonical update unit; the `.tar.gz` -carries its signed manifest internally, and the extra detached `.minisig` lets a -user with only stock `minisign` verify before unpacking. +The staging tree is `bin/kit`, `lib/libkit.a`, `include/`, +`support/rt/{include,lib}/`, `VERSION`, and README/license. The target requires +`KIT_SIGN_KEY=<path>`, `KIT_RELEASE_PUBKEYS=<path...>`, and +`KIT_UPDATE_INDEX_URL=<stable-url>`. It rejects the in-tree **NON-RELEASE** test +key unless a hermetic harness explicitly sets `KIT_RELEASE_ALLOW_TEST_KEY=1`. +The `.kpkg` is the canonical update unit; the `.tar.gz` carries its signed +manifest internally, and the extra detached `.minisig` lets a user with only +stock `minisign` verify before unpacking. ### `scripts/release.sh` — the multi-target matrix @@ -183,8 +186,9 @@ kit update --dry-run # print the plan; change nothing Networked updates resolve the channel index from `--index <url>` or `$KIT_UPDATE_INDEX_URL` and download through a fetch hook in the driver -(`driver_fetch_url`, which shells to `curl`/`wget`); libkit itself stays -network-free, and since packages are signed the transport is untrusted. +(`driver_fetch_url`, which invokes `curl` directly with no downloader +fallback); libkit itself stays network-free, and since packages are signed the +transport is untrusted. **Version policy**: channel-driven updates are **monotonic** by default — `kit update` refuses a version older than the running one. Deliberate moves bypass diff --git a/doc/RELEASE_AUDIT_2026_6_0.md b/doc/RELEASE_AUDIT_2026_6_0.md @@ -488,17 +488,17 @@ Implementation checkpoint (updated 2026-07-16): | KIT-P0-002 | In progress | native SDK precedence implementation present; final macOS matrix pending | | KIT-P1-013 | In progress | Windows direct/PATH/move/repair VM scenario passes; POSIX aggregate pending | | KIT-P1-011 | **Complete** | `test-driver-pkg`: 194 pass, 0 fail | -| KIT-P2-007 | **Complete** | `test-selfdist`: 95 pass, 0 fail; release configuration/test-anchor build gates pass | -| KIT-P1-012 | **Complete** | authenticated updater result cases included in `test-selfdist`: 95 pass, 0 fail | +| KIT-P2-007 | **Complete** | `test-selfdist`: 101 pass, 0 fail; release/dist configuration and test-anchor gates pass | +| KIT-P1-012 | **Complete** | authenticated updater result cases included in `test-selfdist`: 101 pass, 0 fail | | KIT-P1-001 | In progress | public registry and `kit targets` implemented; final aggregate pending | | KIT-P0-004 | In progress | public compatibility boundary implemented; final format matrix pending | | KIT-P1-002 | In progress | owned Android define test passes; NDK matrix pending | | KIT-P1-003 | **Complete** | `test-smoke-rv32`: 20 pass, 0 fail, including concurrent empty-cache/read-only/QEMU cases | | KIT-P1-004 | In progress | rv32 flag/ABI cases pass; final rv64 oracle pending | -| KIT-P0-003 | **Complete** | portable-C O0/O1/O2 native and cross driver cases pass | +| KIT-P0-003 | **Complete** | `test-driver-cc`: 162 pass, 0 fail, including 12 portable-C native/cross O0/O1/O2 cases | | KIT-P1-014 | **Complete** | reserved-`typeof` focused suite: 6 pass, 0 fail, including uthash probe | | KIT-P2-001 | **Complete** | `test-driver-stack-protector`: 133 pass, 0 fail; cJSON build/test passes | -| KIT-P1-015 | **Complete** | 21 multi-source dependency assertions pass in the driver suite | +| KIT-P1-015 | **Complete** | 21 multi-source dependency assertions pass within `test-driver-cc`: 162 pass, 0 fail | | KIT-P2-003 | **Complete** | `test-driver-diagnostics`: 15 pass, 0 fail | | KIT-P1-007 | **Complete** | `test-driver-strip`: object API 26/0, relocatable/archive 5/0, linked ELF/Mach-O/PE matrix 91/0 | | KIT-P1-017 | **Complete** | relocatable/linked ELF, Mach-O, COFF, and Wasm image cases included in `test-driver-tools`: 251 pass, 0 fail | diff --git a/driver/cmd/update.c b/driver/cmd/update.c @@ -748,6 +748,17 @@ int driver_update(int argc, char** argv) { } } + /* --check is specifically an authenticated channel-index query. Do not + * let an accompanying local action bypass that query (or, for an install, + * mutate state despite the read-only spelling). */ + if (do_check && + (file || want_version || do_list || do_prune || do_rollback)) { + driver_errf(UPDATE_TOOL, + "--check cannot be combined with a package, --version, " + "--list, --prune, or --rollback"); + return 2; + } + driver_env_init(&env); ctx = driver_env_to_context(&env); if (up_resolve_paths(&paths) != 0) { diff --git a/driver/env.h b/driver/env.h @@ -279,9 +279,9 @@ int driver_kit_home(char* buf, size_t cap); /* Fetch `url` to the local file `dest`, overwriting it. The transport is * UNTRUSTED — the caller verifies a signature/content-id over the bytes. file:// - * URLs are copied internally; other schemes use curl/wget (POSIX) or curl.exe - * (Windows), with the URL passed as a distinct argv element (no shell). Returns - * 0 on success, nonzero on any failure. */ + * URLs are copied internally; other schemes use curl on POSIX or curl.exe on + * Windows, with the URL passed as a distinct argv element (no shell and no + * downloader fallback). Returns 0 on success, nonzero on any failure. */ int driver_fetch_url(const char* url, const char* dest); /* Set a linked binary output's final mode according to the active umask. diff --git a/mk/dist.mk b/mk/dist.mk @@ -4,33 +4,40 @@ # release (doc/plan/SELFDIST.md). `make dist` builds RELEASE=1, stages a # self-contained kit/ tree, and emits — for the native host triple — a signed # fat .kpkg, a signed portable .tar.gz, and a detached .tar.gz.minisig (the -# stock-minisign bootstrap path). It signs with $(KIT_SIGN_KEY) when set, else -# the in-tree NON-RELEASE test key (with a loud warning), so the full -# make-dist -> verify -> update pipeline runs hermetically. The multi-target -# matrix + channel index live in scripts/release.sh. +# stock-minisign bootstrap path). Release trust is mandatory: callers provide +# KIT_SIGN_KEY, KIT_RELEASE_PUBKEYS, and KIT_UPDATE_INDEX_URL. Hermetic tests +# that intentionally use the in-tree non-release key must opt in explicitly +# with KIT_RELEASE_ALLOW_TEST_KEY=1. The multi-target matrix + channel index +# live in scripts/release.sh. .PHONY: dist +ifneq ($(filter dist,$(MAKECMDGOALS)),) +ifeq ($(strip $(KIT_SIGN_KEY)),) +$(error dist requires KIT_SIGN_KEY (production Minisign secret key file)) +endif +ifneq ($(KIT_RELEASE_ALLOW_TEST_KEY),1) +ifeq ($(realpath $(KIT_SIGN_KEY)),$(realpath test/dist/keys/nonrelease.key)) +$(error dist refuses the in-tree NON-RELEASE test signing key) +endif +endif +endif + DIST_DIR = build/dist DIST_STAGING = $(DIST_DIR)/kit DIST_REL_BIN = build/release/kit DIST_REL_LIB = build/release/libkit.a -DIST_KEY = $(if $(KIT_SIGN_KEY),$(KIT_SIGN_KEY),test/dist/keys/nonrelease.key) +DIST_KEY = $(KIT_SIGN_KEY) DIST_BASE = kit-$(KIT_VERSION)-$(KIT_HOST_TRIPLE) DIST_KPKG = $(DIST_DIR)/$(DIST_BASE).kpkg DIST_TARGZ = $(DIST_DIR)/$(DIST_BASE).tar.gz dist: - $(MAKE) RELEASE=1 \ - KIT_UPDATE_INDEX_URL="$(if $(KIT_UPDATE_INDEX_URL),$(KIT_UPDATE_INDEX_URL),https://example.invalid/kit-development.index)" \ - KIT_RELEASE_PUBKEYS="$(if $(KIT_RELEASE_PUBKEYS),$(KIT_RELEASE_PUBKEYS),test/dist/keys/nonrelease.pub)" \ - KIT_RELEASE_ALLOW_TEST_KEY="$(if $(KIT_RELEASE_PUBKEYS),$(KIT_RELEASE_ALLOW_TEST_KEY),1)" bin - @if [ -z "$(KIT_SIGN_KEY)" ]; then \ - printf '\n*** WARNING: KIT_SIGN_KEY is unset — signing with the in-tree\n'; \ - printf '*** NON-RELEASE test key (test/dist/keys/nonrelease.key). The\n'; \ - printf '*** artifacts verify but are NOT a real release. Set KIT_SIGN_KEY\n'; \ - printf '*** to sign for distribution.\n\n'; \ + @if [ ! -f "$(KIT_SIGN_KEY)" ]; then \ + printf '%s\n' 'dist: KIT_SIGN_KEY is not a file: $(KIT_SIGN_KEY)' >&2; \ + exit 2; \ fi + $(MAKE) RELEASE=1 bin @rm -rf $(DIST_STAGING) @mkdir -p $(DIST_STAGING)/bin $(DIST_STAGING)/lib $(DIST_STAGING)/support/rt cp $(DIST_REL_BIN) $(DIST_STAGING)/bin/kit @@ -43,12 +50,12 @@ dist: @for f in LICENSE LICENSE.txt LICENSE.md NOTICE NOTICE.txt; do \ [ -f "$$f" ] && cp "$$f" "$(DIST_STAGING)/$$f"; done; true $(DIST_REL_BIN) pkg create --name kit --version $(KIT_VERSION) \ - --format kpkg --native-shape fat -s $(DIST_KEY) \ + --format kpkg --native-shape fat -s "$(DIST_KEY)" \ --root $(DIST_STAGING) -o $(DIST_KPKG) $(DIST_REL_BIN) pkg create --name kit --version $(KIT_VERSION) \ - --format tar.gz -s $(DIST_KEY) \ + --format tar.gz -s "$(DIST_KEY)" \ --root $(DIST_STAGING) -o $(DIST_TARGZ) - $(DIST_REL_BIN) pkg sign -s $(DIST_KEY) \ + $(DIST_REL_BIN) pkg sign -s "$(DIST_KEY)" \ -o $(DIST_TARGZ).minisig --comment "kit $(KIT_VERSION) $(KIT_HOST_TRIPLE)" \ $(DIST_TARGZ) @echo "dist: $(abspath $(DIST_KPKG))" diff --git a/scripts/release.sh b/scripts/release.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash # scripts/release.sh — the multi-target kit release driver. # -# This is the matrix counterpart to `make dist`: where `make dist` stages and -# signs the toolchain for the NATIVE host triple only (and falls back to the -# in-tree NON-RELEASE test key), release.sh does the same staging + packaging -# for EVERY hosted (runnable) triple in scripts/hosted.sh's support set, signs +# This is the matrix counterpart to `make dist`: where `make dist` stages, +# packages, and signs the toolchain for the NATIVE host triple only, release.sh +# does the same for EVERY hosted (runnable) triple in scripts/hosted.sh's +# support set, signs # each artifact with the REAL release key, and finally emits + signs one # `kit-release 1` channel index covering all of them. See doc/plan/SELFDIST.md # ("Release artifacts", "Channel index format", and the scripts/release.sh work @@ -25,8 +25,7 @@ # # Prerequisites # ------------- -# * A REAL signing key in $KIT_SIGN_KEY (see below). Unlike `make dist`, this -# script never falls back to the in-tree test key — a release is signed with +# * A REAL signing key in $KIT_SIGN_KEY (see below). A release is signed with # the real release secret or not at all. # * Cross-build sysroots for every foreign-libc target, provisioned once with # `make provision TARGET=<selector>` (doc/plan/SYSROOTS.md). kit_cross.sh @@ -82,8 +81,7 @@ canon_path() { # ---- preconditions --------------------------------------------------------- # A real signing key is mandatory; releasing with the test key is never valid. [ -n "${KIT_SIGN_KEY:-}" ] || die \ - "KIT_SIGN_KEY is unset — set it to the real release minisign secret key. - (Unlike 'make dist', release.sh does NOT fall back to the in-tree test key.)" + "KIT_SIGN_KEY is unset — set it to the real release minisign secret key" [ -f "$KIT_SIGN_KEY" ] || die "KIT_SIGN_KEY=$KIT_SIGN_KEY is not a file" [ -n "${KIT_RELEASE_PUBKEYS:-}" ] || die \ "KIT_RELEASE_PUBKEYS is unset — provide production Minisign public-key file(s)" diff --git a/test/dist/keys/README.md b/test/dist/keys/README.md @@ -4,11 +4,14 @@ minisign (Ed25519) keypair, key id `c1709dd2922282f6`, used only by: - the hermetic self-distribution end-to-end test (`test/dist/run.sh`), and -- the `make dist` fallback signer when `KIT_SIGN_KEY` is unset. +- release-artifact harnesses that explicitly set the key paths and + `KIT_RELEASE_ALLOW_TEST_KEY=1`. -Its public key is embedded in `driver/release_key.c` as a built-in `kit update` -trust anchor **so the test and `make dist` artifacts verify against a built-in -key**. It is published in the open here on purpose; it confers **no** trust over -real kit distributions. The production release keypair is generated offline, its -secret never enters the tree, and its public key replaces/augments the embedded -set at release time (see `doc/plan/SELFDIST.md`). +Development binaries embed its public key as a built-in `kit update` trust +anchor so the hermetic tests can verify without external state. It is published +in the open here on purpose; it confers **no** trust over real kit +distributions. `make dist` and `scripts/release.sh` require explicit release +configuration and reject this key unless a test harness opts in. The production +release keypair is generated offline, its secret never enters the tree, and its +public key replaces/augments the embedded set at release time (see +`doc/plan/SELFDIST.md`). diff --git a/test/dist/run.sh b/test/dist/run.sh @@ -50,6 +50,22 @@ run_ok "release-config-production-shape" make -n -C "$repo_root" RELEASE=1 \ KIT_UPDATE_INDEX_URL=https://updates.example/stable.index \ KIT_RELEASE_PUBKEYS="$work/release-config.pub" bin +# The native distribution target is an official RELEASE=1 artifact path. It +# must never invent a channel, trust anchor, or signing key. Tests may use the +# checked-in non-release pair only through the same explicit opt-in as the +# multi-target release harness. +run_fail "release-dist-sign-key-required" make -n -C "$repo_root" dist +run_fail "release-dist-config-required" make -C "$repo_root" dist \ + KIT_SIGN_KEY="$work/release-config.key" +run_fail "release-dist-test-key-rejected" make -C "$repo_root" dist \ + KIT_SIGN_KEY="$SECKEY" \ + KIT_UPDATE_INDEX_URL=https://updates.example/stable.index \ + KIT_RELEASE_PUBKEYS="$PUBKEY" +run_ok "release-dist-explicit-test-shape" make -n -C "$repo_root" dist \ + KIT_SIGN_KEY="$SECKEY" KIT_RELEASE_ALLOW_TEST_KEY=1 \ + KIT_UPDATE_INDEX_URL=https://updates.example/stable.index \ + KIT_RELEASE_PUBKEYS="$PUBKEY" + # ---- local helpers --------------------------------------------------------- have_cmd() { command -v "$1" >/dev/null 2>&1; } @@ -58,6 +74,24 @@ have_cmd() { command -v "$1" >/dev/null 2>&1; } kh= kit_home() { kh="$work/$1"; export KIT_HOME="$kh"; } +# run_usage NAME CMD... : require the driver's reserved usage status (2). +run_usage() { + name=$1 + shift + "$@" > "$work/$name.out" 2> "$work/$name.err" + got=$? + if [ "$got" -eq 2 ]; then + ok "$name" + else + { + printf 'expected usage status 2, got %s\n' "$got" + sed 's/^/stdout: /' "$work/$name.out" + sed 's/^/stderr: /' "$work/$name.err" + } > "$work/$name.diag" + not_ok "$name" "$work/$name.diag" + fi +} + # stage_kpkg VER : build a signed fat kit kpkg containing bin/kit (= $KIT), a # VERSION file, and a small lib/ payload. Records a pass/fail through run_ok and # leaves the kpkg path in $LAST_KPKG (NOT echoed — calling this in a command @@ -165,6 +199,19 @@ kit_home kh stage_kpkg 2026.6.0 kpkg_60="$LAST_KPKG" +# --check is a channel-index query, never an alias for a local mutating action. +kit_home kh-check-local-package +run_usage "check-local-package-usage" "$KIT" update --check "$kpkg_60" +if [ -e "$KIT_HOME" ]; then + echo "local-package --check created KIT_HOME" > \ + "$work/check-local-package-no-state.diag" + not_ok "check-local-package-no-state" \ + "$work/check-local-package-no-state.diag" +else + ok "check-local-package-no-state" +fi +kit_home kh + run_ok "a-install-60" "$KIT" update "$kpkg_60" contains "a-install-60-msg" "$work/a-install-60.out" "installed kit 2026.6.0" current_is "a-current-60" "$kh" 2026.6.0