clone

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

The forge's own pages send HSTS, nosniff, a referrer policy and a CSP

cat committed fe26ac5
Caddy adds Strict-Transport-Security to every response on the forge's hostname, and to its pages (not /sites/ or /previews/, which are anyone's) X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin and a CSP of frame-ancestors 'self', base-uri 'none' and object-src 'none'. Each is set only if the response has none, so the sign-in and private pages keep their stricter ones. Nothing scripts, styles, fonts or wasm load is restricted.
commit
fe26ac5eb0af68c410079aa849dc38bb8da598c4
parent
ebe809d2761d6e5fae0ff25ad968cf28f64399a2
author
cat <admin@dev.site>
ci
✓ passed in 116s, signed by cat
kind
feat risk score 1.5 of 3, Jev's estimate
review
#29 Security: go get's reflected markup, and headers on the forge's pages
ci attestation
forge-attestation 1
type ci
commit fe26ac5eb0af68c410079aa849dc38bb8da598c4
tree 488519c8dc7176e0dc2e5d0dee9d07b1c4b1ee61
script .forge/ci 8d2000a416f8160bcf710d98b72af9eb76287f58
result pass
exit 0
started 2026-10-19T07:39:55Z
duration 116s
runner (not shown here; refs/notes/ci has it)
system Darwin 25.5.0 arm64
env host
log sha256:37db71267a7f5a396a5929d1ad5f983ffe46ddee536bcbc35c57d393f2f4ba91 312 lines
|pushed wip/i18n
|44fe96f running .forge/ci: greet.sh: say what it is
|  | sh: /bin/sh
|  | ok   greet
|44fe96f pass in 0s, signed
|pushed main
|remote: review #4: /dami/greet/reviews/4/
|pushed wip/i18n
|4b9d238 running .forge/ci: wave at the world
|  | sh: /bin/sh
|  | FAIL greet : got 'hello, world o/', want 'hello, world'
|4b9d238 fail in 0s, signed
|forge: 1 commit(s) did not pass
|remote: review #6: /dami/greet/reviews/6/
|pushed ana/wave
|eb217fb running .forge/ci: update the test for the wave
|  | sh: /bin/sh
|  | ok   greet
|eb217fb 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 d466c56 -> /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
|2e6afba running .forge/ci: web, server and Python versions of greet
|  | sh: /bin/sh
|  | ok   greet
|2e6afba pass in 0s, signed
|pushed main
|0ec6b30 running .forge/ci: messages: notes for the later regions
|  | sh: /bin/sh
|  | ok   greet
|0ec6b30 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: mkdir -p guide/fonts && cp site/assets/fonts/*.woff2 guide/fonts/
|site guide: deployed 6f84fc0 -> /sites/dami/forge/guide/
|(node:86508) 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 1s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.tHfp5E/wt/demo/root/.forge/www
|(node:86668) 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 0s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.tHfp5E/wt/demo/root/.forge/www
|(node:86841) 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 1s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.tHfp5E/wt/demo/root/.forge/www
|(node:87007) 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 2s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.tHfp5E/wt/demo/root/.forge/www
|(node:87712) 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 -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.tHfp5E/wt/demo/root/.forge/www
|235 of 235 pages render the same without 11ty
|guide: 8 files, links ok
|ok

Signature: good, cat

3 files changed, +44 -1
MREADME.md2+-
Mbin/forge13+++++++++++++
Atest/scenarios/web-headers.sh30++++++++++++++++++++++++++++++
M README.md +1 -1
@@ -97,7 +97,7 @@ review page, signed in ─── HTTPS ────────────> the
9797 | Process | Why |9898 | --- | --- |9999 | sshd | git over ssh. Every key is forced through `forge shell <name>`. |100-| Caddy | Serves `.forge/www` as plain files, read-only HTTPS clones (`git http-backend` through fcgiwrap; nothing else inside a bare repo is served; a private repo takes a token) and `go get` meta tags; proxies `/events/`, every path `www` has no file for (to the private server) and, with a pages domain, sites. |100+| Caddy | Serves `.forge/www` as plain files, read-only HTTPS clones (`git http-backend` through fcgiwrap; nothing else inside a bare repo is served; a private repo takes a token) and `go get` meta tags, with security headers (HSTS, nosniff, a referrer policy and a CSP against framing) on the forge's own pages; proxies `/events/`, every path `www` has no file for (to the private server) and, with a pages domain, sites. |101101 | `forge build` | Runs 11ty after each push, in the background and one repo at a time, then swaps the new pages into `www/`. Unchanged pages are hard links to the last build's. First it adds the push's commits to the repo's commit-graph (see [Cleaning up](#cleaning-up)). |102102 | `lib/events-server.ts` | Tails `.forge/events.jsonl`, which hooks and builds append to, and streams it as server-sent events at `/events/<owner>/<repo>/`. Open pages say who pushed and swap in fresh content when the build finishes. Without it pages still work; their status line says offline. A private repo streams only to its readers, signed in. |103103 | `lib/private-server.ts` | Serves private repos' pages to their readers, signed in with a passkey or holding a token, the sign-in pages (`/login/`, `/private/`), and the 404 of every page that isn't there; takes comments and approvals posted from review pages (see [Comments and approvals](#comments-and-approvals)); answers Caddy whether a token may clone a private repo over HTTPS (see [Private pages](#private-pages)). |
M bin/forge +13 -0
A test/scenarios/web-headers.sh +30 -0
@@ -0,0 +1,30 @@
1+# The forge's own pages, through Caddy, carry its security headers: HSTS,2+# no sniffing, the origin alone as referrer, and a CSP that keeps them out3+# of other sites' frames. A site (anyone's pages) gets only HSTS, which is4+# the hostname's, and the sign-in page keeps its own stricter policy.5+. "$(dirname "$0")/../lib.sh"6+user dami7+repo dami/greet8+clone dami dami/greet "$T/w/d"9+at "$T/w/d" commit "first"10+push_as dami "$T/w/d" origin main11+mkdir -p "$T/site" && printf '<h1>docs</h1>\n' >"$T/site/index.html"12+FORGE_USER=dami ok at "$T/w/d" "$FORGE" site push "$T/site" docs13+build14+caddy_server15+for p in / /dami/greet/ /dami/nope/; do16+ http -s -i "$C$p" | tr -d '\r' >"$T/res"17+ has "$T/res" '^Strict-Transport-Security: max-age=31536000$'18+ has "$T/res" '^X-Content-Type-Options: nosniff$'19+ has "$T/res" '^Referrer-Policy: strict-origin-when-cross-origin$'20+ has "$T/res" "^Content-Security-Policy: frame-ancestors 'self'; base-uri 'none'; object-src 'none'$"21+done22+http -s -i "$C/sites/dami/greet/docs/" | tr -d '\r' >"$T/site.res"23+has "$T/site.res" '^HTTP/1.1 200 '24+has "$T/site.res" '<h1>docs</h1>'25+has "$T/site.res" '^Strict-Transport-Security: '26+lacks "$T/site.res" '^(Content-Security-Policy|X-Content-Type-Options|Referrer-Policy):'27+http -s -i "$C/login/" | tr -d '\r' >"$T/login"28+eq "$(grep -c '^Content-Security-Policy:' "$T/login")" 129+has "$T/login" "^Content-Security-Policy: default-src 'self'.*frame-ancestors 'none'"30+has "$T/login" '^Referrer-Policy: same-origin$'