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, then socket login.
  • The project's own package manager installed (npm, pnpm, Yarn, uv, Poetry, Gradle, dotnet, Cargo or Bundler). socket fix uses it to update the lockfile. Maven projects are fixed from the POM alone.
  • A token with fixes:list, full-scans:create and packages: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 fix

Nothing 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 changed

Use 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 tests

Pull requests opened with the workflow's built-in GITHUB_TOKEN don'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 as SOCKET_FIX_GITHUB_TOKEN. Without any GitHub token, socket fix falls 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

MeasureBar teams have set
Fix pull requests that merge cleanly50% or more
Reachable CVEs with a fix pull request open or mergedAgree at kickoff

Troubleshooting

SymptomCauseFix
Reported as no fix availableNo patched version has been publishedSee Patch what you can't upgrade
Reported as not applicableA parent package's version range rules out the fixed versionAdd --allow-overrides to write a scoped override, then test the parent package
A fix is skipped with --no-major-updatesThe only fix is a major version bumpDrop the flag for that CVE, or plan the upgrade
CI applies changes but opens no pull requestNo GitHub token in the jobSet SOCKET_CLI_GITHUB_TOKEN
Python, NuGet, Cargo or Ruby fix fails with a tool errorThe package manager rejected the upgrade, for example a newer Python requirementRead the reason in the output; it comes from your own tool

Reference


Did this page help you?