name: Security Audits on: schedule: - cron: '43 2 * * 1' workflow_dispatch: permissions: contents: read issues: write jobs: bun-audit: name: bun audit runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Check out repository uses: actions/checkout@v7 - name: Set up Bun uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install --frozen-lockfile - name: Run bun audit shell: bash run: | set -o pipefail bun audit --audit-level=high --json | tee bun-audit.json - name: Upload bun audit report if: always() uses: actions/upload-artifact@v7 with: name: bun-audit-report path: bun-audit.json if-no-files-found: ignore govulncheck: name: govulncheck runs-on: ubuntu-latest timeout-minutes: 15 defaults: run: working-directory: apps/api steps: - name: Check out repository uses: actions/checkout@v7 - name: Set up Go uses: actions/setup-go@v7 with: go-version-file: apps/api/go.mod cache-dependency-path: apps/api/go.sum - name: Install govulncheck run: go install golang.org/x/vuln/cmd/govulncheck@latest - name: Run govulncheck shell: bash run: | set -o pipefail govulncheck ./... | tee ../../govulncheck.txt - name: Upload govulncheck report if: always() uses: actions/upload-artifact@v7 with: name: govulncheck-report path: govulncheck.txt if-no-files-found: ignore report-status: name: Report security audit status runs-on: ubuntu-latest timeout-minutes: 10 needs: - bun-audit - govulncheck if: always() steps: - name: Create or update failure issue if: ${{ needs.bun-audit.result != 'success' || needs.govulncheck.result != 'success' }} uses: actions/github-script@v9 env: BUN_AUDIT_RESULT: ${{ needs.bun-audit.result }} GOVULNCHECK_RESULT: ${{ needs.govulncheck.result }} ISSUE_LABEL: security-audit ISSUE_TITLE: Security audits failing with: script: | const owner = context.repo.owner const repo = context.repo.repo const label = process.env.ISSUE_LABEL const title = process.env.ISSUE_TITLE const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}` const body = [ 'One or more scheduled security audit jobs failed.', '', `- Workflow run: ${runUrl}`, `- bun audit: ${process.env.BUN_AUDIT_RESULT}`, `- govulncheck: ${process.env.GOVULNCHECK_RESULT}`, `- Trigger: ${context.eventName}`, `- Commit: ${context.sha}`, ].join('\n') try { await github.rest.issues.getLabel({ owner, repo, name: label }) } catch (error) { if (error.status !== 404) { throw error } await github.rest.issues.createLabel({ owner, repo, name: label, color: 'b60205', description: 'Failures from the scheduled security audits workflow', }) } const { data: issues } = await github.rest.issues.listForRepo({ owner, repo, state: 'open', labels: label, per_page: 100, }) const existing = issues.find((issue) => issue.title === title) if (existing) { await github.rest.issues.createComment({ owner, repo, issue_number: existing.number, body, }) core.info(`Updated existing issue #${existing.number}.`) return } const { data: created } = await github.rest.issues.create({ owner, repo, title, body, labels: [label], }) core.info(`Created issue #${created.number}.`) - name: Close resolved failure issue if: ${{ needs.bun-audit.result == 'success' && needs.govulncheck.result == 'success' }} uses: actions/github-script@v9 env: ISSUE_LABEL: security-audit ISSUE_TITLE: Security audits failing with: script: | const owner = context.repo.owner const repo = context.repo.repo const label = process.env.ISSUE_LABEL const title = process.env.ISSUE_TITLE const { data: issues } = await github.rest.issues.listForRepo({ owner, repo, state: 'open', labels: label, per_page: 100, }) const existing = issues.find((issue) => issue.title === title) if (!existing) { core.info('No open security audit failure issue to close.') return } await github.rest.issues.createComment({ owner, repo, issue_number: existing.number, body: `Security audits recovered in ${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}. Closing this alert.`, }) await github.rest.issues.update({ owner, repo, issue_number: existing.number, state: 'closed', }) core.info(`Closed issue #${existing.number}.`)