diff --git a/documentation/architecture/09-update-architecture.md b/documentation/architecture/09-update-architecture.md index 9620e53c..551cc47f 100644 --- a/documentation/architecture/09-update-architecture.md +++ b/documentation/architecture/09-update-architecture.md @@ -497,6 +497,14 @@ R-636's louder repeated alarm. controller v0.279.0). Where it cannot, the app stays in the catalog; the install dialog and the app page say what the default login is and to change it at once. Operator: *"I don't think we should exclude apps if we can't change the first PW."* The per-app audit and status: `app-catalog-felhom.eu/FIRST-ADMIN.md`. +46. **A setup gate for open-first-run apps is SPIKED before it is built** — *operator ruling 2026-09-29 ("I was leaning + towards B, but let's test A").* The 34 class-4 apps let the first visitor create the admin. Option A: while such an + app is not yet set up, the box lets only a person logged in to the household's dashboard reach it; the gate opens + when the app's own status says an admin exists, or when the household presses "Done, I set it up". Option B: one + fix per app (route (b), R-707). A is built only if the spike passes its exit test in writing; otherwise B continues + and the spike's result is the recorded reason. Outcome: *(filled in by the 2026-09-29 session)*. + Same day, operator: CC changes the admin passwords of demo-hp's installed bookstack and calibre-web and stores them + in the operator's credentials file (not in any repo). --- diff --git a/documentation/audits/login-gate-2026-09-29/A/A1-demo-hp-password-change.txt b/documentation/audits/login-gate-2026-09-29/A/A1-demo-hp-password-change.txt new file mode 100644 index 00000000..a4a19b8d --- /dev/null +++ b/documentation/audits/login-gate-2026-09-29/A/A1-demo-hp-password-change.txt @@ -0,0 +1,12 @@ +== 2026-09-29T06:06:11Z BEFORE (the app's own login form, via traefik) +bookstack admin@admin.com / -> 302->/ | wrong control -> 302->/login +calibre-web admin / -> 302->/ | wrong control -> 200-> +== bookstack: artisan bookstack:create-admin --initial (the app's own command; keeps email + name) +The default admin user has been updated with the provided details! +rc=0 +== calibre-web: cps.py -s (the app's own command), as the app user abc +Password for user 'admin' changed +rc=0 +== 2026-09-29T06:06:16Z AFTER +bookstack default -> 302->/login | new -> 302->/ | wrong control -> 302->/login +calibre-web default -> 200-> | new -> 302->/ | wrong control -> 200-> diff --git a/documentation/audits/login-gate-2026-09-29/A/A2-page-warning-after.txt b/documentation/audits/login-gate-2026-09-29/A/A2-page-warning-after.txt new file mode 100644 index 00000000..374418d4 --- /dev/null +++ b/documentation/audits/login-gate-2026-09-29/A/A2-page-warning-after.txt @@ -0,0 +1,13 @@ +# demo-hp 9201, 2026-09-29 ~06:10Z, controller 0.279.0: the app page (GET /apps/, logged in), searched with the ASCII +# fragment "ismert, k". Positive control: calibre-web/romm/mealie carry it. Negative control: bookstack does not. +bookstack bytes 43386 warning: None +calibre-web bytes 42081 warning: "ismert, közös jelszóval indul: admin / admin123. Telepítés után azonnal változtasd meg." +romm bytes 41671 warning: "ismert, közös jelszóval indul: admin / admin. ..." (stale note — Part D3) +mealie bytes 39898 warning: "ismert, közös jelszóval indul: changeme@example.com / MyPassword. ..." + +FINDING (R-710): bookstack's page stopped warning BEFORE today's change. The catalog gained bookstack's after_install on +2026-09-28; the installed app has no after_install record, and known_login.go reads an absent record as "not run yet" +(line: `return cfg != nil && cfg.AfterInstall != nil && !cfg.AfterInstall.OK`). An app installed before its template +gained an after_install is never run and never warned — its default login was live and unannounced from the catalog +sync on 2026-09-28 until the change at 06:06Z today. +calibre-web still warns after the change: known_login.go only knows after_install records (brief Part A4). diff --git a/documentation/audits/login-gate-2026-09-29/A/a_change.sh b/documentation/audits/login-gate-2026-09-29/A/a_change.sh new file mode 100644 index 00000000..4470e61c --- /dev/null +++ b/documentation/audits/login-gate-2026-09-29/A/a_change.sh @@ -0,0 +1,24 @@ +# Part A (2026-09-29, operator ruling): change the admin passwords of demo-hp's INSTALLED bookstack and calibre-web +# through each app's own route. Passwords arrive on STDIN (line 1 bookstack, line 2 calibre-web); never printed. +set -u +DOM=enkisfelhom.hu; J=/tmp/partA-jar.$$ +read -r PB; read -r PC +bs() { rm -f $J; T=$(curl -sk -c $J -b $J -H "Host: wiki.$DOM" https://127.0.0.1/login | grep -o 'name="_token" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"$//') + curl -sk -o /dev/null -w '%{http_code}->%{redirect_url}' -c $J -b $J -H "Host: wiki.$DOM" -X POST --data-urlencode "_token=$T" --data-urlencode "email=admin@admin.com" --data-urlencode "password=$1" https://127.0.0.1/login | sed "s|https\?://[^/]*||"; } +cw() { rm -f $J; T=$(curl -sk -c $J -b $J -H "Host: books.$DOM" https://127.0.0.1/login | grep -o 'name="csrf_token" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"$//') + curl -sk -o /dev/null -w '%{http_code}->%{redirect_url}' -c $J -b $J -H "Host: books.$DOM" -X POST --data-urlencode "csrf_token=$T" --data-urlencode "username=admin" --data-urlencode "password=$1" --data-urlencode "remember_me=on" https://127.0.0.1/login | sed "s|https\?://[^/]*||"; } +red() { sed -e "s/$(printf '%s' "$PB" | sed 's/[.[\*^$/]/\\&/g')//g" -e "s/$(printf '%s' "$PC" | sed 's/[.[\*^$/]/\\&/g')//g"; } +echo "== $(date -u +%FT%TZ) BEFORE (the app's own login form, via traefik)" +echo "bookstack admin@admin.com / -> $(bs password) | wrong control -> $(bs wrongxyz123)" +echo "calibre-web admin / -> $(cw admin123) | wrong control -> $(cw wrongxyz123)" +echo "== bookstack: artisan bookstack:create-admin --initial (the app's own command; keeps email + name)" +export NEWPW="$PB" +docker exec -e NEWPW bookstack sh -c 'php /app/www/artisan bookstack:create-admin --initial --email=admin@admin.com --name=Admin --password="$NEWPW"' 2>&1 | red; echo "rc=${PIPESTATUS[0]}" +echo "== calibre-web: cps.py -s (the app's own command), as the app user abc" +export NEWPW="$PC" +docker exec -u abc -e NEWPW calibre-web sh -c 'cd /app/calibre-web-automated && python3 cps.py -p /config/app.db -s "admin:$NEWPW"' 2>&1 | red | grep -viE 'ProxyFix|magic shelves|SESSION_COOKIE' ; echo "rc=${PIPESTATUS[0]}" +unset NEWPW +echo "== $(date -u +%FT%TZ) AFTER" +echo "bookstack default -> $(bs password) | new -> $(bs "$PB") | wrong control -> $(bs wrongxyz123)" +echo "calibre-web default -> $(cw admin123) | new -> $(cw "$PC") | wrong control -> $(cw wrongxyz123)" +unset PB PC; rm -f $J diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 0d9a299c..5a1eabc7 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -821,6 +821,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-707** | **[P2] 37 apps still start with a login a stranger can take (`09` §3 decision 45).** Audit of all 53 apps: `app-catalog-felhom.eu/FIRST-ADMIN.md` (class, fix route, status, measured or read). Open: **3 hard-coded defaults** — calibre-web (`admin / admin123`, measured working on demo-hp and 9202; its own `cps.py -s` route needs a generated password WITH a special character — our generator is letters+digits, a controller change), mealie (`changeme@example.com / MyPassword`), wger (`admin / adminadmin`); **34 open first-run screens** (the first visitor creates the admin: actualbudget, adventurelog, audiobookshelf, calcom, docmost, emby, ghost, gitea, gramps-web, home-assistant, homebox, immich, jellyfin, komga, n8n, navidrome, opengist, outline, papra, plant-it, radarr, rallly, recipe-importer, romm, seerr, sonarr, sparkyfitness, tandoor, termix, uptime-kuma, vikunja, wanderer, wishlist, zipline). **Stale notes:** romm's `default_creds` `admin / admin` answers 401 on demo-hp (like a wrong password) — the page now warns with a login that does not exist; zipline's looks stale too. **Measured on demo-hp 2026-09-28 (read-only):** bookstack's default still logs in on the INSTALLED app (the fix is for new installs; the page now warns). Each fix: route (a) env or (b) the app's own CLI/API via `after_install:`, proven on 9202 with the default failing and the generated password working; route (c) a page sentence. Several sessions (operator, 2026-09-28). | **OPEN — P2; owner: CC** | | **R-708** | **[P3-LOW] grafana falls back to password `admin` when its admin field is empty.** `templates/grafana/docker-compose.yml:18` `GF_SECURITY_ADMIN_PASSWORD=${…:-admin}` (read 2026-09-28, the audit). Today the field is generated and required, so it is never empty on a normal install — but an edit, an import or a restore that drops the value would publish grafana with `admin / admin`. **Fix direction:** no default in the compose (`${GF_SECURITY_ADMIN_PASSWORD:?}` refuses to start instead). | **OPEN — P3; owner: CC** | | **R-709** | **[P3-LOW] The deploy page writes the generated admin passwords of installed apps into its HTML.** `internal/web/templates/deploy.html` renders a `type: password` field's decrypted value into a disabled `` (read 2026-09-28; used by the proofs of R-702/R-707 to read the first password as the household sees it). `type: secret` fields got a fetch-on-demand reveal in R-254; `type: password` fields did not. The page needs a login, so this is exposure to a logged-in session's HTML (browser cache, a shared screen, a saved page), not to strangers. **Fix direction:** the R-254 reveal for password fields too. | **OPEN — P3; owner: CC** | +| **R-710** | **[P2-MEDIUM] An app installed before its template gained an `after_install:` is never warned about its default login.** MEASURED 2026-09-29 on demo-hp: bookstack's page carried no known-login sentence although its default `admin@admin.com / password` still logged in (the app was installed before the catalog added bookstack's `after_install` on 2026-09-28; the command never runs for an installed app). `internal/web/known_login.go` reads an ABSENT `after_install` record as "not run yet" for ever. Evidence `audits/login-gate-2026-09-29/A/A2-page-warning-after.txt`. Also: the page has no way to learn of a password the household changed by hand (the brief's Part A4). **Fix direction:** absent record + installed longer than the command's window = in effect; a household "I changed it" press recorded in `app.yaml`. | **OPEN — P2; owner: CC** |