Go-live hardening: server_tokens, resource limits, pinned images, CVE fixes

- Add server_tokens off to nginx (suppress version leakage)
- Add deploy.resources.limits to all containers (db: 512M, api: 256M, nginx: 64M)
- Pin image tags: mysql:8.0 → 8.0.45, nginx:alpine → 1.29.6-alpine
- Fix CVEs: cryptography 43.0.3 → 46.0.5 (HIGH), python-jose 3.3.0 → 3.4.0 (CRITICAL)
- Add .limit(500) to GET /api/flock and GET /api/admin/users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 00:16:28 -07:00
parent 59f9685e2b
commit 392a48dfb9
5 changed files with 23 additions and 5 deletions

View File

@@ -2,8 +2,8 @@ fastapi==0.115.0
uvicorn==0.32.0 uvicorn==0.32.0
sqlalchemy==2.0.36 sqlalchemy==2.0.36
pymysql==1.1.1 pymysql==1.1.1
cryptography==43.0.3 cryptography==46.0.5
pydantic==2.9.2 pydantic==2.9.2
python-jose[cryptography]==3.3.0 python-jose[cryptography]==3.4.0
passlib[bcrypt]==1.7.4 passlib[bcrypt]==1.7.4
bcrypt==4.0.1 bcrypt==4.0.1

View File

@@ -18,7 +18,7 @@ def list_users(
_: User = Depends(get_current_admin), _: User = Depends(get_current_admin),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
return db.scalars(select(User).order_by(User.created_at)).all() return db.scalars(select(User).order_by(User.created_at).limit(500)).all()
@router.post("/users", response_model=UserOut, status_code=201) @router.post("/users", response_model=UserOut, status_code=201)

View File

@@ -22,6 +22,7 @@ def list_flock_history(
select(FlockHistory) select(FlockHistory)
.where(FlockHistory.user_id == current_user.id) .where(FlockHistory.user_id == current_user.id)
.order_by(FlockHistory.date.desc()) .order_by(FlockHistory.date.desc())
.limit(500)
) )
return db.scalars(q).all() return db.scalars(q).all()

View File

@@ -4,8 +4,13 @@ services:
# MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD should each be 20+ random characters. # MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD should each be 20+ random characters.
# Generate with: openssl rand -hex 16 # Generate with: openssl rand -hex 16
db: db:
image: mysql:8.0 image: mysql:8.0.45
restart: unless-stopped restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
env_file: .env env_file: .env
logging: logging:
driver: json-file driver: json-file
@@ -33,6 +38,11 @@ services:
api: api:
build: ./backend build: ./backend
restart: unless-stopped restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 256M
env_file: .env env_file: .env
logging: logging:
driver: json-file driver: json-file
@@ -58,8 +68,13 @@ services:
# ── Nginx ──────────────────────────────────────────────────────────────────── # ── Nginx ────────────────────────────────────────────────────────────────────
nginx: nginx:
image: nginx:alpine image: nginx:1.29.6-alpine
restart: unless-stopped restart: unless-stopped
deploy:
resources:
limits:
cpus: '0.5'
memory: 64M
logging: logging:
driver: json-file driver: json-file
options: options:

View File

@@ -9,6 +9,8 @@ http {
sendfile on; sendfile on;
# ── Gzip compression ────────────────────────────────────────────────────── # ── Gzip compression ──────────────────────────────────────────────────────
server_tokens off;
gzip on; gzip on;
gzip_types text/plain text/css application/javascript application/json; gzip_types text/plain text/css application/javascript application/json;
gzip_min_length 1000; gzip_min_length 1000;