Skip to content
Open
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
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module.exports = {
//
// FIXME: this uuid moduleNameMapper workaround can be removed after sockjs > uuid@v9 release
// https://github.com/uuidjs/uuid/pull/616#issuecomment-1206283882
// eslint-disable-next-line n/no-extraneous-require

"^uuid$": require.resolve("uuid"),
},
};
73 changes: 72 additions & 1 deletion lib/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2019,6 +2019,14 @@ class Server {
return;
}

// Block cross-origin resource loading when Sec-Fetch-* headers are absent (HTTP origins)
if (
this.options.allowedHosts !== "all" &&
!this.isUserCORSWildcardEnabled()
) {
res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
}

next();
},
});
Expand Down Expand Up @@ -3184,6 +3192,53 @@ class Server {
return false;
}

/**
* @private
* @returns {boolean} true when the user has configured a wildcard
* Access-Control-Allow-Origin header (opting into fully open cross-origin access)
*/
isUserCORSWildcardEnabled() {
const { headers } = this.options;

if (!headers) {
return false;
}

if (typeof headers === "function") {
return false;
}

/**
* @param {string | string[]} value header value
* @returns {boolean} true when value is the "*" wildcard
*/
const isWildcard = (value) => {
if (typeof value === "string") {
return value.trim() === "*";
}

if (Array.isArray(value)) {
return value.length === 1 && isWildcard(value[0]);
}

return false;
};

if (Array.isArray(headers)) {
return headers.some(
(header) =>
header.key.toLowerCase() === "access-control-allow-origin" &&
isWildcard(header.value),
);
}

return Object.entries(headers).some(
([key, value]) =>
key.toLowerCase() === "access-control-allow-origin" &&
isWildcard(value),
);
}

/**
* @private
* @param {{ [key: string]: string | undefined }} headers headers
Expand Down Expand Up @@ -3288,10 +3343,20 @@ class Server {
return true;
}

const loopbacks = new Set(["localhost", "127.0.0.1", "::1"]);

// url.parse cannot handle bare IPv6 like "::1", need to bracket it first
const hostForParsing =
loopbacks.has(hostHeader) && hostHeader.includes(":")
? `[${hostHeader}]`
: hostHeader;

// eslint-disable-next-line n/no-deprecated-api
const host = url.parse(
// if hostHeader doesn't have scheme, add // for parsing.
/^(.+:)?\/\//.test(hostHeader) ? hostHeader : `//${hostHeader}`,
/^(.+:)?\/\//.test(hostForParsing)
? hostForParsing
: `//${hostForParsing}`,
false,
true,
).hostname;
Expand All @@ -3304,6 +3369,12 @@ class Server {
return true;
}

// Treat all loopback aliases as equivalent, localhost may resolve to
// 127.0.0.1 or ::1 depending on the OS, causing a false mismatch.
if (loopbacks.has(origin) && loopbacks.has(host)) {
return true;
}

return origin === host;
}

Expand Down
Loading