Перейти к содержимому
Версия 1.0 ещё не выпущена; проект находится на стадии pre-MVP. См. возможности и ограничения версии 1.0.

Безопасность плагинов

Здесь объясняется, как хранить и заменять токен установки плагина Dee Wan, как проверяются полномочия пользователя при выполнении команды и чем ограничен выданный плагину доступ. Также описано, что сможет и чего не сможет сделать злоумышленник, получивший контроль над плагином.

Каждый пример воспроизводится в собственных тестах Dee Wan через настоящие маршруты. Приведённые идентификаторы и секреты вымышлены.

Все маршруты описаны в Protocol v1, а порядок установки — в руководстве Разработка плагинов.

browser ── user session ──► Dee Wan admin Worker
│ control database: installations, token hashes, grants, launch codes
│ tenant database: content, versions, workflow, audit
│
│ HTTPS + installation bearer token
plugin service ◄──────────────┘
│ its own database, its own clock, its own sessions
└── HTTPS + installation bearer token ──► /api/plugin/v1 ──► the same lifecycle service a person uses

Эту границу обеспечивают три свойства:

  1. API плагинов — это не admin API. Он находится под /api/plugin/v1, имеет собственное middleware и использует общий сервис жизненного цикла. Отдельной реализации переходов, которая могла бы отличаться от переходов, запускаемых пользователем, нет.
  2. Интерфейс плагина работает в проверенных границах. Декларативные карточки используют элементы ядра. Необязательные фреймы используют проверенный origin плагина, изолированный sandbox и MessageChannel. Фрейм не получает сессию администратора или токен инсталляции. Поля проходят обычное сохранение, операции — актуальную проверку полномочий хоста. Сторонний HTML/CSS/JS работает в отдельном фрейме с непрозрачным origin в предпросмотре и поддерживаемых рендерах сайта, без доступа к родительскому документу администратора или сайта.
  3. Никакого доступа плагинов к базе данных. Плагин не получает ни клиента Prisma, ни привязки D1, ни учётных данных R2, ни доступа к аккаунту Cloudflare. Всё, что он может прочитать или изменить, проходит через маршруты под /api/plugin/v1 и через ответы, которые ядро у него запрашивает, — результат операции, решение политики или список источника медиа. Ядро проверяет их, прежде чем использовать.
Форма dwp_ плюс 43 URL-безопасных символа — 256 бит случайности из crypto.getRandomValues
Выдаётся плагину Один раз, при передаче активации, по HTTPS на собственный origin манифеста
Хранится ядром Только хеш SHA-256. Нет пути в коде, который мог бы снова вывести токен
Отправляется плагином Authorization: Bearer <token>, в каждом запросе
Никогда не встречается в URL, параметре query, теле ошибки, строке лога, записи аудита или фикстуре
Область действия Одна инсталляция, то есть один плагин на одном сайте

Префикс позволяет распознать утёкшую строку как учётные данные плагина Dee Wan, а защиту от угадывания обеспечивает случайность токена. Некорректное значение bearer отклоняется по формату ещё до чтения базы данных: такая попытка подбора не приводит к поиску в БД.

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

  1. Администратор получает от плагина новый код активации.
  2. Ядро выпускает новый токен и отправляет его вместе с этим кодом на закреплённый activation_url из сохранённого манифеста. Не на URL, переданный в момент ротации.
  3. Только при 2xx ядро заменяет хеш. Если плагин отказывает или недоступен, старый токен продолжает работать — неудачная ротация не должна лишать работающий плагин доступа.
  4. Предыдущий хеш остаётся действительным не более 10 минут, поэтому запрос, уже находящийся в пути, не обрывается посреди ротации.
  5. Первый запрос с НОВЫМ токеном немедленно завершает период одновременного действия. После этого запрос со старым токеном получает окончательный отказ plugin_unauthorized.

Шаг 5 важен, если ваш плагин запускает больше одного воркера. Если один воркер начинает использовать новый токен, пока другой ещё держит старый в памяти, следующий запрос второго воркера отклоняется с окончательным отказом 401 plugin_unauthorized, и если он завершит свою работу по этому ответу, работа будет потеряна. Либо переключите все воркеры на новый токен вместе, либо при plugin_unauthorized один раз перечитайте сохранённый токен и повторите с ним, только если он изменился с момента отправки запроса.

Ядро никогда не сохраняет и никогда не логирует код активации. Плагину, который выдал код, нужно только то, чем его можно проверить: плагин Scheduling хранит SHA-256-хеш каждого кода и срок его действия, но никогда не сам код.

disabled и revoked оба останавливают API плагинов на входе — до доступа к арендатору, до чтения тела. Состояние revoked окончательно: хеши удаляются, и этот токен уже никогда не может стать действительным. Удаление сначала выполняет отзыв, и Dee Wan ничего не утверждает о данных, которых не видит.

Интерфейс управления плагина — это отдельная страница, а cookie сессии Dee Wan никогда не покидают Dee Wan. Связь между ними — код запуска:

  • случайный, одноразовый, хранящийся хешированным и действительный 60 секунд
  • привязанный к инсталляции, сайту, пользователю, действию и — для действия над контентом — к одному экземпляру контента
  • обмениваемый БЭКЕНДОМ плагина, с его собственным bearer-токеном, на запись делегирования
  • код, выпущенный для одной инсталляции, не может быть обменян другой
  • ответ запуска содержит Referrer-Policy: no-referrer. Собственная страница управления плагина должна отправлять тот же заголовок, не загружать никаких сторонних ресурсов до обмена и перенаправлять на URL без dee_wan_launch, как только обмен выполнен.

Перед выпуском кода ядро проверяет сессию и доступ пользователя к сайту. Обычному запуску manage нужно право site:plugins. Для запуска manage с объявленным представлением (редакционный календарь или релизы) достаточно одной модели, доступ к которой выдан плагину и которую пользователь может читать: все записи или только свои. Действию над контентом нужно право чтения конкретной записи. Если её модель не входит в права установки плагина, запуск отклоняется с model_not_granted. Административный интерфейс предлагает действия плагина только для записей моделей, к которым ему предоставлен доступ. Делегирование сохраняет в model_ids точные модели из этого набора, которые пользователь может читать с read или read:own; для действия над контентом это модель выбранной записи. Каждое последующее чтение и каждая команда отклоняются для записи вне этого набора или записи, которую пользователь больше не может читать.

Делегирование — это не учётные данные. Оно само по себе не предоставляет возможностей, а указывает пользователя, чьи полномочия нужно заново проверить:

Каждая команда плагина указывает delegation_id. Прежде чем что-либо записать, ядро заново читает ТЕКУЩИЕ полномочия этого пользователя из управляющей базы данных. Оно никогда не использует вместо них полномочия, сохранённые при создании расписания.

Что охватывает делегирование:

  • Делегирование действия над контентом привязано к одной записи и действует только в её группе переводов. Группа публикуется целиком, поэтому граница проходит по ней. Использование этого делегирования для любой другой записи возвращает requester_unauthorized.
  • Делегирование manage не привязано к конкретной записи. Оно позволяет обратиться к записи, только если её модель всё ещё входит в выданный набор прав и в model_ids делегирования, а пользователь по-прежнему может прочитать эту запись. Право читать только свои записи не даёт доступа к чужим. В остальных случаях возвращается requester_unauthorized или content_not_found.
  • Ядро не проверяет action делегирования при команде. Действие сообщает плагину, какой экран открыть. Это не право доступа.

У делегирования нет срока действия. Это сделано намеренно: задание, запланированное сегодня, может выполниться в следующем месяце. Оно должно по-прежнему ссылаться на пользователя, который его создал. Делегирование ограничено не сроком, а следующими условиями:

  • ТЕКУЩИЕ полномочия человека, перечитываемые при каждой команде, — он всё ещё должен быть активным пользователем, всё ещё иметь доступ к сайту и всё ещё иметь право, нужное для перехода
  • состояние установки плагина: при её отключении, удалении или недействительном токене каждый запрос отклоняется ещё до чтения делегирования
  • выданные права: модель записи должна по-прежнему входить в разрешённый набор, а нужная возможность — оставаться выданной.

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

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

Окно терминала
curl -X POST "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001/transitions/published" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sched:plg_0000000000000000000000000000d1ee:sch_0009:r1" \
--data '{"kind":"publish","expected_current_version_id":"cdocsversion00000000000001","expected_workflow_revision":0,"delegation_id":"pld_0000000000000000000000000000d1ee"}'
{
"outcome": {
"code": "requester_unauthorized",
"message": "The person who requested this no longer holds the authority it needs.",
"retry": false
}
}

Ничего не записано. Это окончательный отказ, повторять запрос нельзя. Плагин должен сообщить пользователю, что для выполнения задания нужен пользователь с соответствующими полномочиями.

Единственное намеренное исключение — повтор. Idempotency-Key, который уже был применён, возвращает первый результат, даже если запрашивающий с тех пор потерял свои полномочия, — работа уже выполнена, и повторный ответ на неё не является новой выдачей права.

Сайт берётся из токена и ни из чего, что отправляет вызывающий. В запросе плагина нет X-Site-Id, нет сайта в теле и нигде нет имени базы данных: любое из этих указаний отклоняется сразу, а не игнорируется, поэтому ошибка в плагине не может превратиться в чтение данных другого сайта.

Указание базы данных

Окно терминала
curl "https://cms.example/api/plugin/v1/context" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp" \
-H "X-D1-Binding: SOME_OTHER_TENANT"
{
"outcome": {
"code": "invalid_request",
"message": "A plugin request cannot name a database.",
"retry": false
}
}

Доступ к моделям — это пересечение возможности и сохранённого списка id моделей контента. Вне области действия ответ — content_not_found, тот же ответ, что и «такого контента нет», поэтому плагин не может выяснить структуру сайта перебором.

Id с другого сайта

Окно терминала
curl "https://cms.example/api/plugin/v1/content/cotherinstance0000000000001" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"outcome": {
"code": "content_not_found",
"message": "No such content in this installation’s scope.",
"retry": false
}
}

Ограничение выданных прав действует уже со следующего запроса:

После исключения модели из выданных прав

Окно терминала
curl "https://cms.example/api/plugin/v1/content/cdocsinstance00000000000001" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"outcome": {
"code": "content_not_found",
"message": "No such content in this installation’s scope.",
"retry": false
}
}

В выданных правах сохраняются id моделей. Поэтому переименование модели не меняет доступ, а после удаления модели соответствующая часть прав перестаёт действовать. Административный интерфейс показывает текущие slug рядом с ID.

Пользовательский cookie не является аутентификацией плагина. Маршруты плагина читают Authorization и ничего больше, поэтому украденную браузерную сессию нельзя использовать для обращения к API плагинов:

Cookie сессии на маршруте плагина

Окно терминала
curl "https://cms.example/api/plugin/v1/context" \
-b "__Secure-better-auth.session_token=$SESSION"
{
"outcome": {
"code": "plugin_unauthorized",
"message": "The installation token is missing, unknown, disabled or revoked.",
"retry": false
}
}

Обратное тоже верно: токен плагина, предъявленный административному маршруту, отклоняется как неаутентифицированный. Этим маршрутам нужны пользовательская сессия, заголовок сайта и site:plugins. У плагина нет ни одного из трёх.

Отключённая инсталляция, до какого-либо доступа к арендатору

Окно терминала
curl "https://cms.example/api/plugin/v1/context" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"outcome": {
"code": "plugin_unauthorized",
"message": "The installation token is missing, unknown, disabled or revoked.",
"retry": false
}
}

Для отсутствующего, неизвестного, отключённого и отозванного токена возвращается один ответ. По нему нельзя определить причину отказа.

Запросы ограничиваются на инсталляцию — 120 за 60-секундное окно — с помощью того же механизма ограничения частоты, что и в остальном продукте, и получают ответ в собственном конверте протокола с Retry-After. Это средства защиты от злоупотреблений. Ничто здесь не измеряется, не сообщается и не сохраняется как телеметрия.

Превышение лимита

Окно терминала
curl "https://cms.example/api/plugin/v1/context" \
-H "Authorization: Bearer dwp_ExampleTokenNotRealExampleTokenNotRealExamp"
{
"outcome": {
"code": "rate_limited",
"message": "Too many requests for this installation.",
"retry": true
}
}

Соблюдайте Retry-After. Если задержка превышает заранее установленный вами предел, завершайте задание отказом вместо дальнейшего ожидания.

