Audiobookshelf had no franksdank.cloud address: http://192.168.0.235:8000/audiobookshelf was the only door (the page recorded it as internal-only, "LAN-only until Frank says otherwise"). So browser use away from the LAN was impossible, and the OIDC round trip could only ever complete where an RFC1918 callback is reachable — the IdP would authenticate you and then hand the browser a private address.
Ordering matters here (external-exposure policy): the outpost proxy provider has to exist before the route flips. That half is already live and is not in this diff — proxy provider abs-proxy (external_host https://abs.franksdank.cloud/, internal_host http://caddy:8017) + application "Audiobookshelf Guard", claimed by the embedded outpost (10 → 11 providers).
Change
File
What
dankclaw/Caddyfile
guarded site block (reverse_proxy anubis:8923 with header_up X-Real-Ip {remote_host}), plus the post-auth @abs handler on http://:8017 → 192.168.0.235:8000. The container carries ROUTER_BASE_PATH=/audiobookshelf, so the app has no root: the bare / is redirected onto the base path inside the post-auth handler, never pre-auth
dankclaw/pihole/addn-hosts
LAN record for the vhost (the pihole seam fails the build without it)
scripts/authentik-audiobookshelf-sso.sh
ABS_EXTRA_BASES (default the vhost): the browser callback is registered per origin, because ABS derives redirect_uri from the origin of the request. The mobile-redirect URI stays on the LAN origin only — the phone is a non-browser client that cannot solve the guard challenge, so it is configured with the LAN address
shared/homepage/services.json
tile points at the vhost; probe stays the LAN host:port so the checker measures the app, not the chain
tests/e2e/specs/14-guard.spec.ts
abs joins GUARDED, leak marker "Sign in with Authentik" — a string the Audiobookshelf login renders and the IdP gate never prints
docs
Infrastructure/Caddy.md gains Guard a new vhost (the provider recipe that nobody recreates); Dankserver/Audiobookshelf.md swaps its Internal-only row and law for Two doors, one app; index.md regenerated
Evidence
scripts/check-okf.py → 0 violations; gen-vault-index.py is a no-op against the tree.
./tests/run.sh → ALL TESTS PASSED (824 tests), including the caddyfile and pihole seams that cross-check the new vhost against addn-hosts.
Provider script re-run after the change: uris ['http://192.168.0.235:8000/audiobookshelf/auth/openid/callback', 'https://abs.franksdank.cloud/audiobookshelf/auth/openid/callback', 'http://192.168.0.235:8000/audiobookshelf/auth/openid/mobile-redirect'], grants ['authorization_code', 'refresh_token'], scopes ['abs_groups', 'email', 'openid', 'profile'] unchanged.
Post-merge/deploy verification is deliberately not claimed here: DNS, TLS, the gate answering an anonymous client, the SPA booting through the vhost, and the SSO round trip over the public name are measured after the deploy, and 14-guard re-proves the chain on every subsequent deploy.
Rollback
Revert the five commits. The IdP rows are additive (abs-proxy and one callback URI); removing the Caddyfile block takes the name away and the LAN door never changed, so nothing on Dankserver moves.
## Symptom
Audiobookshelf had no `franksdank.cloud` address: `http://192.168.0.235:8000/audiobookshelf` was the only door (the page recorded it as internal-only, "LAN-only until Frank says otherwise"). So browser use away from the LAN was impossible, and the OIDC round trip could only ever complete where an RFC1918 callback is reachable — the IdP would authenticate you and then hand the browser a private address.
Ordering matters here (external-exposure policy): the outpost proxy provider has to exist **before** the route flips. That half is already live and is not in this diff — proxy provider `abs-proxy` (`external_host https://abs.franksdank.cloud/`, `internal_host http://caddy:8017`) + application "Audiobookshelf Guard", claimed by the embedded outpost (10 → 11 providers).
## Change
| File | What |
|---|---|
| `dankclaw/Caddyfile` | guarded site block (`reverse_proxy anubis:8923` with `header_up X-Real-Ip {remote_host}`), plus the post-auth `@abs` handler on `http://:8017` → `192.168.0.235:8000`. The container carries `ROUTER_BASE_PATH=/audiobookshelf`, so the app has no root: the bare `/` is redirected onto the base path **inside the post-auth handler**, never pre-auth |
| `dankclaw/pihole/addn-hosts` | LAN record for the vhost (the `pihole` seam fails the build without it) |
| `scripts/authentik-audiobookshelf-sso.sh` | `ABS_EXTRA_BASES` (default the vhost): the browser callback is registered **per origin**, because ABS derives `redirect_uri` from the origin of the request. The `mobile-redirect` URI stays on the LAN origin only — the phone is a non-browser client that cannot solve the guard challenge, so it is configured with the LAN address |
| `shared/homepage/services.json` | tile points at the vhost; `probe` stays the LAN `host:port` so the checker measures the app, not the chain |
| `tests/e2e/specs/14-guard.spec.ts` | `abs` joins `GUARDED`, leak marker `"Sign in with Authentik"` — a string the Audiobookshelf login renders and the IdP gate never prints |
| docs | `Infrastructure/Caddy.md` gains **Guard a new vhost** (the provider recipe that nobody recreates); `Dankserver/Audiobookshelf.md` swaps its Internal-only row and law for **Two doors, one app**; `index.md` regenerated |
## Evidence
- `scripts/check-okf.py` → 0 violations; `gen-vault-index.py` is a no-op against the tree.
- `./tests/run.sh` → **ALL TESTS PASSED** (824 tests), including the `caddyfile` and `pihole` seams that cross-check the new vhost against `addn-hosts`.
- Provider script re-run after the change: `uris ['http://192.168.0.235:8000/audiobookshelf/auth/openid/callback', 'https://abs.franksdank.cloud/audiobookshelf/auth/openid/callback', 'http://192.168.0.235:8000/audiobookshelf/auth/openid/mobile-redirect']`, `grants ['authorization_code', 'refresh_token']`, `scopes ['abs_groups', 'email', 'openid', 'profile']` unchanged.
- Post-merge/deploy verification is deliberately not claimed here: DNS, TLS, the gate answering an anonymous client, the SPA booting through the vhost, and the SSO round trip over the public name are measured after the deploy, and `14-guard` re-proves the chain on every subsequent deploy.
## Rollback
Revert the five commits. The IdP rows are additive (`abs-proxy` and one callback URI); removing the Caddyfile block takes the name away and the LAN door never changed, so nothing on Dankserver moves.
---
cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-13b1-7573-bcd5-ac99636f8bcb
Provider-first exposure: the IdP row (proxy provider abs-proxy + application
"Audiobookshelf Guard", claimed by the embedded outpost) already exists, so the
route can ship. The site block proxies everything to anubis:8923 like every other
guarded vhost, and the post-auth :8017 handler routes the host to
192.168.0.235:8000. The container carries ROUTER_BASE_PATH=/audiobookshelf, so
the app has no root: the bare / is redirected onto the base path inside the
post-auth handler, never pre-auth, so an anonymous client only ever meets the
gate.
The tile carried the bare LAN IP. The probe stays the LAN host:port so the
homepage checker keeps measuring the app rather than the auth chain in front of
it.
ABS builds redirect_uri from the origin of the request, so a second door is a
second URI: ABS_EXTRA_BASES (default the guarded vhost) now contributes a
callback each, while the mobile-redirect URI stays on the LAN origin - the phone
is a non-browser client and cannot solve the guard's challenge, so it is
configured with the LAN address. Measured on the live provider: three URIs,
grants and scopes unchanged.
The guard spec's GUARDED list is the lockstep with the Caddyfile; abs joins it
with the app's own SSO-button label as the leak marker - a string the Audiobookshelf
login renders and the IdP gate never prints.
Infrastructure/Caddy.md now owns the order of operations for exposing a vhost
(proxy provider + application + embedded-outpost claim, then the route, then the
name and the chain audit), because the IdP row is invisible state that nobody
recreates. Audiobookshelf's Internal-only row and law become Two doors, one app;
the homepage link and index.md follow.
dank
merged commit 17ea2f4110 into main2026-11-03 00:57:47 -08:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Symptom
Audiobookshelf had no
franksdank.cloudaddress:http://192.168.0.235:8000/audiobookshelfwas the only door (the page recorded it as internal-only, "LAN-only until Frank says otherwise"). So browser use away from the LAN was impossible, and the OIDC round trip could only ever complete where an RFC1918 callback is reachable — the IdP would authenticate you and then hand the browser a private address.Ordering matters here (external-exposure policy): the outpost proxy provider has to exist before the route flips. That half is already live and is not in this diff — proxy provider
abs-proxy(external_host https://abs.franksdank.cloud/,internal_host http://caddy:8017) + application "Audiobookshelf Guard", claimed by the embedded outpost (10 → 11 providers).Change
dankclaw/Caddyfilereverse_proxy anubis:8923withheader_up X-Real-Ip {remote_host}), plus the post-auth@abshandler onhttp://:8017→192.168.0.235:8000. The container carriesROUTER_BASE_PATH=/audiobookshelf, so the app has no root: the bare/is redirected onto the base path inside the post-auth handler, never pre-authdankclaw/pihole/addn-hostspiholeseam fails the build without it)scripts/authentik-audiobookshelf-sso.shABS_EXTRA_BASES(default the vhost): the browser callback is registered per origin, because ABS derivesredirect_urifrom the origin of the request. Themobile-redirectURI stays on the LAN origin only — the phone is a non-browser client that cannot solve the guard challenge, so it is configured with the LAN addressshared/homepage/services.jsonprobestays the LANhost:portso the checker measures the app, not the chaintests/e2e/specs/14-guard.spec.tsabsjoinsGUARDED, leak marker"Sign in with Authentik"— a string the Audiobookshelf login renders and the IdP gate never printsInfrastructure/Caddy.mdgains Guard a new vhost (the provider recipe that nobody recreates);Dankserver/Audiobookshelf.mdswaps its Internal-only row and law for Two doors, one app;index.mdregeneratedEvidence
scripts/check-okf.py→ 0 violations;gen-vault-index.pyis a no-op against the tree../tests/run.sh→ ALL TESTS PASSED (824 tests), including thecaddyfileandpiholeseams that cross-check the new vhost againstaddn-hosts.uris ['http://192.168.0.235:8000/audiobookshelf/auth/openid/callback', 'https://abs.franksdank.cloud/audiobookshelf/auth/openid/callback', 'http://192.168.0.235:8000/audiobookshelf/auth/openid/mobile-redirect'],grants ['authorization_code', 'refresh_token'],scopes ['abs_groups', 'email', 'openid', 'profile']unchanged.14-guardre-proves the chain on every subsequent deploy.Rollback
Revert the five commits. The IdP rows are additive (
abs-proxyand one callback URI); removing the Caddyfile block takes the name away and the LAN door never changed, so nothing on Dankserver moves.cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-13b1-7573-bcd5-ac99636f8bcb