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