Compare commits

..

1 Commits

Author SHA1 Message Date
Paperclip c85c9b12a7 feat(ci): add Trivy image vulnerability scanning to all Docker builds
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 15:21:08 +00:00
11 changed files with 56 additions and 89 deletions
+36 -43
View File
@@ -163,20 +163,20 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan frontend image for vulnerabilities
uses: anchore/scan-action@v5
id: scan
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.28.0
with:
image: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}"
fail-build: true
severity-cutoff: high
output-format: sarif
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
format: 'sarif'
output: 'trivy-results-frontend.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload frontend scan results to GitHub Security
- name: Upload Trivy scan results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
sarif_file: 'trivy-results-frontend.sarif'
- name: Push Docker image
if: github.event_name == 'push'
@@ -186,7 +186,6 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
target: prod
cache-from: type=gha
- name: Create git tag
@@ -256,23 +255,21 @@ jobs:
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
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.28.0
with:
image: "${{ env.REGISTRY }}/${{ env.AUTH_IMAGE_NAME }}:sha-${{ github.sha }}"
fail-build: true
severity-cutoff: high
output-format: sarif
image-ref: ${{ env.REGISTRY }}/${{ env.AUTH_IMAGE_NAME }}:sha-${{ github.sha }}
format: 'sarif'
output: 'trivy-results-auth.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload auth scan results to GitHub Security
- name: Upload Trivy scan results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
sarif_file: 'trivy-results-auth.sarif'
- name: Push Docker image
if: github.event_name == 'push'
@@ -341,23 +338,21 @@ jobs:
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
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.28.0
with:
image: "${{ env.REGISTRY }}/${{ env.RECEIPTWITNESS_IMAGE_NAME }}:sha-${{ github.sha }}"
fail-build: true
severity-cutoff: high
output-format: sarif
image-ref: ${{ env.REGISTRY }}/${{ env.RECEIPTWITNESS_IMAGE_NAME }}:sha-${{ github.sha }}
format: 'sarif'
output: 'trivy-results-receiptwitness.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload receiptwitness scan results to GitHub Security
- name: Upload Trivy scan results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
sarif_file: 'trivy-results-receiptwitness.sarif'
- name: Push Docker image
if: github.event_name == 'push'
@@ -426,23 +421,21 @@ jobs:
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
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.28.0
with:
image: "${{ env.REGISTRY }}/${{ env.API_IMAGE_NAME }}:sha-${{ github.sha }}"
fail-build: true
severity-cutoff: high
output-format: sarif
image-ref: ${{ env.REGISTRY }}/${{ env.API_IMAGE_NAME }}:sha-${{ github.sha }}
format: 'sarif'
output: 'trivy-results-api.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload api scan results to GitHub Security
- name: Upload Trivy scan results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
sarif_file: 'trivy-results-api.sarif'
- name: Push Docker image
if: github.event_name == 'push'
+1 -4
View File
@@ -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,9 +11,6 @@ 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
+2 -2
View File
@@ -1,6 +1,6 @@
FROM python:3.12-slim AS build
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
RUN apt-get update && 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 upgrade -y && apt-get install -y --no-install-recommends libpq5 && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends libpq5 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN adduser --system --group --uid 1000 app
-2
View File
@@ -1,5 +1,4 @@
FROM node:22-alpine AS builder
RUN apk update && apk upgrade --no-cache
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
@@ -8,7 +7,6 @@ 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* ./
+3 -3
View File
@@ -941,9 +941,9 @@
}
},
"node_modules/defu": {
"version": "6.1.7",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
"integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"license": "MIT"
},
"node_modules/delegates": {
+6 -12
View File
@@ -4,23 +4,17 @@ import pg from "pg";
const { Pool } = pg;
const pool = new Pool({
connectionString:
process.env.DATABASE_URL ??
"postgresql://cartsnitch:cartsnitch@localhost:5432/cartsnitch",
});
const secret = process.env.BETTER_AUTH_SECRET;
if (!secret) {
throw new Error("BETTER_AUTH_SECRET environment variable is required");
}
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
console.warn(
"WARNING: DATABASE_URL is not set — using default localhost connection. " +
"Set DATABASE_URL for production deployments."
);
}
export const pool = new Pool({
connectionString: databaseUrl ?? "postgresql://cartsnitch:cartsnitch@localhost:5432/cartsnitch",
});
export const auth = betterAuth({
database: pool,
basePath: "/auth",
+3 -17
View File
@@ -1,6 +1,6 @@
import { createServer } from "node:http";
import { toNodeHandler } from "better-auth/node";
import { auth, pool } from "./auth.js";
import { auth } from "./auth.js";
const port = parseInt(process.env.PORT ?? "3001", 10);
@@ -9,22 +9,8 @@ const handler = toNodeHandler(auth);
const server = createServer(async (req, res) => {
// Health check
if (req.url === "/health" && req.method === "GET") {
try {
const client = await pool.connect();
try {
await Promise.race([
client.query("SELECT 1"),
new Promise((_, reject) => setTimeout(() => reject(new Error("DB timeout")), 2000)),
]);
} finally {
client.release();
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", db: "connected" }));
} catch {
res.writeHead(503, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "error", db: "unreachable" }));
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
return;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Submodule cartsnitch deleted from a53daddb9a
+3 -3
View File
@@ -9805,9 +9805,9 @@
}
},
"node_modules/vite": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"devOptional": true,
"license": "MIT",
"dependencies": {
+2 -2
View File
@@ -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 upgrade -y && apt-get install -y --no-install-recommends \
RUN apt-get update && 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 upgrade -y && apt-get install -y --no-install-recommends \
RUN apt-get update && apt-get install -y --no-install-recommends \
libnss3 \
libatk1.0-0 \
libatk-bridge2.0-0 \