Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
echo "Install smoke pin: $TAG (make print-sfetch-version)"
bash scripts/install-sfetch.sh --dry-run --tag "$TAG" --require-minisign

- name: Live verified bootstrap (N-1 SHA256SUMS route)
- name: Live verified bootstrap (N-1 minisig/current-key route)
run: |
set -euo pipefail
TAG=$(make -s print-sfetch-version)
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,13 @@ jobs:
# Per-file .sha256 removed - signed SHA256SUMS is the authoritative source
# Maintainer generates SHA256SUMS locally and signs with minisign/pgp

# Appends this matrix entry's archive to the release created above.
# action-gh-release@v3 is idempotent on the tag; parallel matrix jobs each
# upload a distinctly-named asset, so no upload_url plumbing is needed.
# Reopens the same release as a draft while appending this matrix entry's
# distinctly named archive. Every release-writing action must state
# draft: true so parallel uploads cannot publish before signing.
- name: Upload Archive
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
draft: true
files: ${{ env.ASSET_FILE }}

- name: Signing handled manually
Expand Down
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.13] - 2026-09-18

### Changed

- **Verified self-bootstrap and CI smoke now pin published v0.4.12.** The
development bootstrap remains on a fully signed N-1 release and exercises
the current-key minisign route.

### Fixed

- **Release matrix uploads preserve draft state explicitly.** Every
release-writing action step now declares `draft: true`, with a precommit
regression that rejects missing or false draft settings before a tag run.

## [0.4.12] - 2026-09-17

### Changed
Expand Down Expand Up @@ -204,7 +218,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

> **Maintenance note:** This file is pruned to the latest 10 releases. For older entries, see `docs/releases/`.

[Unreleased]: https://github.com/3leaps/sfetch/compare/v0.4.12...HEAD
[Unreleased]: https://github.com/3leaps/sfetch/compare/v0.4.13...HEAD
[0.4.13]: https://github.com/3leaps/sfetch/compare/v0.4.12...v0.4.13
[0.4.12]: https://github.com/3leaps/sfetch/compare/v0.4.11...v0.4.12
[0.4.11]: https://github.com/3leaps/sfetch/compare/v0.4.10...v0.4.11
[0.4.10]: https://github.com/3leaps/sfetch/compare/v0.4.9...v0.4.10
Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ BIN_DIR := $(CURDIR)/bin
# pin (see .github/workflows/ci.yml). Always a fully-signed published release —
# never "latest" and never the in-flight cut — so main CI cannot 404 during the
# tag/upload window. Advance after each release publishes + signs.
SFETCH_VERSION := v0.4.11
SFETCH_VERSION := v0.4.12
GONEAT_VERSION ?= v0.6.0
GOVULNCHECK_VERSION ?= v1.8.0

Expand All @@ -61,7 +61,7 @@ CORPUS_DEST ?= test-corpus
.PHONY: release-verify-key release-verify-minisign-pubkey release-verify-keys release-verify-signatures release-verify
.PHONY: release-clean bootstrap-script build-all gosec gosec-high update-scoop-manifest
.PHONY: version-check version-set version-patch version-minor version-major
.PHONY: print-sfetch-version test-release-verify-checksums test-release-verify-signatures test-bootstrap-sfetch-verified test-bootstrap-range-release test-install-anchor-select
.PHONY: print-sfetch-version test-release-verify-checksums test-release-verify-signatures test-release-workflow-draft test-bootstrap-sfetch-verified test-bootstrap-range-release test-install-anchor-select

all: build

Expand Down Expand Up @@ -211,6 +211,7 @@ precommit: ## Run pre-commit checks (goneat assess + Go tests + build)
# CI runs make precommit (not prepush); keep fail-closed release-verify regression on this path.
$(MAKE) test-release-verify-checksums
$(MAKE) test-release-verify-signatures
$(MAKE) test-release-workflow-draft
$(MAKE) test-bootstrap-sfetch-verified
$(MAKE) test-bootstrap-range-release
$(MAKE) test-install-anchor-select
Expand Down Expand Up @@ -286,6 +287,9 @@ test-release-verify-checksums: ## Regression: fail-closed checksum verify (corru
test-release-verify-signatures: ## Regression: required installer minisig + sign targets
@./scripts/test-release-verify-signatures.sh

