# Per ADR-0015 (CI/CD on Gitea Actions). Weekly full-tree security # scans plus a Lighthouse run against the production environment when # its URL is configured. Complements the per-PR ci.yml workflow with # broader / longer-running checks that don't fit the per-PR budget. name: Security and perf — scheduled on: schedule: # Mondays, 04:00 UTC — outside business hours; before the week starts. - cron: '0 4 * * 1' workflow_dispatch: jobs: full-tree-scan: runs-on: [self-hosted, on-prem] # Step ordering mirrors ci.yml: scanners run before `pnpm install` # so the working tree is not polluted with node_modules content # (READMEs / fixtures of upstream packages contain demo # secrets that gitleaks false-positives on by the hundreds). # The deep-history gitleaks scan here doesn't strictly need it # (history doesn't contain node_modules), but consistency with # ci.yml keeps the two workflows reading the same way. steps: # fetch-depth: 0 → full history. The per-PR gitleaks scan is # shallow + working-tree-only; this scheduled job is where we # do the deep history scan that catches secrets ever committed # (and not just what's currently checked in). - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' cache: 'pnpm' # Full-tree Trivy (no skip-dirs, no severity filter — the per-PR # gate filters by severity for speed; this run wants the full # surface for the security feed). Manual install + curl, same # pattern as ci.yml — see the rationale there. - name: Install Trivy env: # renovate: datasource=github-releases depName=aquasecurity/trivy TRIVY_VERSION: '0.70.0' GITHUB_TOKEN: ${{ secrets.GITHUBCOM_TOKEN }} run: | curl -sfL \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ -o /tmp/trivy.tar.gz \ "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" tar -xzf /tmp/trivy.tar.gz -C /usr/local/bin trivy trivy --version - name: Run Trivy run: | trivy fs \ --scanners vuln \ --ignore-unfixed \ . # Deep gitleaks scan (full git history). Same install pattern as # ci.yml. `--redact` masks any matched secret in the log so we # don't leak it via CI logs themselves. - name: Install gitleaks env: # renovate: datasource=github-releases depName=gitleaks/gitleaks GITLEAKS_VERSION: '8.30.1' GITHUB_TOKEN: ${{ secrets.GITHUBCOM_TOKEN }} run: | curl -sfL \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ -o /tmp/gitleaks.tar.gz \ "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" tar -xzf /tmp/gitleaks.tar.gz -C /usr/local/bin gitleaks gitleaks version - name: Run gitleaks (full history) run: | gitleaks detect \ --source . \ --redact \ --exit-code 1 # npm-advisory check (against pnpm-lock.yaml). Run last so # `pnpm install` does not pollute the working tree before the # scanners above. - run: pnpm install --frozen-lockfile - run: pnpm audit lighthouse-prod: # Skipped silently if the prod URL hasn't been configured yet. if: vars.LHCI_PROD_URL != '' runs-on: [self-hosted, on-prem] steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' cache: 'pnpm' - run: pnpm install --frozen-lockfile - run: pnpm exec lhci collect --url=${{ vars.LHCI_PROD_URL }} --numberOfRuns=3 - run: pnpm exec lhci assert --config=./lighthouserc.js - uses: actions/upload-artifact@v7 if: always() with: name: lighthouseci-prod-report path: .lighthouseci/ retention-days: 90