업데이트로 돌아가기
New releaseAug 30, 2026

watermarks-remover v0.5.0

소유한 콘텐츠에서 AI 워터마크를 제거하는 프라이버시 우선 앱입니다.

공유
_ _ _ ____ ___ ____ ____ _  _ ____ ____ _  _ ____    ____ ____ _  _ ____ _  _ ____ ____
| | | |__|  |  |___ |__/ |\/| |__| |__/ |_/  [__  __ |__/ |___ |\/| |  | |  | |___ |__/
|_|_| |  |  |  |___ |  \ |  | |  | |  \ | \_ ___]    |  \ |___ |  | |__|  \/  |___ |  \

watermarks-remover

CI Release Stars Forks

텍스트와 파일에서 여러 벤더의 AI 출처 표시를 제거하는 에이전트 스킬 + 표준 라이브러리 Python 서비스 — 본인이 소유한 콘텐츠의 프라이버시와 위생을 위해. 이 스킬은 얇은 클라이언트로, HTTP를 통해 기계를 구동하므로 에이전트 호스트에 Python이 필요하지 않습니다.

계층대상방법
A보이지 않는 유니코드, 특수 공백, 양방향 문자, 태그 문자결정적 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를 통해 서비스를 호출합니다. 스킬(마크다운만)을 설치하고 서비스를 시작한 다음, http://127.0.0.1:8765가 아니라면 WATERMARKS_SERVICE_URL을 설정하세요.

Claude Code에서는 가장 빠른 방법이 번들로 제공되는 플러그인 마켓플레이스입니다 — 클론이 필요 없고 제자리에서 업데이트됩니다. 그 외 환경에서는 하나의 설치 프로그램이 지원되는 모든 호스트를 커버합니다 (Python 3.10+ 표준 라이브러리, 의존성 없음):```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` | **Customize → Skills**에서 업로드할 `dist/<skill>.zip` |
| 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 셸용으로 제공됩니다.

