Skip to content

deps(server): bump fast-uri (#232) #574

deps(server): bump fast-uri (#232)

deps(server): bump fast-uri (#232) #574

Workflow file for this run

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}`
});