Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bin/webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const isInstalled = packageName => {
do {
try {
if (
fs.statSync(path.join(dir, "node_modules", packageName)).isDirectory()
fs.statSync('/usr/share/nodejs/webpack-cli/').isDirectory()
) {
return true;
}
Expand All @@ -62,7 +62,7 @@ const isInstalled = packageName => {
*/
const runCli = cli => {
const path = require("path");
const pkgPath = require.resolve(`${cli.package}/package.json`);
const pkgPath = require.resolve(`/usr/share/nodejs/${cli.package}/package.json`);
// eslint-disable-next-line node/no-missing-require
const pkg = require(pkgPath);
// eslint-disable-next-line node/no-missing-require
Expand Down
17 changes: 17 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
node-webpack (5.76.1+dfsg1+~cs17.16.16-1deepin1) unstable; urgency=medium

* Fix CVE-2025-68157, CVE-2025-68458: SSRF vulnerabilities in
HttpUriPlugin CVE-2025-68157: The HTTP(S) resolver enforces
allowedUris only for the initial URL, but does not re-validate
after following HTTP 30x redirects. This allows an import that
appears restricted to a trusted allow-list to be redirected to
URLs outside the allow-list. CVE-2025-68458: The HTTP(S) resolver
can be bypassed to fetch resources from hosts outside allowedUris
by using crafted URLs that include userinfo
(username:password@host). If allowedUris enforcement relies on a raw
string prefix check, a URL that looks allow-listed can pass
validation while the actual network request is sent to a different
authority/host.

-- Yadd <yadd@debian.org> Fri, 08 May 2026 00:55:50 +0800

node-webpack (5.76.1+dfsg1+~cs17.16.16-1) unstable; urgency=medium

* Team upload
Expand Down
178 changes: 178 additions & 0 deletions debian/patches/CVE-2025-68157-68458.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
Description: Fix SSRF vulnerabilities in HttpUriPlugin
CVE-2025-68157: The HTTP(S) resolver enforces allowedUris only for the initial
URL, but does not re-validate after following HTTP 30x redirects. This allows
an import that appears restricted to a trusted allow-list to be redirected to
URLs outside the allow-list.
CVE-2025-68458: The HTTP(S) resolver can be bypassed to fetch resources from
hosts outside allowedUris by using crafted URLs that include userinfo
(username:password@host). If allowedUris enforcement relies on a raw string
prefix check, a URL that looks allow-listed can pass validation while the
actual network request is sent to a different authority/host.
Origin: upstream, https://github.com/webpack/webpack/commit/2179fdbcb and https://github.com/webpack/webpack/commit/c51007023
Bug: https://github.com/webpack/webpack/security/advisories/GHSA-38r7-794h-5758
Bug: https://github.com/webpack/webpack/security/advisories/GHSA-8fgc-7cc6-rx7x
Forwarded: not-needed
Last-Update: 2026-05-08

--- a/lib/schemes/HttpUriPlugin.js
+++ b/lib/schemes/HttpUriPlugin.js
@@ -20,6 +20,8 @@

const getHttp = memoize(() => require("http"));
const getHttps = memoize(() => require("https"));
+
+const MAX_REDIRECTS = 5;
const proxyFetch = (request, proxy) => (url, options, callback) => {
const eventEmitter = new EventEmitter();
const doRequest = socket =>
@@ -139,6 +141,22 @@
* @property {string} contentType
*/

+/**
+ * Sanitize URL for inclusion in error messages
+ * @param {string} href URL string to sanitize
+ * @returns {string} sanitized URL text for logs/errors
+ */
+const sanitizeUrlForError = href => {
+ try {
+ const u = new URL(href);
+ return u.protocol + "//" + u.host;
+ } catch (_err) {
+ return String(href)
+ .slice(0, 200)
+ .replace(/[\r\n]/g, "");
+ }
+};
+
const areLockfileEntriesEqual = (a, b) => {
return (
a.resolved === b.resolved &&
@@ -503,17 +521,56 @@

for (const { scheme, fetch } of schemes) {
/**
- *
+ * Validate redirect location against allowedUris
+ * @param {string} location Location header value
+ * @param {string} base Current absolute URL
+ * @returns {string} absolute, validated redirect target
+ */
+ const validateRedirectLocation = (location, base) => {
+ let nextUrl;
+ try {
+ nextUrl = new URL(location, base);
+ } catch (_err) {
+ throw new Error(
+ "Invalid redirect URL: " + sanitizeUrlForError(location)
+ );
+ }
+ if (nextUrl.protocol !== "http:" && nextUrl.protocol !== "https:") {
+ throw new Error(
+ "Redirected URL uses disallowed protocol: " + sanitizeUrlForError(nextUrl.href)
+ );
+ }
+ if (!isAllowed(nextUrl.href)) {
+ throw new Error(
+ nextUrl.href + " doesn't match the allowedUris policy after redirect. These URIs are allowed:\n" +
+ allowedUris.map(uri => " - " + uri).join("\n")
+ );
+ }
+ return nextUrl.href;
+ };
+
+ /**
* @param {string} url URL
* @param {string} integrity integrity
* @param {function((Error | null)=, { entry: LockfileEntry, content: Buffer, storeLock: boolean }=): void} callback callback
+ * @param {number=} redirectCount number of followed redirects
*/
- const resolveContent = (url, integrity, callback) => {
+ const resolveContent = (url, integrity, callback, redirectCount = 0) => {
const handleResult = (err, result) => {
if (err) return callback(err);
if ("location" in result) {
+ // Validate redirect target before following
+ let absolute;
+ try {
+ absolute = validateRedirectLocation(result.location, url);
+ } catch (err_) {
+ return callback(err_);
+ }
+ if (redirectCount >= MAX_REDIRECTS) {
+ return callback(new Error("Too many redirects"));
+ }
return resolveContent(
- result.location,
+ absolute,
integrity,
(err, innerResult) => {
if (err) return callback(err);
@@ -522,7 +579,8 @@
content: innerResult.content,
storeLock: innerResult.storeLock && result.storeLock
});
- }
+ },
+ redirectCount + 1
);
} else {
if (
@@ -644,8 +702,17 @@
res.statusCode >= 301 &&
res.statusCode <= 308
) {
+ let absolute;
+ try {
+ absolute = new URL(location, url).href;
+ } catch (err) {
+ logger.log(
+ `GET ${url} [${res.statusCode}] -> ${String(location)} (rejected: ${err.message})`
+ );
+ return callback(err);
+ }
const result = {
- location: new URL(location, url).href
+ location: absolute
};
if (
!cachedResult ||
@@ -739,14 +806,35 @@
(url, callback) => fetchContentRaw(url, undefined, callback)
);

+ /**
+ * Check if a URI is allowed
+ * @param {string} uri URI to check
+ * @returns {boolean} true when allowed, otherwise false
+ */
const isAllowed = uri => {
+ let parsedUri;
+ try {
+ // Parse the URI to prevent userinfo bypass attacks
+ // (e.g., http://allowed@malicious/path where @malicious is the actual host)
+ parsedUri = new URL(uri);
+ } catch (_err) {
+ return false;
+ }
for (const allowed of allowedUris) {
if (typeof allowed === "string") {
- if (uri.startsWith(allowed)) return true;
+ let parsedAllowed;
+ try {
+ parsedAllowed = new URL(allowed);
+ } catch (_err) {
+ continue;
+ }
+ if (parsedUri.href.startsWith(parsedAllowed.href)) {
+ return true;
+ }
} else if (typeof allowed === "function") {
- if (allowed(uri)) return true;
+ if (allowed(parsedUri.href)) return true;
} else {
- if (allowed.test(uri)) return true;
+ if (allowed.test(parsedUri.href)) return true;
}
}
return false;
1 change: 1 addition & 0 deletions debian/patches/series
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ webpack-cli-path.patch
terser-webpack-plugin.patch
fix-for-jest-29.patch
fix-tsconfig.patch
CVE-2025-68157-68458.patch
2 changes: 1 addition & 1 deletion lib/optimize/ConcatenatedModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"use strict";

const eslintScope = require("eslint-scope");
const Referencer = require("eslint-scope/lib/referencer");
const Referencer = eslintScope.Referencer;
const {
CachedSource,
ConcatSource,
Expand Down
104 changes: 96 additions & 8 deletions lib/schemes/HttpUriPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const memoize = require("../util/memoize");

const getHttp = memoize(() => require("http"));
const getHttps = memoize(() => require("https"));

const MAX_REDIRECTS = 5;
const proxyFetch = (request, proxy) => (url, options, callback) => {
const eventEmitter = new EventEmitter();
const doRequest = socket =>
Expand Down Expand Up @@ -139,6 +141,22 @@ const parseCacheControl = (cacheControl, requestTime) => {
* @property {string} contentType
*/

/**
* Sanitize URL for inclusion in error messages
* @param {string} href URL string to sanitize
* @returns {string} sanitized URL text for logs/errors
*/
const sanitizeUrlForError = href => {
try {
const u = new URL(href);
return u.protocol + "//" + u.host;
} catch (_err) {
return String(href)
.slice(0, 200)
.replace(/[\r\n]/g, "");
}
};

const areLockfileEntriesEqual = (a, b) => {
return (
a.resolved === b.resolved &&
Expand Down Expand Up @@ -503,17 +521,56 @@ class HttpUriPlugin {

for (const { scheme, fetch } of schemes) {
/**
*
* Validate redirect location against allowedUris
* @param {string} location Location header value
* @param {string} base Current absolute URL
* @returns {string} absolute, validated redirect target
*/
const validateRedirectLocation = (location, base) => {
let nextUrl;
try {
nextUrl = new URL(location, base);
} catch (_err) {
throw new Error(
"Invalid redirect URL: " + sanitizeUrlForError(location)
);
}
if (nextUrl.protocol !== "http:" && nextUrl.protocol !== "https:") {
throw new Error(
"Redirected URL uses disallowed protocol: " + sanitizeUrlForError(nextUrl.href)
);
}
if (!isAllowed(nextUrl.href)) {
throw new Error(
nextUrl.href + " doesn't match the allowedUris policy after redirect. These URIs are allowed:\n" +
allowedUris.map(uri => " - " + uri).join("\n")
);
}
return nextUrl.href;
};

/**
* @param {string} url URL
* @param {string} integrity integrity
* @param {function((Error | null)=, { entry: LockfileEntry, content: Buffer, storeLock: boolean }=): void} callback callback
* @param {number=} redirectCount number of followed redirects
*/
const resolveContent = (url, integrity, callback) => {
const resolveContent = (url, integrity, callback, redirectCount = 0) => {
const handleResult = (err, result) => {
if (err) return callback(err);
if ("location" in result) {
// Validate redirect target before following
let absolute;
try {
absolute = validateRedirectLocation(result.location, url);
} catch (err_) {
return callback(err_);
}
if (redirectCount >= MAX_REDIRECTS) {
return callback(new Error("Too many redirects"));
}
return resolveContent(
result.location,
absolute,
integrity,
(err, innerResult) => {
if (err) return callback(err);
Expand All @@ -522,7 +579,8 @@ class HttpUriPlugin {
content: innerResult.content,
storeLock: innerResult.storeLock && result.storeLock
});
}
},
redirectCount + 1
);
} else {
if (
Expand Down Expand Up @@ -644,8 +702,17 @@ class HttpUriPlugin {
res.statusCode >= 301 &&
res.statusCode <= 308
) {
let absolute;
try {
absolute = new URL(location, url).href;
} catch (err) {
logger.log(
`GET ${url} [${res.statusCode}] -> ${String(location)} (rejected: ${err.message})`
);
return callback(err);
}
const result = {
location: new URL(location, url).href
location: absolute
};
if (
!cachedResult ||
Expand Down Expand Up @@ -739,14 +806,35 @@ class HttpUriPlugin {
(url, callback) => fetchContentRaw(url, undefined, callback)
);

/**
* Check if a URI is allowed
* @param {string} uri URI to check
* @returns {boolean} true when allowed, otherwise false
*/
const isAllowed = uri => {
let parsedUri;
try {
// Parse the URI to prevent userinfo bypass attacks
// (e.g., http://allowed@malicious/path where @malicious is the actual host)
parsedUri = new URL(uri);
} catch (_err) {
return false;
}
for (const allowed of allowedUris) {
if (typeof allowed === "string") {
if (uri.startsWith(allowed)) return true;
let parsedAllowed;
try {
parsedAllowed = new URL(allowed);
} catch (_err) {
continue;
}
if (parsedUri.href.startsWith(parsedAllowed.href)) {
return true;
}
} else if (typeof allowed === "function") {
if (allowed(uri)) return true;
if (allowed(parsedUri.href)) return true;
} else {
if (allowed.test(uri)) return true;
if (allowed.test(parsedUri.href)) return true;
}
}
return false;
Expand Down
Loading
Loading