Skip to main content
Glama

Daedalus

Удалённое управление браузером через расширение Chrome. Мост для eval + постоянные хотфиксы + управление по вкладкам + скриншоты, CDP, куки, перехват сетевого трафика на страницах, где работает расширение Chrome.

Установка

Знайте это до установки: обычный eval использует инъекцию в MAIN-мир без баннера. Когда зонд без исходного кода для CSP не может установить, что доступна динамическая компиляция, Daedalus пытается использовать запасной вариант CDP; успешное подключение показывает баннер Chrome «Daedalus started debugging this browser», пока работает этот запасной вариант. Удерживаемый сеанс CDP или захват сети может продлить подключение. Баннер идентифицирует подключение отладчика, а не гарантию целостности значений.

Загрузите распакованное расширение (extension/) в Chrome:

  1. Перейдите на chrome://extensions

  2. Включите Режим разработчика

  3. Нажмите Загрузить распакованное, выберите extension/

Уникальный токен автоматически генерируется при первой установке и хранится в chrome.storage.local. Просмотрите/измените его на странице параметров расширения (значок головоломки → Daedalus → Options).

Для параллельного сканирования нескольких вкладок отключите фоновое ограничение вкладок Chrome:

chrome --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgrounding

Сам мост (server.py) использует только стандартную библиотеку и не требует установки. CLI и встроенная конечная точка MCP — это Python-пакет в этом репозитории:

pip install .            # the `daedalus` CLI
pip install ".[mcp]"     # ... and the MCP endpoint's dependencies

Без дополнительного пакета mcp мост всё равно запускается и обслуживает всё остальное; он сообщает о неудачной загрузке MCP при запуске, и /mcp не обслуживается.

Related MCP server: Real Browser MCP

Как это работает

  1. Токен: генерируется один раз при установке через crypto.randomUUID(), хранится в chrome.storage.local

  2. ID вкладок: нативный API tabs Chrome — каждая вкладка идентифицируется своим Chrome tabId, регистрируется при создании/обновлении с 30-секундным heartbeat chrome.alarms

  3. Один поток SSE: background.js открывает одно постоянное SSE-соединение fetch (tab=extension) и распределяет входящие команды по нужным вкладкам

  4. Мост страницы: content.js (изолированный мир) ретранслирует сообщения window.GM между фоном и page.js (MAIN-мир). Для eval сначала используется инъекция chrome.scripting в MAIN-мир. Зонд без исходного кода для динамической компиляции направляет страницы, заблокированные CSP, на CDP; при неудаче подключения там достигается ретрансляция страницы. Каждый канал выполняется с семантикой MAIN-мира страницы.

  5. Хотфиксы: хранятся в общем для расширения ключе chrome.storage.local daedalus-hotfixes (не для каждого токена), воспроизводятся фоном на каждой подходящей странице верхнего уровня при загрузке — инъекция в MAIN-мир или CDP, если CSP страницы запрещает динамическую компиляцию — и ограничены версией для непостоянных исправлений. Смена токена не изолирует и не очищает это хранилище.

Отправка команд

Daedalus предоставляет свою поверхность команд расширения как MCP-сервер на <your-bridge>/mcp (транспорт streamable-HTTP). Перед отправкой запроса он требует, чтобы значение Bearer точно совпадало с токеном моста, разрешённым через существующий путь конфигурации CLI: TOKEN переопределяет DAEDALUS_TOKEN, включая необязательный провайдер _settings. Если токен не настроен, поверхность MCP закрывается с ошибкой 401. Конечная точка существует только при установленном дополнительном пакете mcp (см. «Установка» выше). Добавьте его в Claude Code (или любой MCP-клиент) с помощью:

{
  "mcpServers": {
    "daedalus": {
      "url": "https://daedalus.example.com/mcp",
      "headers": { "Authorization": "Bearer <your-bridge-token>" }
    }
  }
}

Токен моста — это тот, который генерирует расширение при установке — виден на странице параметров расширения (значок головоломки → Daedalus → Options).

Этот пример подключает MCP-сервер через публичное имя хоста, но разрешённые хосты транспорта по умолчанию — только loopback (127.0.0.1:*,localhost:*): укажите публичное имя хоста в DAEDALUS_MCP_ALLOWED_HOSTS, иначе проксируемые запросы будут отклонены. См. «Сервер» ниже для всех трёх настроек MCP.