test-release-workflow-draft: ## Regression: every release-writing action preserves draft state
@python3 ./scripts/test-release-workflow-draft.py

test-bootstrap-sfetch-verified: ## Regression: dual-route bootstrap rejects + fail-closed
@./scripts/test-bootstrap-sfetch-verified.sh

Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,22 +214,22 @@ Match `-P` to the tag: tags ≤ v0.4.11 verify under the previous key; newer tag

```bash
# Three-step path when install-sfetch.sh.minisig is published (v0.4.11+)
TAG=v0.4.12
TAG=v0.4.13
curl -fsSL "https://github.com/3leaps/sfetch/releases/download/${TAG}/install-sfetch.sh" -o install-sfetch.sh
curl -fsSL "https://github.com/3leaps/sfetch/releases/download/${TAG}/install-sfetch.sh.minisig" -o install-sfetch.sh.minisig
minisign -Vm install-sfetch.sh -P RWTqUZ/PtmfAbQ3RKIlp/YTKT6zFeNMAZ8iAMelhlmqEsVqjKj3ctSA1
bash install-sfetch.sh --tag "$TAG" --yes

# Or use the shared engine (CI/Makefile; refuses "latest"):
# ./scripts/bootstrap-sfetch-verified.sh --version v0.4.12 --dir ~/.local/bin
# ./scripts/bootstrap-sfetch-verified.sh --version v0.4.13 --dir ~/.local/bin
```

**GitHub Actions:** pin the composite action by commit SHA — see [CI/CD Usage Guide](docs/cicd-usage-guide.md).

```yaml
- uses: 3leaps/sfetch/.github/actions/setup-sfetch@<commit-sha>
with:
sfetch-version: v0.4.12
sfetch-version: v0.4.13
```

#### Quick install (interactive humans)
Expand All @@ -254,7 +254,7 @@ Pass arguments using `bash -s --`:
curl -sSfL .../install-sfetch.sh | bash -s -- --dir ~/bin

# Install specific version
curl -sSfL .../install-sfetch.sh | bash -s -- --tag v0.4.12
curl -sSfL .../install-sfetch.sh | bash -s -- --tag v0.4.13

# Dry run (download and verify, don't install)
curl -sSfL .../install-sfetch.sh | bash -s -- --dry-run
Expand Down Expand Up @@ -302,13 +302,13 @@ Tag CI creates a **draft** release with unsigned archives. Maintainers generate
export SFETCH_MINISIGN_KEY=/path/to/sfetch.key
export SFETCH_PGP_KEY_ID=security@fulmenhq.dev # optional