Ядро вызывает плагин для четырёх вещей: получения манифеста, передачи активации или ротации, вызова объявленной операции, политики или источника медиа и скачивания файла из источника медиа, который выбрал человек. Все вызовы ограничены и закреплены.

  • Только HTTPS. Учётные данные в URL и фрагменты отклоняются.
  • Адреса назначения оцениваются по имени хоста в URL:
    • Литерал IPv4 отклоняется, если он приватный, loopback, link-local, общий (CGNAT), multicast, зарезервированный, для бенчмарков или из одного из диапазонов для документации (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24). Публичный литерал IPv4 принимается.
    • Десятичные и шестнадцатеричные записи, такие как https://2130706433/ или https://0x7f000001/, сначала нормализуются парсером URL в форму с точками, а затем оцениваются так же.
    • Каждый литерал IPv6 отклоняется.
    • Имя без точки отклоняется, как и localhost, localdomain, local, internal и home.arpa, а также любое имя, оканчивающееся на одно из них (printer.local, db.internal).
  • Редиректы отклоняются, а не выполняются. 3xx — это remote_redirect_refused.
  • Тело ответа ограничено 32 КБ, а вызов — 5 секундами. Вызов операции или политики ограничен 64 КБ и 10 секундами на запрос, обмен и ответ вместе.
  • Каждый URL — манифеста, базовый, активации, управления, endpoints операций и политик — должен иметь один origin, поэтому ни один вызов нельзя направить туда, где проверенный манифест ничего не называл.
  • Вызов не отправляет ни контента, ни учётных данных: только id инсталляции, id вызова, одноразовый код и его срок. Плагин обменивает код своим токеном. Ядро принимает ответ только от этой инсталляции, только после обмена и только пока установка плагина активна, а её права по-прежнему разрешают операцию или политику.
  • Обмен операции также возвращает delegation_id. Ядро записывает его вместе с вызовом, привязанным к вызывающему, сайту и инсталляции, а для операции над записью — к модели и записи. Делегирование операции сайта не привязано к записи и охватывает разрешённые модели, которые может читать пользователь, как запуск manage; операциям сайта и так нужен site:plugins. Это никогда не код, и его нельзя обменять на /launch/exchange. Каждое использование проверяется так же, как делегирование запуска.
  • Удалённый ответ разбирается как недоверенный ввод по схеме манифеста. Неизвестный ключ — это отказ, а не то, что можно игнорировать.
  • Ответ источника медиа принимается, только если каждый thumbnail_url и download_url находится на origin манифеста, а delivery_url — на одном из объявленных источником delivery_origins. Ядро выполняет GET выбранного download_url без учётных данных, без cookie и без перенаправлений, в пределах 20 секунд и лимита медиафайла, а затем проверяет байты, как любую загрузку. Окно выбора загружает превью из плагина с referrerpolicy="no-referrer", поэтому ни адрес админки, ни какие-либо учётные данные Dee Wan не доходят до плагина. Копия — собственный файл сайта; ничего публичного не загружается из плагина. Единственное исключение — файл, добавленный в режиме reference: ядро никогда не запрашивает его delivery_url, но опубликованные страницы, копии сайта и генерируемые сайты загружают его с объявленного origin доставки, поэтому этот origin входит в то, с чем связываются браузеры посетителей сайта.
  • Workers не могут разрешить DNS до fetch, поэтому проверяется то, что называет сам URL. Публичное имя, которое разрешается в приватный адрес, находится вне того, что видит проверка.
  • Локальный HTTP возможен только когда ENVIRONMENT=development и точный origin указан в PLUGIN_DEV_ORIGINS. Никаких шаблонов, никаких сокращений для приватных диапазонов, никакого запасного варианта для production.

Оператор может перечислить URL каталогов в PLUGIN_CATALOGUE_URLS через запятую. Их читает блок «Каталог плагинов» в разделе Настройки → Плагины. По умолчанию не задан ни один, встроенного реестра у Dee Wan нет.

  • URL каталога, каждая страница next и каждый URL в записи проходят те же правила, что и URL манифеста. Редиректы отклоняются. next должен оставаться на origin первой страницы.
  • Не больше 4 каталогов, по 5 страниц, 128 КиБ на страницу, 100 записей на страницу и 200 на каталог. Каталог, нарушивший предел, исключается целиком, и причина показывается.
  • Запись — это id, name и manifest_url, плюс необязательные summary, categories, homepage и icon. Неизвестные ключи отклоняются. Неверная запись пропускается, а в сообщении указывается её позиция.
  • Две записи с одним id: побеждает первая — по порядку настройки, затем страниц, затем записей.
  • Иконка показывается только как изображение, без referrer.
  • Запись ничего не разрешает. Проверить отправляет URL манифеста из записи в обычную проверку, а установка снова загружает и проверяет манифест. Формат страницы — в Протоколе v1, в разделе GET /api/plugins/catalogue.
  • Сбой каталога не затрагивает установленные плагины и установку по URL манифеста.

Плагин — это действующее лицо, а не пользователь. Никакой поддельный пользователь не создаётся, и никакой id инсталляции не записывается во внешний ключ пользователя.

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

Административные действия — установка, изменение прав, принятие манифеста, ротация, отключение, включение и удаление — записываются в административный журнал аудита от имени пользователя, выполнившего действие. Если аудит недоступен, операция отклоняется с 503 audit_unavailable, а данные не изменяются.

Запись, которую плагин делает для человека, — сохранение полей, загрузка медиа, предложение — выполняется от имени этого человека: версия или строка медиа принадлежит ему, а журнал аудита администрирования записывает его как действующее лицо с id инсталляции и id плагина в изменении (plugin.content.save, plugin.media.upload, plugin.proposal.create). Без записи в аудите операция отклоняется (temporary_failure) и данные не изменяются. Предложение также хранит имя плагина и email человека, которые показывает редактор записи. Рабочие черновики — собственная несохранённая работа человека: это ни версии, ни аудит, и плагин читает только черновики того человека, чьё делегирование он называет.

Выполнение операции или проверка политики пишется в аудит как plugin.operation.run или plugin.policy.check: действующим лицом указан пользователь, запустивший операцию или проверку, а в изменении — инсталляция, объявленный id, классифицированный исход и хеши входа и ответа. Сами значения и ответ там не хранятся. Выбор администратором поведения при отсутствии ответа политики записывается в аудит как plugin.policy.mode.

Политика видит факты одной записи — модель, запись, язык, ключи изменённых полей и их значения — и ничего больше: ни учётных данных, ни другого контента. Она отвечает allow, warning или refuse. Политика save или restore может вместо этого ответить transform: заменяющие значения, ссылки, медиа или SEO-переопределения для той же записи и того же языка, о которых её спросили. Ядро проверяет преобразованный кандидат так же, как собственный ввод человека, и сохраняет его в составе записи этого человека. Ответ политики не может затронуть другую запись или язык, перевести запись на другой этап процесса или заменить собственные проверки Dee Wan, которые идут первыми. Запись выполняется, только пока показанная политике версия актуальна; изменение в промежутке — обычный конфликт.

Если плагин полностью скомпрометирован, злоумышленник получает один токен установки плагина для одного сайта. Ему доступны только выданные администратором возможности и модели. Каждая операция записи требует delegation_id пользователя, который по-прежнему имеет право на неё, и должна укладываться в область делегирования: одна группа переводов для действия над контентом; для manage — выданные модели, которые пользователь всё ещё может читать. В этих пределах он может:

  • с content:read: читать сводки, а с актуальным делегированием — сохранённые значения и версии в пределах доступных человеку моделей, полей, языков и владения
  • с workflow:publish, workflow:unpublish или workflow:locale: публиковать или снимать с публикации контент, закрепляя точную версию и указывая текущую ревизию workflow
  • с content:write: сохранять значения, связи и медиа существующих записей; с content:create: создавать записи; с content:delete: перемещать записи в Корзину с обычными проверками жизненного цикла
  • с draft:write: читать, писать и отбрасывать собственные рабочие черновики делегировавшего человека
  • с media:write: загружать файлы; с media:read: читать существующие файлы и метаданные; с media:update: менять метаданные в пределах текущих прав человека
  • с content:propose: создавать предложения, которые проверяет человек, и читать свои предложения
  • через выданную политику save или restore: заменять значения, ссылки, медиа или SEO-переопределения в записи, о которой её спросили; через любую выданную политику: видеть факты этой записи и предупреждать или отклонять её
  • через выданную операцию или источник медиа: отвечать на запрос ядра, а ядро проверяет ответ
  • обращаться к маршруту обмена с кодами, которые у него уже есть.

