Skip to content

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_skillcopy_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.) До того как хоть один файл попадёт, каждый путь выполняет по порядку:

  1. Проверка ed25519-подписи издателя (supply_verify_install.check_skill_signature): block — отказ, warn — продолжить (adoption-путь для неподписанных репо / без пиннинга ключа), ok — сверка с ключом, закреплённым за репо. OMS / Sigstore рассмотрены и отклонены владельцем — остаёмся на своих ed25519-квитанциях и закреплённых ключах.
  2. Guard обхода пути (_validate_path_inside): источник скилла обязан резолвиться внутри директории репо.
  3. Guard контрабанды симлинков (copy_skill, copytree(..., symlinks=False)): враждебное репо не может протащить абсолютный путь (~/.aws/credentials, /etc/shadow) в дерево скиллов через симлинк.
  4. Контент-скан невидимого 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).