40 инструментов в 7 группах — вкладки, eval/отладка, медиа, куки, CSS/блокировка, хотфиксы, сеть/CDP. Полный список см. в разделе <mcp> в CLAUDE.md или вызовите tools/list на конечной точке MCP.

Вспомогательный инструмент ручной проверки (без 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

В этом репозитории поставляется оболочка CLI как колесо daedalus-cli (daedalus_cli/), устанавливаемая как команда daedalus. Она читает DAEDALUS_URL и DAEDALUS_TOKEN из окружения, с TOKEN как одноразовым переопределением и ID=<tabId> для нацеливания на конкретную вкладку (опустите для широковещательной рассылки):

DAEDALUS_TOKEN=<tok> daedalus tabs
DAEDALUS_TOKEN=<tok> ID=<tabId> daedalus title
DAEDALUS_TOKEN=<tok> daedalus exec myid 'document.title'

daedalus --help перечисляет все подкоманды, и каждая имеет свой --help. Код exec, который вы отправляете, — это выражение или тело функции, возвращаемое значение которого приходит как результат — см. «Отправка команд» выше для контракта.

Также доступен необязательный механизм импорта: если модуль с именем _settings доступен для импорта в sys.path, CLI использует его функции setting(name, default) и required(name) для DAEDALUS_URL и DAEDALUS_TOKEN вместо встроенного резервного варианта окружения. Переменная окружения TOKEN остаётся одноразовым переопределением токена и имеет приоритет; без _settings DAEDALUS_URL использует значение по умолчанию, а DAEDALUS_TOKEN обязателен. ID остаётся переменной окружения для нацеливания на конкретную вкладку.

Или атомарно опубликуйте файл сырой команды (без MCP, без CLI). Прямое перенаправление в конечное имя .json не поддерживается, потому что поток может увидеть файл до того, как писатель закончит. Запишите соседнее имя, заканчивающееся на .tmp, затем переименуйте его в том же каталоге:

# 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"

Читатель игнорирует соседние имена .tmp. Если старый писатель предоставляет некорректный JSON по конечному имени, читатель оставляет его нетронутым и повторяет попытку, а не удаляет возможно выполняющуюся запись. После атомарного переименования поток SSE доставляет и потребляет команду. Результат сохраняется в $DAEDALUS_DIR/results/<token>_<tabId>.json (для каждой вкладки) и $DAEDALUS_DIR/results/<token>.json (последний писатель побеждает). Завершения инъекции page-main и ретрансляции страницы могут содержать поле exec_ms — время выполнения в контексте страницы в миллисекундах. Страница может подделать или опустить это поле, измеренное на странице, в любом канале. Завершения CDP не содержат exec_ms. Результаты, связанные с поставкой в очереди, также содержат roundtrip_ms — полное время кругового пути, наблюдаемое сервером, в миллисекундах, от постановки команды в очередь (PUT /command) до прибытия результата (POST /result); оно охватывает ожидание в очереди + доставку SSE + ретрансляцию клиента + выполнение + обратный путь, поэтому roundtrip_ms − exec_ms приблизительно соответствует накладным расходам транспорта/очереди, когда оба поля присутствуют. Эти измерения используют разные часы: exec_ms использует performance.now() страницы, а roundtrip_ms использует миллисекунды настенных часов сервера, поэтому их разница является приближением, а не точным вычитанием. Устаревший фрейм без _did не имеет roundtrip_ms.

Асинхронная поддержка

Канал по умолчанию page-main выполняет выражения через принадлежащий странице eval, а тела функций — через принадлежащий странице Function; асинхронная обёртка обеспечивает верхнеуровневый await. Перед отправкой исходного кода фон внедряет только постоянный зонд Function. Если этот зонд возвращает true, инъекция исходного кода выполняется один раз, и каждый исход является терминальным: значение, исключение или ошибка транспорта инъекции сообщаются без повторной попытки исходного кода на CDP. Страница владеет Function и может влиять на этот подсказчик маршрутизации, но зонд не содержит отправленного исходного кода, поэтому выбор другого канала не может дублировать его побочные эффекты.

Если зонд без исходного кода не возвращает true, чаще всего потому, что CSP страницы блокирует динамическую компиляцию, Daedalus пытается использовать CDP. Успешное подключение отображает баннер отладчика Chrome. Runtime.evaluate использует режим REPL для верхнеуровневого await; исходный код, содержащий return, обрабатывается как тело функции, если только обёрточный зонд не анализирует его как выражение. Этот обёрточный зонд — эвристика парсера, а не неисполняемая граница безопасности: исходный код, созданный оператором, может обойти его и выполниться. После отправки финальной оценки CDP каждый исход является терминальным. Только сбой подключения или формы до выполнения отправленного исходного кода достигает ретрансляции страницы. Обещание CDP ограничено 10 секундами, и дескрипторы объектов результата и исключения освобождаются, даже если удерживаемый сеанс или захват сохраняет подключение отладчика.

JavaScript, выполняемый на странице, которой вы не управляете, возвращает значение, которое эта страница может выбрать, независимо от того, какой канал его выполнил. Поле world записывает только то, какой канал выполнения запускал отправленный исходный код; это диагностические метаданные для поведения CSP и отладчика, а не сигнал доверия. Его значения eval: page-main для обычной инъекции, cdp для запасного варианта инспектора и page:<hostname> для финальной ретрансляции, где <hostname> — это location.hostname контентного скрипта. Фон добавляет префикс page:, поэтому имя хоста ретрансляции не может конфликтовать с cdp или page-main; этот факт пространства имён ничего не говорит о возвращаемом значении. CLI отображает поле как channel=..., панель управления делает то же самое, а MCP exec, put, result и ping сохраняют точное значение world. Никто не присваивает класс доверия.

Инъекция по умолчанию и запасной вариант CDP сохраняют поведение классических скриптов для документированных случаев нестрогого режима: with компилируется, устаревшие восьмеричные литералы принимаются, а необъявленное присваивание создаёт глобальную переменную. Режим REPL CDP также позволяет повторные объявления let или const. Ожидание результатов CLI и MCP по умолчанию составляет 15 секунд; тайм-аут вызывающего не отменяет код, уже выполняющийся на странице. Ретрансляция Blob отдельно ожидает до 10 секунд для кода, содержащего await, и 3 секунды в противном случае, прежде чем сообщить о тайм-ауте запасного варианта.

Корреляция ретрансляции остаётся описательной и ограниченной: вкладка отправителя должна совпадать, одно принятое сообщение потребляет случайный идентификатор ретрансляции, и ни один идентификатор ретрансляции не регистрируется, пока зонд инъекции без исходного кода и предварительная отправка CDP-запасного варианта не завершены. Эти контроли предотвращают завершение ретрансляции между вкладками или дублирование; они не устанавливают целостность значений.

Максимум 1000 записей ретрансляции страницы могут быть активными. Новый запасной вариант при заполнении получает одну терминальную ошибку ёмкости без вытеснения активной работы. Каждая зарегистрированная запись истекает через 300 000 мс и получает одну терминальную ошибку тайм-аута, если завершение на той же вкладке или более ранняя ошибка отправки не удалит её.

Панель управления

Браузерная поверхность управления на <your-bridge>/dashboard, которая управляет всем набором команд расширения без CLI — живой список вкладок, eval REPL, скриншоты, куки, хотфиксы, правила блокировки, захват сети, CDP, инъекция CSS, тайминги fetch, загрузка браузера.

  1. Откройте URL во вкладке, которой управляет расширение.

  2. Прокрутите до §12 Settings и вставьте токен со страницы параметров расширения (значок головоломки → Daedalus → Options). Сохраните.

  3. Точка статуса SSE в верхней панели становится голубой, когда панель управления подписана на живые события.

Обслуживается напрямую server.py из каталога dashboard/ репозитория (ванильный JS + ES-модули, без сборки). Живые обновления приходят через существующую конечную точку /stream: server.py помещает события в commands/<token>_dashboard/<ts>_<uuid>.json, когда /register обновляет существующую вкладку, и на успешных путях /sync-tabs, /unregister и /result; /unregister отправляет событие, даже если вкладка отсутствовала. Панель управления потребляет их как кадры kind:'event'.

Предупреждение: контентные и страничные скрипты расширения внедряются в подходящие страницы, включая вкладку панели управления. Широковещательные команды eval (exec -b) выполняются и внутри панели управления — предпочитайте нацеливание на конкретную вкладку или закройте панель управления перед отправкой разрушительного кода.

Самопатч

Хотфиксы

Сохраняйте небольшие патчи, которые воспроизводятся при каждой подходящей загрузке страницы верхнего уровня. Через инструменты MCP: store_hotfix, list_hotfixes, clear_hotfix, clear_hotfixes, set_permanent. Пример (через скрипт-зонд):

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}'

