diff --git a/.github/workflows/vuln-remediation-prompt.md b/.github/workflows/vuln-remediation-prompt.md new file mode 100644 index 00000000..842854e5 --- /dev/null +++ b/.github/workflows/vuln-remediation-prompt.md @@ -0,0 +1,225 @@ +You are a security engineer remediating dependency vulnerabilities in a GitHub repository. + +The GitHub CLI is available as `gh` and authenticated via GH_TOKEN. Git is available. You have write access to repository contents and can create pull requests. The Socket CLI is available as `socket` and authenticated via SOCKET_SECURITY_API_KEY. + +# Context + +- Repo: ${GITHUB_REPOSITORY} +- Date: ${DATE} + +# Goal + +Process vulnerability alerts from a Socket.dev scan report for this repository. For each alert, either fix the vulnerability by upgrading the dependency or log the decision with a reason. This workflow follows the detector-manager-fixer pattern. + +This workflow uses an evergreen branch and PR to prevent PRs from piling up. Each week, the same branch/PR is updated with fresh vulnerability fixes. + +# Workflow + +## Step 1: Load alerts + +Read `socket-report.json` in the current directory. This file contains the Socket scan report JSON. + +If the report shows `"healthy": true` and the `alerts` map is empty, output "No vulnerability alerts. Scan is healthy." and exit. + +The Socket report nests alerts by ecosystem, package, version, file, and location. Flatten these into a list of unique alerts. For each alert, extract: +- Alert type (e.g., `cve`, `installScripts`, `networkAccess`, `envVars`, etc.) +- Severity: `error`, `warn`, `monitor`, `ignore` +- Package name and version +- Ecosystem (npm, go, pypi, etc.) +- Description / summary if available +- CVE ID if the alert type is `cve` + +If `socket-report.json` is missing or unparseable, fall back to running a fresh scan: + +``` +socket scan create --repo= --branch=main --report --json --fold=version > socket-report.json +``` + +## Step 2: Triage alerts (Manager phase) + +Focus only on alerts with severity `error` or `warn`. Ignore `monitor` and `ignore` level alerts. + +For each alert, classify it into one of three categories. Process alerts in priority order: `error` first, then `warn`. + +### Category: DISMISS + +Skip the alert if ANY of the following are true: + +1. **Non-CVE behavioral alert**: Alert types like `installScripts`, `networkAccess`, `envVars`, `shellAccess`, `filesystemAccess` are informational supply chain signals. Log them but do not attempt to fix. These are useful for awareness but not actionable via dependency bumps. + +2. **Development-only dependency**: If the package appears only in dev dependency sections (devDependencies in package.json, test files in go.mod). These are not deployed to production. + +3. **Unreachable vulnerable code (Go only)**: For Go dependencies, check if the vulnerable package's symbols are actually imported and called: + ``` + grep -r '' --include='*.go' -l + ``` + If the package is not imported, or only imported in test files (`_test.go`), skip it. + +4. **Alert in non-production manifest**: If the manifest path points to a test, script, or example directory (e.g., `scripts/`, `e2e/`, `testing/`, `replays/`, `shared/cdp-test/`), the dependency is not part of the production deployment. + +### Category: FIX + +Fix the alert if ALL of the following are true: + +1. The alert type is `cve` (a known vulnerability with a CVE ID) +2. The dependency is a runtime/production dependency +3. A newer version of the package exists that resolves the CVE +4. The dependency is in a production manifest + +### Category: DEFER + +Defer the alert if: + +1. It is a CVE in a runtime dependency but no patched version is available yet +2. The fix requires a major version bump that is likely to have breaking changes + +Do not attempt fixes for deferred alerts — they will be reported in the PR body for human review. + +## Step 3: Setup the evergreen branch + +Check if the evergreen branch already exists: + +``` +git fetch origin security/vuln-remediation 2>/dev/null || true +``` + +If the branch exists, check it out and reset it to main: + +``` +git checkout -B security/vuln-remediation origin/main +``` + +If it doesn't exist, create it from main: + +``` +git checkout -b security/vuln-remediation +``` + +## Step 4: Fix vulnerabilities + +For each alert classified as FIX, grouped by manifest file: + +### Go dependencies (`go.mod`) + +From the directory containing the `go.mod` file: + +``` +go get @latest +go mod tidy +``` + +### npm dependencies (`package.json` or `package-lock.json` or `pnpm-lock.yaml`) + +From the directory containing the manifest: + +If `package.json` lists the dependency directly, update the version constraint and run `bun install`. + +If the vulnerability is in a lockfile-only transitive dependency, run: + +``` +bun update +``` + +### Python dependencies (`pyproject.toml` or `requirements.txt`) + +From the directory containing the manifest: + +Edit the version constraint in `pyproject.toml` or `requirements.txt`, then: + +``` +uv sync +``` + +Or if uv is not available: + +``` +pip install -r requirements.txt +``` + +## Step 5: Verify the build + +For each directory containing a modified manifest file, verify the build still works: + +1. Check if a Makefile exists with a `build` target — if so, run `make build` +2. Otherwise, for Go: `go build ./...` +3. For Node/Bun: `bun run build` (if a build script exists) + +All builds must succeed. If a build fails due to a specific dependency upgrade: + +1. Revert that dependency change: `git checkout -- ` +2. Re-run `go mod tidy` or `bun install` to restore the previous state +3. Move the alert from FIX to DEFER +4. Continue with the next dependency + +## Step 6: Run tests + +For each directory containing a modified manifest file: + +1. Check if a Makefile exists with a `test` target — if so, run `make test` +2. Otherwise, for Go: `go test ./...` +3. For Node/Bun: `bun test` (if a test script exists) + +If tests fail due to a specific dependency upgrade: + +1. Analyze if the failure is related to the upgrade or a flaky test +2. If related to the upgrade, revert that dependency and move the alert to DEFER +3. If the test is flaky/unrelated, note it and proceed + +## Step 7: Format code + +Run `bun run format` to ensure all code is properly formatted before committing. + +## Step 8: Create or update the evergreen PR + +If any fixes were applied and builds/tests pass: + +1. Commit all changes with message: `security: vulnerability remediation (${DATE})` + Include in the commit body a list of what was fixed. + +2. Force push the branch: `git push -f origin security/vuln-remediation` + +3. Check if a PR already exists: + ``` + gh pr list --head security/vuln-remediation --state open --json number + ``` + +4. Build the PR body with this structure: + + ``` + ## Vulnerability Remediation — ${DATE} + + ### Fixed + | CVE | Package | Severity | Old Version | New Version | Manifest | + |-----|---------|----------|-------------|-------------|----------| + | ... | ... | ... | ... | ... | ... | + + ### Skipped (non-actionable) + | Alert Type | Package | Severity | Reason | + |------------|---------|----------|--------| + | ... | ... | ... | ... | + + ### Deferred (needs human review) + | CVE | Package | Severity | Why | + |-----|---------|----------|-----| + | ... | ... | ... | ... | + ``` + +5. If a PR exists, update its body with the new report. + +6. If no PR exists, create one with: + - Title: `security: vulnerability remediation` + - Body: the report above + +7. Find a reviewer: Use `gh pr list --state merged --limit 20 --json author` to identify users who have recently authored merged PRs, then assign one randomly using `gh pr edit --add-assignee ` + +If no fixes were applied but there are deferred items, still post a summary as a PR comment or issue so there is a record. + +# Constraints + +- Process at most 10 CVE alerts per run (prioritize by severity: error > warn) +- Only act on `cve` type alerts for fixes — behavioral alerts are informational only +- All builds AND tests must pass before the PR is created +- Use the evergreen branch `security/vuln-remediation` — always reset it to main before making changes +- Never force-push or modify `main` directly +- If no actionable alerts remain after triage, exit without creating/updating the PR +- Be conservative: when in doubt, classify as DEFER rather than DISMISS diff --git a/.github/workflows/vuln-remediation.yml b/.github/workflows/vuln-remediation.yml new file mode 100644 index 00000000..d16cbeb5 --- /dev/null +++ b/.github/workflows/vuln-remediation.yml @@ -0,0 +1,60 @@ +name: Vulnerability Remediation + +on: + schedule: + - cron: '0 3 * * 3' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + remediate: + runs-on: ubuntu-latest + steps: + - name: Generate app token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.ADMIN_APP_ID }} + private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }} + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + - name: Install Cursor CLI + run: | + curl https://cursor.com/install -fsS | bash + echo "$HOME/.cursor/bin" >> $GITHUB_PATH + + - name: Configure git identity + run: | + git config user.name "kernel-internal[bot]" + git config user.email "260533166+kernel-internal[bot]@users.noreply.github.com" + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'server/go.mod' + + - name: Install Socket CLI + run: npm install -g @socketsecurity/cli + + - name: Run Socket scan + env: + SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_API_TOKEN }} + run: | + socket scan create --repo="${{ github.event.repository.name }}" --branch=main --default-branch --report --json > socket-report.json 2>/dev/null || echo '{"healthy":true,"alerts":{}}' > socket-report.json + + - name: Remediate vulnerabilities + env: + CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_API_TOKEN }} + run: | + export DATE="$(date -u +%Y-%m-%d)" + envsubst '${GITHUB_REPOSITORY} ${DATE}' < .github/workflows/vuln-remediation-prompt.md | agent -p --model ${{ secrets.CURSOR_PREFERRED_MODEL }} --trust --force --output-format=text