From 44d4e4876dea0196c37b792ab4cd4bef815a4d5a Mon Sep 17 00:00:00 2001 From: npeter83 Date: Tue, 18 Aug 2026 02:34:45 +0200 Subject: [PATCH] build(deps): pin a reproducible lock + pip-audit; bump node 20->22 (E1 S4, SEC-5/DX-3) requirements.txt stays the source (floor ranges); requirements.lock pins the exact resolved versions (generated in the python:3.12-slim prod base, extras like psycopg-binary/uvloop included). The prod Dockerfile, Dockerfile.test and CI now install the lock, so the image is reproducible and the versions are the ones pip-audit scans. CI gains a non-blocking pip-audit step over the lock. The frontend build + CI move from the EOL node 20 to node 22. --- .gitea/workflows/check.yml | 21 ++++++++------ Dockerfile | 9 ++++-- backend/Dockerfile.test | 6 ++-- backend/requirements.lock | 57 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 backend/requirements.lock diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml index 74faec22..9fe276ce 100644 --- a/.gitea/workflows/check.yml +++ b/.gitea/workflows/check.yml @@ -3,12 +3,12 @@ # frontend — tsc (3 projects) + eslint + prettier --check + knip + vitest # backend — ruff + the full pytest suite (pure-logic + the DB-backed lane) against a real Postgres # -# Runner: the lab act_runner (LXC 115) maps `ubuntu-latest` -> node:20-bookworm and mounts the Docker -# socket, so service containers work. The backend runs pytest NATIVELY against a `postgres` service -# (not the local docker-compose.test.yml, whose bind-mount is unreliable under a mounted-socket runner); -# fidelity is kept by installing the SAME pinned requirements, Postgres 16, and ffmpeg, and by setting -# SIFTLODE_TEST_REQUIRE_DB=1 so a DB/ffmpeg that failed to come up is a hard error, never a silent skip. -# node 20 matches the prod build image (Dockerfile `node:20-alpine`); S4's node-22 bump moves both. +# Runner: the lab act_runner (LXC 115) mounts the Docker socket, so service containers work. The backend +# runs pytest NATIVELY against a `postgres` service (not the local docker-compose.test.yml, whose +# bind-mount is unreliable under a mounted-socket runner); fidelity is kept by installing the SAME pinned +# requirements, Postgres 16, and ffmpeg, and by setting SIFTLODE_TEST_REQUIRE_DB=1 so a DB/ffmpeg that +# failed to come up is a hard error, never a silent skip. node 22 matches the prod build image +# (Dockerfile `node:22-alpine`). name: gate on: [push, pull_request] @@ -16,7 +16,7 @@ on: [push, pull_request] jobs: frontend: runs-on: ubuntu-latest - container: node:20-bookworm + container: node:22-bookworm defaults: run: working-directory: frontend @@ -56,9 +56,14 @@ jobs: - run: apt-get update && apt-get install -y --no-install-recommends ffmpeg postgresql-client - name: Wait for Postgres run: until pg_isready -h postgres -U siftlode -d siftlode_test; do sleep 1; done - - run: pip install --no-cache-dir -r backend/requirements.txt -r backend/requirements-dev.txt + - run: pip install --no-cache-dir -r backend/requirements.lock -r backend/requirements-dev.txt - name: ruff run: python -m ruff check --config backend/ruff.toml backend scripts - name: pytest run: python -m pytest working-directory: backend + # Dependency CVE audit over the pinned lock. Non-blocking for now (informational) — a real CVE + # shows up in the run without failing the gate; tighten to a hard fail once the noise is known. + - name: pip-audit (advisory) + run: pip install --no-cache-dir pip-audit && pip-audit -r backend/requirements.lock + continue-on-error: true diff --git a/Dockerfile b/Dockerfile index 9a7f1c00..716bf849 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ ARG GIT_SHA=unknown ARG BUILD_DATE= # Stage 1: build the frontend SPA -FROM node:20-alpine AS frontend +FROM node:22-alpine AS frontend WORKDIR /fe # Deps first, keyed on the lockfile, so editing app source never re-resolves node_modules. COPY frontend/package.json frontend/package-lock.json ./ @@ -57,9 +57,12 @@ RUN apt-get update \ && rm /tmp/deno.zip \ && rm -rf /var/lib/apt/lists/* -COPY backend/requirements.txt . +# Install from the PINNED lock (requirements.lock, generated from requirements.txt) so the image is +# byte-reproducible and the exact versions are the ones pip-audit scanned in CI — not whatever floats +# up from the floor ranges on build day. Regenerate the lock when requirements.txt changes (see its header). +COPY backend/requirements.lock . RUN --mount=type=cache,target=/root/.cache/pip \ - pip install --upgrade pip && pip install -r requirements.txt + pip install --upgrade pip && pip install -r requirements.lock COPY --chown=appuser backend/ . # Read at runtime by config.py as the app_version (a committed single source of truth). diff --git a/backend/Dockerfile.test b/backend/Dockerfile.test index 03f3709b..1f71de56 100644 --- a/backend/Dockerfile.test +++ b/backend/Dockerfile.test @@ -14,5 +14,7 @@ WORKDIR /app RUN apt-get update \ && apt-get install -y --no-install-recommends ffmpeg \ && rm -rf /var/lib/apt/lists/* -COPY requirements.txt requirements-dev.txt ./ -RUN pip install --no-cache-dir -r requirements.txt -r requirements-dev.txt +# Install the SAME pinned lock as the prod image (+ the test-only tooling), so the tests run against +# the exact dependency versions prod ships. +COPY requirements.lock requirements-dev.txt ./ +RUN pip install --no-cache-dir -r requirements.lock -r requirements-dev.txt diff --git a/backend/requirements.lock b/backend/requirements.lock new file mode 100644 index 00000000..3b8d8188 --- /dev/null +++ b/backend/requirements.lock @@ -0,0 +1,57 @@ +# Pinned dependency lock for reproducible builds (SEC-5). GENERATED — do not hand-edit. +# Source of truth is requirements.txt (the floor ranges); regenerate after editing it with: +# docker run --rm -v "$PWD":/b python:3.12-slim sh -c 'pip install -q -r /b/requirements.txt && pip freeze --exclude pip --exclude setuptools --exclude wheel' | sort +# The prod Dockerfile + Dockerfile.test + CI install THIS file; pip-audit scans it in CI. +APScheduler==3.11.3 +Authlib==1.7.2 +Mako==1.4.1 +MarkupSafe==3.0.3 +PyYAML==6.0.3 +SQLAlchemy==2.0.52 +alembic==1.19.1 +annotated-doc==0.0.5 +annotated-types==0.8.0 +anyio==4.14.2 +argon2-cffi-bindings==25.1.0 +argon2-cffi==25.1.0 +bgutil-ytdlp-pot-provider==1.3.1 +brotli==1.2.0 +certifi==2026.7.22 +cffi==2.1.1 +charset-normalizer==3.5.1 +click==8.4.2 +cryptography==46.0.7 +fastapi==0.141.1 +feedparser-sgmllib==2.1.0 +feedparser==6.0.14 +greenlet==3.5.5 +h11==0.16.0 +httpcore==1.0.9 +httptools==0.8.0 +httpx==0.28.1 +idna==3.18 +itsdangerous==2.2.0 +joserfc==1.7.4 +mutagen==1.48.1 +numpy==2.5.2 +psycopg-binary==3.3.4 +psycopg==3.3.4 +py3langid==0.3.0 +pycparser==3.0 +pycryptodomex==3.23.0 +pydantic-settings==2.15.0 +pydantic==2.13.4 +pydantic_core==2.46.4 +python-dotenv==1.2.3 +requests==2.34.2 +starlette==1.6.0 +typing-inspection==0.4.4 +typing_extensions==4.16.0 +tzlocal==5.4.4 +urllib3==2.7.0 +uvicorn==0.52.3 +uvloop==0.22.1 +watchfiles==1.2.0 +websockets==17.0.1 +yt-dlp-ejs==0.8.0 +yt-dlp==2026.7.4