Files
skills/README.md
T
dev 278ecca018 Add ctxguard: hook-enforced context sanitization
Keeps credentials, company names, personal names and PII out of the model's
context. Enforcement lives in Claude Code hooks rather than in instructions to
the agent: a skill alone cannot protect anything, because by the time the agent
reads a rule the surrounding context has already been sent.

Credentials are removed irreversibly and marked. Entities from a user-supplied
dictionary become stable aliases, rewritten back to real values on their way to
disk and to the shell, so code and commands referring to them still work.

Published from a clean tree; development history is not included.
2026-09-16 11:33:09 +03:00

355 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```
Удобно завести алиас:
`alias ctxguard='python3 /path/to/skills/plugins/ctxguard/scripts/ctxguard.py'`.
Внутри сессии Claude Code тот же скрипт вызывается как
`python3 "${CLAUDE_PLUGIN_ROOT}/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`.