Threat-model цепочки поставок магазина скиллов
Задача
l26-skill-supply-chain-threat. Английское зеркало:skill-supply-chain-threat-model.md. Спутник кsecurity.md.
Зачем это
Главный вектор атак 2026 — не MCP-сервер, а markdown-скилл. Полезная нагрузка скилла — это проза, которую агент читает буквально, поэтому сигнатурное сканирование байтов доказывает, кто опубликовал, но ничего не говорит о скрытом умысле в тексте. Публичные данные:
- Snyk ToxicSkills (2026-02-05): из 3 984 скиллов 36.8 % с проблемами, 13.4 % критичных; 76 подтверждённых вредоносов (8 живых на публикации); 91 % вредоносных использовали prompt injection; 10.9 % содержали захардкоженные секреты.
- ClawHavoc (февраль 2026): 341 вредоносный скилл из 2 857, инфостилер AMOS; реестр вычистил 2 419.
- Unit 42 (июнь 2026): обход сканера раздуванием README до 22 МБ; подмена рекомендаций агента в рантайме.
- Orca (май 2026): сканирование только при создании, поэтому подмена артефакта после проверки работает.
Формулировка Unit 42 бьёт в корень: экосистеме не хватает изоляции между логикой скилла и полномочиями агента. Этот документ моделирует собственный магазин скиллов TAUSIK по этим примитивам и фиксирует по каждому вектору статус — митигировано, принято или отложено — с указанием кода, который это делает.
Что TAUSIK уже делает (граница доверия)
Есть два пути, которыми скилл попадает в активированное дерево .claude/skills/ — install (skill_manager.install_skill → copy_skill) и activate (service_skills.skill_activate), и оба гоняют одни и те же guard'ы. (Ревью s146 нашло, что контент-скан сначала добавили только в copy_skill, оставив activate путём с подписью, но БЕЗ контент-скана; теперь invisible-Unicode скан — общий guard skill_content_scan.assert_skill_tree_clean, который зовут оба — рецидив install/activate дрейфа, о котором предупреждает докстринг skill_tree_ignore.) До того как хоть один файл попадёт, каждый путь выполняет по порядку:
- Проверка ed25519-подписи издателя (
supply_verify_install.check_skill_signature):block— отказ,warn— продолжить (adoption-путь для неподписанных репо / без пиннинга ключа),ok— сверка с ключом, закреплённым за репо. OMS / Sigstore рассмотрены и отклонены владельцем — остаёмся на своих ed25519-квитанциях и закреплённых ключах. - Guard обхода пути (
_validate_path_inside): источник скилла обязан резолвиться внутри директории репо. - Guard контрабанды симлинков (
copy_skill,copytree(..., symlinks=False)): враждебное репо не может протащить абсолютный путь (~/.aws/credentials,/etc/shadow) в дерево скиллов через симлинк. - Контент-скан невидимого Unicode (
skill_content_scan.scan_skill_tree, добавлен этой задачей — см. ниже).
Два структурных факта вне пути установки важны не меньше:
- Проектный конфиг недоверенный.
.tausik/config.jsonедет с репо и лежит в проектном тире доверия (config_trust.py); он может только ужесточать enforcement, но не ослаблять. Вредоносное репо не отключит гейт и не понизит severity своим коммитным конфигом. - Хуки не едут с репо.
.claude/(включаяsettings.json, который подключает хуки) в gitignore и генерируется локально черезbootstrapиз вендоренного фреймворка. Свежесклонированный проект не несёт исполняемых хуков; пользователь обязан запустить bootstrap.
Таблица угроз
| # | Вектор (источник) | Применим к TAUSIK? | Статус | Мера / обоснование |
|---|---|---|---|---|
| 1 | Подмена после проверки (Orca) — пройти ревью, потом подменить артефакт | Частично | Митигировано на install / принято для уже установленных | Каждый install/update заново тянет артефакт и заново гоняет подпись + контент-скан, поэтому подменённый upstream ловится при следующей установке. Остаток: уже установленный скилл не перепроверяется до переустановки. Ре-скан при активации — дешёвое будущее укрепление (отложено, не сделано). |
| 2 | Тихая перезапись одноимённым | Да (механически) | Митигировано | copy_skill делает rmtree(dst) затем copytree, то есть перезаписывает — но только после прохождения подписи и контент-скана и только из явно добавленного, key-pinned источника. Перезапись под гейтом, не тихая. |
| 3 | Невидимые Unicode-инструкции (U+E0000 tag-block, zero-width, bidi) | Да — был открытый пробел | Митигировано (эта задача) | Новый детектор skill_content_scan блокирует установку, если SKILL.md или любой prose-файл прячет текст для агента (см. ниже). Подпись доказывает кто; это — что. |
| 4 | Раздувание против сканера (Unit 42, README до 22 МБ, чтобы сканер пропустил файл) | Маргинально | Принято (остаток: память) | У нашего контент-скана нет escape-hatch пропуска больших файлов — он читает файл целиком, поэтому обход Unit-42 не работает. Единственный остаток — стоимость памяти на чтение гигантского файла; пер-файловый size-cap — дешёвое продолжение (отложено). |
| 5 | Накрутка счётчика установок (раздуть популярность ради доверия) | Нет | Неприменимо | TAUSIK не показывает ранжирование по популярности / счётчику загрузок (проверено по отсутствию в skill_catalog/skill_repos). Скиллы принимаются явными skill repo add + skill install, а не через trending-список — накручивать нечего. |
| 6 | Хуки из repo-supplied конфига исполняются до согласия (CVE-2025-59536, CVSS 8.7) | В основном нет | Митигировано по дизайну | См. анализ ниже. |
Анализ CVE-2025-59536 (AC2)
CVE: хуки, определённые в repo-supplied .claude/settings.json, исполнялись до диалога согласия, поэтому простое открытие клонированного репо запускало код атакующего. Механизм хуков TAUSIK имеет ту же форму (settings.json → команды хуков), поэтому аналогия требует явного вердикта.
Вердикт: вектор не применим к клонированному проекту TAUSIK, по дизайну..claude/settings.json в gitignore и генерируется локально через bootstrap; он никогда не едет с репо. Открытие клона не запускает хуков — bootstrap это явный, инициируемый пользователем шаг, и он и есть та граница согласия, которой не хватало в CVE. Два остаточных пути названы и диспозиционированы, а не оставлены молча:
- Подделанный вендоренный фреймворк. Если проект вендорит модифицированный фреймворк (
.tausik-lib) и пользователь запускает bootstrap, генерируемые хуки берутся из этого кода. Это проблема целостности фреймворка, не магазина; ограничена тем, что фреймворк — pinned-зависимость под контролем пользователя, плюс bootstrap-drift гейт и подписанные квитанции верификации. Статус: принято (вне границы этого магазина). - Редирект через конфиг. Даже если репо привезёт крафтовый
.tausik/config.json, проектный тир недоверенный и только ужесточает — он не наведёт хук на код атакующего и не ослабит гейт. Статус: митигировано (config_trust.py).
Выбранная мера: задокументировать и держать инвариант — .claude/ в gitignore, хуки генерируются локально, проектный тир конфига недоверенный. Новый диалог согласия не нужен, потому что предусловие (repo-carried исполняемые хуки) не существует. Если будущее изменение когда-либо начнёт трекать .claude/settings.json, этот вердикт переворачивается и pre-trust consent-гейт становится обязательным.
Реализованная мера — детектор невидимого Unicode (AC4)
scripts/skill_content_scan.py детектит скрытые Unicode-инструкции в прозе скилла, и общий guard assert_skill_tree_clean — который зовут оба пути, copy_skill (install) и skill_activate, — отказывает скиллу, содержащему хоть один:
- U+E0000–U+E007F Unicode tag-block — главный вектор «невидимых инструкций» 2026.
- Zero-width форматирование (U+200B/C/D, U+2060 и U+FEFF, кроме ведущего BOM).
- Bidi overrides / isolates (U+202A–U+202E, U+2066–U+2069; Trojan Source, CVE-2021-42574).
- U+00AD soft hyphen.
Он сканирует каждый prose/config/script-файл, который поставляет скилл, — не только .md, потому что payload в references/notes.py или data/config.json доезжает до агента так же, как в SKILL.md (ревью s146, C2). Файлы декодятся с errors="replace", поэтому файл с намеренно-битым байтом всё равно сканируется, а не пропускается молча (fail-open закрыт в ревью s146, C3). Он дополняетbrain_scrubbing._ZERO_WIDTH_RE, который молча вырезает такие символы при матчинге brain-блоклиста: здесь мы детектим и блокируем, и дополнительно покрываем U+E0000 tag-block, который brain-регекс не знает. Покрыто tests/test_skill_content_scan.py и tests/test_skill_activate_supply_chain.py (отравленный скилл падает и на install, и на activate).