clone

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

Publish a forge name and a made-up machine, not the host

cat committed 1401264
Attestations said `runner dami@MacBook-Pro-3`, and site deploys and
releases `Built-By:` the same: the local account and the hostname, both
published, and a hostname can say who someone is. They now say who in
forge terms and where without saying which machine: git config
forge.user, else the name allowed_signers gives the signing key; and git
config forge.machine, else two words and two hex digits picked once from
a hash of the hostname and a random salt and kept in the global git
config, so it stays the same on that machine.

Pages show neither, which also covers what is already published: the CI
line says only how long and who signed, commit pages show the signed
statement without the runner's value (the note in refs/notes/ci keeps it
for checking the signature), the releases and sites pages drop the
builder, and the insights' ci.parquet leaves the runner out.
commit
14012640ec591e8dd97500a5327f823f287fb5df
parent
b3db93038c62d33b856ecbd52a28c7bc3028fc05
author
cat <admin@dev.site>
ci
✓ passed in 75s, signed by cat
kind
feat risk score 2.2 of 3, Jev's estimate
review
#9 Publish a forge name and a made-up machine, not the host
ci attestation
forge-attestation 1
type ci
commit 14012640ec591e8dd97500a5327f823f287fb5df
tree 41f167aececb04f620533fd3520ccc4982acf5dd
script .forge/ci 38df37c0d5a6382f60569079a0dd821094e37e4a
result pass
exit 0
started 2026-10-19T00:15:35Z
duration 75s
runner (not shown here; refs/notes/ci has it)
system Darwin 25.5.0 arm64
env host
log sha256:c0651e0d23e456aec5c44e11bb45bb41379dd3e3d5e718063522cf31f7e0f51c 243 lines
|remote: review #4: /dami/greet/reviews/4/
|pushed wip/i18n
|d56b361 running .forge/ci: greet.sh: say what it is
|  | sh: /bin/sh
|  | ok   greet
|d56b361 pass in 0s, signed
|pushed main
|remote: review #4: /dami/greet/reviews/4/
|pushed wip/i18n
|e5e9ed5 running .forge/ci: wave at the world
|  | sh: /bin/sh
|  | FAIL greet : got 'hello, world o/', want 'hello, world'
|e5e9ed5 fail in 0s, signed
|forge: 1 commit(s) did not pass
|remote: review #6: /dami/greet/reviews/6/
|pushed ana/wave
|c623adc running .forge/ci: update the test for the wave
|  | sh: /bin/sh
|  | ok   greet
|c623adc pass in 0s, signed
|remote: review #6: /dami/greet/reviews/6/
|pushed ana/wave
|warning: You appear to have cloned an empty repository.
|site home: deployed 61d77b8 -> /sites/ana/dotfiles/home/
|created ana/notes
|remote: review #1: /ana/notes/reviews/1/
|pushed ana/todo
|landing #1 A todo list: ana/todo -> main
|2637f4e running .forge/ci: web, server and Python versions of greet
|  | sh: /bin/sh
|  | ok   greet
|2637f4e pass in 0s, signed
|pushed main
|812e8ab running .forge/ci: messages: notes for the later regions
|  | sh: /bin/sh
|  | ok   greet
|812e8ab pass in 0s, signed
|remote: review #7: /dami/greet/reviews/7/
|pushed dami/regions
|created dami/forge
|warning: You appear to have cloned an empty repository.
|site guide: deployed 7b28dad -> /sites/dami/forge/guide/
|(node:56308) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built ana/dotfiles in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:56432) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built ana/notes in 1s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:56569) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built dami/forge in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:56700) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built dami/greet in 1s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:57459) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built - in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|226 of 226 pages render the same without 11ty
|guide: 4 files, links ok
|ok

Signature: good, cat

