FaintMark について
FaintMark は、受信者ごとの画像コピーを作成し、共有画像がどの登録済みコピーに対応するかを調べるローカル実行の Python CLI および MCP サーバーです。レジストリは端末内に保存され、再圧縮やリサイズへの耐性を意図した透かしを使いますが、制約も明記されています。
主な機能
- 01受信者ごとの透かし入りコピーを作成
- 02ローカルレジストリと照合して画像を確認
- 03登録済みコピーを一覧表示
- 04反転や小さな回転を含む詳細チェック
- 05JPEG、PNG、WebP の形式を維持
活用シーン
- 01受信者ごとの識別情報を含む写真を配布
- 02転送画像に登録済み透かしがあるか確認
- 03ローカルに登録した画像コピーを確認
MCP 対応
MCP クライアントに `mark_image`、`check_image`、`list_copies` を提供します。サーバーはローカルで動作し、透かし ID と受信者ラベルを端末内のレジストリで対応付けます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
3掲載時のツール一覧 · 2 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| format | any | いいえ | 既定値:元の形式 |
| image_path | string | はい | ウォーターマークを付ける画像の絶対パス |
| output_dir | any | いいえ | 既定値:画像と同じ場所にある marked/ フォルダー |
| recipients | array | はい | 1~100 個のラベルを各 1 部ずつ |
詳細
FaintMark
受信者ごとに目に見えない画像の透かしを埋め込みます。受信者ごとに透かし入りのコピーを作成し、共有または漏えいした画像がどのコピーかを特定します。チャットアプリによる再圧縮やサイズ変更に耐えられるよう設計されています。下記のアプリでのテストと制限をご覧ください。ローカルで動作し、アカウントもアップロードも不要です。Python CLI と MCP サーバーを提供します。
$ faintmark mark photo.jpg alice bob
alice 991e318a21 /Users/me/Pictures/marked/photo-991e318a21.jpg
bob 746ac9fa88 /Users/me/Pictures/marked/photo-746ac9fa88.jpg
2 copies in /Users/me/Pictures/marked
$ faintmark check ~/Downloads/forwarded.jpg
matched: alice 991e318a21 marked 2026-10-06T23:11:54Z

