From a2d39c849d91b27829d3c6274990cbec046ee4fb Mon Sep 17 00:00:00 2001 From: Andiii208 <246843242+Andiii208@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:06:30 +0800 Subject: [PATCH] fix(packaging): bundle fast-uri 3.1.8 in dist to clear nine advisories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ajv copy inlined into the client and server dist resolved fast-uri 3.1.0, which carries nine published advisories (all fixed in 3.1.8). Because the copy is inlined rather than declared, consumer overrides/resolutions and lockfile-based scanners cannot reach it. Pin fast-uri through resolutions — the security-vulnerability trigger in DEPENDENCY_POLICY.md — so the bundled copy is the patched one. Patch release: the published dependency manifests are unchanged; only dist content changes. The dts shim's provenance comment now names 3.1.8; the URIComponent interface is field-for-field identical, so no type change. Which copy ships is observable only in the built output, so each package now pins it there: a dist-scanning test asserts every fast-uri version marker (rolldown's #region comments and the dts shim's provenance comment) names the pinned version, and that at least one exists — the inlining itself is the fix, so a dropped noExternal entry or a stale pin fails CI instead of shipping. Fixes #2966 --- .changeset/inlined-fast-uri-advisories.md | 6 ++ package.json | 1 + .../test/client/fastUriBundlePin.test.ts | 73 +++++++++++++++++++ .../src/validators/fastUriShim.d.ts | 2 +- .../test/server/fastUriBundlePin.test.ts | 73 +++++++++++++++++++ pnpm-lock.yaml | 15 ++-- 6 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 .changeset/inlined-fast-uri-advisories.md create mode 100644 packages/client/test/client/fastUriBundlePin.test.ts create mode 100644 packages/server/test/server/fastUriBundlePin.test.ts diff --git a/.changeset/inlined-fast-uri-advisories.md b/.changeset/inlined-fast-uri-advisories.md new file mode 100644 index 0000000000..3250fdf3fe --- /dev/null +++ b/.changeset/inlined-fast-uri-advisories.md @@ -0,0 +1,6 @@ +--- +'@modelcontextprotocol/client': patch +'@modelcontextprotocol/server': patch +--- + +The `ajv` and `ajv-formats` copies bundled into `dist/` now resolve `fast-uri` 3.1.8 instead of 3.1.0, which carries nine published security advisories (all fixed in 3.1.8). Because those copies are inlined rather than declared as dependencies, downstream `overrides`/`resolutions` could not reach them and lockfile-based scanners could not see them; this patch ships the rebuilt `dist/`. No API, option, dependency or runtime behavior change. diff --git a/package.json b/package.json index 3072a5bfea..d289daef73 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "zod": "catalog:runtimeShared" }, "resolutions": { + "fast-uri": "3.1.8", "strip-ansi": "6.0.1" } } diff --git a/packages/client/test/client/fastUriBundlePin.test.ts b/packages/client/test/client/fastUriBundlePin.test.ts new file mode 100644 index 0000000000..2f17305e12 --- /dev/null +++ b/packages/client/test/client/fastUriBundlePin.test.ts @@ -0,0 +1,73 @@ +/** + * Bundled fast-uri pin: the copy inlined into this package's dist is the one + * the root `resolutions` field pins — 3.1.8, the first release without the + * nine published advisories. + * + * `ajv`/`ajv-formats` are `noExternal` in tsdown.config.ts, so the transitive + * `fast-uri` is bundled into `dist/` and never reaches the published manifest: + * consumer overrides/resolutions and lockfile-based scanners can neither see + * nor reach it. Which copy actually ships is observable only in the built + * output, so pin it there. The version surfaces in two places — rolldown's + * `#region` comments over the inlined modules, and the dts shim's provenance + * comment (packages/core-internal/src/validators/fastUriShim.d.ts, mapped in + * through `dts.compilerOptions.paths`) — and both must name the pinned + * version. At least one marker must exist at all: the inlining is the fix, and + * a config change that drops `ajv`/`ajv-formats` from `noExternal` would hand + * the unresolved dependency back to consumers. + */ +import { readdirSync, readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { beforeAll, describe, expect, test } from 'vitest'; + +import { ensureBuilt } from '../helpers/ensureBuilt'; + +const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '../..'); +const distDir = join(pkgDir, 'dist'); +const repoRoot = join(pkgDir, '..', '..'); + +/** The exact version root package.json pins `fast-uri` to via `resolutions`. */ +function pinnedFastUri(): string { + const rootManifest = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')) as { + resolutions?: Record; + }; + const pinned = rootManifest.resolutions?.['fast-uri']; + expect(pinned, 'root package.json no longer pins fast-uri via resolutions').toMatch(/^\d+\.\d+\.\d+$/); + return pinned!; +} + +describe('the bundled fast-uri copy is the pinned one', () => { + beforeAll(async () => { + await ensureBuilt(pkgDir); + }, 240_000); + + test('every fast-uri marker in dist names the pinned version', () => { + const pinned = pinnedFastUri(); + // The shipped artifacts: .mjs/.cjs runtime plus .d.mts/.d.cts types. + // Sourcemaps (.map) only mirror those chunks' module paths, so they + // carry no independent copy of the version. + const artifacts = readdirSync(distDir, { recursive: true }) + .map(String) + .filter(file => /\.(mjs|cjs|d\.mts|d\.cts)$/.test(file)); + + const versions = new Set(); + for (const artifact of artifacts) { + const source = readFileSync(join(distDir, artifact), 'utf8'); + for (const marker of source.matchAll(/fast-uri@(\d+\.\d+\.\d+)/g)) { + versions.add(marker[1]!); + } + } + + expect( + [...versions], + 'dist/ carries no fast-uri version marker at all — ajv/ajv-formats must stay noExternal in tsdown.config.ts, ' + + 'which is what inlines fast-uri and its #region markers' + ).toContain(pinned); + expect( + [...versions].filter(version => version !== pinned), + `dist/ inlines a fast-uri version other than the pinned ${pinned} — check the resolutions pin, the lockfile resolution, ` + + 'and the dts shim provenance comment' + ).toEqual([]); + }); +}); diff --git a/packages/core-internal/src/validators/fastUriShim.d.ts b/packages/core-internal/src/validators/fastUriShim.d.ts index d607d15c93..85298af196 100644 --- a/packages/core-internal/src/validators/fastUriShim.d.ts +++ b/packages/core-internal/src/validators/fastUriShim.d.ts @@ -11,7 +11,7 @@ * `dts.compilerOptions.paths` so the type is inlined as a plain named export. * Runtime code is unaffected (this is a `.d.ts`; the path mapping is dts-only). */ -// Field-for-field copy of fast-uri@3.1.0's URIComponent (types/index.d.ts). +// Field-for-field copy of fast-uri@3.1.8's URIComponent (types/index.d.ts). // Keep in sync when bumping fast-uri/ajv. export interface URIComponent { scheme?: string; diff --git a/packages/server/test/server/fastUriBundlePin.test.ts b/packages/server/test/server/fastUriBundlePin.test.ts new file mode 100644 index 0000000000..2f17305e12 --- /dev/null +++ b/packages/server/test/server/fastUriBundlePin.test.ts @@ -0,0 +1,73 @@ +/** + * Bundled fast-uri pin: the copy inlined into this package's dist is the one + * the root `resolutions` field pins — 3.1.8, the first release without the + * nine published advisories. + * + * `ajv`/`ajv-formats` are `noExternal` in tsdown.config.ts, so the transitive + * `fast-uri` is bundled into `dist/` and never reaches the published manifest: + * consumer overrides/resolutions and lockfile-based scanners can neither see + * nor reach it. Which copy actually ships is observable only in the built + * output, so pin it there. The version surfaces in two places — rolldown's + * `#region` comments over the inlined modules, and the dts shim's provenance + * comment (packages/core-internal/src/validators/fastUriShim.d.ts, mapped in + * through `dts.compilerOptions.paths`) — and both must name the pinned + * version. At least one marker must exist at all: the inlining is the fix, and + * a config change that drops `ajv`/`ajv-formats` from `noExternal` would hand + * the unresolved dependency back to consumers. + */ +import { readdirSync, readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { beforeAll, describe, expect, test } from 'vitest'; + +import { ensureBuilt } from '../helpers/ensureBuilt'; + +const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '../..'); +const distDir = join(pkgDir, 'dist'); +const repoRoot = join(pkgDir, '..', '..'); + +/** The exact version root package.json pins `fast-uri` to via `resolutions`. */ +function pinnedFastUri(): string { + const rootManifest = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')) as { + resolutions?: Record; + }; + const pinned = rootManifest.resolutions?.['fast-uri']; + expect(pinned, 'root package.json no longer pins fast-uri via resolutions').toMatch(/^\d+\.\d+\.\d+$/); + return pinned!; +} + +describe('the bundled fast-uri copy is the pinned one', () => { + beforeAll(async () => { + await ensureBuilt(pkgDir); + }, 240_000); + + test('every fast-uri marker in dist names the pinned version', () => { + const pinned = pinnedFastUri(); + // The shipped artifacts: .mjs/.cjs runtime plus .d.mts/.d.cts types. + // Sourcemaps (.map) only mirror those chunks' module paths, so they + // carry no independent copy of the version. + const artifacts = readdirSync(distDir, { recursive: true }) + .map(String) + .filter(file => /\.(mjs|cjs|d\.mts|d\.cts)$/.test(file)); + + const versions = new Set(); + for (const artifact of artifacts) { + const source = readFileSync(join(distDir, artifact), 'utf8'); + for (const marker of source.matchAll(/fast-uri@(\d+\.\d+\.\d+)/g)) { + versions.add(marker[1]!); + } + } + + expect( + [...versions], + 'dist/ carries no fast-uri version marker at all — ajv/ajv-formats must stay noExternal in tsdown.config.ts, ' + + 'which is what inlines fast-uri and its #region markers' + ).toContain(pinned); + expect( + [...versions].filter(version => version !== pinned), + `dist/ inlines a fast-uri version other than the pinned ${pinned} — check the resolutions pin, the lockfile resolution, ` + + 'and the dts shim provenance comment' + ).toEqual([]); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c3cf72692..1523928e1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,6 +137,7 @@ catalogs: version: 4.3.6 overrides: + fast-uri: 3.1.8 strip-ansi: 6.0.1 importers: @@ -5055,8 +5056,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} fastify@5.8.4: resolution: {integrity: sha512-sa42J1xylbBAYUWALSBoyXKPDUvM3OoNOibIefA+Oha57FryXKKCZarA1iDntOCWp3O35voZLuDg2mdODXtPzQ==} @@ -7663,7 +7664,7 @@ snapshots: dependencies: ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) - fast-uri: 3.1.0 + fast-uri: 3.1.8 '@fastify/error@4.2.0': {} @@ -8783,14 +8784,14 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.8 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.8 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -9732,7 +9733,7 @@ snapshots: '@fastify/merge-json-schemas': 0.2.1 ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) - fast-uri: 3.1.0 + fast-uri: 3.1.8 json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 @@ -9744,7 +9745,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.1.0: {} + fast-uri@3.1.8: {} fastify@5.8.4: dependencies: