Skip to content

Instantly share code, notes, and snippets.

@navetacandra
Last active July 9, 2025 16:32
Show Gist options
  • Save navetacandra/c5c709920b7d7c4105a7fee34ae0a46f to your computer and use it in GitHub Desktop.
Save navetacandra/c5c709920b7d7c4105a7fee34ae0a46f to your computer and use it in GitHub Desktop.
CORS Proxy Cloudflare Worker
const OMIT_REQUEST_HEADERS = [
'cookie',
'cookie2',
'x-request-start',
'x-request-id',
'via',
'connect-time',
'total-route-time'
];
const ALLOWED_ORIGINS = [
"example.com",
/^localhost(:\d+)?$/,
/^[\s\S]+\.cf-username\.workers\.dev$/i,
// ...
];
const ALLOWED_METHODS = ['GET', 'POST', 'HEAD', 'OPTIONS'];
const PROXY_ENDPOINT = "/request";
function isOriginAllowed(origin) {
return ALLOWED_ORIGINS.some(allowed =>
allowed instanceof RegExp
? allowed.test(origin)
: allowed === origin
);
}
function createProxyRequest(originalRequest, targetUrl) {
const newHeaders = new Headers(originalRequest.headers);
// Remove sensitive headers
OMIT_REQUEST_HEADERS.forEach(header => newHeaders.delete(header));
// Set new Origin header
newHeaders.set('Origin', new URL(targetUrl).hostname);
return new Request(targetUrl, {
method: originalRequest.method,
headers: newHeaders,
body: originalRequest.body,
redirect: 'follow'
});
}
function handleOptions(request) {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': request.headers.get('Origin') || '*',
'Access-Control-Allow-Methods': ALLOWED_METHODS.join(', '),
'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers') || '',
'Access-Control-Expose-Headers': '*',
'Access-Control-Max-Age': '86400',
}
});
}
export default {
async fetch(request, env, ctx) {
const { method } = request;
const url = new URL(request.url);
const origin = request.headers.get('Origin') || '';
const originHostname = new URL(origin).hostname;
// Handle OPTIONS preflight requests
if (method === 'OPTIONS') {
return handleOptions(request);
}
// Validate HTTP method
if (!ALLOWED_METHODS.includes(method)) {
return new Response(`Method ${method} not allowed`, {
status: 405,
headers: { 'Allow': ALLOWED_METHODS.join(', ') }
});
}
// Check origin against whitelist
if (!isOriginAllowed(originHostname)) {
return new Response('Forbidden', { status: 403 });
}
// Non-proxy endpoint handling
if (url.pathname !== PROXY_ENDPOINT) {
return new Response('', {
status: 200,
headers: {
'Content-Type': 'text/plain',
'Access-Control-Allow-Origin': origin
}
});
}
try {
// Validate and decode target URL
const targetUrl = url.searchParams.get('url');
if (!targetUrl) {
throw new Error('Missing target URL');
}
const decodedUrl = decodeURIComponent(targetUrl);
const targetUrlObj = new URL(decodedUrl);
// Prevent proxy recursion
if (targetUrlObj.hostname === url.hostname && targetUrlObj.pathname === PROXY_ENDPOINT) {
throw new Error('Self-proxying disallowed');
}
// Create and execute proxy request
const proxyRequest = createProxyRequest(request, decodedUrl);
const response = await fetch(proxyRequest);
// Create CORS-enabled response
const corsResponse = new Response(response.body, response);
corsResponse.headers.set('Access-Control-Allow-Origin', origin);
corsResponse.headers.set('Access-Control-Expose-Headers', '*');
corsResponse.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src *; font-src 'self'; object-src 'none'; media-src 'self';"
);
return corsResponse;
} catch (error) {
console.error('Proxy error:', error);
return new Response('Bad Request', {
status: 400,
headers: { 'Access-Control-Allow-Origin': origin }
});
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment