Caputchin
Понимание Caputchin

Как Caputchin изолирует игры в песочнице

Игра Caputchin это сторонний код, который работает в браузере твоего посетителя, на твоей странице. Приходит ли она из маркетплейса или ты размещаешь её сам, Caputchin трактует её как не доверенную по умолчанию и оборачивает в несколько независимых слоёв изоляции, так что баговая или враждебная игра может отрендериться, сыграть и отчитаться о результате, но не может дотянуться ни до чего другого: ни до твоей страницы, ни до данных твоего посетителя, ни до сети.

Эта страница объясняет каждую меру, почему каждая существует и как всё это взаимодействует с CSP, который ты добавляешь на свой собственный сайт.

Модель угроз в одну строку

Игра запускает произвольный JavaScript (и возможно WebAssembly), поставленный третьей стороной. Цель изоляции поэтому такова: игра может вычислять и рисовать, и она может вернуть результат виджету, но не может прочитать или повлиять ни на что вне своего собственного кадра. Каждый слой ниже служит этой одной цели, и они это эшелонированная защита, ни один отдельный слой не считается совершенным.

Слой 1: iframe в песочнице с непрозрачным источником

Каждая игра работает внутри <iframe>, который виджет строит с намеренно минимальным атрибутом sandbox. Единственный выданный токен это allow-scripts, потому что игра это JavaScript и должна работать. Всё остальное удержано, и важнейшее упущение это allow-same-origin.

Без allow-same-origin браузер даёт кадру уникальный, нулевой источник. Этот один факт это несущая граница:

  • Игра не может прочитать DOM, cookie, localStorage твоей страницы или сессию Caputchin, она запечатана в одноразовый источник, чуждый твоему.
  • Поскольку кадр также никогда не может навигировать твоё верхнее окно, открывать попапы, отправлять формы или всплывать нативные диалоги (все эти возможности это токены песочницы, которые Caputchin не выдаёт), нет пути от игры обратно наружу к твоей странице.

Сочетание allow-scripts без allow-same-origin это вся суть: браузер выполняет код игры, но трактует кадр как чужой источник, который не может коснуться ничего твоего. Caputchin никогда не добавляет allow-same-origin к игровому кадру. (Как следствие, каждое сообщение, которое игра постит виджету, приходит от источника "null", чего проверки канала виджета и ожидают, не-нулевой источник сам сигналил бы о неверно настроенной песочнице.)

Слой 2: документ кадра строится инлайн, а не запрашивается

Виджет не наводит iframe на удалённую страницу. Он конструирует весь HTML-документ кадра инлайн (через srcdoc) и вручает его кадру с нулевым источником. Этот документ крошечный: строгий мета-тег CSP (следующий слой), маленький бутстрап рантайма Caputchin и один тег <script>, который загружает бандл игры.

Это один и тот же механизм для обоих путей доставки, различается только URL бандла:

Источник игрыURL бандла в инлайновом документе
Маркетплейс (разрешено платформой)Закреплённый URL бандла с хешем целостности, который Caputchin разрешил при монтировании.
Самостоятельно размещённый (твой game-src)URL, который ты поставил.

Поскольку документ авторствует виджет, а не игра, игра никогда не контролирует CSP, рантайм или структуру кадра, только то, что делает её собственный бандл после загрузки.

Слой 3: Subresource Integrity для игр из маркетплейса

Когда Caputchin отдаёт игру из маркетплейса, он прибивает бандл к неизменной версии и записывает её криптографический хеш (SHA-384). Тег <script>, который загружает бандл, несёт этот хеш как атрибут integrity с crossorigin="anonymous", так что сам браузер отказывается выполнять бандл, если хоть один байт отличается от закреплённого. Скомпрометированный CDN не может подставить другой код; загрузка падает закрыто.

У самостоятельно размещённой игры нет утверждённого платформой хеша (ты корень доверия для своих собственных байтов), так что атрибут integrity для этого пути просто опускается. Смотри контракт реплея о том, как результат игры из маркетплейса независимо перевыводится на сервере, отдельная гарантия от целостности.

Слой 4: строгая инлайновая Content-Security-Policy

Инлайновый документ несёт тугую Content-Security-Policy. Хотя кадр уже с нулевым источником и в песочнице, CSP дополнительно ограничивает, что может делать загруженный код, она отрицает всё по умолчанию и заново выдаёт только минимум:

ДирективаЗначениеПочему
default-src'none'Отрицать всё, что явно не разрешено ниже.
script-srcхеш собственного инлайнового рантайма Caputchin + источник бандла игры + 'wasm-unsafe-eval'Запускать только точный бутстрап Caputchin (закреплённый хешем sha256-, а не 'unsafe-inline') и собственный бандл игры. 'wasm-unsafe-eval' даёт движку WASM компилировать без выдачи полного 'unsafe-eval'.
connect-src'none'Анти-эксфильтрационная директива. Игра не может fetch, XHR, открыть WebSocket или отправить маяк куда-либо. Никакого сетевого выхода вообще.
img-srcdata: (плюс любые источники ассетов скина, ниже)Спрайты инлайн или с разрешённого хоста ассетов.
media-srcdata: (плюс любые источники ассетов скина, ниже)Аудио/видео инлайн или с разрешённого хоста ассетов.
font-srcdata:Только инлайновые шрифты.
style-src'unsafe-inline'Игры задают инлайновые стили; никаких внешних таблиц стилей. (Стили не могут эксфильтровать данные так, как скрипт или сеть.)

