From a314908c7b1ae7fcfe282c68f5aea50d549f1236 Mon Sep 17 00:00:00 2001 From: Kevin Riehl Date: Mon, 4 May 2026 15:58:56 -0700 Subject: [PATCH] Wire security scans into the CI pipeline Replace the test-only workflow with a parallel five-job pipeline: tests, lint+format, gitleaks, Trivy (fs scan + CycloneDX SBOM), and Semgrep SAST. Security scans are report-only initially so the team can baseline findings before flipping the gates to blocking. Adds .gitleaks.toml allowlists for the known dev/test placeholders so the secret scan starts at zero noise. Future build-image / image-scan / push-to-harbor stages are sketched in comments at the bottom of ci.yml. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/ci.yml | 174 ++++++++++++++++++++++++++++++++++++++ .gitea/workflows/test.yml | 39 --------- .gitleaks.toml | 23 +++++ 3 files changed, 197 insertions(+), 39 deletions(-) create mode 100644 .gitea/workflows/ci.yml delete mode 100644 .gitea/workflows/test.yml create mode 100644 .gitleaks.toml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..cee03e6 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,174 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Generate Prisma client + run: pnpm --filter tehriehlbudget-backend exec prisma generate + + - name: Backend tests (Jest) + run: pnpm test:backend + + - name: Frontend tests (Vitest) + # Supabase's createClient validates the URL at module load, so the + # frontend tests need *some* value to import lib/supabase without + # throwing. Real auth calls are stubbed in the tests, so these are + # purely structural placeholders — never used over the wire. + env: + VITE_SUPABASE_URL: http://localhost:54321 + VITE_SUPABASE_ANON_KEY: placeholder-anon-key-for-tests-only + run: pnpm test:frontend + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Generate Prisma client + # Backend ESLint imports types from the generated Prisma client. + run: pnpm --filter tehriehlbudget-backend exec prisma generate + + - name: Lint + run: pnpm lint + + - name: Format check + run: pnpm format:check + + secrets-scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install gitleaks + run: | + curl -sSL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz \ + -o /tmp/gitleaks.tar.gz + tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks + sudo mv /tmp/gitleaks /usr/local/bin/gitleaks + gitleaks version + + - name: Run gitleaks + # TODO: flip to --exit-code 1 once the baseline is clean + run: | + gitleaks detect \ + --source . \ + --config .gitleaks.toml \ + --report-format sarif \ + --report-path gitleaks.sarif \ + --redact \ + --no-git \ + --exit-code 0 + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: gitleaks-report + path: gitleaks.sarif + + vuln-scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Trivy filesystem scan (vuln + misconfig) + # TODO: flip exit-code to '1' once baseline is clean + uses: aquasecurity/trivy-action@0.28.0 + with: + scan-type: fs + scan-ref: . + format: sarif + output: trivy-fs.sarif + severity: HIGH,CRITICAL + exit-code: '0' + scanners: vuln,misconfig + + - name: Trivy SBOM (CycloneDX) + uses: aquasecurity/trivy-action@0.28.0 + with: + scan-type: fs + scan-ref: . + format: cyclonedx + output: sbom.cdx.json + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: trivy-reports + path: | + trivy-fs.sarif + sbom.cdx.json + + sast: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run Semgrep + # TODO: drop the trailing "|| true" once the baseline is clean + run: | + docker run --rm -v "$PWD:/src" returntocorp/semgrep:latest \ + semgrep scan \ + --config p/typescript \ + --config p/react \ + --config p/nodejsscan \ + --config p/owasp-top-ten \ + --sarif --output /src/semgrep.sarif \ + --error /src || true + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: semgrep-report + path: semgrep.sarif + +# ───────────────────────────────────────────────────────────────────────────── +# Future stages (not yet enabled — Dockerfiles do not exist yet): +# +# build-images: +# needs: [test, lint, secrets-scan, vuln-scan, sast] +# - docker build tehriehlbudget-backend → harbor./tehriehlbudget/backend: +# - docker build tehriehlbudget-frontend → harbor./tehriehlbudget/frontend: +# +# image-scan: +# needs: build-images +# - trivy image scan on each built image (severity gate ON here from day one) +# - re-run trivy SBOM on the image so Harbor gets an image-level CycloneDX +# +# push: +# needs: image-scan +# - docker login harbor. (creds via Gitea Actions secrets) +# - docker push backend + frontend tags +# ───────────────────────────────────────────────────────────────────────────── diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml deleted file mode 100644 index 872f18b..0000000 --- a/.gitea/workflows/test.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Tests - -on: - push: - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: pnpm/action-setup@v4 - with: - version: 9 - - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Generate Prisma client - run: pnpm --filter tehriehlbudget-backend exec prisma generate - - - name: Backend tests (Jest) - run: pnpm test:backend - - - name: Frontend tests (Vitest) - # Supabase's createClient validates the URL at module load, so the - # frontend tests need *some* value to import lib/supabase without - # throwing. Real auth calls are stubbed in the tests, so these are - # purely structural placeholders — never used over the wire. - env: - VITE_SUPABASE_URL: http://localhost:54321 - VITE_SUPABASE_ANON_KEY: placeholder-anon-key-for-tests-only - run: pnpm test:frontend diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..ba620b5 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,23 @@ +# Gitleaks configuration +# Extends the built-in default ruleset and adds allowlists for known +# non-secret values that would otherwise trip the scan. + +[extend] +useDefault = true + +[allowlist] +description = "Known dev/test placeholders and generated artifacts" + +regexes = [ + # Frontend test env value, set in .gitea/workflows/ci.yml + '''placeholder-anon-key-for-tests-only''', + # Local-only Postgres password in docker-compose.yml (dev container) + '''development_password''', +] + +paths = [ + '''pnpm-lock\.yaml''', + '''.*/coverage/.*''', + '''.*/dist/.*''', + '''.*/node_modules/.*''', +]