Compare commits
2
Commits
5a2510059d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eed5a8fb12 | ||
|
|
f74bc1a1ff |
No files matched your search
@@ -35,3 +35,12 @@ Maintenance release from the 2026-09-24 review (`reports/maintenance-2026-09-24.
|
|||||||
### Known / accepted
|
### 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.
|
- 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.
|
- 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
@@ -3,21 +3,28 @@ from typing import Any
|
|||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
from jwt import PyJWTError
|
from jwt import PyJWTError
|
||||||
from passlib.context import CryptContext
|
import bcrypt
|
||||||
|
|
||||||
from app.config import get_settings
|
from app.config import get_settings
|
||||||
|
|
||||||
settings = 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:
|
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:
|
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:
|
def create_access_token(data: dict[str, Any]) -> str:
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ aiomysql==0.3.0
|
|||||||
PyMySQL==1.1.2
|
PyMySQL==1.1.2
|
||||||
PyJWT==2.15.0
|
PyJWT==2.15.0
|
||||||
cryptography==50.0.1
|
cryptography==50.0.1
|
||||||
passlib[bcrypt]==1.7.4
|
bcrypt==5.0.0
|
||||||
bcrypt==3.2.2
|
|
||||||
pydantic==2.13.5
|
pydantic==2.13.5
|
||||||
pydantic-settings==2.5.2
|
pydantic-settings==2.5.2
|
||||||
alembic==1.20.0
|
alembic==1.20.0
|
||||||
|
|||||||
+4
-3
@@ -24,7 +24,7 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 180s
|
start_period: 180s
|
||||||
mem_limit: 512m
|
mem_limit: 768m
|
||||||
cpus: 1.0
|
cpus: 1.0
|
||||||
logging: *default-logging
|
logging: *default-logging
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ services:
|
|||||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||||
NTFY_URL: ${NTFY_URL:-}
|
NTFY_URL: ${NTFY_URL:-}
|
||||||
NTFY_TOKEN: ${NTFY_TOKEN:-}
|
NTFY_TOKEN: ${NTFY_TOKEN:-}
|
||||||
|
DOCS_ENABLED: ${DOCS_ENABLED:-false}
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -63,13 +64,13 @@ services:
|
|||||||
container_name: homeschool_frontend
|
container_name: homeschool_frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "8054:80"
|
- "8054:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
networks:
|
networks:
|
||||||
- homeschool_net
|
- homeschool_net
|
||||||
healthcheck:
|
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
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
+4
-2
@@ -10,11 +10,13 @@ COPY . .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Stage 2: Serve with nginx
|
# 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
|
RUN apk upgrade --no-cache
|
||||||
|
USER nginx
|
||||||
|
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 8080
|
||||||
+1
-1
@@ -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;
|
limit_req_zone $binary_remote_addr zone=tv_limit:10m rate=10r/m;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 8080;
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|||||||
@@ -153,13 +153,17 @@ This section was first blocked on backups. The user then asked for the HIGH find
|
|||||||
| Container | Image | Image ID |
|
| Container | Image | Image ID |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| homeschool_db | mysql:8.4.11 | sha256:ee241324a55f… |
|
| homeschool_db | mysql:8.4.11 | sha256:ee241324a55f… |
|
||||||
| homeschool_backend | homeschool-backend | sha256:51b49afb4ec4… |
|
| homeschool_backend | homeschool-backend (v1.1.0) | sha256:1eb66dc84e67… |
|
||||||
| homeschool_frontend | homeschool-frontend | sha256:f9a737699cda… |
|
| homeschool_frontend | homeschool-frontend (v1.1.0) | sha256:50f1de90c447… |
|
||||||
|
|
||||||
### Post-deploy regression
|
### Post-deploy regression
|
||||||
|
|
||||||
After the HIGH deploy, `POST /api/sessions/{id}/timer` returned **500 on every call**: 8 failures, 0 successes, starting 06:43 UTC. FastAPI 0.141 raises on a failed lazy-load of `current_block.subject.options` during response serialization, where 0.115 silently skipped it. The first smoke test didn't cover timer actions. Fixed in v1.1.0 by eager-loading `Subject.options` in `sessions.py` and `dashboard.py`. The smoke test now covers the full timer lifecycle, the TV dashboard with an active block, schedule/subject writes, and every parameterless GET route (from the OpenAPI spec).
|
After the HIGH deploy, `POST /api/sessions/{id}/timer` returned **500 on every call**: 8 failures, 0 successes, starting 06:43 UTC. FastAPI 0.141 raises on a failed lazy-load of `current_block.subject.options` during response serialization, where 0.115 silently skipped it. The first smoke test didn't cover timer actions. Fixed in v1.1.0 by eager-loading `Subject.options` in `sessions.py` and `dashboard.py`. The smoke test now covers the full timer lifecycle, the TV dashboard with an active block, schedule/subject writes, and every parameterless GET route (from the OpenAPI spec).
|
||||||
|
|
||||||
|
### v1.1.0 deploy verification
|
||||||
|
|
||||||
|
All 3 containers are healthy with 0 restarts, and log rotation is active (10m × 3). Public `/api/health` returns 200. Production timer actions return 200 (4 of 4 right after the deploy). Tagged `v1.1.0` and pushed. The pre-review rollback images (`rollback-20260924`) were pruned by the user; `rollback-20260924b` (the HIGH-fix build) remains.
|
||||||
|
|
||||||
### Post-update scan (trivy HIGH/CRITICAL)
|
### Post-update scan (trivy HIGH/CRITICAL)
|
||||||
|
|
||||||
| Image | Before | After |
|
| Image | Before | After |
|
||||||
|
|||||||
Reference in new issue
Block a user