From a86bb70d7a2c6014bf152e01f3392a2d19bf0b4b Mon Sep 17 00:00:00 2001 From: Dennis Dornon Date: Mon, 27 Jul 2026 08:24:42 -0400 Subject: [PATCH 1/2] Mirror root overrides into the acceptance consumer install The fixture cold-install resolves the packed tarball against a stub registry that serves only the versions in the repo lock. Root overrides never apply to dependents, so after the filelist minimatch override the consumer asked for minimatch@^5.0.1, which the lock no longer carries, and the install died with ETARGET. Copy the overrides from the repo manifest into the scratch consumer manifest before the install so its resolution stays inside the stub's catalog. Real consumers hit the public registry, resolve minimatch 5.x normally, and get the vulnerable brace-expansion under filelist; ejs pins jake to 10.x and the patched filelist 2.x only ships with jake 12, so nothing in our manifest can change that. --- tests/acceptance/lib/pack.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/acceptance/lib/pack.ts b/tests/acceptance/lib/pack.ts index 74d663b..76a4710 100644 --- a/tests/acceptance/lib/pack.ts +++ b/tests/acceptance/lib/pack.ts @@ -100,6 +100,23 @@ export async function packAndInstall( env: { ...process.env, npm_config_cache: npmCache }, }); + // The stub registry serves only the versions pinned in the repo lock, and + // npm overrides do not propagate to dependents, so a bare consumer resolves + // ranges the lock has overridden away (filelist -> minimatch@^5) and the + // install 404s. Mirror the root overrides into the consumer manifest so its + // resolution stays inside what the stub can serve. + const repoManifest = JSON.parse( + fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'), + ) as { overrides?: Record }; + if (repoManifest.overrides) { + const consumerManifestPath = path.join(consumerDir, 'package.json'); + const consumerManifest = JSON.parse( + fs.readFileSync(consumerManifestPath, 'utf8'), + ) as Record; + consumerManifest['overrides'] = repoManifest.overrides; + fs.writeFileSync(consumerManifestPath, `${JSON.stringify(consumerManifest, null, 2)}\n`); + } + const registry = await startLocalDependencyRegistry(repoRoot, tempRoot, runner); try { await runner.run( From cdfe01d8e01cf99f241f91bd5fe982f306fb3d79 Mon Sep 17 00:00:00 2001 From: Dennis Dornon Date: Mon, 27 Jul 2026 08:43:22 -0400 Subject: [PATCH 2/2] Make masking linearity tests baseline-relative The two maskUrlUserinfoInText performance tests asserted absolute wall-clock budgets, and a loaded Windows CI runner blew the 1s budget by 27ms. Time a tenth-size run of the same hostile input first, then require the full run to finish within 40x that baseline (floored at 10ms). Linear scaling lands near 10x, quadratic near 100x, so the regression signal survives without the machine-speed dependency. --- src/utils/format.test.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/utils/format.test.ts b/src/utils/format.test.ts index 141daa5..0cf822d 100644 --- a/src/utils/format.test.ts +++ b/src/utils/format.test.ts @@ -690,14 +690,27 @@ describe('maskUrlUserinfoInText', () => { ); }); - it('stays linear when many short authorities follow a distant @', () => { - // A backward lastIndexOf for the authority's @ made this quadratic. - const hostile = `@${' http:x/'.repeat(50_000)}`; + // A baseline run at 1/10 the size sets the bound instead of a fixed + // millisecond budget, so this holds on any machine while still failing on + // quadratic behavior: linear scaling gives ~10x the baseline, quadratic + // gives ~100x, and 40x is the dividing line with slack for noise. The + // floor keeps a near-zero baseline from making the bound vacuous. + function expectLinearScaling(buildInput: (repeat: number) => string, fullRepeat: number): void { + const t0 = Date.now(); + maskUrlUserinfoInText(buildInput(fullRepeat / 10)); + const baseline = Date.now() - t0; + const floor = Math.max(baseline, 10); + const start = Date.now(); + maskUrlUserinfoInText(buildInput(fullRepeat)); + const fullDuration = Date.now() - start; - maskUrlUserinfoInText(hostile); + expect(fullDuration).toBeLessThan(40 * floor); + } - expect(Date.now() - start).toBeLessThan(500); + it('stays linear when many short authorities follow a distant @', () => { + // A backward lastIndexOf for the authority's @ made this quadratic. + expectLinearScaling((repeat) => `@${' http:x/'.repeat(repeat)}`, 50_000); }); it('stays linear on terminator-free scheme repeats and unclosed brackets', () => { @@ -707,11 +720,7 @@ describe('maskUrlUserinfoInText', () => { // its `]`. Past MAX_AUTHORITY_SPAN the adjudicator now fails closed // instead of parsing unbounded candidates. for (const unit of ['https:\\\\u:p@', 'https:\\\\u:p@!', 'https://u:p@[/']) { - const start = Date.now(); - - maskUrlUserinfoInText(unit.repeat(20_000)); - - expect(Date.now() - start).toBeLessThan(1_000); + expectLinearScaling((repeat) => unit.repeat(repeat), 20_000); } });