Skip to content

feat(dockhand): support transitive dependency overrides/constraints in spec.yaml - #669

Merged
danbarr merged 7 commits into
mainfrom
feat/dep-overrides
Aug 5, 2026
Merged

feat(dockhand): support transitive dependency overrides/constraints in spec.yaml#669
danbarr merged 7 commits into
mainfrom
feat/dep-overrides

Conversation

@JAORMX

@JAORMX JAORMX commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Implements the dependency-override mechanism from #668.

What

Renovate version bumps fail the build-containers Grype gate (--fail-on high --only-fixed) when the bumped package pins/caps a transitive dependency to a vulnerable version. This adds an optional override mechanism to spec.yaml, plumbed into the generated Dockerfile. toolhive needs no changes — its BuildFromProtocolSchemeWithName(..., dryRun=true) returns the Dockerfile as a string, which is the injection seam.

Schema (cmd/dockhand/main.go)

# npx -> npm "overrides"
spec:
  overrides:
    - package: "@modelcontextprotocol/sdk"
      version: "1.26.0"
      reason: "required, auditable justification"

# uvx -> uv "--overrides" requirements file
spec:
  constraints:
    - spec: "fastmcp>=3.2.0"
      reason: "required, auditable justification"
  • Overrides []OverrideEntry{Package, Version, Reason} (npx only)
  • Constraints []ConstraintEntry{Spec, Reason} (uvx only)
  • Reason is mandatory — validation fails otherwise, mirroring security.allowed_issues.
  • Overrides are rejected on non-npx protocols; constraints on non-uvx protocols.

Injection

  • npx: rewrites the RUN echo '{...}' > package.json step to embed an overrides block (npm honors overrides only from package.json), kept before the npm install step.
  • uvx: injects a RUN printf ... > /tmp/uv-overrides.txt step before the install step and adds --overrides /tmp/uv-overrides.txt to the uv tool install invocation. The install line is matched by content, ignoring comment lines that merely mention uv tool install.

Verification (end-to-end: dockhand-generated Dockerfile -> docker build -> grype --fail-on high --only-fixed)

PR Override Result
#469 npm override @modelcontextprotocol/sdk 1.26.0 SDK resolves to 1.26.0; grype gate passes (exit 0)
#527 uv constraint fastmcp>=3.2.0 fastmcp 3.4.0; import mcp_clickhouse OK; grype gate passes
#528 uv constraint fastmcp>=3.2.0 fastmcp 3.4.0; import mcp_neo4j_cypher OK; grype passes — but crosses upstream's deliberate <2.14 cap (major 2->3); functional testing of the neo4j tools is required before relying on it.

go build ./..., go vet, go test ./cmd/dockhand/..., and golangci-lint run ./cmd/dockhand/... all pass.

Follow-up