무엇이든 쓰기 전에, 설치 프로그램은 claude.ai 업로드와 Skills API가
강제하는 [Agent Skills](https://agentskills.io) 패키징 규칙에 따라 스킬을
검증합니다: 스펙 전용 프런트매터(`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)를
실행합니다. 기본 검사 방식의 pre-commit 관례에 맞춘 두 가지 모드가 있습니다:

| 모드 | 동작 |
| --- | --- |
| `check` (기본값) | 출처 마크를 보고하고 파일은 그대로 둡니다. 결과는 모델에게 전달되어(종료 코드 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에서는 python3py로 바꾸세요.

훅이 할 수 없는 것. 어떤 훅도 당신이 읽기 전에 어시스턴트의 채팅 메시지를 다시 쓸 수 없습니다. Claude Code의 Stop 훅은 last_assistant_message를 읽기 전용으로 받으며, 최종 응답에 대한 사전 전송 필터는 없습니다 — 이는 이 프로젝트가 이미 Cursor 규칙에 대해 문서화한 것과 동일한 한계입니다. 따라서 결정론적 보장은 에이전트가 작성하는 파일과 git으로 들어가는 모든 것에 대한 pre-commit 게이트를 포괄합니다. 채팅 기록에만 존재하는 텍스트는 여전히 스킬 워크플로에 의존하며, 이는 모델 지시 기반이므로 최선 노력 방식입니다.

Claude Code 플러그인 (마켓플레이스)

이 저장소는 또한 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 …`로 동일하게 작동하며, 로컬
체크아웃에서는 `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의 동일한 스킬 설정도 작동합니다). 번들은 재현 가능하며, 루트에 `SKILL.md`가 있는 단일 최상위 `remove-ai-marks/` 디렉터리를 포함하는데, 이는 업로드가 기대하는 레이아웃입니다.

서비스 도달 가능성은 로컬 설치보다 여기서 더 중요합니다: 스킬은 얇은 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)는
승인된 원고, 문서 및 웹 카피를 위한 자체 포함형 스킬입니다. 이미지, 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 이미지에 사전 설치됨):

도구역할
c2patoolC2PA 매니페스트 검사
exiftool잔여 메타데이터 제거 (특히 PDF)
qpdf구조적 PDF 재구성 — 실제 PDF 제거에 필수 (아래 참조)

코어 스크립트는 Python 3.10+ 표준 라이브러리만 필요합니다. Layer 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**으로 표시한다 — 더 이상 "text"로 폴백하지 않는다. 자동 모드에서 clean_file.py는 이러한 파일을 UTF-8로 디코딩하여 망가진 바이트를 다시 쓰는 대신 거부한다(종료 코드 2, 출력 없음). --as text 또는 --force-text가 명시적 옵트인이다. inspect_file.py는 파일을 unknown으로 보고하며(종료 코드 0), HTTP 서비스는 /inspectkind: "unknown"으로 응답하지만 알 수 없는 형식의 /clean은 거부한다(400 — 알려진 확장자를 가진 파일명을 보내야 한다, 예: notes.txt).

HTTP 서비스

동일한 메커니즘이 stdlib HTTP 서비스(service/scripts/server.py)로 실행된다 — 스킬이 사용하는 인터페이스이자, 벤더링 없이 모든 웹 앱이 통합할 수 있는 방식이다:

메서드경로본문반환
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와 동일한 파일별 파이프라인을 반복하며, 요청당 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` 참조), 표준 라이브러리만 사용 — arbi-serve 같은 자체 호스팅 엔진; 동일 키 전용이며 벤더 오라클이 아님. |
| `claude-text` | — (자리 표시자) | Anthropic이 워터마크 감지 API를 발표했습니다; 이 연결부는 출시되면 활성화됩니다. |

이미지 점수화: `WATERMARKS_SYNTHID_SCORER_URL`이 설정되면 서비스는
`wr-synthid-score` 사이드카(heavy 프로필)를 통해 이미지를 점수화하고,
로컬 `REVERSE_SYNTHID_DIR`이 있으면 체크아웃을 직접 사용합니다. 감지는
fail-soft 방식입니다: 구성되지 않았거나, 시간 초과되었거나, 오류가 발생한 감지기는
`{"available": false, "error": ...}`를 보고하며 정리를 차단하지 않습니다.

### 워터마크 생성 (`/watermark` 및 `/watermark/batch`)

벤치마크 평가 및 왕복 테스트를 위한 워터마크된 텍스트를 생성합니다.
`WATERMARKS_SYNTHID_TEXT_URL`이 설정되면 서비스는 생성을
`wr-synthid-text` 사이드카(harness 프로필)에 위임하고, 로컬 `MARKLLM_DIR`이 있으면
체크아웃을 직접 사용합니다. 감지와 마찬가지로 생성도 fail-soft 방식입니다: 구성되지 않은 생성기는
`{"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

The 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를 확인하고 각 harness/heavy 서비스를 --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"}"

타이포그래피가 non-breaking space에 의존하는 언어(프랑스어 `« … »`, `; : ! ?` 앞의 공백)는 `"options": {"normalize_spaces": false}`를 전달해야 하며, 이는 `clean_text.py --no-normalize-spaces`의 HTTP에 해당한다. 보이지 않는 캐리어는 여전히 제거되며, 공백 재작성만 건너뛴다.

그 외의 모든 것은 선택 사항이며 저장소 루트의 `.env` 파일에 위치한다. `docker compose`는 **`.env`를 자동으로 로드**하고 `compose.yaml`의 `${VAR}` 참조를 여기에서 보간한다(둘 다 설정된 경우 셸 export가 `.env`보다 우선한다).```bash
cp .env.example .env       # then edit
docker compose up -d       # picks up .env automatically

.envgitignore 처리되어 있으며(기본 거부) — 절대 커밋하지 마세요. 호스트 측 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` (via 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` | 스코어러 사이드카용 공유 bearer 키 (비어 있으면 인증 없음) |
| `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` | 텍스트 워터마크 사이드카용 공유 bearer 키 (비어 있으면 인증 없음) |
| `WATERMARKS_SYNTHID_TEXT_TIMEOUT` | `wr-core` | `wr-synthid-text` 사이드카를 기다리는 시간(초) (기본값 120) |
| `WATERMARKS_MARKLLM_SCHEME` | `text_detectors.py` (host) | `/detect`용 MarkLLM 스킴: `kgw` (기본값) / `synthid` |
| `HF_TOKEN` | harness/heavy services | gated 모델용 Hugging Face 토큰 |
| `WATERMARKS_SERVICE_URL` | client only (skill / curl) | 서비스에 접근할 위치; 기본값 `http://127.0.0.1:8765` |
| `WATERMARKS_REWRITE_BACKEND` | `rewrite_text.py` hook | `print-prompt` (기본값) / `ollama` / `openai-compatible` |
| `WATERMARKS_REWRITE_MODEL` | `rewrite_text.py` hook | 모델 이름 (예: `deepseek-v4-flash`) |
| `WATERMARKS_REWRITE_BASE_URL` | `rewrite_text.py` hook | API base (예: `https://api.deepseek.com`) |
| `WATERMARKS_REWRITE_API_KEY` | `rewrite_text.py` hook | API 키 — 환경 변수로만, argv에는 절대 사용하지 않음 |
| `WATERMARKS_REWRITE_ALLOW_REMOTE` | `rewrite_text.py` hook | 비루프백 엔드포인트를 허용하려면 `1` |
| `WATERMARKS_REWRITE_REASONING_EFFORT` | `rewrite_text.py` hook | `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\Scripts\에 venv를 생성하며, 이는 image_meta.pyos.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 점수가 반환됩니다. Fail-soft: 사이드카가 다운되었거나 구성되지 않은 경우 보고서에 {"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 커널을 여전히 포함하는 마지막 인덱스입니다. 이 스크립트는 해당 인덱스에서 `torch` **와** `torchvision`을 함께 설치하여 의존성 설치가 PyPI의 CPU 빌드로 이들을 바꿔치기할 수 없도록 한 다음, 설치 후 `torch.cuda.is_available()`이 true인지 확인합니다. GPU가 감지되었지만 torch가 CPU 전용으로 끝나는 경우, 스크립트는 설정이 성공한 척하지 않고 큰 경고를 출력하고 0이 아닌 코드로 종료합니다.

### `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).

이미지 크기(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에 대한 로컬 탐지기가 없으므로 유일한 로컬 신호는 reverse-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

**탐지 기반 반복 재작성:** Layer B는 이제 반복적으로 재작성하며, 시도가 평가를 통과하는 즉시 중단합니다. 각 평가 라운드는 `--candidates`개의 변형을 생성하며(기본값 **1**, `WATERMARKS_REWRITE_CANDIDATES`), `--max-loops`는 최선의 변형이 반환되기 전까지 실행되는 라운드 수를 제한합니다(기본값 **1**, `WATERMARKS_REWRITE_LOOPS`). 각 변형은 한 번의 재작성 호출과 한 번의 평가로 구성되며, 평가자가 워터마크가 없다고 보고하는 첫 번째 시도에서 라운드가 조기 종료됩니다 — 따라서 `--max-loops`를 높이면 평가가 통과할 때까지 새로운 변형을 재시도합니다(일반적인 깨끗한 재작성은 한 번의 시도로 충분합니다). 평가자는 우선순위에 따라 선택됩니다:

1. **MarkLLM** — `--markllm-scheme`가 전달될 때(그리고 `--markllm-dir`와 함께) 동일 구성 연구 탐지를 수행합니다. 벤더 탐지기 슬롯은 Google의 SynthID-text 탐지기를 위해 MarkLLM 위에 예약되어 있는데, Google은 2026년 8월에 API에서 이를 폐기했습니다 — 향후 벤더 엔드포인트가 여기에 연결될 수 있습니다.
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"}
}

구성되지 않았거나, 시간 초과되거나, 오류가 발생한 detector는 error 사유와 함께 "available": false 항목을 생성하며 rewrite를 절대 실패시키지 않는다. 해당 시도는 그저 통과할 수 없을 뿐이고, 루프는 lexical-divergence 선택으로 폴백한다. 통과 없이 최대치가 소진되면, 가장 워터마크가 적은(가장 낮은 점수의) 시도가 참고 사항과 함께 best-effort로 반환된다.

백엔드가 구성되지 않았거나 해당 의존성이 누락된 경우, rewrite는 계속 진행되며 보고서에는 검증을 사용할 수 없었다고 기록된다. GPU를 권장한다. CPU 실행도 작동하지만 느리며, 모델 다운로드는 몇 GB이다.

하드닝 설정:

  • 어댑터(또는 모든 MarkLLM 실행)의 --offline은 Hugging Face 캐시에서만 스코어링 모델을 로드한다 — 네트워크 송신이 전혀 없으며, 캐시되지 않은 경우 빠르게 실패한다. 사용자 지정 원격 코드는 절대 실행되지 않는다(transformers trust_remote_code는 절대 활성화되지 않음).
  • WATERMARKS_MARKLLM_RLIMIT_AS=<bytes>(env, POSIX)는 MarkLLM detector 서브프로세스에 주소 공간 제한을 적용한다. torch/CUDA는 보통 큰 주소 공간을 필요로 하므로 기본적으로 꺼져 있다.
  • 구성 파일은 1 MiB로 제한되며, 업스트림 체크아웃과 베이스 이미지는 SHA/digest로 고정된다.

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/)는 키가 있는 Gumbel("지수") 텍스트 워터마크를 설명합니다 — 현재 오픈소스 arbi-serve 엔진(`ARBI_WATERMARK_KEY`)에 탑재되어 있으며 — 여기서 샘플러의 노이즈는 마지막 4-토큰 컨텍스트 윈도우의 키 기반 해시에서 파생됩니다. 탐지는 **모델 없는 재현**입니다: 텍스트만으로 `u = PRF(Hash(key, window), token)`을 재계산하고 Gamma 꼬리를 검정하므로 GPU, 모델, 로짓이 필요하지 않습니다. 이 저장소는 해당 탐지기를 `detect_gumbel.py`로 제공합니다 (표준 라이브러리만 사용; p-값은 정수 Gamma 형상에 대한 정확한 포아송 합 항등식입니다):```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과 동일한 정직성 주의사항: 이것은 동일 키 재현(same-key replay) 으로, 생성 시 사용된 것과 동일한 키, 토크나이저, 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를 최선의 노력(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 클래스 워터마크를 얼마나 효과적으로 제거하는지,
그리고 그 비용은 얼마인지를 측정한다. MarkLLM SynthID 스킴(동일 구성
탐지, sanity-gated)으로 워터마크된 샘플과 워터마크되지 않은 샘플을
생성하고, 리라이트 변형들(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 기본값은 loopback |
| `WATERMARKS_REWRITE_API_KEY` | `--rewrite-api-key` | — | API 키 (자식 프로세스에서는 환경 변수로만, argv로는 절대 안 됨) |
| `WATERMARKS_REWRITE_ALLOW_REMOTE=1` | `--rewrite-allow-remote` | off | 비-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이 이를 측정합니다.

선택적 MarkDiffusion 이미지 워터마크 하네스

이미지에 대한 통제된 실험을 위해, 선택적 외부 하네스가 THU-BPM/MarkDiffusion(Apache-2.0)을 감쌉니다. 이는 잠재 확산 모델용 생성적 워터마킹 툴킷입니다(마크를 제거하는 것이 아니라 삽입합니다). 우리는 이를 세 가지 용도로 사용합니다:

  1. 검증 하네스(MarkLLM과 유사하지만 이미지용): 테스트 이미지에 스킴으로 워터마크를 삽입하고, 제거를 실행한 뒤, 동일한 스킴 구성으로 재탐지합니다 — 예를 들어 Tree-Ring 계열 마크가 당신의 파이프라인에서 제거됨을 입증합니다. 이는 검증 하네스이지 오라클이 아닙니다: 탐지에는 생성 모델(그리고 키 기반 스킴의 경우 키)이 필요하므로, 임의의 이미지에 대해 벤더 탐지기가 실패할 것이라고 보증할 수 없습니다.
  2. 선택적 픽셀 제거 엔진: 이 툴킷의 DiffusionPurification 재생성 공격은 clean_image.py --remove-pixel diffusion으로 노출되며, CtrlRegen의 대안입니다. 이는 블라인드 재생성(ControlNet 컨디셔닝 없음)이므로 CtrlRegen보다 이미지 내용이 더 많이 변동합니다 — 보수적인 강도 기본값(0.3)을 사용하며, 폴백/비교용으로 취급되고 결코 보장으로 취급되지 않습니다.
  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 knobs는 MarkLLM harness를 그대로 반영합니다: --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 |
| --- | --- | --- | --- | --- |
| 유니코드 / 편집 기반 텍스트 | Layer A | Layer A | Layer A | Layer A |
| **통계적 샘플링 텍스트** | Layer B 최선 노력 (Anthropic의 탐지 API 출시 시 Claude 이음새) | Layer B 최선 노력 (+ MarkLLM 동일 구성 하네스; Google은 2026년 8월 벤더 탐지기를 폐기함) | 존재하는 경우 Layer B | Layer 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 워터마크는 종종 보이지 않는 문자에만 신호를 숨기는 것이 아니라 **어떤 토큰이 선택되는지** (생성 / 샘플링 편향)에 신호를 숨깁니다. 편집 기반 방식은 유니코드나 동의어 규칙을 주입합니다. 파일 방식은 **C2PA** 또는 생성기 메타데이터를 첨부합니다.

- **Layer A**는 편집 기반 유니코드 운반체를 제거합니다 (테스트 가능).
- **Layer B**는 대규모 재작성을 통해 샘플링 워터마크를 공격합니다 (최선 노력; 패러프레이즈 / 역번역과 같은 문헌 표준 공격).
- **파일 클리너**는 지원되는 컨테이너에서 C2PA/XMP/props를 제거합니다.

벤더가 공개 탐지기와 키를 제공할 때까지, **어떤 도구도** "이것이 공식 검사를 통과하지 못한다"고 정직하게 인증할 수 없습니다. 보고서는 검증 가능한 작업과 최선 노력 작업을 구분해야 합니다.

Layer B에는 **비원본** 모델을 사용하는 것이 좋습니다 (재스탬핑을 피하려는 경우 Claude 텍스트를 Claude로 재작성하지 마십시오).

---

## 면책 조항: 텍스트 워터마크 제거의 비용

텍스트 워터마크는 **문구 자체**에 존재합니다: 신호가 토큰 선택 전반에 퍼져 있으므로 거의 모든 문장이 신호를 조금씩 담고 있습니다. 두 가지 결과가 따르며, 이것이 Layer B가 마법의 지우개가 아니라 *최선 노력*으로 정직하게 설명되는 이유입니다.

1. **제거는 재구성이 아니라 재작성을 의미합니다.** 단락을 섞거나, 제목을 바꾸거나, 가볍게 손보는 것으로는 신호가 거의 움직이지 않습니다. 통계적 마크를 제거하려면 텍스트의 상당 부분을 — 섹션 단위가 아니라 문장 단위로 — 재작성해야 합니다.

2. **재작성은 원고를 저하시킵니다.** 모든 재작성은 원래의 단어 선택을 재작성 모델의 것으로 대체하며, 이는 어조, 목소리, 정밀도를 평탄화합니다. 프로덕션 원고(SEO, 마케팅, 클라이언트 작업)에서 이러한 저하는 실재하며 글쓰기를 가장 중요하게 여기는 사람들에게 종종 눈에 띕니다. 이는 최상위 모델의 텍스트를 가져와 덜 유능한 모델에게 처음부터 재작성하게 하는 것과 같습니다: 결과는 재작성 모델의 한계를 넘을 수 없습니다.

이는 정직한 근본 질문으로 이어집니다:

> 어차피 더 저렴한 모델로 텍스트를 재작성할 계획이라면, 애초에 왜 프리미엄 모델에 비용을 지불하는가? 더 저렴한 모델로 직접 생성하는 것이 더 간단하고, 더 저렴하며, 동일하거나 더 나은 최종 결과를 만들어냅니다.

Layer B는 프리미엄 모델의 **사고와 초안 작성**을 특별히 원하면서 위생 또는 프라이버시 요구 사항을 충족하기 위해 재작성 단계를 감수할 때 의미가 있습니다 — 마크 없는 텍스트로 가는 저렴한 경로로서가 아닙니다.

**Layer B를 건너뛰어야 할 때:**

- **위생보다 품질이 더 중요할 때:** 무손실 경로 — Layer A 유니코드 정리와 파일 메타데이터 클리너 — 를 사용하고 원본 문구를 유지하십시오.
- **어차피 재작성할 때:** **비원본** 모델을 사용하고 (원본 모델로 재작성하면 텍스트가 재스탬핑될 수 있음), 잔여 위험이 남아 있음을 기억하십시오 — 어떤 도구도 벤더 탐지기가 실패할 것이라고 인증할 수 없습니다.

---

## 파일 형식

| 형식 | 검사 | 정리 |
| --- | --- | --- |
| 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 meta 제거, 미디어 정리 + Layer A (암호화된 부분 건너뜀) |
| ODT | meta.xml | 생성기 / AI 관련 meta 제거 |
| HTML | meta, JSON-LD, data-ai* | 태그/속성 제거 |
| Markdown | YAML frontmatter AI 키 | 키 제거 + Layer 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

이미지가 PDF 내부에 있을 때 qpdf로 충분하지 않은 이유

위의 두 패스는 모두 문서, 즉 Info 딕셔너리, XMP 패킷, 객체 그래프를 대상으로 작동합니다. 어느 쪽도 이미지 XObject 내부로 내려가지 않으므로, 스캔본이나 Photoshop 내보내기 — 페이지 자체가 하나의 큰 JPEG인 경우 — 는 이미지가 담고 있는 모든 것을 그대로 유지합니다. 실제 Photoshop으로 내보낸 PDF에서는 "성공적인" 정리 후에도 27개의 태그가 남아 있으며, 그중에는 IFD0:Software, 캡처 타임스탬프, 미리보기 썸네일이 포함됩니다. 같은 이미지에 첨부된 C2PA 매니페스트도 마찬가지로 살아남습니다.

그래서 clean_pdf는 세 번째 패스인 deep_images를 추가하며, 이는 Ghostscript의 pdfwrite로 구동됩니다. 이 패스는 두 단계로 실행되며 파일이 깨끗해지는 즉시 중단됩니다:

  1. 무손실. 패스스루를 사용하는 pdfwrite는 압축된 이미지 데이터를 바이트 단위로 그대로 복사하면서 객체 그래프로부터 문서를 재구성합니다 — 이는 스트림을 전후로 해싱하여 검증됩니다. 이는 PDF가 이미지 주위에 감싸고 있던 모든 것을 제거합니다. 패스스루는 Ghostscript가 지원하는 코덱인 JPEG(DCTDecode)와 JPEG2000(JPXDecode)을 대상으로 합니다. Flate, CCITT, LZW 이미지는 디코딩 후 재인코딩되며, 이는 해당 코덱에서는 실질적으로 무손실이지만 바이트 단위로 동일하지는 않습니다. never는 스트림이 손대지 않고 그대로 유지되어야 하는 문서를 위한 옵션입니다.
  2. 증거가 있을 때만 재인코딩. JPEG 자체의 APPn 세그먼트 — APP1의 EXIF, APP11의 C2PA 매니페스트, APP13의 Photoshop 리소스 — 에 들어 있는 모든 것은 첨부된 바이트와 함께 이동하므로 패스스루가 이를 보존합니다. 2단계는 패스스루를 끈 상태로 동일한 패스를 실행하며, 1단계가 무언가를 남겼음이 입증될 때만 실행됩니다: 모든 모드에서의 AI/C2PA 마커, 또는 always 모드에서 살아남은 모든 APPn 메타데이터. APP0(JFIF)과 APP2(ICC)는 그대로 둡니다 — 첫 번째는 구조적이고 두 번째는 색상이 어떻게 읽히는지를 결정하기 때문입니다. 픽셀은 의심이 아니라 증거에 사용됩니다.

deep_imagesauto(기본값: 문서 스트립 후 마커가 살아남았을 때만 1단계, 그 후에도 살아남으면 2단계), always(모든 PDF에 대해 1단계, 카메라 및 편집기 EXIF에 대해서도 2단계로 에스컬레이션), lossless(1단계만 — 절대 재압축하지 않고, 살아남은 모든 것을 일반적인 still_has_c2pa / post_findings 필드를 통해 보고), never를 받습니다. 인식되지 않는 값은 조용히 auto로 처리되지 않고 거부됩니다. 보고서는 meta.deep_image_passmeta.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를 제거해도 해당 채널이 지워지지는 않습니다.

정리 후 잔여 위험

이 도구는 검증 가능한 제거(유니코드 개수, 메타데이터 작업)와 최선 노력 기반의 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의 가이드로, 업로드 제한과 결과를 읽는 방법을 포함합니다.

제거 옵션(요약)

옵션제거 대상비고
유니코드 스크럽(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를 제공하며 시스템 트레이와 Explorer 통합을 갖추고 있습니다. 이는 별도의 코드베이스입니다: 이 저장소의 Python 서비스를 호출하지 않으며, 지원 형식과 정리 보장이 이 프로젝트와 다릅니다. 자세한 내용은 해당 README를 참조하세요.

unmark-web — 브라우저 웹 UI

unmark-web은 독립적인 MIT 라이선스 정적 웹 클라이언트입니다. 텍스트에서 보이지 않는 유니코드 마크를 제거하고 이미지에서 출처 메타데이터를 전적으로 브라우저 내에서 제거하며, 로컬에서 처리하지 않는 형식의 경우 선택적으로 이 저장소의 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 내보내기, Coverage matrix 맥락 참조) — 아래의 pre-commit 훅은 마크된 파일이 커밋되기 전에 동일한 종류의 문제를 더 일찍 잡아냅니다. 둘 다 기존 CLI(audit_dir.py / clean_file.py)를 래핑합니다 — 별도의 탐지 로직은 없습니다.```yaml

