security: harden production configuration and routes
Run Tests on Branches / Detect Changes (push) Successful in 12s
Run Tests on Branches / Frontend Tests (push) Successful in 2m12s
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
Run Tests on Branches / Parapharmacy API Tests (push) Successful in 2m2s
Run Tests on Branches / PIP Platform Tests (push) Has been skipped
Run Tests on Branches / Backend Tests (push) Successful in 2m8s

This commit is contained in:
Antoni Nuñez Romeu
2026-07-22 17:24:54 +02:00
parent f60af5f6b2
commit 849763896d
21 changed files with 638 additions and 68 deletions
@@ -0,0 +1,101 @@
# FarmaFinder Security Dependency and Release Hardening Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Remove the remaining high/critical dependency and supply-chain risks, make Python and container builds reproducible, and establish deployment-blocking security verification after the configuration and access-control hotfixes.
**Architecture:** Upgrade dependencies in isolated package-family batches while preserving the existing runtime security controls. Resolve Python dependencies into a committed `uv.lock`, pin container inputs by immutable digests, and run dependency, image, and secret scans in CI. Any remaining advisory is recorded with its path, exploitability, mitigation, and owner.
**Tech Stack:** npm workspaces/package-lock, Node.js 20/24, Python 3.13, uv, pip-audit, Docker Compose, Trivy, Gitleaks, Jest, Vitest, pytest.
---
## Files and responsibilities
- Create `docs/security/2026-07-22-dependency-hardening.md`: audit baseline, selected versions/digests, residual-advisory decisions, and verification evidence.
- Create `apps/pip-platform/uv.lock`: resolved, hash-pinned Python dependency graph.
- Modify `apps/backend/package.json`, `apps/parapharmacy-api/package.json`, `apps/frontend/package.json`, `apps/scraper/package.json`, and the root `package-lock.json` for isolated dependency batches.
- Modify both Node Dockerfiles, Compose files, and deployment docs for immutable images and lockfile-enforced installs.
- Create `.github/workflows/security.yml`: reproducible installs, tests, audits, secret scanning, image scanning, and Compose checks.
## Baseline to preserve
The preceding hotfix plan already added production secret validation, service-key middleware, protected parapharmacy mutations, internal Compose networking, production-safe Swagger behavior, and security regression tests. Do not revert those controls. The last online audit reported 56 advisories for the backend/workspace scope and 3 for the parapharmacy API; counts must be refreshed before each batch.
### Task 1: Establish an auditable dependency baseline
**Files:** Create `docs/security/2026-07-22-dependency-hardening.md`.
- [ ] Run `npm ci` from the repository root, then run `npm audit --json`, `npm audit --prefix apps/backend --json`, `npm audit --prefix apps/parapharmacy-api --json`, `npm audit --prefix apps/frontend --json`, and `npm audit --prefix apps/scraper --json`, saving reports only under `/tmp` and never recording secret values.
- [ ] Record counts, direct versions, Node/npm/Python/uv/Docker versions, dependency paths, available fixes, and whether each finding reaches a production image.
- [ ] Confirm no unrelated lockfile rewrite exists with `git diff --check` and `git status --short`.
- [ ] Commit the ledger with `git add docs/security/2026-07-22-dependency-hardening.md && git commit -m "docs: record dependency hardening baseline"`.
### Task 2: Create and verify the Python lockfile
**Files:** Create `apps/pip-platform/uv.lock`; update PIP deployment docs if present.
- [ ] Run `cd apps/pip-platform && uv lock`; do not hand-edit generated lock content.
- [ ] Recreate from the lock with `uv sync --frozen --extra dev`, then run `uv run pytest -q` and `uv run pip-audit --strict`.
- [ ] If `pip-audit` is absent, add it to the development dependency group, rerun `uv lock`, and repeat the frozen sync. Record each Python advisory with package, path, reachability, and owner.
- [ ] Commit with `git add apps/pip-platform/uv.lock && git commit -m "build: lock pip platform dependencies"`.
### Task 3: Pin runtime container inputs immutably
**Files:** Root/PIP Compose files, both Node Dockerfiles, and the dependency-hardening ledger.
- [ ] Replace every runtime `:latest` image with an approved release. Resolve digests with `docker buildx imagetools inspect redis:7-alpine`, `postgres:16-alpine`, `mongo:7`, the approved n8n release, the approved Redis exporter release, and the approved PostgreSQL exporter release.
- [ ] Replace each reference with `repository:tag@sha256:` followed by the exact digest returned by `docker buildx imagetools inspect`, record the UTC update date and approver, and verify the digest is for the deployment platform.
- [ ] Ensure each Node Dockerfile copies its matching `package.json` and lockfile before `npm ci --omit=dev`; build both images and run a startup/config smoke test.
- [ ] Commit the image/Dockerfile batch separately so it can be reverted without undoing dependency code.
### Task 4: Upgrade backend dependency families independently
**Files:** `apps/backend/package.json`, root `package-lock.json`, existing backend tests.
- [ ] Run `npm ci` and `npm test --prefix apps/backend -- --runInBand` before changing versions.
- [ ] Upgrade all direct OpenTelemetry packages as one compatible family: API, auto-instrumentations, exporters, instrumentation-pino, resources, SDKs, and semantic conventions. Do not upgrade only `auto-instrumentations-node`.
- [ ] Regenerate only through npm, inspect the lockfile for unrelated upgrades, and rerun the backend suite.
- [ ] In a separate batch, upgrade `bcrypt` and `sqlite3`; verify native installs under Node 20 and 24, session/database tests, `npm ls tar node-gyp`, and `npm audit --prefix apps/backend --audit-level=high`.
- [ ] Never use `npm audit fix --force`; any major upgrade requires a compatibility note and review. Commit each family separately.
### Task 5: Upgrade parapharmacy and scraper dependency families
**Files:** `apps/parapharmacy-api/package.json`, `apps/scraper/package.json`, root `package-lock.json`, security tests.
- [ ] Upgrade Mongoose within its supported major, regenerate the lockfile, and confirm `npm ls fast-uri` resolves outside the audited range.
- [ ] Run `npm test --prefix apps/parapharmacy-api -- --runInBand __tests__/security.test.js` and `npm audit --prefix apps/parapharmacy-api --audit-level=high`.
- [ ] Upgrade scraper Puppeteer dependencies only after recording the current executable path. Preserve `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true` and `/usr/bin/chromium`; verify with `node -e "console.log(process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium')"`.
- [ ] Run scraper module-load/available tests and commit Mongoose and scraper batches separately.
### Task 6: Upgrade frontend tooling without exposing Vitest UI
**Files:** `apps/frontend/package.json`, root `package-lock.json`, Vite/Vitest config, CI workflow.
- [ ] Confirm no production or CI command uses Vitest UI with `rg -n "vitest( --ui|.*ui)" apps/frontend package.json .github`.
- [ ] Upgrade Vite, Rollup/esbuild, and Vitest as a compatible toolchain; keep React plugin, jsdom, and testing-library versions compatible with the selected Vitest major.
- [ ] Regenerate the lockfile and run `npm test --prefix apps/frontend`, `npm run build --prefix apps/frontend`, and `npm audit --prefix apps/frontend --audit-level=high`.
- [ ] Record any development-only residual advisory and commit this batch separately.
### Task 7: Add deployment-blocking CI gates
**Files:** Create `.github/workflows/security.yml`.
- [ ] Add pull-request/protected-branch jobs for `npm ci` plus all Node tests/builds; workspace/app `npm audit --audit-level=high`; `uv sync --frozen`, pytest, and `pip-audit --strict`; Gitleaks full-history scanning; Trivy high/critical image scanning; and root/PIP Compose rendering.
- [ ] The Compose job must use fixture values and assert that only intended frontend/API ports are published, no internal database/exporter ports are published, and no production-secret `${...:-placeholder}` fallback remains.
- [ ] Build production images with committed lockfiles, scan image digests, upload JSON reports as artifacts, and never upload `.env` files or secret values.
- [ ] Pin third-party actions to reviewed immutable commit SHAs and grant read-only repository permissions. Commit with `git add .github/workflows/security.yml && git commit -m "ci: enforce dependency and image security gates"`.
### Task 8: Run final verification and residual-advisory review
**Files:** Update the dependency-hardening ledger only if verification exposes a regression or documented exception.
- [ ] Run `npm ci`, backend tests, parapharmacy security tests, frontend tests/build, `uv sync --frozen --extra dev`, `uv run pytest`, and `uv run pip-audit --strict`.
- [ ] Render all Compose files with production-like fixture variables; inspect for placeholders, mutable tags, public database/exporter ports, and missing required secrets.
- [ ] Build all production images and black-box test public GETs, 401 unauthenticated mutations, 403 invalid keys, absent production Swagger, and absent host-published internal infrastructure.
- [ ] Re-run all audits. Every remaining high/critical item must list package, dependency path, production reachability, exploitability, mitigation, owner, and review/expiry date; unexplained high/critical findings block deployment.
- [ ] Run `git diff --check`, confirm no real `.env` is tracked, attach command counts to the ledger, and commit the final evidence.
## Self-review
This plan covers the remaining audit findings: dependency-family upgrades, Python locking/auditing, immutable images, lockfile-enforced Docker installs, secret scanning, image scanning, frontend/Vitest exposure, and residual-advisory review. It deliberately avoids forced major upgrades and keeps each risky family in a reversible batch.
@@ -0,0 +1,97 @@
# FarmaFinder Security Hotfixes Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Remove production-blocking secret, access-control, network-exposure, and dependency risks identified by the 2026-07-22 security audit.
**Architecture:** Make production configuration fail closed, centralize service authentication for parapharmacy ingestion operations, and keep internal infrastructure private behind the reverse proxy. Upgrade dependency families in isolated batches with lockfile and runtime verification.
**Tech Stack:** Node.js 20/24, Express, express-session, MongoDB/Mongoose, Docker Compose, npm lockfiles, Python/Pydantic settings, Jest/Vitest.
---
## Files and responsibilities
- Modify `apps/backend/server.js`: production configuration validation, secure session cookie defaults, and any shared service-auth helper integration.
- Create `apps/backend/src/config/required-env.js` and `apps/backend/src/middleware/service-auth.js`: fail-closed environment validation and constant-time service-key verification.
- Modify `apps/parapharmacy-api/src/server.js`, `apps/parapharmacy-api/src/routes/products.js`, and `apps/parapharmacy-api/src/routes/scraper.js`: protect mutation/scrape routes, tighten limits, and hide Swagger in production.
- Modify `docker-compose.yml` and `apps/pip-platform/docker-compose*.yml`: remove secret fallbacks, stop publishing internal ports, and pin image/dependency behavior.
- Modify `apps/pip-platform/src/infrastructure/config/settings.py`: reject placeholder production secrets and wildcard credentialed CORS.
- Modify `apps/backend/.env.example`, `.env.example`, and relevant README/docs: document required secret generation without real values.
- Add/extend `apps/backend/__tests__/server.test.js` and create `apps/parapharmacy-api/__tests__/security.test.js`: regression coverage for fail-closed behavior and route protection.
- Update relevant `package.json` and lockfiles only through package-manager commands after the security tests are in place.
### Task 1: Rotate and inventory credentials before code changes
**Files:** Deployment secret store and local ignored `.env` files; no repository source changes required.
- [ ] Revoke the Expo access token found in the local environment and issue a replacement with the minimum project scope.
- [ ] Generate a new VAPID key pair and replace both public/private values wherever deployed.
- [ ] Rotate `SESSION_SECRET`, `PG_PASSWORD`, `REDIS_PASSWORD`, `N8N_PASSWORD`, `INGEST_API_KEY`, MongoDB credentials, and PIP `JWT_SECRET_KEY` if their values were used outside local development.
- [ ] Search deployment logs, CI variables, shell history, and backups for the old values; record the revocation date without writing secret values to Git.
- [ ] Confirm `git ls-files` contains no actual `.env` file before continuing.
### Task 2: Add fail-closed production configuration validation
**Files:** Create `apps/backend/src/config/required-env.js`; modify `apps/backend/server.js`, `apps/pip-platform/src/infrastructure/config/settings.py`, and both PIP Compose files.
- [ ] Add a backend validator that rejects production startup when `SESSION_SECRET`, `CORS_ORIGIN`, and `PG_URL`/`PG_PASSWORD` are missing or equal to a known placeholder. Use `crypto.timingSafeEqual` only for fixed-length key comparisons; validation itself should compare exact placeholder strings.
- [ ] Replace the backend fallback at `server.js:84` with a required value from the validator.
- [ ] Set `cookie.secure` to `true` whenever `NODE_ENV === 'production'`, while retaining an explicit development override only for local HTTP.
- [ ] Replace every Compose `${SECRET:-placeholder}` expression with `${SECRET:?SECRET must be set}` for production-required secrets.
- [ ] In PIP settings, reject `JWT_SECRET_KEY=change-me-in-production`, reject default database/broker credentials in production, and reject `CORS_ORIGINS=['*']` when credentials are enabled.
- [ ] Add tests that start configuration with missing/placeholder secrets and assert a clear startup error.
### Task 3: Authenticate parapharmacy ingestion and admin mutation routes
**Files:** Create `apps/parapharmacy-api/src/middleware/service-auth.js`; modify `apps/parapharmacy-api/src/server.js`, `src/routes/products.js`, and `src/routes/scraper.js`; create `apps/parapharmacy-api/__tests__/security.test.js`.
- [ ] Require `INGEST_API_KEY` for `POST /api/products`, `POST /api/products/bulk`, and `POST /api/scrape` using `Authorization: Bearer <key>` or a dedicated internal header. Reject missing/malformed keys with 401 and compare supplied keys in constant time.
- [ ] Require a separate admin credential for `PUT /api/products/:id` and `DELETE /api/products/:id`; do not reuse a public frontend session unless the API is intentionally integrated with that session.
- [ ] Add a route-specific limiter for `/api/scrape`, cap queries/sources to bounded arrays, cap product bulk size, and lower `express.json` to the smallest limit required by real payloads.
- [ ] Return `{ error: 'Scraping failed' }` without `error.message` in production.
- [ ] Mount Swagger only when `NODE_ENV !== 'production'` or protect it with the same admin control.
- [ ] Test 401 for unauthenticated mutation/scrape requests, 403/401 for invalid keys, and successful behavior for a valid key. Test that public GET search endpoints remain available.
### Task 4: Close infrastructure network exposure
**Files:** Modify `docker-compose.yml`, `apps/pip-platform/docker-compose.yml`, `apps/pip-platform/docker-compose.runtime.yml`, and `apps/frontend/nginx.conf` as needed.
- [ ] Remove host `ports` for Redis, PostgreSQL, MongoDB, exporters, and n8n; use `127.0.0.1:host:container` only when local operator access is explicitly required.
- [ ] Publish only the intended frontend/reverse-proxy port and route internal API traffic through the proxy or a private Docker network.
- [ ] Add a dedicated internal network and keep database/exporter services off any public-facing network.
- [ ] Add production security headers at the reverse proxy, including HSTS only when HTTPS is guaranteed, and verify proxy headers are preserved.
- [ ] Disable or protect `/api/docs` in production and verify an external request cannot reach n8n, MongoDB, PostgreSQL, Redis, or exporter ports.
### Task 5: Make builds reproducible and remove mutable image inputs
**Files:** `apps/backend/Dockerfile`, `apps/parapharmacy-api/Dockerfile`, `docker-compose.yml`, PIP Compose files, CI workflow files.
- [ ] Replace `npm install --omit=dev` in the parapharmacy Dockerfile with `npm ci --omit=dev`.
- [ ] Pin all runtime image tags to approved versions and digests; record the update date in the deployment documentation.
- [ ] Add CI checks for `npm ci`, `npm audit --audit-level=high`, image vulnerability scanning, and secret scanning.
- [ ] Generate a Python lockfile and run `pip-audit` or `uv audit` against resolved dependencies.
### Task 6: Upgrade vulnerable dependency families in batches
**Files:** `package.json`, `package-lock.json`, each app `package.json`/lockfile touched by the audit.
- [ ] First update patch/minor-compatible vulnerable transitive packages and regenerate lockfiles with `npm install --package-lock-only`; inspect the diff for unrelated upgrades.
- [ ] Upgrade backend OpenTelemetry packages as one compatible family, then `bcrypt`/SQLite-related packages, and rerun backend tests.
- [ ] Upgrade parapharmacy API `mongoose`/`fast-uri` and transitive packages, then rerun API security tests.
- [ ] Upgrade frontend Vite/Rollup/esbuild and Vitest. Keep Vitest UI disabled in production and verify no UI server is started by CI or deployment.
- [ ] Upgrade scraper dependencies and confirm Puppeteer still uses the intended Chromium binary.
- [ ] Re-run `npm audit --json` for the workspace and each lockfile; document any accepted residual advisory with package, path, exploitability, and owner.
### Task 7: Verification gate before deployment
**Files:** No source changes unless verification exposes a regression.
- [ ] Run `git diff --check`.
- [ ] Run backend tests: `npm test --prefix apps/backend`.
- [ ] Run frontend tests: `npm test --prefix apps/frontend`.
- [ ] Run the parapharmacy security tests and all available package tests.
- [ ] Render Compose configuration with production-like variables and confirm no placeholder values remain: `docker compose config`.
- [ ] Build every production image with lockfile-enforced installs.
- [ ] Run a black-box smoke test proving public GET endpoints work, protected mutations return 401 without a key, and internal infrastructure is not host-published.
- [ ] Re-run secret scanning and `npm audit --audit-level=high`; block deployment on any critical/high issue without an explicit documented exception.