各人にはその人専用のファイルだけを送ってください。ファイル名には名前ではなくIDが含まれます。一致した場合に特定されるのは、そのコピーが発行されたラベルであり、共有した人ではありません。透かしは写真上で目立たないように設計されています。JPEG、PNG、WebPは元の形式を維持し、HEICはJPEGになります。
インストール
uvx faintmark mark photo.jpg alice bob # runs without installing; needs uv
uv tool install faintmark # or: pipx install faintmark
macOS、Linux、WindowsでPython 3.11以降が必要です(Intel Macでは3.11~3.13)。uvを使ってPythonをインストールし、FaintMarkを実行できます。最初のmarkまたはcheckの実行時に、AdobeのTrustMark Qモデル(65 MB、SHA-256で検証済み)が~/.faintmarkにダウンロードされます。HEIC入力にはfaintmark[heic]をインストールしてください。uv tool install 'faintmark[heic]'を実行するか、永続的にインストールせずに使う場合はuvx --from 'faintmark[heic]' faintmark mark photo.heic aliceを実行します。uv tool uninstall faintmarkを実行するとコマンドは削除されますが、~/.faintmarkは残ります。
faintmark mark IMAGE RECIPIENT... [-o DIR] [--format png|jpeg|webp] [--json]
faintmark check IMAGE [--deep] [--files] [--json]
faintmark list [-n N] [--files] [--json]
コピーは、-oで別の場所を指定しない限り、画像と同じ場所にあるmarked/に保存されます。スペースを含むラベルは引用符で囲んでください:faintmark mark photo.jpg 'Alice Smith'。1回の実行で1~100人分の透かしを埋め込めます。--jsonを指定すると、一致するMCPツールが返す結果が出力されます。markでは元ファイル名と出力パスが含まれ、checkとlistでは--filesを指定しない限りファイル名とパスは省略されます。check --deepでは、反転や小さな回転も試します。また、何も検出されなかった場合は、登録済みIDのうち、デコーダーの生出力から96個のコードワードビット中26個以内の候補を最大3件、ビット距離とともに一覧表示します。候補の判定は調整されておらず、偶然一致したり偽造されたりする可能性があります。一致として扱われることはありません。--jsonを指定した場合、エラーは{"error": "..."}として出力されます。checkの終了コードは、matchedの場合は0、unregisteredまたはnot_detectedの場合は1、エラーの場合は2です。
markは、透明部分のない非アニメーションのJPEG、PNG、WebP、HEICを受け付けます。上限は2,500万画素です(16ビットRGBは8ビットとして読み込まれます。16ビットグレースケールとCMYKは拒否されます)。このサイズでは約1.5 GBのメモリが必要です。checkは、Pillowで開ける形式であれば先頭フレームを読み込みます。上限は1億画素です。
エージェント
claude mcp add --scope user faintmark -- uvx faintmark mcp
codex mcp add faintmark -- uvx faintmark mcp
gemini mcp add -s user faintmark uvx faintmark mcp
mcpServers 設定を読み込むその他のクライアント(Claude Desktop、Cursor、
Windsurf)では、この項目をクライアントの MCP 設定に追加してください。クライアントが
PATH 上の uvx を見つけられる必要があります。別のレジストリを使うには、サーバーの環境に
FAINTMARK_HOME を設定してください。
{ "mcpServers": { "faintmark": { "type": "stdio", "command": "uvx", "args": ["faintmark", "mcp"] } } }
クライアントごとにいずれかの方法を選んでください。プラグインと拡張機能にはスキルも含まれています。
HEIC 入力の場合は、サーバーを uvx --from 'faintmark[heic]' faintmark mcp として起動してください。
同梱のランチャーでは基本パッケージを使用します。
- Cursor に追加
- VS Code に追加
- Claude Code プラグイン:
claude plugin marketplace add matchalatteguy/FaintMarkの後にclaude plugin install faintmark@faintmark - Codex プラグイン:
codex plugin marketplace add matchalatteguy/FaintMarkの後にcodex plugin add faintmark@faintmark - Gemini 拡張機能:
gemini extensions install https://github.com/matchalatteguy/FaintMark - シェルを使える任意のエージェント向けスキル:
npx skills add matchalatteguy/FaintMark
| ツール | 引数 | 戻り値 |
|---|---|---|
mark_image | image_path、recipients、省略可能な output_dir、format | 出力フォルダーと、各コピーの id、path、warnings |
check_image | image_path、省略可能な files、deep | matched(recipient、id、marked_at、保存済みハッシュがある場合は same_image を含む)、unregistered(id を含む)、または not_detected の status。deep の場合は、結果を復元した反転または回転を示す transform と、検出されなかった場合は registry_size を伴う suspects(id、recipient、bit_errors、same_image) |
list_copies | 省略可能な limit、files | 登録済みのコピー。新しいものから順に表示 |
ツールには絶対パスを指定してください。unregistered は ID がデコードされたものの、このマシンのレジストリに登録されていないことを意味します。not_detected は読み取り可能なマークがないことを意味し、画像の出所については何も示しません。same_image は、発行済みコピーとの知覚ハッシュを比較します。false は、しきい値を超えて異なることを意味します。これはトリミングや、別の画像にコピーされた ID によって起こることがあります。また、この比較で差異を見逃す場合もあります。
check_image と faintmark check --json は、次のいずれかを返します。3 種類の通常のステータス(same_image: false を伴う一致は、画像が発行済みコピーと異なることを意味します)、deep の場合は変換によって復元された結果、または候補を伴う未検出結果(カットオフ以内に登録済み ID がない場合、suspects は空です)。CLI はエラーを最終行に出力します。MCP ツールではツールエラー(isError)として発生します。サービスからのメッセージは一致しますが、引数検証メッセージは異なります。
{"status": "matched", "id": "991e318a21", "recipient": "alice", "marked_at": "2026-10-06T23:11:54Z", "same_image": true}
{"status": "matched", "id": "991e318a21", "recipient": "alice", "marked_at": "2026-10-06T23:11:54Z", "same_image": false}
{"status": "unregistered", "id": "3123f6f877"}
{"status": "not_detected"}
{"status": "matched", "id": "991e318a21", "recipient": "alice", "marked_at": "2026-10-06T23:11:54Z", "same_image": true, "transform": "rotated 4"}
{"status": "not_detected", "suspects": [{"id": "991e318a21", "recipient": "alice", "bit_errors": 21, "same_image": true}], "registry_size": 100}
{"error": "forwarded.jpg: no such file"}
mark_image と faintmark mark --json は1行目を返します。list_copies と
faintmark list --json は2行目を、新しいものから順に返します。
{"original": "photo.jpg", "output_dir": "/Users/me/Pictures/marked", "copies": [{"id": "991e318a21", "recipient": "alice", "path": "/Users/me/Pictures/marked/photo-991e318a21.jpg", "warnings": []}]}
{"copies": [{"id": "991e318a21", "recipient": "alice", "marked_at": "2026-10-06T23:11:54Z", "warnings": []}]}
実際のアプリでのテスト
2026年10月7日、FaintMark 0.1.0、3枚の元写真(iPhoneで撮影した風景、CC0のポートレート、CC0の夕焼け)。各行には、デフォルトの写真画質で、その経路で実際に送信されたサンプルを記載しています。スクリーンショットの行は、受信側のスマートフォンで撮影したスクリーンショットを写真部分だけに切り抜いてから確認したものです。これらは個別の送受信で得られた結果であり、アプリでの復元率や保証を示すものではありません。
| 経路 | 受信データ | 結果 |
|---|---|---|
| WhatsAppの写真、3枚すべて | 長辺1600 px、155~317 KB | 3/3件一致 |
| Discordの添付ファイル、風景 | 元のサイズ、1.7 MB | 1/1件一致 |
| 風景から作成したDiscordステッカー | 217x285 PNG | 1/1件一致 |
| Telegramの写真、風景 | 960x1280、102 KB | 1/1件一致 |
| WeChatの写真、ポートレートと夕焼け | 長辺1644~1920 px、67~302 KB | 2/2件一致 |
| InstagramのDM、ポートレート | 1206x804、176 KB | 1/1件一致 |
| WhatsApp、Discord、Telegram、WeChat、Instagramのチャット画面のスクリーンショット | 写真の長辺286~1022 px | 9/9件一致 |
| Tinderのプロフィールカード、ポートレート(スクリーンショット) | 大幅なトリミング | 検出されず 0/1 |
すべての一致結果で、same_image: true も報告されました。元画像、受信したすべてのファイル、スクリーンショット、切り抜き画像、想定されるIDと結果のマニフェスト(docs/evidence-2026-10-07.json)は、証拠リリースにあります。このレジストリがないマシンでは、受信したファイルはマニフェストに記載されたIDとともにunregisteredを返し、元画像とTinderの切り抜き画像はnot_detectedを返します。X、Signal、iMessage、Instagramストーリーズはテストしていません。
仕組み
TrustMark Q(Adobe、MIT)は、JPEGの再圧縮、WebPへの変換、中程度のサイズ変更に耐えられるよう設計された画像全体のパターンとして、40ビットのIDを埋め込みます。これはフォレンジックウォーターマーキングであり、ステガノグラフィーではありません。TrustMarkを使えば誰でもIDを読み取れますが、IDを名前に対応付けられるのは、あなたのレジストリだけです。FaintMarkは受信者ごとにランダムなIDを生成し、元の形式でコピーを書き出します(元画像の量子化テーブルを使ったJPEG、PNG、WebP。HEICはJPEGになります)。保存したコピーをデコードしてIDを復元できることを確認し、ID、受信者、ファイル、コピーの知覚ハッシュをローカルのSQLiteレジストリに記録します。登録済みの画像が一致すると、checkは知覚ハッシュを比較し、その距離がしきい値を超えた場合はsame_image: falseを報告します。元画像の設定ではマークが失われるJPEGは、代わりに品質95で保存され、その旨が警告に記載されます。すでにマークが付いた画像にマークを付けると、元のマークを置き換え、その旨が表示されます。事前検査では、各コピーをJPEG品質70で再エンコードし、元のサイズ(いずれかの辺がJPEGの上限である65,500 pxを超える場合は警告を出してスキップ)と、1280 pxに縮小したサイズ(画像がより小さい場合は半分のサイズ)で検査します。IDが保持されない場合は警告します。そのようなコピーは、写真としてではなくファイルとして送信してください。
ICCプロファイルは保持されるため、Display P3の写真は同じ色のままです。EXIFなどのメタデータはコピーされません。
Adobeの実装との互換性はテストで確認されています。tests/fixturesにはBCHベクトルと、上流のTrustMark 0.9.2でマークを付けた2枚の画像が含まれています。FaintMarkはそれらのIDをデコードし、マーク付きのピクセルをRGB各チャンネルで1以内の値で再現します。実際のモデルを使ったテストにおけるチャットアプリ風の段階的な変換(JPEG 70~80、WebP 75で1600、1280、1024 pxに縮小し、縮小とJPEG処理を連続して適用。受信したJPEGバイト列を使用)では、12メガピクセルの合成カバー画像2枚と320 pxのフィクスチャでIDを復元できます。ただし、フィクスチャではWebP 75の場合を除きます。
測定した攻撃
このリポジトリとそのコピーを持つ人は、以下のことができます。測定は 2000 px の写真 3 枚で行い、それぞれに 2 つの ID を使用しました。全表は 証拠リリースに添付された攻撃レポートに記載されています。
- 痕跡を見えないように消去できます。 ランダムなペイロードで公開エンコーダーを再実行すると、42~47 dB PSNR で痕跡が消えます(6/6)。その後
checkは、痕跡のない写真とまったく同じくnot_detectedと表示します。 - 上書きできます。 別の ID で再度マークすると、毎回新しい ID が優先されます(後から書いたものが優先、41~44 dB)。これらの試験では、デコーダーは置き換え後の ID を読み取り、以前の ID を読み取ることはありませんでした。
- コピーを組み合わせられます。 受信者 2~3 人分のコピーを平均化すると、通常のデコーダーは毎回破られます。ID 100 件のレジストリを照合した場合、最も近い登録済みコードワードは 264 回中 264 回、共謀者のものでした(距離は 12~30 ビット、最も近い無関係の ID は 28~41 ビット離れていました)。また、26 ビットのしきい値を使った場合、
check --deepは 264 回中 253 回で少なくとも 1 人の共謀者を列挙し、無関係な ID を列挙することはありませんでした。デコーダーの出力がランダムな場合、登録済みの各 ID がしきい値内に入る確率は約 100 万分の 4 なので、レジストリが大きくなるほど偶然の一致も増えます。 - 画像チェックを偽装できます。 ID を別の写真に単純に移植しても
same_imageは失敗します(距離は 28~32 ビット)。最もコストの低いハッシュビットを反転させるよう輝度も調整した巧妙な偽造では、目に見えるものの一般的な色調変化を伴いながら、27 dB PSNR で両方のチェックに合格します。 - 一般的な編集。 ぼかし、ノイズ、品質 40 までの JPEG 圧縮、100 px への縮小と再拡大、反転、明るさとコントラストの変更、印刷して撮影するシミュレーションでは、すべて痕跡が残りました(6/6)。各辺を 15% 以上切り抜くことと 4 度を超える回転では、通常のデコーダーで痕跡を読み取れなくなります。
--deepでは約 10 度までの回転を復元できます。
制限事項
matchedは、デコードされた ID がその受信者に登録されていることを意味します。誰が画像を共有したかを証明するものではありません。コピーは転送できます。matchedはレジストリの照合結果であり、署名ではありません。ID を知っている人なら誰でも、それを別の画像に埋め込めます。知覚ハッシュの比較で異なる画像を検出できる場合がありますが、コピーを認証したり、同じ画像に痕跡が再付与されたことを検出したりはできません。TrustMark はウォーターマーク除去ツールも公開しているため、事情に詳しい受信者は痕跡を除去できます。大幅な編集でも痕跡は除去される可能性があります。- 受信者がコピーを組み合わせると通常の
checkは破られます。--deepはコードワード間の距離に基づいて候補を列挙しますが、偶然の一致や偽造の可能性があり、証明にはなりません。 - アスペクト比が 2:1 を超える画像は中央の正方形部分にのみマークされます。画像をその境界をまたぐように移動する編集では、デコードできなくなる可能性があります。大幅な縮小や切り抜きでは痕跡が失われます。
- 結果はこのマシンのレジストリを信頼します。非公開にしてください。レジストリを編集できる人は誰でも、帰属先を変更できます。
レジストリ
~/.faintmark/registry.sqlite3 は ID と受信者名を対応付けます。非公開にしてバックアップしてください。レジストリがない場合、復元可能な ID は unregistered と報告されます。
FAINTMARK_HOME で別のディレクトリを指定できます。
MIT ライセンスです。TrustMark とそのモデルも Adobe のもので、同じく MIT ライセンスです。CONTRIBUTING、 SECURITY、および リリースを参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。