Limites CORS del navegador: cabeceras, preflight, credenciales y cache
Guia practica para diagnosticar fetch entre origenes sin confundir CORS con red, autorizacion, CSP o Permissions Policy.
BotBrowser Team
Quieres la documentación estructurada de Plataforma?
Este artículo forma parte de la biblioteca editorial. Para pasos de configuración, material de referencia y actualizaciones continuas, entra en la sección de docs.
let pageOrigin = '';
let apiOrigin = '';
const observations = [];
const listen = server => new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
const close = server => new Promise(resolve => server.close(resolve));
const api = http.createServer((request, response) => {
const name = request.url.slice(1);
if (name === 'control') response.setHeader('Access-Control-Allow-Origin', pageOrigin);
observations.push({ name, status: 200, allowOrigin: response.getHeader('Access-Control-Allow-Origin') || null });
response.writeHead(200, { 'Content-Type': 'application/json' });
response.end('{"fixture":true}');
});
const pageServer = http.createServer((request, response) => {
const name = request.url.slice(1);
response.end(<button>Run CORS check</button><output data-testid="cors-status"></output><script> document.querySelector('button').onclick = async () => { try { await fetch('${apiOrigin}/${name}'); document.querySelector('output').textContent = 'CORS_ALLOWED'; } catch { document.querySelector('output').textContent = 'FETCH_UNCLASSIFIED'; } };</script>);
});
await listen(api); await listen(pageServer);
const apiPort = api.address().port; const pagePort = pageServer.address().port;
pageOrigin = http://127.0.0.1:${pagePort};
apiOrigin = http://127.0.0.1:${apiPort};
const browser = await chromium.launch();
const context = await browser.newContext();
context.setDefaultNavigationTimeout(8_000);
async function runCase(name) {
const page = await context.newPage();
try {
await page.goto(${pageOrigin}/${name});
await page.getByRole('button', { name: 'Run CORS check' }).click();
await page.locator('[data-testid="cors-status"]').waitFor({ state: 'visible', timeout: 5_000 });
return await page.locator('[data-testid="cors-status"]').textContent();
} catch { return 'UNKNOWN'; }
finally { await page.close(); }
}
try {
const control = await runCase('control');
const candidate = await runCase('candidate');
assert.equal(control, 'CORS_ALLOWED');
assert.equal(candidate, 'FETCH_UNCLASSIFIED');
assert.deepEqual(observations.map(({ name, allowOrigin }) => ({ name, allowOrigin })), [
{ name: 'control', allowOrigin: pageOrigin }, { name: 'candidate', allowOrigin: null }
]);
} finally { await context.close(); await browser.close(); await close(pageServer); await close(api); }
Control debe mostrar `CORS_ALLOWED` tras leer el cuerpo sintetico. Candidate captura el rechazo de Fetch y muestra `FETCH_UNCLASSIFIED`: ese estado por si solo no identifica CORS. Solo confirma `CORS_BLOCKED` cuando el registro de la API propia demuestra que candidate llego al endpoint, ambas rutas devolvieron el mismo cuerpo y 200, candidate omitio deliberadamente `Access-Control-Allow-Origin`, y control fue legible con el origen exacto de la pagina. Guarda el registro, las cabeceras candidate y el registro DevTools.
Si navegacion, estado o servidor fallan, el resultado es `UNKNOWN`; no lo atribuyas a red ni CORS. La navegacion tiene limite de ocho segundos y cada page se cierra en cleanup.
## Tabla de decision
| Observacion | Responsable inicial | Comprobacion | Conclusion segura |
| --------------------------------- | ------------------- | ------------------------------------------------ | -------------------------------- |
| DNS, TLS o conexion falla | Red/plataforma | Ruta, proxy y salud del servidor | No hubo respuesta CORS |
| Preflight devuelve 4xx/5xx | API/plataforma | Ruta `OPTIONS`, auth, metodo y cabeceras | Fallo el intercambio previo |
| Hay 200 pero el script no lee | API/navegador | Allow-Origin, credenciales y headers expuestos | Fallo compartir CORS o fue opaca |
| Cookie ausente | Auth/navegador | modo de credenciales, SameSite, Secure y alcance | CORS no habilita cookies |
| Funciona para un origen y no otro | API/cache | Allow-list y `Vary: Origin` | La politica o cache difiere |
| Violacion CSP | Seguridad web | `connect-src` y reportes | El bloqueo es CSP, no CORS |
Encaje de BotBrowser
Los BrowserContexts controlados de BotBrowser pueden ejecutar fixtures propios autorizados de control y candidate, aislar cookies y almacenamiento sinteticos y comparar resultados Fetch visibles en un build declarado. La [documentacion de aislamiento multi-cuenta](https://botbrowser.io/docs/identity/multi-account-isolation/) describe ese limite de contexto.
Para validar CORS, BotBrowser puede ejecutar la comparacion control/candidate, pero no puede configurar la respuesta API ni eludir la regla de comparticion del navegador.
BotBrowser no escribe ni despliega cabeceras CORS, no concede autorizacion del servidor, no repara una API de terceros, no cambia el origen y no elude la aplicacion CORS del navegador. Tampoco demuestra que una operacion remota se haya confirmado. La configuracion del servidor, CDN, credenciales, CSP, Permissions Policy y estado de negocio pertenecen a sus respectivos propietarios.
Checklist operativo
1. Declara origen de pagina y API, metodo, cabeceras, credenciales y resultado esperado.
2. Comprueba DNS, TLS, proxy y ruta antes de interpretar un error de politica.
3. Captura Origin, preflight, estado, cabeceras CORS, `Vary` y cache.
4. Separa CORS de CSP, Permissions Policy, permiso de usuario y autorizacion.
5. Usa el fixture propio con navegacion de ocho segundos, estado de cinco y limpieza determinista.
6. Conserva tipos distintos para red, navegacion, timeout, preflight y bloqueo CORS.
**Autor:** BotBrowser Team
Consulta la [guia de aislamiento cross-origin](/es/blog/cross-origin-isolation-and-shared-memory-requirements/) y la [guia de Permissions Policy](/es/blog/permissions-policy-for-embedded-browser-features/).
## Sources
- Fixture URL (example only): https://app.example.test`
- Fixture URL (example only): https://app.example.test/control`
- [Protocolo CORS de Fetch](https://fetch.spec.whatwg.org/#http-cors-protocol)
- [CORS en MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
- [CSP en MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
- [Permissions Policy en MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy)
Artículos Relacionados
Lleva BotBrowser de la investigación a producción
Usa estas guías para entender el modelo y después avanzar hacia validación multiplataforma, contextos aislados y despliegue de navegador preparado para escalar.