Хотфиксы по умолчанию ограничены версией. После изменения версии расширения сохранённые непостоянные исправления пропускаются, а не удаляются; сохранение исправления обновляет запись до текущей версии и снова делает сохранённые непостоянные исправления доступными. Отметьте исправление как постоянное (через permanent: true в store_hotfix или set_permanent), чтобы воспроизводить его при изменениях версии. clear_hotfixes удаляет непостоянные исправления и по умолчанию сохраняет постоянные; передайте include_permanent: true, чтобы удалить всё хранилище.

Команды расширения

Фоновый сервис-воркер принимает типизированные команды (выдаваемые через инструменты MCP или записью JSON с "type": "..." в $DAEDALUS_DIR/commands/):

Команда

Назначение

screenshot

Захват видимой вкладки как PNG

cdp

Выполнение прямых вызовов Chrome DevTools Protocol

net-capture / net-capture-stop / net-capture-get

Полная перехват запросов/ответов через CDP

cookies / set-cookie / remove-cookie / clear-cookies

Доступ к хранилищу cookie

open-tab / open-tabs / focus-tab / close-tab / navigate / reload

Управление вкладками

inject-css / remove-css

Внедрение CSS на вкладку

block-requests / unblock-requests / list-block-rules

Блокировка через declarativeNetRequest