RELEASE_TAG=v0.4.12 make release-download
RELEASE_TAG=v0.4.12 make release-checksums
RELEASE_TAG=v0.4.12 make release-sign # signs manifests + install-sfetch.sh
RELEASE_TAG=v0.4.13 make release-download
RELEASE_TAG=v0.4.13 make release-checksums
RELEASE_TAG=v0.4.13 make release-sign # signs manifests + install-sfetch.sh
make release-verify
RELEASE_TAG=v0.4.12 make release-notes
RELEASE_TAG=v0.4.12 make release-upload
gh release edit v0.4.12 --draft=false # publish only after signatures upload
RELEASE_TAG=v0.4.13 make release-notes
RELEASE_TAG=v0.4.13 make release-upload
gh release edit v0.4.13 --draft=false # publish only after signatures upload
```

Set `RELEASE_TAG` to the tag you're publishing. The scripts in `scripts/` can be used individually if you prefer manual control.
Expand Down
31 changes: 31 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
## v0.4.13

### Summary

Maintenance release that advances development self-bootstrap and CI smoke to
the fully signed v0.4.12 release and keeps every automated release upload in
draft state until the attended sign, verify, upload, and publish ceremony.
There are no CLI, dependency, or trust-anchor changes.

### Highlights

**Verified bootstrap**
- The Makefile N-1 pin advances from v0.4.11 to published v0.4.12.
- A fresh bootstrap of that pin uses the minisign route and current release
trust anchor. Historical v0.4.11 previous-key and v0.4.10 checksum-route
coverage remains in place.

**Draft-preserving release uploads**
- Every `softprops/action-gh-release` step explicitly sets `draft: true`,
including each matrix archive upload.
- A focused precommit regression rejects omitted or false draft settings.
- Publication remains an attended action after signatures and checksum
manifests are verified and uploaded.

### Compatibility

No user migration is required. CLI behavior, dependencies, supported
platforms, and verification trust anchors are unchanged.

---

## v0.4.12

### Summary
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.12
0.4.13
29 changes: 29 additions & 0 deletions docs/releases/v0.4.13.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# sfetch v0.4.13

Maintenance release for verified development bootstrap and draft-preserving
release uploads.

## Highlights

### Verified bootstrap

- The Makefile N-1 self-bootstrap and CI install-smoke pin advances from
v0.4.11 to the fully signed v0.4.12 release.
- Fresh isolated bootstrap verification selects the minisign route and current
release trust anchor for v0.4.12.
- The bootstrap engine accepts v0.4.13 as its committed ceiling. Historical
v0.4.11 previous-key and v0.4.10 checksum-route coverage remains unchanged.

### Draft-preserving release uploads

- Every `softprops/action-gh-release` step explicitly declares `draft: true`,
including all release-matrix archive uploads.
- A regression wired into `make precommit` enumerates those release-writing
steps and rejects omitted or false draft settings.
- Tag CI remains non-consumable until the maintainer completes the attended
sign, verify, upload, and explicit publish sequence.

## Compatibility

There are no CLI, dependency, supported-platform, self-update, or trust-anchor
changes. Existing consumers require no migration.
12 changes: 6 additions & 6 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,15 @@ export SFETCH_MINISIGN_KEY=/path/to/key.key
export SFETCH_MINISIGN_PUB=/path/to/key.pub
export SFETCH_PGP_KEY_ID=your-key-id # optional

RELEASE_TAG=v0.4.12 make release-download
RELEASE_TAG=v0.4.12 make release-checksums
RELEASE_TAG=v0.4.12 make release-sign
RELEASE_TAG=v0.4.13 make release-download
RELEASE_TAG=v0.4.13 make release-checksums
RELEASE_TAG=v0.4.13 make release-sign
make release-verify-signatures
make release-export-keys
make release-verify-keys
RELEASE_TAG=v0.4.12 make release-notes
RELEASE_TAG=v0.4.12 make release-upload
gh release edit v0.4.12 --draft=false
RELEASE_TAG=v0.4.13 make release-notes
RELEASE_TAG=v0.4.13 make release-upload
gh release edit v0.4.13 --draft=false
```

