
watermarks-remover v0.5.0
プライバシー優先のアプリで、所有するコンテンツからAIウォーターマークを除去します。
_ _ _ ____ ___ ____ ____ _ _ ____ ____ _ _ ____ ____ ____ _ _ ____ _ _ ____ ____
| | | |__| | |___ |__/ |\/| |__| |__/ |_/ [__ __ |__/ |___ |\/| | | | | |___ |__/
|_|_| | | | |___ | \ | | | | | \ | \_ ___] | \ |___ | | |__| \/ |___ | \
watermarks-remover
エージェントスキル + 標準ライブラリのみの Python サービスで、テキストおよびファイルから複数ベンダーの AI 由来マークを除去します — あなたが所有するコンテンツのプライバシーと衛生のために。このスキルはシンクライアントです。HTTP 経由で機構を操作するため、エージェントホストに Python は不要です。
| レイヤー | 対象 | 方法 |
|---|---|---|
| A | 不可視 Unicode、特殊スペース、bidi、タグ文字 | 決定論的な Python スクリプト |
| B | 統計的(トークンサンプリング)テキスト透かし | エージェントによるリライト + オプションの 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 |
ベンダー / エコシステム(クラスレベル): Claude、Gemini / SynthID-Text、OpenAI の由来サーフェス、open-LLM の Kirchenbauer 方式(グリーンリスト)およびキー付き Gumbel / EXP(Aaronson)マーク。
最新リリース: v0.7.0
スキルのパス: skills/remove-ai-marks/
サービスのパス: service/
(移行: 以前は remove-claude-marks。スラッシュエイリアス /remove-claude-marks も引き続きドキュメント化されています)
インストール(エージェントスキル)
このスキルはコードを同梱していません — HTTP 経由でサービスを呼び出します。スキル(Markdown のみ)をインストールしてサービスを起動し、http://127.0.0.1:8765 でない場合は WATERMARKS_SERVICE_URL を設定してください。
Claude Code では、最速の方法は同梱の プラグインマーケットプレイスです — クローン不要で、 その場で更新されます。それ以外の環境では、1 つのインストーラーがサポート対象のすべてのホストをカバーします (Python 3.10+ 標準ライブラリ、依存関係なし):```bash python3 install_skill.py --skill remove-ai-marks --target claude-code
| Host | Target | Lands in |
| --- | --- | --- |
| Claude Code (personal) | `--target claude-code` | `~/.claude/skills/<skill>` (`CLAUDE_CONFIG_DIR` を尊重) |
| Claude Code (project) | `--target claude-project --project-dir PATH` | `PATH/.claude/skills/<skill>` |
| Cowork, claude.ai, cloud sessions, routines | `--target cowork` | `dist/<skill>.zip` を **Customize → Skills** でアップロード |
| Cursor | `--target cursor` (デフォルト) | `~/.cursor/skills/<skill>` |
同梱スキル: `remove-ai-marks` (完全版、サービス連携) と
`clean-user-facing-text` (テキストのみ、自己完結)。`--list` で一覧表示されます。
既存のインストールは `--force` を渡さない限り保持されます。置き換えは
まずステージングされ、以前のインストールは一意の名前のバックアップとして
保持されます。`--link` はコピーの代わりにこのチェックアウトをシンボリック
リンクするため、編集が即座に反映されます。Windows では
`py install_skill.py ...` を使用してください。`install-skill.sh` ラッパーは
macOS/Linux シェル用に提供されています。
何かを書き込む前に、インストーラーはスキルを
[Agent Skills](https://agentskills.io) のパッケージングルール
(claude.ai のアップロードと Skills API が強制するもの) に対して検証します:
仕様のみのフロントマター (`name`、`description`、
`license`、`compatibility`、`metadata`、`allowed-tools`)、ディレクトリと
一致する最大 64 文字の小文字ハイフン区切りの `name`、最大 1024 文字の
空でない `description`。Cowork バンドルはさらに 30 MB のアップロード
制限に収まる必要があり、これはパッケージャーが強制します。
### フックによる自動クリーニング (決定論的)
スキルは指示です: モデルがそれを呼び出すかどうかを決定し、
モデルはマークを生成するものです。**フック**は一致するすべてのツール
呼び出しでハーネスによって実行され、協力は必要ありません。これにより
フックはこのワークフローの決定論的な半分となります。
プラグインは `Write|Edit|MultiEdit|NotebookEdit` に `PostToolUse` フックを
登録し、エージェントが書き込んだファイルに対して
[`service/scripts/hook_written_file.py`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/service/scripts/hook_written_file.py)
を実行します。2 つのモードがあり、デフォルトでチェックするという
pre-commit の慣習に一致します:
| Mode | Behaviour |
| --- | --- |
| `check` (デフォルト) | 来歴マークを報告し、ファイルには手を付けません。検出結果はモデルに送信され (exit 2)、モデルがクリーニングを提案できます。 |
| `clean` | マークをその場で除去し、ディスク上のファイルが変更されたことをモデルに通知します。 |
モードはプラグインの設定 (`/plugin manage` の **Hook mode**、フックが
`CLAUDE_PLUGIN_OPTION_HOOK_MODE` として読み取る) から、または環境変数で
`WATERMARKS_HOOK_MODE=clean` を設定して指定します。フックコマンドは
意図的に `${user_config.hook_mode}` を補間し**ません**: Claude Code は、
ユーザーが `/plugin manage` を開いて設定したことのないオプションを参照する
フックの実行を拒否します — 宣言された `default` ではそれを満たしません —
そのため補間すると、新規インストールでフックが黙って実行されなくなる
ことを意味します。検出は `audit_lib` の `scan_file` / `is_actionable` を
再利用するため、フック、pre-commit ゲート、CI SARIF エクスポートが
何をアクション可能と見なすかで一致します。クリーニングは
`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 に置き換えてください。
フックにできないこと。 どのフックも、あなたが読む前にアシスタントのチャットメッセージを書き換えることはできません。Claude Code の Stop フックは last_assistant_message を読み取り専用で受け取り、最終応答用の送信前フィルターは存在しません — これはこのプロジェクトが Cursor ルールについてすでに文書化しているのと同じ制限です。したがって、決定論的な保証がカバーするのはエージェントが書き込むファイル、および git に入る途中のあらゆるものに対するpre-commit ゲートです。チャットのトランスクリプトにのみ存在するテキストは、依然としてスキルワークフローに依存しており、これはモデル指示ベースであるためベストエフォートです。
Claude Code プラグイン(マーケットプレイス)
このリポジトリは Claude Code のプラグインであり、単一プラグインのマーケットプレイス(.claude-plugin/)でもあるため、両方のスキルを 2 つのコマンドでインストールおよび更新できます。クローンやスクリプトは不要です:```
/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 …` で機能し、ローカルチェックアウトからは `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.ai、クラウドセッション、ルーチン)
Cowork セッションは、あなたのマシン上の ~/.claude/skills を読み込みません — それらは
あなたの claude.ai アカウントで有効化されたスキルを読み込み、セッション開始時に同期されます。
そのため、そこにインストールするにはバンドルをアップロードします:```bash
python3 install_skill.py --skill remove-ai-marks --target cowork
writes dist/remove-ai-marks.zip (make package-cowork-skill)
次に、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
Grok```bash
Grok Build / project-local
mkdir -p .grok/skills ln -sfn "$(pwd)/skills/remove-ai-marks" .grok/skills/remove-ai-marks
User-global Grok
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) は、
許可された原稿、ドキュメント、Web コピー向けの自己完結型スキルです。
画像、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
すべてのプロジェクトで、代わりに同じ指示を Cursor の **User Rules** に入れてください。
ルールは一貫性を向上させますが、モデルへの指示にとどまります。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
Windows(Docker なし)
Docker なしで Windows ログイン時にサービスを自動起動する方法については、docs/windows-autostart.md を参照してください。
インフラ全体(コア + オプションのハーネス/ヘビーバックエンド)については、以下の Docker / compose を参照してください。
オプションのシステムツール(存在する場合に自動使用 — コア Docker イメージにはプリインストール済み):
コアスクリプトは Python 3.10+ の標準ライブラリのみが必要です。レイヤー B のモデル呼び出しはオプションです。
クイック使用(スクリプト)```bash
SCRIPTS=service/scripts
Unified inspect / clean
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
Text Layer A
python3 "$SCRIPTS/inspect_text.py" draft.md python3 "$SCRIPTS/clean_text.py" draft.md -o draft.cleaned.md --stats
Layer B rewrite hook (default: print prompt only — no model required)
python3 "$SCRIPTS/rewrite_text.py" draft.md --backend print-prompt --tactic paraphrase
Optional local Ollama (loopback only by default — remote endpoints require
WATERMARKS_REWRITE_ALLOW_REMOTE=1 or --allow-remote):
WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2 \
python3 "$SCRIPTS/rewrite_text.py" draft.md -o draft.rewritten.md
API keys are read from WATERMARKS_REWRITE_API_KEY only (never argv).
Images
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.
検出はマジックナンバーと制御バイト比率によって行われるため、UTF-8 以外のエンコーディングのテキストも引き続き機能します。--force-text はそれをどこでも上書きします。
認識されないフォーマットは自動クリーンされない
classify() は、サポートされているテキスト、画像、コンテナフォーマットのいずれにも一致しないバイト列を unknown とラベル付けします — もはや「テキスト」にフォールバックすることはありません。自動モードでは clean_file.py はそのようなファイルを UTF-8 としてデコードして壊れたバイト列を書き戻す代わりに拒否します(終了コード 2、出力は書き込まれません)。--as text または --force-text が明示的なオプトインです。inspect_file.py はそのファイルを unknown として報告し(終了コード 0)、HTTP サービスは /inspect に対して kind: "unknown" と応答しますが、未知のフォーマットの /clean は拒否します(400 — 既知の拡張子を持つファイル名を送信してください。例: notes.txt)。
HTTP サービス
同じ仕組みが stdlib HTTP サービス(service/scripts/server.py)として動作します — これはスキルが使用するインターフェースであり、あらゆる Web アプリがベンダリングなしで統合できる方法です:
| メソッド | パス | ボディ | 戻り値 |
|---|---|---|---|
| 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", ...}}, ...]} |
バッチエンドポイントは /inspect、/detect、/clean、/watermark と同じファイル単位のパイプラインをループし、1 リクエストあたり 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"}"
サービスはファイル名拡張子でルーティングし、次にマジックバイトでルーティングするため、テキスト / 画像 / コンテナは自動検出されます。`WATERMARKS_SERVER_API_KEY` を設定すると、すべてのリクエストで `Authorization: Bearer <key>` が必須になります。デフォルトではループバックのみにバインドされます(`--host` で上書き可能)。信頼されたネットワーク向けに設計されています。
### ウォーターマーク検出(`/detect` および `detect_before` / `detect_after`)
検出はクリーニングとは別のステップです — サービスは、あなたが要求しない限りベンダー API を呼び出すことはありません:
- **`POST /detect`** は、設定されたウォーターマーク検出器をファイルに対して実行します。
テキスト → ベンダー検出器 + スタイロメトリー、画像 → SynthID ピクセルスコア。
- **`/inspect`** はオプトインの `"detect": true` フラグを受け付け、検出器の結果をテキストレポートに追加します(そして `suspicious` を反転させることができます)。
- **`/clean`** は `"detect_before"` / `"detect_after"` オプションを受け付け、入力とクリーニング後の出力をスコアリングするため、クリーニングが実際に何を変えたかを測定できます。
- **`/clean`** は Layer A の後に Layer B のテキスト書き換えを**デフォルトで**実行します(テキストでは必須のステップです)。**`"strategy"`** オプション(順序付きの `tactic@intensity` リスト、例:`"[email protected],[email protected]"`)は、戦略設定ファイル(下記参照)のデフォルトを上書きします。ステップの書き換えバックエンド/モデルが設定されていない場合、`/clean` は 400 を返します。
テキスト検出器(`/capabilities` → `text_detectors` を参照):
テキスト検出器(`/capabilities` → `text_detectors` を参照):
| 検出器 | 有効化条件 | 備考 |
| --- | --- | --- |
| `markllm` | `MARKLLM_DIR`(ホストのチェックアウト) | 研究用ハーネス(KGW / SynthID スキーム)、同一設定のみ — ベンダーオラクルではありません。 |
| `gumbel` | `WATERMARKS_GUMBEL_KEY` | キー付き Gumbel(Aaronson EXP)スキームのモデル不要な同一キーリプレイ(`detect_gumbel.py` を参照)、stdlib のみ — arbi-serve などのセルフホストエンジン;同一キーのみ、ベンダーオラクルではありません。 |
| `claude-text` | —(プレースホルダー) | Anthropic はウォーターマーク検出 API を発表しました;このシームはそれがリリースされたときに有効になります。 |
画像スコアリング:`WATERMARKS_SYNTHID_SCORER_URL` が設定されている場合、サービスは `wr-synthid-score` サイドカー(heavy プロファイル)を通じて画像をスコアリングします;ローカルの `REVERSE_SYNTHID_DIR` がある場合はチェックアウトを直接使用します。検出はフェイルソフトです:未設定、タイムアウト、またはエラーが発生した検出器は `{"available": false, "error": ...}` を報告し、クリーニングをブロックすることはありません。
### ウォーターマーク生成(`/watermark` および `/watermark/batch`)
ベンチマーク評価とラウンドトリップテスト用のウォーターマーク付きテキストを生成します。`WATERMARKS_SYNTHID_TEXT_URL` が設定されている場合、サービスは生成を `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 アップストリーム) | はい |
| `…:markdiffusion-<tag>` / `:markdiffusion-latest` | MarkDiffusion 画像ハーネス(Apache-2.0 アップストリーム) | はい |
| `watermarks-remover-ctrlregen:local` | CtrlRegen ピクセル除去 — **公開されません**(`noai-watermark` は LICENSE を同梱していません) | ローカルビルドのみ |
| `watermarks-remover-synthid-scorer:local` | reverse-SynthID スコアラー — **公開されません**(非商用 Research License) | ローカルビルドのみ(CLI スコアラー + `heavy` プロファイル下のオプションの `wr-synthid-score` HTTP サイドカー) |
コアサービスのビルドと実行:```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` と、harness プロファイル配下の `wr-synthid-text` サイドカー)。残りの harness/heavy サービスはワンショット CLI です — 検証やピクセル作業が必要な場合は `docker compose run --rm <service> …` で呼び出してください。
実行中のスタックを検証します(終了コードのみ、成功時は出力なし):```bash
make compose-check # or: ./compose-check.sh
GET /health を介して wr-core をチェックし、各ハーネス/ヘビーサービスを --help 付きで実行し、終了コード 0 を要求します。
設定 (docker compose 用の環境変数)
テキストクリーニングには Layer B の設定が必要です — Layer B のリライトはテキストに対する POST /clean の必須ステップであるため、コアサービスはリライトバックエンドをセットアップする必要があります。そうでない場合、テキストクリーニングは HTTP 400 を返します。画像/コンテナメタデータのクリーニングはそのまま動作します。テキストについては、Layer B 戦略の依存関係を設定する必要があります: transformers + roberta-large (デフォルトの mlm ステップ用) および WATERMARKS_REWRITE_* LLM 設定 (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}` を渡す必要があります。これは `clean_text.py --no-normalize-spaces` に相当する HTTP 版です。不可視のキャリアは引き続き削除されます。スキップされるのはスペースの書き換えのみです。
それ以外はすべて任意で、リポジトリルートの `.env` ファイルに記述します。`docker compose` は **`.env` を自動的に読み込み**、そこから `compose.yaml` 内の `${VAR}` 参照を補間します(両方が設定されている場合、シェルのエクスポートが `.env` より優先されます)。```bash
cp .env.example .env # then edit
docker compose up -d # picks up .env automatically
.env は gitignore 対象(デフォルトで拒否)です — 決してコミットしないでください。ホスト側の CLI 実行(rewrite_text.py、スキル)では、同じファイルを環境にエクスポートします:```bash
set -a; . ./.env; set +a; python3 service/scripts/rewrite_text.py /tmp/x.txt -o /tmp/x.rewritten.txt
| Var | Reaches | Purpose |
| --- | --- | --- |
| `WATERMARKS_SERVER_API_KEY` | `wr-core` (compose の `environment` 経由) | HTTP API で `Authorization: Bearer <key>` を要求する |
| `WATERMARKS_GEMINI_*` | — | 2026年8月に削除: Google は API 上の SynthID テキストウォーターマークを廃止した (`vendor-notes.md` を参照) |
| `WATERMARKS_SYNTHID_SCORER_URL` | `wr-core` | SynthID 画像スコアリング用に core を `wr-synthid-score` サイドカーへ向ける (例: heavy プロファイルでは `http://wr-synthid-score:8766`) |
| `WATERMARKS_SYNTHID_SCORER_API_KEY` | `wr-core` + `wr-synthid-score` | スコアラーサイドカー用の共有ベアラーキー (空 = 認証なし) |
| `WATERMARKS_SYNTHID_TEXT_URL` | `wr-core` | SynthID テキストウォーターマーク用に core を `wr-synthid-text` サイドカーへ向ける (例: harness プロファイルでは `http://wr-synthid-text:8767`) |
| `WATERMARKS_SYNTHID_TEXT_API_KEY` | `wr-core` + `wr-synthid-text` | テキストウォーターマークサイドカー用の共有ベアラーキー (空 = 認証なし) |
| `WATERMARKS_SYNTHID_TEXT_TIMEOUT` | `wr-core` | `wr-synthid-text` サイドカーを待つ秒数 (デフォルト 120) |
| `WATERMARKS_MARKLLM_SCHEME` | `text_detectors.py` (ホスト) | `/detect` 用の MarkLLM スキーム: `kgw` (デフォルト) / `synthid` |
| `HF_TOKEN` | harness/heavy サービス | ゲート付きモデル用の Hugging Face トークン |
| `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` フック | API ベース (例: `https://api.deepseek.com`) |
| `WATERMARKS_REWRITE_API_KEY` | `rewrite_text.py` フック | API キー — 環境変数のみ、argv には決して置かない |
| `WATERMARKS_REWRITE_ALLOW_REMOTE` | `rewrite_text.py` フック | 非ループバックエンドポイントを許可するには `1` |
| `WATERMARKS_REWRITE_REASONING_EFFORT` | `rewrite_text.py` フック | `none` (デフォルト) / `low` / `medium` / `high` / `off` |
| `WATERMARKS_CLEAN_STRATEGY_FILE` | `server.py` `/clean` | Layer B 戦略設定 JSON へのパス (デフォルト `config/clean_strategy.json`) |
| `WATERMARKS_GUMBEL_KEY` | `detect_gumbel.py` / `text_detectors.py` | keyed-Gumbel (EXP) 同一キーリプレイ用の秘密キー (例: `0x…`); argv より優先 — 決してログに出力しない |
**テキストクリーニングには Layer B が必須です。** `/clean` は Layer A の後、テキストファイルに対して常にデフォルト戦略 (`config/clean_strategy.json` の `{"default_strategy": "[email protected],[email protected]"}`) を適用します。ただし、リクエストが独自の `"strategy"` オプション (順序付きの `tactic@intensity` リスト) を渡す場合は除きます。戦略ステップは `tactic@intensity` です; `mlm` ステップには `transformers` + `roberta-large` が必要で、任意の LLM ステップ (`paraphrase`、`humanize`、…) には `WATERMARKS_REWRITE_*` 設定が必要です。必要なバックエンド/モデルが設定されていない場合 — または利用可能な戦略がない場合 — `/clean` は**リクエストを 400 で拒否します**。設定パスの優先順位: `--strategy-config` CLI フラグ > `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` は、[`aloshdenny/reverse-SynthID`](https://github.com/aloshdenny/reverse-SynthID) の外部チェックアウトが利用可能な場合、ピクセルドメインの SynthID 信頼度スコアを報告できます。スコアラーは**バンドルされていません**: 実行時にあなたのチェックアウトからロードされ、そのコードは上流プロジェクトの非商用 Research License の下に留まります。
### オプション 1: ワンコマンドブートストラップ (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 を受け付けます(--full は上流の requirements.txt 全体をインストールし、このプロジェクトでは使用しない上流 VAE バイパス用の torch/diffusers を追加します)。
Windows では setup_synthid.ps1(-Dir、-Ref、-Full)を使用してください。これは venv を .venv\Scripts\ に作成します — これは image_meta.py が os.name == "nt" の場合に既に探すレイアウトです。
オプション 2: ローカル Docker ビルド```bash
make docker-synthid-build
Run unprivileged and with a read-only rootfs; the scorer only needs to read
/data and write to stdout/tmp.
docker run --rm
--user "$(id -u):$(id -g)"
--read-only --tmpfs /tmp
-v "$(pwd):/data"
watermarks-remover-synthid-scorer /data/shot.png
イメージはビルド時にアップストリームソースからローカルでビルドされます。公開されていないため、アップストリームコードを再配布することはありません。
### オプション 3: HTTP スコアラーサイドカー (docker compose)
`heavy` プロファイルでは、compose スタックはスコアラーを HTTP サイドカー (`wr-synthid-score`) としても実行するため、**公開されているコアサービス**が非商用のアップストリームコードをバンドルすることなく、クリーニングの前後に画像をスコアリングできます。`wr-core` をそのサイドカーに向け、ベアラーキーを共有してください (`.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
その後、{"options": {"detect_before": true, "detect_after": true}} を指定した POST /clean は、レポートに synthid_before / synthid_after を返し、画像に対する POST /detect は SynthID スコアを返します。フェイルソフト: サイドカーが停止しているか未設定の場合、レポートには
{"available": false, "error": ...} が含まれ、クリーニングは引き続き成功します。
V4 スコアリングは、アップストリームのチェックアウトにある artifacts/spectral_codebook_v4.npz
(`220 MB) を使用します。これは検出/スコアリングのみであり、ピクセル透かしを除去するものではありません。
オプションの CtrlRegen ピクセル除去
ピクセル領域の画像透かし (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 は、新規クローンだけでなく既存のチェックアウトでも固定コミットを再検証します。
ブートストラップ```bash
SCRIPTS=service/scripts
Clones upstream (pinned commit), creates a venv, installs torch + deps.
"$SCRIPTS/setup_ctrlregen.sh"
Standalone removal (default checkout: ~/noai-watermark).
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 の wheel インデックスを調査し、`nvidia-smi` が出力する CUDA バージョン以下で実際に存在する最も高いものを選択します — その番号は*ドライバー*がサポートする最大値であり、ドライバーは後方互換性があるため、ドライバーが 13.1 を報告しても(公開された `cu131` は存在しない)、`cu130` をインストールします。コンピュート能力 7.5 未満では `cu126` を強制します。これは Maxwell/Pascal/Volta カーネルをまだ含む wheel を持つ最後のインデックスです。そのインデックスから `torch` **および** `torchvision` を同時にインストールするため、依存関係のインストールによって PyPI の CPU ビルドに置き換えられることはありません。その後、インストール後に `torch.cuda.is_available()` が true であることを検証します — 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 によるピクセル除去、そして
オプションで逆 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)。
画像サイズ(512×512 ネイティブ上限)
CtrlRegen は 512×512 の Stable Diffusion 1.5 ControlNet です。バックエンドは 任意の入力に対してこれを解決するため、ここでは追加のタイリングは公開されていません:
- ≤512 px: シングルパス — 512 にセンタクロップ/リサイズ、再生成、元に戻す。
- >512 px: 自動オーバーラップタイリング(512 px タイル、192 px オーバーラップ)、 幅/高さを 8 の倍数に整列、その後コサインブレンドでシーム処理。
- いずれのパスでも: 出力は元のサイズにリサイズされ、元の画像にカラーマッチングされます。
非常に大きな画像(例: 4K)は多数のタイルを生成するため、実行はタイル数に比例して スケールします(遅くなり、VRAM 使用量も増加)。実用的な場合は大きな入力を事前に ダウンスケールしてください。タイルサイズとオーバーラップは上流でハードコードされており、 フラグとして公開されていません。
計算、ゲート付きモデル、検証
約 10 GB のモデルダウンロードが見込まれます。GPU を強く推奨します。CPU 実行は
遅くなります。一部の上流モデルはゲート付きであるため、HF_TOKEN をエクスポートしてください
(環境変数のみ — 決して argv にはしない)。clean_ctrlregen.py は依存関係の自動インストールを
拒否します。先に setup_ctrlregen.sh を実行してください。
StegaStamp/Tree-Ring/StableSignature 用のローカル検出器は存在しないため、
唯一のローカルシグナルは逆 SynthID スコア(サロゲート)です。利用可能な場合、
clean_image.py --remove-pixel ctrlregen はそのスコアを前後で報告します。
公式の Google SynthID チェックが最終的な権威であり続けます。
Docker```bash
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` は固定された
コミットでアップストリームをクローンし、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 書き換え前後の検証: rewrite_text.py に --markllm-scheme を渡すと(--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`)。各バリアントは1回のリライト呼び出しと1回の評価で構成され、ラウンドは評価器が透かしなしと報告した最初の試行で早期終了します — したがって `--max-loops` を上げると、評価を通過するまで新しいバリアントを再試行します(典型的なクリーンなリライトは1回の試行で済みます)。評価器は優先度によって選択されます:
1. **MarkLLM** — `--markllm-scheme` が渡された場合(`--markllm-dir` と共に)の同一設定の研究用検出。ベンダー検出器スロットは、Googleが2026年8月にAPIで廃止したGoogleのSynthID-text検出器のためにMarkLLMの上に予約されており、将来のベンダーエンドポイントはそこに接続できます。
2. **bigram-Jaccard 字句的乖離** — 検出器が設定されていない場合。合否判定はないため、すべての試行が生成され、最も字句的に乖離したものが選択されます(元の動作)。
`--json-stats` は評価器、実行された試行回数、合否、および試行ごとの記録を報告します:```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"}
}
未設定、タイムアウト、またはエラーが発生した検出器は、error 理由を持つ "available": false エントリを生成し、リライトを失敗させることはありません。その試行は単に合格できず、ループは字句的乖離の選択にフォールバックします。合格せずに最大回数を使い果たした場合、最も透かしが少ない(最低スコアの)試行が、注記付きのベストエフォートとして返されます。
バックエンドが未設定、またはその依存関係が欠落している場合、リライトは続行され、レポートには検証が利用できなかったことが記録されます。GPU を推奨します。CPU 実行も動作しますが遅く、モデルのダウンロードは数 GB になります。
ハードニング用の設定項目:
- アダプタ(または任意の MarkLLM 実行)の
--offlineは、Hugging Face キャッシュのみからスコアリングモデルをロードします — ネットワーク送信はゼロ。キャッシュされていない場合は即座に失敗します。 カスタムリモートコードは決して実行されません(transformers のtrust_remote_codeは決して有効化されません)。 WATERMARKS_MARKLLM_RLIMIT_AS=<bytes>(環境変数、POSIX)は、MarkLLM 検出器サブプロセスにアドレス空間制限を適用します。torch/CUDA は通常大きなアドレス空間を必要とするため、デフォルトではオフです。- 設定ファイルは 1 MiB に制限されています。アップストリームのチェックアウトとベースイメージは SHA/ダイジェストで固定されています。
Docker```bash
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` として提供しています(標準ライブラリのみ。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 を参照)。
検出誘導リライト: rewrite_text.py に --gumbel-key を渡す(環境変数: 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
鍵は stats やログには決して現れない。自身のエンジンの鍵を保持するセルフホスト運用者は、書き換えによって Gumbel マークがクリアされたことを検証できるが、それ以外の全員にとって Layer B はベストエフォートに過ぎない。
## オプションの SynthID-text 除去ベンチマーク
[`bench_synthid_text.py`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/service/scripts/bench_synthid_text.py) は、Layer B の書き換えが SynthID-text クラスのウォーターマークをどれだけ効果的にクリアできるか、そしてどの程度のコストがかかるかを測定する。MarkLLM の SynthID スキーム(同一設定での検出、サニティゲート付き)でウォーターマーク付き + ウォーターマークなしのサンプルを生成し、あなたの書き換えバリアント(tactic × 最大書き換え試行回数。パスするとループは早期終了する)とコントロール(除去なし、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)。
MarkLLM のチェックアウト(`setup_markllm.sh` / `MARKLLM_DIR`)と書き換えバックエンドが必要である。**書き換えモデルはあなたが設定する LLM である** — スキルが使用するのと同じ `rewrite_text.py` バックエンドである。MarkLLM のデフォルトの `facebook/opt-1.3b`(`--markllm-model`)はウォーターマークの生成器/検出器に過ぎず、決して書き換えは行わない。書き換えモデルは環境変数またはベンチマークフラグで設定する(これらは上記の[設定テーブル](#configuration-env-vars-for-docker-compose)を反映している):
| 環境変数 | ベンチマークフラグ | デフォルト | 意味 |
| --- | --- | --- | --- |
| `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 のデフォルトはループバック |
| `WATERMARKS_REWRITE_API_KEY` | `--rewrite-api-key` | — | API キー(子プロセスでは環境変数のみ、argv には決して含めない) |
| `WATERMARKS_REWRITE_ALLOW_REMOTE=1` | `--rewrite-allow-remote` | off | 非ループバックエンドにコンテンツを送信するために必要 |```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 はこれを測定します。
オプションの MarkDiffusion 画像透かしハーネス
画像に対する制御された実験のために、オプションの外部ハーネスが THU-BPM/MarkDiffusion(Apache-2.0)をラップします。これは潜在拡散モデル向けの生成的透かしツールキットです(マークを埋め込むもので、除去するものではありません)。これを次の3つの目的で使用します:
- 検証ハーネス(MarkLLM と同様だが画像向け):テスト画像にスキームで透かしを入れ、除去を実行し、同じスキーム設定で再検出します — 例えば、Tree-Ring クラスのマークがあなたのパイプラインで消去されることを証明します。これは検証ハーネスであり、オラクルではありません:検出には生成モデル(およびキーベースのスキームではキー)が必要なため、任意の画像に対してベンダーの検出器が失敗することを証明することはできません。
- オプションのピクセル除去エンジン:その
DiffusionPurification再生成攻撃はclean_image.py --remove-pixel diffusionとして公開されており、CtrlRegen の代替です。これはブラインド再生成(ControlNet コンディショニングなし)であるため、CtrlRegen よりも画像コンテンツがドリフトします — 保守的な強度デフォルト(0.3)で、フォールバック/比較として扱われ、決して保証とはなりません。 - Tree-Ring クラスのマーク用のローカル同一スキーム検出器で、「StegaStamp/Tree-Ring/StableSignature 用のローカル検出器がない」というギャップを部分的に埋めます(Tree-Ring/Ring-ID/Gaussian-Shading などをカバーし、StegaStamp / StableSignature / SynthID-media はカバーしません)。
バックエンドはバンドルされていません。setup_markdiffusion.sh は venv を作成し、PyPI から markdiffusion==1.0.2 を(ピン留めして)インストールし、torch は適切なプラットフォームインデックスからインストールします。--checkout は代わりにピン留めされたコミットで編集可能なクローンをインストールします。Stable Diffusion モデル(デフォルト huanzi05/stable-diffusion-2-1-base)は初回実行時に Hugging Face からダウンロードされます。```bash
SCRIPTS=service/scripts
Bootstrap (PyPI pin default; creates ~/markdiffusion/.venv, installs deps).
"$SCRIPTS/setup_markdiffusion.sh"
1. Generate a Tree-Ring watermarked image (+ unwatermarked control).
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
2. Remove with the DiffusionPurification regeneration attack.
MARKDIFFUSION_DIR=~/markdiffusion
~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" purify
wm.png -o wm.purified.png --purification-intensity 0.3 --json
3. Re-detect with the SAME scheme config.
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
Hardening の調整項目は MarkLLM ハーネスを反映しています。--offline は Hugging Face キャッシュのみからモデルをロードし(ネットワーク送信ゼロ、リモートコードなし)、HF_TOKEN は環境変数のみ(argv には決して渡さない)、アルゴリズム設定は 1 MiB に制限され、サブプロセスには CtrlRegen と同じより高いリソース上限が適用されます。
Docker```bash
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ハブにアクセスします。
## カバレッジマトリクス
| チャネル | Claude | Gemini/SynthID | OpenAI | Open-LLM |
| --- | --- | --- | --- | --- |
| Unicode / 編集ベースのテキスト | レイヤーA | レイヤーA | レイヤーA | レイヤーA |
| **統計的サンプリングテキスト** | レイヤーB ベストエフォート(Anthropicの検出APIが提供開始された際のClaudeシーム) | レイヤーB ベストエフォート(+ MarkLLM同一設定ハーネス。Googleは2026年8月にベンダー検出器を廃止) | 存在する場合はレイヤーB | レイヤーB ベストエフォート + オプションの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/propsを除去します。
ベンダーが公開検出器とキーを提供するまで、**どのツールも「これは公式チェックに失敗する」と正直に証明することはできません**。レポートは検証可能な作業とベストエフォートの作業を区別しなければなりません。
レイヤーBには**非オリジン**モデルを推奨します(再スタンプを避けたい場合は、ClaudeのテキストをClaudeで書き換えないでください)。
---
## 免責事項: テキストウォーターマークの除去にかかるコスト
テキストウォーターマークは**文言そのもの**に存在します。シグナルはトークンの選択に分散しているため、ほぼすべての文がその一部を運んでいます。そこから二つの帰結が導かれ、これがレイヤーBが魔法の消しゴムではなく*ベストエフォート*と正直に説明される理由です。
1. **除去とは再構成ではなく言い換えを意味します。** 段落を並べ替えたり、見出しを変えたり、軽い手直しをしても、シグナルはほとんど動きません。統計的マークを剥がすには、テキストのかなりの割合を — セクション単位ではなく文単位で — 書き換える必要があります。
2. **言い換えはコピーを劣化させます。** どんな書き換えも、元の語の選択を書き換えモデルのものに置き換え、トーン、声、精度を平坦化します。制作コピー(SEO、マーケティング、クライアントワーク)では、その劣化は現実のものであり、文章を最も気にかける人々にしばしば見えてしまいます。これは、トップティアのモデルからテキストを取り、より能力の低いモデルにゼロから書き換えさせるようなものです。結果は書き換えモデルの上限を超えることはできません。
そこから、正直な堂々巡りの問いが導かれます:
> どうせ安価なモデルでテキストを書き換えるつもりなら、そもそもなぜプレミアムモデルにお金を払うのか? 安価なモデルで直接生成する方が、よりシンプルで安価であり、同じ — あるいはより良い — 最終結果を生み出します。
レイヤーBは、プレミアムモデルの**思考と下書き**を特に求め、衛生面やプライバシー要件を満たすために書き換えパスを受け入れる場合に意味があります — マークのないテキストへの安価な道としてではありません。
**レイヤーBをスキップすべき場合:**
- **品質が衛生面より重要:** ロスレスパス — レイヤーAのUnicodeスクラブとファイルメタデータクリーナー — を使い、元の文章を保ちます。
- **どうせ書き換える:** **非オリジン**モデルを使い(オリジンモデルで書き換えるとテキストが再スタンプされる可能性があります)、残留リスクは残ることを忘れないでください — どのツールもベンダー検出器が失敗すると証明することはできません。
---
## ファイルフォーマット
| フォーマット | 検査 | クリーニング |
| --- | --- | --- |
| PNG / JPEG / WebP | C2PAチャンク / APP11 / RIFF `C2PA`、AI XMPヒント | メタデータセグメントを削除 |
| AVIF / HEIC | ISOBMFF `jumb` / XMP `uuid` ボックス | ボックスを削除 |
| BMP | 末尾の非画像バイト(標準化されたチャネルなし) | 末尾メタデータを切り詰め、ファイルサイズフィールドを修正 |
| GIF | コメント / XMPアプリケーション拡張 | コメント & XMPを削除、`NETSCAPE2.0` ループを保持 |
| TIFF(クラシック + BigTIFF) | IFDタグ: XMP、EXIF、GPS、IPTC、MakerNote | タグを削除、ペイロードをゼロ化、ストリップを保持 |
| SVG | `<metadata>`、XMP | ブロックを除去 |
| PDF | バイト/XMP + オプションツール | **exiftool** の後に **qpdf**、次に埋め込み画像内のメタデータ用に **ghostscript**。各ツールが欠けると異なるレイヤーが劣化(ドキュメントストリップ、構造的書き換え、埋め込み画像) |
| DOCX | docProps / customXml | propsをスクラブ、customXmlを削除 |
| EPUB | OPFメタデータ、XHTML meta/JSON-LD、埋め込みメディア | OPFをスクラブ、XHTMLメタを除去、メディア + レイヤーAをクリーニング(暗号化部分はスキップ) |
| ODT | meta.xml | 生成器 / AIっぽいメタを削除 |
| HTML | meta、JSON-LD、data-ai* | タグ/属性を除去 |
| Markdown | YAMLフロントマターの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 | ID3v2 `GEOB` フレーム内のC2PAマニフェスト | 一致したフレームまたはID3v2タグ全体を削除 |
FLACサポートはC2PAの標準化されたID3v2キャリアをカバーします。ネイティブFLACメタデータブロック、
Vorbis Comments、波形ドメインのウォーターマークはそのままにされます。
#### PDFにexiftoolだけでなくqpdfが必要な理由
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
なぜ qpdf だけでは PDF 内の画像に不十分なのか
上記の両パスはドキュメント上で動作する。Info 辞書、XMP パケット、オブジェクトグラフ。どちらも画像 XObject には踏み込まないため、スキャンや Photoshop の書き出し — ページそのものが一枚の大きな JPEG であるもの — は、画像が抱えているものをそのまま保持してしまう。実際の Photoshop 書き出し PDF では、「成功した」クリーン後も 27 個のタグが残り、その中には IFD0:Software、キャプチャタイムスタンプ、プレビューサムネイルが含まれる。同じ画像に添付された C2PA マニフェストも同様に生き残る。
そこで clean_pdf は第三のパス deep_images を追加する。これは Ghostscript の pdfwrite によって駆動される。二段階で実行され、ファイルがクリーンになった時点で停止する:
- ロスレス。 パススルーを有効にした
pdfwriteは、圧縮された画像データをバイト単位でそのままコピーしながらオブジェクトグラフからドキュメントを再構築する — これは前後でストリームをハッシュ化することで検証される。これにより、PDF が画像の周囲にまとわりつかせていたものはすべて除去される。パススルーは Ghostscript が対応するコーデック、すなわち JPEG (DCTDecode) と JPEG2000 (JPXDecode) をカバーする。Flate、CCITT、LZW の画像はデコードおよび再エンコードされ、これらのコーデックでは実用上ロスレスだが、バイト単位で同一にはならない。ストリームを一切変更せずに残さなければならないドキュメントにはneverが適している。 - 再エンコードは、証拠がある場合のみ。 JPEG 自身の APPn セグメント — APP1 の EXIF、APP11 の C2PA マニフェスト、APP13 の Photoshop リソース — に存在するものは、それが添付されているバイト列とともに移動するため、パススルーでは保持される。第 2 段はパススルーを無効にして同じパスを実行し、第 1 段が明らかに何かを残した場合にのみ実行される: いずれのモードでも AI/C2PA マーカーが残った場合、または
alwaysの下で何らかの APPn メタデータが残った場合。APP0 (JFIF) と APP2 (ICC) はそのままにされる — 前者は構造的であり、後者は色の読み取り方を決定する。ピクセルは証拠に対して費やされ、疑念に対して費やされることは決してない。
deep_images は auto (デフォルト: マーカーがドキュメントストリップを生き残った場合にのみ第 1 段を実行し、その後それらが生き残った場合に第 2 段を実行)、always (すべての PDF に対して第 1 段を実行し、カメラおよびエディタの EXIF に対しても第 2 段へエスカレート)、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 / EXIF / XMP | はい | ソフトバインド / ピクセルマーク | c2patool、Content Credentials verify |
| SynthIDクラスのメディア | オプションのピクセル除去(外部CtrlRegen)、それ以外はローカルスコア | 音声/動画ウォーターマーク、除去後の残存ピクセルウォーターマーク | プロバイダーツール(例:Google SynthID / 提供されている場合はVertex検出器)、オプションのローカルreverse-SynthIDスコアラー |
| 統計的テキスト | ベストエフォートの書き換え | 軽微な編集後の強いマーク | 公開された汎用検出器はなし、利用可能な場合はベンダーツール |
業界の2層コンテキスト(C2PA + 知覚不能なウォーターマーク):Institute of AI PM guide。
ウォーターマーク検出器
コンテンツにAI由来のマークが付いているかを検証するためのベンダー提供チェッカー:
- Claude: Check if a file was made with Claude — 画像、動画、音声のC2PAコンテンツクレデンシャルを読み取り、Claudeがファイルの生成に関与したかどうかを報告します。ブラウザ内で実行されます。Claudeのテキストウォーターマーク検出APIは現在プライベートプレビュー中です。
- OpenAI: Verify OpenAI-generated content — 画像または音声ファイルをアップロードし、OpenAIの来歴シグナル(C2PAメタデータとSynthIDウォーターマーク)をチェックします。プログラムAPIも利用可能です。
- Google DeepMind: SynthID — AI生成の画像、音声、テキスト、動画向けのGoogleのウォーターマーク技術で、知覚不能なマークがどのように埋め込まれ、検出されるかの概要を説明しています。
- Gemini: Verify AI-generated images, videos, and audio — SynthIDウォーターマークとContent Credentialsを使用してGeminiアプリ内のファイルを検証するためのGoogleのガイドで、アップロード制限や結果の読み方も含まれます。
除去オプション(概要)
| オプション | 除去するもの | 備考 |
|---|---|---|
| Unicodeスクラブ(Layer A) | ZWSP、bidi、タグ、特殊スペース、… | テキストの安全なデフォルト |
| 書き換え(Layer B) | 統計的トークンマーク(ベストエフォート) | スキルによって常に提供される。スタイルにコストがかかる — 免責事項参照 |
| コンテナ/メタデータ除去 | ファイルの来歴 | フォーマット表参照 |
| CtrlRegenピクセル除去(オプション) | ピクセル領域の画像マーク(SynthIDクラス、StegaStamp、Tree-Ring、StableSignature) | 外部バックエンド、重い計算、保守的な強度がデフォルト |
| DiffusionPurificationピクセル除去(オプション) | ピクセル領域の画像マーク(Tree-Ringクラス) | MarkDiffusionバックエンド、ブラインド再生成(CtrlRegenよりドリフトが大きい)、保守的な強度がデフォルト |
| オープンウェイトのローカルモデル | 元のモデルでの再スタンプを回避 | 運用上の代替手段 |
マトリクス:skills/remove-ai-marks/references/removal-matrix.md。
倫理と免責事項
skills/remove-ai-marks/references/ethics.mdを参照してください。あなたのコンテンツに関するプライバシーと研究のためであり、学術的な不正や虚偽の「人間が書いた」という主張のためではありません。
責任ある使用: このプロジェクトは、あなたが所有するか、処理する権限を持つコンテンツを対象としています。ユーザーは現地の規制を遵守し、責任を持って使用する必要があります。開発者は、ユーザーによる潜在的な誤用について一切の責任を負いません。
エコシステム
このリポジトリをラップまたは補完するサードパーティプロジェクトで、発見しやすさのためにのみ掲載しています。これらはこのプロジェクトによって保守、承認、サポートされていません。 このプロジェクトは、それらのコードをレビューしたり、その動作や保証を保証したり、このリストからインストールまたは実行するものについて責任を負いません。各プロジェクトは独自のライセンス、メンテナー、ドキュメントによって管理されています — 使用する前にそれらを読んでください。
MetaClean — デスクトップGUI
MetaCleanは、独立したMITライセンスのRust/Tauriデスクトップアプリケーション(Windows、macOS、Linux)で、ドラッグアンドドロップによるメタデータクリーニング用のパッケージ化されたネイティブGUIを提供し、システムトレイとエクスプローラー統合を備えています。これは別のコードベースです:このリポジトリのPythonサービスを呼び出さず、サポートするフォーマットとクリーニングの保証はこのプロジェクトとは異なります。詳細はそのREADMEを参照してください。
unmark-web — ブラウザWeb UI
unmark-webは、独立したMITライセンスの静的Webクライアントです。テキストから不可視のUnicodeマークを除去し、画像から来歴メタデータを完全にブラウザ内で除去し、ローカルで処理しないフォーマットについてはオプションでこのリポジトリのHTTPサービスを呼び出すことができます。これは別のコードベースであり、このプロジェクトとは提携していません。範囲と制限についてはそのREADMEを参照してください。
DropMarks — macOS GUI
DropMarksは、独立したMITライセンスのmacOS SwiftUIアプリケーションです。これらのstdlibスクリプトのベンダー化されたスナップショットを介して、このリポジトリのinspect_file.py / clean_file.py(およびオプションでrewrite_text.py)を呼び出します。これは別のコードベースであり、このプロジェクトとは提携していません。範囲と制限についてはそのREADMEを参照してください。
プロジェクトの追加
ここにプロジェクトを登録するには、短いエントリを追加するPRを開いてください — プロジェクト名、何をラップまたは追加するか、そしてその独自リポジトリへのリンクです。エントリは簡潔かつ事実に基づいたものにしてください。このプロジェクトとの互換性や承認を主張しないでください。掲載されるプロジェクトは、単に同じ問題に独立して対処するのではなく、このリポジトリを基盤とするか統合するもの(例えば、そのサービスを呼び出すか、その検出エンジンを再利用する)であるべきです。watermarks-removerで始まる、またはよく似た名前は避けてください — 似た名前はどのプロジェクトがどれかを区別しにくくします。
Pre-commitフック
CIゲーティングはすでに存在します(audit_dir.pyのSARIFエクスポート、カバレッジマトリクスのコンテキスト参照) — 以下のpre-commitフックは、マークされたファイルがコミットされる前に、同じクラスの問題をより早く捕捉します。どちらも既存のCLI(audit_dir.py / clean_file.py)をラップしています — 別個の検出ロジックはありません。```yaml
.pre-commit-config.yaml
repos:
- repo: https://github.com/guillaumemeyer/watermarks-remover
rev: v0.5.0 # pin to a tag/commit
hooks:
- id: watermarks-remover-check # fails the commit if marks are found
- id: watermarks-remover-clean # opt-in: cleans staged files in place instead
`watermarks-remover-check` はコミットを失敗させ、検出結果を一覧表示します。`watermarks-remover-clean` はオプトインであり、ステージされたファイルをその場で書き換えます(終了コード 1 を返すので、差分を確認して再ステージしてください — `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
Changelog
v0.7.0 — /clean Layer B の書き換え、ウォーターマーク窃取モジュール、音声/動画ウォーターマーク除去、ベンチマーク/ツール群の拡充
v0.7.0 では、Layer B の統計的マーク書き換えが /clean サービス自体に組み込まれ、設定可能でベンチマーク調整済みの戦略 ([email protected],[email protected]) によって駆動されます。あわせて、ブラックボックス型ウォーターマーク窃取モジュール、破壊的な音声およびフレーム単位の動画ウォーターマーク除去、大幅に強化された書き換えベンチマーク、そして一連の堅牢化・セキュリティ・ツール修正が含まれます。
サービス内での Layer B 書き換え
/cleanは Layer A の後にテキストの Layer B 書き換えを実行します。デフォルトはconfig/clean_strategy.jsonから取得され、リクエストごとのoptions.strategyで上書きできます。必要なバックエンドが設定されていない場合、/cleanは 400 で拒否します (#315)。設定の優先順位:--strategy-config>WATERMARKS_CLEAN_STRATEGY_FILE>config/clean_strategy.json。- 新しい
mlm書き換え戦術: 内容語の一部をマスクし、roberta-largeで穴埋めします — 非自己回帰的な局所編集であるため、出力は元のトークン列とマスク言語モデルの予測が混ざったものになります (#311)。 humanize戦術は humanizer-skill パスを決定論的に適用するようになり (直線引用符、em/en ダッシュなし、フィラーの圧縮、utilize→use)、プロンプト内で human-writer ルールを明示します (#311)。rewrite_text.pyに--strategyCLI パスが追加されました。- 書き換えの正確性: 語彙的乖離における Unicode 単語トークン化 (#305)、丸め前に生のマージンを比較し、選択メタデータ / ランク付けされた p 値を記録 (#249)。
ベンチマーク
- SynthID レシピ探索 + ロバスト測定 (#280)、書き換え語彙の改名、クロス入力探索、humanize-last の順序付け (#302)、humanize 仕上げ後もなおクリアする戦略のみを推奨 (#307)。
- 人間らしさバックエンドとしての Pangram バルク API (#296)、30 文書コーパスによる堅牢化された最小書き換えレベルベンチマーク (#257)、検証済み重みグリッド + 拡張レシピ探索 (#294)、ポーランド語ベンチマークコーパス (#295)。
ウォーターマーク窃取
- 新しいブラックボックス型ウォーターマーク窃取モジュールとプロンプトコーパスダウンローダー (#303)、start-over プローブ失敗時に古い状態をクリア (#310)。
音声 / 動画 / 画像
- silentcipher/AudioSeal/WavMark 向けの破壊的音声ウォーターマーク除去チェーン (テンポ + ピッチ + EQ + 低ビットレート再エンコード → M4A) (#266)。
- 時間的投票を崩壊させるフレーム単位の TrustMark 動画浄化 (#265)。
- MP4/MOV/AVIF/HEIC で認識される C2PA コンテンツ来歴
uuidボックス (#264)。 - ストリッピング時に切り詰められた MP4 末尾を保持 (#242)、音声再エンコード先をコンテナクリーン先と別に保つ (#278)。
- クリーン後スキャンで破棄された exiftool 出力と冗長な SynthID をスキップ (#261)、exiftool が PDF を処理できない場合はクリーンに縮退 (#281)。
- 展開された PNG
zTXt/iTXtを 1 MiB に制限 (#308)、SVG XML DOCTYPE/ENTITY 宣言を除去 (#288)、DOCX バイナリメンバーをバイト安全に保つ (#314)、OOXMLAppVersionを保持 (#289)。
HTTP サービス & CLI
- CLI を反映した、特殊な空白を保持する
/cleanオプション (#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)。
セキュリティ
- data-URI および JSON-LD スキャンにおける多項式 ReDoS を除去 (#306)、SSRF を防ぐため SynthID スコアラーで HTTP リダイレクトをブロック (#252)。
CI、ツール & ドキュメント
- オプションのバックエンド要件が解決できない場合に CI が失敗 (#301)、Docker イメージが ffmpeg を使用可能として報告し Ghostscript をインストール (#272)、依存関係の更新 (cython #299、scipy #298、ruff #297、docker/setup-buildx-action #237)。
- ドキュメント: Watermark Detectors セクション、ETH SRI「Probing SynthID」ブログの参照、Ecosystem ポリシー (ClaudeWatermarks を削除、掲載プロジェクトにこのリポジトリの使用を要求) (#292)。
v0.6.0 — 対応フォーマットの拡大、Layer A の堅牢化、プラグイン & フックの配布、検出誘導型書き換え
フォーマット & コンテナ対応
- AVIF / HEIC: ネイティブ stdlib によるメタデータおよび C2PA ストリッピング (#84, #85)
- BMP / GIF / TIFF: stdlib による検出、検査、メタデータクリーニング — GIF のコメント/XMP 拡張は削除されつつ、
NETSCAPE2.0ループやその他のアニメーションチャンクは保持されます。TIFF の IFD メタデータ (XMP/EXIF/GPS/IPTC/MakerNote) はペイロードをゼロ化しストリップオフセットを保持したまま削除され、クラシック TIFF と BigTIFF の両方に対応します。BMP の末尾メタデータはファイルサイズフィールドを書き換えて切り詰められます (#107) - EPUB: stdlib によるコンテナクリーニング — OPF メタデータと XHTML の meta/JSON-LD をスクラブし、埋め込みラスター/SVG メディアを除去し、XHTML 本文テキストに Layer A を適用し、マーカーを含むメタデータパーツを削除し、OCF 暗号化パーツはそのまま通過させます (#107)
- XLSX / PPTX / DOCX (OOXML): ネイティブ stdlib によるコンテナメタデータ、テキスト、埋め込みメディアのスクラブ。DOCX の
docProps来歴フィールドは常に空にします。customXml削除後にぶら下がったリレーションシップを刈り込みます。DOCX/ODT 本文テキストに Layer A を実行します。Layer A スクラブ前に XML エンティティをデコードします (#91, #100, #76, #83, #73, #80, #74, #81, #142) - SGML/ベクターコンテナ: SVG/ODT 向けの線形時間メタデータストリッピング (GHSA-7vpp-96qp-j9wh) (#147)、SVG、HTML、Markdown 内の埋め込みラスターデータ URI を再帰的に検査・クリーニング (#87, #88)
- 音声 / 動画: MP4/MOV、WAV、MP3 向けの AI/C2PA メタデータストリッピング (#139)、WAV RIFF C2PA チャンクの検出と除去、FLAC C2PA メタデータサポート、部分的な ID3v2 フレーム解析の拒否 (#232)、メタデータストリッピング時に MP4 メディアオフセットを保持 (#183)
- PDF: 埋め込み画像内に存在するメタデータに到達し、XMP を除去するための PDF リサイズを停止。exiftool のインストール有無にかかわらずディープイメージパスを実行。JPEG マーカーのフィルバイトを尊重し、単一のセグメントウォーカーを共有
- PNG: PNG テキストメタデータ内の AI 生成製品名を検出。圧縮された PNG テキスト内の AI マーカーを検出 (#127)。png/isobmff ストリップで切り詰められた末尾を破棄せず保持 (#182)
Layer A (不可視 Unicode) の堅牢化
- Layer A 堅牢化の統合 (#133): 正当な相互運用用途のない予約済み
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として報告)、そしてCfの包括的処理が決して捕捉しなかった 3 つの空白描画 Default_Ignorable キャリア (U+180F、U+3164、U+FFA0) を除去します。それぞれ、すでに対応済みの同種のものと同じ文脈内保持を持ち、部分音節テキストが破損しないようにし、サービスエンジンとベンダリングされた軽量スキルコピーの両方に適用されます - 自身のスクリプトに隣接する可視レイアウトフォーマット制御のストリッピングを停止: エジプト象形文字のクアドラット制御 (
U+13430–U+1343F)、Duployan 速記制御 (U+1BCA0–U+1BCA3)、および楽譜のビーム/タイ/スラー/フレーズ制御 (U+1D173–U+1D17A) は、自身のスクリプトに隣接する場合は保持され、無関係なテキスト間に浮いている場合は依然として除去 (およびフラグ付け) されます。--strip-emoji-glueパラノイドモードでは依然としてどこでも除去されます - 絵文字 / スクリプトの仕上げ: ブロック範囲外の絵文字単体の後の VS16 を保持。スクリプト結合子、旗絵文字、アラビア語 Cf マークを保持。テキストクリーンアップ中の多言語 Unicode を保持 (#34)
Layer B 書き換え & ウォーターマーク検出
- 反復的、検出誘導型の 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) - Keyed-Gumbel (Aaronson EXP) 同一キー検証: 新しい stdlib のみの
detect_gumbel.pyがモデル不要のリプレイテスト (u = PRF(Hash(key, window), token)、正確な Gamma テール p 値、繰り返しウィンドウマスキング) を GPU、モデル、ロジットなしで実装します。rewrite_text.py --gumbel-key(環境変数WATERMARKS_GUMBEL_KEYが優先) はこれを反復ループ評価器にし (優先順位: gumbel > markllm > 語彙的乖離)、/capabilitiesと/detectでgumbelとして公開されます。同一キー専用 — ベンダーオラクルではなく、キーは決してログに記録されません (#190) - ベンチマーク: マルチスキーム MarkLLM テキストベンチマークと検出 (#188)、および再現可能な SynthID テキスト除去ベンチマーク (#145)。デフォルトバリアント
paraphrase:3。レポートと CSV が文書ごとの試行回数を保持 (mean_attempts/att、attempts/evaluator/passed列)。--rewrite-loopsは--max-loopsを反映 - 検出: ベンダーテキストウォーターマーク検出 (Gemini SynthID、Claude seam、MarkLLM) と SynthID 画像スコアラーサイドカー (#109)、CI と監査向けの新しいゼロ LLM 統計・文体計量 AI テキスト検出器 (#68, #69)
配布: プラグイン、フック、スキルのインストール
- リポジトリは Claude Code プラグインかつ単一プラグインマーケットプレイスになりました (
.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は CLI なしでマニフェストをチェックします install_skill.pyに--targetが追加されました (claude-code、claude-project、cowork、cursor) および両方の同梱スキルをカバーする--skillセレクタ、さらに--list、--link、CLAUDE_CONFIG_DIR。coworkターゲットは再現可能なアップロードバンドル (dist/<skill>.zip、単一のトップレベルスキルディレクトリ) をビルドします。すべてのターゲットは Agent Skills パッケージングルールと 30 MB のアップロード制限に対して検証されます。新しいmakeターゲット:install-claude-code-skill、install-claude-code-text-skill、install-claude-project-skill、package-cowork-skill、package-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 エクスポートが一致します。フックは依然としてアシスタントのチャットメッセージを書き換えることはできません — そのようなフックポイントは存在しないため — そのパスはベストエフォートのままです- ステージングされたファイルのチェック/クリーニングのための pre-commit フック統合 (#138)、軽量 Cursor テキストスキル (#35)、
clean-user-facing-textの説明が Cursor を唯一のホストとして挙げなくなりました
HTTP サービス
- バッチエンドポイント:
POST /clean/batch、/inspect/batch(#137) およびPOST /detect/batch(#151) /cleanで画像フォーマット拡張子を保持し、av_metaで安全な書き込みを使用 (#150)、/detectの curl 例でポータブルな base64 を使用 (およびブートストラップの macOSrealpathポータビリティを修正、#185)
監査 / 検査 & セキュリティ
audit_dir.pyにマルチワーカー並行処理と SARIF 2.1.0 エクスポートが追加されました (#101, #102)- ウェブサイトのバイナリフォーマットを実際のスキャナーにルーティング (#177)、サイトマップパーサーで DTD/エンティティ爆弾を拒否 (GHSA-pjg6-92pm-mmcf) (#146)、クラッシュしたクリーナーはクリーンとして読まれるのではなくコミットをブロック (#179)、読めないテキストファイルはクリーンではなく失敗したスキャン (#169)
信頼性 & 正確性の修正
- 2 回目の
--in-place実行で元の.bakを保持。後の zip メンバーの読み取りが失敗した場合に収集された証拠を保持 (#175)、切り詰められた ISOBMFF コンテナでも C2PA バイトスキャンフォールバックを実行 (#176)、失敗したクリーナーとすでにクリーンなファイルを区別 (#159, #161)、失敗した c2patool 実行を「C2PA なし」ではなく不確定として扱う (#156)、クリーンオプションの型を検証 (#111)、テキストウォーターマーク検出で MPS デバイスを自動選択しない (#99)、macOS ポータビリティ — SynthID スコアラーの純粋な--jsonstdout と BSDrealpathプローブ (#70)、_ghostscript_usableの Windowssubprocess_creationflagsパスを修正し、Windows で子プロセスがコンソールウィンドウを開かないようにする - 動作の堅牢化: 良性の JPEG コメント keep-mode を保持、
bench-synthid-textの飲み込まれたフラグを修正、Ghostscript プローブのフラグパススルーと clean_text の不要な noqa を簡素化 (lint)
CI / ツール / ドキュメント
- CI 強制による Ruff リンティングとフォーマット (#103)、テストマトリックスに macOS を追加 (#152)、自動 PR レビュー用の CodeRabbit 設定を追加 (#222)、CODE_OF_CONDUCT/LICENSE およびメインレビューオーナー向けの CODEOWNERS、著作権を Guillaume Meyer と貢献者に帰属 (#228)
- ドキュメント: 声を保持する書き換えガイダンスと声/アクセシビリティの選択肢を保護する方法、Ecosystem の追加 (ClaudeWatermarks、unmark-web) と類似名を推奨しない注記、arXiv 2402.14904 の参照、Task Scheduler 経由の Windows 自動起動ガイド、curl 例でのポータブル base64、ベンダリングされた Cursor-skill テキストエンジンをサービスコピーに固定 (#96)
Unreleased
- Pre-commit クリーンフック (
watermarks-remover-clean/clean_staged.py): コンテンツダイジェスト (SHA-256) とアクティブアクション検出を使用し、無限の再ステージングを要求することなくディスク上のクリーンなファイルを認識 (#173) - OOXML コンテナ保持: ECMA-376 スキーマ制約を満たし、Microsoft Word/Office の「読み取れないコンテンツ」エラーを回避するため、DOCX、XLSX、PPTX のメタデータクリーニング中に
docProps/app.xmlの<AppVersion>をそのまま保持 (#283)
v0.5.0 — サービス & Docker 配布、HTTP API、検証ハーネス
サービス / Docker 配布
- スキル/サービスの分割: スキル (
skills/remove-ai-marks/) は HTTP 経由のコード不要のリモートクライアントになりました。すべての実装はservice/scripts/に移動し、stdlib HTTP エントリポイントであるserver.py(/health、/inspect、/clean、/capabilities) の背後で実行されます - HTTP サービス:
service/scripts/server.pyが JSON/base64 経由でクリーニングパイプラインを公開します。堅牢化は CLI を反映 (サイズ上限、バイナリガード、アトミック書き込み、デフォルトでループバック、オプションのWATERMARKS_SERVER_API_KEYベアラ認証) - OpenAPI:
GET /openapi.jsonが動的に生成された OpenAPI 3.0.3 仕様を提供します (ルートテーブル + ライブ設定から構築されるため、実際のエンドポイントから決して乖離しません)。CI はopenapi-spec-validatorで検証します - コア Docker イメージ (
service/Dockerfile): exiftool / qpdf / c2patool をプリインストールした完全なクリーニングサービス。コマンドを上書きすれば任意の CLI が実行可能なままです - Docker / compose:
compose.yamlがインフラ全体を起動します (coreは常時、markllm/markdiffusionはprofile: harnessの背後、ctrlregen/synthidはローカル専用ビルドとしてprofile: heavyの背後)。サービスにはwr-が接頭辞として付きます。harness/heavy サービスはデフォルトでcommand: ["--help"]になるため、docker compose up --profile harness --profile heavyはクリーンに終了します (ワンショット CLI はdocker compose runで実行)。新しいmake compose-check/compose-check.shが実行中のスタックを検証します (終了コードのみ) - GHCR 公開:
.github/workflows/release-images.ymlがv*タグでcore、markllm、markdiffusionイメージを公開します。ctrlregen/synthidは決して公開されません (上流のライセンス) - 環境設定:
.env.example+ サービス設定ガイド。docker composeは.envを自動ロードします。.envは gitignore されます (デフォルト拒否) - リポジトリ衛生:
.gitignoreとservice/.dockerignoreはデフォルト拒否になりました — 明示的に許可されたパスのみがコミットまたはビルドコンテキストで送信できます (イメージコンテキストはservice/scripts/のみを出荷し、これは Dockerfile が COPY するすべてです) - テスト: HTTP サービス用の
tests/test_http_server.py(13 ケース)。すべてのスイートはservice/scripts/を指すように変更
MarkDiffusion 画像ウォーターマークハーネス (オプション)
- 新しいオプションハーネス (外部
THU-BPM/MarkDiffusion、Apache-2.0): 9 つの画像スキーム (Tree-Ring、Ring-ID、ROBIN、WIND、SFW、Gaussian-Shading、GaussMarker、PRC、SEAL) 用のwatermark/detect/purifyサブコマンドを備えたmarkdiffusion_harness.py clean_image.py --remove-pixel diffusionが代替ピクセル除去エンジンとして MarkDiffusion のDiffusionPurification再生成攻撃を実行します (保守的強度 0.3 がデフォルト)setup_markdiffusion.shブートストラップ (PyPI ピン1.0.2、--checkoutで固定コミットの編集可能クローン) +requirements-markdiffusion.txt+Dockerfile.markdiffusionおよび Makefilebootstrap-markdiffusion/smoke-markdiffusion/docker-markdiffusion-build/docker-markdiffusion-help- モックベースのテスト (
tests/test_markdiffusion_harness.py) — CI に torch なし。references/markdiffusion.mdリファレンスドキュメント - ドキュメント: 同一スキーム専用検証の注意事項 (ベンダー検出器オラクルではない) とブラインド再生成ドリフトの注意事項を README、SKILL.md、
removal-matrix.md、markdiffusion.mdに記載
MarkLLM テキストウォーターマークハーネス (オプション)
- 新しいオプションハーネス (外部
THU-BPM/MarkLLMチェックアウト、Apache-2.0): KGW および SynthID スキーム用のdetect/watermarkサブコマンドを備えたdetect_text_watermark.py rewrite_text.py --markllm-schemeが Layer B 書き換えの前後に検出を実行し、--candidates N>1の場合は候補ごとの検出を実行します (環境変数でゲート、clearedを報告)setup_markllm.shブートストラップ +requirements-markllm.txt(固定依存関係) +Dockerfile.markllmおよび Makefilebootstrap-markllm/smoke-markllm/docker-markllm-build/docker-markllm-help- 堅牢化:
--offlineキャッシュのみのモデルロード (HF 外向き通信なし、リモートコードなし)、1 MiB 設定上限、書き換えサブプロセスでのオプションのWATERMARKS_MARKLLM_RLIMIT_AS、Dockerfile での torch 固定、Dockerfile.markllmでのクローン SHA 検証 - モックベースのテスト (
tests/test_markllm_detect.py、21 ケース) — CI に torch なし。検証ハーネスの注意事項 (同一設定専用、ベンダー検出器オラクルではない) を README、SKILL.md、removal-matrix.md、vendor-notes.mdに記載
修正と仕上げ- Layer B: rewrite_text.py は openai-compatible バックエンドに対してデフォルトで reasoning_effort: "none" を送信するようになりました(--reasoning-effort / WATERMARKS_REWRITE_REASONING_EFFORT、off で省略)。deepseek-v4-flash のような推論モデルは、そうでなければ一行の書き換えに約100秒の思考連鎖を消費します(完了トークン数 9,894 対 12)
- markllm イメージビルドの修正:
requirements-markllm.txtがtokenizers==0.23.1を固定していましたが、これはtransformers==5.15.0と競合します(tokenizers<=0.23.0に制限されるが、0.23.0 のリリースは存在しない)— 現在はtokenizers==0.22.2に固定。torch は CPU ホイールインデックス(torch==2.13.0.*)に移動され、Dockerfile.markdiffusionと同様にイメージは CPU 専用になりました - ctrlregen イメージビルドの修正: 2023年頃の研究用ピン(
safetensors==0.4.3、transformers==4.37.2→tokenizers<0.19)には Python 3.14 ホイールが存在しないため、ベースイメージはpython:3.11-slim(ダイジェスト固定、マルチアーキテクチャ)になりました - 実行時のハーネスイメージの修正:
Dockerfile.markllmとDockerfile.markdiffusionはcommon.pyを/appにコピーしていませんでした(既存のバグ)— 追加しました - WebP: RIFF の
C2PA、XMP、EXIF、ICC プロファイルチャンクに対する標準ライブラリのみの検査とメタデータクリーニング(#37) - BMP / GIF / TIFF: 標準ライブラリのみの検出、検査、メタデータクリーニング — GIF のコメント/XMP 拡張は削除されますが
NETSCAPE2.0ループは保持されます。TIFF の IFD メタデータ(XMP/EXIF/GPS/IPTC/MakerNote)はペイロードをゼロ埋めしストリップオフセットを保持したまま削除され、クラシック TIFF と BigTIFF の両方に対応。BMP の末尾メタデータはファイルサイズフィールドを書き換えて切り詰められます - EPUB: 標準ライブラリのみのコンテナクリーニング — OPF メタデータと XHTML の meta/JSON-LD をスクラブし、埋め込みラスター/SVG メディアを除去し、XHTML 本文テキストに Layer A を適用し、マーカーを含むメタデータパーツを削除し、OCF 暗号化パーツはそのまま通過させます
- ファイル名のサニタイズ: HTTP サービスはクライアント提供の安全でない出力名を拒否します
- markdown frontmatter クリーナーの修正: ネストされた AI キーでクラッシュしリークする問題(#25)
- テキストツールはバイナリ入力を拒否;
--force-textで上書き(#24) --jsonが残余シグナルの終了コードを抑制しなくなりました(#30)inspect_fileが出力にファイル名を表示(#50)- 大文字小文字混在の CMS generator メタタグを保持(#42)
- Layer A で負荷のかかるスクリプトの不可視文字を保持し、PUA を除去(#38、#52)
- Layer A でスクリプト結合子、国旗絵文字、アラビア語 Cf マークを保持(#28)
- ウェブサイト監査を SSRF と gzip 爆弾に対して強化(#49)
- SECURITY.md はプライベートアドバイザリチャネルのみを参照(#51)
- Windows: セットアップブートストラップの PowerShell 移植版(#40)
- ドキュメント: stars/forks シールドを追加し star-history チャートを削除。README 参照に MarkLLM を追加。プルリクエストテンプレート。Docker CLI + API デプロイの計画
v0.4.0 — ピクセル除去、検出信頼度、Windows & 誤検知の修正
オプションの CtrlRegen ピクセル除去(外部バックエンド)
- 外部の
mertizci/noai-watermarkチェックアウトを介したオプションのピクセルドメイン透かし除去:clean_ctrlregen.pyアダプタ +setup_ctrlregen.shブートストラップ(コミット固定、スパースチェックアウト、venv、SHA 検証)、さらにDockerfile.ctrlregenとmake bootstrap-ctrlregen/docker-ctrlregen-build/smoke-ctrlregen clean_image.py --remove-pixel ctrlregenはメタデータ除去 → CtrlRegen 除去 → オプションの reverse-SynthID 前後スコアを実行します。inspect_image.pyは高い SynthID スコアでこのフラグをヒント表示します- 保守的なデフォルト強度
0.25(プリセット 0.15/0.25/0.35/0.5/0.7)。512×512 ネイティブのパイプラインは、より大きな画像に対してバックエンドが自動タイル処理します。torch サブプロセスには環境変数で上書き可能な、より高いリソース上限が適用されます - バックエンドは同梱されません:
noai-watermarkは LICENSE ファイルを同梱しておらず(全権利留保として扱われる)、CtrlRegenEngineを直接使用することで自動インストール/再起動のコードパスはバイパスされます
検出信頼度と集約監査
- 検出結果は
confirmed/probable/informational/likely_false_positiveに分類され、テキスト/画像/コンテナの JSON および人間向けレポートで公開されます - 新しい
audit_dir.py(再帰ツリースキャン)とaudit_website.py(サイトマップ検出 + クロール)がレポートを集約します。SKILL.md に文書化されています
誤検知の修正
- DOCX: 可視本文ではなく
docProps/customXmlのみをスキャン(#14) - テキスト Layer A: 絵文字ベースの後の絵文字
VS16/ZWJを保持。新しい--strip-emoji-glueパラノイドフラグ(#22) - HTML: CMS generator タグを AI メタデータではなく情報提供として扱う(#13)
- PDF: AI マーカーのバイトスキャンからストリームペイロードを除外(#13)
- 検査レポートは未対応/ベストエフォートのパスを注記
Windows サポート
- POSIX 専用の
preexec_fnとos.fchmodをゲートし、書き込みとオプションのツールが Windows で実行できるようにしました(#15、#23) - stdio を UTF-8 に再構成し、リダイレクトされた Windows ストリームが不可視 Unicode で例外を発生させなくなりました。Windows CI レッグ + CLI スモーク実行(#23)
ドキュメントとサプライチェーン
- README の CtrlRegen セクション + 研究参考文献(CtrlRegen、UnMarker、フォレンジックステルスの注意点)、責任ある使用の免責事項。SKILL/マトリクス/vendor-notes/倫理の更新
- Dependabot 設定 + セキュリティパスの CODEOWNERS。scipy/numpy/opencv-python/scikit-learn/pywavelets とベースイメージを Python 3.14-slim に更新
- モックベースの CtrlRegen テスト(CI に torch なし)
v0.3.2 — セキュリティ強化(安全な書き込み、HTTP クライアント、CI サプライチェーン)
- 安全でアトミックな出力書き込み: すべてのクリーナーは一時ファイル + アトミックリネーム(
safe_write_bytes/safe_write_text)で書き込むようになり、シンボリックリンクの宛先を拒否し、同じ安全なパスを通じて.bakバックアップを作成します — 事前に配置されたシンボリックリンク(例:/tmpやダウンロードディレクトリ内)がクリーンな書き込みを任意のファイルにリダイレクトすることはもうできません rewrite_text.pyHTTP クライアントの強化: リダイレクトは完全に拒否されるため、Authorizationヘッダーの API キーが検証されていないホストに再送されることはありません。非ループバックエンドはデフォルトで拒否されます(--allow-remoteまたはWATERMARKS_REWRITE_ALLOW_REMOTE=1でオプトイン)。http(s) スキームのみが受け入れられます。--api-keyは削除されました — キーはWATERMARKS_REWRITE_API_KEYによる環境変数のみです- リソース上限: デフォルト最大入力 1 GiB → 256 MiB、新しい 64 MiB の stdin 上限、DOCX/ODT の zip 予算 512 MiB → 128 MiB、そして exiftool/c2patool/SynthID サブプロセスに
RLIMIT_AS/RLIMIT_FSIZEを適用(すべての上限は環境変数で上書き可能) - サプライチェーン: CI アクションを SHA 固定し
permissions: contents: readを設定、開発依存を固定(requirements-dev.txt)、pip-auditステップ、新しい CodeQL ワークフロー。Docker イメージは非特権ユーザーで実行され、pip は固定されています - スコアラー依存: Pillow を 10.4.0 → 12.3.0 に更新(24 件の既知 CVE)。API 使用法は固定された上流コミットに対して検証済み
- テスト: 18 件の新しいセキュリティ回帰テスト(合計 60 件、すべて成功)
v0.3.1 — より強力な Layer B 統計的透かし書き換え
rewrite_text.pyのデフォルトパラフレーズは、汎用的な書き換えではなく、明示的な語彙選択 + 構文攻撃(節の順序、接続詞、転換語、文の境界、機能語)を実行するようになりました- 新しい
--tactic humanize: 定型的な AI スタイルの言い回しを対象としたゼロショットの「人間のように書く」パス - 新しい
--tactic code: コメント、docstring、文字列リテラルを書き換え、動作と公開 API 名を保持したままローカル識別子をリネームします - 構造パスは、AI 特有の「明確でプロフェッショナルなスタイル」ではなく「自然で多様な人間の散文」を出力するようになりました
- Ollama と OpenAI 互換バックエンドの両方に新しい
--temperature(デフォルト0.9) - 新しい
--candidates N: N 件の書き換えを生成し、最も語彙的に乖離したもの(バイグラム Jaccard 距離)を長さドリフトガード付きで選択します - より強力なモデル衛生: 疑わしい起源だけでなく、既知の透かし入りベンダーを避け、ローカルのオープンウェイトモデルを優先
- 残余リスクレポートは、短く予測可能なテキスト(低リスク)と、長く高エントロピーな散文(高リスク)を区別するようになりました
SKILL.md、removal-matrix.md、vendor-notes.mdのドキュメントを更新。テストは新しいプロンプト、乖離スコアリング、候補選択をカバー
v0.3.0 — オプションの SynthID ピクセルスコアリング
- 外部の
aloshdenny/reverse-SynthIDチェックアウトを介したオプションのピクセルドメイン SynthID スコアラー(score_synthid.py)。inspect_image.py/clean_image.pyでREVERSE_SYNTHID_DIRまたは--synthid-dirにより公開されます setup_synthid.shブートストラップ(スコアラー専用依存。--fullで上流の requirements をインストール)。Dockerfile.synthidとmake docker-synthid-build/docker-synthid-help- Makefile の
smoke-synthidとbootstrap-synthidターゲット - スコアラーアダプタ、CLI 利用不可パス、JSON 解析、ランタイムエラーのテスト
- ドキュメント: 検出/スコアリングのみ(ピクセル除去なし)。上流コードは同梱されず、非商用 Research License の下に留まります
v0.2.0 — c2patool 誤検知の修正
image_meta.py:has_manifestがError: No claim found/No JUMBF data foundをマニフェストとしてフラグしなくなりました(演算子優先順位のバグ: ネガティブマーカーがすべてのポジティブブランチを拒否するようになりました)- 新しい
tests/test_c2patool_report.py(4 ケース: claim なし、JUMBF なし、真正なマニフェスト、ツール不在) - ドキュメント:
c2patoolリンクを修正(リポジトリはcontentauth/c2pa-rsに移動)。テキスト透かし除去の品質コストに関する免責事項を追加
v0.1.0 — パッケージングの磨き上げ + 来歴の誠実さ
Makefile(test/smoke/install-skill)とpytest.ini- Markdown、HTML、SVG のフィクスチャサンプル。PDF 劣化クリーンテスト
- ドキュメント: 業界の二層モデル(ハードバインド C2PA 対 ソフトバインディング / SynthID-media)
- README の残余リスク表 + 外部検証ツールへのリンク
- 参考: Institute of AI PM の C2PA/SynthID ガイド
- ソフトバインディングおよびピクセル/音声/動画の透かしは、スキル/マトリクス/倫理で明示的にスコープ外
v0.0.1 — 初のマルチベンダーリリース
- エージェントスキル
remove-ai-marks(Claude 専用のremove-claude-marksを置き換え) - Layer A: 不可視 Unicode / bidi / タグ文字 / スペースホモグリフ(
inspect_text/clean_text) - Layer B: 書き換えガイダンス + オプションの
rewrite_text.py(print-prompt、Ollama、OpenAI 互換) - ファイル: PNG、JPEG、SVG、PDF、DOCX、ODT、HTML、Markdown の C2PA/AI メタデータ除去
- 統合された
inspect_file.py/clean_file.py - マルチベンダードキュメント(Claude、Gemini/SynthID-class、OpenAI、open-LLM)
- 標準ライブラリ優先のスクリプト。オプションの
c2patool/exiftool
License
MIT — LICENSE を参照。
Bibliography
- How Claude marks AI-generated content (Anthropic)
- Dathathri et al., Scalable watermarking for identifying large language model outputs (SynthID-Text, Nature 2024)
- Google AI for Developers, SynthID safeguards (Gemini API docs)
- C2PA / c2patool
- Kirchenbauer et al., A Watermark for Large Language Models
- Evseev, D. (Arbitration City), Accurate, Costless, and Invisible AI Text Watermarking for Self-Hosted AI Inference (technical report, August 2026) — keyed-Gumbel watermarking shipped in the open-source arbi-serve engine, with exact-test detection and speculative-decoding support — PDF
- THU-BPM/MarkLLM (unified toolkit for evaluating LLM watermarking algorithms)
- Pan et al., MarkDiffusion: An Open-Source Toolkit for Generative Watermarking of Latent Diffusion Models (JMLR) — the embedding toolkit this repo's optional image-watermark harness wraps — code, docs
- Zhang et al., Watermarks in the Sand: Impossibility of Strong Watermarking for Generative Models (ICML 2024)
- Sander et al., Watermarking Makes Language Models Radioactive — watermarks survive fine-tuning and mark downstream models trained on watermarked data
- Pan et al., Can LLM Watermarks Robustly Prevent Unauthorized Knowledge Distillation? — watermark-based provenance and protection against knowledge distillation
- google-deepmind/synthid-text (research reference; not used for detection here)
- aloshdenny/reverse-SynthID (research reference)
- ETH Zurich SRI, Probing SynthID (research blog on the detectability of SynthID watermarks)
- Liu et al., Image Watermarks are Removable Using Controllable Regeneration from Clean Noise (ICLR 2025) — the pixel-regeneration method the optional CtrlRegen backend implements — code
- Kassis & Hengartner, UnMarker: A Universal Attack on Defensive Image Watermarking (arXiv:2405.08363; IEEE S&P 2025) — a universal watermark attack compared on a different metric than CtrlRegen
- Goonatilake & Ateniese, Removing the Watermark Is Not Enough: Forensic Stealth in Generative-AI Watermark Removal (arXiv:2605.09203) — motivates the conservative-intensity default: removal can still leave forensic traces
- mertizci/noai-watermark (CLI/Python toolkit for SynthID/StableSignature/TreeRing removal and AI metadata stripping)
- 0xROOTPLS/DeSynth (SynthID removal for OpenAI/Google images)
- Institute of AI PM, AI Content Provenance and Watermarking: The PM's Guide to C2PA and SynthID (two-layer industry model: C2PA + imperceptible watermark / soft binding; SB 942 / EU AI Act Art. 50 context)