store-hotfix / clear-hotfix / clear-all-hotfixes / list-hotfixes / set-permanent

Управление хотфиксами (постоянные исправления переживают обновления версий)

ext-reload

Перезагрузка самого расширения с диска

fetch-timings

Диагностическое кольцевое буфер для fetch-ретрансляций

Мост GM

window.GM (в page.js, мир MAIN) предоставляет этот подмножество в стиле Tampermonkey:

Метод

Описание

GM.getValue(key, default)

Чтение незарезервированного строкового ключа из расширенного chrome.storage.local (не для каждого токена)

GM.setValue(key, value)

Запись незарезервированного строкового ключа в расширенное хранилище

GM.deleteValue(key)

Удаление незарезервированного строкового ключа из расширенного хранилища

GM.listValues()

Перечисление незарезервированных ключей хранилища

GM.xmlhttpRequest(opts)

HTTP-запрос через фоновый ретранслятор (устойчив к CSP)

GM.addStyle(css)

Внедрение CSS

GM.setClipboard(text, type)

Запись в буфер обмена

GM.notification(opts)

Системное уведомление

GM.openInTab(url, opts)

Открыть новую вкладку

GM.download(opts)

Запустить загрузку

GM.info

Метаданные скрипта

Доступ к cookie — это возможность оператора, а не страницы: он осуществляется через команды cookies / set-cookie / remove-cookie / clear-cookies, перечисленные выше, и намеренно не предоставляется контексту страницы — page.js выполняется на каждой подходящей верхнеуровневой странице, которая в противном случае могла бы читать cookie, которые её собственный document.cookie не видит.

Архитектура

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 ────────────────────┼──►│                      │
└────────────────────────────────────────────┘   └──────────────────────┘
  • Один поток SSE: Фон открывает одно соединение fetch SSE (tab=extension) и направляет команды на целевую вкладку через chrome.tabs.sendMessage. 30-секундный сторожевой таймер принудительно переподключается при устаревших потоках.

  • Маршрутизация по вкладкам: Команды адресуются конкретному tabId Chrome или транслируются на все вкладки.

  • Поведение CSP: GM.xmlhttpRequest отправляет HTTP-работу в fetch фонового сервис-воркера. Обычно eval использует внедрение в мир MAIN без баннера. Когда зонд без исходного кода сообщает, что динамическая компиляция недоступна, CDP предоставляет запасной вариант CSP и показывает отладочный баннер Chrome, если он подключается; сбой подключения достигает ретранслятора страницы/Blob. Результирующее значение world описывает этот канал и не делает никаких заявлений о целостности.

