clone

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

Offline, the edge never redirects to another site's address

cat committed 835172a
The edge's offline copy added a folder's slash with redir {path}/, so //evil.example/x answered 308 to //evil.example/x/, another site. A path that starts // or /\ (decoded) isn't redirected now; it is asked of the copy as it is and gets its 404. Caddy's path matchers see the path with its slashes merged, so the check reads {path} itself. Scenario: a folder still gets its slash, and //, /%5C and /%2F paths get no Location.
commit
835172a22647d43ff8752262a9148c26cf5b2e5e
parent
43f0cccc60bb3c7c724c54b683f5b1a2feb998e8
author
cat <admin@dev.site>
ci
✓ passed in 123s, signed by cat
kind
fix risk score 1.4 of 3, Jev's estimate
review
#30 Security: sign-in, invites, owner pages, lake, caps and the edge
ci attestation
forge-attestation 1
type ci
commit 835172a22647d43ff8752262a9148c26cf5b2e5e
tree 08efa11caebab6fd0c07daa852bd740b95d3fc1b
script .forge/ci 8d2000a416f8160bcf710d98b72af9eb76287f58
result pass
exit 0
started 2026-10-19T07:58:09Z
duration 123s
runner (not shown here; refs/notes/ci has it)
system Darwin 25.5.0 arm64
env host
log sha256:49f18c2403be91531d42a818b6a041c6bfafc781e5843c6c1ec84008ae2f7962 324 lines
|pushed wip/i18n
|c6c01be running .forge/ci: greet.sh: say what it is
|  | sh: /bin/sh
|  | ok   greet
|c6c01be pass in 0s, signed
|pushed main
|remote: review #4: /dami/greet/reviews/4/
|pushed wip/i18n
|572cef7 running .forge/ci: wave at the world
|  | sh: /bin/sh
|  | FAIL greet : got 'hello, world o/', want 'hello, world'
|572cef7 fail in 0s, signed
|forge: 1 commit(s) did not pass
|remote: review #6: /dami/greet/reviews/6/
|pushed ana/wave
|82add05 running .forge/ci: update the test for the wave
|  | sh: /bin/sh
|  | ok   greet
|82add05 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 62e41ed -> /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
|19bf5f0 running .forge/ci: web, server and Python versions of greet
|  | sh: /bin/sh
|  | ok   greet
|19bf5f0 pass in 0s, signed
|pushed main
|3593ce6 running .forge/ci: messages: notes for the later regions
|  | sh: /bin/sh
|  | ok   greet
|3593ce6 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 2de227d -> /sites/dami/forge/guide/
|(node:13562) 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.R1Snr8/wt/demo/root/.forge/www
|(node:13741) 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 -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.R1Snr8/wt/demo/root/.forge/www
|(node:13929) 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 -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.R1Snr8/wt/demo/root/.forge/www
|(node:14112) 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 -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.R1Snr8/wt/demo/root/.forge/www
|(node:14829) 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.R1Snr8/wt/demo/root/.forge/www
|235 of 235 pages render the same without 11ty
|guide: 8 files, links ok
|ok

Signature: good, cat

2 files changed, +34 -0
Mdeploy/edge/Caddyfile5+++++
Atest/scenarios/edge-offline-redirect.sh29+++++++++++++++++++++++++++++
M deploy/edge/Caddyfile +5 -0
A test/scenarios/edge-offline-redirect.sh +29 -0
@@ -0,0 +1,29 @@
1+# Offline, the edge adds the slash a folder's address lacks (/a/b -> /a/b/),2+# but never for a path that starts // or /\, where the slash would make3+# Location another site's address (//evil.test/x/): those are asked of the4+# copy as they are, and get its 404.5+. "$(dirname "$0")/../lib.sh"6+command -v caddy >/dev/null 2>&1 || { note "caddy is not installed: skipped running it"; exit 0; }7+mkdir -p "$T/bucket/dami/greet"8+echo '<p>greet</p>' >"$T/bucket/dami/greet/index.html"9+mport=$(free_port)10+printf '{\n\tadmin off\n\tauto_https off\n\tpersist_config off\n\tdefault_bind 127.0.0.1\n}\n\n:%s {\n\troot * %s\n\tfile_server\n}\n' "$mport" "$T/bucket" >"$T/Caddyfile.bucket"11+eport=$(free_port)12+dead=$(free_port)13+# On 127.0.0.1 only, so no other scenario's server can take the port there.14+sed "s/^:8080 {/:$eport {/; s/127\.0\.0\.1:8081/127.0.0.1:$dead/" "$T_HOME/deploy/edge/Caddyfile" |15+ awk '{ print } $0 == "\tadmin off" { print "\tdefault_bind 127.0.0.1" }' >"$T/Caddyfile.edge"16+for c in bucket edge; do17+ FORGE_DOMAIN=src.test FORGE_MIRROR_URL=http://127.0.0.1:$mport XDG_DATA_HOME=$T/xdg XDG_CONFIG_HOME=$T/xdg \18+ caddy run --config "$T/Caddyfile.$c" --adapter caddyfile >"$T/caddy.$c.log" 2>&1 &19+ T_PIDS="$T_PIDS $!"20+done21+wait_http "http://127.0.0.1:$mport/"22+wait_http "http://127.0.0.1:$eport/"23+e=http://127.0.0.1:$eport24+eq "$(http -s -o /dev/null -w '%{http_code} %{redirect_url}' -H 'Host: src.test' "$e/dami/greet")" "308 $e/dami/greet/"25+for p in //evil.test/x //evil.test /%5Cevil.test/x /%2Fevil.test/x; do26+ http -s -i -H 'Host: src.test' "$e$p" | tr -d '\r' >"$T/res"27+ lacks "$T/res" '^Location:'28+ has "$T/res" '^HTTP/1.1 404 '29+done