diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24a3251..202a9ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ concurrency: permissions: contents: write packages: write + security-events: write env: REGISTRY: ghcr.io @@ -151,17 +152,44 @@ jobs: type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - - name: Build and push Docker image + - name: Build Docker image uses: docker/build-push-action@v6 with: context: . - push: ${{ github.event_name == 'push' }} + load: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} target: prod cache-from: type=gha cache-to: type=gha,mode=max + - name: Scan frontend image for vulnerabilities + uses: anchore/scan-action@v5 + id: scan + with: + image: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}" + fail-build: true + severity-cutoff: high + only-fixed: "true" + output-format: sarif + + - name: Upload frontend scan results to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: ${{ steps.scan.outputs.sarif }} + + - name: Push Docker image + if: github.event_name == 'push' + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + target: prod + cache-from: type=gha + - name: Create git tag if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | @@ -221,14 +249,43 @@ jobs: type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - - name: Build and push auth Docker image + - name: Build Docker image uses: docker/build-push-action@v6 with: context: ./auth file: ./auth/Dockerfile - push: ${{ github.event_name == 'push' }} + load: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Scan auth image for vulnerabilities + uses: anchore/scan-action@v5 + id: scan + with: + image: "${{ env.REGISTRY }}/${{ env.AUTH_IMAGE_NAME }}:sha-${{ github.sha }}" + fail-build: true + severity-cutoff: high + only-fixed: "true" + output-format: sarif + + - name: Upload auth scan results to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: ${{ steps.scan.outputs.sarif }} + + - name: Push Docker image + if: github.event_name == 'push' + uses: docker/build-push-action@v6 + with: + context: ./auth + file: ./auth/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha build-and-push-receiptwitness: runs-on: runners-cartsnitch @@ -278,14 +335,43 @@ jobs: type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - - name: Build and push receiptwitness image + - name: Build Docker image uses: docker/build-push-action@v6 with: context: . file: ./receiptwitness/Dockerfile - push: ${{ github.event_name == 'push' }} + load: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Scan receiptwitness image for vulnerabilities + uses: anchore/scan-action@v5 + id: scan + with: + image: "${{ env.REGISTRY }}/${{ env.RECEIPTWITNESS_IMAGE_NAME }}:sha-${{ github.sha }}" + fail-build: true + severity-cutoff: high + only-fixed: "true" + output-format: sarif + + - name: Upload receiptwitness scan results to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: ${{ steps.scan.outputs.sarif }} + + - name: Push Docker image + if: github.event_name == 'push' + uses: docker/build-push-action@v6 + with: + context: . + file: ./receiptwitness/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha build-and-push-api: runs-on: runners-cartsnitch @@ -335,14 +421,43 @@ jobs: type=raw,value=${{ steps.calver.outputs.version }},enable=${{ github.ref == 'refs/heads/main' }} type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - - name: Build and push API Docker image + - name: Build Docker image uses: docker/build-push-action@v6 with: context: ./api file: ./api/Dockerfile - push: ${{ github.event_name == 'push' }} + load: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Scan api image for vulnerabilities + uses: anchore/scan-action@v5 + id: scan + with: + image: "${{ env.REGISTRY }}/${{ env.API_IMAGE_NAME }}:sha-${{ github.sha }}" + fail-build: true + severity-cutoff: high + only-fixed: "true" + output-format: sarif + + - name: Upload api scan results to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: ${{ steps.scan.outputs.sarif }} + + - name: Push Docker image + if: github.event_name == 'push' + uses: docker/build-push-action@v6 + with: + context: ./api + file: ./api/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha deploy-dev: runs-on: runners-cartsnitch diff --git a/Dockerfile b/Dockerfile index 0b92e95..9f2762b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # Stage 1: Build FROM node:20-alpine AS build - +RUN apk update && apk upgrade --no-cache WORKDIR /app COPY package.json package-lock.json ./ @@ -11,6 +11,9 @@ RUN npm run build # Stage 2: Production — uses nginxinc/nginx-unprivileged which runs as non-root (UID 101) FROM nginxinc/nginx-unprivileged:stable-alpine AS prod +USER root +RUN apk update && apk upgrade --no-cache +USER 101 COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/api/Dockerfile b/api/Dockerfile index e3b4bbf..771d5ec 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.12-slim AS build -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \ libpq-dev \ build-essential \ && rm -rf /var/lib/apt/lists/* @@ -12,7 +12,7 @@ RUN pip install --no-cache-dir --prefix=/install . FROM python:3.12-slim AS prod -RUN apt-get update && apt-get install -y --no-install-recommends libpq5 && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends libpq5 && rm -rf /var/lib/apt/lists/* WORKDIR /app RUN adduser --system --group --uid 1000 app diff --git a/auth/Dockerfile b/auth/Dockerfile index 1028e89..0b88089 100644 --- a/auth/Dockerfile +++ b/auth/Dockerfile @@ -1,4 +1,5 @@ FROM node:22-alpine AS builder +RUN apk update && apk upgrade --no-cache WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci @@ -7,6 +8,7 @@ COPY src/ src/ RUN npm run build FROM node:22-alpine +RUN apk update && apk upgrade --no-cache WORKDIR /app ENV NODE_ENV=production COPY package.json package-lock.json* ./ diff --git a/auth/package-lock.json b/auth/package-lock.json index 1f7bfc9..373abad 100644 --- a/auth/package-lock.json +++ b/auth/package-lock.json @@ -941,9 +941,9 @@ } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, "node_modules/delegates": { diff --git a/receiptwitness/Dockerfile b/receiptwitness/Dockerfile index 8fead61..79e53a3 100644 --- a/receiptwitness/Dockerfile +++ b/receiptwitness/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app # build-essential and libpq-dev are needed to compile any C-extension wheels # (e.g. psycopg2 fallback). No git needed — common/ is copied from the repo root. -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \ libpq-dev \ build-essential \ && rm -rf /var/lib/apt/lists/* @@ -25,7 +25,7 @@ FROM python:3.12-slim AS prod WORKDIR /app # Install Playwright system dependencies for Chromium -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \ libnss3 \ libatk1.0-0 \ libatk-bridge2.0-0 \