Сервер

Запустите мост с его тремя обязательными настройками — он завершает работу при запуске без DAEDALUS_DIR или DAEDALUS_PORT, и каждый маршрут управления мостом закрывается с ошибкой без настроенного токена:

DAEDALUS_DIR=<data-dir> DAEDALUS_PORT=<port> DAEDALUS_TOKEN=<bridge-token> python3 server.py

server.py работает под любым супервизором, который вы используете (здесь — systemd unit). Поместите перед ним TLS-терминирующий обратный прокси, если вы выставляете его за пределы localhost; сам мост говорит по обычному HTTP. Маршруты управления мостом и хранилищем сравнивают свой токен с одним настроенным секретом, разрешаемым через путь конфигурации CLI: TOKEN — это одноразовое переопределение, в противном случае требуется DAEDALUS_TOKEN (и встраиваемый модуль _settings может его предоставить). Отсутствие конфигурации и несоответствия закрываются с ошибкой. Только маршруты, обращённые к странице, POST /segment и GET /segment-status, используют вместо этого возможность, ограниченную задачей.

Встроенный интерфейс MCP принимает три необязательных настройки, документированные вместе, потому что они описывают один слушатель: DAEDALUS_MCP_PORT (по умолчанию 8086) — это порт loopback, к которому он привязывается; его обработчики инструментов используют фактический привязанный URL loopback моста, в том числе когда DAEDALUS_PORT=0; DAEDALUS_LOCAL_URL явно переопределяет этот URL для автономного развёртывания MCP, стоящего перед мостом, работающим в другом месте; и DAEDALUS_MCP_ALLOWED_HOSTS (по умолчанию 127.0.0.1:*,localhost:*) — это разделённый запятыми список разрешённых хостов, который принимает его защита от DNS-ребinding, поэтому при размещении /mcp за публичным именем хоста необходимо указать это имя там.

Конечные точки: 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 требует настроенный токен моста; только POST /segment и GET /segment-status принимают sig, ограниченный задачей. См. CLAUDE.md для заметок о полезной нагрузке и конечных точках.

POST /poll потребляет и удаляет устаревший файл широковещательной команды, если он присутствует.

PUT /command помещает в очередь FIFO для каждой цели (команды подряд к одной вкладке больше не перезаписывают друг друга), помечает каждую идентификатором доставки (_did), чтобы расширение могло дедуплицировать повторно доставленный кадр, и удаляет по TTL команды, не востребованные после DAEDALUS_CMD_TTL секунд (по умолчанию 90). Фоновый сборщик применяет этот TTL и удаляет пустые каталоги очередей, даже если потребитель SSE никогда не подключается. GET /health сообщает о живости потока/реестра/последней доставки для обнаружения молча мёртвого моста.

Мост отклоняет повторные носители авторизации вместо выбора одного значения: это включает token в строках запроса или JSON-телах и job / sig на маршрутах возможностей сегмента, даже если повторные значения равны или пусты. Транспорт MCP также отклоняет повторные заголовки Authorization, Mcp-Session-Id, Host и Origin, а также повторные аргументы job для инструментов сегмента.

Когда расширение публикует результат, сервер предоставляет _did команды как deliveryId и назначает свежий resultGeneration. Ожидающие CLI и MCP сначала просматривают общий слот результата, сопоставляют и идентификатор команды, и deliveryId, затем условно потребляют это поколение с помощью GET /result?...&consume=1&expected=<resultGeneration>. Если другой результат заменяет слот между этими запросами, условное потребление оставляет новый результат на месте и сообщает, что он не был потреблён. Голый consume=1 без expected остаётся разрушительным совместимым чтением текущего слота.

GET /stream удерживает соединение SSE открытым неопределённо долго, доказывая живость комментарием keepalive каждые DAEDALUS_STREAM_KEEPALIVE секунд (по умолчанию 15), а не циклически переподключаясь по таймеру; DAEDALUS_STREAM_MAX_AGE (по умолчанию 3600) — это только потолочный предел на крайний случай. Путь закрытия закреплён в tests/test_stream_lifecycle.py.

