Decision 46 recorded (setup gate spiked first); Part A: demo-hp bookstack + calibre-web admin passwords changed; R-710 filed
gates / gates (push) Successful in 26s

Passwords stored out-of-band (operator's credentials file); value scan clean before commit.

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-09-29 08:07:37 +02:00
parent 58226e9abb
commit 2b1cb246cf
5 changed files with 58 additions and 0 deletions
@@ -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).
---
@@ -0,0 +1,12 @@
== 2026-09-29T06:06:11Z BEFORE (the app's own login form, via traefik)
bookstack admin@admin.com / <catalog default> -> 302->/ | wrong control -> 302->/login
calibre-web admin / <catalog default> -> 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->
@@ -0,0 +1,13 @@
# demo-hp 9201, 2026-09-29 ~06:10Z, controller 0.279.0: the app page (GET /apps/<app>, 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).
@@ -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')/<redacted>/g" -e "s/$(printf '%s' "$PC" | sed 's/[.[\*^$/]/\\&/g')/<redacted>/g"; }
echo "== $(date -u +%FT%TZ) BEFORE (the app's own login form, via traefik)"
echo "bookstack admin@admin.com / <catalog default> -> $(bs password) | wrong control -> $(bs wrongxyz123)"
echo "calibre-web admin / <catalog default> -> $(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
+1
View File
@@ -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 `<input value=…>` (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** |
<!-- DUE-CHECKS-BEGIN — machine-readable. Parsed by scripts/due_checks_gate.py.
One row per dated check. The R-number must have a row above. Dates are UTC.