Чистый эффект: игра работает, рендерится и отчитывается о результате через мост SDK и не может дотянуться ни до чего другого, особенно не до сети. Этот CSP задаёт Caputchin, и это не то, что ты настраиваешь; он перечислен здесь, чтобы ты в точности понимал, насколько игра заключена.

Единственное место, где клиентская настройка расширяет CSP кадра

Скины могут навести поля изображения или аудио на абсолютный URL на твоём собственном CDN (смотри скины). Чтобы эти ассеты загрузились внутри запертого кадра, Caputchin добавляет только эти точные источники ассетов в img-src / media-src кадра, и больше никуда. script-src и connect-src никогда не расширяются, так что даже разрешённый источник ассета нельзя использовать для загрузки кода или открытия сетевого канала. Это единственный, узкий способ, которым настроенное значение влияет на политику кадра, и это всё равно только для ассетов.

Слой 5: односторонний канал результата

У игры нет вызываемой поверхности в твою страницу. Единственный выход это мост SDK: игра вызывает один метод, который через postMessage шлёт свой непрозрачный результат (свою трассу) виджету, который живёт на твоём источнике. Виджет это то, что говорит с API Caputchin; игра никогда не говорит (она не может, connect-src это 'none'). Так что путь доверия это игра → виджет → твой бэкенд, и каждый прыжок только передаёт результат вперёд, никогда не выдаёт игре досягаемость назад.

CSP, который ты задаёшь на своей собственной странице

Слои выше защищают тебя и твоего посетителя от игры. Content-Security-Policy на твоей собственной странице это другой, дополняющий рычаг: он защищает твою страницу от всего, и Caputchin спроектирован работать под строгим. Тебе не надо ослаблять свой CSP, чтобы встроить Caputchin; тебе надо разрешить несколько источников, которые виджет легитимно использует.

Как минимум, виджету нужно:

ДирективаРазрешитьПочему
script-srcисточник, с которого ты загружаешь скрипт виджета, плюс 'unsafe-eval'<script>, который определяет <caputchin-widget> / <caputchin-game>, CDN, который ты выбрал (jsDelivr, твой собственный хост или caputchin.com). 'unsafe-eval' даёт работать испытанию инструментирования; оно требуется, пока инструментирование включено, и ты можешь убрать его, выключив инструментирование на странице Безопасность ключа.
connect-srchttps://caputchin.comВиджет вызывает API Caputchin, чтобы настроить и подтвердить проверку. Если ты наводишь виджет на другой хост API, разреши его вместо этого.
frame-srchttps://caputchin.comУправляет игровым iframe. Поскольку кадр использует инлайновый документ srcdoc, браузеры применяют к нему твой frame-src, так что разреши здесь источник Caputchin.
worker-srcblob:Решатель proof-of-work работает целиком в Web Workers, созданных из blob:-URL, без отката на основной поток, так что это обязательно. Если ты опускаешь worker-src, браузеры откатываются на child-src, затем default-src, а default-src 'self' сам по себе не разрешает blob:.

По желанию добавь 'wasm-unsafe-eval' в script-src: это даёт решателю proof-of-work работать как быстрый WebAssembly. Это не требуется, решатель откатывается на более медленную реализацию на чистом JavaScript (всё ещё внутри Worker) без него.

Тебе не нужно разрешать собственный источник бандла игры (jsDelivr, CDN игры, твой хост game-src) в CSP твоей страницы: этот бандл загружается внутри кадра в песочнице, под собственным CSP кадра, описанным выше, а не под политикой твоей страницы. Твоя страница только кадрирует Caputchin; Caputchin заключает то, что работает внутри.

Если элемент Caputchin молча не загружается под твоим CSP, твоя консоль браузера называет заблокированную директиву; добавь источник или ключевое слово, которое она называет, в эту директиву. Начни строго и открой ровно то, что просит консоль. Тебе никогда не нужен 'unsafe-inline'. Тебе нужен blob: для воркеров (worker-src) и, пока инструментирование включено, 'unsafe-eval' в script-src; требование 'unsafe-eval' уходит, если ты выключаешь инструментирование на странице Безопасность ключа. 'wasm-unsafe-eval' опционален (он ускоряет решатель proof-of-work, который иначе работает медленнее на JavaScript внутри того же Worker).

Почему так много слоёв

Каждый слой был бы значимой границей сам по себе; вместе они значат, что сбой в одном это не пробой. Один нулевой источник уже отгораживает игру от твоих данных; один CSP уже убивает сетевой выход; одна целостность уже прибивает точные байты. Caputchin запускает их все, потому что не доверенный сторонний код это ровно то место, где эшелонированная защита оправдывает себя.

См. также

На этой странице