DAEDALUS_MAX_BODY_SIZE (по умолчанию 64 * 1024 * 1024 байт, 64 МиБ) ограничивает тела запросов, читаемые обработчиками POST, PUT и DELETE; объявленное тело больше лимита получает 413, а запрос без объявленного Content-Length получает 411, а не читается как пустое тело. Увеличьте его при ретрансляции больших сегментов или других полезных нагрузок.

Значения вызывающего, хранящиеся в файловой системе, используют одну политику компонентов пути: она отклоняет .., управляющие символы C0/C1 и суррогаты, недопустимые для Windows символы пути и имена устройств, завершающие точки или пробелы, а также UTF-8-кодировки длиннее 240 байт. Токены моста строже и также отклоняют точки и подчёркивания. Другие имена заданий UTF-8 принимаются, поэтому клиенты должны URL-кодировать их в строках запроса.

POST /segment-job копирует три фиксированные квоты в каждую новую запись задания: DAEDALUS_MAX_SEGMENT_INDEX (по умолчанию 99999), DAEDALUS_MAX_SEGMENTS_PER_JOB (по умолчанию 10000) и DAEDALUS_MAX_SEGMENT_JOB_SIZE (по умолчанию 4 * 1024 * 1024 * 1024 байт, 4 ГиБ). Изменение этих настроек влияет на задания, созданные впоследствии; задание с сохранёнными квотами продолжает использовать свои записанные значения. DAEDALUS_MAX_BODY_SIZE также ограничивает каждый отдельный запрос сегмента.

Безопасность

Прочтите это перед установкой расширения.

Оно внедряет шим window.GM в каждую подходящую верхнеуровневую страницу (<all_urls>, мир MAIN), что позволяет скрипту, отправленному с put, совершать межсайтовые запросы. Следствие — та часть, о которой нужно подумать осознанно: любой подходящий сайт, который вы посещаете, может вызвать этот шим, поэтому этот сайт может отправлять межсайтовые запросы через привилегии расширения, точно так же, как это сделал бы менеджер пользовательских скриптов с подстановочным @match. Если это неприемлемо для вашего просмотра, сузьте matches в extension/manifest.json до хостов, которыми вы действительно управляете, и примите, что мост тогда ничего не делает на других страницах.

Токен моста и URL сервера недоступны через методы GM-хранилища страницы. Они находятся в chrome.storage.local под префиксом daedalus-, и ретранслятор отказывается читать, записывать или перечислять это пространство имён для скриптов страницы. Без этого правила любой посещённый сайт мог бы прочитать ваш токен моста с помощью GM.getValue('daedalus-token') или перенаправить мост с помощью GM.setValue('daedalus-server', ...), молча. tests/test_repo_contract.py закрепляет это правило.

Исходный код хотфикса намеренно является наблюдаемым состоянием страницы, а не конфиденциальным состоянием расширения. Фон выбирает постоянные исправления плюс непостоянные исправления, соответствующие версии расширения, и выполняет каждое из них на странице через те же каналы, которые использует обычный eval — внедрение в мир MAIN, где страница разрешает динамическую компиляцию, CDP, где нет, — поэтому страница может наблюдать исходный код, который она выполняет по любому каналу.

Носитель MCP не может указать файл на хосте сервера для чтения процессом MCP. Инструменты MCP put, внедрение/удаление CSS и хранение хотфиксов принимают только встроенный исходный код. Локальный CLI сохраняет своё удобство с путями к файлам, но процесс CLI читает выбранный оператором файл и отправляет его содержимое встроенно. Таким образом, владение токеном моста предоставляет документированные полномочия управления браузером и расширением, включая чтение сохранённого исходного кода хотфиксов, но не добавляет произвольное чтение файловой системы хоста через эти инструменты.

Маршруты управления мостом и хранения требуют настроенного токена моста; /segment и /segment-status — единственные исключения для capability. Сервер сравнивает токены запросов с секретом, полученным из TOKEN или DAEDALUS_TOKEN, и отклоняет запросы, если секрет не настроен. POST /segment-job требует этот токен моста, потому что он выпускает capability с областью действия задания; ненадёжный JavaScript страницы использует capability только для отправки и запроса этого задания. Любой, кто владеет токеном моста, может управлять вашим браузером. Не открывайте порт моста за пределы loopback без обратного прокси, завершающего TLS, и относитесь к токену как к учётным данным.

