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
225 changes: 225 additions & 0 deletions .github/workflows/vuln-remediation-prompt.md
Original file line number Diff line number Diff line change
@@ -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=<repo-name> --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 '<package-import-path>' --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 <package>@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 <package>
```

### 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 -- <manifest-file> <lockfile>`
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 <pr-number> --add-assignee <username>`

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
60 changes: 60 additions & 0 deletions .github/workflows/vuln-remediation.yml
Original file line number Diff line number Diff line change
@@ -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: '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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan failure silently produces false healthy report

High Severity

When socket scan create fails (bad credentials, network error, service outage, etc.), the || fallback writes {"healthy":true,"alerts":{}} to socket-report.json, making the workflow believe no vulnerabilities exist. Combined with 2>/dev/null suppressing all error output, scan failures are silently treated as a clean bill of health. This defeats the entire purpose of the vulnerability remediation workflow — real vulnerabilities go undetected and unremediated with no indication anything went wrong.

Fix in Cursor Fix in Web


- 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
Loading