Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 104 additions & 2 deletions .github/workflows/snyk.yml
Original file line number Diff line number Diff line change
@@ -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 }}
Expand All @@ -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:
Expand Down Expand Up @@ -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 }}
Expand All @@ -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 = '<!-- snyk-vulnerability-alert -->';

// 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 <vulnerable-package>`,
` 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}`);
}
1 change: 1 addition & 0 deletions .snyk
Original file line number Diff line number Diff line change
Expand Up @@ -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: {}
Expand Down
12 changes: 12 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

163 changes: 163 additions & 0 deletions docs/DEPENDENCY_REVIEW.md
Original file line number Diff line number Diff line change
@@ -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 <package-name>

# 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 <package-name> to resolve <CVE-ID> (#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`.
Loading