Fix what matters
Turn reachable CVEs into upgrade pull requests with socket fix, and measure how many merge cleanly.
socket fix works out the smallest set of upgrades that clears a vulnerability, including bumping a parent package when the fixed version needs it, then writes them to your manifest and lockfile. Run it locally to see what it does, then let CI open the pull requests.
Start with the CVEs reachability marked Reachable or Potentially reachable. Those are the ones worth a developer's time.
Before you start:
npm install -g socket, thensocket login.- The project's own package manager installed (npm, pnpm, Yarn, uv, Poetry, Gradle,
dotnet, Cargo or Bundler).socket fixuses it to update the lockfile. Maven projects are fixed from the POM alone. - A token with
fixes:list,full-scans:createandpackages:list, if you use one in CI. - A clean working tree, so you can review and undo the changes.
1. See what it would change
cd /path/to/your-app
socket fix --all --no-apply-fixes --output-file fixes.json
jq '.fixes | length' fixes.json # how many advisories it can fixNothing is written to the project except fixes.json. Under fixes, each GHSA ID lists the package (purl) and the version it would move to (fixedVersion). Anything it can't fix is reported with a reason: no patched version exists yet, or no upgrade path fits the versions your other dependencies allow.
2. Fix one CVE locally
Take a GHSA or CVE ID from a Reachable alert:
socket fix --id GHSA-xxxx-xxxx-xxxx
npm ci # socket fix updates the lockfiles, not node_modules
npm test
git diff # review exactly what changedUse npm ci, not npm install. socket fix also updates npm's hidden lockfile in node_modules, so npm install reports "up to date" and leaves the old version installed, and your tests pass against the vulnerable code.
If the tests pass, commit it and open a pull request as usual. Once it merges, the next default-branch scan no longer shows the alert.
3. Let CI open the pull requests
In CI mode, socket fix opens one pull request per vulnerability, on a branch named socket/fix/ followed by the GHSA ID. It skips vulnerabilities that already have an open Socket fix pull request, and opens at most 10 per run unless you change --pr-limit.
Add .github/workflows/socket-fix.yml:
name: socket-fix
on:
workflow_dispatch:
schedule:
- cron: '0 6 * * 1' # Mondays 06:00 UTC
permissions:
contents: write
pull-requests: write
jobs:
socket-fix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm ci
- name: Run Socket Fix
env:
SOCKET_CLI_API_TOKEN: ${{ secrets.SOCKET_CLI_API_TOKEN }}
SOCKET_CLI_GITHUB_TOKEN: ${{ secrets.SOCKET_FIX_GITHUB_TOKEN }}
run: npx --yes socket fix --pr-limit 5 --minimum-release-age 1w--minimum-release-age 1w only suggests versions that have been public for a week, which keeps freshly published, possibly malicious releases out of your fix pull requests.
Use a token that triggers your testsPull requests opened with the workflow's built-in
GITHUB_TOKENdon't start your other workflows, so your tests wouldn't run on them. Store a fine-grained personal access token or a GitHub App token with Contents and Pull requests write access asSOCKET_FIX_GITHUB_TOKEN. Without any GitHub token,socket fixfalls back to local mode and opens nothing.
Once you trust the results, --autopilot turns on auto-merge for the pull requests it opens, so they merge when every required check passes. The repository must allow auto-merge.
4. Measure the merge rate
After a week or two, count how the fix pull requests ended up:
gh pr list --state all --limit 500 --json headRefName,state \
| jq '[.[] | select(.headRefName | startswith("socket/fix/"))]
| group_by(.state) | map({state: .[0].state, count: length})'Merged divided by merged plus closed is the merge rate. Leave open ones out until they're decided.
What good looks like
| Measure | Bar teams have set |
|---|---|
| Fix pull requests that merge cleanly | 50% or more |
| Reachable CVEs with a fix pull request open or merged | Agree at kickoff |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Reported as no fix available | No patched version has been published | See Patch what you can't upgrade |
| Reported as not applicable | A parent package's version range rules out the fixed version | Add --allow-overrides to write a scoped override, then test the parent package |
A fix is skipped with --no-major-updates | The only fix is a major version bump | Drop the flag for that CVE, or plan the upgrade |
| CI applies changes but opens no pull request | No GitHub token in the job | Set SOCKET_CLI_GITHUB_TOKEN |
| Python, NuGet, Cargo or Ruby fix fails with a tool error | The package manager rejected the upgrade, for example a newer Python requirement | Read the reason in the output; it comes from your own tool |
Reference
Updated 8 days ago