### What is signed (v0.4.11+)
Expand Down
2 changes: 1 addition & 1 deletion scripts/bootstrap-sfetch-verified.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ set -euo pipefail
# make test-bootstrap-range-release asserts MAX == v$(cat VERSION) using two
# committed values at the same SHA (never self-advancing at install time).
readonly SFETCH_BOOTSTRAP_MIN="v0.4.9"
readonly SFETCH_BOOTSTRAP_MAX="v0.4.12"
readonly SFETCH_BOOTSTRAP_MAX="v0.4.13"
# First release that publishes install-sfetch.sh.minisig (route selection).
# Keep this accurate: too low → minisig route without asset; too high → 5-step
# when 3-step exists. Both routes verify; neither silently falls back.
Expand Down
30 changes: 15 additions & 15 deletions scripts/test-bootstrap-sfetch-verified.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ if "$SCRIPT" --version latest --dir /tmp 2>/dev/null; then fail "latest should f
if "$SCRIPT" --version main --dir /tmp 2>/dev/null; then fail "main should fail"; else pass "rejects main"; fi
if "$SCRIPT" --version v0.4.11-rc1 --dir /tmp 2>/dev/null; then fail "prerelease should fail"; else pass "rejects prerelease"; fi
if "$SCRIPT" --version v0.4.8 --dir /tmp 2>/dev/null; then fail "below min should fail"; else pass "rejects below min"; fi
if "$SCRIPT" --version v0.4.13 --dir /tmp 2>/dev/null; then fail "above max should fail"; else pass "rejects above max"; fi
if "$SCRIPT" --version v0.4.14 --dir /tmp 2>/dev/null; then fail "above max should fail"; else pass "rejects above max"; fi
# F2: leading-zero components must not pass range/route selection
if "$SCRIPT" --version v0.4.09 --dir /tmp 2>/dev/null; then fail "v0.4.09 leading zero should fail"; else pass "rejects v0.4.09 leading zero"; fi
if "$SCRIPT" --version v0.04.11 --dir /tmp 2>/dev/null; then fail "v0.04.11 leading zero should fail"; else pass "rejects v0.04.11 leading zero"; fi
Expand Down Expand Up @@ -212,7 +212,7 @@ NEW_EPHEMERAL_PUBKEY="$(grep -E '^RW' "$NEWPUB" | head -n1 | tr -d '\r\n')"
[ "$OLD_EPHEMERAL_PUBKEY" != "$NEW_EPHEMERAL_PUBKEY" ] || fail "stand-in keys must differ"

SRV_ROOT="$WORKDIR/www"
mkdir -p "$SRV_ROOT/v0.4.11" "$SRV_ROOT/v0.4.10" "$SRV_ROOT/v0.4.12"
mkdir -p "$SRV_ROOT/v0.4.11" "$SRV_ROOT/v0.4.10" "$SRV_ROOT/v0.4.13"

