The examples assumed a repository checkout. A reader who installed from the skill registry has `plugin/scripts/ctxguard.py` at the archive root instead -- singular, and without the `plugins/ctxguard/` prefix, because the archive holds exactly one plugin. Following the examples verbatim gave them "No such file or directory" on the first command. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
365 lines
26 KiB
Markdown
365 lines
26 KiB
Markdown
# ctx-tools — как пользоваться
|
||
|
||
**Маркетплейс плагинов для Claude Code.** Сейчас в нём один плагин:
|
||
|
||
**`ctxguard`** — санитизация контекста: вырезает креденшлы, подменяет имена компаний,
|
||
людей и PII на стабильные обратимые алиасы до того, как они попадут в модель.
|
||
Принуждение живёт в хуках, поэтому агент не может это выключить.
|
||
|
||
Этот файл — про то, как этим пользоваться. Раздача плагина коллегам и процесс
|
||
релизов — в [README.en.md](README.en.md).
|
||
|
||
---
|
||
|
||
## Установка
|
||
|
||
### Вариант 1. Через маркетплейс (рекомендуется)
|
||
|
||
```
|
||
/plugin marketplace add /path/to/skills
|
||
/plugin install ctxguard@ctx-tools
|
||
```
|
||
|
||
Вместо локального пути можно указать `<owner>/<repo>` на GitHub, git-URL или любой
|
||
путь в файловой системе. Ничего не тянется из сети во время работы, зависимостей кроме
|
||
`python3` нет.
|
||
|
||
Для всей команды — объявить на уровне **проекта** и закоммитить результат, чтобы
|
||
клонирования репозитория было достаточно:
|
||
|
||
```
|
||
claude plugin marketplace add <owner>/<repo> --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 <path> 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/<bucket>/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`.
|