Skip to content

Commit c8829ea

Browse files
authored
Merge pull request #6794 from Shopify/josh-faith-pairing-01-20
fix SSRF vulnerability in proxy.ts
2 parents 552839e + 8048cb9 commit c8829ea

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

.changeset/rich-swans-draw.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/theme': patch
3+
---
4+
5+
Protect SSRF vulnerability in proxy requests when hosts don't match

packages/theme/src/cli/utilities/theme-environment/proxy.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import {canProxyRequest, getProxyStorefrontHeaders, injectCdnProxy, patchRenderingResponse} from './proxy.js'
1+
import {
2+
canProxyRequest,
3+
getProxyStorefrontHeaders,
4+
injectCdnProxy,
5+
patchRenderingResponse,
6+
proxyStorefrontRequest,
7+
} from './proxy.js'
28
import {describe, test, expect} from 'vitest'
39
import {createEvent} from 'h3'
410
import {IncomingMessage, ServerResponse} from 'node:http'
@@ -338,4 +344,18 @@ describe('dev proxy', () => {
338344
expect(canProxyRequest(event)).toBeTruthy()
339345
})
340346
})
347+
describe('proxyStorefrontRequest', () => {
348+
test('should reject hostname mismatch and throw error for non-CDN paths (SSRF protection)', async () => {
349+
const event = createH3Event('GET', '//evil.com/some-path')
350+
await expect(proxyStorefrontRequest(event, ctx)).rejects.toThrow(
351+
'Request failed: Hostname mismatch. Expected host: my-store.myshopify.com. Resulting URL hostname: evil.com',
352+
)
353+
})
354+
test('should reject hostname mismatch and throw error for CDN paths (SSRF protection)', async () => {
355+
const event = createH3Event('GET', '/ext/cdn//evil.com/some-path')
356+
await expect(proxyStorefrontRequest(event, ctx)).rejects.toThrow(
357+
'Request failed: Hostname mismatch. Expected host: cdn.shopify.com. Resulting URL hostname: evil.com',
358+
)
359+
})
360+
})
341361
})

packages/theme/src/cli/utilities/theme-environment/proxy.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ export function proxyStorefrontRequest(event: H3Event, ctx: DevServerContext): P
293293
const host = event.path.startsWith(EXTENSION_CDN_PREFIX) ? 'cdn.shopify.com' : ctx.session.storeFqdn
294294
const url = new URL(path, `https://${host}`)
295295

296+
// Check that we aren't redirecting to external hosts
297+
if (url.hostname !== host) {
298+
return Promise.reject(
299+
new Error(`Request failed: Hostname mismatch. Expected host: ${host}. Resulting URL hostname: ${url.hostname}`),
300+
)
301+
}
302+
296303
// When a .css.liquid or .js.liquid file is requested but it doesn't exist in SFR,
297304
// it will be rendered with a query string like `assets/file.css?1234`.
298305
// For some reason, after refreshing, this rendered URL keeps the wrong `?1234`

0 commit comments

Comments
 (0)