🟡 Как мы пустили сторонний код в продукт с 600 000 пользователей
Меня зовут Женя Успенский, я руковожу разработкой фронтенда в Яндекс Трекере и отвечаю за архитектуру платформы плагинов. Это отдельный механизм, который позволяет без навыков кодинга расширять Трекер собственными модулями без модификации его ядра.
Хочу рассказать, как построить безопасную экосистему плагинов: изолировать сторонний JavaScript, организовать взаимодействие с API и при этом сохранить удобный Developer Experience.
🤯 Когда мы задумали дать пользователям возможность писать свой код прямо внутри Трекера, первая реакция команды безопасности была: «Вы что, с ума сошли?» И ребят можно понять. Пустить сторонний JavaScript в B2B-продукт с чувствительными данными — это прямой путь к XSS, вытаскиванию токенов и утечкам.
Поэтому нам нужна была архитектура, которая позволит создавать сложные интерактивные плагины с динамическим UI, но при этом упакует их в железную изоляцию.
🈶 Как мы изолировали код
Взвесив все за и против, остановились на классической изоляции через iframe. Это решение выглядит не так концептуально, зато даёт гарантии безопасности и позволяет быстро выйти в продакшен.
Как это устроено:
🟡 Каждый плагин отдаётся с отдельного изолированного хоста, у которого нет доступа к cookie и хранилищу основного приложения
🟡 В параметрах iframe мы явно разрешаем только выполнение скриптов (allow-scripts) и жёстко ограничиваем все остальные возможности
🟡 Политика безопасности запрещает практически всё, в том числе сетевые запросы на любые внешние адреса, которые отличаются от собственного домена iframe
Все ключевые модули получившейся системы мы оформили как самостоятельные, независимые пакеты. Это позволяет подключать к платформе плагинов любые другие сервисы и не переписывать архитектуру изоляции с нуля. А общение плагина с сервисом и внешним миром происходит через специальный объект Bridge, который для транспорта использует postMessage.
✨ Мы хотели дать авторам плагинов хороший Developer Experience: строгую типизацию, автокомплит параметров в IDE и подсказки, но стандартные пути нас не устраивали. Поэтому мы нашли решение на стыке возможностей TypeScript-типов и объекта Proxy.
🈶 Что мы сделали
Мы отказались от генерации JS-кода в пользу генерации исключительно TypeScript-типов (.d.ts) напрямую из нашей OpenAPI-схемы. А вместо абстрактных имён методов мы стали использовать нативные REST-пути в виде строковых литералов. Синтаксис вызова API в нашем SDK выглядит так:
const issue = await sdk.api.v3.get['/issues/{id}']({
path: { id: 'QUEUE-123' },
query: { expand: 'comments' }
});
Когда разработчик пишет sdk.api.v3.get[ и ставит открывающую кавычку, TypeScript воспринимает это как обращение к свойству огромного интерфейса. Благодаря этому движки автокомплита и в VS Code, и в WebStorm моментально выводят список всех доступных эндпойнтов. И самое главное: IDE показывает полную JSDoc-документацию прямо во время набора строки.
Весь рантайм-роутер умещается буквально в 10 строк благодаря ES6 Proxy. Так что в исполняемом JS-файле нашего SDK нет ни одного упоминания эндпойнтов.
➡️ Читайте все подробности в статье на Хабре. Там я рассказал, как работает платформа плагинов и как мы тестировали её внутри Яндекса. А также поделился тем, что ещё есть под капотом нашего механизма.
Подписывайтесь:
💬 @Yandex4Frontend