UNPKG

next

Version:

The React Framework

100 lines (99 loc) 5.02 kB
import { parseUrl } from '../../../lib/url'; import { warnOnce } from '../../../build/output/log'; import { isCsrfOriginAllowed } from '../../app-render/csrf-protection'; const allowedDevOriginsDocs = 'https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins'; function getBlockedResourcePath(req) { var _parseUrl; return ((_parseUrl = parseUrl(req.url ?? '')) == null ? void 0 : _parseUrl.pathname) ?? req.url ?? '/_next/*'; } function formatBlockedCrossSiteMessage(source, resourcePath) { const lines = [ `Blocked cross-origin request to Next.js dev resource ${resourcePath}${getBlockedSourceDescription(source)}.`, 'Cross-origin access to Next.js dev resources is blocked by default for safety.' ]; // `source` has 3 meanings here: // - `'null'`: browser explicitly sent `Origin: null` for an opaque/sandboxed origin // - hostname string: we parsed an allowlistable host from Origin/Referer // - `undefined` (and effectively empty string): the request did not include a usable host if (source === 'null') { lines.push('', 'This request came from a privacy-sensitive or opaque origin, so Next.js cannot determine which host to allow.', 'If you need it to succeed, load the dev server from a normal origin and add that host to "allowedDevOrigins".'); } else if (source) { lines.push('', 'To allow this host in development, add it to "allowedDevOrigins" in next.config.js and restart the dev server:', '', '// next.config.js', 'module.exports = {', ` allowedDevOrigins: ['${source}'],`, '}'); } else { lines.push('', 'This request did not include an allowlistable source host.', 'If you need it to succeed, make sure the browser sends an Origin or Referer from a host listed in "allowedDevOrigins".'); } lines.push('', `Read more: ${allowedDevOriginsDocs}`); return lines.join('\n'); } function getBlockedSourceDescription(source) { if (source === 'null') { return ' from a privacy-sensitive or opaque origin'; } if (source) { return ` from "${source}"`; } return ' from an unknown source'; } function blockRequest(req, res, source) { warnOnce(formatBlockedCrossSiteMessage(source, getBlockedResourcePath(req))); if ('statusCode' in res) { res.statusCode = 403; } res.end('Unauthorized'); return true; } function parseHostnameFromHeader(header) { const headerValue = Array.isArray(header) ? header[0] : header; if (!headerValue || headerValue === 'null') { return; } const parsedHeader = parseUrl(headerValue); return parsedHeader == null ? void 0 : parsedHeader.hostname.toLowerCase(); } function isInternalEndpoint(req) { if (!req.url) return false; try { // TODO: We should standardize on a single prefix for this const isMiddlewareRequest = req.url.includes('/__nextjs'); const isInternalAsset = req.url.includes('/_next'); // Static media requests are excluded, as they might be loaded via CSS and would fail // CORS checks. const isIgnoredRequest = req.url.includes('/_next/image') || req.url.includes('/_next/static/media'); return !isIgnoredRequest && (isInternalAsset || isMiddlewareRequest); } catch (err) { return false; } } export const blockCrossSiteDEV = (req, res, allowedDevOrigins, hostname)=>{ const allowedOrigins = [ '*.localhost', 'localhost', ...allowedDevOrigins ?? [] ]; if (hostname) { allowedOrigins.push(hostname); } // only process internal URLs/middleware if (!isInternalEndpoint(req)) { return false; } // block non-cors request from cross-site e.g. script tag on // different host if (req.headers['sec-fetch-mode'] === 'no-cors' && req.headers['sec-fetch-site'] === 'cross-site') { // no-cors requests do not send an Origin header, so fall back to Referer // when validating configured cross-site script loads. const refererHostname = parseHostnameFromHeader(req.headers['referer']); if (refererHostname && isCsrfOriginAllowed(refererHostname, allowedOrigins)) { return false; } return blockRequest(req, res, refererHostname); } // ensure websocket requests are only fulfilled from allowed origin const rawOrigin = req.headers['origin']; const originHeader = Array.isArray(rawOrigin) ? rawOrigin[0] : rawOrigin; const parsedOrigin = originHeader && originHeader !== 'null' ? parseUrl(originHeader) : originHeader; const originLowerCase = parsedOrigin === undefined || typeof parsedOrigin === 'string' ? parsedOrigin : parsedOrigin.hostname.toLowerCase(); // Allow requests with no origin since those are just GET requests from same-site return originLowerCase !== undefined && !isCsrfOriginAllowed(originLowerCase, allowedOrigins) && blockRequest(req, res, originLowerCase); }; //# sourceMappingURL=block-cross-site-dev.js.map