v0.286.0: CHANGELOG + README (R-753 visitors apart, R-772, R-773)
gates / gates (push) Successful in 27s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-01 21:04:26 +02:00
parent 1e216d3468
commit d4167420d1
2 changed files with 55 additions and 6 deletions
+40
View File
@@ -1,3 +1,43 @@
## v0.286.0 — the box tells visitors apart (R-753); a health check that could not run is not "healthy" (R-772); a restore keeps the sign-up lock (R-773) (2026-10-01)
**MinAgent: 0.131.0** (unchanged). Needs hub v0.123.0 (unchanged). New strings: `login.msg.*` (3, both languages).
**Catalog companion:** 19 apps whose software reads the LEFTMOST `X-Forwarded-For` carry a router middleware that removes
the chain (app-catalog `04e9516`..`50e4fb4`), pushed BEFORE this release — without it, trusting the tunnel would let a
stranger write the address those apps believe. **Every installed box's traefik and cloudflared are recreated once** when
it takes this release (routing pauses a few seconds; the tunnel reconnects).
- **R-753 — `09` §3 decision 63 (operator ruling: extend the box's own gate; first the box tells visitors apart).**
Measured first (`felhom.eu/documentation/audits/visitors-2026-10-01/A/`, demo-hp's real tunnel): every tunnel visitor
reached traefik as cloudflared's one docker-assigned address; Cloudflare APPENDS the visitor to a client-written
`X-Forwarded-For`, passes a client's `X-Forwarded-Host`/`-Port`, strips a client's `X-Real-IP`, and refuses a
client-sent `CF-Connecting-IP` at the edge (403).
- `felhom-tunnel` network `172.16.253.0/29` (allocation confined to `.4/30` — measured: traefik joining first was
given `.2`): cloudflared alone at `.2`, traefik at `.3`. traefik's `websecure` trusts forwarded headers from
`172.16.253.2/32` only; the entrypoint middleware `felhom-forwarded@file` removes `X-Forwarded-Host/-Uri/-Method/
-Prefix/-Tls-Client-Cert(-Info)`, `Forwarded`, `True-Client-Ip`, `X-Client-Ip`, `X-Cluster-Client-Ip`, `Client-Ip`,
`X-Original-Forwarded-For` and fixes `X-Forwarded-Port: 443` (`internal/infra`).
- `EnsureBaseStack` RECONCILES a running traefik/cloudflared whose rendered files changed (recreate; refuses a rewrite
that would drop the running certificate resolver), writes the middleware file before `traefik.yml`, and moves
cloudflared only once traefik is on the tunnel network. No network → the old shape, nothing trusted.
- `clientIP` (`internal/web/clientaddr.go`): believed only when the TCP peer is traefik (resolved by name); the
rightmost `X-Forwarded-For` entry — the hop traefik saw; the tunnel hop → `CF-Connecting-IP`. `rateKey`: IPv6 per
/64. The dashboard login, claim code, share password and escrow re-auth counters key on it: **a stranger's five
wrong dashboard passwords lock only the stranger** (before: every tunnel visitor shared one key and the household
was locked out of its own dashboard for a minute). The setup gate logs the visitor.
- The dashboard login's messages are keys, informal voice, both languages (`login.msg.*`; the placeholder too).
- Tests: `TestClientIP_*`, `TestLogin_StrangerThroughTheTunnelLocksOnlyHimself`, `TestLoginMessagesFollowTheReader`,
`TestRateKey_IPv6Per64`, `TestPeerResolver_*`, `TestTunnelConstantsAgree`, `TestRenderTraefik_TrustsOnlyTheTunnel`,
`TestRenderCloudflared_AloneOnTheTunnel`, `TestRenderForwardedHeaders_*`, `TestEnsureTunnelNetwork_*`,
`TestEnsureTraefik_*`, `TestEnsureCloudflared_*`, `TestEnsureBaseStack_TunnelOrder`;
`TestLoginRateLimit_RotatingXFF_NotLimited` reversed on purpose (a direct peer's rotating XFF no longer evades).
Red-proofs RP-A1 (leftmost hop), RP-A2 (the tunnel hop as the key), RP-A3 (no reconcile) — each fails.
- **R-772:** a health probe that finds no container to probe records `healthy: false, not_checked: true` (was
`healthy: true`, then 5 minutes of silence), is looked at again on the 10-second cycle, and the app page says the
check did not run. The state stays the containers' (`probeSaysUnhealthy`), so R-630 holds. Red-proof RP-D1.
- **R-773:** a REMOVED app restored from its backup gets its sign-up lock back — the record (`opened_by: restore`) and
the block written before anything starts; the loop sets the app's own switch. An installed app the household never
closed keeps what it had (decision 49). Red-proof RP-D2.
## v0.285.0 — a box keeps two controller versions (decision 56); a crash in the update clean-up fixed (2026-10-01)
**MinAgent: 0.131.0** (unchanged). Needs hub v0.123.0 (unchanged). No new strings.