Files
archy/.planning/RELEASE-1.7.121-TASKS.md
T

191 lines
10 KiB
Markdown
Raw Normal View History

2026-08-12 10:55:50 +00:00
# Release 1.7.121 — task list
Opened 2026-08-03, immediately after v1.7.120-alpha shipped. Everything the operator has
asked for since, plus the items v1.7.120 deliberately left open. Ordered by severity.
Status key: **DONE** (committed) · **READY** (written, not yet committed/tested) ·
**OPEN** (not started) · **BLOCKED** (needs an operator decision)
---
## P0 — Security
### 1. App ports are reachable with no login, on every transport — **OPEN**
> "if I'm logged out I can reach every app port on tailscale and LAN, this can not be
> allowed… it must present the login to access the app with an app icon of what you're
> accessing to confirm, and 2FA if present" — operator, 2026-08-03
- Applies to **Tailscale, LAN, Tor, FIPS** alike, and to "ssh access to that port or whatever".
- Required behaviour: an unauthenticated request to any app port serves a **login page
naming and showing the icon of the app being accessed**, then honours **2FA when set**.
- **Research first:** how umbrelOS and StartOS gate app access (operator asked explicitly).
Both are open source — `getumbrel/umbrel` and `Start9Labs/start-os`. Do not guess at
their model; read it.
- This is the same class as the v1.7.120 `/lnd-connect-info` + `/bitcoin-rpc/` leaks, but
**fleet-wide across every app port** rather than two endpoints. Those two were closed by
moving authorisation to the resource; this needs a general gate.
- Scope note: `fips/app_ports.rs` holds the mesh allowlist; `is_peer_allowed_path` in
`server.rs` holds the peer HTTP allowlist. Neither currently authenticates app ports.
### 2. Filebrowser ships an insecure default login — **OPEN**
- Change the default credential **without breaking the dashboard's Cloud view**, which
authenticates to filebrowser on the user's behalf.
- Related prior art: FED-07 rotated the shipped Fedimint gateway credential and had to
recreate the running container for it to take effect (`06e0e695`) — the same trap
applies here.
### 3. Federation trust escalation — **DONE** (`c0cfc72a`, pushed)
Two independent fail-open paths granted `Trusted` without any operator decision:
- `federation.peer-joined` is **unauthenticated** (middleware no-session list) and
peer-reachable on `/rpc/v1`. Its ed25519 check verifies the caller against **the pubkey
the caller supplied**, so it proves key possession, never authorisation. A join with no
`invite_token` fell through to `TrustLevel::Trusted.min(claimed_trust)`, and
`claimed_trust` defaults to `Trusted` — so anyone able to reach the node could
self-grant Trusted. **Now capped at `Observer`.**
- `merge_transitive_peers` added every peer advertised by a Trusted source as `Trusted`,
making trust viral across the whole federation graph. **Now `Observer`** — which is what
`NodeStateSnapshot.federated_peers`' own doc comment always said it should be
("adds them as Observers on her side… doesn't auto-promote to Trusted"). The code
contradicted its own spec.
- Added `FederatedNode.trust_source` (`invite` | `uninvited-join` | `transitive-merge` |
`manual`, `None` = pre-existing/unknown) so existing grants are **auditable**. Per
operator decision: existing peers are **left alone, not auto-demoted**.
- **Still to do:** surface `trust_source` in `federation.list-nodes` + the UI so the
operator can actually review the `None`/`uninvited-join` population.
### 3b. Granting Trusted must require the node password — **OPEN**
> "to make someone trusted must require the node password to generate the code or change
> in the modal dropdown when you click a node" — operator, 2026-08-03
Re-authentication on privilege escalation. Two entry points, both must be covered:
- **Minting a Trusted invite** (`federation.invite` with `trust_level: "trusted"`) —
"Link Your Nodes" mints Trusted today with no re-auth.
- **Changing a node's level in the UI dropdown** (`federation.set-trust-level` /
`handlers.rs:342`) — promoting Observer → Trusted.
Demotion must NOT require the password: making something less privileged should never be
harder than leaving it. Grant `TrustSource::Manual` on the operator path so the audit
trail distinguishes it from the capped automatic ones.
---
## P1 — Correctness the operator hit directly
### 4. LND UI never updates over OTA — **DONE** (`5088aef5`, pushed)
- `LND_UI_IMAGE` was `lnd-ui:latest` while `BITCOIN_UI_IMAGE` was pinned to
`1.7.119-alpha`. Podman will not re-pull a tag it already holds locally, so nodes kept a
stale lnd-ui forever. **Now pinned to `1.7.119-alpha`.**
- `scripts/first-boot-containers.sh` declared lnd-ui as bridge `-p 18083:80`. That is the
**third copy** of the declaration the UI agent already corrected in
`scripts/container-specs.sh` and `apps/lnd-ui/manifest.yml` — so **fresh installs** still
produced the reproduced `HTTP 000`. **Now `--network host`, ports empty.**
- Root cause worth fixing separately: the same container spec is declared in three places.
### 5. Federated/peered nodes must message without a LoRa hop first — **OPEN**
> "make it so federated/peered nodes can message without needing to connect on Lora first
> once connected"
- Investigate the split contact model (radio contact vs federation peer) — there is prior
art in memory: `project_archy_lora_e2e_rootcause` ("split contact model; don't touch
federation") and `mesh::seed_federation_peers_into_mesh` /
`upsert_federation_peer`, which already mirror federation peers into the mesh table.
- Likely the gap is addressing/route selection rather than transport availability.
### 6. In-app app updates, independent of OTA — **OPEN**
> "we need app update to see updates in the registry, whether UI or not… show the update
> mechanism in the app… a modal and update now / cancel… same in the detail page… the
> update button should show 'see update' and a different graphic for just ui, app, or both
> together. All pushed through the signed-catalog flow." … "This has to show independent of
> OTA updates as a separate pipeline, I think we've done a lot of work on it."
- **Operator says much of this already exists — research the codebase before building.**
Known groundwork: the signed catalog (`releases/app-catalog.json`, `sign-catalog.sh`),
catalog→manifest runtime reload, `package.update` RPC, `check-app-catalog-drift.py`,
and `scripts/image-versions.sh` pinning.
- Needs: registry-version awareness per app, a diff of what changed (UI vs app vs both),
the modal + detail-page affordance, and distinct iconography for the three cases.
---
## P2 — Carried over from v1.7.120
### 7. `create-release.sh` commits the manifest BEFORE signing — **OPEN**
Release commit always carries an **unsigned** manifest; nodes fetch it from branch `main`
and refuse to auto-apply. Caught manually this cycle. Fix the ordering so it cannot ship.
### 8. `gitea-vps2` remote is dead, and is the same server as `gitea-ai` — **OPEN**
Stored token fails auth. `source.archipelago-foundation.org` == `146.59.87.168`, so
`git push gitea-ai` already publishes to the "primary" OTA host. Ties into the existing
"migrate VPS2 IP to domain" todo.
### 9. Fleet SSH host-key rotation — **BLOCKED** (operator decision)
`archipelago-1`, `archy-x250-beta`, `archipelago` share all three SSH host keys; two also
share a TLS private key. Detection shipped; rotation deliberately not performed.
### 10. 5× lifecycle gate — **OPEN**
Not run for v1.7.120 (disclosed in its changelog). Needs repeated reboots of a live node.
### 11. `prod_orchestrator.rs:3181` unreachable code — **OPEN**
`bitcoin_host()` returns unconditionally at :3171, so the podman container-name lookup
below is dead on every path. Pre-existing; spotted in the v1.7.120 build warnings.
---
## Notes for whoever picks this up
- A separate agent is doing **AIUI planning with GSD** — do not touch AIUI.
- AIUI must always be built `VITE_BASE_PATH=/aiui/` (see the memory note); a hand-built
bundle renders a black page.
- Verify security claims on the node, not from the source. v1.7.120's headline bug was a
fix that shipped in the binary and silently never reached the running container.
---
## RESUME HERE — next session
**Landed this session (both pushed):**
- `c0cfc72a` federation trust escalation (items 3) — 42/42 federation tests green
- `5088aef5` lnd-ui OTA pin + host networking (item 4), and this task file
**v1.7.120-alpha is SHIPPED** — signed, published, assets verified live. Do not re-cut it.
### Start with item 3b (password gate) — groundwork already located
Everything needed to implement it, so the next session does not re-search:
- **The helper to use:** `self.auth_manager.verify_password(password).await?` — returns
`bool`. Existing callers to copy the shape from: `api/rpc/node.rs:176`,
`api/rpc/totp.rs:18` / `:66` / `:121`.
- **Entry point A — minting a Trusted invite:** `handle_federation_invite`,
`api/rpc/federation/handlers.rs:58`. It reads `trust_level` from params and
**defaults to `TrustLevel::Trusted` at :72**. Gate only when the resolved level is
`Trusted`; leave Observer invites unchanged.
- **Entry point B — the UI dropdown:** `handle_federation_set_trust`,
`api/rpc/federation/handlers.rs:326`, dispatched as `"federation.set-trust"`
(`api/rpc/dispatcher.rs:353`). Its parse is at `:342`.
- **Rule:** gate PROMOTION to Trusted only. Demotion must stay ungated — making something
less privileged must never be harder than leaving it.
- Set `TrustSource::Manual` on the operator path so the audit trail distinguishes a
deliberate grant from the capped automatic ones.
- Frontend will need the password prompt in both places (invite modal, node dropdown).
### Then item 1 (app ports unauthenticated) — the big one
Start with the research the operator explicitly asked for: how **umbrelOS**
(`getumbrel/umbrel`) and **StartOS** (`Start9Labs/start-os`) gate app access. Read their
model rather than inventing one. Only then design the gate.
Give this a fresh session with real context — it is the largest item here and is the same
bug class as the `/lnd-connect-info` + `/bitcoin-rpc/` leaks fixed in v1.7.120, but across
every app port and every transport.
### Working notes
- A separate agent is doing **AIUI planning with GSD** — do not touch AIUI.
- The shared tree has concurrent agents: stage by explicit path, never `git add -A`.
- Verify security claims **on the node**, not from source. v1.7.120's headline bug was a
fix that shipped in the binary and silently never reached the running container.
- A piped command's exit code is the pipe's, not the script's — redirect to a log file and
read the content.