Skip to main content
Glama
kpshinnik

docs-masked

by kpshinnik
README.md
# docs-masked

Локальное обезличивание документов перед отправкой в языковую модель — и
обратная подстановка после ответа.

Документ никогда не покидает машину в исходном виде. Персональные данные
заменяются устойчивыми тегами (`#PERSON_1#`, `#PHONE_2#`, `#ADDRESS_1#`), в
модель уходит только текст с тегами, а полученный ответ восстанавливается
локально по сейфу соответствий.

```
документ ──▶ маска ──▶ контроль утечки ──▶ модель ──▶ обратная подстановка
           локально      локально          сеть           локально
```

Работает как скилл для Claude Code, как MCP-сервер для любого другого агента
и как обычная утилита командной строки.

## Как это работает

**1. Маска.** Документ разбирается на текстовые фрагменты — абзацы, ячейки,
узлы разметки. В каждом находятся персональные данные, каждое значение
получает устойчивый тег. Один и тот же человек получает один и тот же тег по
всему документу, включая падежные варианты и инициалы: «Иванов Иван Иванович»,
«Иванову» и «Иванов И.И.» — это один `#PERSON_1#`.

**2. Контроль утечки.** Замаскированный текст повторно прогоняется через все
детекторы плюс параноидальный проход: любой `@`, любая цепочка из семи и более
цифр, любой телефоноподобный набор. Если что-то осталось — отправка
**блокируется** исключением, а не предупреждением в логе.

**3. Отправка.** Наружу уходит только текст с тегами. Единственная точка
выхода в сеть — функция `llm.send()`, и она обязана вызвать проверку до
запроса. Каждая отправка пишется в журнал `~/.pii_shield/egress.jsonl`: время,
провайдер, модель, размер, sha256, статус проверки. Содержимое не пишется.

**4. Обратная подстановка.** Ответ модели проходит через сейф: теги заменяются
на оригиналы. Для ФИО подставляется восстановленный именительный падеж — если
в документе человек упомянут только как «Кузнецову Ивану Петровичу», в ответе
он станет «Кузнецов Иван Петрович».

## Установка

```bash
git clone https://github.com/kpshinnik/docs_masked.git ~/.docs_masked/src
cd ~/.docs_masked/src && ./install.sh
```

Скрипт поставит зависимости, положит скилл в `~/.claude/skills/docs-masked` и
напечатает готовый фрагмент конфигурации MCP. Подробности и варианты —
в [docs/INSTALL.md](docs/INSTALL.md).

## Подключение к агенту

| Способ | Кому | Как |
|---|---|---|
| **Скилл** | Claude Code, Claude.ai | `./install.sh` либо `/plugin marketplace add kpshinnik/docs_masked` |
| **MCP-сервер** | Cursor, Windsurf, Codex CLI, Continue, Zed, Cline, Claude Desktop | `python3 mcp_server.py` как stdio-сервер |
| **CLI и правило** | всё остальное | команды в терминале плюс `templates/AGENTS-rule.md` в свой проект |

Пошагово по каждому харнесу — [docs/HARNESSES.md](docs/HARNESSES.md).

MCP-сервер написан без зависимостей: нужен только `python3`. Он отдаёт шесть
инструментов — `mask_text`, `unmask_text`, `verify_text`, `scan_document`,
`mask_document`, `unmask_document`.

## Использование

```bash
docs-masked scan   договор.docx                    # что будет скрыто
docs-masked mask   договор.docx                    # маска + сейф
docs-masked report договор.docx --open             # посмотреть глазами
docs-masked ask    договор.docx -p "Найди риски по срокам"
docs-masked unmask договор.masked.docx --vault договор.docx.vault.json
```

### Команды

| Команда | Что делает |
|---|---|
| `scan FILE` | Показывает, что будет замаскировано. Файл не меняется, сеть не трогается. |
| `mask FILE` | Обезличенная копия в том же формате плюс файл сейфа. |
| `unmask FILE --vault V` | Возвращает оригиналы. |
| `verify FILE` | Проверяет, что персональных данных не осталось. |
| `ask FILE -p "..."` | Полный круг: маска → проверка → модель → восстановленный ответ. |
| `report FILE` | HTML-страница ревью: каждая замена в контексте, значения закрашены. |
| `selftest` | Самопроверка круговорота. |

Полный список флагов — [skills/docs-masked/references/cli.md](skills/docs-masked/references/cli.md).

## Что распознаётся

ФИО в любом падеже (русские, латиница, транслит), организации, адреса, почта,
телефоны, паспорт и код подразделения, СНИЛС, ИНН, ОГРН, КПП, БИК, расчётные
счета, банковские карты, IBAN, полисы ОМС, водительские удостоверения,
автомобильные номера, IP-адреса, `@никнеймы`, даты рождения и выдачи
документов, коды реквизитов (ОКТМО, ОКПО, КБК), плюс ваши собственные строки.

