From 90ae918e21df5e94168e7c3f6d456286527e7e3a Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 30 Apr 2026 14:46:36 -0400 Subject: [PATCH 1/2] Tighten isRemotePath and dev image endpoint origin check --- .changeset/image-endpoint-cleanup.md | 6 ++++++ packages/astro/src/assets/endpoint/dev.ts | 2 +- packages/internal-helpers/src/path.ts | 4 ++-- packages/internal-helpers/test/path.test.ts | 11 +++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 .changeset/image-endpoint-cleanup.md diff --git a/.changeset/image-endpoint-cleanup.md b/.changeset/image-endpoint-cleanup.md new file mode 100644 index 000000000000..320cbdab3dcc --- /dev/null +++ b/.changeset/image-endpoint-cleanup.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'@astrojs/internal-helpers': patch +--- + +Tightens `isRemotePath()` to reject control characters after a leading slash and fixes the dev image endpoint origin check diff --git a/packages/astro/src/assets/endpoint/dev.ts b/packages/astro/src/assets/endpoint/dev.ts index 21dd9d351c3f..32761d53eb85 100644 --- a/packages/astro/src/assets/endpoint/dev.ts +++ b/packages/astro/src/assets/endpoint/dev.ts @@ -55,7 +55,7 @@ async function loadLocalImage(src: string, url: URL) { const sourceUrl = new URL(src, url.origin); // This is only allowed if this is the same origin if (sourceUrl.origin !== url.origin) { - returnValue = undefined; + return undefined; } return loadRemoteImage(sourceUrl); } diff --git a/packages/internal-helpers/src/path.ts b/packages/internal-helpers/src/path.ts index 11b4c843bf21..4cb608a61fdf 100644 --- a/packages/internal-helpers/src/path.ts +++ b/packages/internal-helpers/src/path.ts @@ -148,9 +148,9 @@ export function isRemotePath(src: string) { return false; } - // Check for Unix absolute path (starts with / but not // or /\) + // Check for Unix absolute path (starts with / followed by a normal path character) // This needs to be before the backslash check - if (decoded[0] === '/' && decoded[1] !== '/' && decoded[1] !== '\\') { + if (decoded[0] === '/' && /^\/[a-zA-Z0-9._@-]/.test(decoded)) { return false; } diff --git a/packages/internal-helpers/test/path.test.ts b/packages/internal-helpers/test/path.test.ts index 2981450c08b6..3b1f89768261 100644 --- a/packages/internal-helpers/test/path.test.ts +++ b/packages/internal-helpers/test/path.test.ts @@ -78,6 +78,17 @@ describe('isRemotePath', () => { '\\%0Aexample.com/test', '\\%0Dexample.com/test', + // CRLF injection in path (control chars after leading slash cause URL parser + // to treat the remainder as a protocol-relative URL) + '/%0d%0a/example.com', + '/%0d%0a/example.com:8080/path', + '/%0a/example.com', + '/%0d/example.com', + '/\r\n/example.com', + '/\n/example.com', + '/\r/example.com', + '/\t/example.com', + // IP addresses 'http://192.168.1.1/test', '//192.168.1.1/test', From 87dcb8d69bf29c77e9fc94a9de202902d1164739 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 30 Apr 2026 14:50:00 -0400 Subject: [PATCH 2/2] Use \w in regex to satisfy eslint regexp/prefer-w --- packages/internal-helpers/src/path.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/internal-helpers/src/path.ts b/packages/internal-helpers/src/path.ts index 4cb608a61fdf..58b0a67cf69e 100644 --- a/packages/internal-helpers/src/path.ts +++ b/packages/internal-helpers/src/path.ts @@ -150,7 +150,7 @@ export function isRemotePath(src: string) { // Check for Unix absolute path (starts with / followed by a normal path character) // This needs to be before the backslash check - if (decoded[0] === '/' && /^\/[a-zA-Z0-9._@-]/.test(decoded)) { + if (decoded[0] === '/' && /^\/[\w.@-]/.test(decoded)) { return false; }