Результаты eval не несут гарантии целостности значения. JavaScript, выполняемый на странице, которой вы не управляете, возвращает значение, которое эта страница может выбрать, независимо от того, какой канал его выполнил. Поле world говорит о том, как выполнялся отправленный исходный код, а не о том, стоит ли доверять его значению: page-main — это обычная инъекция в MAIN-world, cdp — это запасной вариант CSP инспектора, а page:<hostname> — это ретранслятор. Обязательный префикс page: добавляется вне контекста страницы, поэтому ни одно имя хоста не может дать cdp или page-main; эта устойчивость к коллизиям носит только описательный характер. На page-main привязки eval и Function, принадлежащие странице, могут читать отправленный исходный код, а также влиять на возвращаемое им значение.

Компиляция CDP избегает разрешения привязок eval и Function страницы, а реализация получает прямые ссылки по ссылке перед их сериализацией. Эти транспортные механизмы не меняют границу доверия: отправленный исходный код по-прежнему читает состояние, контролируемое страницей, и может направлять любое значение, включая примитив, через механизмы обещаний страницы до того, как CDP его получит. Всякий раз, когда отправленный исходный код использует эти контролируемые страницей пути, страница также может выбрать значение, возвращаемое на cdp.

Ретранслятор принимает только сохранённый вызов, связанный с этим случайным id, требует, чтобы вкладка отправителя совпадала, и использует запись один раз. Понижение версии не раскрывает токен моста, URL сервера, id доставки или маршрут результата, не предоставляет дополнительных полномочий расширения или браузера и не может повлиять на вызов в другой вкладке. Эти свойства защищают маршрутизацию и полномочия браузера; они не превращают маркер ретранслятора или возвращаемое им значение JavaScript в сигнал доверия.

Развёртывание

Мост говорит по простому HTTP на loopback. Маршруты управления мостом и хранения требуют настроенного токена и закрываются при его отсутствии; только /segment и /segment-status используют capability с областью действия задания. Две вещи, которые он намеренно НЕ делает, потому что они принадлежат тому, что стоит перед ним:

  • TLS и CORS. server.py не отправляет заголовки CORS. Когда мост кросс-оригинальный, вызовы fetch на стороне страницы в примере HLS-ретранслятора нуждаются в доступе к обоим GET /segment-status и POST /segment; прокси должен разрешать исходный код страницы на обоих маршрутах и обрабатывать методы/заголовки, требуемые предварительным запросом POST. Если статус GET заблокирован, недоступен, не-2xx или недействителен, пример считает задание свежим и повторно отправляет каждый сегмент. Эти записи по-прежнему заменяют те же файлы по индексу, но выполнение теряет экономию на возобновлении/пропуске. Маршрутизируйте оба запроса через GM-мост расширения, если развёртывание не может предоставить такую политику CORS.

  • Обслуживание загруженных файлов. Панель управления ссылается на загрузки по адресу /uploads/<path>, но у моста нет такого маршрута. Не обслуживайте имена и байты загрузок, предоставленные вызывающим, напрямую на источнике панели управления, где исполняемый контент разделял бы источник с хранилищем панели, несущим токен. Либо оставьте эти ссылки недоступными, либо заставьте /uploads/ перенаправлять на отдельный источник только для загрузки, который принудительно использует Content-Disposition: attachment, использует application/octet-stream и отправляет X-Content-Type-Options: nosniff. GET /upload (список) и GET /screenshot обслуживаются самим мостом и не нуждаются в помощи прокси.

Запустите его напрямую с настроенным токеном, и оба этих пункта просто отсутствуют — всё остальное работает.

Файлы

Файл

Описание

extension/manifest.json

Манифест MV3

extension/background.js

Сервисный воркер — SSE, диспетчеризация команд, ретрансляция fetch, скриншоты, CDP, cookies, загрузки

extension/content.js

Ретрансляция сообщений между страницей и фоном

extension/page.js

Мост MAIN-world — window.GM + обработчик eval

extension/options.html / options.js

UI настроек токена/сервера

server.py

Отладочный сервер (также размещает поток демона MCP на 127.0.0.1:8086)

mcp_server.py

MCP-сервер — связывает поверхность команд расширения с server.py по HTTP

scripts/mcp_probe.py

Минимальный клиентский помощник MCP для ручной проверки

