# ctx-tools — как пользоваться **Маркетплейс плагинов для Claude Code.** Сейчас в нём один плагин: **`ctxguard`** — санитизация контекста: вырезает креденшлы, подменяет имена компаний, людей и PII на стабильные обратимые алиасы до того, как они попадут в модель. Принуждение живёт в хуках, поэтому агент не может это выключить. Этот файл — про то, как этим пользоваться. Раздача плагина коллегам и процесс релизов — в [README.en.md](README.en.md). --- ## Установка ### Вариант 1. Через маркетплейс (рекомендуется) ``` /plugin marketplace add /path/to/skills /plugin install ctxguard@ctx-tools ``` Вместо локального пути можно указать `/` на GitHub, git-URL или любой путь в файловой системе. Ничего не тянется из сети во время работы, зависимостей кроме `python3` нет. Для всей команды — объявить на уровне **проекта** и закоммитить результат, чтобы клонирования репозитория было достаточно: ``` claude plugin marketplace add / --scope project claude plugin install ctxguard@ctx-tools --scope project ``` ### Вариант 2. Копированием папки ``` cp -r plugins/ctxguard ~/.claude/skills/ctxguard ``` Загрузится в следующей сессии как `ctxguard@skills-dir`, **вместе с хуками**. Два нюанса: - **Имя должно быть свободно.** Установленный плагин с тем же именем побеждает, и просто *отключить* его недостаточно — имя остаётся занятым до `claude plugin uninstall`. - Копируйте каталог, а не симлинк на репозиторий, который потом переедет. Этот способ — для одной машины или одного коллеги. Маркетплейс — когда одну и ту же версию нужно раздать нескольким людям и обновление должно быть командой, а не перекопированием. --- ## ctxguard: повседневное использование Ниже `ctxguard` — сокращение для вызова скрипта. Сам путь зависит от того, как инструмент установлен, и это первое, обо что спотыкаются: | Откуда запускаете | Путь к скрипту | |---|---| | Клон этого репозитория | `python3 plugins/ctxguard/scripts/ctxguard.py` | | Распакованный ZIP из реестра скиллов | `python3 plugin/scripts/ctxguard.py` | | Внутри сессии Claude Code | `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/ctxguard.py"` | В ZIP-поставке каталог называется `plugin` в единственном числе и лежит в корне архива — там нет промежуточного `plugins/ctxguard/`, потому что архив содержит ровно один плагин. Удобно завести алиас под свой случай, например для распакованного архива: ``` alias ctxguard='python3 ~/.local/share/ctxguard/plugin/scripts/ctxguard.py' ``` Все примеры ниже записаны через это сокращение, подставьте свой путь. ### Что он делает - **Креденшлы удаляются безвозвратно** — на их месте остаётся маркер вида `SECRET:правило:хэш` в угловых скобках. Восстановить значение нельзя, и это осознанно: отредактированный приватный ключ бесполезен. - **Имена и PII превращаются в стабильные алиасы** (`CTXG_COMPANY_A`, `CTXG_PERSON_B`, `CTXG_EMAIL_7F3A`) — и переводятся обратно в реальные значения по пути на диск и в шелл. Поэтому агент продолжает делать реальную работу: код и команды, ссылающиеся на алиасы, работают. - **Принуждение живёт в хуках, а не в скилле.** Агент не может это выключить, а то, что он забыл правила, безвредно — решение принимает хук. Хуки: `SessionStart`, `UserPromptSubmit`, `PreToolUse` (перехват до запуска инструмента), `PostToolUse` (обратный перевод при записи), `SessionEnd`. ### Первый запуск в новом проекте ``` ctxguard init # создать состояние, показать что активно ctxguard scan . # что реально лежит в этом репозитории (только счётчики) ctxguard entity add "Acme Corp" --type company --hint "розничный клиент" ctxguard verify # 43 канареечных кейса через настоящие точки входа хуков ctxguard scan-transcript # что реально дошло до модели ``` **Словарь стартует пустым.** Детект креденшлов и PII работает сразу; имена компаний, людей и хостов защищены только после регистрации — поэтому `scan`, а затем `entity add` — первая работа в новом репозитории. Словарь привязан к **корню git-репозитория**, а не к текущему каталогу, так что сессия, начатая в `src/`, использует тот же словарь. ### Как выбрать `--match` — от этого зависит, скрывает ли алиас хоть что-то | Режим | Когда | Что ловит | |---|---|---| | `exact` (по умолчанию) | имя встречается только в прозе | целые слова, терпит гомоглифы (кириллическая `с` вместо латинской `c`) и разные разделители: `Acme Corp` / `acme-corp` / `AcmeCorp` | | `stem` | всё, что склоняется — в первую очередь русские фамилии | плюс до четырёх символов окончания на слово: `Петров / Петрова / Петровым`. Перебирает по замыслу: `Петров` поймает и `Петровский` | | `ident` | **любое имя, встречающееся в коде** | без границ слова, так что значение находится внутри идентификаторов: `ContosoClient`, `CONTOSO_API_KEY`, `contoso.rs`. С `exact` все три утекут в реальном написании, выглядя защищёнными. Нужно ≥4 символов | | `regex` | точная настройка | используется как есть, без именованных групп | Побеждает самый длинный шаблон, так что регистрировать и `Acme`, и `Acme Corporation` безопасно. Ещё два флага: - `--hint` вставляется в контекст модели **буквально**, поэтому описывайте сущность, не называя её («розничный клиент», а не «Globex — наш клиент»). Хинт, содержащий само значение, отвергается. - `--variant` (можно повторять) — дополнительные написания: транслитерации, аббревиатуры. Если у значения несколько слов, а режим `ident`, — зарегистрируйте отдельно и отличительное слово: код обычно пишет `GlobexClient` для `Globex Retail`. ### Слэш-команды в сессии | Команда | Зачем | |---|---| | `/ctx-entity <значение> [тип]` | Защитить название компании, человека, хост или кодовое имя за стабильным алиасом | | `/ctx-sanitize` | Превратить текст (тикет, лог, письмо) в версию с алиасами, которую можно куда-то вставить | | `/ctx-audit` | Показать, что ctxguard блокировал и подменял | | `/ctx-verify` | Доказать, что защита действительно блокирует заявленное, и измерить реальную утечку | Промпт нельзя перезаписать хуком — только заблокировать. Поэтому если ваш промпт отклонён, штатный путь — `/ctx-sanitize` на текст, ручная подстановка алиасов или регистрация значения через `/ctx-entity`. ### CLI | Команда | Что делает | |---|---| | `ctxguard init [--force]` | создать каталог состояния и политику по умолчанию | | `ctxguard status` | режим, префикс алиасов, число сущностей, правил, записей аудита | | `ctxguard mode enforce\|observe` | `observe` только логирует находки и ничего не блокирует — средство калибровки, не режим эксплуатации | | `ctxguard entity add <значение> --type <тип> [--match …] [--hint …] [--variant …]` | защитить значение. Типы: `company`, `person`, `host`, `project`, `email`, `phone`, `custom` | | `ctxguard entity list` | алиасы, типы, режимы совпадения, хинты — **значения не печатаются никогда** | | `ctxguard entity remove <алиас>` | снять защиту | | `ctxguard entity export --out <файл>` | выгрузить словарь для коллеги (откажется писать внутрь git-репозитория) | | `ctxguard entity import <файл>` | влить словарь, выгруженный на другой машине | | `ctxguard sanitize < <файл>` | stdin → текст с алиасами на stdout, сводка по правилам на stderr | | `ctxguard scan [путь…]` | какие файлы содержат чувствительные значения (только счётчики и id правил) | | `ctxguard scan-transcript [--limit N] [--verbose] [--all-projects]` | что реально дошло до модели | | `ctxguard audit -n 40` | последние решения: id правил, имена инструментов, алиасы — без плейнтекста | | `ctxguard verify [--adversarial]` | канареечный корпус через настоящие точки входа хуков | | `ctxguard run -- <команда>` | выполнить команду с фильтрацией вывода | Ни одна подкоманда не печатает реальное значение сущности или секрета — именно это делает сам инструмент безопасным для запуска изнутри сессии агента. ### Через Makefile ``` make test # юнит-тесты движка make verify # канареечный корпус через настоящие точки входа хуков make leaks # поиск в транскриптах значений, которые реально дошли до модели make scan # чувствительные значения в этом репозитории (только счётчики) make status # что у ctxguard активно make lint # байт-компиляция всех скриптов + валидация JSON make all # lint + test + verify ``` --- ## Что видит агент — и что делать, когда вызов отклонён Пять правил, которые агент получает в контекст: 1. `CTXG_*` — алиасы реальных значений, использовать **буквально**. 2. Никогда не восстанавливать реальное значение и не просить пользователя его вставить. 3. Маркер секрета необратим; записывать его в файл нельзя — это затрёт настоящий креденшл заглушкой, и хук такую запись отклонит. Нужно целиться в более узкий участок через Edit. 4. Некоторые пути ведут в санитизированный кэш («twin»). Читать и править как обычно — изменение прописывается в настоящий файл за вас. Незнакомый путь — это ожидаемо и само по себе сигнал, что в файле что-то было. 5. Отказ — это информация, а не препятствие. В причине названо правило и штатный путь; попытки обойти сами попадают в аудит. | Отказ | Штатный путь | |---|---| | Чтение `.env`, `*.pem`, `~/.ssh/…` | Читать `.env.example` или задокументированную схему. Проверить, что переменная задана, не печатая её: `test -n "$VAR" && echo set` | | Команды, чья задача — напечатать креденшл (дамп окружения, выдача токена, чтение секрета кластера) | Проверять наличие, не печатая значение | | `Grep` с `output_mode: content` | `Bash` + `grep` (этот путь фильтруется), либо `Grep` с `files_with_matches`, а затем `Read` | | Поиск реального защищённого значения | Искать по алиасу — в санитизированных копиях лежат алиасы, а не реальные значения | | Креденшл в командной строке | Передать через переменную окружения или файл, который команда читает сама | | `cd` / `export` / `source` с алиасом | Они выполняются в постоянном шелле и не могут быть обёрнуты фильтром; перестроить команду или выполнить шаг вручную | | Аргумент MCP-инструмента с защищённым значением | Использовать алиас; если инструменту нужно реальное значение — этот шаг выполняет пользователь | Отдельно про изменение файлов: если правка не появилась в настоящем файле — write-through упал и сообщил об этом (`PostToolUse` выходит с кодом 2 и печатает `write-through to FAILED`). Проверьте права на целевой файл и `ctxguard audit -n 20`. Подготовленное содержимое цело в twin-копии. --- ## Проверка, что защита работает Два разных вопроса — две разные команды, и путать их нельзя: ``` ctxguard verify # работает ли механика? -> измеряет замысел ctxguard scan-transcript # утекло ли что-то реально? -> измеряет результат ``` Доказательство — только второе: оно ищет реальные значения из словаря в транскриптах, то есть буквально в том, что было отправлено модели. Ненулевой результат означает, что данные дошли до модели несмотря на хуки — тогда сначала смотрите файлы инструкций (`CLAUDE.md` / `AGENTS.md` грузит харнесс, их не видит ни один хук), затем `ctxguard audit`. По умолчанию сканируются транскрипты **только этого проекта**: сессии других проектов оцениваются против словаря, который им не принадлежит. Расширить — `--all-projects`. Часть находок может быть помечена как **структурные**: значение входит в собственный путь проекта, поэтому появляется в каждом абсолютном пути и в метаданных транскрипта. Никаким хуком это не лечится — только переименованием каталога. --- ## Передача словаря другой машине или коллеге Плагин путешествует через маркетплейс. Словарь — нет, и не должен. `~/.claude/ctx-guard/` живёт на машине и намеренно вне любого репозитория. Передавать его нужно явно, потому что **алиасы невозможно воспроизвести**: - они выдаются в порядке регистрации, так что добавивший две компании в другом порядке получит перевёрнутое соответствие — два человека будут понимать под `CTXG_COMPANY_A` *разные компании*; - соль генерируется на хранилище, поэтому маркеры секретов и PII-алиасы тоже не совпадут, и находки нельзя сопоставить между людьми. На это есть тест, так что рекомендация не может тихо устареть. ``` ctxguard entity export --out ~/team-dict.json # откажется писать внутрь git-репозитория # передавать по каналу, по которому вы отправили бы сами реальные значения — файл содержит их ctxguard entity import team-dict.json # запускать из каталога проекта # удалить файл выгрузки сразу после импорта ``` После импорта обе машины дают побайтово одинаковый вывод — включая маркеры и PII-алиасы. Если в проекте уже есть словарь с другой солью, импорт откажется: принять чужую соль (`--adopt-salt`) значит поменять все уже используемые PII-алиасы и маркеры секретов. --- ## Чего ctxguard не покрывает - `CLAUDE.md` и `AGENTS.md` грузит сам харнесс — **ни один хук их не видит**. Санитизировать вручную; `SessionStart` предупредит, если в них найдены реальные имена. - Структурные утечки: имя, входящее в путь проекта, защитить нельзя. - Пока сущности не зарегистрированы или не импортированы, живы только детект креденшлов и PII — имена компаний, людей и хостов не защищены. Подробнее — `plugins/ctxguard/skills/context-sanitization/references/threat-model.md`. Прочитайте его прежде, чем обещать кому-либо, что данные в безопасности. Необязательно, но полезно — чтобы ожидание пережило отключение плагина, добавьте в свой `CLAUDE.md`: ```markdown Чувствительные данные в этом проекте заменены алиасами (`CTXG_*`), это обеспечивают хуки ctxguard. Используй алиасы буквально, никогда не восстанавливай реальные значения, а причину отказа читай вместо того, чтобы его обходить. См. скилл context-sanitization. ``` --- ## Настройка и отладка Политика: `~/.claude/ctx-guard/policy.json`. Отсутствует или битая — применяются строгие значения по умолчанию, потому что умолчания и есть безопасный откат. Хранится как diff от умолчаний, чтобы исправления правил доходили до установленных копий. Ключевые поля: `mode` (`enforce` / `observe`), `fail_closed` (ошибка санитайзера отклоняет вызов инструмента), `alias_prefix`, `entropy_threshold`, `max_scan_bytes`, `bash_wrap` (выключение убирает фильтрацию вывода шелла целиком — не отключайте ради скорости), `deny_read_paths`, `deny_command_patterns`, `allowlist`, `disabled_secret_rules`. Полный список — `references/policy.md`. **Ложные срабатывания** — в порядке предпочтения: точное значение в `allowlist` → конкретный маркер в `allowlist_substrings` → сужение правила в `detect.py` с тестом → id правила в `disabled_secret_rules`. Сначала посмотрите `ctxguard audit`: правило, которое постоянно срабатывает на безобидном, — это причина, по которой защиту в итоге выключают. **Стоимость:** порядка 134 мс на решение по Bash и 155 мс на Read, который должен просканировать файл и материализовать twin. На задачу из 50 вызовов инструментов — примерно 6–7 секунд. Twin-копии кэшируются и инвалидируются по mtime+size, так что повторные чтения дешёвые. **Правка плагина не подействовала.** Хуки исполняют **установленную копию**, а не рабочее дерево, и `plugin.json` сравнивается по версии, а не по коммиту — релиз без бампа версии не доедет до установленной копии молча. После каждого изменения: ``` claude plugin marketplace update ctx-tools claude plugin update ctxguard@ctx-tools # нужен рестарт сессии ``` Убедиться: `diff -rq ~/.claude/plugins/cache/ctx-tools/ctxguard/<версия>/scripts plugins/ctxguard/scripts`. **Выключить.** `/plugin uninstall ctxguard@ctx-tools`. `mode observe` оставляет детекторы и аудит, но ничего не блокирует — это калибровка, а не способ работать защищённо. Состояние в `~/.claude/ctx-guard/` в обоих случаях остаётся; удалите этот каталог, чтобы убрать и словарь. --- ## Структура репозитория ``` .claude-plugin/marketplace.json манифест маркетплейса plugins/ctxguard/ санитизация контекста: скилл + хуки + CLI docs/specs/ проектный документ (исторический) Makefile единственный вход в проверки ``` ### Куда положить новый скилл | Скиллу нужны… | Куда | |---|---| | хуки, слэш-команды, MCP-серверы, скрипты | отдельный плагин в `plugins/<имя>/` + запись в `marketplace.json` | | ничего, кроме инструкций | плагин-«ведро»: `plugins//skills/<имя>/SKILL.md` | Хуки не могут ехать внутри голого скилла — именно поэтому репозиторий сделан маркетплейсом, а не плоским каталогом `SKILL.md`. Скиллы «только инструкции» стоит собирать в один плагин-ведро: одна установка покрывает все, тогда как плагины, ставящие хуки, добавляются осознанно и по одному. Такого ведра здесь пока нет — создайте его вместе с первым таким скиллом, а не заранее. Конвенции, вслед за экосистемой установленных плагинов: - frontmatter в `SKILL.md` — `name` + `description`, третье лицо, много триггеров - детали уходят в `references/*.md`, не в `SKILL.md` - скрипты — Python 3 **только stdlib**, вызов через `python3 "${CLAUDE_PLUGIN_ROOT}/..."` Начать чтение стоит с `plugins/ctxguard/skills/context-sanitization/SKILL.md`.