deps(server): bump fast-uri (#232) #574
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Security Scanning | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| schedule: | |
| - cron: '0 6 * * 1' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| statuses: write | |
| jobs: | |
| audit: | |
| name: Dependency Audit | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - run: npm ci --ignore-scripts | |
| # scripts/audit-with-exceptions.mjs is the single vulnerability allowlist | |
| # (finding M-19). It fails on an undocumented finding, an exception past | |
| # its reviewBy date, a stale exception, and a severity increase since the | |
| # assessment — all of which the removed scripts/production-audit.mjs let | |
| # through. | |
| - name: Production dependency audit — desktop (moderate+, documented exceptions) | |
| run: node scripts/audit-with-exceptions.mjs | |
| - name: Install server dependencies | |
| run: npm ci --ignore-scripts | |
| working-directory: server | |
| # Previously `npm audit --production --audit-level=high || true`, i.e. a | |
| # step that could not fail (finding M-18). | |
| - name: Production dependency audit — server (moderate+, documented exceptions) | |
| run: node scripts/audit-with-exceptions.mjs --scope=server | |
| # Informational only, and labelled as such: the full tree including dev | |
| # dependencies is not what ships. | |
| - name: Full dependency audit including dev (informational) | |
| run: npm audit || true | |
| - name: Check for outdated dependencies (informational) | |
| run: npm outdated || true | |
| secret-scan: | |
| name: Committed Secret Scan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Full history: the scheduled run scans every blob reachable from any | |
| # ref, and a shallow clone would silently reduce that to the tip | |
| # commit while still reporting a successful history scan. | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| # No `npm ci`: scripts/scan-secrets.mjs has no dependencies beyond Node | |
| # and git, deliberately, so this gate cannot be disabled by a dependency | |
| # resolution problem. | |
| # Run first and on its own: a scanner whose rules have stopped matching | |
| # would otherwise report a clean tree. --self-test scans a synthetic | |
| # sample for every rule and fails if any rule is dead, which is what makes | |
| # the PASS below mean something. | |
| - name: Verify the scanner still detects its own samples | |
| run: node scripts/scan-secrets.mjs --self-test | |
| - name: Scan the working tree for committed secrets | |
| run: node scripts/scan-secrets.mjs --report="${{ runner.temp }}/secret-scan-report.json" | |
| # History is immutable without rewriting every clone, so the known | |
| # historical exposures carry dated records in | |
| # security/secret-scan-allowlist.json. Run weekly rather than per-PR | |
| # because it reads every blob in the repository. | |
| - name: Scan full history for committed secrets | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| run: node scripts/scan-secrets.mjs --history --report="${{ runner.temp }}/secret-scan-history-report.json" | |
| - name: Upload secret scan report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: secret-scan-report | |
| path: ${{ runner.temp }}/secret-scan*.json | |
| if-no-files-found: ignore | |
| retention-days: 90 | |
| snyk: | |
| name: Snyk Vulnerability Scan | |
| runs-on: ubuntu-latest | |
| # Previously skipped on the weekly schedule. That is the run that matters | |
| # most — it catches an advisory published against code that has not changed — | |
| # and with the status job no longer treating a skip as a pass, skipping here | |
| # would report a weekly failure instead. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - run: npm ci --ignore-scripts | |
| - name: Determine whether Snyk is configured | |
| id: snyk-config | |
| env: | |
| SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} | |
| run: | | |
| if [ -n "$SNYK_TOKEN" ]; then | |
| echo 'configured=true' >> "$GITHUB_OUTPUT" | |
| else | |
| echo 'configured=false' >> "$GITHUB_OUTPUT" | |
| echo '::warning::SNYK_TOKEN is not configured; this job runs the self-contained audit gate instead so its result still reflects a scan that actually ran.' | |
| fi | |
| # No continue-on-error (finding M-18): when Snyk is configured, a high or | |
| # critical finding fails the job. | |
| - name: Run Snyk to check for vulnerabilities | |
| if: steps.snyk-config.outputs.configured == 'true' | |
| uses: snyk/actions/node@v1.0.0 | |
| env: | |
| SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} | |
| with: | |
| args: --severity-threshold=high | |
| # When no token exists the job must still produce a real verdict rather | |
| # than being skipped and then reported as a success. This runs the | |
| # committed gate at the same severity threshold Snyk was configured for, | |
| # over both workspaces, so a green `snyk` status always means "a | |
| # vulnerability scan ran and found nothing at high+". | |
| - name: Fallback scan — install server dependencies | |
| if: steps.snyk-config.outputs.configured != 'true' | |
| run: npm ci --ignore-scripts | |
| working-directory: server | |
| - name: Fallback vulnerability scan (no Snyk token configured) | |
| if: steps.snyk-config.outputs.configured != 'true' | |
| run: | | |
| node scripts/audit-with-exceptions.mjs --severity=high | |
| node scripts/audit-with-exceptions.mjs --scope=server --severity=high | |
| lint: | |
| name: Lint & Static Analysis | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - run: npm ci --ignore-scripts | |
| - name: Run ESLint | |
| run: npm run lint | |
| test-security: | |
| name: Security Tests | |
| runs-on: ubuntu-latest | |
| env: | |
| ELECTRON_SKIP_BINARY_DOWNLOAD: '1' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - name: Install system dependencies | |
| run: sudo apt-get update && sudo apt-get install -y python3 make g++ | |
| - run: npm ci | |
| - name: Rebuild native modules | |
| run: npm rebuild better-sqlite3-multiple-ciphers | |
| - name: Run cross-org access tests | |
| run: npm run test:security | |
| - name: Run business logic tests | |
| run: npm run test:business | |
| - name: Run compliance validation tests | |
| run: npm run test:compliance | |
| load-test: | |
| name: Performance Load Test | |
| runs-on: ubuntu-latest | |
| env: | |
| ELECTRON_SKIP_BINARY_DOWNLOAD: '1' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - name: Install system dependencies | |
| run: sudo apt-get update && sudo apt-get install -y python3 make g++ | |
| - run: npm ci | |
| - name: Rebuild native modules | |
| run: npm rebuild better-sqlite3-multiple-ciphers | |
| - name: Run load tests | |
| run: node scripts/run-test-suites.cjs performance | |
| lockfile-check: | |
| name: Lockfile Integrity | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| # A missing lockfile means `npm ci` resolves differently on every run, | |
| # which defeats both the audit gate and the SBOM. This used to emit a | |
| # ::warning:: and pass. | |
| - name: Verify lockfiles are committed | |
| run: | | |
| missing=0 | |
| for f in package-lock.json server/package-lock.json; do | |
| if [ ! -f "$f" ]; then | |
| echo "::error::$f is not committed — dependency resolution is not reproducible" | |
| missing=1 | |
| fi | |
| done | |
| exit $missing | |
| - name: Verify clean install matches lockfile | |
| run: npm ci --ignore-scripts | |
| - name: Verify clean server install matches lockfile | |
| run: npm ci --ignore-scripts | |
| working-directory: server | |
| report-audit-status: | |
| name: Report audit status | |
| runs-on: ubuntu-latest | |
| needs: audit | |
| if: always() | |
| steps: | |
| - name: Set audit commit status | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const state = '${{ needs.audit.result }}' === 'success' ? 'success' : 'failure'; | |
| const sha = context.payload.pull_request | |
| ? context.payload.pull_request.head.sha | |
| : context.sha; | |
| await github.rest.repos.createCommitStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| sha, | |
| state, | |
| context: 'audit', | |
| description: `Dependency audit ${state}`, | |
| target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` | |
| }); | |
| report-snyk-status: | |
| name: Report snyk status | |
| runs-on: ubuntu-latest | |
| needs: snyk | |
| if: always() | |
| steps: | |
| - name: Set snyk commit status | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| // A skipped scan is NOT a passing scan (finding M-18): this used to | |
| // map 'skipped' to 'success', so deleting the job, or any condition | |
| // that stopped it running, silently produced a green scan status. | |
| // The job now runs on every trigger and falls back to the committed | |
| // audit gate when no Snyk token exists, so anything other than | |
| // 'success' is a real failure. | |
| const result = '${{ needs.snyk.result }}'; | |
| const state = result === 'success' ? 'success' : 'failure'; | |
| const sha = context.payload.pull_request | |
| ? context.payload.pull_request.head.sha | |
| : context.sha; | |
| await github.rest.repos.createCommitStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| sha, | |
| state, | |
| context: 'snyk', | |
| description: `Snyk vulnerability scan ${result}`, | |
| target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` | |
| }); | |
| report-secret-scan-status: | |
| name: Report secret scan status | |
| runs-on: ubuntu-latest | |
| needs: secret-scan | |
| if: always() | |
| steps: | |
| - name: Set secret-scan commit status | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const result = '${{ needs['secret-scan'].result }}'; | |
| const state = result === 'success' ? 'success' : 'failure'; | |
| const sha = context.payload.pull_request | |
| ? context.payload.pull_request.head.sha | |
| : context.sha; | |
| await github.rest.repos.createCommitStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| sha, | |
| state, | |
| context: 'secret-scan', | |
| description: `Committed secret scan ${result}`, | |
| target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` | |
| }); |