Motor de Analisis
Documentacion tecnica del motor de analisis basado en Puppeteer, Chrome DevTools Protocol y contextos incognito.
El motor de analisis de Legal Cookies se basa en Puppeteer con Chromium headless, complementado con Chrome DevTools Protocol (CDP) para capacidades avanzadas de interceptacion.
Puppeteer y Chromium
Puppeteer es una biblioteca de Node.js que proporciona una API de alto nivel para controlar navegadores Chromium. Lo utilizamos porque:
- Fidelidad total: Ejecuta el mismo motor de renderizado que Chrome, garantizando que vemos exactamente lo que veria un usuario
- Control completo: Acceso a todas las APIs del navegador (cookies, storage, red)
- CDP integrado: Capacidad de usar el protocolo de DevTools para funciones avanzadas
- Headless: Funciona sin interfaz grafica, ideal para automatizacion
Configuracion del navegador
const browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu'
]
});Viewport y User-Agent
Simulamos un navegador de escritorio estandar:
| Configuracion | Valor |
|---|---|
| Viewport | 1280x800 |
| User-Agent | Chrome moderno en Windows/Mac |
| Timeout navegacion | 30 segundos |
| Timeout por defecto | 10 segundos |
Contextos Incognito
Cada analisis se ejecuta en un BrowserContext independiente, equivalente a una ventana de incognito:
// Crear contexto aislado
const context = await browser.createBrowserContext();
// Crear pagina en el contexto
const page = await context.newPage();
// ... realizar analisis ...
// Cerrar contexto (limpia automaticamente todo)
await context.close();Beneficios del contexto incognito
| Beneficio | Descripcion |
|---|---|
| Aislamiento | Cada analisis tiene su propio almacenamiento |
| Limpieza automatica | Al cerrar, se eliminan cookies, cache y storage |
| Sin temporales huerfanos | No se acumulan perfiles de navegador en disco |
| Reproducibilidad | Cada analisis parte del mismo estado inicial |
Antes de implementar contextos incognito, los temporales de Puppeteer podian acumular gigabytes de datos en /tmp. Ahora cada analisis se limpia automaticamente al terminar.
Chrome DevTools Protocol (CDP)
CDP es un protocolo de bajo nivel que permite comunicacion directa con el motor de Chromium. Lo utilizamos para detectar cookies HttpOnly que no son visibles a JavaScript.
El problema de las cookies HttpOnly
Las cookies marcadas como HttpOnly son una medida de seguridad que impide que JavaScript acceda a ellas (proteccion contra XSS). Sin embargo, esto tambien significa que la API estandar de Puppeteer para obtener cookies no las detecta.
Solucion: Interceptar headers HTTP
Activamos una sesion CDP para interceptar todos los headers Set-Cookie de las respuestas HTTP:
// Crear sesion CDP
const cdpSession = await page.createCDPSession();
// Habilitar eventos de red
await cdpSession.send('Network.enable');
// Escuchar respuestas HTTP
cdpSession.on('Network.responseReceived', (params) => {
const setCookieHeaders = params.response.headers['set-cookie'];
if (setCookieHeaders) {
// Parsear y almacenar cookies HttpOnly
parseCookiesFromHeaders(setCookieHeaders);
}
});Parseado de headers Set-Cookie
Los headers Set-Cookie tienen un formato especifico que parseamos:
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=LaxExtraemos:
- Nombre y valor:
session_id=abc123 - Atributos:
Path,HttpOnly,Secure,SameSite,Expires,Max-Age - Dominio: Del header o inferido de la URL
Merge de cookies
El resultado final combina ambas fuentes, eliminando duplicados:
const jsCookies = await extractCookies(page); // Accesibles a JS
const httpOnlyCookies = extractFromHeaders(headers); // HttpOnly via CDP
// Merge por (name + domain + path)
const allCookies = mergeCookies(jsCookies, httpOnlyCookies);Cada cookie incluye un campo source que indica su origen:
| source | Descripcion |
|---|---|
javascript | Detectada via API de Puppeteer |
http-header | Detectada via header Set-Cookie |
Interceptor de Requests
Capturamos todas las peticiones de red para detectar requests a terceros:
await page.setRequestInterception(true);
page.on('request', (request) => {
const url = new URL(request.url());
// Detectar si es third-party
if (url.hostname !== targetDomain) {
thirdPartyRequests.push({
url: request.url(),
domain: url.hostname,
type: request.resourceType()
});
}
request.continue();
});Tipos de recursos capturados
| Tipo | Incluido | Razon |
|---|---|---|
script | Si | Scripts de tracking/analytics |
image | Si | Pixeles de tracking |
xhr/fetch | Si | Beacons y APIs de terceros |
iframe | Si | Embeds de terceros |
stylesheet | No | Generalmente no implica tracking |
font | No | No implica tratamiento de datos |
Captura post-load (10 segundos)
Muchos scripts de tracking se cargan de forma diferida para no afectar al tiempo de carga percibido. Por eso mantenemos el interceptor activo 10 segundos adicionales despues de que la pagina alcance networkidle2:
// Navegar y esperar carga inicial
await page.goto(url, { waitUntil: 'networkidle2' });
// Esperar 10 segundos adicionales para scripts diferidos
await new Promise(resolve => setTimeout(resolve, 10000));
// Ahora si obtener los requests capturados
const requests = getInterceptedRequests();Esta espera de 10 segundos es un balance entre exhaustividad y tiempo de analisis. Scripts que se cargan mas tarde (por ejemplo, tras scroll o interaccion) no seran detectados.
Deteccion de CMPs
Detectamos plataformas de gestion de consentimiento (CMP) con un sistema hibrido de 3 capas:
Capa 1: CMPs conocidos por firma DOM
Buscamos selectores especificos de 25+ CMPs conocidos:
const KNOWN_CMPS = [
{ name: 'OneTrust', selector: '#onetrust-consent-sdk' },
{ name: 'Cookiebot', selector: '#CybotCookiebotDialog' },
{ name: 'Didomi', selector: '#didomi-host' },
{ name: 'TrustArc', selector: '#truste-consent-track' },
// ... 21 mas
];Capa 2: IAB TCF v2.0 API
Si no encontramos un CMP conocido, verificamos si existe la API estandar de la industria:
// Verificar existencia de __tcfapi (IAB TCF v2.0)
const tcfData = await page.evaluate(() => {
if (typeof window.__tcfapi === 'function') {
return { exists: true };
}
return null;
});Capa 3: Heuristica visual
Como ultimo recurso, buscamos banners con palabras clave tipicas de interfaces de consentimiento:
- cookie, consent, privacy, gdpr
- privacidad, aceptar, rechazar
- preferencias, configuracion
Resultado de deteccion
El resultado indica el metodo utilizado:
interface CMPDetection {
name: string | null;
detected: boolean;
bannerVisible: boolean;
configured: boolean;
method?: 'known' | 'tcf_api' | 'heuristic';
tcfCmpId?: number;
}Verificacion de funcionamiento del CMP
Una vez detectado un CMP, intentamos rechazar el consentimiento para verificar si realmente bloquea cookies y scripts:
Proceso de verificacion
- Captura inicial: Registramos todas las cookies presentes antes de interactuar
- Busqueda de boton de rechazo: Usamos selectores especificos para cada CMP conocido
- Click en rechazo: Hacemos click en el boton de "Rechazar" o "Solo necesarias"
- Espera de procesamiento: 2 segundos para que el CMP procese la decision
- Captura final: Registramos las cookies despues del rechazo
- Comparacion: Calculamos que cookies fueron bloqueadas
Selectores de rechazo por CMP
Cada CMP tiene selectores especificos para su boton de rechazo:
const CMP_REJECT_SELECTORS = {
"OneTrust": ["#onetrust-reject-all-handler", ...],
"Cookiebot": ["#CybotCookiebotDialogBodyButtonDecline", ...],
"Didomi": ["#didomi-notice-disagree-button", ...],
"CookieYes": [".cky-btn-reject", ...],
// ... 25+ CMPs soportados
};Resultado del analisis de consentimiento
El informe incluye:
interface ConsentAnalysis {
status: "cmp_rejected" | "cmp_not_interactable" | "no_cmp";
cookiesBeforeReject: number; // Cookies antes de rechazar
cookiesAfterReject: number; // Cookies despues de rechazar
cookiesBlocked: RawCookie[]; // Cookies que fueron eliminadas
cookiesStillPresent: RawCookie[]; // Cookies que persisten
rejectMethod: string; // Selector usado para rechazar
}Interpretacion
| Resultado | Significado |
|---|---|
cmp_rejected + cookiesBlocked > 0 | CMP funciona: elimino cookies al rechazar |
cmp_rejected + cookiesBlocked = 0 | CMP detectado pero no elimino cookies |
cmp_not_interactable | CMP detectado pero no pudimos interactuar |
no_cmp | No se detecto plataforma de consentimiento |
Si el CMP es detectado pero no podemos interactuar (boton no encontrado, timeout, etc.), lo registramos como cmp_not_interactable con una nota explicativa.
Limpieza de temporales
Historicamente, Puppeteer creaba directorios temporales en /tmp/puppeteer_* que no se limpiaban automaticamente. Con nuestro sistema de contextos incognito, esto ya no es un problema.
Adicionalmente, proporcionamos un script de limpieza para eliminar temporales huerfanos existentes:
npx tsx scripts/cleanup-chromium-temps.tsEste script busca y elimina directorios que coincidan con los patrones:
puppeteer_dev_chrome_profile-*puppeteer_dev_profile-*chromium-*.org.chromium.*