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'
]
});Viewports y User-Agent
El analisis base simula un navegador de escritorio amplio y la evidencia avanzada anade perfiles representativos para comparar comportamiento:
| Configuracion | Valor |
|---|---|
| Viewport base | 1920x1080 |
| Evidencia escritorio | 1366x900 |
| Evidencia movil | 390x844 |
| User-Agent | Chrome moderno en Windows/Mac |
| Timeout navegacion | Hasta 60 segundos en el analisis base |
| Timeout por defecto | 30 segundos |
Sesion limpia controlada
Cada analisis parte de una pagina nueva y el sistema limpia cookies/cache al finalizar. La evidencia avanzada se obtiene en navegaciones independientes para separar estados (before, reject, accept, reloadAfterReject, scroll y movil/escritorio):
// Crear pagina limpia
const page = await browser.newPage();
// ... realizar analisis ...
// Limpiar cookies/cache y cerrar pagina
await client.send('Network.clearBrowserCookies');
await client.send('Network.clearBrowserCache');
await page.close();Beneficios de la sesion limpia
| Beneficio | Descripcion |
|---|---|
| Menos contaminacion | No se reutilizan cookies de una prueba anterior |
| Limpieza explicita | Se limpian cookies y cache tras el analisis |
| Estados separados | La matriz avanzada usa navegaciones independientes por accion |
| Reproducibilidad | Cada analisis registra fecha, navegador, viewport y limitaciones |
La sesion limpia no equivale a una auditoria manual completa: no ejecuta login, formularios, compras ni navegacion profunda. Es una captura tecnica repetible del comportamiento observable antes y durante el recorrido automatizado.
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 las peticiones de red observadas durante la carga, la ventana posterior y el recorrido técnico 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. La evidencia avanzada añade un scroll tecnico acotado antes del consentimiento para detectar cargas diferidas, pero scripts que dependan de login, formularios, clicks concretos o navegacion profunda pueden no ser 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.*