Он не может:

  • получить доступ к другому сайту, другой модели или делегированиям другой инсталляции
  • использовать возможность, операцию или политику, на которые ему не выданы права
  • писать без делегирования или сверх того, что делегировавший человек может сделать сейчас
  • читать пользователей, роли, сессии, настройки, определения моделей или аудит либо весь список медиатеки
  • читать сохранённые значения и версии вне доступной делегировавшему человеку проекции
  • читать медиа или менять метаданные без возможности инсталляции и соответствующего права человека; защищённые файлы сохраняют проверки сайта и видимости
  • выполнять код в родительском документе администратора или получить доступ к базе данных, R2 либо учётным данным Cloudflare
  • опубликовать черновик новее закреплённого, пройти через изменённый workflow, переопределить зависимый контент или использовать какой-либо маршрут переопределения — в protocol v1 их нет
  • действовать от имени человека, потерявшего полномочия, или выдать себя за другого пользователя: delegation_id — ссылка на сохранённую запись, а не заявление о полномочиях
  • превратить повтор мутации во второй эффект или сохранить версию, загрузить медиа или создать предложение без записи в аудите. Рабочие черновики в аудит не пишутся (см. Аудит).

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

Если утёк код запуска — через referrer, лог, общий URL, — он одноразовый, ему не более 60 секунд, он хранится хешированным и бесполезен без токена инсталляции, необходимого для его обмена.

Агент может создать ссылку запуска. Учётные данные управления или подключённое приложение с plugins:use получают ту же ссылку через API управления или MCP — её создаёт тот же код с теми же проверками, от имени создателя учётных данных. Ответ — только ссылка и срок её действия, никогда не результат плагина. Последующее делегирование принадлежит создателю и ограничено моделями и областями доступа этих учётных данных. Оно включает только возможности установки плагина, разрешённые этими областями (content:read — для content:read и workflow:read; lifecycle:publish — для workflow:publish, workflow:unpublish и workflow:locale; content:write — для content:write, content:create и draft:write; content:trash — для content:delete; media:write — для media:write, media:read, media:update и media:source; content:propose). Команда вне этих ограничений отклоняется с capability_refused. Области доступа могут только сужать права.

Агент может запустить объявленную операцию. С plugins:use Management API и MCP также запускают операцию interactive, которую объявляет манифест, и возвращают её результат. Это единственный результат плагина, который получает агент. Ядро отправляет на ваш адрес POST с одноразовым кодом, никогда не с учётными данными управления и не со значениями вызывающего; вы обмениваете код на связанный ввод с токеном своей инсталляции. Перед каждым запуском и перед повтором результата по ключу ядро заново проверяет состояние плагина, выданные ему права и текущие права вызывающего пользователя. Аудит хранит дайджесты значений и результата, а не сами значения. Операция automation агенту никогда не предлагается.

Если скомпрометирован администратор, он может установить плагин и выдать ему то, что есть у него самого. Это реальный риск в этой архитектуре, и именно поэтому site:plugins — отдельное право (из ролей, с которыми начинает новый сайт, оно есть только у admin), никогда не подразумевается правом на публикацию, и поэтому каждый шаг установки проходит аудит, а права выдаются вручную только в пределах явного запроса манифеста.

Dee Wan не может отвечать за работу самого плагина: его доступность, точность часов, политику повторов, содержимое его базы данных и удаление ваших данных по запросу. Это должно быть описано в документации плагина. Администратору следует прочитать её, прежде чем предоставлять доступ. Пример — Плагин планирования.

Создание, удаление, чтение существующих медиа и изменение метаданных требуют отдельных одобренных возможностей. Прежние content:write и media:write не дают этих прав. Новые и изменённые связи, в том числе внутри компонентов, должны соответствовать и списку моделей установки, и списку моделей делегирования. Неизменённая непрозрачная ссылка может сохраняться при другом редактировании.


Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: e207e63239a627fbd2ae9c414ef7c6eb19fcfe5a (с локальными изменениями)
Дата сборки (UTC): 2026-10-21T09:28:24.214Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску