Add JS-CATCH-FALLBACK-01 rule and update npm packages

Add PHP-ALIAS-01 rule: prohibit field aliasing in serialization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-23 07:36:18 +00:00
parent 3cc590186a
commit 3ce82a924a
1256 changed files with 6491 additions and 3989 deletions

View File

@@ -34,6 +34,8 @@ const memoize = require("../util/memoize");
const getHttp = memoize(() => require("http"));
const getHttps = memoize(() => require("https"));
const MAX_REDIRECTS = 5;
/**
* @param {typeof import("http") | typeof import("https")} request request
* @param {string | URL | undefined} proxy proxy
@@ -200,6 +202,22 @@ const areLockfileEntriesEqual = (a, b) =>
const entryToString = (entry) =>
`resolved: ${entry.resolved}, integrity: ${entry.integrity}, contentType: ${entry.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, "");
}
};
class Lockfile {
constructor() {
this.version = 1;
@@ -317,7 +335,7 @@ const cachedWithoutKey = (fn) => {
* @template R
* @param {FnWithKey<T, R>} fn function
* @param {FnWithKey<T, R>=} forceFn function for the second try
* @returns {(FnWithKey<T, R>) & { force: FnWithKey<T, R> }} cached function
* @returns {FnWithKey<T, R> & { force: FnWithKey<T, R> }} cached function
*/
const cachedWithKey = (fn, forceFn = fn) => {
/**
@@ -636,12 +654,47 @@ class HttpUriPlugin {
};
for (const { scheme, fetch } of schemes) {
/**
* @param {string} location Location header value (relative or absolute)
* @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)}`,
{ cause: err }
);
}
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 | null} integrity integrity
* @param {(err: Error | null, resolveContentResult?: ResolveContentResult) => void} callback callback
* @param {number=} redirectCount number of followed redirects
*/
const resolveContent = (url, integrity, callback) => {
const resolveContent = (
url,
integrity,
callback,
redirectCount = 0
) => {
/**
* @param {Error | null} err error
* @param {FetchResult=} _result fetch result
@@ -653,8 +706,18 @@ class HttpUriPlugin {
const result = /** @type {FetchResult} */ (_result);
if ("location" in result) {
// Validate redirect target before following
let absolute;
try {
absolute = validateRedirectLocation(result.location, url);
} catch (err_) {
return callback(/** @type {Error} */ (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);
@@ -665,7 +728,8 @@ class HttpUriPlugin {
content,
storeLock: storeLock && result.storeLock
});
}
},
redirectCount + 1
);
}
@@ -781,9 +845,16 @@ class HttpUriPlugin {
res.statusCode >= 301 &&
res.statusCode <= 308
) {
const result = {
location: new URL(location, url).href
};
let absolute;
try {
absolute = validateRedirectLocation(location, url);
} catch (err) {
logger.log(
`GET ${url} [${res.statusCode}] -> ${String(location)} (rejected: ${/** @type {Error} */ (err).message})`
);
return callback(/** @type {Error} */ (err));
}
const result = { location: absolute };
if (
!cachedResult ||
!("location" in cachedResult) ||
@@ -882,12 +953,28 @@ class HttpUriPlugin {
* @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;
} else if (allowed.test(uri)) {
if (allowed(parsedUri.href)) return true;
} else if (allowed.test(parsedUri.href)) {
return true;
}
}