pii-mask
Skill9 starsChanged 2 months agoMITView on GitHub
Замаскировать персональные данные в тексте перед облачной LLM и развернуть оригиналы в ответе - шлюз pii-mask. Брать для транскрипта, переписки, письма с ПД. Не нужен для обезличенного текста.
- Deletes or force-pushes
Files
Маскировка ПД перед облачной моделью
Шлюз заменяет персданные метками ({{PERSON_1}}) и формат-сохраняющими фейками (+7 000 ..., userN@example.com), а после ответа модели возвращает оригиналы. Соответствие "метка - оригинал" живет в приватном mapping-файле вне рабочей папки дочерней модели; сервис не хранит ничего.
Движок: pii-mask - локальный, офлайн, русскоцентричный (ПД-форматы РФ, NER и морфология русского). Не установлен - скилл неприменим, ставится оттуда.
Главное правило: не читай оригинал
Текущая облачная сессия не получает техническую изоляцию от этого скилла: запрет читать оригинал и mapping здесь остается правилом дисциплины. При передаче работы дочернему Codex техническую границу создает только отдельный запуск через описанный ниже Linux-маршрут. Оригинал и mapping не открывать: пути передавать локальному CLI, содержимое - нет.
Дальнейший порядок с дочерним Codex применяй только когда такой отдельный запуск уже выбран и разрешен пользователем; сам скилл не переключает текущую модель или провайдера.
- Файл прогоняется через CLI не глядя - родитель видит только путь.
- Дочерний Codex читает и обрабатывает только masked-файл в новой рабочей папке.
- Локальный CLI разворачивает masked-ответ вне этой папки.
Тот же запрет касается mapping-файла: в нем лежат оригиналы списком, это самый концентрированный источник ПД во всей схеме. Не открывать, не цитировать, не пересылать.
Порядок
Если исходник - PDF с таблицами
Как извлечен текст - решает больше, чем настройки шлюза. Такой PDF превращается в текст двумя способами, и они дают разный результат:
pdftotext -layout документ.pdf текст.txt # сохраняет колонки - и рвет сущности
pdftotext документ.pdf текст.txt # поток в порядке чтения - сущности целыеПостраничный режим сохраняет визуальную верстку, а вместе с ней разрывает то, что маскируется: ФИО разъезжается по колонкам, идентификатор рвется по дефису, ключевое слово реквизита остается на одной строке, а число уходит на другую. Замер на акте проверки (76 страниц): полных ФИО капсом видно 55 против 879, обрубков идентификаторов 978 против 11.
Победителя на все документы тут нет - прогоните оба режима и сравните. Поток отменяет физическую раскладку, и на документе, где раскладка и есть граница сущности (две колонки "имя - фамилия", метка реквизита слева и значение справа), он способен размотать колонки и развести то, что было парой. Критерий выбора - не читаемость текста, а целость сущностей: в потоке ФИО собрано целиком, метка стоит рядом со своим значением.
Штатная проверка после маскировки (ниже) этот промах не поймает: она считает остаточных кандидатов ПД, а разорванная сущность кандидатом уже не выглядит и дает честный ноль. Сравнивать надо два извлечения между собой, до маскировки.
pdftotext не делает OCR. У скана он вернет пустой текст, у смешанного документа - только страницы с текстовым слоем, молча. Прежде чем работать с результатом, сверьте объем: пусто или подозрительно мало - документ сканированный, и маскировать в нем нечего, потому что и текста нет.
Маскировка
Всегда задавай -o и --mapping явно; полный Linux-порядок с отдельными путями приведен ниже. При необходимости добавь --audit (второй проход локальной LLM), --types или --no-ner.
Проверить, что маскировать сейчас можно, - двумя командами, и обе нужны:
pii-mask where # откуда работает код: путь импортированного модуля
D=$(mktemp -d) # приватная папка: mapping пробы не должен лежать по угадываемому пути
echo "Иван Петров, +7 912 345-67-89" | pii-mask mask - --mapping "$D/probe.json"; RC=$?
rm -rf "$D"; [ $RC -eq 0 ] || echo "!!! проба маскирования упала (код $RC)"Первая показывает, какая копия исполняется (форма 4 в silent-failure.md): путь должен вести в установленный экземпляр; рабочее дерево репозитория или снесенный с диска каталог - поломка. Вторая - что маскирование работает на заведомо непустом входе: имя в выводе заменено меткой ({{PERSON_1}}), телефон - форматным фейком (+7 000 ...). Проба упала или имя с телефоном остались как были - маскировать нельзя, текст модели не отдается.
Живость сервиса проверкой не считается. systemctl ... is-active, открытый порт и /health/live с ответом ok молчат одинаково при исправном и мертвом маскировщике - прецедент 28.09.2026: HTTP-шлюз месяц работал из удаленного каталога, Linux держал открытый файл, и все три признака были зелеными, а каждый запрос /mask возвращал 500. Health-ручка остается для мониторинга самого сервиса.
Основной путь - CLI. HTTP-путь (POST /mask, POST /unmask) - вариант для того, кто поднял сервис осознанно и сам следит, из какой копии он работает: второй канал живет своей жизнью и протухает незаметно, а выигрыш в скорости - секунды (замер: CLI 2,7 с против 0,8 с у сервиса). Сервис запускается из установленного экземпляра. Из рабочего дерева репозитория нельзя: там незакоммиченная правка, переключенная ветка или проверка теста меняют поведение боевого маскировщика.
Отдельный запуск дочернего Codex на Linux
Поддержан только фактически проверенный маршрут: Linux, bubblewrap и Codex CLI 0.162.1 с bundled Code Mode host через установленный scripts/codex-sandbox.py --mode eval. Путь к скрипту - из установки в корень клиентского проекта по manifest.yaml; локальный клон канона не искать. Другие CLI-версии, оболочки и ОС не сертифицированы.
Создай две новые пустые временные папки: рабочую для дочернего Codex и приватную для mapping и восстановленного ответа. Оставь исходник на локальном пути вне workspace и runtime mounts. Песочница показывает дочернему Codex read-only runtime mounts: /usr, /bin, /lib, /lib64, /sbin, выбранные файлы /etc/ssl, /etc/ca-certificates, /etc/resolv.conf, /etc/hosts, /etc/passwd, /etc/localtime, а также установленные каталоги Codex package/native binaries и Node runtime. Исходник и приватная папка должны быть обычными локальными путями вне этих mounts; путь исходника после разрешения symlink-ов также должен быть вне них. Произвольные пути как вход этим маршрутом не поддерживаются. Укажи PII_PROJECT_ROOT как корень клиентского проекта, где установлен скрипт, а PII_SOURCE - путь к исходнику. Не копируй в workspace каталог задачи, репозиторий, историю git, конфиги, произвольные файлы, оригинал или mapping; не создавай там копии, hardlink-и или symlink-и на них. Права 0700 на папки не защищают от другого процесса того же UID.
PII_PROJECT_ROOT='/путь/к/корню-клиентского-проекта'
PII_SOURCE='/путь/к/локальному-исходнику'
PII_WORKSPACE=$(mktemp -d /tmp/pii-work-XXXXXX)
PII_PRIVATE=$(mktemp -d /tmp/pii-private-XXXXXX)
chmod 700 "$PII_WORKSPACE" "$PII_PRIVATE"
pii-mask mask "$PII_SOURCE" -o "$PII_WORKSPACE/input.masked.md" --mapping "$PII_PRIVATE/input.mapping.json"Проведи обязательную проверку остаточных ПД из раздела "Что проверить после маскировки" до запуска дочерней модели. Если проверка не пройдена - остановись. В свежем workspace находится только проверенный input.masked.md; оригинал, mapping и будущий final output находятся вне workspace и runtime mounts песочницы.
Сначала выполни offline preflight. Он использует синтетическую авторизацию и модель-заполнитель, не читает реальную авторизацию и не вызывает модель:
python3 "$PII_PROJECT_ROOT/scripts/codex-sandbox.py" --mode eval --root "$PII_WORKSPACE" --model synthetic-never-called --preflightТолько после успешного preflight укажи PII_MODEL - slug одобренной тобой и доступной модели - и отдельно запусти обработку. Замени <задача пользователя без исходных ПД> формулировкой задачи; prompt разрешает использовать только masked input и требует сохранить masked output:
PII_MODEL='slug-одобренной-доступной-модели'
printf '%s\n' 'Задача пользователя: <задача пользователя без исходных ПД>. Используй только /workspace/input.masked.md и сохрани результат в /workspace/answer.masked.md.' \
| python3 "$PII_PROJECT_ROOT/scripts/codex-sandbox.py" --mode eval --root "$PII_WORKSPACE" --model "$PII_MODEL"Затем локально разверни ответ вне workspace:
pii-mask unmask "$PII_WORKSPACE/answer.masked.md" --mapping "$PII_PRIVATE/input.mapping.json" -o "$PII_PRIVATE/answer.final.md"Eval создает отдельные state и strict config; не переиспользуй пользовательский CODEX_HOME, plugins, MCP, hooks или git history. Обычные режимы audit и implement с пользовательским состоянием или worktree не заменяют этот маршрут. Если нативная изоляция недоступна или preflight завершился ошибкой, остановись: unsandboxed fallback запрещен.
Проверки подтвердили чтение masked input и запись masked output, а также отказ чтения известных путей оригинала и mapping, абсолютных и относительных symlink-алиасов, /proc/1/root и алиасов private state. Уже помещенная в workspace копия оригинала или hardlink читаемы: безопасность зависит от чистого staging и не защищает от ошибки подготовки. Offline file probes и Code Mode readiness, как и mask/unmask roundtrip на синтетике, являются техническими canary. Они не подтверждают полный live SDD, текущую родительскую сессию или другие версии Codex, вендоров и ОС.
Когда включать --audit
Второй проход локальной модели ловит косвенное - "моя жена Оля", адрес без формата, ник без собачки. Regex и NER такого не видят.
Цена - время: порядка получаса машинного счета на транскрипт часовой встречи (CPU). Это батч-режим: запускать в фоне и не ждать в диалоге. Для короткого текста, где важна только форматная часть (телефоны, почты, ИНН), аудит не нужен.
Аудит fail-closed: запрошен, а локальная LLM недоступна - будет ошибка, а не тихая маскировка без аудита.
Что проверить после маскировки
Прежде чем отдавать текст модели - убедиться, что маскировка сработала, не вынося значения наружу: посчитать остаточных кандидатов ПД в замаскированном тексте и дословные утечки оригиналов из mapping, напечатав только счетчики и типы.
Ноль - можно работать. Не ноль - смотреть глазами локально, а не печатать найденное в лог сессии. Счетчик говорит "что-то не так", но не "что именно": на живом транскрипте расхождение счетчиков означало не пропущенные ПД, а ложную сущность - NER принял за имя обычное слово, и в тексте остались другие его вхождения. Диагноз по одним счетчикам ставить нельзя, и тем более нельзя по ним править код.
Чего скилл не делает
Скилл закрывает один сценарий: текст уходит машине, и кривая маскировка там не страшна - лишняя маска мешает модели чуть-чуть, а пропуск ловится тем, что модель персданных не запоминает.
Второй сценарий - обезличить документ, который прочитает человек (кейс для показа, публичный разбор, портфолио) - этим скиллом не решается. Машинный проход годится там только как детектор: показать, что вы забыли. Исполнителем он быть не может по двум причинам: оставляет квазиидентификаторы (см. ниже) и портит текст ложными масками, а документ для человека обязан читаться. Такой документ переписывается вручную, а не маскируется.
Квазиидентификаторы: чего движок не поймает по определению
Он ищет прямые идентификаторы - имя, телефон, почту, домен. Человека же опознают их сочетания, каждое из которых по отдельности безобидно:
- работодатель плюс город плюс точные даты работы;
- уникальный или редкий тайтл должности;
- вуз плюс год выпуска плюс специальность;
- узкая предметная область на маленьком рынке.
Реальный случай: "руководитель направления аналитики сервисных функций и взыскания" в конкретном банке - это один человек на рынке, и вычищенное имя не спасает.
Лечится не маской, а огрублением: "банк из топ-20" вместо названия, "2019-2024" вместо точных месяцев, "руководитель аналитического направления" вместо точного тайтла. Огрубление - решение автора документа, не агента: степень допустимой потери смысла знает только он.
Границы
- Имена возвращаются в именительном падеже: "заявка для Иван Петров". Это осознанное ограничение шлюза, не баг.
- Города и страны по умолчанию не маскируются - в рабочих текстах это чаще контекст, чем ПД.
- NER обучен на новостях: спонтанная речь и редкие имена даются хуже, а расшифровка без нормальной пунктуации дает ложные срабатывания на обычных словах. Для чувствительного текста -
--audit; для расшифровок - проверять результат. - Разнос сущности по документу ("нашли один раз - замаскировать все вхождения") допустим только от детерминированного признака: организационно-правовая форма плюс название в кавычках. От находок NER так делать нельзя - одно ложное срабатывание размножится на весь документ и сделает текст непригодным для модели, ради которой все и делается. Разносятся различающие слова названия, а не родовые: у "ООО Бюро кредитных историй Ромашка" по документу поедет "Ромашка", но не "бюро" и не "кредитных". Короткая аббревиатура - только в своем регистре: "ОКБ" это организация, "окб" внутри слова случайность.
- Цена разноса - лишняя маска, и она никуда не делась: различающее слово может совпасть с обычным (название "Мир" замаскирует слово "Мир" в соседней фразе) или с фамилией человека (название "Иванов" и директор Иванов получат одну ORG-метку). Список родовых слов в движке конечный, за его пределами это не ловится. Название короткое или словарное - проверить, что разнос не задел лишнего, прежде чем отдавать текст модели.
- Mapping удаляется вместе с задачей. Это единственное место, где ПД существуют вне исходника.
Модель и текущие назначения
Перед новым заданием и запуском применяй маршрутизацию моделей: свежий MODELS выше канонического дефолта. Границы данных и полномочий этого навыка сохраняются.
Other versions1 project uses a version of this.
Word for word · 1
- dewil/claude-toolkit★ 9100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository37 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- agent-browserSkill
- android-apk-releaseSkill
- asana-blockersSkill
- asana-commentsSkill
- asana-projectSkill
- backlog-taskSkill
- canon-syncSkill
- csv-xlsxSkill
- estimate-calibrationSkill
- gdocsSkill
- harvest-canonSkill
- implementSkill
- jev-decideSkill
- llm-ruSkill
- local-transcriptionSkill
- md-docxSkill
- md-pdfSkill
- md-pptxSkill
- memory-compactSkill
- memory-searchSkill
- migrateSkill
- minimal-codeSkill
- mymeet-snapshotSkill
- office-mdSkill
- proofreadSkill
- qa-browserSkill
- redmine-snapshotSkill
- repo-publicationSkill
- requirementsSkill
- reviewSkill
- sddSkill
- session-costSkill
- telegram-sendSkill
- telegram-snapshotSkill
- tutorSkill
- web-researchSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

