diff --git a/.github/workflows/security-snyk.yml b/.github/workflows/security-snyk.yml new file mode 100644 index 0000000000..67241ddc34 --- /dev/null +++ b/.github/workflows/security-snyk.yml @@ -0,0 +1,96 @@ +name: Snyk security + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + actions: read # Required by upload-sarif for private repositories. + security-events: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + snyk: + name: Snyk scans + runs-on: ubuntu-latest + env: + SNYK_TOKEN_PRESENT: ${{ secrets.SNYK_TOKEN != '' }} + steps: + - name: Skip when SNYK_TOKEN is unavailable + if: env.SNYK_TOKEN_PRESENT != 'true' + run: | + echo "SNYK_TOKEN is unavailable; skipping Snyk scans." + echo "This is expected for fork PRs, Dependabot PRs, and pre-rollout runs before the secret is added." + + - name: Checkout + if: env.SNYK_TOKEN_PRESENT == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Snyk + if: env.SNYK_TOKEN_PRESENT == 'true' + uses: snyk/actions/setup@8e119fbb6c251787721d34ba683ed48eba792766 # master + + - name: Snyk Code scan + if: env.SNYK_TOKEN_PRESENT == 'true' + id: snyk-code + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + run: | + snyk code test . \ + --severity-threshold=high \ + --sarif-file-output=snyk-code.sarif + + - name: Warn when Snyk Code SARIF is missing + if: env.SNYK_TOKEN_PRESENT == 'true' && hashFiles('snyk-code.sarif') == '' + run: | + echo "::warning::Snyk Code did not produce snyk-code.sarif; check the scan step logs before relying on SAST findings." + + - name: Upload Snyk Code SARIF + if: env.SNYK_TOKEN_PRESENT == 'true' && hashFiles('snyk-code.sarif') != '' + uses: github/codeql-action/upload-sarif@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3 + with: + sarif_file: snyk-code.sarif + category: snyk-code-swift + + - name: Setup Bun + if: env.SNYK_TOKEN_PRESENT == 'true' + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + + - name: Install web dependencies + if: env.SNYK_TOKEN_PRESENT == 'true' + working-directory: web + run: bun install --frozen-lockfile + + - name: Snyk Open Source scan + if: env.SNYK_TOKEN_PRESENT == 'true' + id: snyk-oss + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + run: | + snyk test \ + --file=web/package.json \ + --package-manager=npm \ + --severity-threshold=high \ + --sarif-file-output=snyk-oss.sarif + + - name: Warn when Snyk Open Source SARIF is missing + if: env.SNYK_TOKEN_PRESENT == 'true' && hashFiles('snyk-oss.sarif') == '' + run: | + echo "::warning::Snyk Open Source did not produce snyk-oss.sarif; check the scan step logs before relying on OSS findings." + + - name: Upload Snyk Open Source SARIF + if: env.SNYK_TOKEN_PRESENT == 'true' && hashFiles('snyk-oss.sarif') != '' + uses: github/codeql-action/upload-sarif@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3 + with: + sarif_file: snyk-oss.sarif + category: snyk-open-source-web-npm diff --git a/.snyk b/.snyk new file mode 100644 index 0000000000..808ef82f86 --- /dev/null +++ b/.snyk @@ -0,0 +1,33 @@ +# Snyk (https://snyk.io) policy file. +# +# CI rollout default: report high and critical findings. The threshold is +# enforced in .github/workflows/security-snyk.yml with --severity-threshold=high. +# +# Code SAST is intentionally scoped to cmux Swift sources for v1. SCA remains +# scoped by command line to web/package.json. +version: v1.25.0 + +exclude: + code: + - .github/** + - cmuxd/** + - daemon/** + - docs/** + - dogfood/** + - Examples/** + - ghostty/** + - homebrew-cmux/** + - Prototypes/** + - Resources/** + - scripts/** + - tests/** + - tests_v2/** + - vendor/** + - web/** + +# Add temporary ignores with: +# snyk ignore --id= --expiry=YYYY-MM-DD --reason="..." +# Keep every ignore narrow, justified, and expiring. +ignore: {} + +patch: {} diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 0000000000..8a5c6b9676 --- /dev/null +++ b/docs/security.md @@ -0,0 +1,30 @@ +# Security scanning + +cmux uses the `Snyk security` GitHub Actions workflow for rollout-safe security review on pull requests and pushes to `main`. + +## What runs + +- Snyk Code runs SAST against cmux Swift sources at `high` severity and above. +- Snyk Open Source runs SCA against the docs site manifest at `web/package.json` at `high` severity and above. + +The v1 SCA scope is npm only. The workflow runs `bun install --frozen-lockfile` first, then tells Snyk to treat `web/package.json` as an npm project. Until Snyk supports `bun.lock` directly, Snyk resolves dependencies from the installed `web/node_modules` snapshot rather than parsing the Bun lockfile itself. + +Swift Package Manager dependencies are resolved through `GhosttyTabs.xcodeproj/project.pbxproj` instead of a top-level `Package.swift` or `Package.resolved`, so SPM SCA needs a follow-up design before enabling. + +## Where findings appear + +The workflow writes SARIF for each scanner and uploads it with `github/codeql-action/upload-sarif`. GitHub shows those results in code scanning, the repository Security tab, and inline PR annotations. After the Snyk GitHub App is installed for the `manaflow-ai/cmux` repository, Snyk App comments may also appear on PRs. + +## Fork PR behavior + +The workflow does not run Snyk scans unless `SNYK_TOKEN` is available. Fork PRs, Dependabot PRs, and the initial rollout period before the secret is added produce a clean skip instead of a red CI result. + +## Ignores + +Use `.snyk` for reviewed, temporary ignores. Prefer fixing the finding first. If an ignore is necessary, add it with an expiry and a concrete reason: + +```bash +snyk ignore --id= --expiry=YYYY-MM-DD --reason="why this is safe until the expiry" +``` + +Keep ignores narrow, expiring, and reviewed in the PR that adds them.