daedalus
Daedalus
Control remoto del navegador mediante una extensión de Chrome. Puente de eval + hotfixes persistentes + control por pestaña + capturas de pantalla, CDP, cookies, captura de red en páginas donde Chrome ejecuta la extensión.
Instalación
Debes saber esto antes de instalar: el eval ordinario usa inyección en el mundo MAIN sin banner. Cuando una sonda CSP sin fuente no puede establecer que la compilación dinámica está disponible, Daedalus intenta el fallback de CDP; una conexión exitosa muestra el banner "Daedalus started debugging this browser" de Chrome mientras ese fallback se ejecuta. Una sesión CDP mantenida o la captura de red pueden mantener la conexión por más tiempo. El banner identifica una conexión de depurador, no una garantía de integridad de valor.
Carga la extensión sin empaquetar (extension/) en Chrome:
Visita
chrome://extensionsActiva Modo de desarrollador
Haz clic en Cargar descomprimida, selecciona
extension/
Se genera automáticamente un token único en la primera instalación y se almacena en chrome.storage.local. Puedes verlo/cambiarlo desde la página de opciones de la extensión (icono de puzle → Daedalus → Opciones).
Para el raspado paralelo de múltiples pestañas, desactiva la limitación de pestañas en segundo plano de Chrome:
chrome --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgroundingEl puente en sí (server.py) solo usa la biblioteca estándar y no necesita instalación. La CLI y el endpoint MCP en proceso son el paquete de Python de este repositorio:
pip install . # the `daedalus` CLI
pip install ".[mcp]" # ... and the MCP endpoint's dependenciesSin el extra mcp, el puente igualmente se inicia y sirve todo lo demás; informa del fallo de arranque de MCP al inicio y /mcp no se sirve.
Related MCP server: Real Browser MCP
Cómo funciona
Token: Se genera una vez en la instalación mediante
crypto.randomUUID(), se almacena enchrome.storage.localIDs de pestaña: La API nativa
tabsde Chrome — cada pestaña se identifica por sutabIdde Chrome, se registra en creación/actualización con un latido dechrome.alarmsde 30sFlujo SSE único:
background.jsabre una conexión SSEfetchpersistente (tab=extension) y despacha los comandos entrantes a la pestaña correctaPuente de página:
content.js(mundo ISOLATED) retransmite los mensajeswindow.GMentre el fondo ypage.js(mundo MAIN). El eval usa primero la inyección en el mundo MAIN dechrome.scripting. Una sonda de compilación dinámica sin fuente enruta las páginas bloqueadas por CSP a CDP; si falla la conexión allí, se llega al relé de página. Cada canal se ejecuta con semántica del mundo MAIN de la página.Hotfixes: Se almacenan en la clave
daedalus-hotfixesdechrome.storage.local(no por token), se reproducen en cada página de nivel superior elegible al cargar — inyección en el mundo MAIN, o CDP cuando la CSP de la página prohíbe la compilación dinámica — y están sujetos a versión para los arreglos no permanentes. Rotar el token no aísla ni borra este almacén.
Envío de comandos
Daedalus expone su superficie de comandos de extensión como un servidor MCP en <your-bridge>/mcp (transporte streamable-HTTP). Antes de despachar una solicitud, exige que el valor Bearer coincida exactamente con el token del puente resuelto a través de la ruta de configuración existente de la CLI: TOKEN anula DAEDALUS_TOKEN, incluido un proveedor _settings opcional. Sin token configurado, la superficie MCP falla cerrada con 401. El endpoint solo existe cuando el extra mcp está instalado (ver "Instalación" arriba). Añádelo a Claude Code (o a cualquier cliente MCP) con:
{
"mcpServers": {
"daedalus": {
"url": "https://daedalus.example.com/mcp",
"headers": { "Authorization": "Bearer <your-bridge-token>" }
}
}
}El token del puente es el que genera la extensión en la instalación — visible en la página de opciones de la extensión (icono de puzle → Daedalus → Opciones).
Ese ejemplo pone el servidor MCP detrás de un nombre de host público, pero los hosts permitidos por defecto del transporte son solo de bucle local (127.0.0.1:*,localhost:*): nombra el host público en DAEDALUS_MCP_ALLOWED_HOSTS o las solicitudes proxy serán rechazadas. Ver "Servidor" abajo para los tres ajustes de MCP.
40 herramientas en 7 grupos — pestañas, eval/depuración, medios, cookies, CSS/bloqueo, hotfixes, red/CDP. Ver la sección <mcp> de CLAUDE.md para la lista completa, o llama a tools/list en el endpoint MCP.
Helper de verificación manual (no necesita cliente MCP):
TOKEN=<tok> python3 scripts/mcp_probe.py list
TOKEN=<tok> python3 scripts/mcp_probe.py call title '{"tab_id":"<tabId>"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call screenshot '{"include_image":true}'CLI
Una CLI de shell se distribuye como el wheel daedalus-cli en este repositorio
(daedalus_cli/), instalada como el comando daedalus. Lee DAEDALUS_URL
y DAEDALUS_TOKEN del entorno, con TOKEN como anulación puntual
e ID=<tabId> para apuntar a una pestaña específica (omítelo para transmitir):
DAEDALUS_TOKEN=<tok> daedalus tabs
DAEDALUS_TOKEN=<tok> ID=<tabId> daedalus title
DAEDALUS_TOKEN=<tok> daedalus exec myid 'document.title'daedalus --help lista todos los subcomandos, y cada uno tiene su propio --help. El
código exec que envías es una expresión o un cuerpo de función cuyo valor de retorno
vuelve como resultado — ver "Envío de comandos" arriba para el contrato.
También hay un punto de integración de importación opcional: si un módulo llamado _settings
es importable en sys.path, la CLI usa sus funciones setting(name, default) y
required(name) para DAEDALUS_URL y DAEDALUS_TOKEN en lugar del
respaldo de entorno integrado. La variable de entorno TOKEN sigue siendo la anulación
puntual de token y tiene prioridad; sin _settings,
DAEDALUS_URL usa su valor por defecto y DAEDALUS_TOKEN es obligatorio. ID sigue siendo
la variable de entorno para apuntar a una pestaña específica.
O publica atómicamente un archivo de comando crudo (sin MCP, sin CLI). La redirección directa
al nombre final .json no está soportada porque el flujo puede observar un archivo
antes de que el escritor termine. Escribe un nombre hermano que termine en .tmp, luego renómbralo
dentro del mismo directorio:
# Broadcast to all tabs
commands_dir="$DAEDALUS_DIR/commands"
final="$commands_dir/<token>.json"
tmp="$(mktemp "$commands_dir/.<token>.XXXXXX.tmp")"
printf '%s\n' '{"id":"test1","code":"document.title"}' > "$tmp" &&
mv "$tmp" "$final"
# Target a specific tab
final="$commands_dir/<token>_<tabId>.json"
tmp="$(mktemp "$commands_dir/.<token>_<tabId>.XXXXXX.tmp")"
printf '%s\n' '{"id":"test1","code":"document.title"}' > "$tmp" &&
mv "$tmp" "$final"El lector ignora los nombres hermanos .tmp. Si un escritor anterior expone JSON malformado
en el nombre final, el lector lo deja intacto y reintenta en lugar de
borrar una escritura posiblemente en curso. Después del renombrado atómico, el flujo SSE
entrega y consume el comando. El resultado cae en $DAEDALUS_DIR/results/<token>_<tabId>.json (por pestaña) y $DAEDALUS_DIR/results/<token>.json (el último escritor gana). Las finalizaciones de eval de inyección page-main y de relé de página pueden llevar un campo exec_ms — el tiempo de ejecución en el contexto de la página en milisegundos. Una página puede falsear u omitir este campo temporizado por página en cualquiera de los dos canales. Las finalizaciones de CDP no llevan exec_ms. Los resultados asociados con una entrega en cola también llevan roundtrip_ms — el tiempo de ida y vuelta completo observado por el servidor en milisegundos, desde la puesta en cola del comando (PUT /command) hasta la llegada del resultado (POST /result); abarca la espera en cola + entrega SSE + relé del cliente + ejecución + viaje de retorno, por lo que roundtrip_ms − exec_ms aproxima la sobrecarga de transporte/cola cuando ambos campos están presentes. Estas mediciones usan relojes diferentes: exec_ms usa performance.now() de la página, mientras que roundtrip_ms usa milisegundos de reloj de pared del servidor, por lo que su diferencia es una aproximación, no una resta exacta. Un marco heredado sin _did no tiene roundtrip_ms.
Soporte asíncrono
El canal por defecto page-main ejecuta expresiones a través del eval propiedad de la página y
cuerpos de función a través del Function propiedad de la página; un envoltorio asíncrono proporciona
await de nivel superior. Antes de enviar el código fuente, el fondo inyecta solo una
sonda constante Function. Si esa sonda devuelve true, la inyección del código fuente se intenta
una vez y cada resultado es terminal: un valor, una excepción o un
error de transporte de inyección se informa sin reintentar el código fuente en CDP. La
página posee Function y puede influir en esta pista de enrutamiento, pero la sonda no contiene
código fuente enviado, por lo que elegir otro canal no puede duplicar sus efectos secundarios.
Si la sonda sin fuente no devuelve true, lo más común es que la CSP de la página bloquee
la compilación dinámica, Daedalus intenta CDP. Una conexión exitosa muestra
el banner de depurador de Chrome. Runtime.evaluate usa el modo REPL para await
de nivel superior; el código fuente que contiene return se trata como un cuerpo de función a menos que
una sonda envolvente lo analice como una expresión. Esa sonda envolvente es una heurística
de análisis, no un límite de seguridad sin ejecución: el código fuente manipulado por el operador puede
escaparla y ejecutarse. Después de que se despacha la evaluación final de CDP, cada resultado
es terminal. Solo el fallo de conexión o de forma antes de que el código fuente enviado se ejecute llega
al relé de página. El acuerdo de promesa de CDP está limitado a 10 segundos, y los manejadores
de objetos de resultado y excepción se liberan incluso cuando una sesión mantenida o una captura
retiene la conexión del depurador.
El JavaScript evaluado en una página que no controlas devuelve un valor que esa página puede
elegir, independientemente del canal que lo ejecutó. El campo world registra solo qué canal
de ejecución ejecutó el código fuente enviado; es metadato de diagnóstico para el comportamiento
de CSP y depurador, no una señal de confianza. Sus valores de eval son page-main
para la inyección ordinaria, cdp para el fallback del inspector y
page:<hostname> para el relé final, donde <hostname> es el location.hostname
del script de contenido. El fondo añade el prefijo page:, por lo que un
hostname de relé no puede colisionar con cdp o page-main; este hecho de espacio de nombres
no dice nada sobre el valor devuelto. La CLI muestra el campo como
channel=..., el panel hace lo mismo, y MCP exec, put, result y
ping preservan el valor exacto de world. Ninguno asigna una clase de confianza.
La inyección por defecto y el fallback de CDP preservan el comportamiento de script clásico para
los casos de modo sloppy documentados: with compila, los literales octales heredados son
aceptados, y una asignación no declarada crea un global. El modo REPL de CDP también
permite declaraciones repetidas let o const. Las esperas de resultado de CLI y MCP tienen un
valor por defecto de 15 segundos; un tiempo de espera del llamador no cancela el código que ya se está ejecutando en
la página. El relé de Blob espera por separado hasta 10 segundos para el código que contiene
await, y 3 segundos en caso contrario, antes de informar un tiempo de espera de fallback.
La correlación del relé sigue siendo descriptiva y limitada: la pestaña del remitente debe coincidir, un mensaje aceptado consume el id de relé aleatorio, y ningún id de relé se registra hasta que la sonda de inyección sin fuente y el fallback de CDP previo al envío hayan terminado. Estos controles previenen la finalización de relé entre pestañas o duplicada; no establecen integridad de valor.
Como máximo 1,000 entradas de relé de página pueden estar activas. Un nuevo fallback en capacidad recibe un error de capacidad terminal sin expulsar el trabajo activo. Cada entrada registrada expira después de 300,000 ms y recibe un error de tiempo de espera terminal si ninguna finalización de la misma pestaña o un fallo de envío anterior la elimina.
Panel
Una superficie de control basada en navegador en <your-bridge>/dashboard que maneja todo el conjunto de comandos de la extensión sin la CLI — lista de pestañas en vivo, REPL de eval, capturas de pantalla, cookies, hotfixes, reglas de bloqueo, captura de red, CDP, inyección de CSS, tiempos de fetch, navegador de subida.
Abre la URL en una pestaña que la extensión controle.
Desplázate a §12 Configuración y pega el token de la página de opciones de la extensión (icono de puzle → Daedalus → Opciones). Guarda.
El punto de estado SSE en la barra superior se vuelve cian cuando el panel está suscrito a eventos en vivo.
Servido directamente por server.py desde el directorio dashboard/ del repositorio (JS vanilla + módulos ES, sin compilación). Las actualizaciones en vivo vienen a través del endpoint /stream existente: server.py pone eventos en cola en commands/<token>_dashboard/<ts>_<uuid>.json cuando /register actualiza una pestaña existente, y en las rutas exitosas de /sync-tabs, /unregister y /result; /unregister emite incluso cuando no había pestaña presente. El panel los drena como marcos kind:'event'.
Advertencia: los scripts de contenido y de página de la extensión se inyectan en páginas coincidentes, incluida la pestaña del panel. Los comandos de eval de difusión (exec -b) también se ejecutan dentro del panel — prefiere apuntar a una pestaña específica o cierra el panel antes de difundir código disruptivo.
Auto-parche
Hotfixes
Persiste pequeños parches que se reproducen en cada carga de página de nivel superior elegible. Mediante herramientas MCP: store_hotfix, list_hotfixes, clear_hotfix, clear_hotfixes, set_permanent. Ejemplo (a través del script de sonda):
TOKEN=<tok> python3 scripts/mcp_probe.py call store_hotfix '{"fix_id":"my-fix","code":"console.log(\"patched\")"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call store_hotfix '{"fix_id":"always-on","code":"console.log(\"baseline\")","permanent":true}'
TOKEN=<tok> python3 scripts/mcp_probe.py call set_permanent '{"fix_id":"my-fix","permanent":true}'
TOKEN=<tok> python3 scripts/mcp_probe.py call list_hotfixes
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfix '{"fix_id":"my-fix"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfixes
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfixes '{"include_permanent":true}'Los hotfixes están sujetos a versión por defecto. Después de un cambio de versión de la extensión, los arreglos no permanentes retenidos se omiten en lugar de eliminarse; almacenar un arreglo actualiza el registro a la versión actual y hace que sus arreglos no permanentes retenidos vuelvan a ser elegibles. Marca un arreglo como permanente (mediante permanent: true en store_hotfix, o set_permanent) para reproducirlo a través de cambios de versión. clear_hotfixes elimina los arreglos no permanentes y mantiene los permanentes por defecto; pasa include_permanent: true para eliminar todo el almacén.
Comandos de extensión
El service worker en segundo plano acepta comandos tipados (emitidos mediante herramientas MCP o escribiendo JSON con "type": "..." en $DAEDALUS_DIR/commands/):
Comando | Propósito |
| Capturar la pestaña visible como PNG |
| Emitir llamadas sin procesar del Chrome DevTools Protocol |
| Intercepción completa de solicitudes/respuestas mediante CDP |
| Acceso al almacén de cookies |
| Control de pestañas |
| Inyección de CSS por pestaña |
| Bloqueo mediante declarativeNetRequest |
| Gestión de hotfixes (las correcciones permanentes sobreviven a los cambios de versión) |
| Recargar la propia extensión desde el disco |
| Búfer circular de diagnóstico para los relés de fetch |
Puente GM
window.GM (en page.js, mundo MAIN) proporciona este subconjunto al estilo Tampermonkey:
Método | Descripción |
| Leer una clave de cadena no reservada de |
| Escribir una clave de cadena no reservada en el almacenamiento de toda la extensión |
| Eliminar una clave de cadena no reservada del almacenamiento de toda la extensión |
| Listar las claves de almacenamiento no reservadas |
| Solicitud HTTP retransmitida en segundo plano (inmune a CSP) |
| Inyectar CSS |
| Escribir en el portapapeles |
| Notificación de escritorio |
| Abrir una nueva pestaña |
| Activar una descarga |
| Metadatos del script |
El acceso a las cookies es una capacidad del operador, no de la página: pasa por los
comandos cookies / set-cookie / remove-cookie / clear-cookies
autenticados con token mencionados arriba y deliberadamente no se expone al contexto de la página — page.js se ejecuta
en cada página de nivel superior que coincida, que de otro modo podría leer cookies que su propio
document.cookie no puede ver.
Arquitectura
Browser (matching tab) Server (your bridge host)
┌────────────────────────────────────────────┐ ┌──────────────────────┐
│ MAIN world │ │ bridge (server.py) │
│ ├─ page-main: default injection channel │ │ /stream?token │
│ └─ page.js: GM + relay channel │ │ watches commands/ │
│ ▲ │ │ │
│ │ window.postMessage │ │ /result writes │
│ content.js (ISOLATED) │ │ results/ │
│ ▲ │ │ │
│ │ chrome.runtime │ │ │
│ background.js (service worker) │ │ │
│ ├─ CDP: CSP fallback channel │ │ │
│ ├─ single SSE stream ◄────────────────────┼───┤ │
│ └─ POST result, fetch ────────────────────┼──►│ │
└────────────────────────────────────────────┘ └──────────────────────┘Un único flujo SSE: El fondo abre una única conexión SSE
fetch(tab=extension) y enruta los comandos a la pestaña objetivo mediantechrome.tabs.sendMessage. Un watchdog de 30s fuerza la reconexión en flujos obsoletos.Enrutamiento por pestaña: Los comandos se dirigen a un
tabIdespecífico de Chrome o se transmiten a todas las pestañas.Comportamiento CSP:
GM.xmlhttpRequestenvía el trabajo HTTP alfetchdel service worker en segundo plano. La evaluación normalmente usa inyección en el mundo MAIN sin banner. Cuando la sonda sin fuente informa que la compilación dinámica no está disponible, CDP proporciona el respaldo CSP y muestra el banner de depuración de Chrome si se adjunta; un fallo de adjunción llega al relé de página/Blob. El valorworldresultante describe ese canal y no hace ninguna afirmación de integridad.
Servidor
Inicie el puente con sus tres ajustes obligatorios — sale al inicio sin DAEDALUS_DIR o DAEDALUS_PORT, y cada ruta de control del puente falla de forma segura sin un token configurado:
DAEDALUS_DIR=<data-dir> DAEDALUS_PORT=<port> DAEDALUS_TOKEN=<bridge-token> python3 server.pyserver.py se ejecuta bajo el supervisor que use (aquí una unidad systemd). Ponga un proxy inverso con terminación TLS delante si lo expone más allá de localhost; el puente mismo habla HTTP plano. Las rutas de control del puente y de almacenamiento comparan su token con un secreto configurado, resuelto a través de la ruta de configuración CLI: TOKEN es la anulación de una sola vez; de lo contrario, se requiere DAEDALUS_TOKEN (y un módulo _settings incrustado puede proporcionarlo). La configuración faltante y las discrepancias fallan de forma segura. Solo las rutas orientadas a la página POST /segment y GET /segment-status usan en su lugar una capacidad con ámbito de trabajo.
El front-end MCP en proceso toma tres ajustes opcionales, documentados juntos porque describen un único listener: DAEDALUS_MCP_PORT (por defecto 8086) es el puerto de bucle local al que se enlaza; sus manejadores de herramientas usan la URL de bucle local realmente enlazada del puente, incluso cuando DAEDALUS_PORT=0; DAEDALUS_LOCAL_URL anula explícitamente esa URL para un despliegue MCP independiente que se enfrenta a un puente que se ejecuta en otro lugar; y DAEDALUS_MCP_ALLOWED_HOSTS (por defecto 127.0.0.1:*,localhost:*) es la lista de hosts permitidos separada por comas que su protección contra el rebinding de DNS acepta, por lo que poner /mcp detrás de un nombre de host público significa nombrar ese host allí.
Endpoints: GET /stream, GET /tabs, GET /health, GET /dashboard[/<asset>], POST /register, POST /sync-tabs, POST /unregister, POST /poll, POST /result, PUT /command, GET /result, POST/GET/DELETE /upload, GET /screenshot, POST /segment-job, POST /segment + GET /segment-status. POST /segment-job requiere el token del puente configurado; solo POST /segment y GET /segment-status toman la sig con ámbito de trabajo. Consulte CLAUDE.md para notas sobre cargas útiles y endpoints.
POST /poll consume y elimina el archivo de comando de difusión heredado cuando está presente.
PUT /command pone en cola en una cola de directorio FIFO por destino (los comandos consecutivos a una pestaña ya no se sobrescriben), sella cada uno con un id de entrega (_did) para que la extensión pueda deduplicar un marco reenviado, y elimina por TTL los comandos no reclamados después de DAEDALUS_CMD_TTL segundos (por defecto 90). Un recolector en segundo plano aplica ese TTL y elimina los directorios de cola vacíos incluso cuando ningún consumidor SSE se conecta nunca. GET /health informa la actividad de flujo/registro/última entrega para detectar un puente muerto silenciosamente.
El puente rechaza los portadores de autoridad repetidos en lugar de seleccionar un valor:
esto incluye token en cadenas de consulta o cuerpos JSON y job / sig en las
rutas de capacidad de segmento, incluso cuando los valores repetidos son iguales o están en blanco.
El transporte MCP también rechaza los encabezados Authorization, Mcp-Session-Id,
Host y Origin repetidos, además de los argumentos job repetidos para las herramientas
de segmento.
Cuando la extensión publica un resultado, el servidor expone el _did del comando como
deliveryId y asigna un nuevo resultGeneration. Los esperadores CLI y MCP primero
echan un vistazo a la ranura de resultado compartida, comparan tanto el id de comando como el deliveryId, luego
consumen condicionalmente esa generación con
GET /result?...&consume=1&expected=<resultGeneration>. Si otro resultado
reemplaza la ranura entre esas solicitudes, el consumo condicional deja el
nuevo resultado en su lugar e informa que no fue consumido. Un consume=1 simple
sin expected sigue siendo una lectura de compatibilidad destructiva de la ranura
actual.
GET /stream mantiene la conexión SSE abierta indefinidamente, demostrando actividad con un comentario de keepalive cada DAEDALUS_STREAM_KEEPALIVE segundos (por defecto 15) en lugar de ciclar la conexión con un temporizador; DAEDALUS_STREAM_MAX_AGE (por defecto 3600) es solo un techo de último recurso. La ruta de cierre está fijada por tests/test_stream_lifecycle.py.
DAEDALUS_MAX_BODY_SIZE (por defecto 64 * 1024 * 1024 bytes, 64 MiB) limita los cuerpos de solicitud leídos por los manejadores POST, PUT y DELETE; un cuerpo declarado mayor que el límite recibe 413, y una solicitud que no declara Content-Length recibe 411 en lugar de leerse como un cuerpo vacío. Auméntelo al retransmitir segmentos más grandes u otras cargas útiles.
Los valores de llamador respaldados por el sistema de archivos usan una política de un solo componente de ruta: rechaza
.., caracteres de control C0/C1 y sustitutos, caracteres de ruta no válidos en Windows
y nombres de dispositivos, puntos o espacios finales, y codificaciones UTF-8 de más de 240
bytes. Los tokens del puente son más estrictos y también rechazan puntos y guiones bajos. Otros
nombres de trabajo UTF-8 se aceptan, por lo que los clientes deben codificarlos en URL en las cadenas de consulta.
POST /segment-job copia tres cuotas fijas en cada nuevo registro de trabajo:
DAEDALUS_MAX_SEGMENT_INDEX (por defecto 99999),
DAEDALUS_MAX_SEGMENTS_PER_JOB (por defecto 10000) y
DAEDALUS_MAX_SEGMENT_JOB_SIZE (por defecto 4 * 1024 * 1024 * 1024 bytes,
4 GiB). Cambiar esos ajustes afecta a los trabajos creados posteriormente; un trabajo con
cuotas almacenadas continúa usando sus valores registrados. DAEDALUS_MAX_BODY_SIZE
también limita cada solicitud de segmento individual.
Seguridad
Lea esto antes de instalar la extensión.
Inyecta un shim window.GM en cada página de nivel superior que coincida
(<all_urls>, mundo MAIN),
que es lo que permite que un script enviado con put haga solicitudes de origen cruzado. La
consecuencia es la parte de la que debe ser deliberado: cualquier sitio que coincida que visite
puede llamar a ese shim, por lo que ese sitio puede emitir solicitudes de origen cruzado a través de los
privilegios de la extensión, exactamente como lo haría un administrador de usuarios con un
@match comodín. Si eso no es aceptable para su navegación, reduzca los
matches en extension/manifest.json a los hosts que realmente maneja, y
acepte que el puente entonces no hace nada en otras páginas.
El token del puente y la URL del servidor no son accesibles a través de los métodos de almacenamiento GM de la página. Viven en chrome.storage.local bajo el prefijo daedalus-, y el relé se niega a leer, escribir o enumerar ese espacio de nombres para los scripts de la página. Sin esa regla, cualquier sitio visitado podría leer su token del puente con GM.getValue('daedalus-token') o redirigir el puente con GM.setValue('daedalus-server', ...), silenciosamente. tests/test_repo_contract.py fija la regla.
El origen del hotfix es deliberadamente un estado observable por la página, no un estado confidencial de la extensión. El fondo selecciona las correcciones permanentes más las correcciones no permanentes que coinciden con la versión de la extensión y ejecuta cada una en la página a través de los mismos canales que usa la evaluación ordinaria — inyección en el mundo MAIN donde la página permite la compilación dinámica, CDP donde no — por lo que la página puede observar el origen que ejecuta en cualquiera de los canales.
Un portador MCP no puede nombrar un archivo del host del servidor para que el proceso MCP lo lea. Las herramientas MCP put, inyección/eliminación de CSS y almacenamiento de hotfixes aceptan solo origen en línea. El CLI local mantiene su conveniencia de ruta de archivo, pero el proceso CLI lee ese archivo seleccionado por el operador y envía su contenido en línea. Por lo tanto, tener el token del puente otorga la autoridad documentada de control del navegador y de la extensión, incluida la lectura del origen de hotfix almacenado, pero no agrega una lectura arbitraria del sistema de archivos del host a través de estas herramientas.
Las rutas de control del puente y de almacenamiento requieren el token del puente configurado;
/segment y /segment-status son la única excepción de capacidad. El servidor
compara los tokens de las solicitudes con el secreto resuelto desde TOKEN o
DAEDALUS_TOKEN y rechaza las solicitudes cuando no hay ningún secreto configurado.
POST /segment-job requiere ese token del puente porque acuña la capacidad
limitada al trabajo; el JavaScript de la página no confiable usa la capacidad solo para publicar y consultar
ese trabajo. Cualquiera que tenga el token del puente puede controlar tu navegador. No expongas
el puerto del puente más allá de loopback sin un proxy inverso que termine TLS, y
trata el token como una credencial.
Los resultados de evaluación no ofrecen garantía de integridad del valor. El JavaScript evaluado en una
página que no controlas devuelve un valor que esa página puede elegir, independientemente de
qué canal lo haya ejecutado. El campo world indica cómo se ejecutó el código enviado,
no si hay que confiar en su valor: page-main es inyección ordinaria en el mundo MAIN,
cdp es el respaldo CSP del inspector, y page:<hostname> es el relé. El
prefijo obligatorio page: se añade fuera del contexto de la página, por lo que ningún hostname puede
producir cdp o page-main; esa resistencia a colisiones es solo descriptiva.
En page-main, los enlaces eval y Function propiedad de la página pueden leer el código
enviado, así como influir en su valor devuelto.
La compilación CDP evita resolver los enlaces eval y Function de la página, y
la implementación recupera los manejadores directos por referencia antes de serializarlos.
Esos mecanismos de transporte no cambian el límite de confianza: el código
enviado sigue leyendo el estado controlado por la página y puede enrutar cualquier valor, incluido un
primitivo, a través de la maquinaria de promesas de la página antes de que CDP lo reciba. Siempre que el
código enviado use esas rutas controladas por la página, la página puede elegir el valor
devuelto en cdp también.
El relé acepta solo la invocación almacenada asociada con ese id aleatorio, requiere que la pestaña del remitente coincida, y consume la entrada una vez. La degradación no revela el token del puente, la URL del servidor, el id de entrega ni la ruta de resultados, no otorga autoridad adicional de extensión o navegador, y no puede afectar una invocación en otra pestaña. Esas propiedades protegen el enrutamiento y la autoridad del navegador; no convierten el marcador del relé ni su valor JavaScript devuelto en una señal de confianza.
Despliegue
El puente habla HTTP plano en loopback. Las rutas de control del puente y de almacenamiento
requieren el token configurado y fallan de forma segura cuando está ausente; solo /segment
y /segment-status usan la capacidad limitada al trabajo. Dos cosas que deliberadamente
NO hace, porque pertenecen a lo que esté delante de él:
TLS y CORS.
server.pyno envía cabeceras CORS. Cuando el puente es de origen cruzado, las llamadasfetchdel lado de la página del relé HLS de ejemplo necesitan acceso a tantoGET /segment-statuscomoPOST /segment; el proxy debe permitir el origen de la página en ambas rutas y manejar los métodos/cabeceras requeridos por el preflight del POST. Si el GET de estado está bloqueado, no disponible, no es 2xx o es inválido, el ejemplo trata el trabajo como nuevo y vuelve a publicar cada segmento. Esas escrituras siguen reemplazando los mismos archivos por índice, pero la ejecución pierde sus ahorros de reanudar/saltar. Enruta ambas solicitudes a través del puente GM de la extensión en su lugar si el despliegue no puede proporcionar esa política CORS.Servir subidas almacenadas. El panel enlaza descargas en
/uploads/<path>, pero el puente no tiene esa ruta. No sirvas nombres y bytes de subida proporcionados por el llamador directamente en el origen del panel, donde el contenido ejecutable compartiría un origen con el almacenamiento del panel que lleva el token. O deja esos enlaces no disponibles, o haz que/uploads/redirija a un origen separado solo de descarga que fuerceContent-Disposition: attachment, useapplication/octet-streamy envíeX-Content-Type-Options: nosniff.GET /upload(listado) yGET /screenshotson servidos por el propio puente y no necesitan ayuda del proxy.
Ejecútalo directamente con un token configurado y ambos simplemente están ausentes — todo lo demás funciona.
Archivos
Archivo | Descripción |
| Manifiesto MV3 |
| Service worker — SSE, despacho de comandos, relé de fetch, capturas de pantalla, CDP, cookies, descargas |
| Relé de mensajes entre la página y el background |
| Puente del mundo MAIN — |
| UI de configuración de token/servidor |
| Servidor de depuración (también aloja el hilo del daemon MCP en 127.0.0.1:8086) |
| Servidor MCP — conecta la superficie de comandos de la extensión a |
| Cliente MCP mínimo para verificación manual |
| Verificación de consistencia de versiones / incremento en cada sitio de versión |
| Puertas de consistencia de versiones pre-commit + pre-push |
| El CLI de shell, publicado como la rueda |
| La superficie de control del navegador que |
| Scripts para ejecutar en una página mediante |
| Las suites; |
Ejemplos
examples/ contiene scripts pensados para ejecutarse dentro de una página con put. Cinco de
los seis demuestran una parte del puente en lugar de un sitio particular; el de
Discord es deliberadamente específico del sitio, porque desplazarse hacia atrás a través de una
lista virtualizada es una técnica que no puedes mostrar sin una lista virtualizada real:
Ejemplo | Muestra |
| Un hotfix permanente del mundo MAIN en |
| El relé |
| Encontrar y destruir una instancia de reproductor que no dejará de reintentar |
| Rellenar una entrada controlada por React para que el estado propio de React realmente cambie |
| Desplazarse hacia atrás a través de una lista de mensajes virtualizada y extraerla |
| La llamada más pequeña posible a |
Toman su configuración mediante sustitución de __PLACEHOLDER__ antes de
ser enviados, porque put envía un script en lugar de llamar a una función y no
tiene forma de pasar argumentos.
Cada uno es el CUERPO de una función async, no un script independiente: el puente
envuelve lo que envías. Por eso la mayoría termina en un return de nivel superior, y uno de
ellos — scrape-discord-messages.js — también usa un await de nivel superior; ambos son
válidos donde realmente se ejecutan. node --check analiza cada archivo en el
envoltorio CommonJS, que permite el return de nivel superior, pero rechaza el
await de nivel superior — así que exactamente ese archivo falla la verificación de sintaxis.
Desarrollo
python3 run_tests.py ejecuta todas las suites e imprime una línea agregada que nombra
lo que verificó: cuántas suites se ejecutaron, cuántas pruebas pasaron y cuántas
se omitieron. Una suite en la que todas las pruebas se omitieron no verificó nada, por lo que se
informa como
OVERALL: INCOMPLETE con una salida distinta de cero en lugar de como un pase; la causa
habitual es una dependencia faltante, y la línea NO COVERAGE encima nombra la
suite. Instalar el extra mcp (ver "Instalación") es lo que necesita la suite MCP.
La cadena de versión vive en varios lugares a través de la extensión, el panel
y el paquete CLI. python3 scripts/check_versions.py es la lista — nombra
cada sitio e informa cuántos encontró, por eso este párrafo no repite
un recuento que quedaría obsoleto la próxima vez que se añada uno.
Incrementa todos juntos, nunca a mano:
python3 scripts/check_versions.py --set 0.18.0 # rewrite every site
python3 scripts/check_versions.py # verify the working tree.githooks/pre-commit verifica el índice y .githooks/pre-push verifica cada commit
enviado, así que un incremento a medias no puede aterrizar. Cada clon debe optar una vez — git no ejecutará
hooks desde un directorio rastreado de otra manera:
git config core.hooksPath .githooksSi el navegador carga la extensión desde un checkout diferente al que editas, instala los hooks allí también — y recuerda que recargar la extensión relee los archivos del lado del navegador, no del tuyo.
This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Access Kernel's cloud-based browsers and app actions via MCP (remote HTTP + OAuth).
Browserless MCP — wraps the Browserless headless-Chromium REST API (browserless.io)
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables MCP clients to drive a real, logged-in Chrome browser for web automation tasks like navigation, clicking, typing, and screenshotting.2 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables browser automation over MCP using a real Chrome browser with existing profile, supporting real tabs, downloads, cookies, and RPA workflows.104 npmMIT
- FlicenseNot gradedqualityCmaintenanceEnables MCP clients to control real Chrome tabs in existing profiles, supporting navigation, interaction, screenshots, and console/network inspection across multiple Chrome instances.-
- AlicenseBqualityCmaintenanceEnables MCP clients to drive a real Chromium browser for automation, including navigation, JavaScript execution, CDP commands, network capture, and multi-tab control.7GPL 3.0