From f1c350040cbc7e6a0b05979ff3759838ba4a0c1d Mon Sep 17 00:00:00 2001 From: archipelago Date: Thu, 6 Aug 2026 19:48:43 -0400 Subject: [PATCH] docs: record the Authorization-strip root cause and the relay ownership fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Item 1 was not an interception problem and needed no session-aware rule — the gate was deleting the app's own Authorization header. Item 4's /relay 502 was a root-owned volume, not networking. Both deployed and verified on archi-dev-box; the extension login itself still needs a human in a browser. --- .planning/RESUME-2026-08-06-media-loop.md | 46 +++++++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/.planning/RESUME-2026-08-06-media-loop.md b/.planning/RESUME-2026-08-06-media-loop.md index 010719dd..58a5269e 100644 --- a/.planning/RESUME-2026-08-06-media-loop.md +++ b/.planning/RESUME-2026-08-06-media-loop.md @@ -29,12 +29,34 @@ as `archipelago`. ## Ordered work list -### 1. Gate vs app-owned auth (fleet-wide) — HALF DONE -Done + deployed + verified: credential-less allowlist, `manifest.json` 401→200, root and -`/api/auth/*` still 401 (`is_credentialless_public_path`, exact match only, 2 tests). -**Remaining:** the app's own `/api/auth/*` is still intercepted, so IndeeHub cannot establish -its Nostr session. Needs a session-aware rule: once a VALID gate session exists, proxy the -app's auth endpoints through instead of challenging. Security-critical — justify in code. +### 1. Gate vs app-owned auth (fleet-wide) — ✅ DONE, deployed + verified (`d9592c72`) +Round 1: credential-less allowlist, `manifest.json` 401→200 (`is_credentialless_public_path`). + +Round 2 (2026-08-06 ~19:50) — the actual cause of "Nostr signer doesn't work anywhere". +It was **not** a challenge/interception problem, and no session-aware rule was needed: the +gate was **deleting the app's own `Authorization` header** on every proxied request +(`parts.headers.remove(header::AUTHORIZATION)`, unconditional, `appgate/mod.rs`). IndeeHub +sends `Authorization: Nostr ` to its own `/api/auth/nostr/session`; the header +arrived stripped and its backend answered `401 "Authorization header is missing"`. No signer +could ever satisfy that — which is exactly why a NIP-07 **extension in a tab**, the **iframe** +bridge (`nostr-provider.js`) and **AIUI** all failed at once while the signing was fine. + +Fix: `authorize()` now reports WHICH credential allowed the request. The header is dropped +only when it WAS the gate's own `Bearer `; every other scheme (Nostr, Basic, +an app-issued bearer) is forwarded. Mirrors the surgical cookie strip above it. The +credential-less allowlist still drops it (nothing there needs auth). + +Live proof on archi-dev-box, authenticated with a real gate session: +- before → `401 {"message":"Authorization header is missing"}` +- after → `400 {"message":"Event is not a valid NIP-98 HTTP auth event"}` — identical to + the same POST on loopback, i.e. the signed event now reaches the app +- unauthenticated → still `401` (gate still challenges; boundary intact) + +**Blast radius was much wider than IndeeHub**: 27 apps are gated, and this broke any of them +that authenticate with the `Authorization` header (Vaultwarden, Jellyfin, Nextcloud/WebDAV, +Gitea tokens, Grafana). Same mechanism — not individually retested. + +Still needs a human: an actual extension login in a browser. ### 2. AI Data Access grants → node-side (unblocks everything AI) `aiPermissions.ts` uses `localStorage` (`archipelago-ai-permissions`), which is PER-ORIGIN. @@ -51,8 +73,16 @@ Fix the pairing + exclude headers; the real answer is structured model output, n - Content source: films are `projects`, `GET /api/projects` via its nginx on :7778. Public count on this node is **0**. Port 4000 is not host-mapped. `/graphql` is the SPA. - Private films need a **Nostr session** (`/api/projects/private` says Cognito is disabled). -- `/relay` is 502 **direct on loopback** — IndeeHub's own nginx can't reach its relay - container. Independent of the gate. Fix separately. +- ~~`/relay` is 502 **direct on loopback**~~ — ✅ FIXED 2026-08-06. Not a networking + problem: DNS resolved (`relay` → 10.89.1.3) and nothing was listening. The relay's volume + `/usr/src/app/db` was owned by **root** while nostr-rs-relay runs as `appuser` (uid 1000), + so it crash-looped on `unable to open database file: .../nostr.db`. Repair (volume was + empty, no data at risk): + `podman unshare chown 1000:1000 ~/.local/share/containers/storage/volumes/indeedhub-relay-data/_data` + then `podman restart indeedhub-relay`. DB v18 built; `/relay` now 200 with its NIP-11 doc. + **Same ownership-bug family still open elsewhere** — the reconciler logs + `reconcile failed app_id=btcpay-server error=chown /var/lib/archipelago/postgres-btcpay failed`. + Worth a sweep: rootless volume dirs created root-owned for non-root container users. ### 5. Node-side Nostr signer — the highest-leverage piece Collapses IndeeHub's private auth, the app-auth half of item 1, and Phase C's zaps into one