This PR only adds the mechanism — it does not modify the three renovate spec.yaml files. After this merges to main, each renovate PR (#469, #527, #528) needs to be rebased on main and have its override/constraint block added to its spec.yaml.

Refs #668

🤖 Generated with Claude Code

…n spec.yaml

Renovate version bumps fail the build-containers Grype gate when the bumped
package pins or caps a transitive dependency to a vulnerable version. Add an
optional dependency-override mechanism to the spec.yaml schema, plumbed into the
generated Dockerfile.

- npx: spec.overrides ([]{package, version, reason}) is injected as an npm
  "overrides" block in the generated package.json before the npm install step.
- uvx: spec.constraints ([]{spec, reason}) is written to a uv overrides
  requirements file and passed to "uv tool install --overrides".

Both injection points match the install step by content (not line number) so
they stay robust to toolhive template formatting. Every entry requires a
non-empty reason (validation fails otherwise) so the justification for
circumventing an upstream pin is auditable in-repo.

Verified end-to-end against the CI build + Grype recipe:
- #469 @brightdata/mcp 2.9.5 + override @modelcontextprotocol/sdk 1.26.0:
  resolves to SDK 1.26.0, grype --fail-on high --only-fixed passes.
- #527 mcp-clickhouse 0.3.0 + constraint fastmcp>=3.2.0: fastmcp 3.4.0,
  import mcp_clickhouse OK, grype passes.

Refs #668

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@toolhive-release-app

toolhive-release-app Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

🔒 MCP Security Scan Results

✅ adb-mysql-mcp-server

  • Status: Passed
  • Tools scanned: 3
  • Result: No security issues detected

Summary: Scanned 1 MCP server(s), all passed security checks. ✅

@toolhive-release-app

Copy link
Copy Markdown
Contributor

🛡️ Skill Security Scan Results

⚠️ No skills were scanned in this PR.

samuv added a commit that referenced this pull request Jul 3, 2026
next-devtools-mcp hard-pins SDK 1.25.2 which fails the Grype gate for
GHSA-345p-7cg4-v4c7. Bump via npm overrides (same major, fixed in 1.26.0).

Also cherry-picks dockhand override support from #669.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread cmd/dockhand/main.go
Comment thread cmd/dockhand/main.go Outdated
danbarr added 5 commits August 5, 2026 10:59
# Conflicts:
#	cmd/dockhand/main.go
…drift

The injection anchors are matched against toolhive's generated Dockerfile,
but the tests only exercised hand-written samples that mirror it, so drift
in the real template would pass tests and silently ship an image with no
overrides applied.

- Match the uvx install step on the bare "uv tool install" verb instead of
  requiring the quoted package spec to follow immediately. toolhive's
  template conditionally emits its own flags in between (RuntimeConfig
  .BuildWith renders as "--with '<spec>'"), which the old anchor missed
  entirely.
- Parse the package.json payload toolhive emits and add the overrides key
  to it, rather than rebuilding the file from hardcoded name/version
  constants. Any other field toolhive puts there is now preserved instead
  of silently dropped.
- Add tests that inject into Dockerfiles generated by the pinned toolhive
  version, covering the uvx step both with and without build-time
  constraints. These fail on anchor drift; they catch the --with case the
  previous anchor could not.
The security scan runs the package directly rather than the built image,
so it never saw the overrides injected into the Dockerfile and exercised
a different dependency set than the one that ships.

Pass spec.constraints through to the scanner as a uv overrides
requirements file (uv takes a file, not inline specifiers), written to a
temp file for the duration of the scan.

npx spec.overrides are not reapplied: npm honors "overrides" only from a
package.json it installs into, and the scan has no project directory.
That is safe for the intended use case, since swapping a vulnerable but
working dependency changes neither startup nor the tool surface being
analyzed, but log a note so a future startup-affecting override does not
fail confusingly.
adb-mysql-mcp-server depends on mcp[cli]>=1.8.0 with no upper bound.
mcp 2.0.0 removed the mcp.server.fastmcp module this server imports at
startup, breaking both the container build and the smoke test canary in
build-containers.yml.

This is also the first spec.yaml to exercise the override mechanism, so
CI now actually covers it end to end rather than only unit tests.
Override values were interpolated into RUN lines inside unescaped single
quotes. A PEP 508 requirement legitimately contains single quotes in an
environment marker, so

    fastmcp>=3.2.0; python_version < '3.14'

was written to the overrides file as

    fastmcp>=3.2.0; python_version < 3.14

which is no longer a valid marker. Anything following the quote also ran
as shell at image build time, so a spec value was able to execute
arbitrary commands during the build. The npm path had the same flaw via
the echoed package.json payload.

- Add shellSingleQuote and use it for both the uv override specs and the
  npm package.json payload, escaping embedded quotes as '\''.
- Reject control characters in override/constraint values. Quoting makes
  shell metacharacters inert, but a newline would still terminate the RUN
  instruction, and none of these fields has a legitimate use for one.
- Cover quoted markers, embedded quotes, and injection attempts. These
  execute the emitted line through a real shell and compare the file it
  writes, rather than assuming how the line parses.
@danbarr

danbarr commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Picking this up since it had been sitting a while, and the mcp 2.0.0 release made it urgent: adb-mysql-mcp-server started failing the build-containers smoke test canary because it depends on mcp[cli]>=1.8.0 with no upper bound, and mcp 2.0.0 removed the mcp.server.fastmcp module it imports at startup. This branch is the fix for that, so it now carries a spec that actually uses the mechanism.

Merged main rather than rebasing, so @JAORMX's original commit and @samuv's review threads stay intact and anchored.

Added on top of the original commit:

  • 5d7d2c5 harden the injection against template drift. The anchors match toolhive's generated Dockerfile, but the tests only exercised hand written samples that mirror it, so drift would pass tests and silently ship an image with no overrides applied. This surfaced a live bug: toolhive's uvx.tmpl now conditionally emits its own flags between uv tool install and the quoted package spec (RuntimeConfig.BuildWith renders as --with '<spec>'), which the old uv tool install " anchor did not match at all. Also changed the npm path to parse the package.json toolhive emits and add the overrides key to it, instead of rebuilding it from hardcoded name and version, which would have silently dropped any field toolhive added. New tests inject into Dockerfiles generated by the pinned toolhive version, with and without build time constraints.
  • 6544e87 reapply uvx overrides during the security scan. Addresses @samuv's P1 for uvx; see that thread for the npx reasoning.
  • c699147 quote override values interpolated into the Dockerfile. Addresses P2, and extends the same fix to the npm path, which had the identical flaw. Details in that thread.
  • 126f50f the adb-mysql-mcp-server fix itself. Since the original PR deliberately left spec.yaml changes to follow-ups, no server was exercising the mechanism yet and CI reported "No MCP servers were scanned". With this spec included, the override path now gets real coverage in CI rather than only unit tests.

Verification: go build ./..., go vet ./..., go test ./cmd/dockhand/..., and golangci-lint run ./cmd/dockhand/... all clean. The adb container builds with mcp==1.29.0 and starts with its tools and resources activated. The scan passes for both adb-mysql-mcp-server with mcp<2 and mcp-clickhouse 0.3.0 with fastmcp>=3.2.0.

This supersedes #827, which took the narrower approach of wiring through toolhive's native --build-with flag. That only adds a resolvable constraint, so it cannot force past an upstream pin, which is the case #668 actually needs. Closing it in favor of this.

The new test cases pushed "1.26.0" and "injection attempt" past
goconst's occurrence threshold, failing CI lint.
Comment thread uvx/adb-mysql-mcp-server/spec.yaml
@danbarr
danbarr merged commit 2bfb47b into main Aug 5, 2026
250 of 263 checks passed
@danbarr
danbarr deleted the feat/dep-overrides branch August 5, 2026 18:49
danbarr added a commit that referenced this pull request Aug 5, 2026
@neondatabase/mcp-server-neon is deprecated on npm in favor of the hosted
server at mcp.neon.tech. Its last publish was 0.6.5 on 2026-03-02, which
is already the version pinned here, so there is nothing to bump to.
Upstream's main is now a Next.js app for the hosted service and is never
published under this npm name.

The practical effect is that this server can never pass the Grype gate:
its abandoned tree exact-pins roughly 30 packages and produces 86
findings, including a critical in next (GHSA-9qr9-h5gf-34mp) and about 34
highs. Clearing that would take ~30 override entries against a package
that will never be updated, which is not a defensible use of the
mechanism added in #669.

The remote server is already in toolhive-catalog. Removing the spec does
not delete the published image, it just stops carrying it forward.

Closes #835

Co-authored-by: Dan Barr <6922515+danbarr@users.noreply.github.com>
danbarr added a commit that referenced this pull request Aug 5, 2026
* fix(adb-mysql-mcp-server): force cryptography>=50.0.0

Grype flags cryptography 48.0.1 for GHSA-g6cj-pr64-35w5 (High): the
PKCS#7 EnvelopedData decryption path exposes a Bleichenbacher oracle
through distinguishable errors and timing. The affected range is
>=44.0.0,<50.0.0 and the fix is 50.0.0.

alibabacloud-tea-openapi caps cryptography <49.0.0 for python>=3.9,
which excludes the fixed version, so the constraint is needed to pull
it forward. This is not a regression from the mcp<2 constraint;
cryptography resolves to 48.0.1 with and without it.

.grype.yaml treats runtime dependencies under /opt/uv-tools as not
ignorable, so an ignore rule is not the right instrument here. Asking
upstream to raise the cap is the more correct long-term fix but is not
something we control.

Verified locally: image installs cryptography 50.0.0 and mcp 1.29.0,
grype --fail-on high --only-fixed reports no vulnerabilities, the
container starts and activates 3 tools plus 4 resources, and the MCP
security scan passes with all tools SAFE.

Residual risk: imports and the RSA signing primitive are covered, but
not live API calls against AnalyticDB, which need credentials.

Fixes #828

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(chroma-mcp): constrain mcp[cli] to >=1.28.1 for 5 HIGH CVEs

chroma-mcp 0.2.6 exact-pins mcp[cli]==1.6.0, which excludes the fix
for all five HIGH advisories Grype reports against the published
image: GHSA-3qhf-m339-9g5v (fixed 1.9.4), GHSA-j975-95f5-7wqh
(1.10.0), GHSA-9h52-p55h-vw2f (1.23.0), GHSA-jpw9-pfvf-9f58 (1.27.2)
and GHSA-vj7q-gjh5-988w (1.28.1).

1.28.1 is the lowest floor that clears all five. A smaller bump to
1.23.0 would newly expose GHSA-hvrp-rf83-w775 (HIGH, affects
>=1.23.0,<=1.27.1), which the pinned 1.6.0 predates. The constraint
is capped <2 so uv cannot resolve the mcp 2.x major.

chroma-mcp imports only mcp.server.fastmcp.FastMCP, which is stable
across the 1.x line, so this stays well inside same-major territory.

Verified: image builds, resolves mcp 1.29.0, grype --fail-on high
--only-fixed reports no vulnerabilities, and the mcp-scan enumerates
all 13 chroma_* tools with no findings.

Refs #830

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(brightdata-mcp): override SDK to 1.26.0 to clear 3 high CVEs

@brightdata/mcp 2.9.0 exact-pins @modelcontextprotocol/sdk 1.21.2,
which fails the Grype gate with three high findings:

  GHSA-w48q-cv73-mx4w  DNS rebinding  fixed 1.24.0
  GHSA-8r9q-7v3j-jr4g  ReDoS          fixed 1.25.2
  GHSA-345p-7cg4-v4c7  >=1.10.0,<=1.25.3, fixed 1.26.0

An exact pin below every fix, so this is the override shape #669
introduced. 1.26.0 is the lowest version clearing all three: 1.25.2
is still inside the GHSA-345p range. Same-major, no API break.

Verified: grype --fail-on high --only-fixed exits 0 with no
high/critical findings, npm resolves 1.26.0 for both the direct
dependency and fastmcp's copy (deduped), and the container starts
and enumerates its tool list over stdio.

Refs #830

* fix(mcp-jetbrains): override SDK to 1.26.0 to clear 2 high CVEs

No code-scanning alerts landed for this server, so the findings were
established by building the image and running grype locally:

  @modelcontextprotocol/sdk 1.7.0
  GHSA-w48q-cv73-mx4w  DNS rebinding  fixed 1.24.0   high
  GHSA-8r9q-7v3j-jr4g  ReDoS          fixed 1.25.2   high

@jetbrains/mcp-proxy 1.8.0 exact-pins the SDK at 1.7.0, so this is the
same shape as onchain-mcp. 1.26.0 rather than 1.25.2 because
GHSA-345p-7cg4-v4c7 affects >=1.10.0,<=1.25.3, which 1.7.0 predates; a
smaller bump would trade two advisories for a third. Same-major.

Verified: grype --fail-on high --only-fixed exits 0, npm reports
@modelcontextprotocol/sdk@1.26.0 overridden, and the proxy completes
the MCP initialize handshake. tools/list then returns "No working IDE
endpoint available", which is the expected response with no JetBrains
IDE to proxy to and the reason this server carries insecure_ignore.

Refs #830

* fix(astra-db-mcp): override undici to 6.28.0 to clear 3 high CVEs

undici 5.29.0 reaches the image through
astra-db-mcp -> mcp-evals -> @actions/core -> @actions/http-client,
which caps it at ^5.25.4:

  GHSA-vrm6-8vpv-qv8q  fixed 6.24.0  high
  GHSA-v9p9-hfj2-hcw8  fixed 6.24.0  high
  GHSA-vxpw-j846-p89q  fixed 6.27.0  high

The 5.x line is end of life, so no 5.x release can carry these fixes
and the cap excludes every fix. 6.28.0 is the last 6.x release; no
high advisory affects >=6.27.0 on that line, and stopping at 6.x
avoids the 7.x-only advisories (GHSA-hm92-r4w5-c3mj,
GHSA-vmh5-mc38-953g, GHSA-4cwx-7wf7-3272).

This is a cross-major force, so worth flagging. It is low risk here:
@actions/http-client 4.0.1 upstream has itself moved to undici
^6.23.0, and the consumer is the mcp-evals harness rather than the
server's request path.

Verified: grype --fail-on high --only-fixed exits 0 with only medium
and low findings remaining, npm reports undici@6.28.0 overridden, and
scripts/mcp-scan/run_scan.py exits 0 with all 19 tools enumerated and
SAFE.

Refs #830

* fix(browserbase-mcp-server): override sharp and undici to clear 4 high CVEs

Grype reports two capped dependencies, not just the sharp one #830
recorded:

  sharp 0.33.5 (capped ^0.33.0)
  GHSA-f88m-g3jw-g9cj  libvips CVEs, affects <0.35.0   high

  undici 5.29.0 (capped ^5.29.0 by @ai-sdk/provider-utils, reached via
  @browserbasehq/stagehand -> @ai-sdk/amazon-bedrock)
  GHSA-vrm6-8vpv-qv8q  fixed 6.24.0                    high
  GHSA-v9p9-hfj2-hcw8  fixed 6.24.0                    high
  GHSA-vxpw-j846-p89q  fixed 6.27.0                    high

Both caps exclude every fix. sharp goes to 0.35.3, the newest 0.35.x
and the only sharp advisory in play. undici goes to 6.28.0, the last
6.x release, which clears all three without stepping into the 7.x-only
advisories.

Both are effectively cross-major forces: sharp 0.x minors are breaking
by convention, and undici 5 to 6 is a real major. Mitigating evidence
is that @ai-sdk/provider-utils 5.x upstream now uses undici ^7.28.0,
so that consumer tracks well past 6.x.

Verified: grype --fail-on high --only-fixed reports "No vulnerabilities
found" and exits 0; sharp loads in the container against libvips 8.18.3
and round-trips a create/resize/re-encode; the server completes the MCP
initialize handshake and enumerates its tool list over stdio.

Note, unrelated to this change: @browserbasehq/mcp-server-browserbase
2.4.3 is currently uninstallable from a clean npm cache. Its transitive
ai@5.0.228 exact-pins @ai-sdk/gateway@2.0.127, which does not exist in
the registry. This reproduces with a bare `npm install` and no
overrides, and is the likely cause of the intermittent build-step
failures. CI will keep failing at npm install until upstream publishes
a fixed release.

Refs #830

---------

Co-authored-by: Dan Barr <6922515+danbarr@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
samuv added a commit that referenced this pull request Aug 6, 2026
Add packaging for next-devtools-mcp v0.4.0 (Vercel), rebased onto main
now that dockhand transitive dependency overrides (#669) have landed.

Package: https://www.npmjs.com/package/next-devtools-mcp
Repository: https://github.com/vercel/next-devtools-mcp

Pins @modelcontextprotocol/sdk to 1.26.0 via spec.overrides to clear
GHSA-345p-7cg4-v4c7 from the upstream 1.25.2 hard-pin.

Co-authored-by: Cursor <cursoragent@cursor.com>
@samuv samuv mentioned this pull request Aug 6, 2026
5 tasks
samuv added a commit that referenced this pull request Aug 6, 2026
Add packaging for next-devtools-mcp v0.4.0 (Vercel), rebased onto main
now that dockhand transitive dependency overrides (#669) have landed.

Package: https://www.npmjs.com/package/next-devtools-mcp
Repository: https://github.com/vercel/next-devtools-mcp

Pins @modelcontextprotocol/sdk to 1.26.0 via spec.overrides to clear
GHSA-345p-7cg4-v4c7 from the upstream 1.25.2 hard-pin.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants