Compare commits
No files matched your search
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Agent skill and Claude Code plugin for the remove-ai-watermarks CLI.",
|
"description": "Agent skill and Claude Code plugin for the remove-ai-watermarks CLI.",
|
||||||
"version": "1.0.9"
|
"version": "1.0.13"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,26 +27,35 @@ top-level Python API, and runtime detection/removal pipelines.
|
|||||||
|
|
||||||
The unified maintainer entry point is `scripts/provider_oracles.py`; its workflow and
|
The unified maintainer entry point is `scripts/provider_oracles.py`; its workflow and
|
||||||
slot schema live in `docs/provider-oracles.md`. Multi-account and multi-network work
|
slot schema live in `docs/provider-oracles.md`. Multi-account and multi-network work
|
||||||
selects one named slot explicitly from the gitignored repository-local
|
selects each named slot explicitly from the gitignored repository-local
|
||||||
`.oracle-slots.json` by default and snapshots it into the immutable batch manifest.
|
`.oracle-slots.json` by default and snapshots it into a separate immutable batch
|
||||||
Never commit the local slot file.
|
manifest. A provider check may use the finite set of configured account slots and surfaces
|
||||||
Do not add account or IP rotation, rate-limit failover, or cross-session result merging.
|
after a refusal. Do not add new logins, IP rotation, network-route failover,
|
||||||
|
unbounded retries, or cross-session result merging. Never commit the local
|
||||||
|
slot file.
|
||||||
Every manual result stays bound to the exact sanitized upload hash and preserves the
|
Every manual result stays bound to the exact sanitized upload hash and preserves the
|
||||||
provider response verbatim.
|
provider response verbatim.
|
||||||
|
|
||||||
Provider surface selection is API-first. OpenAI and Microsoft use their official API
|
Provider surface selection is API-first. OpenAI and Microsoft use their official API
|
||||||
adapters before their Web surfaces when the requested media and local configuration are
|
adapters before their Web surfaces when the requested media and local configuration are
|
||||||
supported. Google and Meta currently use Web because this tooling has no usable API route
|
supported. Google and Meta currently use Web because this tooling has no usable API route
|
||||||
for them. Web remains an explicit second surface, never an automatic retry after an API
|
for them. A request to use a provider covers every configured surface, including Web,
|
||||||
refusal, quota response, transport error, or indeterminate verdict.
|
without a second confirmation. An API refusal, quota response, transport error, or
|
||||||
|
indeterminate verdict stays on that surface and never silently submits the file to Web.
|
||||||
|
|
||||||
Google Web oracle automation uses only the user's existing authenticated real Chrome
|
Google Web oracle automation uses only the user's existing authenticated real Chrome
|
||||||
session and explicit `/u/N/` account index. OpenAI, Microsoft, and Meta Web automation
|
session. Gemini uses an explicit `/u/N/` account index. The SynthID Detector at
|
||||||
|
`https://synthid.com/` (`synthid-web`) uses the same Chrome profile and does not take a
|
||||||
|
`/u/N/` index; its terms forbid driving the page as an automated API. OpenAI, Microsoft, and Meta Web automation
|
||||||
uses isolated Playwright contexts; it must never attach to or copy state from the user's
|
uses isolated Playwright contexts; it must never attach to or copy state from the user's
|
||||||
real browser. Proxy credentials are read only through a slot's named environment variable
|
real browser. Proxy credentials are read only through a slot's named environment variable
|
||||||
and never enter a manifest or log. Dotenv interpolation may derive routed proxy URLs from
|
and never enter a manifest or log. Dotenv interpolation may derive routed proxy URLs from
|
||||||
one secret key without mutating the process environment. A selected proxy context may
|
one secret key without mutating the process environment. A selected proxy context may
|
||||||
accept that proxy's TLS interception certificate; direct contexts keep normal TLS checks.
|
accept that proxy's TLS interception certificate; direct contexts keep normal TLS checks.
|
||||||
|
ThorData rotating endpoints are pinned to one manifest-derived sticky session for the
|
||||||
|
life of a Web batch so one browser context does not change exit identity between page
|
||||||
|
requests. This is route continuity, not account or IP failover; a refusal still ends on
|
||||||
|
the selected slot and never selects another route automatically.
|
||||||
Upload readiness is surface-specific: OpenAI reaches it at page load but keeps background
|
Upload readiness is surface-specific: OpenAI reaches it at page load but keeps background
|
||||||
requests alive, while Microsoft and Meta require network idle. Preserve exact provider
|
requests alive, while Microsoft and Meta require network idle. Preserve exact provider
|
||||||
errors as indeterminate evidence rather than retrying or interpreting them as clean.
|
errors as indeterminate evidence rather than retrying or interpreting them as clean.
|
||||||
@@ -73,9 +82,12 @@ The same rule applies to install hints: name the extra that actually makes the c
|
|||||||
|
|
||||||
Run `bash maintain.sh` from the repository root. The authoritative type gate is scoped to `src/`; full-project Pyright can exhaust Node memory on the ML dependency graph.
|
Run `bash maintain.sh` from the repository root. The authoritative type gate is scoped to `src/`; full-project Pyright can exhaust Node memory on the ML dependency graph.
|
||||||
|
|
||||||
|
The full gate outlasts the agent's 10-minute foreground shell cap, which kills it mid-suite. Run it detached under `nohup caffeinate -dimu` with its exit code written to a file and poll that file. To stop a stray run, kill its own PID, never `pkill -f pytest`, which also takes down other sessions' suites on the machine.
|
||||||
|
|
||||||
The security scanner's exit status is authoritative, including a failure after a success
|
The security scanner's exit status is authoritative, including a failure after a success
|
||||||
message. `tests/test_maintenance.py` checks clean, vulnerable, and broken scanner
|
message. `tests/test_maintenance.py` checks clean, vulnerable, and broken scanner
|
||||||
runs through the real shell entry point. Recheck unresolved advisories using the
|
runs through the real shell entry point, and pins the CI `security` job to the
|
||||||
|
same scanner line with no `continue-on-error`. Recheck unresolved advisories using the
|
||||||
procedure in `docs/development.md`; do not revive the old text-matching bypass.
|
procedure in `docs/development.md`; do not revive the old text-matching bypass.
|
||||||
|
|
||||||
Boundary modules for cv2, Torch, and Diffusers may carry narrow per-file relaxations for unknown third-party types. Keep pure-logic files strict, preserve the local piexif stub, and fix real errors before widening a pragma.
|
Boundary modules for cv2, Torch, and Diffusers may carry narrow per-file relaxations for unknown third-party types. Keep pure-logic files strict, preserve the local piexif stub, and fix real errors before widening a pragma.
|
||||||
@@ -129,6 +141,10 @@ Do not classify an entire module as untestable because its main path downloads a
|
|||||||
- the photo-classify gate in `test_classify.py`: DEFINITELY is ridge AND MLP,
|
- the photo-classify gate in `test_classify.py`: DEFINITELY is ridge AND MLP,
|
||||||
POSSIBLY does not emit a provider, and `identify` does not import
|
POSSIBLY does not emit a provider, and `identify` does not import
|
||||||
`remove_ai_watermarks.classify`.
|
`remove_ai_watermarks.classify`.
|
||||||
|
- the source-export classifier in `test_source_classify.py`: the Hub revision
|
||||||
|
and artifact hash are immutable, the pickle-free schema and exact GELU are
|
||||||
|
validated, threshold mutation changes the real decision, and `identify`
|
||||||
|
never consumes the result as watermark evidence.
|
||||||
- the `InvisibleOptions` defaults, in `test_api.py`. When one signature promises to
|
- the `InvisibleOptions` defaults, in `test_api.py`. When one signature promises to
|
||||||
mirror another, compare them field by field rather than pinning the values you happen
|
mirror another, compare them field by field rather than pinning the values you happen
|
||||||
to know about, so the next field added on one side and not the other fails at the
|
to know about, so the next field added on one side and not the other fails at the
|
||||||
@@ -295,3 +311,7 @@ explicit unchanged decodes when comparing stored pixels. Execute Ubuntu Bash
|
|||||||
workflow harnesses only on POSIX hosts with Bash; keep their pure Python checks
|
workflow harnesses only on POSIX hosts with Bash; keep their pure Python checks
|
||||||
active on every platform. Round floating-point image output before uint8
|
active on every platform. Round floating-point image output before uint8
|
||||||
conversion when quantization must preserve constant values across OpenCV builds.
|
conversion when quantization must preserve constant values across OpenCV builds.
|
||||||
|
OpenCV 5 draws Hershey `putText` glyphs smaller and heavier than 4.x, so a
|
||||||
|
calibrated template drawn with `putText` ships as a committed PNG whose recipe
|
||||||
|
refuses any OpenCV but 4.x (`scripts/render_video_hershey_templates.py`,
|
||||||
|
`scripts/build_openart_alpha.py`); never render one at import time.
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: provider-oracles
|
name: provider-oracles
|
||||||
description: Run hash-bound Google, OpenAI, Microsoft, or Meta watermark-oracle checks for this repository. Use for provider verification, multi-account Gemini checks, isolated Playwright checks, or recording oracle evidence.
|
description: Run hash-bound Google, OpenAI, Microsoft, or Meta watermark-oracle checks for this repository. Use for provider verification, multi-account Gemini checks, isolated Playwright checks, or recording oracle evidence.
|
||||||
|
metadata:
|
||||||
|
internal: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Provider oracles
|
# Provider oracles
|
||||||
@@ -14,22 +16,39 @@ Named slots live in the gitignored repository-local
|
|||||||
pass `--slots PATH` only to override the local path.
|
pass `--slots PATH` only to override the local path.
|
||||||
|
|
||||||
Never substitute one provider's negative verdict for another provider's signal.
|
Never substitute one provider's negative verdict for another provider's signal.
|
||||||
Do not optimize candidates adaptively against an external oracle. A retry,
|
Do not optimize candidates adaptively against an external oracle.
|
||||||
different account, or different network is a new explicit operator decision,
|
|
||||||
not automatic fallback.
|
## Account continuation
|
||||||
|
|
||||||
|
A request to verify a bounded set of files with a provider authorizes using that
|
||||||
|
provider's configured account slots unless the user limits the account scope.
|
||||||
|
Before starting, identify the finite set of available authenticated slots and
|
||||||
|
prepare one immutable batch per slot. After a quota refusal or service failure,
|
||||||
|
record the exact response, leave that slot's remaining rows unsubmitted, and
|
||||||
|
continue automatically through the remaining authorized slots. Do not stop to
|
||||||
|
request permission for each account or file already covered by the task.
|
||||||
|
|
||||||
|
Submit each frozen artifact at most once per slot in that pass. Preserve
|
||||||
|
inconclusive results and disagreements between accounts. Stop when the bounded
|
||||||
|
plan is complete or no authorized usable slot remains; report remaining rows.
|
||||||
|
New logins, another provider, or a new network route require authorization.
|
||||||
|
The oracles and slots already configured for the requested provider may be used
|
||||||
|
again, including their Web surfaces, without a second confirmation. Account
|
||||||
|
continuation does not authorize changing candidate pixels or erasing failures.
|
||||||
|
|
||||||
## Default surface priority
|
## Default surface priority
|
||||||
|
|
||||||
Prefer an official API adapter whenever that provider, media type, and configured
|
Prefer an official API adapter whenever that provider, media type, and configured
|
||||||
slot support it. Use Web only when no usable API route exists or the operator
|
slot support it. For OpenAI and Microsoft, the default plans are API then Web.
|
||||||
explicitly requests the Web surface as a second check. For OpenAI and Microsoft,
|
Google and Meta currently have only usable Web routes in this tooling. Google's
|
||||||
the default plans are API then Web. Google and Meta currently have only usable
|
routes are `gemini-web` and `synthid-web`. Inspect the order with `plan PROVIDER`.
|
||||||
Web routes in this tooling. Inspect the order with `plan PROVIDER`.
|
The same request may use every configured surface.
|
||||||
|
|
||||||
The priority is a selection policy, not automatic failover. An API refusal,
|
The priority is a selection policy, not automatic failover. An API refusal,
|
||||||
quota response, transport error, or indeterminate verdict must be recorded and
|
quota response, transport error, or indeterminate verdict must be recorded and
|
||||||
reported. Do not submit the same artifact to Web unless the operator separately
|
reported. The same request already covers every configured surface of that
|
||||||
authorizes that next surface.
|
provider, including Web. Run Web as its own acknowledged check and keep its
|
||||||
|
verdict separate. Do not replace the API result with the Web result.
|
||||||
|
|
||||||
## Route by surface
|
## Route by surface
|
||||||
|
|
||||||
@@ -44,18 +63,51 @@ Playwright CLI runner, copied cookies, or a new login.
|
|||||||
real Chrome profile and navigate directly to
|
real Chrome profile and navigate directly to
|
||||||
`https://gemini.google.com/u/<google_account_index>/app`.
|
`https://gemini.google.com/u/<google_account_index>/app`.
|
||||||
3. Prepare an immutable `gemini-web` batch outside the repository.
|
3. Prepare an immutable `gemini-web` batch outside the repository.
|
||||||
4. Before each upload, request the action-time confirmation required for sending
|
For every image check, visually inspect the source first. Remove every visible
|
||||||
that exact prepared file to Gemini. Do not upload the source path directly.
|
AI-provider logo or label with the project's visible-mark remover, derive all
|
||||||
5. Attach one prepared upload and ask: `Was this image/video/audio created or
|
paired controls and candidates from that cleaned image, and let `prepare` strip
|
||||||
edited by Google AI?`
|
AI metadata. Inspect the prepared upload again and do not submit it if a visible
|
||||||
|
provider mark remains. A model answer based on a visible logo is not a SynthID
|
||||||
|
result.
|
||||||
|
4. Confirm that the prepared files and provider are covered by the user's
|
||||||
|
request or existing upload approval. Ask only if that scope is missing or
|
||||||
|
changed; do not request confirmation again for each file or account in the
|
||||||
|
authorized plan. Do not upload the source path directly.
|
||||||
|
5. Attach one prepared upload and ask: `Does this image/video/audio contain a
|
||||||
|
SynthID watermark? Use the SynthID verification tool only; do not infer from
|
||||||
|
visible content, logos, text, or metadata.` Do not substitute a general
|
||||||
|
Google-AI-origin question; it permits visual-content reasoning instead of a
|
||||||
|
SynthID verdict.
|
||||||
6. Read the settled SynthID tool outcome, not the surrounding model reasoning.
|
6. Read the settled SynthID tool outcome, not the surrounding model reasoning.
|
||||||
Preserve unclear, quota, and refusal wording rather than interpreting it as
|
Preserve unclear, quota, and refusal wording rather than interpreting it as
|
||||||
clean.
|
clean.
|
||||||
7. Record the verbatim response and verify the completed batch.
|
7. Record the verbatim response and verify the completed batch.
|
||||||
|
|
||||||
If the selected Chrome profile or `/u/N/` account is not already authenticated,
|
If a planned Chrome profile or `/u/N/` account is not already authenticated,
|
||||||
stop and ask the user to select the intended existing session. Do not sign in or
|
record that it is unavailable and continue with another authorized existing
|
||||||
switch to another account implicitly.
|
session. Ask the user only when no usable authorized session remains. Do not
|
||||||
|
start a new login.
|
||||||
|
|
||||||
|
### SynthID Detector
|
||||||
|
|
||||||
|
Use the user's real authenticated Chrome. Do not use isolated Playwright: the
|
||||||
|
portal's additional terms forbid using the page as an automated API, bulk
|
||||||
|
verification, and quota evasion.
|
||||||
|
|
||||||
|
1. Validate a `synthid-web` slot. It names `browser_profile` and `account_label`.
|
||||||
|
It does not use `google_account_index` or a proxy variable.
|
||||||
|
2. Prepare an immutable batch outside the repository. Inspect images the same
|
||||||
|
way as for Gemini, and do not submit a file that still shows a provider logo.
|
||||||
|
3. Open `https://synthid.com/` in the named Chrome profile. Accept the terms
|
||||||
|
dialog when it is shown. Do not start a new login. If the page requires
|
||||||
|
sign-in and that profile has no session, leave the row unsubmitted.
|
||||||
|
4. Attach one prepared upload through the page's own file control.
|
||||||
|
5. Read the settled label. `Watermark detected` is `detected`. `Watermark not
|
||||||
|
detected` is `not_detected`. Quota, sign-in, and unclear wording stay
|
||||||
|
`refused` or `indeterminate`.
|
||||||
|
6. Record the verbatim response. The result covers partner SynthID from Google
|
||||||
|
DeepMind, Nvidia, OpenAI, and Kakao, and it does not name which partner. Do
|
||||||
|
not record it as a Gemini-only or OpenAI-only verdict.
|
||||||
|
|
||||||
### OpenAI, Microsoft, and Meta Web
|
### OpenAI, Microsoft, and Meta Web
|
||||||
|
|
||||||
@@ -88,13 +140,22 @@ OpenAI, Microsoft, and Meta Web slots may each name one of them through
|
|||||||
verify egress before relying on it, and never automatically change route after
|
verify egress before relying on it, and never automatically change route after
|
||||||
a refusal. Bound a batch before starting.
|
a refusal. Bound a batch before starting.
|
||||||
|
|
||||||
|
ThorData user-and-password endpoints without `sessid` rotate the exit on each
|
||||||
|
request, which breaks a browser page whose resources must share one network
|
||||||
|
identity. The runner therefore derives one sticky `sessid` from the immutable
|
||||||
|
manifest hash and keeps it for that batch. A new prepared batch gets a new
|
||||||
|
session; rerunning or resuming the same batch keeps its session while the proxy
|
||||||
|
retains it. An explicit `sessid` in the configured route is preserved. This is
|
||||||
|
session continuity, not permission to retry a refused batch on another route.
|
||||||
|
|
||||||
### OpenAI API
|
### OpenAI API
|
||||||
|
|
||||||
Use `check-openai` with one explicit upload acknowledgement. Keep named API keys
|
Use `check-openai` with one explicit upload acknowledgement. Keep named API keys
|
||||||
as `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`, and `OPENAI_API_KEY_3` in the local `.env`, and select
|
as `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`, and `OPENAI_API_KEY_3` in the local `.env`, and select
|
||||||
only their variable names through an `openai-api` slot's `api_key_env`. Never
|
only their variable names through an `openai-api` slot's `api_key_env`. Never
|
||||||
print a key, copy one into a slot or manifest, or fall back automatically to a
|
print a key or copy one into a slot or manifest. Continue through authorized
|
||||||
second key. API slots use a direct SDK transport that ignores environment proxy
|
named API slots according to the bounded account plan above; keep a separate
|
||||||
|
batch for each. API slots use a direct SDK transport that ignores environment proxy
|
||||||
settings. The current adapter is image-only.
|
settings. The current adapter is image-only.
|
||||||
|
|
||||||
### Microsoft API
|
### Microsoft API
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: signal-discovery
|
name: signal-discovery
|
||||||
description: Discover missing AI provenance and watermark coverage in the remove-ai-watermarks library from fresh or historical corpora, then validate detection and removal parity. Use for corpus gap mining, periodic signal audits, new-provider discovery, or deciding whether old retained data must be replayed. Do not use for ordinary per-file identification or removal.
|
description: Discover missing AI provenance and watermark coverage in the remove-ai-watermarks library from fresh or historical corpora, then validate detection and removal parity. Use for corpus gap mining, periodic signal audits, new-provider discovery, or deciding whether old retained data must be replayed. Do not use for ordinary per-file identification or removal.
|
||||||
|
metadata:
|
||||||
|
internal: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Signal discovery
|
# Signal discovery
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "remove-ai-watermarks",
|
||||||
|
"version": "1.0.13",
|
||||||
|
"description": "Identify and remove AI provenance marks from images and video the user generated or edited, using the remove-ai-watermarks CLI.",
|
||||||
|
"author": {
|
||||||
|
"name": "wiltodelta",
|
||||||
|
"url": "https://github.com/wiltodelta"
|
||||||
|
},
|
||||||
|
"homepage": "https://raiw.cc",
|
||||||
|
"repository": "https://github.com/wiltodelta/remove-ai-watermarks",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"keywords": [
|
||||||
|
"ai-watermark",
|
||||||
|
"watermark-removal",
|
||||||
|
"c2pa",
|
||||||
|
"synthid",
|
||||||
|
"provenance",
|
||||||
|
"gemini",
|
||||||
|
"sora"
|
||||||
|
],
|
||||||
|
"skills": "./skills/",
|
||||||
|
"interface": {
|
||||||
|
"displayName": "Remove AI Watermarks",
|
||||||
|
"shortDescription": "Identify and remove AI watermarks and provenance metadata",
|
||||||
|
"longDescription": "Runs the open-source remove-ai-watermarks CLI locally to identify C2PA, IPTC, EXIF and TC260 provenance, visible AI-generator labels and supported invisible watermarks, then removes them from images and video the user generated or edited. It does not remove stock, agency or other third-party marks.",
|
||||||
|
"developerName": "wiltodelta",
|
||||||
|
"category": "Productivity",
|
||||||
|
"capabilities": ["Read", "Write"],
|
||||||
|
"websiteURL": "https://github.com/wiltodelta/remove-ai-watermarks",
|
||||||
|
"defaultPrompt": [
|
||||||
|
"Check this image for AI watermarks and provenance metadata",
|
||||||
|
"Remove the AI metadata from my generated video without re-encoding it",
|
||||||
|
"Remove the visible AI label from the corner of my image"
|
||||||
|
],
|
||||||
|
"brandColor": "#A78BFA",
|
||||||
|
"composerIcon": "./assets/icon.svg",
|
||||||
|
"logo": "./assets/icon.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Local state and secrets never belong in a plugin bundle.
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
.oracle-slots.json
|
||||||
|
.local-eval/
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
# Generated media and model artifacts.
|
||||||
|
data/
|
||||||
|
*.mp4
|
||||||
|
*.onnx
|
||||||
|
*.safetensors
|
||||||
|
*.pt
|
||||||
@@ -6,6 +6,10 @@
|
|||||||
# Create and manage keys at: https://platform.openai.com/api-keys
|
# Create and manage keys at: https://platform.openai.com/api-keys
|
||||||
# OPENAI_API_KEY=
|
# OPENAI_API_KEY=
|
||||||
|
|
||||||
|
# GitHub token (optional; only for scripts/sync_c2pa_soft_bindings.py)
|
||||||
|
# Raises the api.github.com rate limit from 60 to 5000 requests/hour. GH_TOKEN is read as a fallback.
|
||||||
|
# GITHUB_TOKEN=
|
||||||
|
|
||||||
# Local photo-classifier weights (optional; otherwise fetched from Hugging Face)
|
# Local photo-classifier weights (optional; otherwise fetched from Hugging Face)
|
||||||
# Directory contents are documented in docs/photo-classify.md.
|
# Directory contents are documented in docs/photo-classify.md.
|
||||||
# RAIW_CLASSIFY_WEIGHTS=/path/to/classifier-weights
|
# RAIW_CLASSIFY_WEIGHTS=/path/to/classifier-weights
|
||||||
@@ -19,6 +23,11 @@
|
|||||||
# Fallback cache root when HF_HOME is unset (optional)
|
# Fallback cache root when HF_HOME is unset (optional)
|
||||||
# XDG_CACHE_HOME=/path/to/cache
|
# XDG_CACHE_HOME=/path/to/cache
|
||||||
|
|
||||||
|
# Gemini API key (optional; only for generating Google provider samples by hand)
|
||||||
|
# Paid tier: Nano Banana 2 Lite and Gemini Omni Flash have no free tier.
|
||||||
|
# Create and manage keys at: https://aistudio.google.com/apikey
|
||||||
|
# GEMINI_API_KEY=
|
||||||
|
|
||||||
# Named OpenAI keys for provider-oracle slots (optional; keep each value secret)
|
# Named OpenAI keys for provider-oracle slots (optional; keep each value secret)
|
||||||
# OPENAI_API_KEY_1=
|
# OPENAI_API_KEY_1=
|
||||||
# OPENAI_API_KEY_2=
|
# OPENAI_API_KEY_2=
|
||||||
@@ -34,6 +43,7 @@
|
|||||||
|
|
||||||
# ThorData residential proxies (optional; only for explicitly selected Web oracle routes)
|
# ThorData residential proxies (optional; only for explicitly selected Web oracle routes)
|
||||||
# Generate these URLs in the ThorData endpoint generator; do not commit credentials.
|
# Generate these URLs in the ThorData endpoint generator; do not commit credentials.
|
||||||
|
# The Web runner pins a rotating user-and-password route to one sticky session per manifest.
|
||||||
# THORDATA_ROUTE_US=http://td-customer-USERNAME-country-US:PASSWORD@HOST.pr.thordata.net:9999
|
# THORDATA_ROUTE_US=http://td-customer-USERNAME-country-US:PASSWORD@HOST.pr.thordata.net:9999
|
||||||
# THORDATA_ROUTE_DE=http://td-customer-USERNAME-country-DE:PASSWORD@HOST.pr.thordata.net:9999
|
# THORDATA_ROUTE_DE=http://td-customer-USERNAME-country-DE:PASSWORD@HOST.pr.thordata.net:9999
|
||||||
# THORDATA_ROUTE_GB=http://td-customer-USERNAME-country-GB:PASSWORD@HOST.pr.thordata.net:9999
|
# THORDATA_ROUTE_GB=http://td-customer-USERNAME-country-GB:PASSWORD@HOST.pr.thordata.net:9999
|
||||||
|
|||||||
@@ -14,18 +14,22 @@ updates:
|
|||||||
update-types:
|
update-types:
|
||||||
- "minor"
|
- "minor"
|
||||||
- "patch"
|
- "patch"
|
||||||
# The `all` extra must remain jointly resolvable. These candidates violate
|
# The `all` extra must remain jointly resolvable. PaddleX 3.7 (the OCR
|
||||||
# compatibility constraints: older supported TrustMark builds require NumPy 1.x, OpenCV 4.12+
|
# extra) requires NumPy <2.4, mirrored by `constraint-dependencies` in
|
||||||
# requires NumPy 2 on newer Python versions, and this project keeps tokenizers
|
# pyproject.toml, and this project keeps tokenizers below 0.23 for the stable
|
||||||
# below 0.23 for the stable Transformers path. Keep Dependabot on the
|
# Transformers path. Keep Dependabot on the compatible lines until those
|
||||||
# compatible lines until those constraints move.
|
# constraints move. Transformers 5.16+ requires tokenizers 0.23.1+,
|
||||||
|
# and Diffusers requires huggingface-hub <2. Mirror the manifest bounds.
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: "numpy"
|
- dependency-name: "numpy"
|
||||||
versions:
|
versions:
|
||||||
- ">=2"
|
- ">=2.4"
|
||||||
- dependency-name: "opencv-python-headless"
|
- dependency-name: "transformers"
|
||||||
versions:
|
versions:
|
||||||
- ">=4.12"
|
- ">=5.16"
|
||||||
|
- dependency-name: "huggingface-hub"
|
||||||
|
versions:
|
||||||
|
- ">=2"
|
||||||
- dependency-name: "tokenizers"
|
- dependency-name: "tokenizers"
|
||||||
versions:
|
versions:
|
||||||
- ">=0.23"
|
- ">=0.23"
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
resolve:
|
resolve:
|
||||||
|
# Model-only prereleases must not reach PyPI, Homebrew, Spaces, or ComfyUI.
|
||||||
|
if: ${{ github.event_name != 'release' || (startsWith(github.event.release.tag_name, 'v') && !github.event.release.prerelease) }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Thirty attempts may spend up to 20 seconds each in the JSON request,
|
# Thirty attempts may spend up to 20 seconds each in the JSON request,
|
||||||
# simple-index request, and inter-attempt delay. Leave bounded headroom.
|
# simple-index request, and inter-attempt delay. Leave bounded headroom.
|
||||||
@@ -60,7 +62,7 @@ jobs:
|
|||||||
PYTHON
|
PYTHON
|
||||||
|
|
||||||
- name: Upload distributed release identity
|
- name: Upload distributed release identity
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: distributed-release-${{ github.run_attempt }}
|
name: distributed-release-${{ github.run_attempt }}
|
||||||
path: release-identity.json
|
path: release-identity.json
|
||||||
@@ -102,7 +104,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the tap
|
- name: Checkout the tap
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
repository: wiltodelta/homebrew-tap
|
repository: wiltodelta/homebrew-tap
|
||||||
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||||
@@ -225,7 +227,7 @@ jobs:
|
|||||||
needs: resolve
|
needs: resolve
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Publish the agent skill when its version moved
|
- name: Publish the agent skill when its version moved
|
||||||
env:
|
env:
|
||||||
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
name: Publish image-source classifier to Hugging Face
|
||||||
|
|
||||||
|
# Manual only. The derivative weights live in a GitHub model-freeze release;
|
||||||
|
# private training images and caches never enter this repository.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
confirm:
|
||||||
|
description: "Type publish"
|
||||||
|
required: true
|
||||||
|
weights_tag:
|
||||||
|
description: "GitHub Release tag holding openai-google-source-v1.npz"
|
||||||
|
required: true
|
||||||
|
default: image-source-freeze-2026-09-25-v1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
if: ${{ github.event.inputs.confirm == 'publish' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
- name: Download derivative weights
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
TAG: ${{ github.event.inputs.weights_tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p weights
|
||||||
|
gh release download "$TAG" \
|
||||||
|
--repo "${{ github.repository }}" \
|
||||||
|
--pattern openai-google-source-v1.npz \
|
||||||
|
--dir weights
|
||||||
|
test -s weights/openai-google-source-v1.npz
|
||||||
|
- name: Verify artifact hash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
expected="$(jq -r .artifact_sha256 docs/image-source-hf/metrics.json)"
|
||||||
|
test "${#expected}" -eq 64
|
||||||
|
echo "$expected weights/openai-google-source-v1.npz" | sha256sum --check
|
||||||
|
- name: Publish to Hugging Face
|
||||||
|
env:
|
||||||
|
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||||
|
SHA: ${{ github.sha }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${HF_TOKEN:-}" ]; then
|
||||||
|
echo "HF_TOKEN secret is not set" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
uv run --no-project --with huggingface-hub \
|
||||||
|
python scripts/publish_image_source_hf.py \
|
||||||
|
--src weights \
|
||||||
|
--message "Image-source classifier freeze (GitHub ${SHA:0:7})"
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
name: Publish to MCP registry
|
||||||
|
|
||||||
|
# Lists the released PyPI package in the official MCP registry
|
||||||
|
# (registry.modelcontextprotocol.io) from server.json at the release tag.
|
||||||
|
# GitHub OIDC proves ownership of the io.github.wiltodelta namespace, so no
|
||||||
|
# interactive login or stored secret is involved. The registry validates the
|
||||||
|
# PyPI release itself (the mcp-name marker in its description), and the Release
|
||||||
|
# event fires in parallel with publish.yml's upload, so this waits until PyPI
|
||||||
|
# serves the version first. A version the registry already lists is skipped.
|
||||||
|
# It is a workflow of its own, not a distribute.yml job, so a manual dispatch
|
||||||
|
# re-lists one version without re-running Homebrew, the Space, or ComfyUI, and
|
||||||
|
# a registry outage never fails release verification.
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Released version to list (no leading v), e.g. 0.46.0"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
# Model-only prereleases are not Python package releases.
|
||||||
|
if: ${{ github.event_name != 'release' || (startsWith(github.event.release.tag_name, 'v') && !github.event.release.prerelease) }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
env:
|
||||||
|
SERVER_NAME: io.github.wiltodelta/remove-ai-watermarks
|
||||||
|
# The same name percent-encoded, as the registry's path segment takes it.
|
||||||
|
SERVER_PATH: https://registry.modelcontextprotocol.io/v0/servers/io.github.wiltodelta%2Fremove-ai-watermarks
|
||||||
|
# Pinned publisher release; the digest is GitHub's sha256 for the asset.
|
||||||
|
PUBLISHER_URL: https://github.com/modelcontextprotocol/registry/releases/download/v1.8.1/mcp-publisher_linux_amd64.tar.gz
|
||||||
|
PUBLISHER_SHA256: a06c9096dcb9727c13555b6be26c7effa707b01f06a4c561ba7a3635443cf2cc
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Resolve target version
|
||||||
|
id: v
|
||||||
|
env:
|
||||||
|
EVENT_NAME: ${{ github.event_name }}
|
||||||
|
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||||
|
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ "$EVENT_NAME" = "release" ]; then
|
||||||
|
v="${RELEASE_TAG#v}"
|
||||||
|
else
|
||||||
|
v="$INPUT_VERSION"
|
||||||
|
fi
|
||||||
|
# The version becomes a git ref and a URL component. Reject malformed input.
|
||||||
|
[[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+([a-zA-Z0-9.+-]*)$ ]]
|
||||||
|
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Skip a version the registry already lists
|
||||||
|
id: listed
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.v.outputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
status=$(curl -sS --connect-timeout 10 --max-time 20 -o /dev/null -w '%{http_code}' "$SERVER_PATH/versions/$VERSION")
|
||||||
|
case "$status" in
|
||||||
|
200) echo "listed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "- MCP registry: $SERVER_NAME $VERSION was already listed" >> "$GITHUB_STEP_SUMMARY" ;;
|
||||||
|
404) echo "listed=false" >> "$GITHUB_OUTPUT" ;;
|
||||||
|
*) echo "::error::Registry lookup returned HTTP $status"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# mcp-publisher reads only ./server.json, so fetch nothing else.
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
if: ${{ steps.listed.outputs.listed == 'false' }}
|
||||||
|
with:
|
||||||
|
ref: v${{ steps.v.outputs.version }}
|
||||||
|
sparse-checkout: server.json
|
||||||
|
sparse-checkout-cone-mode: false
|
||||||
|
|
||||||
|
- name: Check server.json matches the release
|
||||||
|
if: ${{ steps.listed.outputs.listed == 'false' }}
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.v.outputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PYTHON'
|
||||||
|
import json, os
|
||||||
|
manifest = json.load(open("server.json", encoding="utf-8"))
|
||||||
|
version = os.environ["VERSION"]
|
||||||
|
assert manifest["name"] == os.environ["SERVER_NAME"], manifest["name"]
|
||||||
|
found = [manifest["version"], *(p["version"] for p in manifest["packages"])]
|
||||||
|
assert all(v == version for v in found), f"server.json versions {found} != {version}"
|
||||||
|
PYTHON
|
||||||
|
|
||||||
|
- name: Wait for PyPI to serve the release
|
||||||
|
if: ${{ steps.listed.outputs.listed == 'false' }}
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.v.outputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for attempt in $(seq 1 40); do
|
||||||
|
status=$(curl -sS --connect-timeout 10 --max-time 20 -o /dev/null -w '%{http_code}' \
|
||||||
|
"https://pypi.org/pypi/remove-ai-watermarks/$VERSION/json" || true)
|
||||||
|
if [ "$status" = "200" ]; then
|
||||||
|
echo "PyPI serves $VERSION"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Attempt $attempt: PyPI returned HTTP $status for $VERSION"
|
||||||
|
sleep 30
|
||||||
|
done
|
||||||
|
echo "::error::PyPI never served $VERSION"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Publish with a verified mcp-publisher
|
||||||
|
if: ${{ steps.listed.outputs.listed == 'false' }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
curl -fsSL -o mcp-publisher.tar.gz "$PUBLISHER_URL"
|
||||||
|
echo "$PUBLISHER_SHA256 mcp-publisher.tar.gz" | sha256sum -c -
|
||||||
|
tar xzf mcp-publisher.tar.gz mcp-publisher
|
||||||
|
./mcp-publisher login github-oidc
|
||||||
|
./mcp-publisher publish
|
||||||
|
|
||||||
|
- name: Read the entry back
|
||||||
|
if: ${{ steps.listed.outputs.listed == 'false' }}
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.v.outputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for attempt in $(seq 1 10); do
|
||||||
|
status=$(curl -sS --connect-timeout 10 --max-time 20 -o /dev/null -w '%{http_code}' \
|
||||||
|
"$SERVER_PATH/versions/$VERSION" || true)
|
||||||
|
if [ "$status" = "200" ]; then
|
||||||
|
echo "- MCP registry: $SERVER_NAME $VERSION is listed" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Attempt $attempt: registry returned HTTP $status"
|
||||||
|
sleep 15
|
||||||
|
done
|
||||||
|
echo "::error::The registry does not list $SERVER_NAME $VERSION"
|
||||||
|
exit 1
|
||||||
@@ -36,9 +36,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
name: Publish source classifier to Hugging Face
|
||||||
|
|
||||||
|
# Manual only. The derivative model weights live in a GitHub model-freeze
|
||||||
|
# release; the private training corpus and caches never enter this repository.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
confirm:
|
||||||
|
description: "Type publish"
|
||||||
|
required: true
|
||||||
|
weights_tag:
|
||||||
|
description: "GitHub Release tag holding source-pipeline-mlp.npz"
|
||||||
|
required: true
|
||||||
|
default: source-classify-freeze-2026-09-16-r2
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
if: ${{ github.event.inputs.confirm == 'publish' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Download derivative model weights
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
TAG: ${{ github.event.inputs.weights_tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p weights
|
||||||
|
gh release download "$TAG" \
|
||||||
|
--repo "${{ github.repository }}" \
|
||||||
|
--pattern source-pipeline-mlp.npz \
|
||||||
|
--dir weights
|
||||||
|
test -s weights/source-pipeline-mlp.npz
|
||||||
|
|
||||||
|
- name: Verify the frozen artifact
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
expected="$(jq -r .artifact_sha256 docs/source-classify-hf/metrics.json)"
|
||||||
|
test "${#expected}" -eq 64
|
||||||
|
echo "$expected weights/source-pipeline-mlp.npz" | sha256sum --check
|
||||||
|
|
||||||
|
- name: Publish to Hugging Face
|
||||||
|
env:
|
||||||
|
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||||
|
SHA: ${{ github.sha }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${HF_TOKEN:-}" ]; then
|
||||||
|
echo "HF_TOKEN secret is not set" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
uv run --no-project --with huggingface-hub \
|
||||||
|
python scripts/publish_source_classify_hf.py \
|
||||||
|
--src weights \
|
||||||
|
--message "Source-classify freeze (GitHub ${SHA:0:7})"
|
||||||
@@ -10,12 +10,14 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
|
# Model-only prereleases are not Python package releases.
|
||||||
|
if: ${{ startsWith(github.event.release.tag_name, 'v') && !github.event.release.prerelease }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: pypi
|
environment: pypi
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
|
|
||||||
- name: Verify release tag matches package version
|
- name: Verify release tag matches package version
|
||||||
run: |
|
run: |
|
||||||
@@ -38,4 +40,4 @@ jobs:
|
|||||||
# is needed. uv's uploader also accepts Metadata-Version 2.5 -- the permanent
|
# is needed. uv's uploader also accepts Metadata-Version 2.5 -- the permanent
|
||||||
# fix that lets the hatchling pin be relaxed (see pyproject [build-system]).
|
# fix that lets the hatchling pin be relaxed (see pyproject [build-system]).
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
run: uv publish
|
run: uv publish
|
||||||
@@ -32,8 +32,8 @@ jobs:
|
|||||||
skill:
|
skill:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
- name: Sync dev environment
|
- name: Sync dev environment
|
||||||
run: uv sync --frozen --extra dev
|
run: uv sync --frozen --extra dev
|
||||||
- name: Agent skill packaging tests
|
- name: Agent skill packaging tests
|
||||||
|
|||||||
@@ -16,17 +16,32 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
- name: Sync dev environment
|
- name: Sync dev environment
|
||||||
run: uv sync --frozen --extra dev
|
run: uv sync --frozen --extra dev
|
||||||
- name: Check C2PA soft-binding registry snapshot
|
- name: Check C2PA soft-binding registry snapshot
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: uv run python scripts/sync_c2pa_soft_bindings.py --check
|
run: uv run python scripts/sync_c2pa_soft_bindings.py --check
|
||||||
- name: Ruff lint
|
- name: Ruff lint
|
||||||
run: uv run ruff check
|
run: uv run ruff check
|
||||||
- name: Ruff format check
|
- name: Ruff format check
|
||||||
run: uv run ruff format --check
|
run: uv run ruff format --check
|
||||||
|
|
||||||
|
security:
|
||||||
|
name: lockfile vulnerability scan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
|
with:
|
||||||
|
enable-cache: false
|
||||||
|
# Policy and triaged blocks: docs/development.md "Known security-gate blocks".
|
||||||
|
- name: Scan uv.lock with uv-secure
|
||||||
|
run: uvx uv-secure uv.lock
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: test ${{ matrix.os }} py${{ matrix.python-version }}
|
name: test ${{ matrix.os }} py${{ matrix.python-version }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -54,8 +69,8 @@ jobs:
|
|||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.14"
|
python-version: "3.14"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Sync dev environment
|
- name: Sync dev environment
|
||||||
@@ -67,8 +82,8 @@ jobs:
|
|||||||
name: versioned transport contracts py3.14
|
name: versioned transport contracts py3.14
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.14"
|
python-version: "3.14"
|
||||||
- name: Create clean runtime contract environment
|
- name: Create clean runtime contract environment
|
||||||
@@ -89,8 +104,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
- name: Install ffmpeg
|
- name: Install ffmpeg
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Download distributed release identity
|
- name: Download distributed release identity
|
||||||
if: ${{ github.event_name == 'workflow_run' }}
|
if: ${{ github.event_name == 'workflow_run' }}
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||||
with:
|
with:
|
||||||
pattern: distributed-release-*
|
pattern: distributed-release-*
|
||||||
path: distribution-identities
|
path: distribution-identities
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ You are a **principal Python engineer** maintaining a CLI tool and library for r
|
|||||||
|
|
||||||
The project gives users control over provenance marks on content they generated or edited themselves. It does not automatically remove stock-agency, marketplace, classifieds, tiled-preview, or other marks that protect a third party's paid or copyrighted asset.
|
The project gives users control over provenance marks on content they generated or edited themselves. It does not automatically remove stock-agency, marketplace, classifieds, tiled-preview, or other marks that protect a third party's paid or copyrighted asset.
|
||||||
|
|
||||||
- Add visible templates only for AI-generation labels.
|
- Add visible templates only for marks the vendor declares as AI-generation labels, whatever plan hides them; plan-removable product branding ("Made with Gamma") is not one.
|
||||||
- Do not add stock, agency, or classifieds marks to `watermark_registry.py`.
|
- Do not add stock, agency, or classifieds marks to `watermark_registry.py`.
|
||||||
|
- Do not add templates for regulator-defined disclosure icons (EU Code of Practice Annex 1).
|
||||||
|
- LLM text watermarks are out of scope.
|
||||||
- Keep `erase --region` generic and user-directed; do not build an automatic stock-watermark remover on it.
|
- Keep `erase --region` generic and user-directed; do not build an automatic stock-watermark remover on it.
|
||||||
|
|
||||||
Full boundary and legal context: [`docs/legal-and-safety.md`](docs/legal-and-safety.md).
|
Full boundary and legal context: [`docs/legal-and-safety.md`](docs/legal-and-safety.md).
|
||||||
@@ -35,15 +37,17 @@ The gate does not pass `--ignore-unfixed` to uv-secure, so a transitive CVE with
|
|||||||
|
|
||||||
Command, gate, typing, and model-test invariants auto-load from [`.claude/rules/development.md`](.claude/rules/development.md). Environment recovery, CI behavior, and fixture policy live in [`docs/development.md`](docs/development.md).
|
Command, gate, typing, and model-test invariants auto-load from [`.claude/rules/development.md`](.claude/rules/development.md). Environment recovery, CI behavior, and fixture policy live in [`docs/development.md`](docs/development.md).
|
||||||
|
|
||||||
Before a release, read [`docs/release-and-distribution.md`](docs/release-and-distribution.md). Treat the release as complete only after PyPI, Homebrew, the Hugging Face Space, and the ComfyUI Registry are verified; conda is not published. Keep the source-distribution public allowlist.
|
Before a release, read [`docs/release-and-distribution.md`](docs/release-and-distribution.md). Treat the release as complete only after PyPI, Homebrew, the Hugging Face Space, and the ComfyUI Registry are verified; conda is not published. The MCP registry listing publishes itself from `publish-mcp-registry.yml` and does not block a release. Keep the source-distribution public allowlist.
|
||||||
|
|
||||||
The published Agent Skill is [`skills/remove-ai-watermarks/`](skills/remove-ai-watermarks/). Update it in the same change as any CLI edit that alters commands, extras, exit codes, mark keys, or the intended-use boundary. Install and publish steps: [`docs/agent-skill.md`](docs/agent-skill.md).
|
The published Agent Skill is [`skills/remove-ai-watermarks/`](skills/remove-ai-watermarks/). Update it in the same change as any CLI edit that alters commands, extras, exit codes, mark keys, or the intended-use boundary. Install and publish steps: [`docs/agent-skill.md`](docs/agent-skill.md).
|
||||||
|
|
||||||
|
The same skill tree is the Codex plugin (`.codex-plugin/plugin.json`). `remove-ai-watermarks mcp` is a standard-library MCP server that runs one CLI command per tool call; keep it dependency-free, because the MCP registry launches it as a bare `uvx remove-ai-watermarks mcp` that cannot add extras ([`docs/mcp-server.md`](docs/mcp-server.md)).
|
||||||
|
|
||||||
## Module architecture
|
## Module architecture
|
||||||
|
|
||||||
[`docs/module-internals.md`](docs/module-internals.md) is the canonical per-module map, including design decisions, thresholds, calibration history, incident records, and regression guards. Read the relevant section before changing a subsystem.
|
[`docs/module-internals.md`](docs/module-internals.md) is the canonical per-module map, including design decisions, thresholds, calibration history, incident records, and regression guards. Read the relevant section before changing a subsystem.
|
||||||
|
|
||||||
Research and current constraints are routed through [`docs/index.md`](docs/index.md), especially [`docs/known-limitations.md`](docs/known-limitations.md), [`docs/supported-signals.md`](docs/supported-signals.md), [`docs/synthid.md`](docs/synthid.md), and [`docs/watermarking-landscape.md`](docs/watermarking-landscape.md). Pixel photo classification is [`docs/photo-classify.md`](docs/photo-classify.md); it is not `identify`. Classifier research is split between general [`AI-generated image classifiers`](docs/ai-generated-image-classifiers.md) and [`SynthID source classifiers`](docs/synthid-classifiers.md). Other SynthID campaign logs are [`docs/synthid-detector-research.md`](docs/synthid-detector-research.md) and [`docs/synthid-removal-research.md`](docs/synthid-removal-research.md). The pre-split chronological archive is [`docs/synthid-detector-removal-plan.md`](docs/synthid-detector-removal-plan.md). The benchmark kernel and its pinned local oracles (AudioSeal, VideoSeal) cover the audio/video watermark cohorts and research studies: [`docs/benchmark-kernel.md`](docs/benchmark-kernel.md).
|
Research and current constraints are routed through [`docs/index.md`](docs/index.md), especially [`docs/known-limitations.md`](docs/known-limitations.md), [`docs/supported-signals.md`](docs/supported-signals.md), [`docs/synthid.md`](docs/synthid.md), and [`docs/watermarking-landscape.md`](docs/watermarking-landscape.md). Pixel photo classification is [`docs/photo-classify.md`](docs/photo-classify.md); the separate OpenAI/Google/unknown export-pipeline signal is [`docs/source-classify.md`](docs/source-classify.md). Neither runs inside `identify` or constitutes SynthID detection. Classifier research is split between general [`AI-generated image classifiers`](docs/ai-generated-image-classifiers.md) and [`SynthID source classifiers`](docs/synthid-classifiers.md). Other SynthID campaign logs are [`docs/synthid-detector-research.md`](docs/synthid-detector-research.md) and [`docs/synthid-removal-research.md`](docs/synthid-removal-research.md). The pre-split chronological archive is [`docs/synthid-detector-removal-plan.md`](docs/synthid-detector-removal-plan.md). The benchmark kernel and its pinned local oracles (AudioSeal, Perth, PixelSeal, VideoSeal) cover image, audio, and video watermark cohorts and research studies: [`docs/benchmark-kernel.md`](docs/benchmark-kernel.md).
|
||||||
|
|
||||||
## Data safety
|
## Data safety
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,15 @@ Remove AI provenance marks from images and video you generated yourself:
|
|||||||
- invisible pixel watermarks through direct local-format disruption or diffusion regeneration;
|
- invisible pixel watermarks through direct local-format disruption or diffusion regeneration;
|
||||||
- C2PA, EXIF, XMP, IPTC, and related AI metadata.
|
- C2PA, EXIF, XMP, IPTC, and related AI metadata.
|
||||||
|
|
||||||
|
| Before | After |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install "remove-ai-watermarks[visible]" # CLI for visible marks and metadata
|
||||||
|
npx skills add wiltodelta/remove-ai-watermarks # agent skill for Claude Code, Codex, Cursor, and other agents
|
||||||
|
```
|
||||||
|
|
||||||
Video support covers provenance identification, complete visible-plus-metadata
|
Video support covers provenance identification, complete visible-plus-metadata
|
||||||
cleaning, directory batches, visible Sora, Veo, Seedance, Doubao, Dola,
|
cleaning, directory batches, visible Sora, Veo, Seedance, Doubao, Dola,
|
||||||
Hailuo AI, and Kling AI mark removal, and oracle-certified VAE regeneration for video SynthID
|
Hailuo AI, and Kling AI mark removal, and oracle-certified VAE regeneration for video SynthID
|
||||||
@@ -24,9 +33,12 @@ removal.
|
|||||||
[](https://github.com/sponsors/wiltodelta)
|
[](https://github.com/sponsors/wiltodelta)
|
||||||
[](https://skills.sh/wiltodelta/remove-ai-watermarks)
|
[](https://skills.sh/wiltodelta/remove-ai-watermarks)
|
||||||
|
|
||||||
> This project is for lawful use on content you own. It does not target stock
|
> This is a research project on AI provenance signals, for lawful use on content
|
||||||
> agency previews or other watermarks that protect third party paid content.
|
> you own. It is provided as is, without warranty or liability, and each user is
|
||||||
> See [scope, safety, and legal notes](docs/legal-and-safety.md).
|
> responsible for how they use it. It does not target stock agency previews or
|
||||||
|
> other watermarks that protect third party paid content. Some jurisdictions,
|
||||||
|
> including China, restrict removing AI labels or supplying tools that do. See
|
||||||
|
> [scope, safety, and legal notes](docs/legal-and-safety.md).
|
||||||
|
|
||||||
## Choose what you want to do
|
## Choose what you want to do
|
||||||
|
|
||||||
@@ -59,6 +71,7 @@ specialized Python API can inspect and disrupt the validated local
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Metadata inspection and stripping | `remove-ai-watermarks` |
|
| Metadata inspection and stripping | `remove-ai-watermarks` |
|
||||||
| Photograph AI-versus-camera classification | `remove-ai-watermarks[classify]` |
|
| Photograph AI-versus-camera classification | `remove-ai-watermarks[classify]` |
|
||||||
|
| OpenAI/Google/unknown source-export classification | `remove-ai-watermarks[source-classify]` |
|
||||||
| Visible detection and removal | `remove-ai-watermarks[visible]` |
|
| Visible detection and removal | `remove-ai-watermarks[visible]` |
|
||||||
| Visible video processing | `remove-ai-watermarks[video]` |
|
| Visible video processing | `remove-ai-watermarks[video]` |
|
||||||
| Video SynthID removal | `remove-ai-watermarks[video,diffusion]` |
|
| Video SynthID removal | `remove-ai-watermarks[video,diffusion]` |
|
||||||
@@ -68,7 +81,7 @@ specialized Python API can inspect and disrupt the validated local
|
|||||||
| Every production feature available on the active Python | `remove-ai-watermarks[all]` |
|
| Every production feature available on the active Python | `remove-ai-watermarks[all]` |
|
||||||
|
|
||||||
Lower-level and specialized extras include `pixels`, `heif`, `trustmark`,
|
Lower-level and specialized extras include `pixels`, `heif`, `trustmark`,
|
||||||
`migan`, `lama`, `diffusion`, and `classify-onnx`. The
|
`migan`, `lama`, `diffusion`, `classify-onnx`, and `source-classify`. The
|
||||||
[installation guide](docs/installation.md#feature-extras) documents their exact
|
[installation guide](docs/installation.md#feature-extras) documents their exact
|
||||||
dependency composition, Python compatibility, and model requirements.
|
dependency composition, Python compatibility, and model requirements.
|
||||||
|
|
||||||
@@ -96,6 +109,19 @@ remove-ai-watermarks classify image.png
|
|||||||
|
|
||||||
Guide: [photo pixel classification](docs/photo-classify.md).
|
Guide: [photo pixel classification](docs/photo-classify.md).
|
||||||
|
|
||||||
|
For a lightweight, abstaining OpenAI/Google/unknown source-export signal after
|
||||||
|
metadata removal, use the separate Python API. It is not a SynthID detector:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import remove_ai_watermarks as raiw
|
||||||
|
|
||||||
|
result = raiw.classify_source("image.png")
|
||||||
|
print(result.label, result.reason)
|
||||||
|
```
|
||||||
|
|
||||||
|
Install `remove-ai-watermarks[source-classify]`. Guide:
|
||||||
|
[source-pipeline classification](docs/source-classify.md).
|
||||||
|
|
||||||
Signed provenance is the supported route for SynthID and `identify` reads it.
|
Signed provenance is the supported route for SynthID and `identify` reads it.
|
||||||
There is no local SynthID pixel detector in the package. Research on a
|
There is no local SynthID pixel detector in the package. Research on a
|
||||||
periodic lattice expert is in `scripts/synthid_runtime/` and
|
periodic lattice expert is in `scripts/synthid_runtime/` and
|
||||||
@@ -188,9 +214,12 @@ preserved through a timestamped in-memory NUT bridge instead of being flattened
|
|||||||
to the average frame rate. Non-zero source start timestamps are retained
|
to the average frame rate. Non-zero source start timestamps are retained
|
||||||
together with the copied audio offset. The default `--mark auto`
|
together with the copied audio offset. The default `--mark auto`
|
||||||
scans all providers in one decode pass and selects the first stable match in
|
scans all providers in one decode pass and selects the first stable match in
|
||||||
the specificity order shown below. Pass an explicit mark to restrict detection
|
the specificity order shown below. Valid Google AI-video provenance breaks a
|
||||||
to one provider.
|
Sora/Veo cross-match in favor of Veo when the diamond is nested in the Sora
|
||||||
Sora covers the moving Sora 2 mascot and wordmark. Veo covers both the current
|
region or Veo persists through the complete clip while Sora does not.
|
||||||
|
Pass an explicit mark to restrict detection to one provider.
|
||||||
|
Sora covers the moving Sora 2 mascot, with or without the wordmark; mascot-only
|
||||||
|
matches also require the complete two-eye shape. Veo covers both the current
|
||||||
four-point diamond and the legacy `Veo` text. Seedance covers the fixed boxed
|
four-point diamond and the legacy `Veo` text. Seedance covers the fixed boxed
|
||||||
`AI` label, Dola covers the fixed `Dola AI` text, Hailuo AI covers the composite
|
`AI` label, Dola covers the fixed `Dola AI` text, Hailuo AI covers the composite
|
||||||
`MINIMAX | hailuo AI` label, and Kling AI covers the bottom-right `KLING AI` or
|
`MINIMAX | hailuo AI` label, and Kling AI covers the bottom-right `KLING AI` or
|
||||||
@@ -254,11 +283,8 @@ features, and development setup.
|
|||||||
|
|
||||||
### Visible Gemini mark
|
### Visible Gemini mark
|
||||||
|
|
||||||
| Before | After |
|
The `after` raster at the top of this page is generated from the tracked `before`
|
||||||
| --- | --- |
|
raster by the public path:
|
||||||
|  |  |
|
|
||||||
|
|
||||||
The `after` raster is generated from the tracked `before` raster by the public path:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run remove-ai-watermarks visible demo_banana_before.png \
|
uv run remove-ai-watermarks visible demo_banana_before.png \
|
||||||
@@ -350,11 +376,17 @@ remove-ai-watermarks batch ./images --mode all
|
|||||||
Visible mark support includes:
|
Visible mark support includes:
|
||||||
|
|
||||||
- Google Gemini and Nano Banana visible sparkle watermark;
|
- Google Gemini and Nano Banana visible sparkle watermark;
|
||||||
- Doubao, the Jimeng wordmark and top-left `AI生成` pill, Qwen, Kling AI,
|
- Doubao, the Jimeng wordmark and top-left `AI生成` layouts, Qwen, Kling AI,
|
||||||
Yuanbao, Baidu, LiblibAI's bottom-center wordmark and compact top-left pill,
|
Yuanbao, Dola, WorkBuddy, Baidu, LiblibAI's bottom-center wordmark and compact
|
||||||
and RunningHub labels;
|
top-left pill, and RunningHub labels;
|
||||||
- one calibrated Microsoft top-right white AI-badge variant;
|
- calibrated Microsoft top-right white and beige AI-badge layouts;
|
||||||
- one calibrated Samsung Galaxy AI label variant.
|
- calibrated Italian and Korean Samsung Galaxy AI disclosures;
|
||||||
|
- a generic brand-less bottom-right `AI生成` fallback for OS/gallery-level
|
||||||
|
AI-edit stamps that carry no vendor wordmark of their own (vivo and Xiaomi
|
||||||
|
Gallery confirmed); synthetic-only calibration, weaker evidence than the
|
||||||
|
vendor-tuned detectors above;
|
||||||
|
- the OpenArt wordmark, placed at the frame center rather than a corner;
|
||||||
|
uncalibrated (one confirmed real case, no captured corpus).
|
||||||
|
|
||||||
Metadata and provenance inspection covers C2PA, EXIF, XMP, IPTC, common
|
Metadata and provenance inspection covers C2PA, EXIF, XMP, IPTC, common
|
||||||
generator parameters in image and video containers, China TC260 AIGC labels,
|
generator parameters in image and video containers, China TC260 AIGC labels,
|
||||||
@@ -450,9 +482,23 @@ Claude Code can add this repository as a plugin marketplace:
|
|||||||
/plugin install remove-ai-watermarks@remove-ai-watermarks
|
/plugin install remove-ai-watermarks@remove-ai-watermarks
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Codex reads the same skill through [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json).
|
||||||
The skill refuses stock-agency and other third-party paid-asset marks. See
|
The skill refuses stock-agency and other third-party paid-asset marks. See
|
||||||
[the agent skill guide](docs/agent-skill.md).
|
[the agent skill guide](docs/agent-skill.md).
|
||||||
|
|
||||||
|
## MCP server
|
||||||
|
|
||||||
|
`remove-ai-watermarks mcp` serves `identify`, `remove` and `batch` as Model
|
||||||
|
Context Protocol tools over stdio, each a thin wrapper around one CLI command:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"mcpServers": {"remove-ai-watermarks": {"command": "uvx", "args": ["remove-ai-watermarks", "mcp"]}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [the MCP server guide](docs/mcp-server.md).
|
||||||
|
|
||||||
|
<!-- mcp-name: io.github.wiltodelta/remove-ai-watermarks -->
|
||||||
|
|
||||||
## ComfyUI
|
## ComfyUI
|
||||||
|
|
||||||
The separate
|
The separate
|
||||||
@@ -464,13 +510,15 @@ invisible removal.
|
|||||||
|
|
||||||
- A missing local signal means unknown, not clean. Proprietary pixel
|
- A missing local signal means unknown, not clean. Proprietary pixel
|
||||||
watermarks may remain after metadata has been stripped.
|
watermarks may remain after metadata has been stripped.
|
||||||
|
[What each path leaves behind](docs/known-limitations.md#what-each-path-leaves-behind)
|
||||||
|
lists, per command, what may remain and how to check it.
|
||||||
- Visible removal reconstructs a small region. Results depend on the background
|
- Visible removal reconstructs a small region. Results depend on the background
|
||||||
and selected fill backend.
|
and selected fill backend.
|
||||||
- Invisible removal changes the whole image and may alter faces, text, or fine
|
- Invisible removal changes the whole image and may alter faces, text, or fine
|
||||||
detail.
|
detail.
|
||||||
- Visible video removal recognizes the moving Sora 2 wordmark, the current Veo
|
- Visible video removal recognizes the moving Sora 2 wordmark, the current Veo
|
||||||
diamond plus legacy `Veo` text, the Seedance boxed `AI` label, and the fixed
|
diamond plus legacy `Veo` text, the Seedance boxed `AI` label, and the fixed
|
||||||
Doubao, Dola, Hailuo AI, and Kling AI labels. It does not recognize the older Sora Turbo
|
Doubao, Dola, Hailuo AI, Vidu, and Kling AI labels. It does not recognize the older Sora Turbo
|
||||||
corner swirl or unregistered layouts from those providers.
|
corner swirl or unregistered layouts from those providers.
|
||||||
The classical OpenCV backend can smear structured backgrounds; use MI-GAN or
|
The classical OpenCV backend can smear structured backgrounds; use MI-GAN or
|
||||||
LaMa when recovery quality matters.
|
LaMa when recovery quality matters.
|
||||||
@@ -545,7 +593,8 @@ bash maintain.sh
|
|||||||
```
|
```
|
||||||
|
|
||||||
See [module internals](docs/module-internals.md) before changing a subsystem
|
See [module internals](docs/module-internals.md) before changing a subsystem
|
||||||
with documented invariants.
|
with documented invariants. Report vulnerabilities as described in
|
||||||
|
[SECURITY.md](SECURITY.md).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Security policy
|
||||||
|
|
||||||
|
## Supported versions
|
||||||
|
|
||||||
|
Security fixes go into the latest release. Older releases are not patched;
|
||||||
|
upgrade through the channel you installed from (PyPI or Homebrew).
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
Report vulnerabilities privately through
|
||||||
|
[GitHub private vulnerability reporting](https://github.com/wiltodelta/remove-ai-watermarks/security/advisories/new).
|
||||||
|
Do not open a public issue for an unpatched vulnerability. Include the affected
|
||||||
|
version, the command or API call, and a minimal input that reproduces it.
|
||||||
|
|
||||||
|
Vulnerabilities in a dependency, such as a CVE in a package from `uv.lock`, can
|
||||||
|
be reported as a regular issue when the advisory is already public.
|
||||||
|
|
||||||
|
## Dependency vulnerabilities
|
||||||
|
|
||||||
|
Dependabot raises alerts and update pull requests for `uv.lock` and GitHub
|
||||||
|
Actions, and CI scans `uv.lock` with
|
||||||
|
[`uv-secure`](https://github.com/owenlamont/uv-secure) on every push and pull
|
||||||
|
request. A vulnerable dependency is upgraded to a fixed release; advisories
|
||||||
|
without one are tracked under
|
||||||
|
[Known security-gate blocks](docs/development.md#known-security-gate-blocks).
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="512" height="512" fill="none" stroke="url(#lg)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!--
|
||||||
|
The raiw.cc brand mark: the Lucide "sparkle" and "x" glyphs.
|
||||||
|
Lucide, https://lucide.dev, ISC License, Copyright (c) Lucide Contributors.
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||||
|
with or without fee is hereby granted, provided that the above copyright notice
|
||||||
|
and this permission notice appear in all copies.
|
||||||
|
-->
|
||||||
|
<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z" fill="url(#lg)" fill-opacity="0.15"/>
|
||||||
|
<g transform="translate(1 17) scale(0.3)" stroke-width="5">
|
||||||
|
<path d="M18 6 6 18"/>
|
||||||
|
<path d="m6 6 12 12"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="lg" x1="0" y1="0" x2="24" y2="24">
|
||||||
|
<stop stop-color="#a78bfa"/>
|
||||||
|
<stop offset="1" stop-color="#06b6d4"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -10,6 +10,11 @@ data/
|
|||||||
visible/ Per-mark example gallery: one synthetic canonical example per
|
visible/ Per-mark example gallery: one synthetic canonical example per
|
||||||
registered mark plus documented provider originals where available
|
registered mark plus documented provider originals where available
|
||||||
(see fixtures/visible/README.md)
|
(see fixtures/visible/README.md)
|
||||||
|
synthetic/ Frozen synthetic renders that unit tests stamp onto frames, each
|
||||||
|
produced by the script named in the test that loads it
|
||||||
|
captures/
|
||||||
|
2026-09/ Provider outputs from the maintainer's own accounts, stored as
|
||||||
|
served, indexed by manifest.csv (see its README)
|
||||||
calibration/
|
calibration/
|
||||||
<vendor>/ Minimal controlled inputs needed to rebuild detector assets
|
<vendor>/ Minimal controlled inputs needed to rebuild detector assets
|
||||||
synthid/
|
synthid/
|
||||||
@@ -51,9 +56,15 @@ evaluation outputs outside the repository.
|
|||||||
`evaluations/engine-selection/` is the bounded exception for ORIGINAL study
|
`evaluations/engine-selection/` is the bounded exception for ORIGINAL study
|
||||||
inputs: its manifest tracks a prompt-matched OpenAI/Meta content matrix, not
|
inputs: its manifest tracks a prompt-matched OpenAI/Meta content matrix, not
|
||||||
generated model outputs or watermark-oracle evidence.
|
generated model outputs or watermark-oracle evidence.
|
||||||
6. Keep third-party fixture license notices in `licenses/`, outside directories
|
6. Put provider captures that no test reads in `captures/<yyyy-mm>/`, with a
|
||||||
|
`manifest.csv` row per capture. A capture that is also a fixture is stored
|
||||||
|
once, in `fixtures/`, and its manifest row points there. Request a new
|
||||||
|
capture only for a service x vendor pair not yet captured, or for a
|
||||||
|
service's own model: another version of a known pair adds no provenance
|
||||||
|
evidence.
|
||||||
|
7. Keep third-party fixture license notices in `licenses/`, outside directories
|
||||||
that tests enumerate as media inputs.
|
that tests enumerate as media inputs.
|
||||||
7. Runtime detector assets belong in `src/remove_ai_watermarks/assets/`.
|
8. Runtime detector assets belong in `src/remove_ai_watermarks/assets/`.
|
||||||
Unregistered research candidates belong in
|
Unregistered research candidates belong in
|
||||||
`scripts/assets/visible-mark-candidates/` so they are not shipped in the
|
`scripts/assets/visible-mark-candidates/` so they are not shipped in the
|
||||||
wheel.
|
wheel.
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Provider captures, September 2026
|
||||||
|
|
||||||
|
Original outputs downloaded from the maintainer's own accounts between
|
||||||
|
2026-09-23 and 2026-09-25 and cleared for publication by the maintainer. The
|
||||||
|
services are the Gemini API, xAI Grok, Vidu, Wan, Apple Image Playground,
|
||||||
|
YouTube (re-encodes of our own uploads), and the paid Higgsfield, Runway and
|
||||||
|
Dreamina plans. Every prompt is a neutral coffee-mug scene; Higgsfield
|
||||||
|
Genjutsu and the video-edit samples take our own Higgsfield outputs as input.
|
||||||
|
|
||||||
|
Files are stored byte-for-byte as served. Nothing is re-encoded, because the
|
||||||
|
provenance under study (C2PA, TC260 labels, `hf-job-id` chunks) lives in the
|
||||||
|
container bytes.
|
||||||
|
|
||||||
|
`manifest.csv` lists every capture once, including the ones that are already
|
||||||
|
test fixtures: those rows point at their `data/fixtures/` path instead of a
|
||||||
|
second copy (`stored` is `existing`). Columns:
|
||||||
|
|
||||||
|
| Column | Meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| `sha256` | Digest of the stored file |
|
||||||
|
| `path` | Canonical repository path |
|
||||||
|
| `stored` | `new` for files in this directory, `existing` for a fixture already tracked |
|
||||||
|
| `service` | Service the file was downloaded from |
|
||||||
|
| `source_name` | Local capture name, which names the model |
|
||||||
|
| `captured` | Capture date |
|
||||||
|
| `ai_from_metadata`, `platform` | `identify` (images) or `identify_video` (videos) verdict from metadata only, at capture time |
|
||||||
|
|
||||||
|
What each service does to the upstream provenance, and the per-model
|
||||||
|
findings, are summarized in the live-captures table of
|
||||||
|
[`docs/watermarking-landscape.md`](../../../docs/watermarking-landscape.md).
|
||||||
|
The `google-photos/` files are the maintainer's own iPhone photos edited in
|
||||||
|
Google Photos on iOS (an Ask edit replacing a dog with a cat, an eraser edit, and
|
||||||
|
two other AI edits), published unmodified with their EXIF GPS position by the
|
||||||
|
maintainer's decision on 2026-09-25. Google's checker in Gemini found SynthID in
|
||||||
|
all four with the metadata stripped. The Apple Clean Up photo is not stored.
|
||||||
|
The `google-photos-web/` files are a negative control from the Google Photos web
|
||||||
|
editor (2026-09-26): one of the maintainer's photos, downloaded untouched and after
|
||||||
|
Enhance, Sky (Vivid) and a square crop. The web editor offers no generative tool and
|
||||||
|
writes no Content Credentials, so none of the four carries C2PA, IPTC or an AI
|
||||||
|
credit, and Google's checker in Gemini found no SynthID in any of them (three
|
||||||
|
explicit negatives; the crop drew a reply that did not name SynthID and is recorded
|
||||||
|
as indeterminate).
|
||||||
|
|
||||||
|
- License: repository license; generated and explicitly cleared by the maintainer
|
||||||
|
After Width: | Height: | Size: 6.2 MiB |
|
After Width: | Height: | Size: 6.8 MiB |
|
After Width: | Height: | Size: 7.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 395 KiB |
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 679 KiB |
|
After Width: | Height: | Size: 636 KiB |
|
After Width: | Height: | Size: 603 KiB |
|
After Width: | Height: | Size: 627 KiB |
|
After Width: | Height: | Size: 715 KiB |
|
After Width: | Height: | Size: 529 KiB |
|
After Width: | Height: | Size: 420 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 691 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 945 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 4.7 MiB |
|
After Width: | Height: | Size: 5.9 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 4.6 MiB |
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 4.9 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 6.2 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 4.9 MiB |
|
After Width: | Height: | Size: 1.6 MiB |