Skip to content

Nothing re-evaluates dependency overrides when renovate bumps the parent package #837

Description

@danbarr

spec.overrides and spec.constraints (added in #669) are static. When renovate bumps the parent package, nothing checks whether the override should stay, move, or go away. Raised while reviewing #469, where the override happened to remain correct across the bump, but only because the upstream pin did not move.

What existing CI already catches

The Grype gate runs on every bump, so these are covered today:

  • Override became insufficient — a new advisory affects our target version. The gate fails.
  • Override became incompatible — the new parent version cannot work with our forced version. The build or scan fails.

What nothing catches

  • Silent downgrade. Upstream raises its own floor above our pin, and because npx overrides are exact versions, ours drags it back down. The gate still passes, so this is invisible and is the only case that causes active harm.
  • Lingering unnecessary override. Upstream fixed their pin and ours is now dead weight. The gate passes either way, so it just accumulates.

The two kinds behave differently

Worth separating, because it concentrates the risk:

  • npx overrides are exact pins (version: "1.26.0"). They go stale and can downgrade.
  • uvx constraints are usually floors (cryptography>=50.0.0, mcp[cli]>=1.28.1,<2). A floor cannot downgrade anything, and insufficiency is already caught by the gate.

So the problem is almost entirely on the npx side. Current inventory is 5 specs and 6 overrides:

Spec Override Direct or transitive
npx/brightdata-mcp @modelcontextprotocol/sdk 1.26.0 direct
npx/mcp-jetbrains @modelcontextprotocol/sdk 1.26.0 direct
npx/onchain-mcp @modelcontextprotocol/sdk 1.26.0 direct
npx/astra-db-mcp undici 6.28.0 transitive, via @actions/http-client
npx/browserbase-mcp-server sharp 0.35.3 direct
npx/browserbase-mcp-server undici 6.28.0 transitive, via @ai-sdk/provider-utils

Proposed starting point: a downgrade check

Deterministic, cheap, and it targets the one failure mode that silently causes harm.

Reading what upstream declares is not sufficient, because two of the six overrides are transitive and the parent does not declare them at all. The approach that handles both uniformly is to resolve the tree without the override and compare:

npm install --package-lock-only --no-audit --no-fund

That takes about 11 seconds for two servers and downloads no tarballs, so it is viable in CI.

One design subtlety found while prototyping. The lockfile resolves a package to a set of versions, not one, because npm keeps multiple copies. For the two servers above:

  • @modelcontextprotocol/sdk resolves to both 1.21.2 and 1.30.0
  • undici resolves to both 5.29.0 and 7.29.0

npm overrides are tree-global, so undici: 6.28.0 is simultaneously an upgrade of the 5.29.0 copy and a downgrade of the 7.29.0 one. A scalar "resolved vs pinned" comparison would be wrong. The check should:

  1. Resolve without overrides and collect every version of the overridden package in the tree.
  2. Report which copies the override raises and which it lowers.
  3. Flag when the pin sits below the highest version otherwise present, since that is the downgrade case.

Open question for implementation: whether that flag should hard-fail the build or post a comment. A legitimate case exists where upstream's floor exceeds our pin and our pin is still the security-correct target, which is exactly why undici was pinned to the last 6.x rather than 7.x. Leaning toward a comment plus a label, with the Grype gate remaining the only hard gate.

Follow-ons, not part of the starting point

  • A renovate customManager for npx override versions. Idiomatic here: renovate.json already has 4, all matching spec: -> package: -> version: per protocol directory, and an override entry is the same shape. That turns "nothing re-assesses the target" into "the target is bumped like any other dependency", with the Grype gate as the safety net. Deliberately not for uvx constraints, since rewriting inside a PEP 508 range is fiddly and a floor does not go stale the same way.
  • A periodic necessity audit. Answering "is this override still needed?" means building without it and comparing, which is too expensive per-PR. Better as a weekly report, and it could hang off the periodic scan fixed in fix(ci): make the periodic security scan actually report findings #834.

Rejected

A blocking comment on every renovate PR touching an override-bearing spec. It would only fire on 5 specs so it is not especially noisy, but it is toil: a human re-deriving by hand what the check above can answer mechanically.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageIssue needs initial triage by a maintainer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions