diff --git a/docs/config/server-options.md b/docs/config/server-options.md index dbeec4428120f5..a9d5d52df3c826 100644 --- a/docs/config/server-options.md +++ b/docs/config/server-options.md @@ -90,7 +90,7 @@ Configure custom proxy rules for the dev server. Expects an object of `{ key: op Note that if you are using non-relative [`base`](/config/shared-options.md#base), you must prefix each key with that `base`. -Extends [`http-proxy`](https://github.com/http-party/node-http-proxy#options). Additional options are [here](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/proxy.ts#L13). Note that [unlike http-proxy](https://github.com/http-party/node-http-proxy/issues/1669), the `changeOrigin` option will change both host and origin headers to match the target. +Extends [`http-proxy`](https://github.com/http-party/node-http-proxy#options). Additional options are [here](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/proxy.ts#L13). In some cases, you might also want to configure the underlying dev server (e.g. to add custom middlewares to the internal [connect](https://github.com/senchalabs/connect) app). In order to do that, you need to write your own [plugin](/guide/using-plugins.html) and use [configureServer](/guide/api-plugin.html#configureserver) function. @@ -123,9 +123,11 @@ export default defineConfig({ }, }, // Proxying websockets or socket.io: ws://localhost:5173/socket.io -> ws://localhost:5174/socket.io + // Exercise caution using `rewriteWsOrigin` as it can leave the proxying open to CSRF attacks. '/socket.io': { target: 'ws://localhost:5174', ws: true, + rewriteWsOrigin: true, }, }, }, diff --git a/packages/vite/src/node/server/middlewares/proxy.ts b/packages/vite/src/node/server/middlewares/proxy.ts index 4ceb4d8d435426..17f6d7a235d583 100644 --- a/packages/vite/src/node/server/middlewares/proxy.ts +++ b/packages/vite/src/node/server/middlewares/proxy.ts @@ -27,20 +27,35 @@ export interface ProxyOptions extends HttpProxy.ServerOptions { res: http.ServerResponse, options: ProxyOptions, ) => void | null | undefined | false | string + /** + * rewrite the Origin header of a WebSocket request to match the the target + * + * **Exercise caution as rewriting the Origin can leave the proxying open to [CSRF attacks](https://owasp.org/www-community/attacks/csrf).** + */ + rewriteWsOrigin?: boolean | undefined } -const setOriginHeader = ( +const rewriteOriginHeader = ( proxyReq: http.ClientRequest, - options: HttpProxy.ServerOptions, + options: ProxyOptions, + config: ResolvedConfig, ) => { // Browsers may send Origin headers even with same-origin // requests. It is common for WebSocket servers to check the Origin - // header, so if changeOrigin is true we change the Origin to match + // header, so if rewriteWsOrigin is true we change the Origin to match // the target URL. - // https://github.com/http-party/node-http-proxy/issues/1669 - if (options.changeOrigin) { + if (options.rewriteWsOrigin) { const { target } = options + if (proxyReq.headersSent) { + config.logger.warn( + colors.yellow( + `Unable to rewrite Origin header as headers are already sent.`, + ), + ) + return + } + if (proxyReq.getHeader('origin') && target) { const changedOrigin = typeof target === 'object' @@ -112,12 +127,8 @@ export function proxyMiddleware( } }) - proxy.on('proxyReq', (proxyReq, req, res, options) => { - setOriginHeader(proxyReq, options) - }) - proxy.on('proxyReqWs', (proxyReq, req, socket, options, head) => { - setOriginHeader(proxyReq, options) + rewriteOriginHeader(proxyReq, options, config) socket.on('error', (err) => { config.logger.error(