clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

Take release signatures from the repo's admins only

cat committed 49243f1
Any registered key could sign a release's SHA256SUMS, so a release
pushed by the owner could carry the word of a user with no say over the
repo. pre-receive now wants the signer's principal to be the repo's
owner or listed in forge/collaborators, as for pushing the ref itself.
commit
49243f1e974926e5980facf27975ce8859ca890b
parent
3e0e6259bd67e28e82c05677fe81384015ff01d4
author
cat <admin@dev.site>
kind
fix risk score 2.2 of 3, Jev's estimate
5 files changed, +24 -13
MREADME.md7+++++--
Mbin/forge6++++--
Mguide/index.html2+-
Mlib/releases.ts5+++--
Mtest/scenarios/release-signature.sh17+++++++++++------
M README.md +5 -2
M bin/forge +4 -2
@@ -1822,8 +1822,8 @@ scan_secrets() {
18221822 # trailers name the tag and its commit (as this push leaves it), whose tree18231823 # is regular files only, none over forge.releasemaxsize, each listed in18241824 # SHA256SUMS with its sha256, and SHA256SUMS.sig a forge-release signature1825-# of SHA256SUMS for this repo and tag (release_message) by a key of this1826-# forge. With forge.releaserequireci true,1825+# of SHA256SUMS for this repo and tag (release_message) by the key of the1826+# repo's owner or a collaborator. With forge.releaserequireci true,18271827 # the tag's tree needs a verified passing CI attestation too. Releases skip18281828 # the secret check, which reads branches and tags only. Uses $updates,18291829 # $notes_new and the scratch directory $rt.
@@ -1874,6 +1874,8 @@ check_release() {
18741874 release_message "$OWNER/$NAME" "$2" "$rt/sums" "$rt/message"18751875 rsigner=$(verify_block "$rt/message" "$rt/sums.sig" "$FORGE_STATE/allowed_signers" "$RNS") ||18761876 deny "$rn: SHA256SUMS.sig is not a good $RNS signature of SHA256SUMS for $OWNER/$NAME $2 by a key of this forge"1877+ [ "$rsigner" = "$OWNER" ] || grep -qxF -- "$rsigner" "$GIT_DIR/forge/collaborators" 2>/dev/null ||1878+ deny "$rn: SHA256SUMS.sig is $rsigner's, and only $OWNER and collaborators of $OWNER/$NAME sign its releases"18771879 [ "$(git config --bool forge.releaserequireci || echo false)" = true ] || return 018781880 notes=${notes_new:-refs/notes/ci}18791881 [ "$notes" != "$ZERO" ] || notes=refs/notes/ci
M guide/index.html +1 -1
@@ -215,7 +215,7 @@ log sha256:6cc79019... 4 lines
215215 <p>The pages server serves them the way Cloudflare Pages does, so a site built for there works unchanged: <code>_headers</code> and <code>_redirects</code> apply, <code>/about</code> finds <code>about.html</code> or <code>about/index.html</code>, a missing page gets the nearest <code>404.html</code> (or <code>index.html</code>, for a single-page app), and previews are sent with <code>noindex</code>. Old <code>/sites/...</code> paths redirect to the hostname. Without a pages domain, sites are plain files under <code>/sites/&lt;owner&gt;/&lt;repo&gt;/&lt;name&gt;/</code> on the forge itself.</p>216216 <p>Deleting a branch deletes its previews: the ref, the served link and the hostname, so the sites and review pages stop listing them. Previews of merged branches stay until their branch is deleted. The extracted files stay in the store until <code>forge gc</code>, which removes store trees nothing links to, hostnames whose deploy is gone and leftover previews, skipping store entries changed in the last ten minutes, so it can run while people push.</p>217217 <h3>Releases are the same idea</h3>218-<p><code>forge release publish v1.0 &lt;file&gt;...</code> commits a tag's files to <code>refs/releases/v1.0</code> with a <code>SHA256SUMS</code> signed by your key for that repo and tag (<code>ssh-keygen -n forge-release</code>), so it can't pass for another release. <code>pre-receive</code> checks every sum, the signature, the tag and a size cap, and with <code>forge.releaserequireci</code> a verified pass for the tag's tree. <code>post-receive</code> extracts the tree into the store and hard-links the files into <code>/dami/greet/releases/download/v1.0/</code>, plus <code>latest/</code>; hard links, so the mirror copies them. Clones and fetches never see <code>refs/releases</code>. A private repo's releases aren't served; its readers get them with <code>ssh git@host release get</code>.</p>218+<p><code>forge release publish v1.0 &lt;file&gt;...</code> commits a tag's files to <code>refs/releases/v1.0</code> with a <code>SHA256SUMS</code> signed by your key for that repo and tag (<code>ssh-keygen -n forge-release</code>), so it can't pass for another release. <code>pre-receive</code> checks every sum, the signature (an owner's or collaborator's key), the tag and a size cap, and with <code>forge.releaserequireci</code> a verified pass for the tag's tree. <code>post-receive</code> extracts the tree into the store and hard-links the files into <code>/dami/greet/releases/download/v1.0/</code>, plus <code>latest/</code>; hard links, so the mirror copies them. Clones and fetches never see <code>refs/releases</code>. A private repo's releases aren't served; its readers get them with <code>ssh git@host release get</code>.</p>219219 </div>220220 </section>221221
M lib/releases.ts +3 -2
M test/scenarios/release-signature.sh +11 -6