# Dep Remediation

> Fix vulnerable dependencies correctly — the golden rule (bump the direct dependency / BOM, never pin the transitive), cross-check the fix version against ecosystem advisories, defer breaking-major upgrades, and compile + unit-test locally before pushing. Load for any dependency remediation or version-bump work in php-composer, gradle-springboot, maven-springboot, pip, or npm projects.

- Skill: `blazemeter/dep-remediation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add blazemeter/dep-remediation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/blazemeter/dep-remediation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Blazemeter (https://skillmd.com/u/blazemeter)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/blazemeter/dep-remediation

---


# Golden rule — fix the DIRECT dependency, never the transitive

Applies the same way across **every** ecosystem this skill covers — php-composer, gradle-springboot,
maven-springboot, pip, npm: if a vulnerable library **Y** is pulled in by a direct dependency **X**,
bump **X** so it resolves a fixed **Y**. Do **not** pin/override the transitive **Y** directly — no
matter which ecosystem-specific mechanism would make that easy (a Maven/Gradle explicit version
override, npm's `overrides`/`resolutions`, a pip line pinning the transitive package directly). If
no direct-dep bump resolves the alert, **defer and note it** — never pin the transitive as a
workaround.

**BOM-managed transitives are a special case of the same rule, not a different one.** Under Spring
Boot (`gradle-springboot`, `maven-springboot`), several transitives (Spring Framework, micrometer,
logback, jackson) are version-pinned by the **Spring Boot BOM** — the one dependency that brings
them all — so "the direct dependency" for those is the **BOM version**, not the individual
transitive's own `<spring-framework.version>` / `<micrometer.version>` override. Ecosystems with no
BOM concept (`pip`, `npm`, `php-composer`) don't have this special case at all — there, "the direct
dependency" is simply whichever direct entry in the manifest pulls in the vulnerable transitive; see
the per-stack recipe below for exactly where that's pinned in each.

Two more non-negotiables:
- **Cross-check the fix version against the ecosystem advisory DB.** Mend's suggested version may
  itself be under advisory (e.g. `aws/aws-sdk-php` 3.368.0 was) — escalate to the latest
  non-advisory version.
- **Defer breaking majors.** If a fix needs a breaking/major upgrade that fails the build (within
  the attempt cap), **revert just that dependency**, keep the other fixes, and record the deferred
  one (e.g. "symfony/yaml 3.4 → major upgrade required, deferred"). Also **fix tests that a dep
  change legitimately breaks** (e.g. an SDK changing an error string).

**Only fix alerts in the project's own stack ecosystem.** Alerts in a different manifest (Python
`requirements.txt` or npm `package.json` under `tools/` in a PHP repo) are **deferred and listed**,
not fixed here.

# Fix recipe by stack

## `php-composer`
- Bump the **direct `require`** in `composer.json` (incl. the direct dep that pulls a vulnerable transitive, e.g. bump `guzzle` to pull a fixed `psr7`) → `composer update <vendor/pkg> --with-dependencies --ignore-platform-reqs` → commit `composer.lock`.
- **Always pass `--ignore-platform-reqs`** on the update. The sandbox resolving/locking versions
  here won't have every runtime PHP extension the app loads in production (e.g. `ext-mongodb`,
  `ext-grpc`, `ext-zstd`) — that's fine, since this step is dependency-graph math, not execution.
  It doesn't change which versions get resolved, only whether the solver requires the extension to
  be *present in this sandbox* to do the math. Real runtime correctness is still fully verified by
  `make test-docker-cov` below (which builds the repo's own Dockerfile, with the real extensions)
  and the Jenkins CI gate — neither is affected by this flag.
- **Cross-check:** `composer audit` (Mend's suggestion may be under a Packagist advisory).
- **Local build + test (pre-push):** `make phpstan` (static) + `make test-docker-cov` (the dockerized unit suite CI runs — needs Docker).
- **Internal forks** (`mongator`, `mondator`, `php-resque-ex`, `php-resque-ex-scheduler`, `PHP-Multivariate-Regression`, `Restler`, `mandrill-api-php`) resolve from `github.com/Blazemeter` git `repositories`, not Packagist — a CVE there is fixed **upstream in that repo + a ref bump**, not a registry version.

## `gradle-springboot`
- BOM-managed transitive → **bump the Spring Boot version** (not the individual transitive); a genuinely direct dep → bump its literal version in `build.gradle.kts`. Verify with `./gradlew dependencies`.
- **Local build + test (pre-push):** `./gradlew test`.

## `maven-springboot`
- BOM-managed transitive → **bump `<spring-boot.version>`** in `pom.xml` (the BOM), *not* individual `<spring-framework.version>` / `<micrometer.version>` overrides; a genuinely direct dep → bump its `<version>`. Verify with `mvn -q dependency:tree`.
- **Local build + test (pre-push):** `mvn -q -B verify`.
- **Repo note:** Blazemeter builds don't use `aws-nexus` (unrelated machine-global mirror). Let the repo's own build config resolve deps — don't inject a Nexus.

## `pip`
- No BOM concept in pip — bump the **direct dependency**'s pin. For a vulnerable transitive, bump
  the direct package that pulls it in (same golden rule: never pin the transitive directly).
- Pin location varies by repo: `requirements.txt` (`pkg==x.y.z`) is the common case; some repos pin
  in `setup.py`'s `install_requires` or `setup.cfg` instead — bump wherever the version is actually
  pinned. Regenerate any lock file the repo uses (e.g. `pip-compile`) if present.
- **Cross-check:** `pip-audit` (falls back to `safety check` if `pip-audit` isn't available) — Mend's
  suggested version may itself be under advisory on PyPI/OSV.
- **Match the repo's own pinned Python version.** Different repos pin different exact versions
  (confirmed so far: `3.11.15`, `3.13`) — read it from the repo's own `Dockerfile`
  (`FROM python:X.Y` / a `PYTHON_VERSION` build arg) or CI config before running anything, rather
  than using whatever Python happens to be on the local machine, so the local run actually reflects
  what CI/production will see.
- **Local build + test (pre-push), in this preference order:**
  1. **`pyenv`** — `pyenv install -s <version>` (idempotent — skips if already installed) then run
     under that version (e.g. `PYENV_VERSION=<version> pyenv exec pip install ...` /
     `pyenv exec pytest`). No daemon dependency, and each version installs once then stays cached
     for every future run — the orchestrator's own image pre-installs the versions known today.
  2. **Docker** (`python:<version>` container) if pyenv isn't available — matches CI's own
     containerized environment exactly, but depends on a working Docker daemon.
  3. **Homebrew** (`brew install python@<version>`) only as a last resort — slower, and awkward
     for holding multiple pinned versions side by side.
  Then: `pip install -r requirements.txt -r test-requirements.txt` (adjust filenames per repo) then
  `pytest`. Prefer the repo's own `Makefile`/CI target if one exists (e.g. `make test`) over calling
  `pytest` directly, so local runs match what CI actually runs.

## `npm`
- Bump the **direct dependency** in `package.json` (`dependencies`/`devDependencies`) to the fixed
  version — including the direct dep that pulls a vulnerable transitive. Do **not** reach for
  `overrides`/`resolutions` to pin the transitive directly; that's the golden rule's "never pin the
  transitive" applied to npm's own override mechanism.
- Update with `npm install <pkg>@<version> --save-exact` (or the repo's existing version-pinning
  style) so `package-lock.json` regenerates and gets committed alongside `package.json`.
- **Cross-check:** `npm audit` — Mend's suggested version may itself still be flagged. Don't reach
  for `npm audit fix --force`; it can pull in unrelated breaking majors indiscriminately instead of
  the targeted golden-rule bump.
- **Local build + test (pre-push):** `npm ci` then whatever the repo's `package.json` `scripts`
  actually define (commonly `npm test`; TypeScript repos often need `npm run build` first) — check
  `scripts` rather than assuming, so local runs match CI.

# Local build+test discipline

Compile + run the unit suite **locally before pushing** (fail fast — don't burn a CI cycle). Only
push if green; if local tests fail, fix forward locally first. If the local env can't run them
(e.g. Docker unavailable), say so and rely on the CI gate. `Code Insight`
(`codeinsight-project.yml`, Revenera) is separate license scanning — not the CVE flow.