make_stub_installer() {
local dest="$1"
Expand Down Expand Up @@ -244,7 +244,7 @@ STUB

make_stub_installer "$SRV_ROOT/v0.4.11/install-sfetch.sh"
make_stub_installer "$SRV_ROOT/v0.4.10/install-sfetch.sh"
make_stub_installer "$SRV_ROOT/v0.4.12/install-sfetch.sh"
make_stub_installer "$SRV_ROOT/v0.4.13/install-sfetch.sh"

minisign -S -s "$KEY" -t "test-v0.4.11" -m "$SRV_ROOT/v0.4.11/install-sfetch.sh"
(
Expand Down Expand Up @@ -337,47 +337,47 @@ grep -Eq 'anchor=legacy' "$OUT_CURONLY" || fail "expected anchor=legacy selectio
if grep -q '^route=' "$OUT_CURONLY"; then fail "failed run must not emit machine route="; fi
pass "old pin ignores current anchor (no fallback)"

# Post-cutover selection: v0.4.12 is in the production engine's supported range.
# Post-cutover selection: v0.4.13 is the production engine's accepted ceiling.
# Stand-in keys prove the rule: new pins use current, previous-key sigs fail.
NEWENG="$WORKDIR/newpin-engine.sh"
make_fixture_engine "$NEWENG" "$BASE" "$OLD_EPHEMERAL_PUBKEY" "SFETCH_MINISIGN_PUBKEY_LEGACY"
sed -i.bak "s|${PROD_PUBKEY}|${NEW_EPHEMERAL_PUBKEY}|g" "$NEWENG"
grep -q "$NEW_EPHEMERAL_PUBKEY" "$NEWENG" || fail "new-pin engine missing current stand-in"
grep -q "$PROD_PUBKEY" "$NEWENG" && fail "new-pin engine still has production current key"
grep -q "$OLD_EPHEMERAL_PUBKEY" "$NEWENG" || fail "new-pin engine lost legacy stand-in"
grep -q 'SFETCH_BOOTSTRAP_MAX="v0.4.12"' "$NEWENG" || fail "new-pin engine MAX drifted"
grep -q 'SFETCH_BOOTSTRAP_MAX="v0.4.13"' "$NEWENG" || fail "new-pin engine MAX drifted"

# Positive: post-cutover pin verifies against the current anchor.
minisign -S -s "$NEWKEY" -t "test-v0.4.12-new" -m "$SRV_ROOT/v0.4.12/install-sfetch.sh"
minisign -S -s "$NEWKEY" -t "test-v0.4.13-new" -m "$SRV_ROOT/v0.4.13/install-sfetch.sh"
NEWGOOD_DIR="$WORKDIR/newgood"
mkdir -p "$NEWGOOD_DIR"
set +e
OUT_NEWGOOD="$WORKDIR/out-newgood.txt"
"$NEWENG" --version v0.4.12 --dir "$NEWGOOD_DIR" >"$OUT_NEWGOOD" 2>&1
"$NEWENG" --version v0.4.13 --dir "$NEWGOOD_DIR" >"$OUT_NEWGOOD" 2>&1
RC=$?
set -e
[ "$RC" -eq 0 ] || fail "positive v0.4.12 should succeed (log: $(cat "$OUT_NEWGOOD"))"
[ "$RC" -eq 0 ] || fail "positive v0.4.13 should succeed (log: $(cat "$OUT_NEWGOOD"))"
[ "$(awk 'BEGIN{c=0} /^route=/{c++} END{print c}' "$OUT_NEWGOOD")" -eq 1 ] || fail "expected one route= field"
grep -q '^route=minisig$' "$OUT_NEWGOOD" || fail "positive v0.4.12 machine route missing"
grep -Eq 'anchor=current' "$OUT_NEWGOOD" || fail "positive v0.4.12 should log anchor=current"
grep -q '^route=minisig$' "$OUT_NEWGOOD" || fail "positive v0.4.13 machine route missing"
grep -Eq 'anchor=current' "$OUT_NEWGOOD" || fail "positive v0.4.13 should log anchor=current"
[ -f "$NEWGOOD_DIR/.stub-ran" ] || fail "installer should execute after successful verify"
[ -x "$NEWGOOD_DIR/sfetch" ] || fail "sfetch binary should be installed"
version_output_matches_pin "$("$NEWGOOD_DIR/sfetch" --version 2>&1)" "v0.4.12" || fail "stub sfetch version"
version_output_matches_pin "$("$NEWGOOD_DIR/sfetch" --version 2>&1)" "v0.4.13" || fail "stub sfetch version"
pass "post-cutover pin uses current anchor"

# Negative: previous-key signature must fail for a post-cutover pin (no downgrade).
minisign -S -s "$OLDKEY" -t "test-v0.4.12-old" -m "$SRV_ROOT/v0.4.12/install-sfetch.sh"
minisign -S -s "$OLDKEY" -t "test-v0.4.13-old" -m "$SRV_ROOT/v0.4.13/install-sfetch.sh"
NEWDOWNGRADE_DIR="$WORKDIR/newdowngrade"
mkdir -p "$NEWDOWNGRADE_DIR"
set +e
OUT_DOWNGRADE="$WORKDIR/out-downgrade.txt"
"$NEWENG" --version v0.4.12 --dir "$NEWDOWNGRADE_DIR" >"$OUT_DOWNGRADE" 2>&1
"$NEWENG" --version v0.4.13 --dir "$NEWDOWNGRADE_DIR" >"$OUT_DOWNGRADE" 2>&1
RC=$?
set -e
[ "$RC" -ne 0 ] || fail "previous-key signature for v0.4.12 must fail"
[ "$RC" -ne 0 ] || fail "previous-key signature for v0.4.13 must fail"
[ ! -f "$NEWDOWNGRADE_DIR/.stub-ran" ] || fail "installer must not run on downgrade attempt"
[ ! -f "$NEWDOWNGRADE_DIR/sfetch" ] || fail "sfetch must not be installed on failed verify"
grep -Eq 'anchor=current' "$OUT_DOWNGRADE" || fail "expected anchor=current selection for v0.4.12"
grep -Eq 'anchor=current' "$OUT_DOWNGRADE" || fail "expected anchor=current selection for v0.4.13"
if grep -q '^route=' "$OUT_DOWNGRADE"; then fail "failed run must not emit machine route="; fi
pass "post-cutover pin rejects previous-key signature (no downgrade)"

Expand Down
Loading
Loading