7546f104a3
Single PR closing every open code-scanning alert at https://github.com/PicPeak/picpeak/security/code-scanning. Both repos go from 27 open alerts → 0 across direct deps, transitive deps, and build- time bundled deps. ## Backend (`backend/package.json` + overrides) Direct dep bumps: - axios 1.15.2 → 1.16.0 (closes 9 alerts: 7 high + 1 med + 1 low) - nodemailer 8.0.10 → ^9.0.1 (closes 1 high — SSRF + file-read via raw option) - multer 2.1.1 → 2.2.0 (closes 2 alerts: 1 high + 1 med) - form-data 4.0.5 → 4.0.6 (closes 1 high) - tar ≥7.5.13 → ≥7.5.16 (closes 1 med) - postcss 8.5.6 → 8.5.10 (closes 1 med) - i18next-http-backend 3.0.2 → 3.0.5 (closes 1 med — backend lagged frontend) - js-yaml 4.1.1 → ^4.2.0 (closes 1 med) - joi 17.13.3 → ^17.13.4 (closes 1 med) Overrides updated to match deps (npm rejected the install otherwise) + nodemailer ^9.0.1 added as override so imapflow + mailparser transitive bundling of older nodemailer is also fixed. Babel devDep auto-bumped via `npm audit fix` (low-severity arbitrary file read). Backend npm audit: 0 vulnerabilities. ## Frontend (`frontend/package.json`) Direct dep bumps: - axios 1.15.2 → 1.16.0 - postcss 8.5.6 → 8.5.10 - i18next-http-backend 3.0.5 → 3.0.5 (already current — kept for parity) `npm audit fix` swept up 12 transitive issues at the same time: - vitest (1 critical — file read on UI server) - vite (2 high — fs.deny bypass, NTLM hash via launch-editor) - ws (2 high — uninitialized memory + DoS) - dompurify (8 mod — multiple IN_PLACE / hook-pollution XSS vectors) - react-router-dom + react-router (1 mod transitive) - esbuild (1 mod — dev server file read) - @babel/core (1 low) Frontend npm audit: 0 vulnerabilities. ## Frontend Dockerfile - Build stage: `node:20-alpine` → `node:22-alpine` Closes the npm-bundled CVE class (picomatch, ip-address, brace-expansion, @sigstore/core, tar) that came from Node 20's older bundled npm. Matches the backend Dockerfile base. The nginx serving stage stays at `nginx:1.28-alpine` — that tag is rolling, so the next build picks up the fixed 1.28.3-r4 layer that closes the 4 nginx CVEs. ## Verification - Backend: `npm audit` → 0 vulnerabilities ✅ - Frontend: `npm audit` → 0 vulnerabilities ✅ - Backend Jest (workflow engine, rounding, WhatsApp): 47/47 pass ✅ - Frontend Vitest: 84/84 pass ✅ - `frontend npm run build`: succeeds ✅ - nodemailer 9 sanity check: our usage is `createTransport({host,port,secure,auth})` + `sendMail({from,to,subject,html,text})` — we don't touch the `raw` option that 9.x tightened, so the major bump is API-compatible.
85 lines
2.7 KiB
Docker
85 lines
2.7 KiB
Docker
# Build stage — node:22-alpine drops npm-bundled CVEs in older Node 20
|
|
# (picomatch, ip-address, brace-expansion, @sigstore/core, tar) since the
|
|
# bundled npm version is newer in 22. Matches the backend Dockerfile base.
|
|
FROM node:22-alpine AS builder
|
|
|
|
# Add build arguments
|
|
ARG CACHEBUST=1
|
|
ARG BUILD_DATE
|
|
ARG VCS_REF
|
|
ARG VERSION
|
|
|
|
# Add labels for GitHub Container Registry
|
|
LABEL org.opencontainers.image.source="https://github.com/PicPeak/picpeak"
|
|
LABEL org.opencontainers.image.description="PicPeak Frontend Application"
|
|
LABEL org.opencontainers.image.licenses="MIT"
|
|
|
|
# Set working directory
|
|
WORKDIR /app
|
|
|
|
# Copy package files
|
|
COPY package*.json ./
|
|
|
|
# Install dependencies
|
|
RUN npm ci --legacy-peer-deps
|
|
|
|
# Copy source files
|
|
COPY . .
|
|
|
|
# Build the application
|
|
RUN npm run build
|
|
|
|
# Production stage (Alpine 3.23 with OpenSSL 3.5.5, patched libexpat)
|
|
FROM nginx:1.28-alpine
|
|
|
|
# Upgrade all packages to fix security vulnerabilities (OpenSSL, libexpat, BusyBox CVEs)
|
|
RUN apk upgrade --no-cache
|
|
|
|
# Install runtime dependencies. `gettext` provides envsubst, used by
|
|
# docker-entrypoint.sh for the BRAND_TITLE / BRAND_DESCRIPTION runtime
|
|
# substitution into index.html (#521 — runtime fix for self-hosters
|
|
# on the pre-built GHCR image who can't override at build time).
|
|
RUN apk add --no-cache curl gettext
|
|
|
|
# Remove default nginx config
|
|
RUN rm -rf /etc/nginx/conf.d/*
|
|
|
|
# Copy custom nginx config
|
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
|
|
# Copy built application from builder stage
|
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
|
|
# Snapshot index.html as a template so the entrypoint always renders
|
|
# from a known-good source — not from its own previous substitution.
|
|
# Container restarts can change BRAND_TITLE freely; the rendered file
|
|
# is recomputed from the .tpl each time.
|
|
RUN mv /usr/share/nginx/html/index.html /usr/share/nginx/html/index.html.tpl
|
|
|
|
# Runtime entrypoint that envsubsts the template and execs nginx
|
|
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
|
|
|
# Set permissions (nginx user already exists in nginx:alpine)
|
|
RUN chown -R nginx:nginx /usr/share/nginx/html && \
|
|
chown -R nginx:nginx /var/cache/nginx && \
|
|
chown -R nginx:nginx /var/log/nginx && \
|
|
touch /var/run/nginx.pid && \
|
|
chown -R nginx:nginx /var/run/nginx.pid
|
|
|
|
# Expose port
|
|
EXPOSE 80
|
|
|
|
# Health check
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
CMD curl -f http://localhost/health || exit 1
|
|
|
|
# Switch to non-root user
|
|
USER nginx
|
|
|
|
# Start nginx via the entrypoint so each container start re-renders
|
|
# index.html from the template against the current BRAND_TITLE /
|
|
# BRAND_DESCRIPTION env vars (defaults applied when unset).
|
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
|
CMD ["nginx", "-g", "daemon off;"]
|