Fix MEDIUM findings from 2026-09-24 maintenance review

- Backend retries DB connection at startup (up to 180s) so host reboots
  no longer crash-loop it; add backend and frontend healthchecks
- Docker log rotation (json-file 10m x 3) on all services
- ntfy alerts use X-Real-IP (set by nginx after real_ip resolution)
  instead of the client-controlled first X-Forwarded-For entry
- Frontend build on Node 24 LTS with package-lock.json + npm ci;
  axios 1.20.0, vite 5.4.21
- README: backup/restore/rollback runbook, real-IP proxy trust notes
- Release-Notes/v1.1.md; version 1.1.0

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
derekcandClaude Opus 5.5 committed 2026-09-24 23:44:25 -07:00
1 parent 3170c7f4eb
commit 4130467b22
11 files changed
+1851 -20

No files matched your search

+10 -10
View File
@@ -134,7 +134,7 @@ This section was first blocked on backups. The user then asked for the HIGH find
| Real-IP rate limiting tested | PASS | Test nginx: client A was limited after its burst, client B was unaffected, and a forged XFF prefix resolved to the true client. |
| Deploy | PASS* | The MySQL data upgrade (80040 → 80411) took about 90s, longer than the healthcheck window, so compose aborted the dependent containers. The user started them manually. Fixed by adding `start_period: 180s` to the db healthcheck. |
| Sections 2/3 re-run | PASS (partial) | All containers up, backend logs clean, public `/api/health` 200. Live bundle now contains `8e92ae6`. nginx logs real client IPs. The user confirmed the site is back up. |
| Version tag / release notes | DEFERRED | The repo has no tagging or `Release-Notes/` convention yet. |
| Version tag / release notes | PASS | `v1.1.0`, `Release-Notes/v1.1.md` |
| Old images cleanup | DEFERRED | Keep the rollback tags and volume copy until the update has been stable for about a week. Then remove them: `docker rmi homeschool-{backend,frontend}:rollback-20260924 mysql:8.0.40` and `docker volume rm homeschool_mysql_data_pre84_20260924`. |
### Changes applied
@@ -184,13 +184,13 @@ This section was first blocked on backups. The user then asked for the HIGH find
3. ~~Fixable HIGH/CRITICAL CVEs in all images and Python deps.~~ Fixed by the rebuild and dependency bumps.
4. ~~MySQL 8.0 is EOL.~~ Upgraded to 8.4.11 LTS.
### MEDIUM
### MEDIUM (all resolved 2026-09-24, v1.1.0)
5. ~~The frontend is not deployed at HEAD.~~ Resolved by the rebuild.
6. No Docker log rotation.
7. The backend crash-loops on every host reboot (DB startup race). The db `start_period` does **not** fix this: on reboot, Docker's restart policy ignores `depends_on`. It needs connect-retry in the backend startup. backend and frontend have no healthchecks.
8. Node 20 (build stage) is EOL, and there is no `package-lock.json`.
9. There is no rollback/restore runbook, and there are no release tags.
10. Client IP in ntfy alerts can be spoofed through `X-Forwarded-For`.
6. ~~No Docker log rotation.~~ Added `x-logging` anchor (json-file, 10m × 3) to all services.
7. ~~The backend crash-loops on reboot, and there are no backend/frontend healthchecks.~~ `_wait_for_db()` in `main.py` retries for up to 180s. Tested: the backend started before its DB, logged retries, and came up with 0 restarts. Healthchecks were added to both services.
8. ~~Node 20 EOL and no lockfile.~~ Now node:24.21.0-alpine with `package-lock.json` and `npm ci`. axios → 1.20.0, vite → 5.4.21. Remaining npm audit items (vite ≤6.4.2 / esbuild) are dev-server-only and accepted; the fix needs Vite 6.4+.
9. ~~No rollback/restore runbook or release tags.~~ README now has "Backup, Restore & Rollback" and documents real-IP proxy trust. Added `Release-Notes/v1.1.md` and tagged `v1.1.0`.
10. ~~Client IP in ntfy alerts can be spoofed.~~ New `app/utils/client_ip.py` reads `X-Real-IP`, which nginx sets after real_ip resolution. Tested: a forged XFF is ignored.
### LOW
11. db memory is at 82% of its 512 MiB limit.
@@ -208,7 +208,7 @@ Portainer/NPM drift, functional smoke tests, ntfy test, off-host/VM backups, HTT
- Failed (critical/high): 7. That is backups (counted once, CRITICAL), rate limiting, image CVEs, dependency CVEs, runtime EOL, log rotation and frontend drift.
- Failed (non-critical) / WARN: 18
- Pending user confirmation: 14
- Deferred: 2 (release tag, old image cleanup)
- Updates applied: 4 (real-IP rate limiting, Python deps, base images, MySQL 8.4)
- Deferred: 1 (old image cleanup)
- Updates applied: 9 (HIGH: real-IP rate limiting, Python deps, base images, MySQL 8.4. MEDIUM: log rotation, DB-wait + healthchecks, Node 24 + lockfile, runbook + release tag, trusted client IP)
**Overall: NEEDS ATTENTION.** All HIGH findings are resolved. The CRITICAL no-backups finding is still open: the pre-update dump and volume copy are on-host only, with no schedule.
**Overall: NEEDS ATTENTION.** All HIGH and MEDIUM findings are resolved. The CRITICAL no-backups finding is still open: the pre-update dump and volume copy are on-host only, with no schedule.