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) <noreply@anthropic.com>
This commit is contained in:
@@ -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.<host>/tehriehlbudget/backend:<sha>
|
||||
# - docker build tehriehlbudget-frontend → harbor.<host>/tehriehlbudget/frontend:<sha>
|
||||
#
|
||||
# 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.<self-hosted-domain> (creds via Gitea Actions secrets)
|
||||
# - docker push backend + frontend tags
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -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
|
||||
@@ -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/.*''',
|
||||
]
|
||||
Reference in New Issue
Block a user