--- id: 0039 title: Gestor de cookies y sesiones por profile status: pending priority: high created: 2026-05-04 depends_on: [0038] --- ## Contexto y objetivo Una vez que `0038` deja al app lanzar browsers externos con `--user-data-dir` por profile, las cookies y el `localStorage` quedan persistidas automaticamente por Chrome/Edge en disco. Este issue cubre la **gestion explicita** de esas sesiones desde graph_explorer: visualizarlas, exportarlas, importarlas, limpiarlas y monitorizar cuando caducan. Sin esto, los profiles son cajas negras que solo se manipulan abriendo el browser. Un OSINT serio necesita ver "que sesiones tengo activas, en que dominios, cuando expiran, que hacer si alguna se rompe". ## Alcance ### 1. Inventario de sesiones (CDP read-only) Panel **Sessions** dentro o junto al panel **Browsers**: ``` ┌─ Sessions in profile: linkedin ─────────────────────────┐ │ Domain Cookies Auth? Expires │ │ linkedin.com 42 Yes 2026-08-12 │ │ www.linkedin.com 18 Yes session │ │ static.licdn.com 6 No 2027-01-01 │ │ google-analytics.com 3 No 2026-11-01 │ │ │ │ [Export profile] [Import .json] [Clear domain] │ │ [Clear all cookies] [Open browser] │ └──────────────────────────────────────────────────────────┘ ``` Datos via `Network.getAllCookies` (CDP). Detectar "Auth?" heuristico: cookie con flag `httpOnly=true` o nombre matching `/sess|auth|token|sid|li_at|c_user/i`. ### 2. Export / import de profiles - **Export profile** → `/local_files/exports/-.json` con TODAS las cookies + un manifest (`browser`, `version`, `created_at`, `domains`). - **Import .json** → escribe via `Network.setCookie` sobre un profile vivo. Si el profile no esta vivo, lanzarlo headless solo para inyectar y cerrarlo. - Formato JSON compatible con la extension EditThisCookie (estandar de facto), para que el usuario pueda importar/exportar fuera del app. ### 3. Limpieza selectiva | Accion | Implementacion CDP | |---|---| | Clear all cookies | `Network.clearBrowserCookies` | | Clear domain | iterar `getAllCookies` + `deleteCookies` filtrado | | Clear cookie | `Network.deleteCookies` con name+domain | | Clear localStorage de un origin | `Storage.clearDataForOrigin` | Antes de cualquier "clear all" → confirmacion modal con el numero de cookies que se van a perder. ### 4. Health check de sesiones autenticadas Por profile, opcional, definir en `/.fn_session.yaml`: ```yaml auth_check: - name: linkedin url: https://www.linkedin.com/feed/ success_selector: "main[role='main']" redirect_to_login_means_failed: true - name: google url: https://myaccount.google.com success_selector: "[data-email]" ``` El app puede lanzar un check por sesion (CDP navigate + check selector) y mostrar: ``` linkedin ✓ Authenticated (last check: 2 min ago) google ✗ Login expired (redirected to /accounts/signin) twitter ? Never checked [check now] ``` Health checks NO son automaticos por defecto — requieren click manual para no quemar sesiones con polls innecesarios. Opcion de "check on launch" por profile. ### 5. Lock / busy state Cuando un enricher esta usando un profile (CDP busy), bloquear acciones destructivas (clear, import) en el panel — solo lectura permitida. Visual: candado al lado del profile + tooltip "in use by enricher fetch_webpage_browser". Lock implementado en `graph_explorer.db` tabla `browser_locks(profile, holder, acquired_at)`. TTL 60s por si el holder muere sin liberar. ## Schema en `graph_explorer.db` ```sql CREATE TABLE browser_profiles ( name TEXT PRIMARY KEY, browser_path TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, notes TEXT ); CREATE TABLE browser_locks ( profile TEXT PRIMARY KEY REFERENCES browser_profiles(name), holder TEXT, -- enricher_id, panel, etc. acquired_at TIMESTAMP, expires_at TIMESTAMP ); CREATE TABLE browser_session_checks ( id INTEGER PRIMARY KEY, profile TEXT REFERENCES browser_profiles(name), check_name TEXT, -- 'linkedin', 'google', ... status TEXT, -- 'authenticated' | 'expired' | 'error' detail TEXT, checked_at TIMESTAMP ); ``` ## Plan de implementacion | Fase | Entregable | |---|---| | 0039a | `cdp-cli cookies list --profile X` — JSON con todas las cookies por dominio. Lectura via `Network.getAllCookies`. | | 0039b | `cdp-cli cookies export --profile X --out file.json` — formato EditThisCookie. | | 0039c | `cdp-cli cookies import --profile X --in file.json` — `Network.setCookie` masivo. | | 0039d | `cdp-cli cookies clear --profile X [--domain Y]` — clear selectivo. | | 0039e | Panel **Sessions** en C++: tabla agregada por dominio, botones de export/import/clear. | | 0039f | `cdp-cli session-check --profile X --config .yaml` — health check con selectores. | | 0039g | UI de health check en panel Sessions: status por sesion, boton "check now". | | 0039h | Locks: implementar `browser_locks` + UI para mostrar profile busy. | | 0039i | Migrar profiles preexistentes (creados manualmente) — boton "Register existing folder". | ## Riesgos y mitigaciones | Riesgo | Mitigacion | |---|---| | Importar cookies sobreescribe datos validos | Dialogo de import con preview + opcion "merge" vs "replace". | | Export con cookies de auth = secret en disco | `local_files/exports/` con permisos 0600 en POSIX. Warning explicito al exportar. Documentar en panel. | | Health check quema rate limit del sitio | Caching: si ultimo check < 5 min, no re-checkear sin force. | | Cookies httpOnly no se ven via JS pero si via CDP | Documentar: el panel muestra MAS cookies que las visibles desde DevTools de la pagina. | | Diferencias de schema entre Chrome / Edge / Brave | CDP es estandar — mismo subset funciona en todos. Tests por browser en CI manual. | ## Definicion de hecho - En el panel **Sessions** del profile `linkedin`, veo 42 cookies agrupadas por dominio, con flag de auth detectado y fecha de expiracion. - Click en **Export profile** genera un JSON valido que la extension EditThisCookie puede importar en otro browser. - Click en **Clear domain: google-analytics.com** elimina solo esas 3 cookies sin tocar las demas. - Configurando un `auth_check` para LinkedIn y haciendo click en **Check now**, el app lanza CDP, navega, evalua el selector y muestra `✓ Authenticated` en menos de 4s. - Un enricher corriendo sobre `linkedin` bloquea las acciones destructivas del panel (boton "Clear all" deshabilitado con tooltip). - Cerrar manualmente el browser y volver a lanzarlo desde el panel conserva todas las cookies del profile (verificado por el inventario pre/post). ## Fuera de alcance - Cifrado at-rest del `user-data-dir` — Chrome ya cifra los secrets con DPAPI (Windows) / Keychain (Mac). En Linux Chrome usa cifrado debil por defecto, pero esta fuera de nuestro control. - Sync de profiles entre PCs — los `local_files/browser_profiles/` estan gitignorados a proposito (secrets). Si en el futuro hace falta, un export manual + import en el otro PC es la via. - Auto-renovacion de tokens (cuando un sitio refresca cookies via refresh_token) — fuera de v1, demasiado especifico por sitio.