12 files changed, +122 -17
MREADME.md12++++++-
Mbin/forge53+++++++++++++++++++++++++++---
Mguide/index.html2+-
Mlib/forge.ts4++
Mlib/insights.ts2+-
Mlib/render.ts4+-
Msite/_includes/insights-body.njk2+-
Msite/repo/commit.njk2+-
Msite/repo/releases.njk2+-
Msite/repo/sites.njk2+-
Atest/scenarios/publish-who.sh49++++++++++++++++++++++++++++
Mtest/scenarios/release-page.sh5++-
M README.md +10 -2
@@ -361,7 +361,7 @@ script .forge/ci 99a0d5e...
361361 result pass362362 exit 0363363 duration 41s364-runner dami@mbp364+runner cat@early-yak-00365365 system Darwin 25.5.0 arm64366366 env host367367 log sha256:... 120 lines
@@ -369,6 +369,14 @@ log sha256:... 120 lines
369369 -----BEGIN SSH SIGNATURE-----370370 ```371371 372+`runner` is your forge name and a made-up name for the machine:373+`git config forge.user`, else the name `allowed_signers` gives your signing374+key; and `git config forge.machine`, else two words and two hex digits375+picked once from a hash of the hostname and a random salt, then kept in your376+global git config. Never the hostname or your local account, since the377+statement is published; site deploys and releases name their builder the378+same way (`Built-By`). Pages don't show either: they show who signed.379+372380 The statement is appended to `refs/notes/ci`, and `forge push` merges and373381 pushes those notes with the branch. Results are keyed by **tree**, so a374382 reworded or reordered commit with an identical tree keeps its result without
@@ -680,7 +688,7 @@ Tables: `events` (pushes, comments, approvals, builds), `builds` and
680688 `build_repos` (each line of `metrics.jsonl`), `reviews` (each line of a681689 review log: created, revisions, approvals, deleted), `review_ends` (landed682690 or closed, and when) and `ci` (each attestation: commit, tree, result,683-duration, runner, signer). A review counts as landed when its branch is691+duration, runner, signer; `ci.parquet` leaves the runner out). A review counts as landed when its branch is684692 deleted with its tip in the default branch, as `forge land` leaves it; one685693 whose tip reaches the default branch while the branch stays counts from686694 when the lake saw it.
M bin/forge +48 -5
@@ -149,6 +149,49 @@ stack_base() {
149149 done150150 }151151 152+# whoami_at: who made an attestation, site deploy or release, and where, as153+# the lines publish it: <forge user>@<machine>. The forge user is154+# git config forge.user, else the name allowed_signers gives the signing155+# key (forge.allowedsigners), else unknown; never the local account name.156+# The machine is git config forge.machine, which is a made-up name the157+# first time: two words and two hex digits picked by a hash of the158+# hostname and a random salt, stored in the global git config, so it stays159+# the same on this machine and says nothing about it. Pages show neither:160+# who signed is what they show.161+whoami_at() { printf '%s@%s' "$(forge_user)" "$(machine)"; }162+163+forge_user() {164+ u=$(git config --get forge.user 2>/dev/null || true)165+ if [ -z "$u" ]; then166+ k=$(signing_key 2>/dev/null || true)167+ case $k in *.pub) ;; *) [ -f "$k.pub" ] && k=$k.pub ;; esac168+ s=$(git config --get forge.allowedsigners 2>/dev/null || true)169+ case $s in "~/"*) s=$HOME/${s#"~/"} ;; esac170+ if [ -f "$k" ] && [ -f "$s" ]; then171+ blob=$(awk '{ print $2; exit }' "$k")172+ [ -z "$blob" ] || u=$(awk -v b="$blob" '{ for (i = 2; i <= NF; i++) if ($i == b) { split($1, p, ","); print p[1]; exit } }' "$s")173+ fi174+ fi175+ printf '%s' "${u:-unknown}"176+}177+178+machine() {179+ m=$(git config --get forge.machine 2>/dev/null || true)180+ if [ -z "$m" ]; then181+ h=$( (hostname 2>/dev/null; od -An -N16 -tx1 /dev/urandom) | { shasum -a 256 2>/dev/null || sha256sum; } | cut -c1-6)182+ m=$(printf '%s\n' "$h" | awk '{183+ a = "amber brisk calm deft eager fair gentle hazel ivory jolly keen lucid mellow nimble olive plain quiet rosy sage tidy umber vivid wry young zesty bold clear dusky early fern grand humble"184+ n = "heron otter finch badger lark marten wren stoat ibis vole puffin tern egret hare lynx newt plover raven robin seal shrike swift teal thrush viper whelk yak zebu crane dove kestrel moth"185+ split(a, A, " "); split(n, N, " ")186+ x = index("0123456789abcdef", substr($0, 1, 1)) * 16 + index("0123456789abcdef", substr($0, 2, 1))187+ y = index("0123456789abcdef", substr($0, 3, 1)) * 16 + index("0123456789abcdef", substr($0, 4, 1))188+ printf "%s-%s-%s", A[x % 32 + 1], N[y % 32 + 1], substr($0, 5, 2)189+ }')190+ git config --global forge.machine "$m" 2>/dev/null || true191+ fi192+ printf '%s' "$m"193+}194+152195 signing_key() {153196 k=${FORGE_SIGNING_KEY:-$(git config --get forge.signingkey || git config --get user.signingkey || true)}154197 if [ -z "$k" ]; then
@@ -262,7 +305,7 @@ attest_one() {
262305 echo "exit $code"263306 echo "started $started"264307 echo "duration ${dur}s"265- echo "runner $(id -un)@$(hostname -s 2>/dev/null || hostname)"308+ echo "runner $(whoami_at)"266309 echo "system $(uname -srm)"267310 echo "env $envdesc"268311 echo "log sha256:$(sha256 "$tmp/log") $(wc -l <"$tmp/log" | tr -d ' ') lines"
@@ -454,8 +497,8 @@ publish_dir() {
454497 say "site $name: unchanged"455498 return 0456499 fi457- msg=$(printf 'site %s: %s\n\nSource-Commit: %s\nSource-Ref: %s\nBuild: %s\nBuilt-By: %s@%s\n' \458- "$name" "$(git log -1 --format=%s "$src" 2>/dev/null || echo "$dir")" "$src" "$br" "$build" "$(id -un)" "$(hostname -s 2>/dev/null || hostname)")500+ msg=$(printf 'site %s: %s\n\nSource-Commit: %s\nSource-Ref: %s\nBuild: %s\nBuilt-By: %s\n' \501+ "$name" "$(git log -1 --format=%s "$src" 2>/dev/null || echo "$dir")" "$src" "$br" "$build" "$(whoami_at)")459502 # Signed with the key that signs CI results, so the forge can show who460503 # deployed it.461504 commit=
@@ -584,8 +627,8 @@ release_publish() {
584627 for f in SHA256SUMS SHA256SUMS.sig; do printf '100644 blob %s\t%s\n' "$(git hash-object -w -- "$tmp/$f")" "$f" >>"$tmp/tree"; done585628 tree=$(git mktree <"$tmp/tree")586629 rm -rf "$tmp"587- msg=$(printf 'release %s: %s\n\nTag: %s\nSource-Commit: %s\nBuilt-By: %s@%s\n' \588- "$tag" "$(git log -1 --format=%s "$src")" "$tag" "$src" "$(id -un)" "$(hostname -s 2>/dev/null || hostname)")630+ msg=$(printf 'release %s: %s\n\nTag: %s\nSource-Commit: %s\nBuilt-By: %s\n' \631+ "$tag" "$(git log -1 --format=%s "$src")" "$tag" "$src" "$(whoami_at)")589632 commit=$(printf '%s\n' "$msg" | git -c gpg.format=ssh -c "user.signingkey=$key" commit-tree -S "$tree" 2>/dev/null) ||590633 commit=$(printf '%s\n' "$msg" | git commit-tree "$tree")591634 r=$(remote)
M guide/index.html +1 -1
M lib/forge.ts +4 -0
M lib/insights.ts +1 -1
M lib/render.ts +2 -2
M site/_includes/insights-body.njk +1 -1
M site/repo/commit.njk +1 -1
M site/repo/releases.njk +1 -1
M site/repo/sites.njk +1 -1
A test/scenarios/publish-who.sh +49 -0
@@ -0,0 +1,49 @@
1+# What attestations, site deploys and releases publish about who made them:2+# the forge name the signing key has in allowed_signers (or forge.user) and3+# a made-up machine name kept in the global git config (or forge.machine);4+# never the hostname or the local account. Pages show neither, only who5+# signed.6+. "$(dirname "$0")/../lib.sh"7+user dami8+repo dami/greet9+clone dami dami/greet "$T/w/d"10+mkdir -p "$T/w/d/.forge"11+printf '#!/bin/sh\ntrue\n' >"$T/w/d/.forge/ci"12+chmod +x "$T/w/d/.forge/ci"13+at "$T/w/d" commit "first"14+export FORGE_USER=dami15+host=$(hostname -s 2>/dev/null || hostname)16+me=$(id -un)17+runner() { git -C "$T/w/d" notes --ref=ci show "$1" | sed -n 's/^runner //p' | tail -n 1; }18+19+ok at "$T/w/d" "$FORGE" attest20+r=$(runner HEAD)21+case $r in dami@[a-z]*-[a-z]*-[0-9a-f][0-9a-f]) ;; *) fail "runner is '$r'" ;; esac22+case $r in *"$host"* | "$me"@*) [ "$me" = dami ] || fail "runner '$r' names the host or the account" ;; esac23+m=$(git config --global --get forge.machine)24+eq "dami@$m" "$r"25+# The same name next time.26+at "$T/w/d" commit "second"27+ok at "$T/w/d" "$FORGE" attest28+eq "$(runner HEAD)" "$r"29+# forge.user and forge.machine win.30+at "$T/w/d" git config forge.user someone31+at "$T/w/d" git config forge.machine lab-box32+at "$T/w/d" commit "third"33+ok at "$T/w/d" "$FORGE" attest34+eq "$(runner HEAD)" someone@lab-box35+at "$T/w/d" git config --unset forge.user36+at "$T/w/d" git config --unset forge.machine37+38+# A release names its builder the same way.39+at "$T/w/d" git tag v140+mkdir "$T/f" && printf 'x\n' >"$T/f/tool"41+ok push_as dami "$T/w/d" origin main refs/notes/ci42+ok at "$T/w/d" "$FORGE" release publish v1 "$T/f/tool"43+eq "$(git --git-dir="$FORGE_ROOT/dami/greet.git" log -1 --format='%(trailers:key=Built-By,valueonly)' refs/releases/v1 | tr -d '\n')" "$r"44+45+# Pages show who signed, not the runner or the builder.46+build dami/greet47+lacks "$FORGE_WWW/dami/greet/commit/$(git -C "$T/w/d" rev-parse HEAD~1)/index.html" "$m"48+has "$FORGE_WWW/dami/greet/commit/$(git -C "$T/w/d" rev-parse HEAD~1)/index.html" 'signed by dami'49+lacks "$FORGE_WWW/dami/greet/releases/index.html" "$m"
M test/scenarios/release-page.sh +3 -2