Skip to content

Instantly share code, notes, and snippets.

@tieubao
Last active May 15, 2026 10:56
Show Gist options
  • Select an option

  • Save tieubao/1732a4285d558ffcc1a1277918bef8b5 to your computer and use it in GitHub Desktop.

Select an option

Save tieubao/1732a4285d558ffcc1a1277918bef8b5 to your computer and use it in GitHub Desktop.
Claude Code skill: drop a CVE/GHSA/exploit link and it sweeps your repos, triages by deployment status, and patches the vulnerable ones with per-repo confirmation.
name patch-exploit
description Use when the user shares a vulnerability, exploit, or security advisory (CVE link, GHSA, X/Twitter post, vendor blog, `npm audit` output) and wants their repos audited and patched. Trigger phrases include "there's an exploit at <link>", "patch this CVE", "audit for <vuln>", "is X vulnerable to <CVE>?", "we got hit by <CVE>", any pasted CVE-ID / GHSA-ID / security advisory URL. Parses the advisory (npm / pip / uv / go / cargo / gem / cf-worker), sweeps the configured workspace for matching packages, triages by deployment status, then patches vulnerable+deployed repos one-by-one with per-repo confirmation, verification, and a `fix/<slug>` commit. NOT for: zero-context "look for vulns in my code" (that's a generic audit, no advisory anchor); host-OS CVEs (kernel, openssh, macOS); CVEs in client repos owned by other engineers.

patch-exploit

A Claude Code skill: when the user drops a vulnerability link or CVE ID, this skill audits their repos and patches the vulnerable ones with per-repo confirmation.

Install

  1. Save this file as ~/.claude/skills/patch-exploit/SKILL.md.
  2. Set WORKSPACE_ROOT to where your repos live (defaults to ~/workspace/).
  3. Optional: set PATCH_EXPLOIT_LOG to a markdown file path if you want each run logged (one prepended entry per run). Leave unset to skip logging.

The skill is invoked automatically by Claude Code when the user types a trigger phrase or pastes a CVE/GHSA URL. No slash command needed.

Activation

Fire on any of:

  • A URL pointing at: x.com / twitter.com (security researcher post), github.com/advisories/GHSA-*, nvd.nist.gov, cve.org, vendor security blogs (vercel.com/security, nodejs.org/security, etc.).
  • A bare CVE-ID (CVE-YYYY-NNNNN) or GHSA-ID (GHSA-xxxx-xxxx-xxxx).
  • Pasted npm audit / pnpm audit / pip-audit / cargo audit / govulncheck output.
  • The phrases: "there's an exploit", "patch this CVE", "audit for ", "is X vulnerable to ?", "we got hit by ".

Do not fire when the user is asking a general-knowledge question about a CVE (no audit/patch ask), or when the advisory is for the host OS / kernel / sshd (host-side patching is not in scope; surface "this is a host patch, not a repo patch" instead).

Workflow

1. Parse the advisory

Extract these fields. Refuse to proceed if affected package + vulnerable range + fix version cannot all be pinned. Ask the user to clarify rather than guessing.

Field How to get it
Affected package(s) Advisory body
Ecosystem npm, pip/uv, go, cargo, gem, cf-worker-binding, etc.
Vulnerable version range e.g. >=13.4.13 <15.5.16 || >=16.0.0 <16.2.5
Fix version(s) The lowest patched release per branch
Attack vector One-line summary (RCE, SSRF, prototype pollution, etc.)
Hosting caveat Self-hosted only? Browser-side only? Requires enabled?
CVSS / severity If listed

Source-specific fetch:

  • x.com / twitter.com: curl -s https://api.fxtwitter.com/<user>/status/<id> | jq '.tweet'. The site itself returns a 302 to a login wall; fxtwitter exposes the tweet body as JSON. Mirror services like vxtwitter / supadata can lag or fail.
  • GitHub Security Advisory: gh api /advisories/GHSA-xxxx-xxxx-xxxx returns JSON with vulnerabilities[].vulnerable_version_range and vulnerabilities[].first_patched_version.identifier.
  • NVD / cve.org: WebFetch the page, then ask the user to confirm the version range if the page is ambiguous (NVD often is).
  • Vendor blog: WebFetch.

2. Sweep the workspace

Default scope: $WORKSPACE_ROOT (or ~/workspace/ if unset). The user can override at invocation time ("just this repo", "include ~/clients/", etc.).

Manifest files per ecosystem:

Ecosystem Declared Locked
npm/pnpm/yarn package.json pnpm-lock.yaml / package-lock.json / yarn.lock
Python (uv/pip/poetry) pyproject.toml uv.lock / requirements.txt (frozen) / poetry.lock
Go go.mod go.sum
Rust Cargo.toml Cargo.lock
Ruby Gemfile Gemfile.lock
Cloudflare Worker binding wrangler.toml compatibility_date n/a

Scan command template (npm example):

cd "$WORKSPACE_ROOT" && find . -name 'package.json' \
  -not -path '*/node_modules/*' \
  -not -path '*/.next/*' \
  -not -path '*/dist/*' \
  -not -path '*/build/*' 2>/dev/null

For each manifest hit, read:

  1. Declared version (jq '.dependencies."<pkg>" // .devDependencies."<pkg>"' for npm).
  2. Locked version from the lockfile (regex on pnpm-lock.yaml, jq on package-lock.json, etc.). Locked wins; declared is just the constraint.
  3. Deployment evidence: vercel.json, wrangler.toml, Dockerfile, fly.toml, .vercel/, netlify.toml, a deploy job in .github/workflows/*.yml, a LaunchAgent/Daemon plist under tools/*/deploy/, a systemd unit. Absence of all = local-only.

3. Triage table

Print this exact format. Keep it scannable.

| Repo                  | Pkg     | Declared | Locked  | Vulnerable | Deployed | Verdict   |
|-----------------------|---------|----------|---------|------------|----------|-----------|
| myorg/web-app         | next    | ^14.2.0  | 14.2.3  | Yes        | Yes      | Urgent    |
| myorg/internal-tool   | next    | ^15.5.0  | 15.5.20 | No         | Yes      | Past-fix  |
| myorg/scratch/trial   | next    | 16.2.6   | 16.2.6  | No         | No       | Skip      |

Verdicts:

  • Urgent: vulnerable + deployed. Patch this session.
  • Low: vulnerable + local-only. Patch but no rush; bundle with next maintenance.
  • Past-fix: locked version >= fix version. Note only.
  • Skip: not vulnerable OR third-party clone that you do not maintain.

4. Patch (per repo, with confirmation)

For each Urgent or Low row, ask the user before running anything. One AskUserQuestion per repo (do not batch). Show:

  • The current locked version
  • The target fix version
  • The exact command you will run
  • The verification you will run after (tests / build / lint, per repo norms)

Patch command per ecosystem (use the lockfile-aware form):

Ecosystem Upgrade command
pnpm pnpm up <pkg>@<fix> (project root)
npm npm install <pkg>@<fix> --save-exact then npm install to refresh lockfile
yarn yarn upgrade <pkg>@<fix>
uv uv lock --upgrade-package <pkg> then verify with uv pip list | grep <pkg>
pip (frozen) pip install --upgrade <pkg>==<fix> then pip freeze > requirements.txt
poetry poetry update <pkg> (or poetry add <pkg>@^<fix>)
go go get <pkg>@<fix> then go mod tidy
cargo cargo update -p <pkg> --precise <fix>
bundler bundle update <pkg> --conservative

Major-version bumps are not silent. If the user-chosen fix crosses a major (e.g. Next.js 14 → 16), call this out explicitly in the per-repo confirmation. Major bumps may need migration work beyond a lockfile change. Offer the in-major alternative (e.g. "your branch is 15.x; the same fix lives at 15.5.16, no major bump needed") when one exists.

5. Verify

After the upgrade, run before committing:

  1. Install: pnpm install / uv sync / go mod tidy / cargo build (depending on ecosystem). If install fails, stop. Surface the error verbatim and ask.
  2. Tests: look for package.json scripts (test, typecheck, lint, build), pyproject.toml [tool.pytest], go test ./..., cargo test. Run what exists. Skip what doesn't (do not invent a test command).
  3. Build: if a build script exists, run it.

If any step fails, do not commit. Surface the failure and ask the user. Max 5 fix attempts before escalating.

6. Commit

Branch name: fix/<cve-id-or-pkg-slug> (e.g. fix/cve-2026-nextjs-86, fix/next-16-2-5). Kebab-case, no owner prefix, no dates, no spec IDs.

Commit message (conventional commits):

fix(<scope>): bump <pkg> to <fix> for <CVE-ID or GHSA-ID>

<one-line attack vector summary>

Refs: <advisory URL>

Do not push without explicit user instruction.

7. Log (optional)

If the environment variable PATCH_EXPLOIT_LOG points at a markdown file, prepend a 3-6 line entry at the top of that file. Format:

## YYYY-MM-DD (patch-exploit run, <advisory-short-name>): <one-line outcome>

Advisory: <URL>. Affected: <pkg> <vulnerable-range>. Fix: <fix-version>.

Swept `$WORKSPACE_ROOT`: N repos with <pkg>, M vulnerable, K deployed. Patched: <list>. Skipped past-fix: <list>.

Verification: <what ran, what passed>. Branches: `fix/<slug>` in <repos>.

If PATCH_EXPLOIT_LOG is unset, skip this step entirely.

If the variable is set and no vulnerable repos were found, still write one line so the audit is recorded.

Output format during the run

User-facing updates should be tight. Don't narrate every grep. Status checkpoints only:

  • After parse: "Advisory parsed. Affected: . Fix: . Sweeping ."
  • After sweep: print the triage table.
  • Per repo patch: AskUserQuestion with the exact command.
  • Per repo verify: print the test/build output verbatim on failure, "passed: " on success.
  • End: print the log entry that was written (or "no log file configured").

Negative cases (when this skill should hand off)

Situation Where to route
Host-OS CVE (kernel, openssh, macOS Safari) Tell the user: "this is a host patch, not a repo patch. Run brew upgrade / Software Update / apt upgrade on the affected host." Do not pretend to patch.
Generic "audit my code for vulns" with no advisory Use npm audit / pnpm audit / pip-audit / cargo audit / gh dependabot alerts first. Surface results, then loop back to this skill per-advisory.
CVE in a client repo (not owned) Stop. Tell the user the client owns the patch; if they want a PR, ask for explicit go-ahead.
Cloudflare Worker compat-date issue (not a package, a binding) Different patch shape (edit wrangler.toml + redeploy). Handle inline, but do not try to use a package-manager command.

Configuration summary

Env var Purpose Default
WORKSPACE_ROOT Root path to sweep ~/workspace/
PATCH_EXPLOIT_LOG Optional markdown log file unset (no logging)

Default autonomy: confirm per repo, then patch + verify + commit. The user can override at invocation time ("just report", "auto-patch deployed").

Skill version: 0.1.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment