diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index bec3e0860..0d1c70121 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -1,21 +1,31 @@ name: 🔒 Snyk -# Dependency vulnerability scanning. +# Dependency vulnerability scanning and automated triage alerting. # # Runs as its own workflow rather than a job inside CI, matching how this repo # already separates concerns (lint.yml, typecheck.yml, main.yml, chromatic.yml # are each one file). Concretely it needs a different trigger gate, a different # secret, and has different failure semantics from a build — folding it into # main.yml would mean a security finding reads as "the build broke". +# +# In addition to push/PR events, this runs on a recurring weekly schedule +# (Mondays at 06:00 UTC) to catch newly disclosed CVEs even when no new code +# has been merged. When high/critical vulnerabilities are detected on main, +# it automatically creates or updates a tracking issue for maintainer triage. on: push: branches: [main] pull_request: branches: [main] + schedule: + # Recurring weekly scan: Every Monday at 06:00 UTC (matches Dependabot's weekly schedule) + - cron: '0 6 * * 1' + workflow_dispatch: permissions: contents: read + issues: write concurrency: group: snyk-${{ github.ref }} @@ -30,9 +40,11 @@ jobs: # almost every contribution here comes from one. Without this gate SNYK_TOKEN # would be empty on those runs and the job would fail red on contributor PRs # through no fault of theirs — the same trap the Vercel check falls into. - # main still gets scanned on every push. + # main still gets scanned on every push, weekly schedule, and manual dispatch. if: >- github.event_name == 'push' || + github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository steps: @@ -60,6 +72,7 @@ jobs: run: npm install --package-lock-only --ignore-scripts - name: Run Snyk + id: snyk uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} @@ -68,3 +81,92 @@ jobs: # a wall of low-severity transitive noise. Lower it once the backlog # is clean. args: --severity-threshold=high --file=package-lock.json + + - name: Alert on High/Critical Snyk Findings + if: failure() && steps.snyk.outcome == 'failure' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch') + uses: actions/github-script@v9 + env: + HAS_SNYK_TOKEN: ${{ secrets.SNYK_TOKEN != '' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + if (process.env.HAS_SNYK_TOKEN !== 'true') { + console.log('SNYK_TOKEN secret is not set; skipping automated vulnerability issue creation.'); + return; + } + + const owner = context.repo.owner; + const repo = context.repo.repo; + const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`; + const commitSha = context.sha ? context.sha.substring(0, 7) : 'unknown'; + const MARKER = ''; + + // Check if an open alert issue already exists to avoid spamming duplicates + const { data: issues } = await github.rest.issues.listForRepo({ + owner, + repo, + state: 'open', + labels: 'security', + per_page: 50, + }); + + const existingIssue = issues.find(i => i.body && i.body.includes(MARKER)); + + if (existingIssue) { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: existingIssue.number, + body: [ + `### ⚠️ Recurring Snyk Scan Alert Update`, + ``, + `The automated Snyk scan on \`main\` detected active high/critical vulnerabilities.`, + ``, + `- **Trigger:** \`${context.eventName}\``, + `- **Commit:** \`${commitSha}\``, + `- **Workflow Run:** [View Snyk Logs](${runUrl})`, + ``, + `Please follow the triage and remediation guide in [docs/DEPENDENCY_REVIEW.md](https://github.com/${owner}/${repo}/blob/main/docs/DEPENDENCY_REVIEW.md).`, + ].join('\n'), + }); + console.log(`Updated existing security tracking issue #${existingIssue.number}`); + } else { + const newIssue = await github.rest.issues.create({ + owner, + repo, + title: '🔒 [Security Alert] High/Critical vulnerabilities detected by Snyk', + labels: ['security', 'dependencies', 'type:security', 'level:intermediate'], + body: [ + MARKER, + `## 🔒 Snyk Security Scan Alert`, + ``, + `The recurring Snyk dependency scan on \`main\` has detected one or more **high** or **critical** severity vulnerabilities.`, + ``, + `### Scan Context`, + `- **Trigger Event:** \`${context.eventName}\``, + `- **Commit:** \`${commitSha}\``, + `- **Run Details:** [View Workflow Run & Vulnerability Logs](${runUrl})`, + `- **Severity Threshold:** \`high\` / \`critical\``, + ``, + `### Triage Expectations & SLA`, + `- **Critical Findings:** Triage within **48 hours**; resolve/mitigate within **7 days**.`, + `- **High Findings:** Triage within **5 business days**; resolve/mitigate within **14 days**.`, + ``, + `### Action Checklist for Maintainers & Contributors`, + `1. **Inspect Findings:** Review the [Workflow Run](${runUrl}) logs to identify the vulnerable packages and CVE details.`, + `2. **Check Dependabot Overlap:** Review open [Dependabot PRs](https://github.com/${owner}/${repo}/pulls?q=is%3Apr+is%3Aopen+label%3Adependencies) to see if an automated update PR already exists for the flagged package.`, + `3. **Local Reproduction & Fix:**`, + ` \`\`\`bash`, + ` npm run security # generates temporary package-lock.json and runs snyk test`, + ` bun update `, + ` bun test`, + ` bun run build`, + ` \`\`\``, + ` > **Note:** Never commit \`package-lock.json\`. Keep \`bun.lock\` as the single source of truth.`, + `4. **Unreachable / Dev-Only Exceptions:** If a vulnerability is confirmed non-exploitable (e.g. dev-only build tool with no client-side runtime path), document the exception with a clear rationale and expiry date in [\`.snyk\`](https://github.com/${owner}/${repo}/blob/main/.snyk).`, + ``, + `📖 Process details: [docs/DEPENDENCY_REVIEW.md](https://github.com/${owner}/${repo}/blob/main/docs/DEPENDENCY_REVIEW.md)`, + ].join('\n'), + }); + console.log(`Created new security tracking issue #${newIssue.data.number}`); + } diff --git a/.snyk b/.snyk index 4a92b4e8f..ac66b40b3 100644 --- a/.snyk +++ b/.snyk @@ -12,6 +12,7 @@ # reason: Not reachable — dev-only transitive dep, no runtime path # expires: 2026-12-31T00:00:00.000Z # +# See docs/DEPENDENCY_REVIEW.md for the full triage and exception procedure. # `patch` is unused: this project has no Snyk-patchable dependencies. version: v1.25.0 ignore: {} diff --git a/SECURITY.md b/SECURITY.md index f7967c47b..9775e84f8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -46,3 +46,15 @@ Reframe processes all video files **100% client-side** using FFmpeg.wasm. Your f ## Disclosure Policy We follow **responsible disclosure**. Please give us reasonable time to address the issue before any public disclosure. We aim to resolve critical vulnerabilities within **14 days** of confirmation. + +## Dependency Security & Recurring Review Cadence + +Reframe uses automated **Snyk** scanning and **Dependabot** to continuously inspect and update third-party dependencies: + +- **Weekly Recurring Scans:** Snyk scans `main` every Monday at 06:00 UTC and on every push to detect high and critical severity CVEs. +- **Weekly Dependabot Updates:** Dependabot checks and proposes dependency updates weekly on Mondays. +- **Triage Cadence & Response SLA:** + - **Critical findings:** Triaged within **48 hours**; resolved or mitigated within **7 days**. + - **High findings:** Triaged within **5 business days**; resolved or mitigated within **14 days**. +- **Process Guide:** Full procedures for local scans, checking Dependabot overlap, lockfile rules, and `.snyk` exception policies are documented in [docs/DEPENDENCY_REVIEW.md](docs/DEPENDENCY_REVIEW.md). + diff --git a/docs/DEPENDENCY_REVIEW.md b/docs/DEPENDENCY_REVIEW.md new file mode 100644 index 000000000..bfb1c1e25 --- /dev/null +++ b/docs/DEPENDENCY_REVIEW.md @@ -0,0 +1,163 @@ +# Dependency Review & Vulnerability Triage Cadence + +This document establishes the recurring dependency update and vulnerability review process for Reframe, resolving [#1790](https://github.com/reframe-oss/reframe/issues/1790). + +--- + +## 1. Overview & Purpose + +Reframe processes video client-side using FFmpeg.wasm, meaning code security and dependency supply chain integrity are essential. + +Automated security scans in CI can easily become background noise if there is no defined response process when scans report vulnerabilities. This cadence establishes a proactive, human-in-the-loop review rhythm combining: +- **Continuous & Recurring Snyk Scanning:** Detecting high and critical CVEs across direct and transitive dependencies. +- **Weekly Dependabot Automated Updates:** Keeping npm packages and GitHub Actions up to date. +- **Automated Issue Alerting:** Automatically raising or updating a tracking issue when Snyk detects high/critical vulnerabilities on `main`. +- **Defined Triage SLAs:** Clear response timelines and resolution procedures so vulnerabilities are triaged rather than ignored. + +--- + +## 2. Review Cadence & Architecture + +```mermaid +graph TD + A["Recurring Triggers"] --> B["Weekly Snyk Scan (Mondays 06:00 UTC)"] + A --> C["Push to main"] + A --> D["Weekly Dependabot (Mondays)"] + + B --> E{"Snyk finds High/Critical CVEs?"} + C --> E + + E -- "Yes" --> F["Automated GitHub Action: Opens or Updates Tracking Issue"] + E -- "No" --> G["Clean CI Badge / Green Status"] + + F --> H["Triage within SLA (48h Critical / 5d High)"] + D --> I["Check Dependabot PRs for overlapping CVE fixes"] + I --> H + H --> J["Remediation via bun update or .snyk Policy Exception"] + J --> K["Verify with bun test & next build"] + K --> L["PR Merged to main & Alert Issue Closed"] +``` + +### Automation Components + +| Tool | Frequency | Target | Action on Finding | +|---|---|---|---| +| **Snyk Scan** (`.github/workflows/snyk.yml`) | Every Monday at 06:00 UTC & on push to `main` | Production & transitive dependencies (`package-lock.json` generated from `package.json`) | Fails CI and automatically opens or updates a GitHub issue labeled `security` & `dependencies`. | +| **Dependabot** (`.github/dependabot.yml`) | Every Monday | `npm` packages & `github-actions` | Opens up to 5 automated update PRs. | +| **Manual Scan** (`npm run security`) | On demand | Local developer workspace | Outputs detailed vulnerability breakdown and path traces. | + +--- + +## 3. Triage SLAs & Response Timelines + +When a dependency vulnerability is detected, the following response windows apply: + +| Severity | CVSS Score | Initial Triage Window | Remediation / Mitigation Window | +|---|---|---|---| +| **Critical** | 9.0 – 10.0 | Within **48 hours** | Within **7 calendar days** | +| **High** | 7.0 – 8.9 | Within **5 business days** | Within **14 calendar days** | +| **Moderate / Low** | 0.1 – 6.9 | Reviewed weekly during regular Dependabot cycles | Next planned dependency release | + +- **Initial Triage:** A maintainer or assigned contributor reviews the alert, confirms whether the vulnerability is reachable/exploitable, and identifies whether a patch is available. +- **Remediation:** Merging an updated version or applying a documented `.snyk` exception. + +--- + +## 4. Step-by-Step Triage & Remediation Workflow + +### Step 1: Inspect the Alert + +When Snyk detects vulnerabilities on `main`: +1. An issue titled `🔒 [Security Alert] High/Critical vulnerabilities detected by Snyk` is created or commented on automatically. +2. Open the linked workflow run log to inspect: + - Affected package name and installed version + - Vulnerability CVE / Snyk identifier + - Vulnerability path (e.g., `reframe > next > @next/env > postcss`) + - Whether a fixed version is available + +### Step 2: Cross-Reference Dependabot PRs for Overlap + +Before manually updating dependencies, check open [Dependabot PRs](https://github.com/reframe-oss/reframe/pulls?q=is%3Apr+is%3Aopen+label%3Adependencies): +- Dependabot often opens an automated bump PR for the exact vulnerable package on Monday mornings. +- If a Dependabot PR addresses the flagged package: + 1. Review the PR's CI checks and changelog. + 2. Test locally: `bun test` and `bun run build`. + 3. Merge the Dependabot PR. Snyk will re-scan `main` automatically upon merge. + +### Step 3: Local Reproduction + +To run the exact Snyk scan locally: + +```bash +# Ensure SNYK_TOKEN is in your environment if scanning authenticated: +export SNYK_TOKEN="your-token" # or set in PowerShell: $env:SNYK_TOKEN="your-token" + +# Run the project security script: +npm run security +``` + +> [!IMPORTANT] +> **Lockfile Rule:** Reframe uses `bun.lock` as the single source of truth. The `npm run security` script temporarily creates `package-lock.json` because Snyk's parser requires npm/yarn format. **Never commit `package-lock.json`** to git. + +### Step 4: Patching with Bun + +If updating manually: + +```bash +# 1. Update the target package using bun +bun update + +# 2. Run all tests to ensure no regressions +bun test + +# 3. Verify production build +bun run build + +# 4. Run linter +bun run lint + +# 5. Verify the vulnerability is cleared +npm run security +``` + +Submit a PR with conventional commit format: +``` +fix(security): bump to resolve (#1790) +``` + +--- + +## 5. False Positives & Dev-Only Exceptions (`.snyk`) + +If a vulnerability has no upstream fix available or is provably non-exploitable (for example, a dev-dependency tool with no client-side runtime path in our static export): + +1. **Criteria for an exception:** + - The affected code path is **not reachable** in the client-side browser bundle. + - No upstream patch exists yet, or an immediate upgrade would introduce catastrophic breaking changes. +2. **Documenting the exception in `.snyk`:** + Add the finding to the `ignore` section in `.snyk`. Every exception **must** include a clear rationale and an expiration date (maximum 90 days): + +```yaml +version: v1.25.0 +ignore: + SNYK-JS-EXAMPLE-123456: + - '*': + reason: 'Unreachable: dev-only build dependency with no client-side runtime execution in static export.' + expires: 2026-12-31T00:00:00.000Z +patch: {} +``` + +> [!WARNING] +> Never add an ignore rule without an expiration date. Expired ignores ensure that newly released upstream fixes are promptly revisited rather than forgotten. + +--- + +## 6. Roles & Responsibilities + +- **Maintainers:** + - Monitor automated Snyk alert issues and assign triage owners within the SLA window. + - Review and merge weekly Dependabot PRs. + - Review any proposed `.snyk` ignore exceptions. +- **Contributors & GSSoC'26 Participants:** + - Can request assignment on open Snyk vulnerability alert issues to research fixes or upgrade dependencies. + - Follow the testing and lockfile standards in `CLAUDE.md`.