scripts/check_versions.py

Проверка согласованности версий / обновление во всех местах версий

.githooks/

pre-commit + pre-push проверки согласованности версий

daedalus_cli/

Оболочка CLI, опубликованная как колесо daedalus-cli

dashboard/

Поверхность управления браузером, которую server.py обслуживает по адресу /dashboard

examples/

Скрипты для запуска на странице через put — см. ниже

tests/, run_tests.py

Наборы тестов; python3 run_tests.py запускает все из них

Примеры

examples/ содержит скрипты, предназначенные для запуска внутри страницы с помощью put. Пять из шести демонстрируют часть моста, а не конкретный сайт; пример Discord намеренно специфичен для сайта, потому что прокрутка назад через виртуализированный список — это техника, которую нельзя показать без реального виртуализированного списка:

Пример

Показывает

request-log-hotfix.js

Постоянный hotfix в MAIN-world на document_start и почему все три этих слова важны

hls-segment-relay.js

Ретранслятор /segment + /segment-status: возобновляемый, идемпотентный, с ограниченными повторами

kill-hls.js

Поиск и уничтожение экземпляра плеера, который не перестаёт повторять попытки

react-fill-input.js

Заполнение управляемого React ввода, чтобы собственное состояние React действительно изменилось

scrape-discord-messages.js

Прокрутка назад через виртуализированный список сообщений и его извлечение

open-tabs.js

Минимально возможный вызов GM.openInTab

Они получают свою конфигурацию через подстановку __PLACEHOLDER__ перед отправкой, потому что put отправляет скрипт, а не вызывает функцию, и у него нет способа передать аргументы.

Каждый из них — это ТЕЛО асинхронной функции, а не отдельный скрипт: мост оборачивает то, что вы отправляете. Вот почему большинство заканчиваются верхнеуровневым return, и один из них — scrape-discord-messages.js — также использует верхнеуровневый await; оба допустимы там, где они реально выполняются. node --check разбирает каждый файл в обёртке CommonJS, которая разрешает верхнеуровневый return, но отклоняет верхнеуровневый await — поэтому именно этот файл не проходит проверку синтаксиса.

Разработка

python3 run_tests.py запускает все наборы и выводит одну агрегированную строку, называющую то, что было проверено: сколько наборов запущено, сколько тестов прошло и сколько пропущено. Набор, в котором все тесты пропущены, ничего не проверяет, поэтому он сообщается как OVERALL: INCOMPLETE с ненулевым кодом выхода, а не как проход; обычная причина — отсутствующая зависимость, и строка NO COVERAGE над ним называет набор. Установка дополнительного пакета mcp (см. «Установка») — это то, что нужно набору MCP.

Строка версии находится в нескольких местах: в расширении, панели управления и пакете CLI. python3 scripts/check_versions.py — это список: он называет каждое место и сообщает, сколько их найдено, поэтому этот абзац не повторяет число, которое устарело бы при следующем добавлении. Обновляйте их вместе, никогда вручную:

python3 scripts/check_versions.py --set 0.18.0   # rewrite every site
python3 scripts/check_versions.py                # verify the working tree

.githooks/pre-commit проверяет индекс, а .githooks/pre-push проверяет каждый отправленный коммит, поэтому частичное обновление не может попасть в репозиторий. Каждый клон должен один раз включить хуки — git не будет запускать хуки из отслеживаемой директории иначе:

git config core.hooksPath .githooks

Если браузер загружает расширение из другого checkout, чем тот, который вы редактируете, установите хуки и там — и помните, что перезагрузка расширения перечитывает файлы на стороне браузера, а не на вашей.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables MCP clients to drive a real, logged-in Chrome browser for web automation tasks like navigation, clicking, typing, and screenshotting.
    2 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables browser automation over MCP using a real Chrome browser with existing profile, supporting real tabs, downloads, cookies, and RPA workflows.
    104 npm
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables MCP clients to control real Chrome tabs in existing profiles, supporting navigation, interaction, screenshots, and console/network inspection across multiple Chrome instances.
    -
  • A
    license
    B
    quality
    C
    maintenance
    Enables MCP clients to drive a real Chromium browser for automation, including navigation, JavaScript execution, CDP commands, network capture, and multi-tab control.
    7
    GPL 3.0