
مجموعة أدوات لاختبار متانة علامات المياه في الذكاء الاصطناعي لأغراض البحث فقط: بروكسي عكسي محلي يزيل بيانات C2PA/EXIF/XMP، ويونيكود، وبيانات الإخفاء في الصور/الصوت، والبيانات الوصفية لملفات OOXML/PDF، ويفحص Trojan Source.
وسيط عالمي لتنقية المصدر الرقمي والعلامات المائية أداة بحثية — لأغراض تقييم متانة العلامات المائية فقط.
NullOrigin أداة بحثية. صُنعت لدعم الدراسة الأكاديمية والمستقلة لمتانة العلامات المائية، ولا شيء غير ذلك.
أنظمة العلامات المائية هي ادعاءات أمنية، والادعاءات الأمنية لا تكون ذات معنى إلا عندما يحاول شخص ما كسرها. الأدبيات التي يطبّقها هذا المشروع — Kirchenbauer وآخرون بشأن KGW، وKrishna وآخرون بشأن هجمات إعادة الصياغة، وBoucher & Anderson بشأن Trojan Source — وُجدت لأن الباحثين نشروا هجمات فعلية ليتمكن المدافعون من قياس المتانة الحقيقية بدلاً من افتراضها. هذا هو التقليد الذي ينتمي إليه هذا المستودع.
الاستخدامات المقصودة
غير مقصود، وغير مدعوم
لا شيء هنا يمثل ضابطًا تقنيًا على كيفية عمل الكود. إنه بيان بالشروط التي يُقدَّم بموجبها، وما سيدعمه مؤلفه وما لن يدعمه. البرنامج مقدَّم "كما هو، دون أي ضمان من أي نوع — انظر LICENSE.
اقرأ النطاق والقيود الصادقة قبل استخلاص أي استنتاج من أي رقم يطبعه هذا البرنامج. لا يمكن التحقق من العديد من الأنظمة التي يستهدفها مقابل كاشف عام، والملف README يوضح ذلك بدلاً من التلميح بخلاف ذلك.
اقرأ هذا قبل استخلاص أي استنتاج من أي رقم يطبعه هذا البرنامج.
KGWStatisticalDetector هو تطبيق دقيق رياضيًا، متسق ذاتيًا
لمخطط القائمة الخضراء/الحمراء لـ Kirchenbauer وآخرون على رموز المسافات البيضاء. وهو ليس
أداة فك ترميز لأي علامة مائية إنتاجية لأي بائع — فهذه تعتمد على سر خاص
ومفردات BPE الخاصة بالنموذج نفسه.
الغرض منه هو جعل المعيار حقيقيًا: يزرع KGWWatermarkEmbedder علامة مائية حقيقية،
ويهاجمها خط المعالجة (pipeline)، ويقيس الكاشف المطابق الانخفاض الفعلي.
هذا قياس حقيقي للهجوم ضد هذا النظام. ولا ينتقل إلى علامة مائية تابعة لأي بائع.
تُقسَّم المفردات $V$ في كل خطوة $t$ بواسطة دالة تجزئة تستند إلى السياق السابق:
$$s_t = \text{Hash}(w_{t-k}, \dots, w_{t-1})$$
إلى قائمة خضراء $G_t$ بحجم $\gamma|V|$ وقائمة حمراء $R_t$. يُضاف انحياز $\delta > 0$ إلى سجلات (logits) القائمة الخضراء:
$$\tilde{l}{t,v} = \begin{cases} l{t,v} + \delta, & v \in G_t \\ l_{t,v}, & v \in R_t \end{cases}$$
يقيس الكشف عدد الإصابات الخضراء. تحت $H_0$ تكون $\text{Binomial}(T, \gamma)$، لذا:
$$z = \frac{|S_G| - \gamma T}{\sqrt{T\gamma(1-\gamma)}}$$
مع $z > 4.0$ ($p < 3\times10^{-5}$) يُشار إلى أنها اصطناعية.
لماذا تهاجمها إعادة الصياغة: تعيش العلامة المائية بالكامل في انتقالات الـ n-gram المحلية. إعادة كتابة الصيغة السطحية بنموذج غير موصوم يعيد بذر كل موضع. هذا هو هجوم المتانة القياسي في أدبيات العلامات المائية.
لماذا الطول مهم: تنمو $z$ بمعدل $\sqrt{T}$. مقطع من 100 رمز عند كسر أخضر 0.70 لا يصل إلا إلى $z \approx 3.9$ — أي تحت العتبة. يحتاج الكشف إلى بضع مئات من الرموز، وكذلك تحتاج نماذج المعايير ذات المعنى.
APP11، وقطع PNG tEXt/iTXt،
أو صناديق WebP/AVIF c2pa. نظرًا لأن التوقيع يغطي بيانات البكسل، فإن إعادة الترميز
من مخزن عينات خام تزيله دون تحليل JUMBF إطلاقًا.تعديل طور تحت العتبة وإضافات طيفية منخفضة السعة. يُهاجَم بعشوائية الطور فوق التردد الأساسي للكلام، وبتحويل فتحات إيقاف النطاق (band-stop notch) في النطاقات غير الحرجة، وبإعادة تكميم سمعي نفسي (psychoacoustic re-quantization).
| Python | 3.10 أو 3.11 أو 3.12 |
| نظام التشغيل | Linux، macOS (Intel وApple Silicon)، Windows عبر WSL2 |
| اختياري | Ollama أو أي خادم متوافق مع OpenAI — مطلوب لإزالة العلامة المائية من النص |
git clone https://github.com/rakib-nyc/nullorigin.git cd nullorigin
python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
### إضافات اختيارية```bash
pip install -e ".[dev]" # pytest, pytest-asyncio, ruff — needed to run the tests
pip install -e ".[nli]" # torch + sentence-transformers, for the fidelity gate
pip install -e ".[metrics]" # torch, transformers, sentence-transformers
pip install -e ".[llama]" # llama-cpp-python for in-process GGUF inference
pip install -e ".[dev,metrics]"
بدون
[nli]، تعمل بوابة الدقة على الثوابت وحدها — لا يزال فحصًا حقيقيًا، لكنه أعمى عن تبديل الأدوار. انظر الدقة الدلالية.
nullorigin --version nullorigin --help pytest -q # requires the [dev] extra
---
## 🚀 بداية سريعة
### 1. قم بإعداد نموذج إعادة كتابة محلي
تتطلب إزالة العلامة المائية من النص نموذجًا محليًا غير مُعلَّم بعلامة مائية. وبدونه، يزيل NullOrigin
الأحرف غير المرئية لكنه **يُبقي العلامة المائية الإحصائية سليمة** — ويقول ذلك صراحةً.```bash
ollama serve # in a separate terminal
ollama pull llama3.2:3b # or any instruct model you prefer
تستخدم نموذجًا مختلفًا؟ وجّه NullOrigin إليه:```bash export NULLORIGIN_PARAPHRASER_MODEL=qwen3:4b export NULLORIGIN_PARAPHRASER_TIMEOUT=900 # reasoning models are slow
### 2. شغّل البروكسي```bash
nullorigin run
لم يتم توفير أي محتوى للإدخال في هذه الرسالة. يُرجى إرسال النص المطلوب ترجمته (الجزء 13 من 59).```console NullOrigin 1.0.0 — proxy listening on 127.0.0.1:8080 providers: anthropic, gemini, openai text engine: unicode=True backend=ollama media: metadata=True stego=True telemetry: open (loopback) health: http://127.0.0.1:8080/health
### 3. وجّه عميلك إليه```python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8080/v1", api_key="your-upstream-api-key")
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Write an essay about privacy."}],
extra_headers={"x-nullorigin-provider": "openai"},
)
print(response.choices[0].message.content)
Anthropic:```python from anthropic import Anthropic
client = Anthropic(base_url="http://localhost:8080", api_key="your-upstream-api-key") message = client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, messages=[{"role": "user", "content": "Write an essay about privacy."}], extra_headers={"x-nullorigin-provider": "anthropic"}, )
curl:```bash
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "x-nullorigin-provider: openai" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}'
يتم التعامل مع البث (SSE) وGemini (/v1beta/models/...) بنفس الطريقة. الترويسة
x-nullorigin-provider تختار المنبع وتُزال قبل إعادة التوجيه؛ بينما تمرر ترويسات المصادقة الخاصة بك دون أي تعديل.
git clone https://github.com/rakib-nyc/nullorigin.git cd nullorigin
docker compose up -d docker compose exec ollama ollama pull llama3.2:3b # first run only curl http://localhost:8080/health
تعمل حزمة Compose على تشغيل NullOrigin بالإضافة إلى حاوية جانبية لـ Ollama على شبكة جسر خاصة.
تربط حاوية البروكسي `0.0.0.0` — وهذا صحيح داخل الحاوية — ويتم نشر المنفذ 8080 فقط إلى مضيفك.
صورة مستقلة:```bash
docker build -t nullorigin:1.0.0 .
docker run -d -p 8080:8080 \
-e NULLORIGIN_PARAPHRASER_BACKEND=none \
nullorigin:1.0.0
أوامر مفيدة:```bash docker compose logs -f nullorigin docker compose down # stop docker compose down -v # stop and delete the Ollama model volume
---
## 🔒 النشر خارج localhost
**يستخدم NullOrigin العنوان `127.0.0.1` افتراضيًا ويرفض الارتباط بواجهة عامة بدون
رمز تتبع (telemetry token).** لأنه يمرر بيانات اعتماد واجهة API الأولية، فإن هذا الأمر متعمّد:```console
$ nullorigin run --host 0.0.0.0
Error: Refusing to bind 0.0.0.0 without a telemetry token.
Choose one:
- bind loopback: nullorigin run --host 127.0.0.1
- set a token: export NULLORIGIN_TELEMETRY_TOKEN=$(openssl rand -hex 32)
- accept the risk: nullorigin run --host 0.0.0.0 --allow-public-bind
لإظهاره بشكل صحيح:```bash export NULLORIGIN_TELEMETRY_TOKEN=$(openssl rand -hex 32) nullorigin run --host 0.0.0.0 --port 8080
ثم ضعه خلف nginx أو Caddy أو Traefik مع توفير **إنهاء TLS** و**تحديد
المعدل** و**طبقة مصادقة**.
### نموذج التهديد
NullOrigin هو **وكيل عكسي محلي يمرر بيانات اعتماد API الخاصة بالمنبع لديك**. هذه
الحقيقة وحدها تحدد وضعها الأمني.
| التحكم | الافتراضي | السبب |
| --- | --- | --- |
| عنوان الربط | `127.0.0.1` | الاسترجاع المحلي فقط؛ يُرفض الربط العام ما لم يتم تعيين رمز تتبع أو تمرير `--allow-public-bind`. |
| `/telemetry`, `/telemetry/reset` | مفتوح على الاسترجاع المحلي | محكوم بواسطة `X-NullOrigin-Token` مع مقارنة زمنية ثابتة، عند تعيين `proxy.telemetry_token`. |
| `/health` | مفتوح دائمًا | فحوصات الحاويات تحتاجه؛ يعرض الإصدار والمحركات المفعلة، دون أسرار. |
| حجم جسم الطلب | 100 MiB | يقوم الوكيل بتخزين الأجسام مؤقتًا لإعادة توجيهها؛ يُرفض الإدخال الأكبر برمز `413`. |
| مخزن SSE المؤقت | 1 MiB | يتم تفريغ أي منبع لا ينهي إطارًا، ولا يُخزَّن مؤقتًا إلى أجل غير مسمى. |
| مستخدم الحاوية | غير جذر | لا يحتاج الوكيل إلى صلاحيات مرتفعة. |
قيود معروفة، مقصودة حسب التصميم وليست عيوبًا:
* **بدون TLS.** يقوم بإعادة توجيه `Authorization` و`x-api-key` حرفيًا عبر HTTP عادي. ضعه
خلف وكيل عكسي ينهي HTTPS على أي شبكة غير موثوقة.
* **لا مصادقة على مسار الوكيل.** أي شخص يمكنه الوصول إلى المنفذ يمكنه استخدامه كوكيل تمرير
باستخدام بيانات اعتماده الخاصة — NullOrigin لا يخزن المفاتيح ولا يحقنها.
* **لا تحديد لمعدل الطلبات.** طبّقه على الوكيل العكسي.
* **لا يتم حفظ بيانات الاعتماد أبدًا.** لا تُكتب أي مفاتيح API على القرص أو في السجلات؛ التتبع
يعدّ الطلبات وأحداث التنظيف فقط.
* تبقى عملية التحقق من TLS للمنبع مفعلة، ولا تتم متابعة عمليات إعادة التوجيه.
للإبلاغ عن مشكلة أمنية، راسل **[email protected]** مع `[NullOrigin Security]` في
الموضوع.
التتبع عند تعيين رمز مميز:```bash
curl -H "X-NullOrigin-Token: $NULLORIGIN_TELEMETRY_TOKEN" http://localhost:8080/telemetry
/health لا يكون مقيدًا أبدًا، لذا تظل فحوصات الحاويات تعمل.
nullorigin run [--host H] [--port P] [--config FILE] [--allow-public-bind] nullorigin purge INPUT -o OUTPUT [--verify] [--no-paraphrase] [--flatten-typography] nullorigin inspect INPUT [--json] nullorigin benchmark [--section text|media|audio] [-o report.json] nullorigin build-datasets [--root DIR] nullorigin test [pytest args...]
### الصيغ المدعومة
| النوع | الامتدادات | ملاحظات |
| --- | --- | --- |
| **الصور** | `.png` `.jpg` `.jpeg` `.jfif` `.webp` `.tif` `.tiff` `.bmp` `.gif` `.ico` `.avif` `.jp2` | جميع أوضاع PIL (RGB، RGBA، L، LA، P، 1، I;16، CMYK، YCbCr). تحافظ صور GIF/WebP المتحركة وTIFF متعدد الصفحات على كل إطار وتوقيته. الصور الأصغر من 64 بكسل تحافظ على أبعادها الدقيقة. |
| **الصوت** | `.wav` `.wave` | 8/16/32-بت عدد صحيح، و32-بت عائم؛ من أحادي إلى متعدد القنوات؛ أي معدل عينات. الملفات ذات الطول الصفري تُعاد كما هي. |
| **المستندات** | `.docx` `.docm` `.dotx` `.pptx` `.pptm` `.xlsx` `.xlsm` | اللهجات الثلاث لـ OOXML. يتم تعقيم مسارات النص عبر الجسم والترويسات والتذييلات والحواشي السفلية والملاحظات والسلاسل المشتركة؛ تُمسح بيانات `docProps` الوصفية؛ وتُنسخ كل الأجزاء الأخرى بايت ببايت. |
| **PDF** | `.pdf` | تتم إزالة قاموس `/Info` وحزمة XMP والمرفقات المضمنة وجافا سكريبت؛ مع الحفاظ على الصفحات والنص والهندسة. الملفات المحمية بكلمة مرور مرفوضة. انظر الملاحظة أدناه. |
| **الكود المصدري** | `.py` `.js` `.ts` `.go` `.rs` `.java` `.c` `.cpp` `.rb` `.php` `.sh` `.sql` + 50 أكثر | فحص Trojan Source والأحرف المتشابهة. **لا NFKC، ولا إعادة صياغة** — انظر أدناه. |
| **النص** | أي شيء آخر قابل لفك التشفير | UTF-8 وUTF-8 BOM وUTF-16 وUTF-32 وCP1252 وLatin-1 — تُكتشف تلقائيًا وتُكتب مرة أخرى **بنفس الترميز**. |
كل شيء آخر **يُرفض مع إرشادات محددة** بدلاً من قراءته كـ UTF-8 وإفساده — `.mp3` يُوجَّه إلى `ffmpeg -i in.mp3 out.wav`، وملفات `.doc`/`.ppt`/`.xls` القديمة إلى إعادة الحفظ بصيغة OOXML. الملف المرفوض لا يُنتج أي مخرجات أبدًا.
تم التحقق على مجموعة من 69 ملفًا تشمل كل صيغة مذكورة أعلاه: **59 ملفًا عولجت بشكل صحيح، و10 رُفضت دون مشاكل، وصفر أعطال، وصفر مخرجات تالفة.**
### الكود يُستبعد من أداة إعادة الصياغة
ردود المساعد تمزج النثر والكود في سلسلة واحدة. إن تسليم الأمر كله إلى نموذج إعادة صياغة يعيد كتابة الكود جنبًا إلى جنب مع النثر — وتنخفض درجة z في كلتا الحالتين، فلا يلاحظ أي شيء في المراحل اللاحقة.
لذلك، تُقسَّم الردود قبل إعادة كتابة أي شيء:
| الجزء | المعالجة |
| --- | --- |
| النثر | تنظيف يونيكود ثم إعادة كتابة |
| الكتل المسيجة (``` و ~~~) | إزالة الأحرف غير المرئية والأحرف ثنائية الاتجاه. **لا NFKC، ولا تُعاد كتابتها أبدًا.** |
| أجزاء `` `code` `` المضمنة | نفس الشيء |
ينطبق هذا أيضًا على مسار البث، حيث يفتح سياج في دلتا واحدة ويُغلق بعد عدة دلتات. الدلتا التي تمتد عبر الحدود تُقسَّم سطرًا بسطر، بحيث يُعامل كل من الإغلاق ``` والنثر الذي يليه بشكل مختلف. السياج غير المُغلق يفشل بأمان: الجزء المتبقي محمي بدلاً من إعادة كتابته.
عطِّل ذلك باستخدام `text.protect_code_blocks: false` إذا كنت تريد السلوك القديم.
### ملفات الكود المصدري: فحص أمني، وليس إزالة علامة مائية
لا توجد علامة مائية في الكود المصدري المُولَّد بالذكاء الاصطناعي. لا يضع أي مزود علامة مائية على مخرجات الكود، ولا يوجد كاشف عام. أي شخص يدّعي إزالتها إنما يبيعك شيئًا.
ما *يمتلكه* الكود المصدري فعلًا هو سطح هجوم حقيقي ومنشور:
* **مصدر طروادة (Trojan Source)** ([CVE-2021-42574](https://nvd.nist.gov/vuln/detail/CVE-2021-42574)، Boucher وAnderson 2021) — تعيد الأحرف ثنائية الاتجاه ترتيب طريقة *عرض* الكود دون تغيير طريقة *ترجمته*. يوافق المراجع على برنامج واحد؛ بينما يبني المترجم برنامجًا آخر.
* **معرّفات الأحرف المتشابهة (Homoglyph identifiers)** ([CVE-2021-42694](https://nvd.nist.gov/vuln/detail/CVE-2021-42694)) — حرف `а` السيريلي بدلًا من `a` اللاتيني يُنشئ اسمين يظهران بشكل متطابق.```console
$ nullorigin purge auth.py -o auth_clean.py --verify
Scanning source file auth.py...
bidi controls removed: 4
invisible chars removed: 0
TROJAN SOURCE DETECTED (CVE-2021-42574): 4 bidirectional control character(s).
This file rendered differently than it compiled. Review the diff.
Findings:
CRITICAL line 3:25 U+202E RIGHT-TO-LEFT OVERRIDE — reorders displayed text
if access_level != "user // Check if admin":
بعد التنقية، يقرأ السطر if access_level != "user // Check if admin": — كان
"التعليق" داخل السلسلة طوال الوقت.
ثلاثة أمور لا يقوم بها مسار الكود عمدًا، لأن مسار النص العام فعلها جميعًا وكل واحد منها خطأ في المصدر:
"Hello" أصبحت
"Hello"، و"office" أصبحت "office". وهذا يغيّر ما يقارنه البرنامج ويجزّئه
وينقله.а يدمج معرّفَين
يعاملهما المترجم حاليًا كمختلفين — مما يغيّر السلوك بصمت. الخطورة تكون
MEDIUM فقط للرموز متعددة الأنظمة الكتابية (totаl)، وهي بصمة الهجوم الفعلية؛ أما الكلمة
المكتوبة بالكامل بنظام كتابة آخر فهي نص أجنبي عادي وتُسجَّل INFO. يمكن تفعيل ذلك
عبر --fold-homoglyphs-in-code بعد مراجعتها.يُصدر inspect --json لكل نتيجة الخطورة والسطر والعمود ونقطة الترميز، فيتناسب
مع CI كبوابة pre-commit أو بوابة PR.
يُزال بشكل قابل للتحقق: قاموس /Info (Author, Title, Subject, Keywords,
Creator, Producer, CreationDate, ModDate)، وحزمة XMP في /Root/Metadata، والملفات
المرفقة المضمّنة، وجافاسكريبت على مستوى المستند. الصفحات والنص وهندسة الصفحة
مُحفَظة بالضبط؛ والعملية عديمة الأثر (idempotent) وثابتة البايتات.
يُكتشف لكن لا يُزال: الأحرف غير المرئية داخل تدفقات محتوى الصفحات. PDF
يرسم النص حرفًا حرفًا عبر ترميز خاص بالخط — فالمسافة صفرية العرض في خط CID-keyed
هي فهرس صورة حرف ببايتين، وليست U+200B حرفيًا — لذا فإن إعادة كتابة عامة
ستُتلف التخطيط بدل تنظيفه. يُبلّغ inspect عن العدد؛ بينما تطبع purge
تحذيرًا بدل الصمت، لأن الصمت سيوحي بأنه "لا يوجد أي منها". لإزالتها، استخرج النص
وشغّل nullorigin purge عليه وأعد توليد PDF.
الخيار --strip-annotations متاح لكنه معطّل افتراضيًا: فالتعليقات التوضيحية تشمل الروابط
وحقول النماذج، وليست التعليقات فقط، لذا إزالتها تغيّر سلوك المستند.
الشرطات الطويلة (em dashes) وعلامات الاقتباس المنحنية وعلامات الحذف هي ناتج عادي
لمعالجات النصوص. تحتفظ NullOrigin بها افتراضيًا وتُبلغ عنها بشكل منفصل عن النتائج الحقيقية، لأن
تسطيحها يضرّ بالمستند دون تعقيم أي شيء. استخدم
--flatten-typography إذا كنت تريد تحديدًا مخرجات ASCII.
أما الحروف المربكة عبر الأنظمة الكتابية فمختلفة — فالحرف السيريلي о في منتصف كلمة إنجليزية
لا استخدام شرعي له — وتلك تُطوى افتراضيًا.
يُبلّغ --verify عن القياسات قبل/بعد بدل التأكيد على النجاح:```console
$ nullorigin purge article.txt -o clean.txt --verify
Cleaning text structure and token transitions in article.txt...
removed 14 invisible characters, folded 3 homoglyphs
applying semantic restructuring via ollama backend...
restructuring complete
Saved clean text to clean.txt
Verification (KGW statistical detector): z-score before: +5.3021 (p=5.73e-08) z-score after: +0.8874 (p=0.187) detected before/after (z>4.0): True -> False
إذا تعذّر الوصول إلى الواجهة الخلفية، يتم الإبلاغ عن ذلك كتحذير على stderr — فالتراجع الصامت
سيبدو مطابقًا لعملية تعقيم ناجحة.
---
## 📊 قياس الأداء```bash
nullorigin build-datasets
nullorigin benchmark
كل قيمة تُقاس في المكان نفسه: يُوسَم النص بـ KGWWatermarkEmbedder، يُمرَّر عبر خط الإنتاج الفعلي، ثم يُعاد تقييمه باستخدام الكاشف المطابق. يخرج المُشغِّل برمز غير صفري عندما لا تُستوفى العتبات ويوضح السبب.
العتبات (من توجيه المشروع):
| القياس | الهدف |
|---|---|
| درجة z بعد التعقيم | $\lvert z\rvert \le 1.5$ |
| التشابه الدلالي | $\ge 0.92$ |
| SSIM للصورة | $\ge 0.95$ |
| PSNR للصورة | $\ge 36$ dB |
قسم النص الكامل الموجود في datasets/text/watermarked_kgw.json، أعيدت كتابته عبر Ollama (qwen3:4b) على MacBook من السلسلة M (~150 ثانية لكل مقطع):```text
sample z_before z_after reduced detected
kgw_000 4.212 -0.065 4.277 no
kgw_001 5.297 0.484 4.813 no
kgw_002 6.120 -0.482 6.601 no
kgw_003 4.711 1.271 3.440 no
kgw_004 5.696 0.209 5.486 no
invisible_payload -0.447 1.091 -1.538 no
mean z: 4.2647 -> 0.4182 max |z| after: 1.271 (target: <= 1.5) still detected at z > 4.0: 0 of 5 invisible chars remaining: 0
pass_z_threshold: PASS pass_no_detection: PASS pass_unicode_purge: PASS OVERALL: PASS (3/3)
كل عينة موقّعة بعلامة مائية انتقلت من حالة الاكتشاف إلى عدم الاكتشاف. لاحظ `kgw_003` عند z = 1.271 — تحت العتبة لكنها الأقرب إليها، وهو الشكل الصادق لهذا الهجوم: إنه إحصائي، وليس ضمانًا.
الوسيط، مُقاسًا عبر تركيبات الصور:```text
sample ssim psnr_dB meta_clear
c2pa_tagged.png 0.9950 46.84 yes
exif_tagged.jpg 0.9690 40.54 yes
clean_control.png 0.9951 46.90 yes
كلا عتبتَي الصورة مجتازتان (SSIM ≥ 0.95, PSNR ≥ 36 dB). ستختلف الأرقام باختلاف النموذج والعتاد والمقطع النصي.
إعادة الصياغة التي تغيّر حقيقةً تحصل على النتيجة ذاتها التي تحصل عليها إعادة الصياغة الأمينة على مقياس العلامة المائية. الفحص الواضح لهذا لا يعمل، ولا الفحص الأقل وضوحًا. أُجري القياس على ست حالات انحراف إضافةً إلى حالة ضابطة أمينة:
التداخل المعجمي معكوس. كل تعديل يدمّر المعنى حصل على نتيجة أعلى من إعادة الصياغة الأمينة، لأن إعادة الصياغة الجيدة لا تتقاسم مع مصدرها سوى n-grams قليلة، بينما يتقاسم النص المحرَّف جميعها تقريبًا.
تشابه جيب التمام للتضمين لا يصلح الأمر. ثلاث من ست حالات محرَّفة تجتاز عتبة 0.92.
"Alice paid Bob" و"Bob paid Alice" هما نفس كيس الكلمات وتحصلان على 0.985؛
بينما "must not disable" → "must disable" تسجّل 0.947. تضمينات الجمل ترمّز الصلة
الموضوعية، لا الحقيقة.
لذا تُفحص الأمانة في طبقتين، ولا تعتمد أيٌّ منهما على جيب التمام:
negation count changed: 1 → 0). تُقارَن الأفعال الناقصة ومحددات الكمية
حسب فئة المعنى، لذا ينجح may → might ويفشل may → must. وهي عمياء عن تبديل
الأدوار عندما يبقى كل كيان موجودًا.nullorigin[nli]؛ وبدونه
يُبلَّغ عن القيد لا أن يُخفى.قياس الانحراف بعد فوات الأوان لا يفيد إذا كان النص المتضرر قد أُعيد بالفعل.
الفحص الفاشل يعيد المحاولة عند درجة حرارة أقل — الانحراف مدفوع بدرجة الحرارة —
وبعد استنفاد ميزانية إعادة المحاولة يُعيد النص الأصلي، مع ok=False والسبب.```yaml
text:
fidelity:
enabled: true
max_retries: 2
temperature_step: 0.25
use_nli: true
nli_threshold: 0.5
هذا يعني أيضًا أن الهجوم والمخاطرة يشتركان في قرص ضبط واحد: رفع درجة الحرارة يقلل من
z-score *ويزيد* معدل الانجراف. يُبلِّغ المعيار عنهما معًا بدلًا من اعتبارهما فحوصًا
مستقلة.
### مقاييس أخرى
* **Perplexity** — PPL حقيقية لـ GPT-2 مع `torch` + `transformers`، وإلا `unigram_entropy_proxy`، مُعلَّمة كتقريبية و**غير** قابلة للمقارنة مع PPL المنشورة.
* **تشابه جيب التمام** لا يزال يُبلَّغ كـ `mean_cosine_or_lexical`، للرجوع إليه فقط.
لم يعد بوابة نجاح/فشل، للأسباب المذكورة في الجدول أعلاه.
## ⚙️ الإعداد
ترتيب التطبيق، من الأدنى إلى الأعلى أولوية:
1. الإعدادات الافتراضية المدمجة
2. `nullorigin.yaml` (يُبحث عنها في `./`, `../`, `/app/`, أو `$NULLORIGIN_CONFIG`)
3. متغيرات البيئة `NULLORIGIN_*`
4. خيارات سطر الأوامر الصريحة
### الإعدادات الرئيسية
| الإعداد | الافتراضي | ملاحظات |
| --- | --- | --- |
| `proxy.host` | `127.0.0.1` | استرجاع محلي. يُرفض الربط العام بدون رمز قياس عن بُعد. |
| `proxy.port` | `8080` | |
| `proxy.default_provider` | `openai` | يُستخدم عند عدم إرسال ترويسة `x-nullorigin-provider`. |
| `proxy.telemetry_token` | `""` | يحمي `/telemetry` و`/telemetry/reset`. |
| `proxy.max_request_bytes` | `104857600` | 100 MiB؛ تحصل الطلبات الأكبر على `413`. |
| `text.paraphraser.backend` | `ollama` | `none` \| `ollama` \| `openai_compatible` \| `llama_cpp` \| `lexical`. `none` تُبقي العلامة المائية الإحصائية سليمة. `lexical` لا تحتاج إلى نموذج لكنها هجوم أضعف بكثير. |
| `text.clean_unicode` | `true` | إزالة أحرف العرض الصفري وكتلة Tags. |
| `text.fold_homoglyphs` | `true` | تحويل الأحرف السيريلية/اليونانية المربكة إلى ASCII. |
| `text.stream_window_tokens` | `40` | تُخزَّن الدلتا مؤقتًا قبل إعادة كتابة مقطع البث. |
| `media.crop_mode` | `trim` | `trim` يزيح الإحداثيات دون إعادة أخذ العينات؛ `resample` يستعيد الأبعاد الدقيقة لكنه يكلف تقريبًا SSIM 0.81 / PSNR 31 dB حتى عند اقتصاص 0.5%؛ `none` يعطّل التمريرة الهندسية. |
| `audio.low_cut_hz` | `800.0` | يحافظ على الطور الأقل من هذه القيمة من أجل الوضوح. |
### متغيرات البيئة```bash
NULLORIGIN_CONFIG # path to nullorigin.yaml
NULLORIGIN_HOST # bind address
NULLORIGIN_PORT
NULLORIGIN_TELEMETRY_TOKEN
NULLORIGIN_MAX_REQUEST_BYTES
NULLORIGIN_DEFAULT_PROVIDER
NULLORIGIN_PARAPHRASER_BACKEND # none | ollama | openai_compatible | llama_cpp | lexical
NULLORIGIN_PARAPHRASER_ENDPOINT # alias: NULLORIGIN_OLLAMA_ENDPOINT
NULLORIGIN_PARAPHRASER_MODEL
NULLORIGIN_PARAPHRASER_MODEL_PATH # llama_cpp GGUF path
NULLORIGIN_PARAPHRASER_API_KEY
NULLORIGIN_PARAPHRASER_TIMEOUT
NULLORIGIN_PARAPHRASER_TEMPERATURE
NULLORIGIN_CLEAN_UNICODE
NULLORIGIN_FOLD_HOMOGLYPHS
NULLORIGIN_PURGE_METADATA
NULLORIGIN_DISRUPT_STEGO
NULLORIGIN_DISRUPT_AUDIO
model 'llama3.2:3b' not found
النموذج المُهيأ لم يتم سحبه. شغّل ollama list لمعرفة ما لديك، ثم إما
ollama pull llama3.2:3b أو اضبط NULLORIGIN_PARAPHRASER_MODEL على نموذج لديك
بالفعل.
WARNING: ollama backend unavailable (ReadTimeout)
تجاوزت إعادة الكتابة text.paraphraser.timeout_seconds (الافتراضي 120 ثانية). نماذج
الاستدلال مثل qwen3 تستغرق عادةً أكثر من 150 ثانية لكل فقرة على وحدة المعالجة المركزية. ارفع
القيمة: export NULLORIGIN_PARAPHRASER_TIMEOUT=900، أو استخدم نموذج تعليمات أصغر.
يخرج nullorigin benchmark بالرمز 1 مع pass_no_detection: FAIL
يعمل كما هو مقصود. لم يكن أي محرك إعادة كتابة متاحًا، لذا عملت طبقة اليونيكود فقط
ونجت العلامة المائية الإحصائية. شغّل Ollama، أو اضبط المحرك على lexical لمقارنة
دون تبعيات.
semantic_check: INCONCLUSIVE
متوقع دون تثبيت الإضافة الاختيارية [metrics]. انظر صدق المقاييس.
Error: Refusing to bind 0.0.0.0 without a telemetry token
مقصود. انظر النشر خارج localhost.
Multiple top-level packages discovered in a flat-layout
أنت على نسخة قديمة من المستودع. يحدد pyproject.toml قائمة حزم صريحة؛ اسحب أحدث إصدار.
اختبارات async تُبلغ عن UsageError بخصوص مكوّن async مفقود
أمر مقصود — بدونه، يعرض pytest اختبارات async def على أنها ناجحة دون انتظارها.
pip install -e ".[dev]".
Docker: curl: (7) Failed to connect مباشرة بعد compose up
فترة بدء فحص الصحة هي 10 ثوانٍ. انتظر، ثم تحقق من docker compose logs nullorigin.
Client / Application
|
[http://localhost:8080/v1/...]
v
+===================================================+
| NULLORIGIN CORE PROXY |
| HTTP/SSE interceptor · provider schema adapter |
| /health · /telemetry · transparent auth passthru |
+===================================================+
|
[request forwarded unmodified]
v
Upstream Provider API (Anthropic / OpenAI / Gemini)
|
[watermarked payload]
v
+===================================================+
| SANITIZATION PIPELINE ROUTER |
+===================================================+
/ | \
(text/JSON+SSE) (image/*) (audio/wav) v v v +----------------+ +------------------+ +------------------+ | MODULE B: TEXT | | MODULE C: MEDIA | | MODULE D: AUDIO | | unicode purge | | C2PA/EXIF scrub | | phase randomize | | homoglyph fold | | DWT threshold | | notch shifting | | KGW detector | | Fourier phase | | psychoacoustic | | SLM rewriter | | dither | | requantization | +----------------+ +------------------+ +------------------+ \ | / +----------------+---------------------+ v Schema reconstruction (SSE framing preserved) v Sanitized stream / file
### التخطيط```text
nullorigin/
├── cli.py # run, purge, inspect, benchmark, build-datasets, test
├── config.py # Pydantic v2 settings + env overrides
├── proxy/
│ ├── server.py # FastAPI reverse proxy, /health, /telemetry
│ ├── interceptors.py # SSE frame parser + sliding-window rewriter
│ ├── telemetry.py # thread-safe runtime counters
│ └── schemas.py # provider request/response models
├── engines/
│ ├── text/
│ │ ├── unicode_cleaner.py # invisible chars, Tags block, homoglyphs
│ │ ├── paraphraser.py # pluggable rewrite backends
│ │ └── kgw_detector.py # detector + Viterbi embedder
│ ├── media/
│ │ ├── c2pa_remover.py # JUMBF/EXIF/XMP stripping + inspection
│ │ └── stego_breaker.py # DWT thresholding, Fourier phase, dither
│ └── audio/
│ └── audio_cleaner.py # phase randomization, notch shifting
└── evaluation/
├── metrics.py # SSIM, PSNR, PPL, semantic similarity
├── datasets.py # deterministic fixture generation
└── runner.py # measured benchmark harness
pytest -q
384 اختبارًا. تغطي المجموعة محلل إطارات SSE ضد حدود قطع متعارضة، ودورة حياة البث عبر البروكسي، وكل وضع من أوضاع صور PIL، وSSIM مقارنةً بقيمة مغلقة الصيغة وتنفيذ مرجعي بالقوة الغاشمة، وقابلية اكتشاف العلامة المائية في مجموعة البيانات.
تفشل اختبارات Async بصوت عالٍ إذا لم يتم تثبيت مكوّن إضافي async بدلاً من تخطيها بصمت.
---
## 📖 الاستشهاد وإعادة الاستخدام
مرخّص بموجب Apache-2.0، الذي يسمح بالاستخدام والتعديل وإعادة التوزيع بشرط
الاحتفاظ بإشعار حقوق النشر والنسبة إلى **Muhammad Rakibul Islam**. انظر
[LICENSE](https://github.com/rakib-nyc/nullorigin/blob/HEAD/LICENSE) و[NOTICE](https://github.com/rakib-nyc/nullorigin/blob/HEAD/NOTICE).
إذا كان هذا العمل يدعم منشورًا، فيُرجى الاستشهاد به على النحو التالي:```bibtex
@software{islam_nullorigin_2026,
author = {Islam, Muhammad Rakibul},
title = {{NullOrigin}: Universal AI Provenance and Watermark
Sanitization Middleware},
year = {2026},
version = {1.2.0},
url = {https://github.com/rakib-nyc/nullorigin},
note = {Research artifact for watermarking robustness evaluation}
}
هذا المستودع منشور كنتاج بحثي مكتمل وهو لا يقبل pull requests. يمكنك عمل fork له بموجب شروط الرخصة. الأسئلة و النتائج مرحب بها عبر البريد الإلكتروني على [email protected].
الإصدار 1.0.0. المجموعة مكتملة وفق مواصفاتها ومختبرة بالكامل، مع الحدود المعروفة التالية:
[nli]، فإن تبديل
الأدوار التي تحافظ على كل كيان غير قابل للاكتشاف، والتقرير يذكر ذلك.انظر CHANGELOG.md لسجل الإصدارات.
هذا المستودع هو نتاج بمستوى بحثي صدر لأغراض البحث الإحصائي، وتقييم الخصوصية، وقياس متانة العلامات المائية، واختبار المرونة التشفيرية.```text Copyright 2026 Muhammad Rakibul Islam [email protected]
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
**لا توجد ضمانات.** يُقدَّم البرنامج "كما هو"، دون أي ضمان من أي نوع،
سواء كان صريحًا أو ضمنيًا.
| الطبقة | ما تفعله فعليًا |
|---|
| الأحرف غير المرئية | فعالة تمامًا. تتم إزالة الأحرف ذات العرض الصفري، وأحرف التحكم في الاتجاه، ومحددات التغيّر، وأحمال كتلة Unicode Tags بالكامل، مع الإبلاغ عن العدد. يتم طي التشابهات بين النصوص (homoglyph confusables) عبر النصوص (السيريلية/اليونانية التي تُعرض كـ ASCII). |
| بيانات المستند الوصفية (.docx) | فعالة تمامًا. يتم مسح المؤلف، وآخر محرر، وعدد المراجعات، والطوابع الزمنية، والقوالب، وإصدار التطبيق من docProps، مع الحفاظ على التنسيق بايتًا ببايت. |
| C2PA / EXIF / XMP | فعالة تمامًا. تُعاد بناء الصورة من عينات البكسل الخام إلى حاوية جديدة، بحيث تختفي بيانات JUMBF الموقّعة وجميع البيانات الوصفية. تم التحقق منها بالاختبار ضد نماذج موصولة بعلامات. |
| العلامة المائية الإحصائية KGW | تعتمد كليًا على محرك إعادة الكتابة. بدون نموذج محلي مُهيأ، تبقى العلامة المائية الإحصائية قائمة — والأداة توضح ذلك بدلاً من التلميح بخلافه. |
| SynthID-Text / SynthID-Image / Tree-Ring | غير قابلة للتحقق هنا. تستخدم مفاتيح خاصة وأجهزة فك ترميز مملوكة. يطبّق NullOrigin التشوهات التي تصفها الأدبيات، لكن لا يُدّعى أنها تهزم أجهزة الكشف الحقيقية، لأنه لا يوجد كاشف عام للقياس مقابله. |
| AudioSeal / SynthID-Audio | غير قابلة للتحقق هنا، للسبب نفسه. |
| اختياري | Docker 20.10+ مع Compose v2 |
| الحالة | lexical_f1 | تشابه جيب التمام للتضمين | NLI ثنائي الاتجاه |
|---|
| حذف النفي | 0.70 | 0.77 ✓ | 0.000 ✓ |
| الرقم 5 → 50 | 0.82 | 0.81 ✓ | 0.000 ✓ |
| تبديل الكيان/الدور | 0.81 | 0.985 ✗ | 0.000 ✓ |
| محدد الكمية all → some | 0.88 | 0.91 ✓ | 0.000 ✓ |
| إزالة التلطيف | 0.27 | 0.953 ✗ | 0.011 ✓ |
| "must not" → "must" | 0.83 | 0.947 ✗ | 0.000 ✓ |
| إعادة صياغة أمينة | 0.33 | 0.931 ✓ | 0.998 ✓ |