| 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. |
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.
- Save this file as
~/.claude/skills/patch-exploit/SKILL.md. - Set
WORKSPACE_ROOTto where your repos live (defaults to~/workspace/). - Optional: set
PATCH_EXPLOIT_LOGto 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.
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/govulncheckoutput. - 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).
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-xxxxreturns JSON withvulnerabilities[].vulnerable_version_rangeandvulnerabilities[].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.
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/nullFor each manifest hit, read:
- Declared version (
jq '.dependencies."<pkg>" // .devDependencies."<pkg>"'for npm). - Locked version from the lockfile (regex on
pnpm-lock.yaml,jqonpackage-lock.json, etc.). Locked wins; declared is just the constraint. - Deployment evidence:
vercel.json,wrangler.toml,Dockerfile,fly.toml,.vercel/,netlify.toml, adeployjob in.github/workflows/*.yml, a LaunchAgent/Daemon plist undertools/*/deploy/, asystemdunit. Absence of all = local-only.
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.
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.
After the upgrade, run before committing:
- Install:
pnpm install/uv sync/go mod tidy/cargo build(depending on ecosystem). If install fails, stop. Surface the error verbatim and ask. - Tests: look for
package.jsonscripts (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). - 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.
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.
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.
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").
| 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. |
| 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