Идентификаторы проверяются по-настоящему: контрольная сумма СНИЛС, контрольные
разряды ИНН и ОГРН, алгоритм Луна для карт, mod-97 для IBAN. Полная таблица —
[references/coverage.md](skills/docs-masked/references/coverage.md).

## Форматы

| Формат | Чтение | Запись на место |
|---|---|---|
| `.txt` `.md` `.rst` `.log` `.tex` `.yaml` `.ini` | да | да |
| `.docx` | да | да, с сохранением форматирования |
| `.xlsx` `.xlsm` | да | да |
| `.csv` `.tsv` | да | да |
| `.json` | да | да |
| `.html` `.htm` | да | да |
| `.pdf` | да | по флагу `--pdf-redact`, с физическим вымарыванием |
| `.rtf` `.doc` `.odt` | да | нет (только macOS, через `textutil`) |

DOCX обходится по XML, а не через `document.paragraphs`: иначе теряются
абзацы внутри полей контента и надписей — на настоящем договоре из-за этого
пропадала целая колонка блока реквизитов. В таблицах заголовок колонки
используется как контекст: ячейка `500100732259` сама по себе неотличима от
случайного числа, а в колонке «ИНН» распознаётся уверенно.

## Python API

```python
from pii_shield import ask_document

res = ask_document("договор.docx", "Составь резюме и найди риски",
                   provider="anthropic")
print(res.answer)          # имена уже восстановлены
```

Ручной контроль каждого шага:

```python
from pii_shield import mask_text, assert_clean, unmask_text

r = mask_text(raw)                 # r.text — с тегами, r.vault — сейф
assert_clean(r.text)               # LeakGuardError, если что-то осталось
answer = call_model(r.text)        # наружу уходит только маска
final, unknown = unmask_text(answer, r.vault, mode="canonical")
```

Подробнее — [references/api.md](skills/docs-masked/references/api.md).

## Сейф соответствий

Сейф — единственное, что связывает теги с оригиналами. Без него обратная
подстановка невозможна.

- Пишется рядом с документом как `<файл>.vault.json`, права `0600`.
- Шифруется по флагу `--pass-env` (scrypt + Fernet).
- Хранит каноничную форму, все встреченные варианты и журнал вхождений в
  порядке документа — благодаря журналу точное восстановление возвращает
  исходную словоформу, а не каноничную.
- Внесён в `.gitignore`. Не коммитьте его.

## Точность и границы

Инструмент устроен так, чтобы **ошибаться в безопасную сторону**: лучше
замаскировать лишнее, чем пропустить. Что стоит знать:

- **Скан-PDF без текстового слоя** не обрабатывается — нужен OCR.
- **Однофамильцы без инициалов** получают отдельные теги, а не сливаются в
  одного человека.
- **Голое число без подсказок** может быть не распознано как идентификатор —
  но параноидальный проход всё равно не выпустит такой текст наружу.
- **Произвольные латинские имена** без славянских окончаний и без обращения
  (`Mr.`, `Dr.`) не распознаются: ловить любую пару заглавных слов дало бы
  больше вреда, чем пользы.

На критичном документе стоит один раз посмотреть `docs-masked report` глазами.

## Разработка

```bash
python3 -m pytest tests/ -q          # тесты
python3 -m pii_shield.cli selftest
python3 samples/make_samples.py      # пересоздать тестовые документы
```

Инварианты, которые нельзя ломать, перечислены в [AGENTS.md](AGENTS.md).
Всё в `samples/` синтетическое; каталог `examples/` зарезервирован под ваши
локальные документы и в репозиторий не попадает.

## Лицензия

MIT.

TDQS

A3.5/5.0

Scored across 6 tools

Disambiguation5/5

Each tool targets a distinct operation (mask vs unmask, text vs file) and the descriptions clearly differentiate text vs document variants. No two tools could be confused for the same purpose.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern using snake_case: mask_text, unmask_text, verify_text, scan_document, mask_document, unmask_document. The verbs are predictable and the nouns clearly indicate the resource type.

Tool Count5/5

With exactly 6 tools, the server is well-scoped for the domain of personal data masking/unmasking. Each tool serves a necessary role without redundancy, covering both text and document workflows in a focused manner.

Completeness5/5

The tool surface covers the full lifecycle: mask (text+document), unmask (text+document), verification, and preview. There are no obvious gaps—the server provides everything needed to protect and restore personal data in both free text and structured documents.

Maintenance

ActivityMaintained
ResponsivenessNo issues