.pre-commit-config.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

변경 로그

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로 채웁니다 — 비자기회귀적 로컬 편집이므로 출력은 원본 토큰 스트림과 마스킹된 LM 예측을 혼합합니다(#311).
  • humanize 전술은 이제 휴머나이저 스킬 패스를 결정론적으로 적용하고(직선 따옴표, en/em 대시 없음, 필러 축소, utilizeuse) 프롬프트에 휴먼 작성자 규칙을 명시합니다(#311). rewrite_text.py--strategy CLI 경로가 추가되었습니다.
  • 재작성 정확성: 어휘적 발산에서 유니코드 단어 토큰화(#305); 반올림 전 원시 마진 비교 및 선택 메타데이터 / 순위별 p-값 기록(#249).

벤치마크

  • SynthID 레시피 검색 + 강건한 측정(#280); 재작성 어휘 이름 변경, 교차 입력 검색, humanize-last 순서(#302); 휴머나이즈 폴리시 이후에도 여전히 통과하는 전략만 권장(#307).
  • Pangram 벌크 API를 인간 유사성 백엔드로 사용(#296); 30개 문서 코퍼스로 강화된 최소 재작성 수준 벤치마크(#257); 검증된 가중치 그리드 + 확장된 레시피 검색(#294); 폴란드어 벤치마크 코퍼스(#295).

워터마크 탈취

  • 새로운 블랙박스 워터마크 탈취 모듈 및 프롬프트 코퍼스 다운로더(#303); 시작 재시도 프로브 실패 시 오래된 상태 정리(#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); OOXML AppVersion 보존(#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 comment/XMP 확장은 삭제되지만 NETSCAPE2.0 루핑 및 기타 애니메이션 청크는 보존됩니다; TIFF IFD 메타데이터(XMP/EXIF/GPS/IPTC/MakerNote)는 페이로드를 0으로 만들고 스트립 오프셋을 유지한 채 삭제되며, 클래식 및 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 (보이지 않는 유니코드) 강화

  • 통합 Layer A 강화(#133): 정당한 교환 용도가 없는 예약된 Default_Ignorable 코드 포인트(U+2065, U+FFF0U+FFF8, U+E0000, U+E0080U+E00FF, U+E01F0U+E0FFFreserved_ignorable로 보고됨), 66개 비문자(U+FDD0U+FDEF 및 평면별 U+FFFE/U+FFFFnoncharacter로 보고됨), 그리고 Cf 캐치올이 결코 보지 못한 세 개의 공백 렌더링 Default_Ignorable 캐리어(U+180F, U+3164, U+FFA0)를 제거합니다. 각각은 이미 커버된 형제들과 동일한 문맥 내 보존을 가지므로 부분 음절 텍스트가 손상되지 않으며, 각각은 서비스 엔진과 벤더링된 경량 스킬 사본 모두에 적용됩니다
  • 자체 스크립트 옆의 가시적 레이아웃 형식 제어 문자 스트리핑 중단: 이집트 상형문자 쿼드라트 제어(U+13430U+1343F), Duployan 속기 제어(U+1BCA0U+1BCA3), 음악 빔/타이/슬러/프레이즈 제어(U+1D173U+1D17A)는 이제 자체 스크립트에 인접할 때 보존되며, 관련 없는 텍스트 사이에 떠 있을 때는 여전히 스트리핑(및 플래그)됩니다; --strip-emoji-glue 편집증 모드는 여전히 모든 곳에서 이들을 스트리핑합니다
  • 이모지 / 스크립트 폴리시: 블록 범위 밖의 이모지 싱글톤 뒤에 VS16 보존; 스크립트 조이너, 플래그 이모지, 아랍어 Cf 마크 보존; 텍스트 정리 중 다국어 유니코드 보존(#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)
  • 키드-Gumbel (Aaronson EXP) 동일 키 검증: 새로운 stdlib 전용 detect_gumbel.py가 모델 없는 리플레이 테스트(u = PRF(Hash(key, window), token); 정확한 Gamma-tail 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-validateclaude 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-skill
  • PostToolUse 훅을 통한 결정론적 자동 정리(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 사용(및 부트스트랩에서 macOS realpath 이식성 수정, #185)

감사 / 검사 & 보안

  • audit_dir.py에 다중 워커 동시성 및 SARIF 2.1.0 내보내기 추가(#101, #102)
  • 웹사이트 바이너리 형식을 실제 스캐너로 라우팅(#177); 사이트맵 파서에서 DTD/엔티티 폭탄 거부(GHSA-pjg6-92pm-mmcf)(#146); 충돌한 클리너가 깨끗한 것으로 읽히는 대신 커밋을 차단(#179); 읽을 수 없는 텍스트 파일은 깨끗한 것이 아니라 실패한 스캔(#169)

신뢰성 & 정확성 수정

  • 두 번째 --in-place 실행이 원본 .bak을 보존; 이후 zip 멤버 읽기 실패 시 수집된 증거 유지(#175); 잘린 ISOBMFF 컨테이너도 C2PA 바이트 스캔 폴백 실행(#176); 실패한 클리너와 이미 깨끗한 파일 구분(#159, #161); 실패한 c2patool 실행을 "C2PA 없음"이 아닌 불확정으로 처리(#156); clean 옵션 유형 검증(#111); 텍스트 워터마크 탐지에 MPS 장치 자동 선택 금지(#99); macOS 이식성 — SynthID 스코어러를 위한 순수 --json stdout 및 BSD realpath 프로브(#70); _ghostscript_usable의 Windows subprocess_creationflags 경로 수정 및 Windows에서 자식 프로세스가 콘솔 창을 여는 것 중지
  • 동작 강화: 양성 JPEG comment 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 스킬 텍스트 엔진을 서비스 사본에 고정(#96)

미출시

  • Pre-commit clean 훅(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 / markdiffusionprofile: harness 뒤; ctrlregen / synthid는 로컬 전용 빌드로 profile: heavy 뒤); 서비스는 wr- 접두사가 붙습니다; 하네스/헤비 서비스는 기본적으로 command: ["--help"]이므로 docker compose up --profile harness --profile heavy가 정상 종료됩니다(원샷 CLI는 docker compose run으로 실행); 새로운 make compose-check / compose-check.sh가 실행 중인 스택을 검증합니다(종료 코드만)
  • GHCR 게시: .github/workflows/release-images.ymlv* 태그에서 core, markllm, markdiffusion 이미지를 게시합니다; ctrlregen / synthid는 절대 게시되지 않습니다(업스트림 라이선싱)
  • 환경 구성: .env.example + 서비스 구성 가이드; docker compose.env를 자동 로드; .env는 gitignore됨(기본 거부)
  • 저장소 위생: .gitignoreservice/.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 및 Makefile bootstrap-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 및 Makefile bootstrap-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 vs 12)

  • markllm 이미지 빌드 수정: requirements-markllm.txttokenizers==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.2tokenizers<0.19)은 Python 3.14 휠을 제공하지 않으므로, 베이스 이미지가 이제 python:3.11-slim(다이제스트 고정, 멀티 아키텍처)입니다
  • 런타임 시 하네스 이미지 수정: Dockerfile.markllmDockerfile.markdiffusioncommon.py/app에 복사하지 않았습니다 (기존 버그) — 추가됨
  • WebP: RIFF C2PA, XMP, EXIF, ICC 프로필 청크에 대한 표준 라이브러리 전용 검사 및 메타데이터 정리 (#37)
  • BMP / GIF / TIFF: 표준 라이브러리 전용 탐지, 검사, 메타데이터 정리 — GIF 주석/XMP 확장은 제거되지만 NETSCAPE2.0 루핑은 유지됩니다; TIFF IFD 메타데이터(XMP/EXIF/GPS/IPTC/MakerNote)는 페이로드가 0으로 채워지고 스트립 오프셋이 유지된 채 제거되며, 클래식 및 BigTIFF 모두 지원됩니다; BMP 후행 메타데이터는 파일 크기 필드가 재작성되면서 잘립니다
  • EPUB: 표준 라이브러리 전용 컨테이너 정리 — OPF 메타데이터와 XHTML meta/JSON-LD 스크럽, 내장 래스터/SVG 미디어 제거, XHTML 본문 텍스트에 Layer A 적용, 마커 포함 메타데이터 부분 제거, OCF 암호화 부분은 그대로 통과
  • 파일명 정리: HTTP 서비스가 안전하지 않은 클라이언트 제공 출력 이름을 거부합니다
  • markdown frontmatter cleaner 수정 — 중첩된 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.ctrlregenmake 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_fnos.fchmod를 게이트하여 쓰기 및 선택적 도구가 Windows에서 실행되도록 함 (#15, #23)
  • stdio를 UTF-8로 재구성하여 리디렉션된 Windows 스트림이 비가시 유니코드에서 더 이상 예외를 발생시키지 않도록 함; Windows CI 레그 + CLI 스모크 실행 (#23)

문서 및 공급망

  • README CtrlRegen 섹션 + 연구 참조 (CtrlRegen, UnMarker, 포렌식 스텔스 주의사항), 책임 있는 사용 면책 조항; SKILL/matrix/vendor-notes/ethics 업데이트
  • 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.py HTTP 클라이언트 강화: 리디렉션이 완전히 거부되므로 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 액션이 permissions: contents: read로 SHA 고정됨, 개발 의존성 고정(requirements-dev.txt), pip-audit 단계, 새로운 CodeQL 워크플로; Docker 이미지가 이제 pip가 고정된 비권한 사용자로 실행됩니다
  • 스코어러 의존성: Pillow 10.4.0 → 12.3.0으로 범프 (알려진 CVE 24개); API 사용은 고정된 업스트림 커밋에 대해 검증됨
  • 테스트: 새로운 보안 회귀 테스트 18개 (총 60개, 모두 통과)

v0.3.1 — 더 강력한 Layer B 통계적 워터마크 재작성

  • rewrite_text.py의 기본 패러프레이즈가 이제 일반적인 재작성이 아닌 명시적인 단어 선택 + 구문 공격(절 순서, 접속사, 전환어, 문장 경계, 기능어)을 수행합니다
  • 새로운 --tactic humanize: 정형화된 AI 스타일 표현을 대상으로 하는 제로샷 "인간처럼 쓰기" 패스
  • 새로운 --tactic code: 주석, 독스트링, 문자열 리터럴을 재작성하고 동작과 공개 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); REVERSE_SYNTHID_DIR 또는 --synthid-dirinspect_image.py / clean_image.py에 노출됨
  • setup_synthid.sh 부트스트랩 (스코어러 전용 의존성; --full은 업스트림 요구사항 설치); Dockerfile.synthidmake docker-synthid-build / docker-synthid-help
  • Makefile smoke-synthidbootstrap-synthid 타겟
  • 스코어러 어댑터, CLI 사용 불가 경로, JSON 파싱, 런타임 오류에 대한 테스트
  • 문서: 탐지/스코어링 전용 (픽셀 제거 없음); 업스트림 코드는 번들되지 않으며 비상업적 연구 라이선스 하에 유지됩니다

v0.2.0 — c2patool 오탐 수정

  • image_meta.py: has_manifest가 더 이상 Error: No claim found / No JUMBF data found를 매니페스트로 플래그하지 않습니다 (연산자 우선순위 버그: 부정 마커가 이제 모든 긍정 분기를 거부합니다)
  • 새로운 tests/test_c2patool_report.py (4가지 케이스: 클레임 없음, JUMBF 없음, 진짜 매니페스트, 도구 부재)
  • 문서: c2patool 링크 수정 (저장소가 contentauth/c2pa-rs로 이동); 텍스트 워터마크 제거의 품질 비용에 대한 면책 조항 추가

v0.1.0 — 패키징 다듬기 + 출처 정직성

  • Makefile (test / smoke / install-skill) 및 pytest.ini
  • Markdown, HTML, SVG용 픽스처 샘플; PDF 저하 정리 테스트
  • 문서: 업계 2계층 모델 (하드 바인딩 C2PA vs 소프트 바인딩 / SynthID-media)
  • README 잔여 위험 표 + 외부 검증 도구 링크
  • 참조: Institute of AI PM C2PA/SynthID 가이드
  • 소프트 바인딩 및 픽셀/오디오/비디오 워터마크는 skill/matrix/ethics에서 명시적으로 범위 밖

v0.0.1 — 최초 멀티 벤더 릴리스

  • 에이전트 스킬 remove-ai-marks (Claude 전용 remove-claude-marks 대체)
  • Layer A: 비가시 유니코드 / 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

카테고리