6b6191a426
Triage of an external SAST/SCA scan run on 2026-05-06. Most loud findings were already resolved by PR #412 (the 18-CVE backport); this PR addresses the residual real items: * Drop unused `handlebars` from backend deps. The runtime require was removed in PR #367 (#367) but the package.json line stayed. handlebars was the source of two flagged criticals (CVE-2026-33937 RCE, GHSA-2w6w-674q-4c4q AST injection) plus 8 highs — all now gone. * `npm audit fix` on backend + frontend. Bumps transitive picomatch, flatted, postcss, brace-expansion via lockfile, and direct dompurify, lodash, vite, i18next-http-backend within their existing semver ranges. Both audits now report 0 vulnerabilities. * Add `event.origin === window.location.origin` check to the THEME_PREVIEW message listener in PreviewPage. The branding page posts from the same origin, so nothing legitimate is rejected; without the check, any third party that window.open()'d the preview could push arbitrary branding/theme payloads (semgrep insufficient-postmessage-origin-validation). * nginx: `proxy_hide_header` for X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Content-Security-Policy, Permissions-Policy, Strict-Transport-Security at server level. nginx adds these itself, but helmet on the backend was also emitting them — clients were seeing duplicates (testssl flagged "Multiple X-Frame-Options / CSP / Permissions-Policy / Referrer-Policy headers" on the live origin). Single source of truth now. * Dockerfile hardening (checkov): - HEALTHCHECK on backend/Dockerfile, backend/Dockerfile.dev, frontend/Dockerfile.dev. Frontend production Dockerfile already had one. - USER node in frontend/Dockerfile.dev (was running as root). * GitHub Actions docker-build.yml: explicit top-level `permissions: contents: read`. Per-job blocks already declare `packages: write` where needed; this stops future steps from inheriting unintended privileges (CKV2_GHA_1). Backend npm audit: 4 vulns -> 0. Frontend npm audit: 6 vulns -> 0. Backend unit tests: 13 suites, 131/132 passing (1 pre-existing skip). Frontend type-check + lint: clean. The pre-existing integration-test failures (live DB / S3 required) and the ThemeCustomizerEnhanced QueryClientProvider failures are unrelated and reproduce on origin/beta without these changes.
72 lines
2.4 KiB
Docker
72 lines
2.4 KiB
Docker
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/the-luap/picpeak"
|
|
LABEL org.opencontainers.image.description="PicPeak Backend Service"
|
|
LABEL org.opencontainers.image.licenses="MIT"
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy package files
|
|
COPY package*.json ./
|
|
|
|
# Install dependencies (--omit=dev replaces deprecated --only=production)
|
|
RUN npm ci --omit=dev
|
|
|
|
# Copy application files
|
|
COPY . .
|
|
|
|
# Production stage
|
|
FROM node:22-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
# Upgrade all packages to fix security vulnerabilities (OpenSSL, libexpat, BusyBox CVEs)
|
|
RUN apk upgrade --no-cache
|
|
|
|
# Upgrade npm to fix tar, minimatch, brace-expansion CVEs in npm's own deps
|
|
# Pin to 10.x to stay compatible with Node 22 Alpine (npm 11.x has dependency issues)
|
|
RUN npm install -g npm@10
|
|
|
|
# Install dumb-init for proper signal handling, postgresql-client for database
|
|
# checks, and ffmpeg for video upload support. Alpine's ffmpeg package ships
|
|
# both `ffmpeg` and `ffprobe` built natively against musl libc — the npm
|
|
# `@ffmpeg-installer/ffmpeg` binary is glibc-built and (a) doesn't reliably
|
|
# run on Alpine and (b) only includes ffmpeg, not ffprobe (which the video
|
|
# pipeline calls via fluent-ffmpeg.ffprobe()).
|
|
RUN apk add --no-cache dumb-init postgresql-client ffmpeg
|
|
|
|
# Create non-root user
|
|
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
|
|
|
|
# Copy from builder
|
|
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
|
|
COPY --chown=nodejs:nodejs . .
|
|
|
|
# Ensure all source files are readable and wait script is executable
|
|
RUN chmod -R a+r /app && chmod +x wait-for-db.sh
|
|
|
|
# Create necessary directories
|
|
RUN mkdir -p storage/events/active storage/events/archived storage/thumbnails data logs && \
|
|
chown -R nodejs:nodejs storage data logs
|
|
|
|
USER nodejs
|
|
|
|
EXPOSE 3000
|
|
|
|
# Healthcheck hits the same /health endpoint already used by the e2e
|
|
# runner and by the docker-compose `depends_on: condition: service_healthy`
|
|
# checks. wget is part of the Alpine base image. Long start-period covers
|
|
# the wait-for-db.sh delay before the Node process starts listening.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
|
|
|
ENTRYPOINT ["dumb-init", "--"]
|
|
CMD ["./wait-for-db.sh", "node", "server.js"]
|