From eed5a8fb1251b96dda08200e51a33c1f708eed52 Mon Sep 17 00:00:00 2001 From: derekc Date: Fri, 25 Sep 2026 00:01:44 -0700 Subject: [PATCH] Fix LOW findings from 2026-09-24 maintenance review - Frontend on nginx-unprivileged (non-root, container port 8080) - Replace unmaintained passlib with bcrypt 5.0.0 (hash-compatible) - Pass DOCS_ENABLED through to the backend container - Raise db mem_limit to 768m for MySQL 8.4 Co-Authored-By: Claude Opus 5.5 --- Release-Notes/v1.1.md | 9 +++++++++ backend/app/auth/jwt.py | 15 +++++++++++---- backend/requirements.txt | 3 +-- docker-compose.yml | 7 ++++--- frontend/Dockerfile | 6 ++++-- frontend/nginx.conf | 2 +- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Release-Notes/v1.1.md b/Release-Notes/v1.1.md index 40d5101..b0a2006 100644 --- a/Release-Notes/v1.1.md +++ b/Release-Notes/v1.1.md @@ -35,3 +35,12 @@ Maintenance release from the 2026-09-24 review (`reports/maintenance-2026-09-24. ### Known / accepted - npm audit: vite ≤6.4.2 and esbuild advisories affect only the **dev server**, which production never runs (it serves the static build). Fixing them fully needs Vite 6.4+/7. - mysql:8.4.11 has upstream findings in Oracle's image: curl, bundled Python tools, and gosu's Go stdlib. + +## v1.1.1 — 2026-09-25 + +Low-severity items from the 2026-09-24 review. + +- Frontend runs as non-root: `nginxinc/nginx-unprivileged:1.30.5-alpine`, listening on **8080** in the container. Host port 8054 is unchanged, so NPM needs no change. +- Replaced unmaintained `passlib` with `bcrypt` 5.0.0 directly. Existing `$2b$` hashes still verify; passwords are truncated to 72 bytes as before. A malformed hash now fails verification instead of raising. +- `DOCS_ENABLED` is now passed to the backend container (default `false`), so the README instructions work. +- db `mem_limit` 512m → 768m (MySQL 8.4 was at ~86% of 512m). diff --git a/backend/app/auth/jwt.py b/backend/app/auth/jwt.py index 73a1f04..e2a2aa4 100644 --- a/backend/app/auth/jwt.py +++ b/backend/app/auth/jwt.py @@ -3,21 +3,28 @@ from typing import Any import jwt from jwt import PyJWTError -from passlib.context import CryptContext +import bcrypt from app.config import get_settings settings = get_settings() -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def _encode(plain: str) -> bytes: + # bcrypt only uses the first 72 bytes; passlib truncated silently, so do the + # same to keep existing hashes verifiable (bcrypt>=5 raises instead). + return plain.encode("utf-8")[:72] def hash_password(plain: str) -> str: - return pwd_context.hash(plain) + return bcrypt.hashpw(_encode(plain), bcrypt.gensalt(rounds=12)).decode("ascii") def verify_password(plain: str, hashed: str) -> bool: - return pwd_context.verify(plain, hashed) + try: + return bcrypt.checkpw(_encode(plain), hashed.encode("ascii")) + except ValueError: # malformed hash + return False def create_access_token(data: dict[str, Any]) -> str: diff --git a/backend/requirements.txt b/backend/requirements.txt index 6c3cecc..be0d788 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -7,8 +7,7 @@ aiomysql==0.3.0 PyMySQL==1.1.2 PyJWT==2.15.0 cryptography==50.0.1 -passlib[bcrypt]==1.7.4 -bcrypt==3.2.2 +bcrypt==5.0.0 pydantic==2.13.5 pydantic-settings==2.5.2 alembic==1.20.0 diff --git a/docker-compose.yml b/docker-compose.yml index 45ff2e5..3b77806 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: timeout: 5s retries: 5 start_period: 180s - mem_limit: 512m + mem_limit: 768m cpus: 1.0 logging: *default-logging @@ -43,6 +43,7 @@ services: ADMIN_PASSWORD: ${ADMIN_PASSWORD} NTFY_URL: ${NTFY_URL:-} NTFY_TOKEN: ${NTFY_TOKEN:-} + DOCS_ENABLED: ${DOCS_ENABLED:-false} depends_on: db: condition: service_healthy @@ -63,13 +64,13 @@ services: container_name: homeschool_frontend restart: unless-stopped ports: - - "8054:80" + - "8054:8080" depends_on: - backend networks: - homeschool_net healthcheck: - test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/"] + test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/"] interval: 30s timeout: 5s retries: 3 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 6d22698..e45e9fb 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -10,11 +10,13 @@ COPY . . RUN npm run build # Stage 2: Serve with nginx -FROM nginx:1.30.5-alpine +FROM nginxinc/nginx-unprivileged:1.30.5-alpine +USER root RUN apk upgrade --no-cache +USER nginx COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf -EXPOSE 80 +EXPOSE 8080 diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 9942ad3..f9b5208 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -33,7 +33,7 @@ limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m; limit_req_zone $binary_remote_addr zone=tv_limit:10m rate=10r/m; server { - listen 80; + listen 8080; server_tokens off; root /usr/share/nginx/html; index index.html;