Блог
Руководства

Пользовательские skill'ы: шаблоны, которые остаются безопасными

Как skill компилируется в отмеченную запись контекста: whitelist, один проход, три уровня scope, DLP до компиляции и freemium max_skills (3 или без лимита).

Simmetric Team8 мин чтения12/10/2026
Пользовательские skill'ы: шаблоны, которые остаются безопасными

Инструкции, которые вызывают, а не код, которому доверяют

Пользовательский skill - переиспользуемый шаблон prompt'а: написан однажды, вызывается из чата как slash-команд вновь и вновь. Проектная ставка в формуле "шаблон prompt'а": skill не содержит HTTP-вызовов, файлового ввода-вывода, условной логики, песочницы - бежать некуда, поэтому функция air-gap-safe по самому построению. Что skill содержит - фиксированный管线 компиляции с настоящими границами: параметры, whitelist-ованные по схеме, один проход подстановки, маскировка до компиляции и инъекция, приходящая как недоверенные данные.

Эта статья проходят путь написания от начала до конца: как пишется и компилируется шаблон, грамматика slash-команд, три уровня scope, граница привилегий, держащая компилированный вывод честным, и лицензионная форма (max_skills - 3 в Community, без лимита в Enterprise).

Шаблон и его правила

Шаблон - простая строка с placeholder'ами {{param}}:

text
Translate to {{targetLang}}: {{input}}

Конвейер компиляции навязывает четыре правила (прибитые к схеме):

  • Whitelist - подставляются только ключи, объявленные в inputSchema.properties skill'а (плюс его дефолтные параметры). Неизвестный placeholder остаётся литеральным в выводе - никогда не решается из окружения, системного контекста, чего-либо ещё.
  • Один проход - выполняется ровно один проход замены. Подставленное значение, содержащее внутри себя формы маркеров {...}, никогда не разворачивается снова; инъекция через шаблон умирает здесь на уровне структуры.
  • Согласованность - каждый placeholder шаблона обязан иметь свою запись в input-схеме; create/patch без неё = именованный 400.
  • Отвержение маркеров - статический текст шаблона (без placeholder'ов) не должен содержать формы tool-call-маркеров межпровайдерного класса; они отвергаются при create/patch. Значения параметров несут свою собственную защиту ниже.

Грамматика slash-команд

Символ / открывает палитру skill'ов (автодополнение); выбранная строка вставляет свой slug без отправки. На отправке клиент разбирает аргументы и шлёт вызов вместе с набранным сообщением:

  • Позиционно: /translate Ciao mondo - токены сплавляются в первое обязательное поле.
  • Парами: /translate input=Ciao - именованные заполнения; отрезки в кавычках (input="Ciao mondo") несут пробелы.
  • Смешанно и по умолчанию: input="Ciao" targetLang=Spanish, или обязательный ключ, принимающий свой объявленный дефолт.

Два поведения делают грамматику заслуживающей доверия, а не хитрой:

  • Правило «никогда не ошибка» - нераспознанное /слово, имя builtin или выключенная строка проскальзывает в обычное сообщение: без вызова, без ошибки, без ничего спрятанного.
  • Сервер перепроверяет всё - неизвестные ключи игнорируются; slug заново решается на сервере с тем же фильтром scope при каждом вызове (караул IDOR).

Список зарезервированных slug'ов (фиксированные команды чата + имена builtin skill'ов) делает затенение невозможным: пользовательский skill никогда не может взять зарезервированное имя.

Путь запроса: решить, замаскировать, скомпилировать, обернуть

text
"/slug params" → POST chat stream { message, skillCall }
→ resolveInvocableSkill (на сервере, с фильтром scope)
→ DLP-маска ДО компиляции (значения сканируются, когда DLP активен)
→ compileTemplate (whitelist-замена, один проход) + спотлайт-обёртка
→ ОДНА отмеченная запись контекста уровня пользователя в оркестраторе

Порядок с DLP - несущая деталь: параметры маскируются до компиляции. При обратном порядке компиляции текст, вписанный в шаблон, мог бы спрятать параметр от сканера; при этом порядке компилированный prompt содержит только замаскированные значения. (Сама DLP-труба - нумерованные маски, зашифрованные оригиналы, admin-размаскирование - своя отдельная история.)

Граница привилегий

Компилированный вывод шаблона - данные, предоставленные пользователем, а не инструкции. Он входит в разговор одной отмеченной записью контекста - с меткой, со счётом бюджета, как всякий tool result, и прибит привилегийным тестом: пользовательский skill может добавить контекст, но никогда не может добавить tools, разрешения, записи реестра. Транскрипт хранит набранное сообщение; в контекст skill'а входит только компилированный prompt.

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

Scope: три уровня, решаемые на сервере

  • Личный - решает его только автор (любой пользователь с правом создания).
  • Workspace - всякий, кто болтает в этом workspace (создаётся editor+; viewer получает явный 403). Сохранённая строка несёт null в user id - строка со штампом автора протекла бы сквозь workspace'ы, и схема закрывает этот класс.
  • Глобальный - все в организации (создаётся только админами).

Заявленный клиентом scope никогда не доверяется: сервер решает уровень по сохранённой форме и фильтрует решение на каждый чат. Правки и удаления действуют со следующего чата - без hooks, без рестартов. Удаление - мягкий надгробный камень, и slug освобождается сразу: имя удалённого skill'а пересоздаваемо на месте.

Freemium-факт: max_skills

Лицензионный лимит зеркалит прочие числовые заслоны платформы: 3 пользовательских skill'а в Community, без лимита в Enterprise - строка census'а F-SKILL-1, стоящая на лицензионных константах:

  • Действует только на CREATE - заградить вызовы значило бы рвать чат посреди по лицензионной причине - отвергнутый проект; счёт живёт на провизионировании.
  • Счёт - по организации, по пользовательским строкам - builtin и marketplace-строки никогда не считаются.
  • Сверх лимита стандартная paywall-форма называет функцию и ступень - и выход self-service: каждый пользователь может удалить свои skill'ы, освободив квоту, без вмешательства админа.

Последнее свойство - freemium-договор, сделанный честно: лимит виден в интерфейсе управления, 402 объясняет себя, и выход не стоит тикета поддержки.

Читать дальше

Путеводитель несёт полную операторскую справку - endpoint'ы, диалог формы, семантику test-preview - на /ru/docs/custom-skills:

  • Custom skills - путеводитель
  • Факты этого поста: строки FACT-CENSUS.md F-SKILL-1 (пользовательские skill'ы + freemium max_skills) и F-DLP-1 (слой маскировки, по которому идёт этот путь)

Provenance: Simmetric Chat docs/CUSTOM_SKILLS.md (v0.28.1, Phase 190 SKIL-01..05), packages/shared/src/schemas/skill.schema.ts, license.ts (max_skills) - на зафиксированном census'ом документальном состоянии (SHA aca5b8ff).

Похожие статьи