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 <[email protected]>
This commit is contained in:
derekcandClaude Opus 5.5 committed 2026-09-25 00:01:44 -07:00
1 parent f74bc1a1ff
commit eed5a8fb12
6 files changed
+30 -12

No files matched your search

+9
View File
@@ -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).
+11 -4
View File
@@ -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:
+1 -2
View File
@@ -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
+4 -3
View File
@@ -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
+4 -2
View File
@@ -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
+1 -1
View File
@@ -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;