From 01451d74e08d112e3c153a50f81113fb78751b0e Mon Sep 17 00:00:00 2001 From: Frank Koenders Date: Sat, 28 Feb 2026 20:14:49 +0100 Subject: [PATCH 1/2] Replace glob-slasher with local path.posix utility to fix Windows glob matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit glob-slasher (v1.0.1, 2014, EOL) depends on glob-slash, which calls path.normalize(path.join('/', value)). On Windows, path.normalize converts forward slashes to backslashes, breaking glob pattern matching entirely — e.g. /api/** becomes \api\** and never matches any request URL. Replace it with a minimal local utility (src/utils/slasher.ts) that uses path.posix throughout, guaranteeing forward slashes on all platforms. --- package-lock.json | 37 ---------------------------- package.json | 1 - src/middleware/headers.js | 2 +- src/middleware/redirects.js | 2 +- src/middleware/rewrites.ts | 2 +- src/utils/slasher.ts | 43 +++++++++++++++++++++++++++++++++ test/unit/utils/slasher.spec.ts | 31 ++++++++++++++++++++++++ 7 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 src/utils/slasher.ts create mode 100644 test/unit/utils/slasher.spec.ts diff --git a/package-lock.json b/package-lock.json index cea356b..eabd36d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "compression": "^1.7.0", "connect": "^3.7.0", "destroy": "^1.0.4", - "glob-slasher": "^1.0.1", "is-url": "^1.2.2", "join-path": "^1.1.1", "lodash": "^4.17.19", @@ -3397,21 +3396,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob-slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/glob-slash/-/glob-slash-1.0.0.tgz", - "integrity": "sha512-ZwFh34WZhZX28ntCMAP1mwyAJkn8+Omagvt/GvA+JQM/qgT0+MR2NPF3vhvgdshfdvDyGZXs8fPXW84K32Wjuw==" - }, - "node_modules/glob-slasher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/glob-slasher/-/glob-slasher-1.0.1.tgz", - "integrity": "sha512-5MUzqFiycIKLMD1B0dYOE4hGgLLUZUNGGYO4BExdwT32wUwW3DBOE7lMQars7vB1q43Fb3Tyt+HmgLKsJhDYdg==", - "dependencies": { - "glob-slash": "^1.0.0", - "lodash.isobject": "^2.4.1", - "toxic": "^1.0.0" - } - }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -4189,11 +4173,6 @@ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, - "node_modules/lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha512-XpqGh1e7hhkOzftBfWE7zt+Yn9mVHFkDhicVttvKLsoCMLVVL+xTQjfjB4X4vtznauxv0QZ5ZAeqjvat0dh62Q==" - }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -4206,14 +4185,6 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, - "node_modules/lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha512-sTebg2a1PoicYEZXD5PBdQcTlIJ6hUslrlWr7iV0O7n+i4596s2NQ9I5CaZ5FbXSfya/9WQsrYLANUJv9paYVA==", - "dependencies": { - "lodash._objecttypes": "~2.4.1" - } - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6454,14 +6425,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/toxic": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toxic/-/toxic-1.0.1.tgz", - "integrity": "sha512-WI3rIGdcaKULYg7KVoB0zcjikqvcYYvcuT6D89bFPz2rVR0Rl0PK6x8/X62rtdLtBKIE985NzVf/auTtGegIIg==", - "dependencies": { - "lodash": "^4.17.10" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", diff --git a/package.json b/package.json index 876e9a4..9fb0a8a 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "compression": "^1.7.0", "connect": "^3.7.0", "destroy": "^1.0.4", - "glob-slasher": "^1.0.1", "is-url": "^1.2.2", "join-path": "^1.1.1", "lodash": "^4.17.19", diff --git a/src/middleware/headers.js b/src/middleware/headers.js index cae67ec..beba78e 100644 --- a/src/middleware/headers.js +++ b/src/middleware/headers.js @@ -20,7 +20,7 @@ */ const _ = require("lodash"); -const slasher = require("glob-slasher"); +const { slasher } = require("../utils/slasher"); const urlParser = require("url"); const onHeaders = require("on-headers"); const patterns = require("../utils/patterns"); diff --git a/src/middleware/redirects.js b/src/middleware/redirects.js index c4705c7..9e09918 100644 --- a/src/middleware/redirects.js +++ b/src/middleware/redirects.js @@ -24,7 +24,7 @@ const _ = require("lodash"); const patterns = require("../utils/patterns"); const pathToRegexp = require("path-to-regexp"); -const slasher = require("glob-slasher"); +const { slasher } = require("../utils/slasher"); function formatExternalUrl(u) { const cleaned = u diff --git a/src/middleware/rewrites.ts b/src/middleware/rewrites.ts index 8f17da7..76ba89b 100644 --- a/src/middleware/rewrites.ts +++ b/src/middleware/rewrites.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const slasher = require("glob-slasher"); +const { slasher } = require("../utils/slasher"); import * as urlParser from "url"; import { NextFunction } from "connect"; import { IncomingMessage, ServerResponse } from "http"; diff --git a/src/utils/slasher.ts b/src/utils/slasher.ts new file mode 100644 index 0000000..be06adc --- /dev/null +++ b/src/utils/slasher.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2026 Google LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import * as path from "path"; + +/** + * Adds a leading slash to a glob pattern and normalizes it using POSIX path + * separators. Handles negated globs (e.g. `!src` → `!/src`). + * Uses path.posix to ensure forward slashes on all platforms, including Windows. + * @param spec glob pattern to normalize + * @returns normalized glob pattern with a leading slash + */ +export function slasher( + spec: string | null | undefined, +): string | null | undefined { + if (!spec) { + return spec; + } + + if (spec.startsWith("!")) { + return "!" + path.posix.normalize(path.posix.join("/", spec.slice(1))); + } + + return path.posix.normalize(path.posix.join("/", spec)); +} diff --git a/test/unit/utils/slasher.spec.ts b/test/unit/utils/slasher.spec.ts new file mode 100644 index 0000000..dfbd9cd --- /dev/null +++ b/test/unit/utils/slasher.spec.ts @@ -0,0 +1,31 @@ +import { expect } from "chai"; +import { slasher } from "../../../src/utils/slasher"; + +describe("slasher", () => { + it("adds a leading slash to a plain path", () => { + expect(slasher("pathname")).to.equal("/pathname"); + }); + + it("is idempotent on already-slashed paths", () => { + expect(slasher("/pathname")).to.equal("/pathname"); + }); + + it("handles glob wildcards", () => { + expect(slasher("**")).to.equal("/**"); + expect(slasher("api/**")).to.equal("/api/**"); + }); + + it("handles negated globs", () => { + expect(slasher("!**/something")).to.equal("!/**/something"); + expect(slasher("!/already/slashed")).to.equal("!/already/slashed"); + }); + + it("normalizes double leading slashes", () => { + expect(slasher("//double")).to.equal("/double"); + }); + + it("passes through falsy values", () => { + expect(slasher(null)).to.equal(null); + expect(slasher(undefined)).to.equal(undefined); + }); +}); From 77dbaf1b7331fa6190fb39f2f1c7d25d4083bbef Mon Sep 17 00:00:00 2001 From: Frank Koenders Date: Sun, 22 Mar 2026 17:12:40 +0100 Subject: [PATCH 2/2] Fix package-lock.json --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index acd9441..e260c90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4158,12 +4158,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",