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:
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.
- Docker (
python:<version> container) if pyenv isn't available — matches CI's own
containerized environment exactly, but depends on a working Docker daemon.
- 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.
1---2name: dep-remediation3description: 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.4---56# Golden rule — fix the DIRECT dependency, never the transitive78Applies the same way across **every** ecosystem this skill covers — php-composer, gradle-springboot,9maven-springboot, pip, npm: if a vulnerable library **Y** is pulled in by a direct dependency **X**,10bump **X** so it resolves a fixed **Y**. Do **not** pin/override the transitive **Y** directly — no11matter which ecosystem-specific mechanism would make that easy (a Maven/Gradle explicit version12override, npm's `overrides`/`resolutions`, a pip line pinning the transitive package directly). If13no direct-dep bump resolves the alert, **defer and note it** — never pin the transitive as a14workaround.1516**BOM-managed transitives are a special case of the same rule, not a different one.** Under Spring17Boot (`gradle-springboot`, `maven-springboot`), several transitives (Spring Framework, micrometer,18logback, jackson) are version-pinned by the **Spring Boot BOM** — the one dependency that brings19them all — so "the direct dependency" for those is the **BOM version**, not the individual20transitive's own `<spring-framework.version>` / `<micrometer.version>` override. Ecosystems with no21BOM concept (`pip`, `npm`, `php-composer`) don't have this special case at all — there, "the direct22dependency" is simply whichever direct entry in the manifest pulls in the vulnerable transitive; see23the per-stack recipe below for exactly where that's pinned in each.2425Two more non-negotiables:26- **Cross-check the fix version against the ecosystem advisory DB.** Mend's suggested version may27 itself be under advisory (e.g. `aws/aws-sdk-php` 3.368.0 was) — escalate to the latest28 non-advisory version.29- **Defer breaking majors.** If a fix needs a breaking/major upgrade that fails the build (within30 the attempt cap), **revert just that dependency**, keep the other fixes, and record the deferred31 one (e.g. "symfony/yaml 3.4 → major upgrade required, deferred"). Also **fix tests that a dep32 change legitimately breaks** (e.g. an SDK changing an error string).3334**Only fix alerts in the project's own stack ecosystem.** Alerts in a different manifest (Python35`requirements.txt` or npm `package.json` under `tools/` in a PHP repo) are **deferred and listed**,36not fixed here.3738# Fix recipe by stack3940## `php-composer`41- 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`.42- **Always pass `--ignore-platform-reqs`** on the update. The sandbox resolving/locking versions43 here won't have every runtime PHP extension the app loads in production (e.g. `ext-mongodb`,44 `ext-grpc`, `ext-zstd`) — that's fine, since this step is dependency-graph math, not execution.45 It doesn't change which versions get resolved, only whether the solver requires the extension to46 be *present in this sandbox* to do the math. Real runtime correctness is still fully verified by47 `make test-docker-cov` below (which builds the repo's own Dockerfile, with the real extensions)48 and the Jenkins CI gate — neither is affected by this flag.49- **Cross-check:** `composer audit` (Mend's suggestion may be under a Packagist advisory).50- **Local build + test (pre-push):** `make phpstan` (static) + `make test-docker-cov` (the dockerized unit suite CI runs — needs Docker).51- **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.5253## `gradle-springboot`54- 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`.55- **Local build + test (pre-push):** `./gradlew test`.5657## `maven-springboot`58- 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`.59- **Local build + test (pre-push):** `mvn -q -B verify`.60- **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.6162## `pip`63- No BOM concept in pip — bump the **direct dependency**'s pin. For a vulnerable transitive, bump64 the direct package that pulls it in (same golden rule: never pin the transitive directly).65- Pin location varies by repo: `requirements.txt` (`pkg==x.y.z`) is the common case; some repos pin66 in `setup.py`'s `install_requires` or `setup.cfg` instead — bump wherever the version is actually67 pinned. Regenerate any lock file the repo uses (e.g. `pip-compile`) if present.68- **Cross-check:** `pip-audit` (falls back to `safety check` if `pip-audit` isn't available) — Mend's69 suggested version may itself be under advisory on PyPI/OSV.70- **Match the repo's own pinned Python version.** Different repos pin different exact versions71 (confirmed so far: `3.11.15`, `3.13`) — read it from the repo's own `Dockerfile`72 (`FROM python:X.Y` / a `PYTHON_VERSION` build arg) or CI config before running anything, rather73 than using whatever Python happens to be on the local machine, so the local run actually reflects74 what CI/production will see.75- **Local build + test (pre-push), in this preference order:**76 1. **`pyenv`** — `pyenv install -s <version>` (idempotent — skips if already installed) then run77 under that version (e.g. `PYENV_VERSION=<version> pyenv exec pip install ...` /78 `pyenv exec pytest`). No daemon dependency, and each version installs once then stays cached79 for every future run — the orchestrator's own image pre-installs the versions known today.80 2. **Docker** (`python:<version>` container) if pyenv isn't available — matches CI's own81 containerized environment exactly, but depends on a working Docker daemon.82 3. **Homebrew** (`brew install python@<version>`) only as a last resort — slower, and awkward83 for holding multiple pinned versions side by side.84 Then: `pip install -r requirements.txt -r test-requirements.txt` (adjust filenames per repo) then85 `pytest`. Prefer the repo's own `Makefile`/CI target if one exists (e.g. `make test`) over calling86 `pytest` directly, so local runs match what CI actually runs.8788## `npm`89- Bump the **direct dependency** in `package.json` (`dependencies`/`devDependencies`) to the fixed90 version — including the direct dep that pulls a vulnerable transitive. Do **not** reach for91 `overrides`/`resolutions` to pin the transitive directly; that's the golden rule's "never pin the92 transitive" applied to npm's own override mechanism.93- Update with `npm install <pkg>@<version> --save-exact` (or the repo's existing version-pinning94 style) so `package-lock.json` regenerates and gets committed alongside `package.json`.95- **Cross-check:** `npm audit` — Mend's suggested version may itself still be flagged. Don't reach96 for `npm audit fix --force`; it can pull in unrelated breaking majors indiscriminately instead of97 the targeted golden-rule bump.98- **Local build + test (pre-push):** `npm ci` then whatever the repo's `package.json` `scripts`99 actually define (commonly `npm test`; TypeScript repos often need `npm run build` first) — check100 `scripts` rather than assuming, so local runs match CI.101102# Local build+test discipline103104Compile + run the unit suite **locally before pushing** (fail fast — don't burn a CI cycle). Only105push if green; if local tests fail, fix forward locally first. If the local env can't run them106(e.g. Docker unavailable), say so and rely on the CI gate. `Code Insight`107(`codeinsight-project.yml`, Revenera) is separate license scanning — not the CVE flow.