
Приложение с приоритетом приватности, которое удаляет AI-водяные знаки из вашего собственного контента.
_ _ _ ____ ___ ____ ____ _ _ ____ ____ _ _ ____ ____ ____ _ _ ____ _ _ ____ ____
| | | |__| | |___ |__/ |\/| |__| |__/ |_/ [__ __ |__/ |___ |\/| | | | | |___ |__/
|_|_| | | | |___ | \ | | | | | \ | \_ ___] | \ |___ | | |__| \/ |___ | \
Навык агента + сервис на stdlib Python для удаления мультивендорных меток происхождения ИИ из текста и файлов — для приватности и гигиены контента, которым вы владеете. Навык представляет собой тонкий клиент: он управляет механизмом по HTTP, поэтому хосту агента не требуется Python.
Вендоры / экосистемы (на уровне классов): Claude, Gemini / SynthID-Text, поверхности происхождения OpenAI, метки open-LLM в стиле Kirchenbauer (green-list) и keyed-Gumbel / EXP (Aaronson).
Последний релиз: v0.7.0
Путь навыка: skills/remove-ai-marks/
Путь сервиса: service/
(миграция: ранее remove-claude-marks; слэш-алиас /remove-claude-marks по-прежнему задокументирован)
Навык не содержит кода — он вызывает сервис по HTTP. Установите навык (только markdown) и запустите сервис, затем задайте WATERMARKS_SERVICE_URL, если он отличается от http://127.0.0.1:8765.
В Claude Code самый быстрый путь — встроенный маркетплейс плагинов — без клонирования, и он обновляется на месте. Во всех остальных случаях один установщик покрывает каждый поддерживаемый хост (Python 3.10+ stdlib, без зависимостей):```bash python3 install_skill.py --skill remove-ai-marks --target claude-code
| Хост | Цель | Куда устанавливается |
| --- | --- | --- |
| Claude Code (личный) | `--target claude-code` | `~/.claude/skills/<skill>` (учитывает `CLAUDE_CONFIG_DIR`) |
| Claude Code (проектный) | `--target claude-project --project-dir PATH` | `PATH/.claude/skills/<skill>` |
| Cowork, claude.ai, облачные сессии, рутины | `--target cowork` | `dist/<skill>.zip` для загрузки в разделе **Customize → Skills** |
| Cursor | `--target cursor` (по умолчанию) | `~/.cursor/skills/<skill>` |
Поставляемые навыки: `remove-ai-marks` (полный, с поддержкой сервиса) и
`clean-user-facing-text` (только текст, самодостаточный). `--list` выводит их.
Существующие установки сохраняются, если не передан `--force`; замена
сначала подготавливается, а предыдущая установка сохраняется как резервная
копия с уникальным именем. `--link` создаёт символические ссылки на этот
checkout вместо копирования, поэтому правки подхватываются
на лету. В Windows используйте `py install_skill.py ...`; обёртка `install-skill.sh`
предоставлена для оболочек macOS/Linux.
Прежде чем что-либо записывать, установщик проверяет навык на соответствие
правилам упаковки [Agent Skills](https://agentskills.io), которые применяют
загрузки claude.ai и Skills API: frontmatter только по спецификации (`name`, `description`,
`license`, `compatibility`, `metadata`, `allowed-tools`), `name` в нижнем регистре через дефис
длиной не более 64 символов, совпадающий с именем каталога, непустой
`description` длиной не более 1024 символов. Пакет Cowork дополнительно
должен укладываться в лимит загрузки 30 МБ, который обеспечивает упаковщик.
### Автоматическая очистка через хук (детерминированная)
Навык — это инструкция: модель решает, вызывать ли его, а
модель — это то, что создаёт метки. **Хук** выполняется средой
при каждом соответствующем вызове инструмента, согласие не требуется. Это делает хук
детерминированной половиной данного рабочего процесса.
Плагин регистрирует хук `PostToolUse` на `Write|Edit|MultiEdit|NotebookEdit`,
который запускает [`service/scripts/hook_written_file.py`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/service/scripts/hook_written_file.py)
для файла, который только что записал агент. Два режима, соответствующие
конвенции pre-commit о проверке по умолчанию:
| Режим | Поведение |
| --- | --- |
| `check` (по умолчанию) | Сообщает о метках происхождения, не трогая файл. Результаты передаются модели (exit 2), чтобы она могла предложить их очистить. |
| `clean` | Удаляет метки на месте, затем сообщает модели, что файл на диске изменился. |
Задайте режим в настройках плагина (**Hook mode** в `/plugin manage`,
читается хуком как `CLAUDE_PLUGIN_OPTION_HOOK_MODE`), или с помощью
`WATERMARKS_HOOK_MODE=clean` в окружении. Команда хука намеренно
**не** интерполирует `${user_config.hook_mode}`: Claude Code отказывается запускать
хук, который ссылается на опцию, которую пользователь никогда не открывал в `/plugin manage`, чтобы
её задать — объявленный `default` не удовлетворяет этому условию — поэтому интерполяция означала бы,
что хук молча никогда не запустится при свежей установке. Обнаружение повторно использует
`scan_file` / `is_actionable` из `audit_lib`, поэтому хук, шлюз pre-commit и экспорт
SARIF в CI согласованно определяют, что считается требующим действий; очистка вызывает
`clean_file.py`, поэтому логика очистки не дублируется. Режим `clean` пишет во
временный файл-сосед и заменяет только при реальном различии, поэтому файлы, которые
уже были чистыми, сохраняют свой mtime и не перезапускают наблюдатели за файлами.
Без плагина подключите его самостоятельно в `~/.claude/settings.json` (или в проектном
`.claude/settings.json`):```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "python3",
"args": ["/path/to/watermarks-remover/service/scripts/hook_written_file.py",
"--mode", "check"],
"timeout": 30
}
]
}
]
}
}
В Windows замените python3 на py.
Чего хук сделать не может. Ни один хук не может переписать сообщение ассистента в чате
до того, как вы его прочитаете. Хук Stop в Claude Code получает last_assistant_message
только для чтения, и фильтра перед отправкой финальных ответов не существует — то же ограничение,
которое этот проект уже документирует для правил Cursor. Таким образом, детерминированная гарантия
охватывает файлы, которые записывает агент, плюс
гейт pre-commit для всего, что попадает в git. Текст, который
существует только в стенограмме чата, по-прежнему зависит от рабочего процесса навыка,
который основан на инструкциях модели и поэтому работает по мере возможностей.
Репозиторий также является плагином Claude Code и маркетплейсом с одним плагином
(.claude-plugin/), поэтому оба навыка устанавливаются и обновляются двумя
командами, без клонирования или скрипта:```
/plugin marketplace add guillaumemeyer/watermarks-remover
/plugin install watermarks-remover@watermarks-remover
Навыки затем загружаются с пространством имён: `/watermarks-remover:remove-ai-marks` и
`/watermarks-remover:clean-user-facing-text` (голый `/remove-ai-marks` также
работает, когда имя не занято ничем другим). `/plugin marketplace update
watermarks-remover` подтягивает более поздние версии. То же самое работает из CLI с
`claude plugin marketplace add …` / `claude plugin install …`, а также из локального
checkout, если передать путь вместо `owner/repo`.
Мейнтейнерам: `make plugin-validate` запускает `claude plugin validate . --strict`
против обоих манифестов; `tests/test_plugin_manifest.py` покрывает те же файлы
без необходимости в CLI.
### Claude Code```bash
# Personal — available in all your projects
python3 install_skill.py --skill remove-ai-marks --target claude-code
# or: make install-claude-code-skill
# Project — commit .claude/skills/ to share it with the repo
python3 install_skill.py --skill remove-ai-marks --target claude-project \
--project-dir /path/to/project
# or: make install-claude-project-skill PROJECT=/path/to/project
Claude Code подхватывает персональные и проектные навыки без перезапуска; /skills
показывает, что было загружено. Вызывайте через /remove-ai-marks или попросите
«убрать AI-водяные знаки / C2PA / метки Claude / текст класса SynthID». Проектная
установка — это также то, что читают облачные сессии,
поскольку они клонируют репозиторий и загружают его .claude/skills/.
Сессии Cowork не читают ~/.claude/skills на вашей машине — они загружают
навыки, включённые для вашей учётной записи claude.ai, синхронизированные при
запуске сессии. Поэтому устанавливайте туда, загружая пакет:```bash
python3 install_skill.py --skill remove-ai-marks --target cowork
Затем в приложении Claude Desktop откройте **Customize → Skills → Add** и загрузите
zip-архив (те же настройки навыка на claude.ai тоже работают). Пакет
воспроизводим и содержит единственный каталог верхнего уровня `remove-ai-marks/` с
`SKILL.md` в его корне — именно такой макет ожидает загрузка.
Доступность сервиса здесь важнее, чем при локальной установке: навык — это
тонкий HTTP-клиент, поэтому сессия должна иметь возможность достучаться до `WATERMARKS_SERVICE_URL`.
Сессии Cowork, работающие локально на вашей машине, достигают локального `make serve`;
облачные сессии и рутины выполняются удалённо и нуждаются в URL сервиса, доступном оттуда
(и в установленном на нём `WATERMARKS_SERVER_API_KEY`). Если вам нужен навык вообще без
сервиса, загрузите вместо него `clean-user-facing-text` — он только для текста и
поставляется со своими собственными скриптами:```bash
python3 install_skill.py --skill clean-user-facing-text --target cowork
mkdir -p .grok/skills ln -sfn "$(pwd)/skills/remove-ai-marks" .grok/skills/remove-ai-marks
mkdir -p ~/.grok/skills ln -sfn "$(pwd)/skills/remove-ai-marks" ~/.grok/skills/remove-ai-marks
### Необязательный навык только для текста
[`skills/clean-user-facing-text/`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/skills/clean-user-facing-text) — это
самодостаточный навык для авторизованных рукописей, документации и веб-
текстов. Он исключает инструменты для изображений, C2PA, сервисов и внешних
моделей, а вместо обращения к сервису запускает собственные встроенные
скрипты Layer A.```bash
python3 install_skill.py --skill clean-user-facing-text --target claude-code
python3 install_skill.py --skill clean-user-facing-text --target cursor
Вызов навыка выбирается моделью. Проекты, которые явно принимают этот
рабочий процесс в Cursor, также могут скопировать необязательное правило:```bash
mkdir -p /path/to/project/.cursor/rules
cp integrations/cursor/clean-user-facing-text.mdc
/path/to/project/.cursor/rules/clean-user-facing-text.mdc
Для всех проектов вместо этого поместите одну и ту же инструкцию в **User Rules** Cursor.
Правила повышают согласованность, но остаются инструкциями для модели; Cursor не предоставляет
детерминированный фильтр перед отправкой для финальных ответов чата.
### Запуск сервиса
Самый быстрый путь — локальный HTTP-сервер (только стандартная библиотека Python 3.10+ — без зависимостей, без Docker):```bash
make serve # http://127.0.0.1:8765
# or directly:
python3 service/scripts/server.py --host 127.0.0.1 --port 8765
См. docs/windows-autostart.md для автозапуска сервиса при входе в Windows без Docker.
Для всей инфраструктуры (ядро + опциональные harness/heavy бэкенды) см. Docker / compose ниже.
Опциональные системные инструменты (автоматически используются при наличии — предустановлены в основном Docker-образе):
Основным скриптам нужен только Python 3.10+ stdlib. Вызовы моделей Layer B опциональны.
SCRIPTS=service/scripts
python3 "$SCRIPTS/inspect_file.py" draft.md python3 "$SCRIPTS/clean_file.py" draft.md -o draft.cleaned.md python3 "$SCRIPTS/clean_file.py" photo.png -o photo.cleaned.png python3 "$SCRIPTS/clean_file.py" notes.docx -o notes.cleaned.docx
python3 "$SCRIPTS/inspect_text.py" draft.md python3 "$SCRIPTS/clean_text.py" draft.md -o draft.cleaned.md --stats
python3 "$SCRIPTS/rewrite_text.py" draft.md --backend print-prompt --tactic paraphrase
python3 "$SCRIPTS/inspect_image.py" shot.png python3 "$SCRIPTS/clean_image.py" shot.png -o shot.cleaned.png
### Инструменты для работы с текстом отказываются от бинарного ввода
`inspect_text.py`, `clean_text.py` и `rewrite_text.py` работают с текстом. При
указании на `.docx`, `.pdf` или изображение они раньше декодировали сжатые байты и
выводили любые попавшиеся кодовые точки — шум, отражающий сжатие, а не
содержимое, — а затем `clean_text.py` записывал эти искажённые байты обратно,
уничтожая файл. Теперь они отказываются от бинарного ввода и называют инструмент,
который с ним справляется:```bash
python3 "$SCRIPTS/inspect_text.py" report.docx
# refusing to treat report.docx as text: it looks like a ZIP container (DOCX, ODT, …).
# Use inspect_file.py / clean_file.py, which route by format,
# or pass --force-text to scan the raw bytes anyway.
Обнаружение выполняется по magic number плюс соотношению управляющих байтов, поэтому текст в кодировках, отличных от UTF-8, продолжает работать. --force-text переопределяет это везде.
classify() помечает байты, не соответствующие ни одному поддерживаемому текстовому, графическому или контейнерному формату, как unknown — больше нет отката к "text". В автоматическом режиме clean_file.py отказывается от таких файлов (выход 2, вывод не записывается) вместо декодирования их как UTF-8 и записи обратно искажённых байтов; --as text или --force-text — это явные способы согласия. inspect_file.py сообщает о файле как unknown (выход 0), а HTTP-сервис отвечает на /inspect с kind: "unknown", но отклоняет /clean для неизвестных форматов (400 — отправьте имя файла с известным расширением, например notes.txt).
Тот же механизм работает как HTTP-сервис на стандартной библиотеке (service/scripts/server.py) — интерфейс, который использует skill, и способ, которым любое веб-приложение может интегрироваться без вендоринга:
Пакетные эндпоинты прогоняют тот же конвейер обработки каждого файла, что и /inspect, /detect, /clean и /watermark, с ограничением в WATERMARKS_MAX_BATCH_FILES файлов на запрос (по умолчанию 50). Некорректная запись (плохой base64, неизвестная опция, нераспознанный формат) проявляется как "ok": false для этой записи со строкой "error" — это никогда не прерывает остальную часть пакета.```bash
WM="http://127.0.0.1:8765"
curl -s "$WM/health" # {"ok": true, "version": "..."}
curl -s "$WM/openapi.json" # machine-readable OpenAPI 3.0.3 contract
curl -s -X POST "$WM/clean" -H 'Content-Type: application/json'
-d "{"file": "$(base64 < notes.md | tr -d '\n')", "name": "notes.md"}"
Сервис маршрутизирует по расширению файла, а затем по magic bytes, поэтому текст / изображение / контейнер определяются автоматически. Установите `WATERMARKS_SERVER_API_KEY`, чтобы требовать `Authorization: Bearer <key>` для каждого запроса. По умолчанию привязка только к loopback (`--host` для переопределения); предназначено для доверенной сети.
### Обнаружение водяных знаков (`/detect` и `detect_before` / `detect_after`)
Обнаружение — это отдельный шаг от очистки — сервис никогда не вызывает API
производителей, если вы его об этом не попросите:
- **`POST /detect`** запускает настроенные детекторы водяных знаков на файле.
Текст → детекторы производителей + стилометрия; изображение → оценка пикселей SynthID.
- **`/inspect`** принимает опциональный флаг `"detect": true`, который добавляет
результаты детекторов в текстовый отчёт (и может переключить `suspicious`).
- **`/clean`** принимает опции `"detect_before"` / `"detect_after"`, чтобы
оценить входные данные и очищенный вывод, так вы можете измерить, что именно
изменила очистка.
- **`/clean`** запускает перезапись текста Layer B после Layer A **по умолчанию** (это
обязательный шаг для текста). Опция **`"strategy"`** (упорядоченный
список `tactic@intensity`, например `"[email protected],[email protected]"`) переопределяет
значение по умолчанию из файла конфигурации стратегии (см. ниже). Когда
бэкенд/модель перезаписи для шага не настроены, `/clean` возвращает 400.
Текстовые детекторы (см. `/capabilities` → `text_detectors`):
Текстовые детекторы (см. `/capabilities` → `text_detectors`):
| Детектор | Активируется | Примечания |
| --- | --- | --- |
| `markllm` | `MARKLLM_DIR` (локальная копия) | Исследовательский инструмент (схемы KGW / SynthID), только с той же конфигурацией — не оракул производителя. |
| `gumbel` | `WATERMARKS_GUMBEL_KEY` | Воспроизведение без модели с тем же ключом для схемы keyed-Gumbel (Aaronson EXP) (см. `detect_gumbel.py`), только stdlib — самостоятельно размещаемые движки, такие как arbi-serve; только с тем же ключом, не оракул производителя. |
| `claude-text` | — (заглушка) | Anthropic анонсировала API обнаружения водяных знаков; этот интерфейс активируется, когда он появится. |
Оценка изображений: когда задан `WATERMARKS_SYNTHID_SCORER_URL`, сервис
оценивает изображения через sidecar `wr-synthid-score` (профиль heavy); при
локальном `REVERSE_SYNTHID_DIR` он использует локальную копию напрямую. Обнаружение
отказоустойчиво: ненастроенные, просроченные или завершившиеся ошибкой детекторы сообщают
`{"available": false, "error": ...}` и никогда не блокируют очистку.
### Генерация водяных знаков (`/watermark` и `/watermark/batch`)
Генерирует текст с водяными знаками для оценки бенчмарков и сквозного тестирования.
Когда задан `WATERMARKS_SYNTHID_TEXT_URL`, сервис делегирует генерацию
sidecar `wr-synthid-text` (профиль harness); при локальном `MARKLLM_DIR` он использует
локальную копию напрямую. Как и обнаружение, генерация отказоустойчива: ненастроенный генератор
сообщает `{"ok": false, "error": ...}`.
## Docker / compose
Опубликованные образы (GHCR):
| Тег образа | Содержимое | Опубликован? |
| --- | --- | --- |
| `ghcr.io/guillaumemeyer/watermarks-remover:<tag>` / `:latest` | Основной HTTP-сервис + все очистители + exiftool / qpdf / c2patool | Да |
| `…:markllm-<tag>` / `:markllm-latest` | Инструмент текстовых водяных знаков MarkLLM (Apache-2.0 upstream) | Да |
| `…:markdiffusion-<tag>` / `:markdiffusion-latest` | Инструмент изображений MarkDiffusion (Apache-2.0 upstream) | Да |
| `watermarks-remover-ctrlregen:local` | Пиксельное удаление CtrlRegen — **никогда не публикуется** (`noai-watermark` не поставляет LICENSE) | Только локальная сборка |
| `watermarks-remover-synthid-scorer:local` | Оценщик reverse-SynthID — **никогда не публикуется** (некоммерческая Research License) | Только локальная сборка (CLI-оценщик + опциональный HTTP sidecar `wr-synthid-score` в профиле `heavy`) |
Сборка и запуск основного сервиса:```bash
make docker-core-build
docker run --rm -p 127.0.0.1:8765:8765 --read-only --tmpfs /tmp watermarks-remover
# any CLI stays runnable by overriding the command:
docker run --rm -v "$(pwd):/data" watermarks-remover \
/app/scripts/clean_file.py /data/notes.md -o /data/notes.cleaned.md
Полный запуск инфраструктуры:```bash docker compose up -d # core HTTP service only docker compose --profile harness up -d # + markllm / markdiffusion / wr-synthid-text sidecar docker compose --profile heavy up -d # + ctrlregen / synthid (local builds) docker compose --profile harness --profile heavy up -d # all services
Стек compose сопоставляет основной сервис с `127.0.0.1:8765`. Постоянные сервисы работают как фоновые демоны (`wr-core` и сайдкар `wr-synthid-text` в профиле harness). Остальные сервисы harness/heavy — это одноразовые CLI — вызывайте их через `docker compose run --rm <service> …`, когда требуется проверка или работа с пикселями.
Проверка работающего стека (только код выхода, без вывода при успехе):```bash
make compose-check # or: ./compose-check.sh
Проверяет wr-core через GET /health и запускает каждый harness/heavy сервис с --help, требуя код выхода 0.
Очистка текста требует конфигурации Layer B — перезапись Layer B является
обязательным шагом для POST /clean применительно к тексту, поэтому базовому сервису необходимо настроить бэкенд перезаписи, иначе очистка текста возвращает HTTP 400. Очистка метаданных изображений/контейнеров работает из коробки. Для текста необходимо настроить зависимости стратегии Layer B: transformers + roberta-large (для шага mlm по умолчанию) и конфигурацию LLM WATERMARKS_REWRITE_* (для шага paraphrase):```bash
echo "Hello\u200bWorld\u00ad!" > /tmp/sample.txt
curl -s -X POST http://127.0.0.1:8765/clean -H 'Content-Type: application/json'
-d "{"file": "$(base64 < /tmp/sample.txt | tr -d '\n')", "name": "sample.txt"}"
Языки, типографика которых опирается на неразрывный пробел (французские `« … »`, пробел перед `; : ! ?`), должны передавать `"options": {"normalize_spaces": false}` — HTTP-эквивалент `clean_text.py --no-normalize-spaces`. Невидимые носители всё равно удаляются; пропускается только перезапись пробелов.
Всё остальное необязательно и находится в файле `.env` в корне репозитория. `docker compose` **автоматически загружает `.env`** и подставляет из него ссылки `${VAR}` в `compose.yaml` (экспортированные в оболочке переменные имеют приоритет над `.env`, если заданы оба).```bash
cp .env.example .env # then edit
docker compose up -d # picks up .env automatically
.env игнорируется git (запрет по умолчанию) — никогда не коммитьте его. Для запусков CLI на стороне хоста (rewrite_text.py, скилл) экспортируйте тот же файл в окружение:```bash
set -a; . ./.env; set +a; python3 service/scripts/rewrite_text.py /tmp/x.txt -o /tmp/x.rewritten.txt
| Переменная | Достигает | Назначение |
| --- | --- | --- |
| `WATERMARKS_SERVER_API_KEY` | `wr-core` (через compose `environment`) | Требовать `Authorization: Bearer <key>` для HTTP API |
| `WATERMARKS_GEMINI_*` | — | Удалено в августе 2026: Google прекратил поддержку текстового водяного знака SynthID в API (см. `vendor-notes.md`) |
| `WATERMARKS_SYNTHID_SCORER_URL` | `wr-core` | Указать core на sidecar `wr-synthid-score` для оценки изображений SynthID (например, `http://wr-synthid-score:8766` в профиле heavy) |
| `WATERMARKS_SYNTHID_SCORER_API_KEY` | `wr-core` + `wr-synthid-score` | Общий bearer-ключ для sidecar-оценщика (пусто = без аутентификации) |
| `WATERMARKS_SYNTHID_TEXT_URL` | `wr-core` | Указать core на sidecar `wr-synthid-text` для текстового водяного знака SynthID (например, `http://wr-synthid-text:8767` в профиле harness) |
| `WATERMARKS_SYNTHID_TEXT_API_KEY` | `wr-core` + `wr-synthid-text` | Общий bearer-ключ для sidecar текстового водяного знака (пусто = без аутентификации) |
| `WATERMARKS_SYNTHID_TEXT_TIMEOUT` | `wr-core` | Секунды ожидания sidecar `wr-synthid-text` (по умолчанию 120) |
| `WATERMARKS_MARKLLM_SCHEME` | `text_detectors.py` (хост) | Схема MarkLLM для `/detect`: `kgw` (по умолчанию) / `synthid` |
| `HF_TOKEN` | harness/heavy сервисы | Токен Hugging Face для gated-моделей |
| `WATERMARKS_SERVICE_URL` | только клиент (skill / curl) | Где доступен сервис; по умолчанию `http://127.0.0.1:8765` |
| `WATERMARKS_REWRITE_BACKEND` | хук `rewrite_text.py` | `print-prompt` (по умолчанию) / `ollama` / `openai-compatible` |
| `WATERMARKS_REWRITE_MODEL` | хук `rewrite_text.py` | Имя модели (например, `deepseek-v4-flash`) |
| `WATERMARKS_REWRITE_BASE_URL` | хук `rewrite_text.py` | Базовый URL API (например, `https://api.deepseek.com`) |
| `WATERMARKS_REWRITE_API_KEY` | хук `rewrite_text.py` | Ключ API — только через env, никогда в argv |
| `WATERMARKS_REWRITE_ALLOW_REMOTE` | хук `rewrite_text.py` | `1`, чтобы разрешить не-loopback эндпоинты |
| `WATERMARKS_REWRITE_REASONING_EFFORT` | хук `rewrite_text.py` | `none` (по умолчанию) / `low` / `medium` / `high` / `off` |
| `WATERMARKS_CLEAN_STRATEGY_FILE` | `server.py` `/clean` | Путь к JSON-конфигу стратегии Layer B (по умолчанию `config/clean_strategy.json`) |
| `WATERMARKS_GUMBEL_KEY` | `detect_gumbel.py` / `text_detectors.py` | Секретный ключ для keyed-Gumbel (EXP) same-key replay (например, `0x…`); предпочтительнее argv — никогда не логируется |
**Layer B требуется для очистки текста.** `/clean` всегда применяет стратегию
по умолчанию (из `config/clean_strategy.json`, `{"default_strategy": "[email protected],[email protected]"}`) к текстовому файлу после Layer A, если только запрос не передаёт собственную опцию `"strategy"` (упорядоченный список `tactic@intensity`). Шаг стратегии — это `tactic@intensity`; шагу `mlm` нужны `transformers` + `roberta-large`, а любому шагу LLM (`paraphrase`, `humanize`, …) нужна конфигурация `WATERMARKS_REWRITE_*`. Если требуемый бэкенд/модель не настроены — или стратегия недоступна — `/clean` **отклоняет запрос с кодом 400**. Приоритет для пути к конфигу: флаг CLI `--strategy-config` > переменная окружения `WATERMARKS_CLEAN_STRATEGY_FILE` > значение по умолчанию `config/clean_strategy.json`.
Образы публикуются автоматически по тегам `v*` через [`.github/workflows/release-images.yml`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/.github/workflows/release-images.yml).
## Опциональная попиксельная оценка SynthID
`inspect_image.py` и `clean_image.py` могут сообщать оценку уверенности SynthID
в пиксельной области, когда доступна внешняя копия
[`aloshdenny/reverse-SynthID`](https://github.com/aloshdenny/reverse-SynthID).
Оценщик **не входит в комплект**: он загружается во время выполнения из вашей
копии, и его код остаётся под некоммерческой
Research License вышестоящего проекта.
### Вариант 1: bootstrap одной командой (без Docker)```bash
SCRIPTS=service/scripts
# Clones upstream, creates a venv, and installs scorer-only dependencies.
"$SCRIPTS/setup_synthid.sh"
# Score an image (default checkout: ~/reverse-SynthID).
REVERSE_SYNTHID_DIR=~/reverse-SynthID \
~/reverse-SynthID/.venv/bin/python "$SCRIPTS/score_synthid.py" shot.png
# Or surface the score from inspect / clean (same venv Python).
REVERSE_SYNTHID_DIR=~/reverse-SynthID \
~/reverse-SynthID/.venv/bin/python "$SCRIPTS/inspect_image.py" shot.png
setup_synthid.sh принимает --dir PATH, --ref REF и --full (устанавливает
полный upstream requirements.txt, который добавляет torch/diffusers для
upstream VAE bypass, который в этом проекте не используется).
В Windows используйте setup_synthid.ps1 (-Dir, -Ref, -Full), который создаёт
venv в .venv\Scripts\ — структуру, которую image_meta.py уже ищет при
os.name == "nt".
make docker-synthid-build
docker run --rm
--user "$(id -u):$(id -g)"
--read-only --tmpfs /tmp
-v "$(pwd):/data"
watermarks-remover-synthid-scorer /data/shot.png
Образ собирается локально из исходного кода upstream во время сборки. Он не
публикуется, поэтому не распространяет код upstream.
### Вариант 3: HTTP-сайдкар scorer (docker compose)
В профиле `heavy` стек compose также запускает scorer как HTTP-сайдкар
(`wr-synthid-score`), чтобы **опубликованный основной сервис** мог оценивать
изображения до/после очистки без включения в поставку несвободного кода
upstream. Направьте `wr-core` на него и задайте общий bearer-ключ (см. `.env.example`):```bash
# .env
WATERMARKS_SYNTHID_SCORER_URL=http://wr-synthid-score:8766
WATERMARKS_SYNTHID_SCORER_API_KEY=change-me
docker compose --profile heavy up -d
Затем POST /clean с {"options": {"detect_before": true, "detect_after": true}} возвращает synthid_before / synthid_after в
отчёте, а POST /detect для изображения возвращает оценку SynthID. Отказоустойчивость:
если sidecar недоступен или не настроен, отчёты содержат
{"available": false, "error": ...}, и очистка всё равно завершается успешно.
Оценка V4 использует artifacts/spectral_codebook_v4.npz из upstream-репозитория
(`220 MB). Это только обнаружение/оценка — она не удаляет пиксельные
водяные знаки.
Для пиксельных водяных знаков на изображениях (класса SynthID, StegaStamp, Tree-Ring,
StableSignature) опциональный внешний бэкенд запускает конвейер CtrlRegen
(ControlNet + DINOv2 IP-Adapter с управляемой регенерацией). Бэкенд —
mertizci/noai-watermark, поддерживаемая
реализация метода ICLR 2025
CtrlRegen с автоматическим тайлингом.
Бэкенд не включён в поставку и не содержит файла LICENSE, поэтому рассматривается как
с сохранением всех прав: он клонируется на зафиксированном коммите и загружается во время выполнения.
Его зависимости исследовательской эпохи (requirements-ctrlregen.txt — например,
transformers==4.37.2, diffusers==0.27.2) содержат опубликованные рекомендации по безопасности и
намеренно не являются актуальными, поэтому они устанавливаются только внутри
выделенного venv, создаваемого этим скриптом, и никогда в основной образ сервиса;
setup_ctrlregen.sh также повторно проверяет зафиксированный коммит в существующих
клонах, а не только в свежих.
SCRIPTS=service/scripts
"$SCRIPTS/setup_ctrlregen.sh"
NOAI_WATERMARK_DIR=~/noai-watermark
~/noai-watermark/.venv/bin/python "$SCRIPTS/clean_ctrlregen.py" shot.png -o shot.ctrlregen.png
В Windows используйте `setup_ctrlregen.ps1` (те же флаги, что и `-Dir`, `-Ref`, `-Python`);
venv размещается в `.venv\Scripts\`, который `clean_image.py` уже разрешает.
Он проверяет опубликованные индексы колёс PyTorch и выбирает наивысший из них на уровне
или ниже версии CUDA, которую выводит `nvidia-smi` и которая действительно существует — это число
является максимумом, поддерживаемым *драйвером*, а драйверы обратно совместимы, поэтому
драйвер, сообщающий 13.1 (нет опубликованного `cu131`), устанавливает `cu130`. Ниже вычислительной
способности 7.5 он принудительно выбирает `cu126`, последний индекс, чьи колёса всё ещё содержат
ядра Maxwell/Pascal/Volta. Он устанавливает `torch` **и** `torchvision`
вместе из этого индекса, чтобы установка зависимостей не могла подменить их на CPU-
сборки из PyPI, затем после установки проверяет, что `torch.cuda.is_available()`
истинно — если GPU был обнаружен, но torch оказывается только для CPU, скрипт громко предупреждает
и завершается с ненулевым кодом, вместо того чтобы делать вид, что установка успешна.
### Из `clean_image.py````bash
NOAI_WATERMARK_DIR=~/noai-watermark \
~/noai-watermark/.venv/bin/python "$SCRIPTS/clean_image.py" shot.png \
-o shot.cleaned.png --remove-pixel ctrlregen
Порядок операций: сначала удаление метаданных, затем удаление пикселей CtrlRegen, затем
необязательная оценка reverse-SynthID до/после (когда также задан REVERSE_SYNTHID_DIR).
Интенсивность по умолчанию консервативна (--ctrlregen-intensity 0.25), потому что
более высокая интенсивность удаляет больше водяного знака, но сильнее перегенерирует изображение.
Документированные пресеты: 0.15 минимальный / 0.25 по умолчанию / 0.35 сбалансированный /
0.5 агрессивный / 0.7 максимальный (значение по умолчанию бэкенда — 0.5). --ctrlregen-steps
по умолчанию равен 50 (эффективные шаги денойзинга ≈ steps × intensity).
CtrlRegen — это ControlNet для Stable Diffusion 1.5 с разрешением 512×512. Бэкенд решает это для произвольных входных данных, поэтому здесь не предоставляется дополнительное тайлирование:
Очень большие изображения (например, 4K) создают много тайлов, поэтому выполнение масштабируется с числом тайлов (медленнее и больше VRAM). По возможности предварительно уменьшайте большие входные данные; размер тайла и перекрытие жёстко заданы в upstream и не предоставляются как флаги.
Ожидайте ~10 ГБ загрузок моделей; GPU настоятельно рекомендуется, а запуски на CPU
медленны. Некоторые upstream-модели являются gated, поэтому экспортируйте HF_TOKEN (только через env —
никогда через argv). clean_ctrlregen.py отказывается автоматически устанавливать зависимости; сначала запустите
setup_ctrlregen.sh.
Локального детектора для StegaStamp/Tree-Ring/StableSignature нет, поэтому
единственный локальный сигнал — это оценка reverse-SynthID (суррогат). Когда она доступна,
clean_image.py --remove-pixel ctrlregen сообщает эту оценку до/после; официальная проверка
Google SynthID остаётся окончательным авторитетом.
make docker-ctrlregen-build
docker run --rm -e HF_TOKEN="$HF_TOKEN"
--user "$(id -u):$(id -g)"
-v "$(pwd):/data"
watermarks-remover-ctrlregen /data/shot.png -o /data/shot.ctrlregen.png
## Опциональная проверка текстовых водяных знаков MarkLLM
Для **контролируемых экспериментов** опциональная внешняя обвязка оборачивает
[`THU-BPM/MarkLLM`](https://github.com/THU-BPM/MarkLLM) (Apache-2.0), чтобы
наносить водяные знаки на тестовый текст и повторно обнаруживать их после
перезаписи на уровне Layer B — например, доказать, что
метка KGW (Kirchenbauer, ваша строка «open-LLM») или SynthID-Text (строка Gemini)
исчезает после вашей перезаписи. Это **обвязка для проверки, а не оракул**:
обнаружение MarkLLM действительно только против *той же* конфигурации схемы и ключей, использованных при
генерации, и оно не может гарантировать, что детектор вендора даст сбой.
Бэкенд **не поставляется в комплекте**. `setup_markllm.sh` клонирует upstream на закреплённом
коммите, создаёт venv и устанавливает закреплённые зависимости (torch + transformers); модель
оценки (по умолчанию `facebook/opt-1.3b`, Apache-2.0) загружается с Hugging
Face при первом запуске.```bash
SCRIPTS=service/scripts
# Bootstrap (clones upstream, creates ~/MarkLLM/.venv, installs deps).
"$SCRIPTS/setup_markllm.sh"
# Generate watermarked + unwatermarked sample text under the KGW scheme.
MARKLLM_DIR=~/MarkLLM \
~/MarkLLM/.venv/bin/python "$SCRIPTS/detect_text_watermark.py" watermark prompt.txt \
--scheme kgw -o wm.txt -o2 plain.txt
# Detect the scheme mark in a text file.
MARKLLM_DIR=~/MarkLLM \
~/MarkLLM/.venv/bin/python "$SCRIPTS/detect_text_watermark.py" detect wm.txt --scheme kgw --json
Проверка вокруг перезаписи Layer B: передайте --markllm-scheme в
rewrite_text.py (вместе с --markllm-dir), и он записывает обнаружение MarkLLM
до/после плюс флаг cleared:```bash
export WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2
MARKLLM_DIR=~/MarkLLM
python3 "$SCRIPTS/rewrite_text.py" wm.txt -o wm.rewritten.txt
--markllm-scheme kgw --markllm-dir "$HOME/MarkLLM" --json-stats
**Обнаружение с управлением итеративным переписыванием:** Слой B теперь переписывает итеративно и останавливается, как только попытка проходит оценку. Каждый раунд оценки генерирует варианты `--candidates` (по умолчанию **1**, `WATERMARKS_REWRITE_CANDIDATES`), а `--max-loops` ограничивает количество раундов, выполняемых до возврата варианта с наилучшим усилием (по умолчанию **1**, `WATERMARKS_REWRITE_LOOPS`). Каждый вариант — это один вызов переписывания плюс одна оценка, и раунд завершается досрочно при первой попытке, которую оценщик сообщает как не содержащую водяной знак — поэтому увеличение `--max-loops` повторяет новые варианты до тех пор, пока оценка не пройдёт (типичное чистое переписывание стоит одной попытки). Оценщик выбирается по приоритету:
1. **MarkLLM** — исследовательское обнаружение с той же конфигурацией, когда передан `--markllm-scheme` (вместе с `--markllm-dir`). Слот для детектора вендора зарезервирован выше MarkLLM для детектора SynthID-text от Google, который Google отключил в своём API в августе 2026 года — будущая конечная точка вендора может быть подключена туда.
2. **лексическое расхождение bigram-Jaccard** — когда детектор не настроен; нет вердикта pass/fail, поэтому каждая попытка генерируется и выбирается наиболее лексически расходящаяся (исходное поведение).
`--json-stats` сообщает оценщик, сделанные попытки, pass/fail и записи по каждой попытке:```json
{
"evaluator": "markllm",
"candidates": 1,
"max_loops": 2,
"attempts_made": 2,
"passed": true,
"candidate_scores": [
{
"lexical_divergence": 0.91,
"selection_score": 0.91,
"selected": false,
"passed": false,
"evaluation": {"detector": "markllm", "available": true, "scheme": "kgw",
"is_watermarked": true, "score": 4.3, "threshold": 3.0}
},
{
"lexical_divergence": 0.84,
"selection_score": 0.84,
"selected": true,
"passed": true,
"evaluation": {"detector": "markllm", "available": true, "scheme": "kgw",
"is_watermarked": false, "score": 1.7, "threshold": 3.0}
}
],
"markllm": {"scheme": "kgw", "before": {"...": "..."}, "after": {"...": "..."},
"cleared": true, "note": "same-config only"}
}
Детектор, который не настроен, завершается по тайм-ауту или выдаёт ошибку, даёт запись
"available": false с причиной error и никогда не приводит к сбою
перезаписи — такая попытка просто не может пройти, и цикл переходит к
выбору на основе лексического расхождения. Когда максимум исчерпан без прохождения,
возвращается попытка с наименьшим водяным знаком (наименьшим баллом) как лучшее возможное решение с
примечанием.
Если бэкенд не настроен или его зависимости отсутствуют, перезапись продолжается, а в отчёте отмечается, что верификация была недоступна. Рекомендуется GPU; запуски на CPU работают, но медленны, а загрузка модели составляет несколько ГБ.
Параметры усиления защиты:
--offline на адаптере (или любом запуске MarkLLM) загружает модель оценки
только из кэша Hugging Face — нулевой сетевой трафик; быстро завершается с ошибкой, если нет в кэше.
Пользовательский удалённый код никогда не выполняется (transformers trust_remote_code
никогда не включается).WATERMARKS_MARKLLM_RLIMIT_AS=<bytes> (env, POSIX) применяет ограничение адресного пространства
к подпроцессу детектора MarkLLM. По умолчанию отключено, поскольку torch/CUDA
обычно требует больших адресных пространств.make docker-markllm-build
docker run --rm --user "$(id -u):$(id -g)" -v "$(pwd):/data"
watermarks-remover-markllm detect /data/wm.txt --scheme kgw --json
### Проверка Keyed-Gumbel (Aaronson EXP) с тем же ключом
[Технический отчёт ARBI](https://arbicity.com/news/ai-text-watermarking-for-self-hosted-ai/) описывает
keyed-Gumbel («экспоненциальный») текстовый водяной знак — теперь поставляемый в открытом
движке arbi-serve (`ARBI_WATERMARK_KEY`) — где шум сэмплера выводится
из ключевого хеша последнего 4-токенного контекстного окна. Детекция — это
**воспроизведение без модели**: пересчитайте `u = PRF(Hash(key, window), token)` по
одному лишь тексту и проверьте хвост гамма-распределения, так что это не требует GPU, модели или логитов.
Этот репозиторий поставляет такой детектор как `detect_gumbel.py` (только stdlib; p-значение
представляет собой точное тождество пуассоновской суммы для целочисленной формы гамма-распределения):```bash
# Text mode (deterministic word/run tokenizer) — quick checks and rewrite-loop
# evaluation; exact replay against a real engine needs its tokenizer:
python3 service/scripts/detect_gumbel.py draft.txt --key 0x... --json
# Exact replay: pass the engine's token ids (JSON array or one per line).
python3 service/scripts/detect_gumbel.py ids.json --tokens --key 0x... --json
Та же оговорка о честности, что и для MarkLLM: это воспроизведение с тем же ключом — действительно только против того же ключа, токенизатора и схемы PRF, использованных при генерации, и отрицательный результат ничего не доказывает. Схема HMAC-SHA256 здесь — это проверяемая реализация, а не бит-совместимая с каким-либо конкретным ядром движка (см. docstring модуля о том, что адаптировать для точного воспроизведения).
Перезапись с обнаружением: передайте --gumbel-key в rewrite_text.py
(переменная окружения: WATERMARKS_GUMBEL_KEY, предпочтительно), и итеративный цикл перезаписи
управляется воспроизведением Gumbel с тем же ключом — приоритет оценщика становится gumbel >
MarkLLM > лексическое расхождение — с отчётом gumbel.before/after/cleared:```bash
export WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2
export WATERMARKS_GUMBEL_KEY=0x...
python3 "$SCRIPTS/rewrite_text.py" wm.txt -o wm.rewritten.txt --json-stats
Ключ никогда не появляется в статистике или логах. Самостоятельно размещённые операторы, владеющие ключом своего движка, могут проверить, что перезапись очистила метку Gumbel; все остальные рассматривают Layer B как best-effort.
## Опциональный бенчмарк удаления SynthID-text
[`bench_synthid_text.py`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/service/scripts/bench_synthid_text.py) измеряет, насколько эффективно перезапись Layer B очищает водяные знаки класса SynthID-text и какой ценой. Он генерирует образцы с водяным знаком и без него с помощью схемы SynthID из MarkLLM (детектирование с той же конфигурацией, с проверкой sanity), запускает ваши варианты перезаписи (тактика × максимальное число попыток перезаписи; цикл останавливается досрочно при успехе) плюс контрольные варианты (без удаления, только Layer A, опциональная проверка повторного нанесения) и записывает распространяемый `report.md` /
`results.json` / `results.csv`. Полное руководство:
[`docs/synthid-text-benchmark.md`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/docs/synthid-text-benchmark.md).
Требуется checkout MarkLLM (`setup_markllm.sh` / `MARKLLM_DIR`) и бэкенд перезаписи. **Модель перезаписи — это LLM, которую вы настраиваете** — тот же бэкенд `rewrite_text.py`, который использует навык. Стандартная модель MarkLLM
`facebook/opt-1.3b` (`--markllm-model`) — это только генератор/детектор водяного знака; она никогда не выполняет перезапись. Настройте модель перезаписи через переменные окружения или флаги бенчмарка (они повторяют
[таблицу конфигурации](#configuration-env-vars-for-docker-compose) выше):
| Env var | Флаг бенчмарка | По умолчанию | Значение |
| --- | --- | --- | --- |
| `WATERMARKS_REWRITE_BACKEND` | `--rewrite-backend` | `ollama` | `ollama` или `openai-compatible` |
| `WATERMARKS_REWRITE_MODEL` | `--rewrite-model` | *(обязательно)* | LLM, выполняющая перезапись (например, `llama3.2`, `deepseek-v4-flash`) |
| `WATERMARKS_REWRITE_BASE_URL` | `--rewrite-base-url` | `http://127.0.0.1:11434` | Эндпоинт; значение Ollama по умолчанию — loopback |
| `WATERMARKS_REWRITE_API_KEY` | `--rewrite-api-key` | — | API-ключ (только через env в дочернем процессе, никогда через argv) |
| `WATERMARKS_REWRITE_ALLOW_REMOTE=1` | `--rewrite-allow-remote` | выкл. | Требуется для отправки содержимого на не-loopback эндпоинты |```bash
# Ollama (loopback):
python3 service/scripts/bench_synthid_text.py --markllm-dir ~/MarkLLM \
--rewrite-backend ollama --rewrite-model llama3.2
# OpenAI-compatible API (remote):
WATERMARKS_REWRITE_API_KEY=... python3 service/scripts/bench_synthid_text.py \
--markllm-dir ~/MarkLLM --rewrite-backend openai-compatible \
--rewrite-model deepseek-v4-flash --rewrite-base-url https://api.deepseek.com \
--rewrite-allow-remote
Используйте неоригинальную модель для перезаписи (не перезаписывайте той же моделью с водяным знаком, которая сгенерировала текст), иначе перезапись может повторно поставить метку; --restamp-control измеряет это.
Для контролируемых экспериментов с изображениями опциональный внешний стенд оборачивает THU-BPM/MarkDiffusion (Apache-2.0) — набор инструментов генеративного нанесения водяных знаков для моделей латентной диффузии (он встраивает метки — он их не удаляет). Мы используем его для трёх вещей:
DiffusionPurification доступна как clean_image.py --remove-pixel diffusion — альтернатива CtrlRegen. Это слепая регенерация (без conditioning ControlNet), поэтому она сильнее смещает содержимое изображения, чем CtrlRegen — консервативное значение интенсивности по умолчанию (0.3), рассматривается как запасной вариант/для сравнения, но никогда как гарантия.Бэкенд не поставляется в комплекте. setup_markdiffusion.sh создаёт venv и устанавливает markdiffusion==1.0.2 из PyPI (с фиксацией версии), при этом torch устанавливается из индекса нужной платформы; --checkout вместо этого устанавливает редактируемую копию на зафиксированном коммите. Модель Stable Diffusion (по умолчанию huanzi05/stable-diffusion-2-1-base) скачивается с Hugging Face при первом запуске.```bash
SCRIPTS=service/scripts
"$SCRIPTS/setup_markdiffusion.sh"
echo "a red fox in snow" > /tmp/prompt.txt
MARKDIFFUSION_DIR=~/markdiffusion
~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" watermark
/tmp/prompt.txt -o wm.png -o2 plain.png --scheme tr --json
MARKDIFFUSION_DIR=~/markdiffusion
~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" purify
wm.png -o wm.purified.png --purification-intensity 0.3 --json
MARKDIFFUSION_DIR=~/markdiffusion
~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" detect
wm.purified.png --scheme tr --detector-type l1_distance --json
Или запустите очистку как часть обычного конвейера образов:```bash
MARKDIFFUSION_DIR=~/markdiffusion \
~/markdiffusion/.venv/bin/python "$SCRIPTS/clean_image.py" shot.png \
-o shot.cleaned.png --remove-pixel diffusion
Параметры усиления безопасности повторяют подход MarkLLM: --offline загружает модель только из кэша Hugging Face (нулевой сетевой трафик, без удалённого кода), HF_TOKEN передаётся только через переменные окружения (никогда через аргументы командной строки), конфигурации алгоритмов ограничены 1 МиБ, а подпроцесс получает те же повышенные лимиты ресурсов, что и CtrlRegen.
make docker-markdiffusion-build
docker run --rm --user "$(id -u):$(id -g)" -v "$(pwd):/data"
watermarks-remover-markdiffusion detect /data/wm.png --scheme tr --json
Образ устанавливает CPU torch; пользователям CUDA следует запустить `setup_markdiffusion.sh`
на хосте. Загрузки моделей по-прежнему обращаются к HF hub при первом запуске.
## Матрица покрытия
| Канал | Claude | Gemini/SynthID | OpenAI | Open-LLM |
| --- | --- | --- | --- | --- |
| Unicode / текстовые метки на основе правок | Слой A | Слой A | Слой A | Слой A |
| **Текст статистической выборки** | Слой B, по мере возможностей (шов Claude, когда выйдет API обнаружения Anthropic) | Слой B, по мере возможностей (+ harness MarkLLM с той же конфигурацией; Google вывел из эксплуатации вендорский детектор в августе 2026) | Слой B, если присутствует | Слой B, по мере возможностей + опциональный harness MarkLLM |
| C2PA / метаданные файла | Да (перечисленные форматы) | Да, когда присутствует | Да, когда присутствует | Да, когда присутствует |
| Пиксельные метки изображений | Вне области охвата | Опционально оценка SynthID + удаление CtrlRegen (внешнее); опционально обнаружение MarkDiffusion по той же схеме + удаление DiffusionPurification (внешнее) | Вне области охвата | Опционально удаление CtrlRegen / MarkDiffusion (внешнее) |
| Бэкдоры в обучении | Вне области охвата | Вне области охвата | Вне области охвата | Вне области охвата |
Подробности: [`skills/remove-ai-marks/references/vendor-notes.md`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/skills/remove-ai-marks/references/vendor-notes.md), [`mark-classes.md`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/skills/remove-ai-marks/references/mark-classes.md).
---
## Как работает текстовая маркировка (кратко)
Современные водяные знаки LLM часто прячут сигнал в том, **какие токены выбираются** (генеративное / выборочное смещение), а не только в невидимых символах. Схемы на основе правок внедряют Unicode или правила синонимов. Файловые схемы прикрепляют **C2PA** или метаданные генератора.
- **Слой A** удаляет носители Unicode на основе правок (проверяемо).
- **Слой B** атакует водяные знаки выборки через глубокую переработку (по мере возможностей; стандартные для литературы атаки, такие как перефразирование / обратный перевод).
- **Очистители файлов** удаляют C2PA/XMP/свойства из поддерживаемых контейнеров.
Пока вендоры не выпустят публичные детекторы и ключи, **ни один инструмент не может честно удостоверить**, что «это не пройдёт официальную проверку». Отчёты должны разделять проверяемую работу и работу по мере возможностей.
Предпочитайте **не-исходную** модель для Слоя B (не переписывайте текст Claude с помощью Claude, если вы пытаетесь избежать повторной маркировки).
---
## Отказ от ответственности: чего стоит удаление текстового водяного знака
Текстовые водяные знаки живут **в самой формулировке**: сигнал распределён по выбору токенов, поэтому почти каждое предложение несёт его частицу. Отсюда следуют два вывода, и именно поэтому Слой B честно описывается как *по мере возможностей*, а не как волшебный ластик.
1. **Удаление означает переформулирование, а не реструктуризацию.** Перестановка абзацев, изменение заголовков или лёгкая правка едва сдвигают сигнал. Чтобы снять статистическую метку, требуется переписать значительную долю текста — предложение за предложением, а не раздел за разделом.
2. **Переформулирование ухудшает текст.** Любая переработка заменяет исходный выбор слов на выбор модели-переписчика, что сглаживает тон, голос и точность. На продакшн-текстах (SEO, маркетинг, клиентская работа) это ухудшение реально и часто заметно тем, кто больше всего заботится о тексте. Это как взять текст от модели топового уровня и попросить менее способную модель переписать его с нуля: результат не может превысить потолок модели-переписчика.
Что подводит к честному вопросу по замкнутому кругу:
> Если всё равно планируется переписать текст более дешёвой моделью, зачем вообще платить за премиум-модель? Генерация сразу более дешёвой моделью проще, дешевле и даёт тот же — или лучший — конечный результат.
Слой B имеет смысл, когда вы специально хотите **мышление и черновик** премиум-модели и принимаете проход переписывания ради соблюдения требований гигиены или приватности — а не как дешёвый путь к тексту без меток.
**Когда пропускать Слой B:**
- **Качество важнее гигиены:** используйте путь без потерь — очистку Unicode Слоем A плюс очистители метаданных файлов — и сохраните исходную прозу.
- **Всё равно переписываете:** используйте **не-исходную** модель (переписывание исходной моделью может повторно поставить метку), и помните, что остаточный риск сохраняется — ни один инструмент не может удостоверить, что вендорский детектор не сработает.
---
## Форматы файлов
| Формат | Инспекция | Очистка |
| --- | --- | --- |
| PNG / JPEG / WebP | Чанки C2PA / APP11 / RIFF `C2PA`, подсказки AI XMP | Удалить сегменты метаданных |
| AVIF / HEIC | Боксы ISOBMFF `jumb` / XMP `uuid` | Удалить боксы |
| BMP | Завершающие не-изображённые байты (нет стандартизированного канала) | Обрезать завершающие метаданные, исправить поле размера файла |
| GIF | Расширения Comment / XMP application | Удалить comment и XMP, сохранить цикл `NETSCAPE2.0` |
| TIFF (classic + BigTIFF) | Теги IFD: XMP, EXIF, GPS, IPTC, MakerNote | Удалить теги, обнулить полезные нагрузки, сохранить strips |
| SVG | `<metadata>`, XMP | Удалить блоки |
| PDF | Байты/XMP + опциональные инструменты | **exiftool**, затем **qpdf**, затем **ghostscript** для метаданных внутри встроенных изображений; каждый отсутствующий инструмент ухудшает свой слой (удаление из документа, структурная перезапись, встроенные изображения) |
| DOCX | docProps / customXml | Очистить свойства, удалить customXml |
| EPUB | Метаданные OPF, XHTML meta/JSON-LD, встроенные медиа | Очистить OPF, удалить XHTML meta, очистить медиа + Слой A (пропускает зашифрованные части) |
| ODT | meta.xml | Удалить метаданные генератора / похожие на AI |
| HTML | meta, JSON-LD, data-ai* | Удалить теги/атрибуты |
| Markdown | YAML frontmatter с AI-ключами | Удалить ключи + тело Слоем A |
| MP4 / MOV / M4A / M4V | Боксы ISOBMFF `jumb`/`uuid` (тот же механизм, что у AVIF/HEIC) + теги генератора `moov/udta` | Удалить боксы |
| WAV | Чанки RIFF `C2PA` / `LIST INFO`, встроенный чанк `id3\x20` | Удалить чанки |
| MP3 | Кадры ID3v2 (v2.3/v2.4 покадрово; v2.2 весь тег) | Удалить совпавшие кадры или весь тег |
| FLAC | Манифест C2PA в кадре ID3v2 `GEOB` | Удалить совпавший кадр или весь тег ID3v2 |
Поддержка FLAC охватывает стандартизированный носитель ID3v2 от C2PA. Нативные блоки метаданных FLAC,
Vorbis Comments и водяные знаки в области формы сигнала остаются нетронутыми.
#### Почему PDF нужен qpdf, а не только exiftool
ExifTool записывает PDF **инкрементально**. `exiftool -all=` добавляет блок
`%BeginExifToolUpdate`, который освобождает объект Info и удаляет `/Info` из
трейлера — но исходные байты метаданных остаются в файле дословно, и
сам exiftool может отменить правку с помощью `-PDF-update:all=`. Команда завершается
с кодом `0`, просмотрщики не показывают метаданные, а файл становится *больше* — это и есть признак.
Для инструмента удаления происхождения это тихая утечка, поэтому `clean_pdf` выполняет
после прохода exiftool команду `qpdf --linearize`, которая пересобирает документ
из его графа объектов и удаляет теперь уже неиспользуемые объекты. Без установленного `qpdf`
очистка всё равно выполняется, но сообщает об этом:```
warning: exiftool PDF edits are incremental — the original metadata bytes
remain recoverable; install qpdf for a structural rewrite
Оба прохода выше работают с документом: словарём Info, пакетом XMP,
графом объектов. Ни один из них не спускается в XObject изображения, поэтому скан или
экспорт из Photoshop — страница, которая является одним большим JPEG, — сохраняет всё, что несёт изображение. На реальном PDF, экспортированном из Photoshop, после «успешной» очистки остаётся 27 тегов, среди них IFD0:Software, временные метки захвата и превью-миниатюра; манифест C2PA, прикреплённый к тому же изображению, тоже переживает её.
Поэтому clean_pdf добавляет третий проход, deep_images, управляемый Ghostscript
pdfwrite. Он выполняется в две ступени и останавливается, как только файл становится чистым:
pdfwrite с pass-through пересобирает документ из
графа объектов, копируя сжатые данные изображения байт в байт — это проверяется
хешированием потоков до и после. Это очищает всё, что PDF обернул вокруг
изображения. Pass-through покрывает кодеки, которые Ghostscript поддерживает
для него: JPEG (DCTDecode) и JPEG2000 (JPXDecode); изображения Flate, CCITT и LZW
декодируются и перекодируются, что на практике для этих
кодеков не даёт потерь, но не является байт-идентичным. never — это опция для документа, потоки которого
должны остаться нетронутыми.always, любые сохранившиеся метаданные APPn. APP0 (JFIF) и APP2 (ICC) остаются
нетронутыми — первый структурный, а второй определяет, как
читаются цвета. Пиксели тратятся на доказательства, никогда на подозрения.deep_images принимает auto (по умолчанию: только ступень 1, когда маркеры пережили
очистку документа, затем ступень 2, если они переживают и её), always (ступень 1 для каждого
PDF, с эскалацией до ступени 2 также для EXIF камер и редакторов), lossless (только ступень 1
— никогда не пересжимать и сообщать обо всём, что сохранилось, через обычные
поля still_has_c2pa / post_findings) и never. Нераспознанное значение
отклоняется, а не молча трактуется как auto. Отчёт сообщает, какие ступени выполнялись,
через meta.deep_image_pass и meta.images_reencoded, а когда проход
пропускается, он называет опцию, которая зашла бы дальше:```text
deep image pass not needed for AI/C2PA markers; pass deep_images="always"
to also clear non-AI EXIF inside images
Без установленного Ghostscript очистка всё равно выполняется и сообщает, к чему не удалось получить доступ:```text
warning: metadata inside embedded images left in place; install ghostscript
for the deep image pass
Удаление водяных знаков в пиксельной области теперь доступно как опциональный внешний бэкенд CtrlRegen (см. выше); это регенерирующий удалитель, а не гарантия. Мягкая привязка C2PA (водяной знак в контенте, который может повторно связать удалённый манифест Content Credentials после удаления метаданных) остаётся вне области охвата. Удаление жёстко привязанного C2PA не очищает эти каналы.
Этот инструмент сообщает о проверяемых удалениях (подсчёт Unicode, действия с метаданными) и наилучших возможных перезаписях Layer B. Он не может гарантировать, что детекторы поставщиков не сработают.
Чтобы самостоятельно проверить остаточные сигналы (опционально, извне):
Отраслевой двухслойный контекст (C2PA + невоспринимаемый водяной знак): руководство Institute of AI PM.
Проверяющие инструменты от поставщиков для определения, несёт ли контент метки происхождения ИИ:
Матрица: skills/remove-ai-marks/references/removal-matrix.md.
См. skills/remove-ai-marks/references/ethics.md. Для приватности и исследований вашего контента — не для академического мошенничества или ложных заявлений о «написанном человеком».
Ответственное использование: Этот проект предназначен для контента, которым вы владеете или который уполномочены обрабатывать. Пользователи должны соблюдать местные нормативы и использовать его ответственно. Разработчики отказываются от любой ответственности за возможное неправомерное использование пользователями.
Сторонние проекты, которые оборачивают или дополняют этот репозиторий, перечислены только для облегчения поиска. Они не поддерживаются, не одобряются и не обеспечиваются этим проектом. Этот проект не проверяет их код, не ручается за их поведение или гарантии и не несёт ответственности за всё, что вы установите или запустите из этого списка. Каждый проект регулируется собственной лицензией, сопровождающими и документацией — прочитайте их перед использованием.
MetaClean — независимое настольное приложение на Rust/Tauri под лицензией MIT (Windows, macOS, Linux), предоставляющее упакованный нативный GUI для очистки метаданных перетаскиванием, с системным треем и интеграцией с Проводником. Это отдельная кодовая база: оно не вызывает Python-сервис этого репозитория, и его поддерживаемые форматы и гарантии очистки отличаются от таковых у этого проекта. Подробности см. в его README.
unmark-web — независимый статический веб-клиент под лицензией MIT. Он удаляет невидимые метки Unicode из текста и удаляет метаданные происхождения из изображений полностью в браузере, а также может опционально вызывать HTTP-сервис этого репозитория для форматов, которые он не обрабатывает локально. Это отдельная кодовая база, не связанная с этим проектом; см. его README для области охвата и ограничений.
DropMarks — независимое приложение для macOS на SwiftUI под лицензией MIT. Оно вызывает inspect_file.py / clean_file.py этого репозитория (и опционально rewrite_text.py) через вендорный снимок этих stdlib-скриптов. Это отдельная кодовая база, не связанная с этим проектом; см. его README для области охвата и ограничений.
Чтобы зарегистрировать проект здесь, откройте PR, добавив краткую запись — название проекта, что он оборачивает или добавляет, и ссылку на его собственный репозиторий. Держите записи краткими и фактическими; не заявляйте о совместимости с этим проектом или его одобрении. Проект в списке должен строиться на этом репозитории или интегрировать его — например, вызывая его сервис или повторно используя его движок обнаружения — а не просто независимо решать ту же проблему. Пожалуйста, избегайте имён, которые начинаются с watermarks-remover или близко напоминают его — похожие имена затрудняют понимание, какой проект какой.
CI-гейтинг уже существует (экспорт SARIF из audit_dir.py, см. контекст Матрицы покрытия) — хуки pre-commit ниже ловят тот же класс проблем раньше, ещё до того, как помеченный файл будет закоммичен. Оба оборачивают существующие CLI (audit_dir.py / clean_file.py) — без отдельной логики обнаружения.```yaml
repos:
`watermarks-remover-check` завершает коммит с ошибкой и выводит список найденных проблем; `watermarks-remover-clean` включается по желанию и перезаписывает проиндексированные файлы на месте (завершается с кодом 1, чтобы вы просмотрели diff и повторно проиндексировали — та же условность, что и у хуков автоисправления вроде `ruff --fix`). Когда очиститель вообще не может обработать файл — он упал, был убит или не создал отчёт — `watermarks-remover-clean` называет этот файл и завершается с кодом 3, чтобы очиститель, давший сбой, никогда не был принят за уже чистый файл. Запускайте любой из них вручную с помощью `python3 service/scripts/check_staged.py <files...>` / `clean_staged.py <files...>`.
## Тесты```bash
python3 -m venv .venv && .venv/bin/pip install pytest
.venv/bin/python -m pytest # or: make test
make smoke # quick CLI smoke on fixtures
/clean, модуль кражи водяных знаков, удаление аудио/видео водяных знаков и расширение бенчмарков/инструментовv0.7.0 переносит переписывание статистических меток Layer B непосредственно в сервис /clean, управляемое настраиваемой, оптимизированной по бенчмаркам стратегией ([email protected],[email protected]). Вместе с этим: модуль кражи водяных знаков методом чёрного ящика, деструктивное удаление аудио- и покадровых видео-водяных знаков, существенно расширенный бенчмарк переписывания и набор исправлений по укреплению, безопасности и инструментарию.
Переписывание Layer B в сервисе
/clean выполняет переписывание Layer B для текста после Layer A. Значение по умолчанию берётся из config/clean_strategy.json; параметр options.strategy для отдельного запроса переопределяет его, а /clean отклоняет запрос с кодом 400, когда требуемый бэкенд не настроен (#315). Приоритет конфигурации: --strategy-config > WATERMARKS_CLEAN_STRATEGY_FILE > config/clean_strategy.json.mlm: маскирование доли содержательных слов и заполнение с помощью roberta-large — неавторегрессивное локальное редактирование, поэтому выходные данные смешивают исходный поток токенов с предсказаниями masked-LM (#311).humanize теперь применяет проход humanizer-skill детерминированно (прямые кавычки, без en/em тире, свёртка филлеров, utilize→use) и называет правила человеческого письма в промпте (#311). получил CLI-путь .Бенчмарк
Кража водяных знаков
Аудио / видео / изображения
uuid происхождения контента C2PA на MP4/MOV/AVIF/HEIC (#264).zTXt/iTXt до 1 MiB (#308); удаление объявлений SVG XML DOCTYPE/ENTITY (#288); побайтовая безопасность бинарных членов DOCX (#314); сохранение OOXML AppVersion (#289).HTTP-сервис и CLI
/clean для сохранения экзотических пробелов, зеркалирующая CLI (#274); /inspect предоставляет явные классы доказательств в подозрительной полезной нагрузке (#277); временные метки в логах HTTP-запросов (#256); передача байтов полезной нагрузки в HTTP-оценку SynthID и inspect_* во избежание избыточного обратного чтения.clean_file.py получил -q/--quiet/--only-changed (#254).Навыки, плагин и хуки
clean-user-facing-text (#258); запуск хука PostToolUse сделан кроссплатформенным (#255); хук pre-commit рассматривает побайтово идентичные чистые нетекстовые файлы как изменённые (#238).Аудит
audit_dir.py сканирует исходные, документационные и i18n-файлы, которые пропустил роутер (#284); сканирует .ts/.tsx/.jsx/.gd и выравнивает уверенность по пробелам между форматами (#273); поддержка audit_website.py --sarif (#194); укрепление резервных копий на месте, статуса чистого файла, вердикта SynthID, усечённого ID3v2 и маршрутизации zip (#201).Безопасность
CI, инструментарий и документация
Покрытие форматов и контейнеров
NETSCAPE2.0 и другие анимационные чанки сохраняются; метаданные TIFF IFD (XMP/EXIF/GPS/IPTC/MakerNote) отбрасываются с обнулением полезных нагрузок и сохранением смещений полос, как для классического TIFF, так и для BigTIFF; хвостовые метаданные BMP усекаются с перезаписью поля размера файла (#107)docProps; удаление висячих связей после удаления customXml; запуск Layer A по тексту тела DOCX/ODT; декодирование XML-сущностей перед очисткой Layer A (#91, #100, #76, #83, #73, #80, #74, #81, #142)Укрепление Layer A (невидимый Unicode)
Default_Ignorable без легитимного применения для обмена (U+2065, U+FFF0–U+FFF8, U+E0000, U+E0080–U+E00FF, U+E01F0–U+E0FFF — сообщается как reserved_ignorable), 66 несимволов (U+FDD0–U+FDEF плюс U+FFFE/U+FFFF для каждой плоскости — сообщается как noncharacter), и три носителя Default_Ignorable с пустым отображением, которые универсальный перехватчик никогда не видел (, , ). Каждый имеет такое же контекстное сохранение, как и его уже охваченные собратья, поэтому текст с частичными слогами не повреждается, и каждый применяется как к движку сервиса, так и к вендорированной облегчённой копии навыкаПереписывание Layer B и обнаружение водяных знаков
--candidates (по умолчанию 1, WATERMARKS_REWRITE_CANDIDATES), а --max-loops (по умолчанию 1, WATERMARKS_REWRITE_LOOPS) ограничивает раунды оценки, останавливаясь, как только попытка проходит детекцию. Приоритет оценщика: MarkLLM (--markllm-scheme) > лексическая дивергенция bigram-Jaccard (запасной вариант). rewrite_text.py --json-stats теперь сообщает evaluator / max_loops / attempts_made / passed и candidate_scores для каждой попытки (#153)detect_gumbel.py, использующий только stdlib, реализует тест воспроизведения без модели (u = PRF(Hash(key, window), token); точное p-значение Gamma-хвоста; маскирование повторяющихся окон) без GPU, модели или логитов. (переменная окружения , предпочтительно) делает его оценщиком итеративного цикла (приоритет: gumbel > markllm > лексическая дивергенция), и он представлен как в и . Только с тем же ключом — не вендорский оракул; ключ никогда не логируется (#190)Распространение: плагин, хуки и установка навыков
.claude-plugin/plugin.json + marketplace.json), поэтому оба навыка устанавливаются с помощью /plugin marketplace add guillaumemeyer/watermarks-remover, затем /plugin install watermarks-remover@watermarks-remover, и обновляются на месте. make plugin-validate запускает claude plugin validate . --strict; tests/test_plugin_manifest.py проверяет манифесты без CLIinstall_skill.py обзавёлся --target (claude-code, claude-project, cowork, cursor) и селектором , охватывающим оба поставляемых навыка, плюс , и . Цель собирает воспроизводимый пакет для загрузки (, единый каталог навыка верхнего уровня); каждая цель валидируется по правилам упаковки Agent Skills и лимиту загрузки в 30 МБ. Новые цели : , , , , HTTP-сервис
POST /clean/batch, /inspect/batch (#137) и POST /detect/batch (#151)/clean и использование безопасных записей в av_meta (#150); использование переносимого base64 в примере curl для /detect (и исправление переносимости realpath в macOS в бутстрапах, #185)Аудит / инспекция и безопасность
audit_dir.py обзавёлся многопоточной параллельностью и экспортом SARIF 2.1.0 (#101, #102)Исправления надёжности и корректности
--in-place сохраняет оригинальный .bak; сохранение собранных доказательств, когда последующий член zip не читается (#175); усечённые контейнеры ISOBMFF всё ещё запускают байтовое сканирование C2PA как запасной вариант (#176); различение упавшего очистителя от уже чистого файла (#159, #161); рассмотрение неудачного запуска c2patool как неубедительного, а не как "нет C2PA" (#156); валидация типов опций очистки (#111); никогда не выбирать устройство MPS автоматически для детекции текстовых водяных знаков (#99); переносимость macOS — чистый --json stdout для оценщика SynthID и проба BSD realpath (#70); исправление пути Windows subprocess_creationflags в _ghostscript_usable и предотвращение открытия дочерними процессами окна консоли в Windowsbench-synthid-text; упрощение сквозной передачи флагов для пробы Ghostscript и удаление ненужного noqa в clean_text (lint)CI / инструментарий / документация
watermarks-remover-clean / clean_staged.py): использование дайджестов содержимого (SHA-256) и активного обнаружения действий, чтобы чистые файлы на диске распознавались без требования бесконечного повторного стейджинга (#173)<AppVersion> нетронутым в docProps/app.xml во время очистки метаданных DOCX, XLSX и PPTX для удовлетворения ограничений схемы ECMA-376 и избежания ошибок "нечитаемое содержимое" в Microsoft Word/Office (#283)Распространение сервиса / Docker
skills/remove-ai-marks/) теперь является удалённым клиентом без кода поверх HTTP; вся реализация перемещена в service/scripts/ и работает за server.py, точкой входа HTTP на stdlib (/health, /inspect, /clean, /capabilities)service/scripts/server.py предоставляет конвейер очистки через JSON/base64; укрепление зеркалирует CLI (ограничения размера, защита от бинарных файлов, атомарные записи, loopback по умолчанию, опциональная bearer-аутентификация WATERMARKS_SERVER_API_KEY)| Слой | Цель | Как |
|---|
| A | Невидимый Unicode, экзотические пробелы, bidi, тег-символы | Детерминированные Python-скрипты |
| B | Статистические (token-sampling) текстовые водяные знаки | Перезапись агентом + опциональный хук rewrite_text.py |
| Файлы | C2PA / EXIF / XMP / свойства документов | PNG, JPEG, WebP, AVIF, HEIC, BMP, GIF, TIFF, SVG, PDF, DOCX, XLSX, PPTX, EPUB, ODT, HTML, Markdown, MP4/MOV/M4A/M4V, WAV, MP3, FLAC |
| Инструмент | Роль |
|---|
c2patool | Просмотр манифестов C2PA |
exiftool | Удаление остаточных метаданных (особенно PDF) |
qpdf | Структурная пересборка PDF — требуется для реального удаления из PDF (см. ниже) |
| Метод | Путь | Тело | Возвращает |
|---|
| GET | /health | — | {"ok": true, "version": ...} |
| GET | /capabilities | — | опциональные инструменты / бэкенды, доступные для использования (каждый инструмент проверяется по версии, а не просто находится в PATH) |
| GET | /openapi.json | — | динамически генерируемая спецификация OpenAPI 3.0.3 |
| POST | /inspect | {"file": "<base64>", "name": "notes.md"} | {"ok", "kind", "suspicious", "report"} |
| POST | /detect | {"file": "<base64>", "name": "notes.txt"} | {"ok", "kind", "detections": [...]} |
| POST | /clean | {"file": "<base64>", "name": "notes.md", "options": {...}} | {"ok", "kind", "cleaned": "<base64>", "report"} |
| POST | /watermark | {"text": "...", "keys": [118, 504, ...], "options": {...}} или {"file": "<base64>", ...} | {"ok", "kind", "watermarked_text", "report": {"scheme_used", ...}} |
| POST | /inspect/batch | {"files": [{"file": "<base64>", "name": "notes.md"}, ...]} | {"ok", "results": [{"name", "ok", "kind", "suspicious", "report"}, ...]} |
| POST | /detect/batch | {"files": [{"file": "<base64>", "name": "notes.txt"}, ...]} | {"ok", "results": [{"name", "ok", "kind", "detections", "report"}, ...]} |
| POST | /clean/batch | {"files": [{"file": "<base64>", "name": "notes.md", "options": {...}}, ...]} | {"ok", "results": [{"name", "ok", "kind", "cleaned", "report"}, ...]} |
| POST | /watermark/batch | {"files": [{"text": "...", "keys": [...]}, {"file": "<base64>"}, ...]} | {"ok", "results": [{"name", "ok", "kind", "watermarked_text", "report": {"scheme_used", ...}}, ...]} |
| Канал | Что мы удаляем | Что может остаться | Внешняя проверка (примеры) |
|---|
| Жёстко привязанный C2PA / EXIF / XMP | Да | Мягко привязанные / пиксельные метки | c2patool, Content Credentials verify |
| Медиа класса SynthID | Опциональное удаление пикселей (внешний CtrlRegen); иначе локальная оценка | Водяной знак аудио/видео; остаточный пиксельный водяной знак после удаления | Инструменты поставщиков (например, Google SynthID / детектор Vertex, где предлагается); опциональный локальный оценщик reverse-SynthID |
| Статистический текст | Перезапись с наилучшими возможностями | Сильные метки после лёгкого редактирования | Публичного универсального детектора нет; инструменты поставщиков, когда доступны |
| Вариант | Удаляет | Примечания |
|---|
| Очистка Unicode (Layer A) | ZWSP, bidi, теги, экзотические пробелы, … | Безопасный вариант по умолчанию для текста |
| Перезапись (Layer B) | Статистические токенные метки (наилучшие возможные) | Всегда предлагается навыком; стоит стиля — см. Отказ от ответственности |
| Удаление контейнера/метаданных | Происхождение файла | См. таблицу форматов |
| Удаление пикселей CtrlRegen (опционально) | Метки изображений в пиксельной области (класса SynthID, StegaStamp, Tree-Ring, StableSignature) | Внешний бэкенд; тяжёлые вычисления; консервативная интенсивность по умолчанию |
| Удаление пикселей DiffusionPurification (опционально) | Метки изображений в пиксельной области (класса Tree-Ring) | Бэкенд MarkDiffusion; слепая регенерация (больше дрейфа, чем у CtrlRegen); консервативная интенсивность по умолчанию |
| Локальные модели с открытыми весами | Избежать повторного нанесения меток исходной моделью | Операционная альтернатива |
rewrite_text.py--strategyCfU+180FU+3164U+FFA0U+13430–U+1343F), управляющие символы стенографии Дюплойе (U+1BCA0–U+1BCA3) и управляющие символы музыкальных балок/лиг/связок/фраз (U+1D173–U+1D17A) теперь сохраняются при соседстве с их собственным письмом и по-прежнему удаляются (и помечаются) при нахождении между несвязанным текстом; параноидальный режим --strip-emoji-glue всё ещё удаляет их вездеrewrite_text.py --gumbel-keyWATERMARKS_GUMBEL_KEYgumbel/capabilities/detectparaphrase:3; отчёт и CSV содержат попытки на документ (столбцы mean_attempts / att, attempts / evaluator / passed); --rewrite-loops зеркалирует --max-loops--skill--list--linkCLAUDE_CONFIG_DIRcoworkdist/<skill>.zipmakeinstall-claude-code-skillinstall-claude-code-text-skillinstall-claude-project-skillpackage-cowork-skillpackage-cowork-text-skillPostToolUse (hooks/hooks.json + service/scripts/hook_written_file.py): после того как агент записывает файл, обвязка запускает хук независимо от того, сотрудничает ли модель. check (по умолчанию) сообщает метки модели; clean удаляет их на месте и сообщает модели, что файл перемещён, заменяя только при реальном различии, поэтому чистые файлы сохраняют свой mtime. Режим берётся из настройки плагина hook_mode или WATERMARKS_HOOK_MODE; детекция переиспользует audit_lib.scan_file / is_actionable, поэтому хук, шлюз pre-commit и экспорт CI SARIF согласованы. Хук всё ещё не может переписать сообщение чата ассистента — такой точки хука не существует — поэтому этот путь остаётся по мере возможностиclean-user-facing-text больше не называет Cursor единственным хостом