ابدأ من هنا

نموذج التهديد

قراءة في 66 دقيقةعرض بصيغة Markdown ↗

تسمّي هذه الوثيقة خصوم URnetwork وتقول ماذا يفعل النظام وماذا لا يفعل حيال كل واحد منهم. وهي مكتوبة كي تُهاجَم. وحيث تكون الخاصية مشروطة، فالشرطُ في الجملة نفسها؛ وحيث يكون الأمر قصداً تصميمياً لا يُنفّذه الكود المشحون بعد، فهو موسوم بأنه قصد تصميمي، لا خاصية. ويسبق القسمَ 1 ملخصٌ للقراء الذين لن يقرأوا السجل كاملاً.

والمعيارُ المرجعي هو شرح Tor نفسها للهجمات التي لا يهزمها التوجيه البصلي: فالخصمُ الذي يراقب طرفَي دائرة يستطيع ربطهما بالتوقيت، وTor تقول ذلك علناً. وURnetwork لا تشحن أي حركة تمويه ولا أي حشو، فالأمر نفسه صحيح هنا، والقسمُ 8 يقوله بلا تليين.

حالة الضمان، تُذكر مرة واحدة وتنطبق على كل ما دونها. لا يوجد تدقيق مستقل للبروتوكول ولا لمحرك الاتصال ولا لكود خوادم المشغّل — وهي الطبقة التي تقوم عليها ادعاءات الخصوصية. وهناك تقييمان خارجيان من 2025 يغطيان سطوحاً أخرى: اختبار اختراق مستقل من طرف ثالث لتطبيق الويب وواجهة API ‏(25 أبريل – 5 مايو 2025، باعتمادات، ويدوي، وفق OWASP Top 10 إضافةً إلى مراجعة لضوابط ASVS)، وتقييم Leviathan Security Group بمستوى MASA AL2 لتطبيق Android (أُنجز في 23 مايو 2025)، وقد اجتازه، وتحدّد Leviathan نفسها نطاقه بأنه «ليس تقييماً أمنياً شاملاً ولا اختبار اختراق وافياً». ولم يفحص أي منهما التسجيل ولا الاحتفاظ بالبيانات ولا مسار البيانات ولا البروتوكول. ومن ثمّ فكلُّ ادّعاء في هذه الوثيقة ادّعاءٌ عن مصدر قابل للقراءة، يستطيع أي أحد فحصه ولم يفحصه أحدٌ خارج المشروع كاملاً بعد.

كيف تقرأ الاستشهادات

تستشهد الادّعاءات بصيغة path:line في مقابل أشجار العمل عند /Users/brien/urnetwork/{connect,sdk,server,proxy,extension} بتاريخ 2026-09-17، حين أُعيد حلُّ كل استشهاد مربوط بالسطر في هذه الوثيقة في مقابل الأشجار بحسب المعرّف. وأرقامُ الأسطر تنزاح؛ أما المعرّفات فلا. وحيث تثبتت نتيجةٌ في تمريرة تحقق من الكود سابقة، فهي تستشهد بـreview/verified/ARCHITECTURE.md أو بـreview/verified/PRIVACY-ENFORCEMENT.md، وكلاهما يحمل العُرف نفسه.

ولم تكن إعادةُ الحلّ تلك تجميلية، وهي جديرة بالتسجيل تحذيراً لكل من يقرأ نسخة أقدم. فبين تمريرة 2026-08-07 وهذه التمريرة تضاعف طولُ connect/transfer.go ثلاث مرات تقريباً، وانزاح كلُّ استشهاد في connect تقريباً — فبوابةُ الإرسال ذات الفشل المغلق المستشهَد بها عند transfer.go:2796 هي اليوم عند :6751، وبوابةُ الاستقبال المستشهَد بها عند :5844 هي عند :14886. فعامِل أيَّ رقم سطر في نسخة من هذه الوثيقة لا تحمل التاريخ أعلاه على أنه غير موثوق، وتنقّل بحسب المعرّف.

وتُستعمل ثلاثة أوسمة عن قصد:

  • خاصية — الكودُ ينفّذها، والمهاجمُ الذي يسيطر على الأطراف الأخرى لا يزال عاجزاً عن انتهاكها.
  • انضباط — الكودُ يختار ألا يفعل شيئاً هو قادر على فعله. وتغييرٌ في النشر أو رقعةٌ من سطر واحد قد يعكسه.
  • قصد تصميمي — التصميمُ يقول إن هذا هو الهدف؛ والكودُ المشحون لا ينفّذه بعد.

الملخص

هذا القسم للقراء الذين لن يقرأوا السجل كاملاً. وكلُّ جملة فيه يسندها قسمٌ مرقَّم أدناه أو بيانُ الضمان أعلاه.

ما هو النظام. URnetwork شبكةُ خصوصية يُرحّل فيها الأعضاء حركةَ بيانات أعضاء آخرين. والمسارُ هو: أنت ← الموسّع ← المشغّل ← المزوّد ← الإنترنت. وقسمةُ الثقة بين طرفَي ترحيل: المشغّل، الذي يعرف من أنت، والمزوّد، الذي يرى إلى أين تذهب حركةُ بياناتك.

السؤال الذي وُجدت هذه الوثيقة لتجيب عنه. هل يستطيع مشغّلٌ غير موثوق ومجموعةٌ من المزوّدين غير الموثوقين أن يحملوا مع ذلك جلسةً خاصة ومجهولة، ما داموا غير متواطئين؟ والجوابُ الأمين اليوم هو معظم الطريق، والفجوةُ يمكن تحديد موضعها بدقة: فعمى المزوّد عن الهوية يصمد في وجه مزوّد خبيث إلى أقصى حد، بلا شرط؛ وعمى المشغّل عن المحتوى يصمد في وجه مشغّل سلبي وفي وجه أي مهاجم على المسار، لكنه يقوم على أن يوزّع المشغّلُ مفاتيحَ هوية المزوّدين بأمانة، وعلى ألا يختار مزوّديك اختياراً عدائياً. و§1.1 هو نموذج الثقة الذي يقرّر ذلك طرفاً طرفاً؛ و§7 هو الآلية الكامنة خلف هذا التحفّظ.

الخاصيتان، ولكل منهما نطاقها. على المسار المُرحَّل لا يتلقى المزوّد أبداً عنوان IP المصدري الحقيقي الخاص بك؛ ولا يغيّر ذلك أي إعداد ولا أي إصدار مزوّد (§2). والمشغّلُ لا يستطيع قراءة الجلسة المختومة بين العميل والمزوّد، وهي متاحة على التطبيقات الأصلية الخمسة (Android وiOS وmacOS وWindows وLinux) باسم تشفير ما بعد الكم (Post Quantum Encryption) ‏(§2.1). ولا جلسة مختومة في امتداد المتصفح — فجهازُ عميله يعمل داخل المشغّل، الذي يقوم مقام نقطة ترجمة بين البروتوكولات، فلا يمكن أن يوجد ختم هناك (§2.4).

ثلاثة اعتمادات على الثقة، وأين يقف كلٌّ منها. كان الختم يفشل مفتوحاً في صمت في كل الأوضاع. أُصلح في 2026-08-08/10: فمع تفعيل تشفير ما بعد الكم يعمل العميل بالفشل المغلق، ويرفض إرسال بيانات التطبيقات نصاً صريحاً أو قبولها بدل أن يتدهور إليها (§2.2). ويبقى أمران، وكلاهما ثقةٌ بالمشغّل لا بالتشفير:

  1. توزيع المفاتيح. هل يستطيع المشغّل استبدال مفتاح مزوّد؟ صار ذلك أصعب بكثير منذ 2026-09-17، ولم يعد قابلاً للإنكار. فمع تفعيل تشفير ما بعد الكم يحجب العميلُ شفرةَ الجلسة حتى يُعضَّد مفتاحُ المزوّد الذي يزوّده العقد بتاريخِ تسجيلٍ موقَّع، مسلسَلٍ بالتجزئة، ومقيَّدٍ بالنطاق، نشره المشغّل، والخلافُ المتحقَّق منه يقتل الجلسة مع ذلك المزوّد (§7.4). فالمشغّلُ الذي يستبدل صار عليه الآن أن يوقّع الاستبدال، تاركاً سجلاً دائماً يُنسَب إليه ويفترق عما يراه كل قارئ آخر لمعرّف العميل ذاك. أما ما لا يوقفه فمشغّلٌ خبيث منذ أول اتصال لك ومتسقٌ في ذلك: فهو يوقّع سلسلةً واحدة متماسكة تسمّي مفتاحه هو، والعميلُ الذي لا يملك رؤيةً مستقلة لأيّ الموقّعين صاحبُ السلطة لا يستطيع أن يميّز. فالموقَّعُ ليس عصيّاً على التزوير من الموقِّع نفسه.
  2. اختيار المزوّدين. المشغّلُ يرتّب مجموعة المزوّدين ويعيدها، ولا سبيل لدى العميل للتحقق من أن المزوّدين المعروضين عليه مستقلون بعضهم عن بعض أو عن المشغّل (§5 و§6.1). فالمشغّلُ غير الموثوق لا يحتاج إذن إلى التواطؤ مع مزوّدين؛ بل يستطيع أن يختار مزوّديه هو.
  3. ثم الافتراضي. ضمانُ الفشل المغلق مربوط بمفتاح لا يزال يُشحن متوقفاً في التطبيقات الأصلية الخمسة كلها (§2.2)، فالعميلُ بالإعداد الافتراضي لا يفتح أي جلسة مع المزوّد إطلاقاً: فكلُّ حزمة من حزم التطبيقات تعبر المشغّلَ غير مختومة، على المسار القياسي.

والبنود 7 و8 و9 من verify/BEFORELAUNCH.md تتتبّع الثلاثة كلها؛ وتصف الأقسام §2.2 و§6 و§7.4 الحال الراهنة.

ما لا يدافع النظام ضده. الخصمُ الذي يراقب شبكة وصولك والمزوّدين الذين يحملون حركة بياناتك معاً يستطيع ربط الطرفين بالحجم والتوقيت (§8.1). والمراقبُ السلبي العالمي خارج النطاق كلياً (§8.3). وURnetwork لا تشحن حشواً ولا حركة تمويه ولا خلطاً؛ والقسمُ 11 هو القائمة الكاملة.

الضمان. لا يوجد تدقيق مستقل للبروتوكول ولا لمحرك الاتصال ولا لكود خوادم المشغّل؛ وهناك تقييمان خارجيان من 2025 يغطيان سطوحاً أخرى: اختبارُ اختراق لتطبيق الويب وواجهة API، وتقييمُ Leviathan Security Group بمستوى MASA AL2 لتطبيق Android، وقد اجتازه.

أين يسكن التفصيل. يعرّف القسم 1 الأطرافَ ويقرّر نموذج الثقة طرفاً طرفاً (§1.1). ويغطي القسم 2 أوضاعَ الاتصال الثلاثة وما يراه كل طرف في كل منها. وتتناول الأقسام من 3 إلى 5 كل خصم على حدة: مزوّد خبيث، ومراقب شبكة، والمشغّل. ويغطي القسمان 6 و7 المزوّدين الذين يشغّلهم المشغّل، والتواطؤ، واستبدال مفاتيح المزوّدين. وتغطي الأقسام من 8 إلى 12 ربطَ الحركة، ومعرّفات الأجهزة، والمطالب القانونية، وقائمةَ ما لا يدافع النظام ضده، وما تعذّر على هذه الوثيقة إثباته. ويقول القسم 13 أين تُبلَّغ الثغرات والتصحيحات.

1. الأطراف

الطرفيشغّلهالموضع
العميلالمستخدمالتطبيق أو نسخة SDK؛ وعلى مسارَي المتصفح والبروكسي يعمل على خوادم المشغّل بدلاً من ذلك (§2.4)
موزّع حمل المدخلالمشغّلnginx؛ يختم عنوان العميل الملاحَظ في X-UR-Forwarded-For (xops/gitops-unused/gitops-prod/urnetwork/connect/ingress.yaml:9 — وهو بيان المدخل الوحيد في الشجرة، ودليلُه اسمه gitops-unused، فقد لا يصف الإنتاج؛ §12)
خدمة الاتصالالمشغّلعمليةٌ أو عمليتان على مضيفين مختلفين، يجمعهما اتصالُ تبادل داخلي (server/connect/resident.go:395). وليست «خادم ترحيل»
واجهة API / مستوى التحكمالمشغّلالمصادقة، واكتشاف المزوّدين وترتيبهم، والعقود، وبحثُ المفتاح العام (server/api/api.go)
المزوّدأي عضويتلقى الحزم المُرحَّلة ويطلب الوجهة من اتصاله هو (connect/ip.go:7167 NewRemoteUserNatProvider؛ وReceive عند :8726، وReceiveBatch عند :8486 ← LocalUserNat، :731,784)
الموسّعأي متطوعالساقُ الأولى في المسار: مرحّلُ تمرير TLS على عنوان مستقل. يحمل الجلسة بين العميل والمنصة من دون أن تنتهي عنده، فهو يرى عنوان الاتصال ولا يستطيع فك التشفير — فالمجرى الداخلي هو «TLS الخاص بالعميل نفسه إلى الوجهة، الذي لا يرى الموسّعُ داخله أبداً» (connect/extender/extender.go:35-44؛ connect/net_extender.go:34-48)
محلِّلات DoHCloudflare وGoogle وQuad9 وOpenDNSمسارُ التطبيق يحلّ DNS عبر HTTPS خلال النفق إلى واحد من هذه الأربعة (connect/net_http_doh.go:148-166)
معالِجات الدفعStripe وApple وGoogle وSolana/Circleيحملون الهوية الحقيقية للحسابات المدفوعة؛ والمشغّل يخزّن مفاتيح الوصل (server/db_migrations.go:1804، :4732، :2441)

والمشغّلُ هو BringYour, Inc.، وهي شركةٌ مساهمة من فئة C في ولاية ديلاوير بعنوان إشعارات في سان فرانسيسكو (docs/legal/terms.md:8-9,265-271 يسمّي الكيانَ والعنوان؛ أما التأسيس نفسه فلا تذكره الشروط المنشورة). ويغطي القسم 10 ما يعنيه ذلك للإجراءات القانونية.

ملاحظةٌ في المصطلح، لأن الطبقة الاقتصادية في الشبكة تستعمل كلمةً مختلفة للطرف نفسه. فالمزوّدون يُسمَّون مُعدِّنين (miners) حيث يُدفع لهم مقابل عرض النطاق الذي يحملونه، والشبكةُ مصمَّمة لتضم مشغّلين كثيرين يعمل كلٌّ منهم باستقلال بدل مشغّل واحد. ولا يغيّر أيٌّ من الأمرين التشفير: فالنمطُ دائماً عميلٌ واحد ↔ مشغّلٌ واحد ↔ مزوّدٌ واحد، وكلُّ خاصية أدناه خاصيةٌ لذلك المثلث. وما تغيّره تعدديةُ المشغّلين والمزوّدين هو معقوليةُ افتراض عدم التواطؤ في §1.1، لا الآليةُ التي يحميها ذلك الافتراض.

1.1 نموذج الثقة

اقرأ هذا الجدول على هذا النحو: إن كان هذا الطرف خبيثاً إلى أقصى حد والآخرون أمناء، فهل تصمد الخاصية؟

الخاصيةأمام مزوّد خبيثأمام موسّع خبيثأمام مشغّل خبيث
هويتك مخفية عن المخرج (فالمزوّد لا يعرف عنوانك ولا حسابك أبداً)نعم — بلا شرط. فالعنوانُ لا يكون على السلك أبداً؛ ونقطةُ دخول المزوّد لا تأخذ إلا معرّفات (§2 و§3)نعم. فالموسّعُ يرى عنوانك، الذي تعرفه شركةُ الإنترنت لديك أصلاً، ولا يعرف شيئاً عن المخرج. والاستثناءُ هو دور المزوّد، الذي تشغّله حين تشارك اتصالك: فهو يعرّف بنفسه للموسّعات التي يقيسها بمعرّف عميله (§4)لا. فالمشغّلُ يعرف من أنت بحكم البناء؛ وذلك دوره (§5)
وجهاتك مخفية عن المنسّق (فالمشغّل لا يستطيع قراءة الجلسة)لا ينطبق — فالمزوّد هو الطرف الذي يرى الوجهاتنعم. فالموسّعُ يمرّر جلسةً لا يستطيع إنهاءها (§4)مشروط. يصمد أمام مشغّل سلبي، وأمام الخفض، وأمام مشغّل ينقلب خبيثاً لاحقاً، وأمام مشغّل غير متسق بين قنواته هو. ولا يصمد أمام مشغّل خبيث ومتسق مع نفسه منذ أول اتصال (§7.4)
لا خفض إلى النص الصريح (فالحركة إما مختومة وإما لا تجري)نعم، مع تفعيل المفتاح. فبوابةُ الاستقبال ترفض النص الصريح المجرَّد من غلافه (§2.2)نعم، مع تفعيل المفتاحنعم، مع تفعيل المفتاح — فالإغفالُ يصير حرماناً من الخدمة لا إفصاحاً. ولا، مع إيقافه، وهو لا يزال الافتراضيَّ المشحون (§2.2)
لا يوجد سجلُّ تصفح يُكرَه على تقديمه أو يُسرَّبنعمنعمنعم — بنيوياً. فلا يوجد حقل وجهة ولا مضيف ولا URL ولا SNI ولا منفذ ولا نطاق في أي موضع من سجل النقل (§5 و§10)
حركتك غير قابلة للربط من طرف إلى طرفلالالا. لا حشو، ولا حركة تمويه، ولا خلط — خارج النطاق بحكم التصميم (§8)

وتتبع ذلك ثلاثةُ أمور، وهي الشكلُ الأمين للجواب عن السؤال المطروح في الملخص.

المزوّدُ لا يُؤتمَن على شيء، وذلك مفروضٌ بنيوياً. لا بالسياسة، ولا بأن يشغّل المزوّدُ الإصدارَ الرسمي — بل بأن العنوان لا يُرسَل أبداً. وهذه أقوى خاصية في الوثيقة.

المشغّلُ يُؤتمَن على أمرين، وكلاهما مفتوح. فهو يوزّع مفاتيحَ هوية المزوّدين التي يُفحص الختم في مقابلها (§7)، ويختار أيَّ المزوّدين يُعرَضون عليك (§5 و§6.1). ولا يستطيع العميل التحقق من أيٍّ منهما اليوم.

وهذان الأمران غير مستقلين، والثاني هو الأحدّ. فالوثيقةُ التي تقول «آمنٌ ما لم يتواطأ المشغّل والمزوّدون» تهوّن المشكلة ما دام المشغّل هو من ينتقي المزوّدين: فلا حاجة به إلى استمالة أحد إن كان يستطيع أن يختار من يسيطر عليهم أصلاً. والتمييزُ في التصميم هو بين الاستبدال والاختيار (connect/DESIGNNOTES2.md §3.4). ولإغلاق الاستبدال جوابٌ تشفيري، وواحدٌ منه قيد البناء (§7.4). أما إغلاقُ الاختيار فيحتاج إلى شيء يستطيع العميل فحصه بشأن استقلال مجموعةٍ من المزوّدين، ولا شيء في الكود المشحون يحاول ذلك.

2. الأوضاع الثلاثة، وما يراه كل طرف

هذا الجدول هو المرجع وهو مطابق لـOVERVIEW.md. وكلُّ ما عداه في هذه الوثيقة هو الآلية خلفه.

الوضعما يراه المشغّلما يراه المزوّدالافتراضي والتوافر
مُرحَّل مختوماتصال الحساب/المصدر، والاقتران بالمزوّد، والنص المشفّر مع التوقيت/الحجمحركة الوجهات، ومعرّف الجهاز/العقد، وليس عنوان IP المصدري الحقيقيالتطبيقات الأصلية وحدها، وبالتفعيل اليدوي اليوم: صدر الحكم بأن يكون الافتراضي، ولا يزال يُشحن متوقفاً؛ انظر §2.2
مُرحَّل قياسياتصال الحساب/المصدر، والاقتران بالمزوّد، والوجهات الداخلية وبايتات الحزمحركة الوجهات، ومعرّف الجهاز/العقد، وليس عنوان IP المصدري الحقيقيالافتراضي في التطبيقات الأصلية اليوم، لأن الختم يُشحن متوقفاً (§2.2)، ومسارا المتصفح والبروكسي (§2.4)؛ ومع تفعيل الختم يُتخطّى المزوّد الذي يتعذّر الختم معه بدل أن يُخدَم هنا (§2.2)
مباشردور أقل في الترحيلعنوان IP المصدري الحقيقي وحركة الوجهاتيُفعَّل يدوياً، بتعطيل الإخفاء القوي للهوية (§2.3)

وتتبع ذلك عبارتان غير متناظرتين، وهما ليستا على القوة نفسها:

عمى المزوّد عن الهوية خاصية، وهي غير مشروطة على المسارين المُرحَّلين معاً. فنقطةُ الدخول الواردة عند المزوّد تأخذ معرّفات لا عنواناً أبداً: فدالة RemoteUserNatProvider.Receive مُوسَّطة بـTransferPath من ثلاثة معرّفات بطول 16 بايت (connect/ip.go:8726، وصيغةُ الدفعات عند :8486؛ connect/connect.go:50). ولا يحمل أي protobuf للنقل عنوانَ عميل ولا مدينة ولا دولة — فبحثُ grep في connect/protocol/*.proto عن location|city|country|region لا يعيد شيئاً (review/verified/ARCHITECTURE.md §1.3). ولا يوجد إعداد يوقف هذا على مسار مُرحَّل، ولا يستطيع أي إصدار مزوّد الحصول على عنوان لا يُرسَل إليه أصلاً.

عمى المشغّل عن المحتوى إعدادٌ لم يصر افتراضياً بعد — فوق آلية بقي بها عيب واحد. وللآلية اسم: تشفير ما بعد الكم، وهو عنصر تحكم في درج الاتصال بتطبيقات Android وiOS وmacOS وWindows وLinux، يختم جلسةً من طرف إلى طرف بين عميلك والمزوّد (§2.1). والشرطُ الأول أن يجد المستخدمُ المفتاح، لأنه لا يزال يُشحن متوقفاً (أدناه). أما أن يكون الختم قد أخفق في القيام في صمت فلم يعد شرطاً منذ 2026-08-10: فمع تفعيل المفتاح، الاتصالُ الذي يتعذّر ختمه لا يحمل أي حركة تطبيقات إطلاقاً بدل أن يحملها في العلن (§2.2). والذي يبقى مشروطاً هو أن يكون المشغّل، الذي يخدم مفاتيح المزوّدين التي يُفحص الختم في مقابلها، يخدمها بأمانة (§7.4). اقرأ ذلك قبل أن تعوّل على هذه الخاصية؛ فهو غير ظاهر للمستخدم.

الافتراضي عند الإطلاق: صدر الحكم بتفعيله؛ ولا يزال يُشحن متوقفاً حتى 2026-09-24. حكم مالك المنتج في 2026-08-07 بأن تُشحن الجلسة المختومة مفعّلة، كي يكون عمى المشغّل هو الافتراضي لا اختياراً على المستخدم أن يتخذه. ولم يتحقق ذلك بعد. فعَلَمُ ملف الأداء لا يزال false في التطبيقات الخمسة كلها — android/.../PerformanceProfileSettings.kt:34,88 وapple/app/network/Shared/ViewModels/DeviceManager.swift:395,479 وwindows/app/src/App/SdkHost.h:192 وlinux/app/src/ConnectDrawer.cpp:746 — وSDK تُطلق الجهاز بلا ملف أداء أصلاً. ومن دون العَلَم يُبقي عملاءُ النافذة على DefaultEncryptionSettings، ووضعُها EncryptionModeOff ‏(connect/transfer_encrypt.go:768-770؛ sdk/device_local.go:4483)، فالعميلُ بالإعداد الافتراضي اليوم لا يعمل بوضع Required ولا بوضع Opportunistic: فهو لا يفتح أي جلسة مع المزوّد، وكلُّ حزمة من حزم التطبيقات تعبر المشغّلَ غير مختومة (البند 7 من verify/BEFORELAUNCH.md، والحكم: يُخفق). وكلُّ عبارة في هذه الوثيقة عن سلوك الفشل المغلق محصورةٌ بحال تفعيل المفتاح، وعلى القارئ أن يفترض أنه متوقف ما لم يفعّله بنفسه.

وتلك الفجوة صارت تهمّ في اتجاه أكثر مما كانت تهمّ من قبل. فلأن بوابة الفشل المغلق مربوطة بهذا المفتاح نفسه، فإن تفعيلَ الافتراضي من شأنه أن ينشر ضماناً حقيقياً إلى كل مستخدم بدل أن يوسّع مدى إخفاق صامت وحسب — وهو عكسُ ما حذّر منه هذا القسم حين كان الختم يستطيع أن يفشل مفتوحاً في كل الأوضاع. وما لا يزال الافتراضي عاجزاً عن إصلاحه هو §7.4، وهي خاصية في الختم نفسه: فالختمُ المفعَّل افتراضياً والذي يستطيع المشغّل أن يضع آلةً في وسطه لا يحمي إلا من مشغّل سلبي.

وامتداد المتصفح بلا أي جلسة مختومة إطلاقاً (extension/src/utils/auth-params.ts:33، performance_profile: null). وذلك حدُّ نطاق لا افتراضيٌّ، وحكمُ الإطلاق لا يغيّره.

وحيثما غاب الختم — نقاط نهاية المتصفح والبروكسي (§2.4)، والتطبيقات الأصلية عند إيقاف الختم (§2.2) — يُنهي المشغّل بروتوكول TLS/QUIC الخاص بالعميل ويُرحّل حزم IP نصاً صريحاً (connect/protocol/ip.proto:9-16). وهو يختار ألا يحلّل إلا ترويسة التوجيه — فـFilteredTransferFrame لا يحتوي إلا مسار النقل (connect/protocol/transfer.proto:85-87)، والمرحّلُ يفكّ ترميز تلك الرسالة بالضبط ولا شيء أكثر (server/connect/resident.go:3824) — وذلك انضباط، لا حاجزٌ تشفيري.

2.1 ما هو الختم، بدقة

جلسةُ TLS 1.3 مباشرةً بين العميل والمزوّد، محمولةً بوصفها أُطر تحكم عادية عبر المشغّل نفسه: فرسائلُ EncryptedControl تحمل بايتات المصافحة وتركب تدفق Pack/Ack العادي الموثوق المرتَّب، بجلسة واحدة لكل زوج من الأنداد، ويأخذ ClientId الأدنى معجمياً دورَ عميل TLS ‏(connect/transfer_encrypt.go:30-60). وتبادلُ المفاتيح هو الهجين X25519MLKEM768، مع X25519 التقليدي احتياطاً (connect/transfer_encrypt.go:376-384)؛ وAEAD هو AES-256-GCM على مفتاح بطول 32 بايت يُصدَّر تحت وسم RFC 5705 المسمّى urnetwork-connect-aead، مع nonce عشوائي بطول 12 بايت لكل رسالة (:88-99 للوسوم والأطوال، و:283-301 للبناء)؛ والهوياتُ Ed25519، يولّدها ClientKeyManager داخل العملية (connect/transfer_key.go:87-143، وهو استدعاءُ ed25519.GenerateKey الوحيد في الأشجار الثلاث، عند :113) — فعبارةُ «ما بعد الكم» تخص تبادل المفاتيح وحده. وTLS المتبادل مطلوب (ClientAuth: tls.RequireAnyClientCert، connect/transfer_encrypt.go:406)، وتُفحص شهادةُ النِّد عند طبقة التسلسل في مقابل التزام العقد لا عبر منظومة TLS؛ ويشرح التعليقُ عند connect/transfer_encrypt.go:386-398 أنه لولا mTLS لما كان لدى الجانب الذي يؤدي دور خادم TLS أيُّ PeerCertificates، ولفشل التحقق من العقد لنصف أزواج الأنداد كلها.

2.2 فشلٌ مغلق حين تطلبه، وفشلٌ مفتوح حين لا تطلبه

تحديث 2026-08-10. كان هذا القسم يقول إن الختم يفشل مفتوحاً في صمت وإنه لا يوجد خيار للفشل المغلق. لم يعد ذلك صحيحاً، وهذا التغيير أهمُّ تقسية في تاريخ هذه الوثيقة — فقد أغلق مسار الخفض في الاتجاهين معاً. وما يلي هو السلوك الراهن مقروءاً من المصدر.

لا يزال التشفير خاصية ثنائية القيمة: Cipher() != nil، ولا تزال الشفرة المعدومة تعني أن الجلسة غير صالحة للاستعمال: فمصافحةٌ فاشلة، أو إثباتُ هوية فاشل، أو عقدٌ لم يحمل مفتاح النِّد العام أبداً — كلُّها تتركها معدومة. والسببُ البنيوي جديرٌ بأن يُذكر مرة واحدة، لأن كل ما عداه يتوقف عليه — فـAEAD موجود قبل أن يُصادَق على النِّد، ويُحجب عن قصد. فـcompleteHandshake يركنه على derivedTlsCipher بدل أن يكشفه (connect/transfer_encrypt.go:1719-1726)، ولا يرقّي حقبةً إلى حال «قائمة» إلا maybeVerifyPendingPeerIdentityProof، ولا يفعل ذلك إلا عند إثبات يتحقق (:1928-2050، والترقيةُ عند :1979-1981). وCipher() يقرأ establishedEpoch ‏(:2625)، فالنِّدُّ غير المثبَت لا يتميّز، في نظر كل مستدعٍ، عن مصافحة لم تكتمل.

وتصحيحٌ واحد للنسخ الأسبق من هذا القسم: فشلُ إثبات الهوية لم يعد مجرد «تُترك بلا مصادقة». فهو الآن يضبط أيضاً identityFailedTerminal، ويلغي الحقبة، ويُطلق EncryptionEventIdentityFailed ‏(connect/transfer_encrypt.go:2005-2030). ولا يزال سطر السجل يقول «الجلسة تُركت بلا مصادقة» (:2027)، والصياغةُ تهوّن مما يفعله الكود.

والذي تغيّر هو ما يحدث بعد ذلك. فهناك الآن ثلاثة أوضاع (connect/transfer_encrypt.go، EncryptionMode):

الوضعالسلوك
EncryptionModeOffالقيمة الصفرية. طبقةُ الجلسة خاملة، وكلُّ شيء نصاً صريحاً.
EncryptionModeOpportunisticيختم متى قامت جلسة، ونصاً صريحاً حتى ذلك الحين — أو دائماً إن لم تقم أبداً. وهو السلوك التاريخي.
EncryptionModeRequiredلا يكشف بيانات التطبيقات نصاً صريحاً أبداً، لا إلى ندٍّ تُتوقَّع معه جلسة ولا منه.

وتحت وضع EncryptionModeRequired يُفرَض الضمان عند أربع نقاط:

  • بوابةُ دخول الإرسال (connect/transfer.go:6751-6800، في SendSequence.Pack). فحزمةُ التطبيق تنتظر الشفرة ضمن مهلة المستدعي ثم تُرفَض بلا إرسال — ولا تُخفَّض أبداً — بخطأ مُصنَّف ErrEncryptionRequiredNotEstablished ‏(connect/transfer_encrypt.go:505). والبوابةُ تقع عن قصد قبل إسناد رقم تسلسل: فمصافحةُ دور العميل تركب هذا التسلسل نفسه، وحجزُ إطار أُسند إليه رقمُ تسلسل سيفتح ثغرةً في جانب الاستقبال المرتَّب ويعلّق ClientHello خلف الثغرة، فتقع المصافحةُ التي كانت ستفتح البوابة في جمود متبادل.
  • صمّامُ الإرسال الاحتياطي (connect/transfer.go:11726-11738، في writeMaybeWrappedBytes). فالإطارُ الذي يبلغ الكاتبَ بلا شفرة يُرفَض بدل أن يُكتَب، وهو ما يغطي السباق الضيق حين تُهدَم جلسة بين وضع الإطار في الطابور وكتابته.
  • بوابةُ الاستقبال (connect/transfer.go:14886-14913). فإطارُ تطبيقٍ يأتي نصاً صريحاً من ندٍّ تُتوقَّع معه جلسة يُسقَط ويُدوَّن — وهذا هو النصف الأهم، لأنه يغلق الخفض حين يجرّد المهاجمُ الغلاف ويكون المتلقي لولا ذلك سيقبل النص الصريح. ويُقَرّ الإطار ثم يُهمَل بدل أن يُترك بلا إقرار، إذ حجبُ الإقرار يفتح ثغرة في التسلسل المرتَّب ويُعلِق الطرفين كليهما.
  • التصفيةُ المسبقة للمرشَّحين (connect/ip_remote_multi_client.go:11695-11710، EncryptionCapabilityPrefilter، وقيمتها الافتراضية true عند :196). فمرشَّحُ النافذة الذي تقول عنه واجهةُ مفاتيح المنصة خارج النطاق إنه لم ينشر مفتاح هوية قط يُفشَّل فوراً، إذ لا يستطيع أبداً أن يكمل المصافحة. وقاعدةُ الرفض ضيقة عن قصد (:13229-13236): إذ إن خطأ الجلب لا يُفضي إلى الرفض، كي لا يمكن تحويلُ تعذّر الوصول إلى المشغّل حظراً للمزوّد. وهي لا تعجّل إلا فشلاً مؤكداً؛ ولا تقبل مرشَّحاً أبداً.

وأُطرُ التحكم في المصافحة، والإقرارات، وأندادُ مستوى التحكم مستثناةٌ بحكم التصميم — فالبوابة تغطي حمولة التطبيقات، لا السقالة التي تُمهّد لها.

والوضعُ الذي تحصل عليه يحدده مفتاح تشفير ما بعد الكم. فحين يكون مفعّلاً يعمل عميلُ المستهلك بوضع EncryptionModeRequired ‏(connect/ip_remote_multi_client.go:13080-13091، انطلاقاً من عَلَم الملف عند :1311): فالمزوّدُ الذي يتعذّر عليه إقامةُ جلسة لا يحمل لك أي حركة تطبيقات إطلاقاً، بدل أن يحملها في العلن. والكلفةُ المعلنة هي التوافر، وقد قُبلت عن قصد. أما المزوّدون فيعملون بوضع EncryptionModeOpportunistic ‏(sdk/device_local_provider.go:186) كي يستطيع المزوّد الواحد أن يخدم المستهلكين المختومين وغير المختومين معاً؛ وذلك خيارُ توافق على جانب المستجيب ولا يضعف ضمان البادئ.

ولاحظ أن تعداد الأوضاع قيمتُه الصفرية هي Off ‏(connect/transfer_encrypt.go:469-504)، فبنيةُ الإعدادات الصفرية لا تشفّر شيئاً بدل أن تشفّر نصف تشفير في صمت. وذلك خيارٌ مقصود اتُّخذ حين استُبدل Encrypt bool بالحالة الثلاثية.

وماذا يعني هذا للمفتاح. فمع تفعيل تشفير ما بعد الكم لم يعد الختم يفشل مفتوحاً: بل يفشل مغلقاً، وبصوت عالٍ، عند الإرسال والاستقبال معاً. ومع إيقافه، وهكذا تُشحن التطبيقات، لا يكون العميل انتهازياً حتى: فهو يعمل بوضع EncryptionModeOff ولا يبدأ جلسةً أبداً، فتجري كلُّ حركة تطبيقاته غير مختومة، ولا شيء يخبر المستخدم. فالعبارةُ الأمينة إذن مشروطة، والافتراضيُّ يهمّ: انظر فقرة الافتراضي عند الإطلاق في §2.

والسلوكُ مغطّى باختبارات، لا بتعليقات وحدها — TestRequiredEncryptionFailsClosedAgainstPlaintextPeer وTestRequiredGateNonBlockingSendRefusesPreCipher وTestRequiredGateBoundedBudgetRefusesUnsent وTestRequiredSendRefusalTypedErrorAndEvent وTestRequiredContractFreeWithoutKeySourceFailsClosed.

وتعليقٌ واحد قديم ينبغي تجاهله أثناء التدقيق: فالتعليقُ داخل completeHandshake عند connect/transfer_encrypt.go:1723-1725 لا يزال يؤكّد بإطلاق أنه إلى أن يتحقق إثباتُ الهوية، «يرى مسارُ التغليف أن cipher == nil وتجري الحركةُ نصاً صريحاً». وذلك صحيح في وضعَي Off وOpportunistic وحدهما؛ أما البوابات أعلاه فتَجُبُّه تحت Required. والتعليقُ سابقٌ للإصلاح.

وقد أُغلق ذلك جزئياً منذئذ: فهناك الآن سطحٌ للهوية، وإن لم يكن لافتةً لكل اتصال. فـSDK تصدّر مجموعةَ الأنداد الذين قامت معهم جلسةٌ متحقَّقٌ من هويتها، ومعها بصمةُ عرض قياسية — فـsdk/post_quantum_identity.go يعرّف PublicIdentityKeyHash بأنه «تجزئةُ العرض القياسيةُ الوحيدة لمفاتيح الهوية على كل منصة: SHA256 للمفتاح، مرمَّزةً بـbase32 بأحرف كبيرة بلا حشو (RFC 4648)»، مع ProviderIdentity عند :24-30 ومتحكّمِ عرض عند sdk/post_quantum_identity_view_controller.go:21، ويستهلكها التطبيقان كلاهما (PostQuantumIdentityViewModel.kt وPostQuantumIdentityStore.swift). وذلك أكثر من خطّاف: إنه بدايةُ قناة مقارنة يستطيع المستخدم فحصها وهي مستقلة عن المشغّل، إذ يمكن مقارنة بصمة حُصل عليها من مكان آخر بالبصمة المعروضة. أما ما لا يزال ناقصاً فمؤشرٌ بسيط لكل اتصال يقول «هذه الجلسة مختومة». وإشاراتُ سجل الجهاز الباقية هي peer identity proof verified — cipher is now usable ‏(connect/transfer_encrypt.go:2004) عند النجاح، وErrorf عند الفشل (:2026-2029)، وحدثُ NotifyRequiredSendBlocked حين ترفض البوابة.

2.3 الوضع المباشر

إيقافُ الإخفاء القوي للهوية يضبط AllowDirect، وتعليقُ حقله نفسه يقول: // setting this to true exposes the real source IP to the provider (sdk/sdk.go:1130-1139). ويُجبَر التدفق على مجرى ندٍّ لند (connect/ip_remote_multi_client_probe.go:1207-1209) عبر قناة بيانات WebRTC/ICE ‏(connect/transport_p2p_webrtc.go:818-825)، وICE يعني أن كلتا النقطتين تعرف عنوان الأخرى. والافتراضيُّ هو الإيقاف — فملفُّ أداء معدوم يعطي القيمة false ‏(connect/ip_remote_multi_client.go:841-850؛ sdk/sdk.go:1130-1139).

وحالتان مُجبَرتان: الأنداد على الشبكة نفسها (أجهزتك أنت) يُسمح لهم بالمباشر دائماً (connect/ip_remote_multi_client.go:843-850,2656-2660)، والأجهزةُ المستضافة تُجبره على الإيقاف (sdk/device_local.go:608-615). ولاحظ أن الوضع المباشر يزيح المشغّل عن مسار البيانات وحده. فالعقودُ واختيارُ المزوّدين ورسائلُ التحكم لا تزال تمر عبر المنصة، فيظل المشغّل يعرف أي مزوّد استخدمت وكم بايت تحرك.

2.4 المسار الذي لا يغطيه الجدول: نقاط نهاية المتصفح والبروكسي

على امتداد المتصفح، وتطبيق الويب، ونقاط نهاية HTTPS/SOCKS5/WireGuard، يشغّل المشغّلُ العميل. فالامتدادُ يهيّئ جهاز بروكسي على جانب الخادم (extension/src/utils/auth-params.ts:22-36، enable_socks/enable_http) ويُوجَّه المتصفح إلى نقطة نهاية البروكسي لدى المشغّل — بـHTTPS CONNECT افتراضياً (extension/src/bridge/background.ts:220؛ extension/src/utils/proxy-manager.ts:264). ونسخةُ SDK التي تفتح العقود وتحمل نافذة المزوّدين تعمل في server/proxy، لا على جهاز المستخدم.

ولهذا لا يمكن أن توجد جلسة مختومة على هذه المنصات، بحكم المعمارية لا بحكم جهد هندسي لم يُبذل بعد. فالجلسةُ المختومة بين العميل والمزوّد، وضمانُها يتوقف على أن تعيش نقطةُ العميل على جهاز المستخدم نفسه. والمتصفحُ أو عميلُ البروكسي العادي يتكلم بروتوكوله هو (HTTPS CONNECT أو SOCKS5 أو WireGuard) ولا يستطيع تشغيل محرك الشبكة، فيشغّل المشغّل جهازَ العميل عن بُعد ويقوم مقام نقطة الترجمة بين البروتوكولين. وأي ختم يُتفاوض عليه من ذلك الجهاز البعيد سيبدأ داخل المشغّل — وهو الطرف الذي وُجد الختم ليُعميه. وقد قبل التنفيذُ هذه المقايضة عن قصد: فهذه السطوح موجودة للتيسير على منصات وبروتوكولات لا تستطيع استضافة العميل الكامل، ويجب ألا تصفها الوثائق بأنها مختومة.

والنتائج، مذكورةً بصراحة:

  • عمى المزوّد عن الهوية لا يزال قائماً — فالمزوّد لا يزال يتلقى معرّفات فقط.
  • وعمى المشغّل لا وجود له على هذا المسار بأي صورة. فالمشغّل يُنهي اتصال البروكسي، ويتلقى مضيف الوجهة من CONNECT، ويحمل مفاتيح العميل.
  • والجهازُ المستضاف لا يركّب أي مُجمِّع لترقية DNS (server/proxy/proxy_device.go:858، SetUpgradeMuxSettings(nil))، فـDoH داخل النفق في مسار التطبيق (§3.2) لا ينطبق هنا.

والذي يقوم مقام التشفير على هذا المسار هو انضباط التخزين: فمسارُ بيانات البروكسي لا يسجّل شيئاً على المسارات التي يقودها العميل، واختبارُ انحدار يثبّت ذلك — فـproxy/socks5_nolog_test.go، وفيه TestClientDrivenTrafficNeverLogs، يؤكّد انعدام أسطر السجل عبر الحالات المشوَّهة والمفرطة الحجم وغير القابلة للطلب. وتحفّظان أمينان: دافعُ الاختبار المعلن هو حجبُ الخدمة بتضخيم السجلات لا الخصوصية، ومعالِجُ التعافي من الذعر يسجّل عنوان عميل (proxy/socks5_server.go:129-132). انظر review/verified/PRIVACY-ENFORCEMENT.md §2.2.

3. الخصم: مزوّد خبيث

داخل النطاق، ومفترَض. يستطيع أي أحد أن يزوّد. ولا يوجد تصديق ولا رهن ولا فحص هوية ولا مقاومة لهجمات Sybil على مشاركة المزوّدين — فالبحثُ عبر أشجار server وconnect وsdk عن sybil|kyc|attestation لا يعيد شيئاً ذا صلة. والمزوّدُ يشغّل كوداً مفتوح المصدر على عتاد يسيطر عليه، فافترض أنه يشغّل إصداراً معدَّلاً.

ما يراه. كلَّ ما تراه شركةُ الإنترنت عن الحركة التي يحملها: عنوانَ الوجهة ومنفذها، وأحجامَ الحزم وتوقيتها، واسمَ SNI في TLS حيث يرسله العميل صريحاً. وهو يتلقى حزم IP خاماً ويطلبها بنفسه (connect/protocol/ip.proto:9-16؛ connect/ip.go LocalUserNat). ويرى أيضاً client_id المحمول بوصفه SourceId للعقد (connect/protocol/transfer.pb.go:1779؛ server/controller/connect_controller.go:816-830) — وهو معرّفٌ لكل خانة في النافذة لا يستطيع حلَّه إلى حساب إلا المشغّل، وليس device_id أبداً، إذ لا حقل له على السلك. وعمرُه، والأشياءُ العديدة التي تبقى فعلاً عبر الجلسات، في §9.

ما يستطيع فعله.

  • HTTP نصاً صريحاً: قراءته وتعديله. المنفذ 80 يُمرَّر إلى الخروج من دون تغيير افتراضياً — فـHttpUpgradeUnencrypted هو الوضع الافتراضي (connect/ip_mux_upgrade.go:38-47). والمزوّدُ في موضع نقطة Wi-Fi معادية لأي حركة ليست مشفّرة في ذاتها. وHTTPS يحمي محتوى التطبيق من المزوّد؛ ولا شيء في URnetwork يضيف طبقة ثانية فوق TLS الخاص بالوجهة نفسها.
  • إسقاط الحركة أو تأخيرها أو تعطيلها انتقائياً. المزوّدُ الذي يُقرّ باستلام الحركة ولا يعيد شيئاً يُوسم ثقباً أسود ويُزال (connect/ip_remote_multi_client.go:37-54)، فالمنعُ يُكتشف ويُلتفّ حوله — لكن الكشف إحصائي، والحركةُ التي رآها قبل الإزالة قد رُئيت بالفعل.
  • الكذب في الأداء لاجتذاب الحركة. الترتيبُ يستخدم زمن الاستجابة ومعدل النقل المقيسين (server/model/network_client_location_model.go:4098,4220)، والقياسُ للمشغّل لا تقريرٌ ذاتي من المزوّد، فهذا محدود — لكنه مدخلُ ترتيب لا ضابطَ سلامة.
  • ربطُ التدفقات داخل رؤيته هو. التثبيتُ لكل موقع يثبّت موقعاً على مزوّد واحد (connect/ip_remote_multi_client.go:837)، فيرى المزوّد الواحد شريحةً متماسكة من تصفح عميل واحد للمواقع التي يحملها.

ما لا يستطيع فعله بلا مساعدة. معرفةَ عنوانك الحقيقي على مسار مُرحَّل، أو معرفةَ حسابك أو بريدك الإلكتروني، أو نسبةَ client_id إلى شخص. فتلك تتطلب المشغّل (§5).

3.1 ماذا تقيّد طبقة ip_security وماذا لا تقيّد

طبقةُ ip_security في محرك الاتصال تعمل على خروج المزوّد نفسه وعلى جهاز المستخدم نفسه، لا مركزياً عند المشغّل أبداً — فحقلا المشغّل IngressSecurityPolicyGenerator/EgressSecurityPolicyGenerator موجودان لكن لا يُسنَدان ولا يُقرآن أبداً (server/connect/resident.go:263-264؛ review/verified/PRIVACY-ENFORCEMENT.md §4.1). وهي تحجب تواقيع BitTorrent ومشاركة الملفات، والبروتوكولات المبهمة غير القياسية، والوجهات المدرجة في قوائم السمعة، وحركةَ أنماط الهجوم، وتفعل ذلك على أساس رقيق عن قصد: خماسيةُ التدفق إضافةً إلى بادئة حمولة محدودة بـ8 حزم / 512 بايت.

وجداولُ السمعة مولَّدة لا مصونة يدوياً. فـconnect/security وconnect/blocker يجمّعان تغذيات استخبارات تهديد عامة — abuse.ch Feodo Tracker لمراكز قيادة شبكات البوت، وSpamhaus DROP وDROPv6، وEmerging Threats للعناوين المخترَقة، وBlocklist.de، وCINS Army، وTweetFeed، وViriBack، وBruteForceBlocker — في جداول نطاقات محزومة (46,789 نطاق IPv4 في لقطة 2026-08-05؛ وترويسةُ connect/ip_security_cfaa_block.go تحمل قائمة التغذيات والإسناد). وخطُّ إصدار البناء يعيد توليد الجدولين من التغذيات الحية (build/all/run.sh، خطوة CONNECT_IP_UPDATE)، فكلُّ إصدار يشحن لقطة حالية، والعميلُ المحدَّث يحجب أحدث فضاء عناوين مُدرَج. واللقطةُ تشيخ مع البناء المثبَّت؛ فالقوائم تتحدث مع كل إصدار، لا عبر الأثير.

وحدُّ بادئة الحمولة عند connect/ip_security_dmca.go:136، مع مسح اسم الخادم من مفتاح التدفق (:366,386,424) والعدّادات مفتاحُها (version, protocol, port) وحقلُ IP فيها لا يُفعَّل أبداً (connect/ip_security.go:371).

إنها ضابطُ سلامة للمزوّدين الأمناء، لا ضابطَ أمان في وجه المعادين منهم. فهي تقيّد ما يحمله إلى الخارج اتصالُ المزوّد. ولا تفعل شيئاً حيال ما يفعله المزوّد بالحركة التي يحملها فعلاً، وهي تعمل في عملية يملكها المزوّد، وإصدارٌ معدَّل يستطيع تعطيلها. فلا تقرأها حداً على مزوّد خبيث.

وتفصيلٌ واحد في التبليغ مكانه هنا لأنه قناةُ بيانات وصفية. فتطابقُ توقيع BitTorrent يعيد Incident، وهو يستدعي ReportAbuse (connect/ip.go:9014,9110)، فيرسل إطار تحكم PeerAudit إلى المشغّل يحمل معرّف جهاز النِّد وقيمة منطقية — بلا وجهة ولا نطاق ولا محتويات (connect/transfer.go:2971). وليس لدى المشغّل معالِجٌ لذلك النوع من الرسائل اليوم، فلا يُخزَّن شيء عند الاستلام (server/controller/connect_controller.go:132,436-437؛ وبحثُ grep على مستوى المستودع عن PeerAudit في server يعيد صفر نتيجة). وذلك انضباط على بُعد جملة case واحدة من التغير. أما إسقاطات البروتوكولات المبهمة المشفّرة فصامتة بلا أي تبليغ (connect/ip_security_dmca.go:483-486).

3.2 نظام أسماء النطاقات

على مسار التطبيق وSDK، يُعترض DNS العادي على المنفذ 53 في UDP وTCP ويُحلّ عبر DoH داخل النفق (connect/ip_mux_upgrade.go:120-148، مركَّبٌ افتراضياً عند sdk/device_local.go:7105)، إلى Cloudflare أو Google أو Quad9 أو OpenDNS (connect/net_http_doh.go:150-155). فيرى المزوّد إذن اتصال HTTPS بمحلِّل عام، لا الاستعلام. وحدّان أمينان:

  • نافذةُ تسريب DNS عند البدء موثَّقة في المصدر. فبينما لا يزال DoH داخل النفق يقوم، يُسابَق الاستعلام أمام محلِّل عبر خروج المضيف المحلي، «على حساب تسريب DNS وجيز أثناء البدء» (connect/ip_mux_upgrade.go:120-126,78-92). وشبكتُك المحلية وشركةُ الإنترنت تستطيعان رؤية تلك الاستعلامات.
  • نقطةُ نهاية WireGuard تسلّم العميل محلِّلاً عاماً ثابتاً، DNS = 1.1.1.1 (server/model/network_client_proxy_model.go:774)، والمنفذ 53 يُمرَّر بلا فحص من سياسة الأمان (connect/ip_security_cfaa.go:113). وعلى ذلك المسار تعبر استعلاماتُ DNS المزوّدَ نصاً صريحاً، ويستطيع المزوّد قراءتها والإجابة عنها.

ومحلِّلات DoH الأربعة أطرافٌ ثالثة ترى استعلامات تصل من عنوان خروج مزوّد، غير مربوطة بحسابك. وذلك اعتمادٌ حقيقي وهو غير قابل للتصفير: فلا بد لأحد أن يجيب عن DNS.

4. الخصم: مراقب شبكة

أربعةُ مواضع، من الأضعف إلى الأقوى.

شبكتك المحلية وشركة الإنترنت. يريان أنك تتصل بـconnect.bringyour.com عبر TLS/QUIC، أو بموسّع، مع الأحجام والتوقيتات. ولا يريان الوجهات داخل النفق، إلا في نافذة DNS الموثَّقة عند البدء أعلاه. وحيث تُحجب المنصة، تظهر الموسّعات بوصفها خدمات عادية على منافذ مصدَّقة ويبدّل العملاء شخصياتها مع تجزئة عشوائية (connect/net_extender.go:123-133؛ وتُبنى عند connect/net_extender_network.go:915)، ويوجد نقلٌ بهيئة DNS للشبكات التي لا يفلت منها إلا DNS ‏(connect/transport_pt.go:37,73). وهذه آلياتٌ لعبور جدران الحماية المحلية والإقليمية، مرتَّبة دون وسائل النقل المباشرة (connect/transport.go:29-33). وهي ليست دفاعات ضد تحليل الحركة، ولا ينبغي أن تُوصف بذلك أبداً.

مشغّل موسّع. يستطيع أي أحد تشغيل واحد، والتطبيقُ يقبل عنوان IP يُدخَل يدوياً، ولا يُشترط توقيع افتراضياً (connect/extender/extender.go:35-44). والموسّعُ هو نِدّ TCP، فهو يرى عنوان IP الخاص بالمستخدم المتصل، ولا يستطيع فك تشفير ما يمرّره، لأن جلسة TLS تجري خلاله إلى المنصة بدل أن تنتهي عنده (connect/net_extender.go:34-48). فساقُ الموسّع تضع إذن في المسار مراقباً لعنوانك عند القفزة الأولى ليس هو المشغّل. وتلك هي المقايضة التي يعقدها التصميم كي يوصلك عبر الحجب، ويستحق حجمُها الدقة: فساقُ الموسّع لا تضيف تشفيراً ولا مجهولية — إنها تعرف ما تعرفه شركةُ الإنترنت لديك أصلاً ولا شيء أكثر، باستثناءٍ واحد يرد أدناه. وفي العميل المشحون تُسابَق وسائلُ النقل المباشرة أولاً وتتوسع طالباتُ الموسّعات حين تفشل تلك (connect/net_http.go:1686-1709)؛ والعميلُ المضبوط بموسّعات مخصصة يستخدمها طريقه الوحيد (connect/net_http.go:1686-1709).

والاستثناءُ هو دور المزوّد. فكلُّ عميل يسبر الموسّعات ليرتّبها بحسب زمن الذهاب والإياب. ولا يصدّق الجهازُ على ما قاسه إلا ما دام يزوّد: يبدأ التصديق حين تشغّل مشاركة الاتصال ويتوقف حين توقفها، ولو في منتصف جولة سبر، والجهازُ الذي لا يزوّد أبداً يسبر الموسّعات من غير أن يكشف هويته (sdk/device_local_provider.go:1155-1167 وconnect/net_extender_network.go:1240). وما دام يزوّد، يحمل كلُّ ادّعاءٍ معرّفَ عميله ويُوقَّع بمفتاح عميله (connect/DESIGNNOTES4.md §1 و§3). فيعرف الموسّعُ أن المزوّدَ صاحبَ ذلك المعرّف، ومن ثَمّ صاحبَ ذلك المفتاح العام (§9.2)، قد قاسه من ذلك العنوان؛ ويتلقى المشغّلُ معرّفَ المزوّد، والموسّعَ، وزمنَ الذهاب والإياب، والوقتَ. والموسّعاتُ يقيس بعضُها بعضاً بالطريقة نفسها، ويوقّع الهدفُ توقيعاً مشتركاً على كل رحلة ذهاب وإياب يقبلها، ويحتفظ المشغّلُ بقياسات ping هذه يوماً واحداً ليشتقّ منها المواقع (connect/GEOMAP.md §2). وهذه بياناتٌ تشغيلية عن بنية تحتية عامة، تحت مفتاحٍ دائمٍ وعلنيٍّ أصلاً في دور المزوّد.

مراقبُ طرف واحد. مراقبةُ جانب العميل وحده تعطي «هذا المستخدم متصل بـURnetwork» وملفَّ بايتات وتوقيت. ومراقبةُ خروج مزوّد وحده تعطي الوجهات وملفَّ بايتات وتوقيت بلا هوية مرتبطة.

مراقبُ الطرفين. انظر §8. وهو الخصم الذي لا تهزمه URnetwork.

5. الخصم: المشغّل

المشغّل يصادق العملاء، ويختار المزوّدين ويرتّبهم، ويحرّر العقود، ويوزّع المفاتيح العامة، ويحمل الحزم على المسارات المُرحَّلة. وافترض في هذا القسم أنه معادٍ أو مُكرَه.

ما يراه، بلا أي جهد إضافي: حسابك؛ وعنوانك لحظة اتصالك؛ ومدينة/منطقة/دولة مشتقة من ذلك العنوان ببحث محلي في قاعدة بيانات — فـipDb في server/ip.go يفتح قاعدة بيانات MaxMind GeoLite2-City المضمَّنة (mmdb/geolite2.mmdb) من القرص، وip.go لا يجري أي نداء شبكي من أي نوع، فلا يُسلَّم العنوان أبداً إلى خدمة تحديد موقع جغرافي — ومحفوظة لكل اتصال (SetConnectionLocation في server/model/network_client_location_model.go)؛ وأي المزوّدين خدموك؛ وعددَ البايتات؛ وعلى المسار القياسي، عناوينَ الوجهات وبايتات الحزم داخل النفق — وإن كانت تلك عادةً داخل HTTPS الخاص بالوجهة نفسها أصلاً.

ما يخزّنه. سجلاتُ الاتصال والمصادقة تحفظ تجزئة أحادية الاتجاه بمفتاح لكتلة /29 المحيطة (في IPv4) أو /56 (في IPv6) بدل العنوان (server/ip.go:46-72). ودقةٌ تهمّ المدقّق: إنه pepper واحد على مستوى العملية كلها من الخزنة، محفوظٌ في الذاكرة مرة واحدة، بلا ملح لكل صف وبلا أي آلية تدوير في المستودع كله (server/ip.go:42-43)؛ وفضاءُ مفاتيح IPv4 هو 2^29 كتلة، فمن يملك الـpepper يستطيع عكسها بالقوة الغاشمة. وسرّيةُ الـpepper هي الخاصية الأمنية كلها. ونظامُ /verify الفرعي يستخدم /48 لـIPv6 لا /56 ‏(server/ip.go:74-89؛ server/model/verify_model.go:113-116). ومنفذُ المصدر يُخزَّن صريحاً إلى جانب التجزئة (server/db_migrations.go:2044). وصفُّ تدقيق إنشاء الحساب يسجّل التجزئة المفلفلة والمنفذ، ولا يسجّل أبداً ip:port الخام (server/model/network_model.go:1157)، وصفوفُ التدقيق تُزال بعد 180 يوماً (server/model/audit_model.go:1018,1052، مجدولةً عند server/taskworker/taskworker.go:85-88).

ما لا وجود له أصلاً كي يُخزَّن. لا تظهر أي وجهة ولا مضيف ولا URL ولا SNI ولا منفذ ولا نطاق في أي موضع من سجل النقل — فبحثُ grep في ملف الترحيل الكامل عن تلك المصطلحات يعيد صفر نتيجة (review/verified/PRIVACY-ENFORCEMENT.md §1.2). وتسجيلُ HTTP يمر عبر قائمة سماح من خمس ترويسات بالضبط (server/http_log.go:13-29)، مثبَّتة باختبار. ولا يحمل أي مقياس Prometheus عنواناً ولا مضيفاً ولا وجهة ولا منفذاً ولا معرّف عميل؛ ومن 39 مقياساً معلَناً ثلاثةٌ فقط موسومة وكلُّ وسم منها تعدادٌ محدود (review/verified/PRIVACY-ENFORCEMENT.md §2.5). ورفعُ سجلات تبويب الدعم يُصرَّف إلى io.Discard ‏(server/controller/log_file_controller.go:76)؛ ولا يبقى إلا البيانات الوصفية.

5.1 إلى متى يُحتفَظ بأي من ذلك، وإلى أين يذهب

الاحتفاظُ تفرضه عملياتُ كنس مجدولة في server/taskworker، لا سياسةٌ. والنوافذُ أدناه مقروءة من الثوابت التي تستدعيها تلك العمليات، وهي أقصر مما قد يتوقعه قارئُ سياسة الخصوصية — وهي سياسةٌ لا تنص على أي مدة احتفاظ إطلاقاً.

البياناتمدة الاحتفاظموضع الإنفاذ
صفوف الاتصالات، وما يتعلق بها لكل اتصال من مدينة/منطقة/دولة وزمن استجابة وسرعة8 ساعاتtaskworker/work/network_client_work.go:91؛ والحذفُ يتتالى إلى network_client_location في العبارة نفسها (model/network_client_model.go:2580)
عميل أعلى مستوى خامل (لا مصادقة ولا اتصال) ← يوسَم غير نشط30 يوماًTopLevelClientIdleExpiration ‏(model/network_client_model.go:2576)
عميل غير نشط ← حذف قاطع، مع التتاليات30 يوماً أخرىNetworkClientReapAfterDeactivate ‏(:2556)
فالجهاز غير المستخدَم إذن، من أوله إلى آخرهنحو 60 يوماًالنافذتان متسلسلتان
العقود المكتملة7 أيامtaskworker/work/subscription_work.go:21-43
صفوف التدقيق180 يوماًmodel/audit_model.go:1018,1052

ملاحظتان تهمّان المدقّق. نافذةُ الخمول شُدّدت من 90 يوماً إلى 30 في 2026-07-18؛ والثابتُ يحمل مسوّغه بنفسه، وتعليقٌ قديم عند taskworker/work/network_client_work.go:83-91 ما يزال يقول 90، وهو المصدرُ الأرجح لذلك الرقم إن صادفته في موضع آخر. وRemoveLocationLookupResults مجدولةٌ في كل دورة لكنها بلا أي مفعول (no-op) — فجسمُها ونداؤها إلى النموذج كلاهما مُعلَّق كتعليق، ولا وجود لجدول كهذا أصلاً. إنها بقيةٌ أثرية من الكود، لا بياناتٌ لم تُكنَس؛ فموقعُ كل اتصال يُحذف مع صف الاتصال أعلاه.

تحديدُ الموقع الجغرافي لا يغادر الآلة أبداً. فبحثُ المدينة/المنطقة/الدولة يقرأ قاعدة بيانات MaxMind GeoLite2-City المضمَّنة، mmdb/geolite2.mmdb، من القرص المحلي (ipDb في server/ip.go)، وip.go لا يتضمن أي عميل HTTP ولا يجري أي نداء شبكي. ويحدّث المشغّل ذلك الملف بعميل geoipupdate من MaxMind، وهو تنزيلٌ لا يرسل إلى MaxMind شيئاً عن أي مستخدم. This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com.‎ وتتضمن GeoLite2 بيانات GeoNames، المتاحة بموجب CC BY 4.0. فلا يُرسَل أي عنوان إلى خدمة تحديد موقع جغرافي، ولا تُشارَك أي معلومات شخصية مع أي طرف ثالث. والأطرافُ الثالثة الوحيدة في جدول الأطراف في هذه الوثيقة تحوز بياناتٍ يسلّمها المستخدم إليها مباشرةً باختياره ذلك المسار: محلِّلات DoH التي تصلها استعلاماتُ DNS الخاصة بالمستخدم نفسه عبر النفق، ومعالِجات الدفع التي يسجّل عندها ليُفوتَر.

موزّعُ حمل المدخل لا يسجّل عناوين العملاء، وذلك قابل للفحص. فإعدادُ nginx في موزّع الحمل يولّده warp، وهو مفتوح المصدر بترخيص Apache 2.0 ‏(warp/LICENSE)، فهذا مصدرٌ يستطيع أي أحد قراءته لا ادّعاءٌ عن النشر. وهناك طبقتان:

  • سجلُّ الوصول يستخدم صيغةً مبنية لهذا الغرض تُسقط العنوان — فـlog_format noclientaddr يحمل الوقت، والاتصال، والمضيف، والطلب، والحالة، والبايتات، والتوقيتات، والخادمَ الخلفي (upstream)، ووكيلَ المستخدم (user-agent)، وتعليقاتُ الإعداد تسمّي الغائبَ عن قصد: $remote_addr، وكذلك $http_x_forwarded_for و$http_referer، «فكلاهما قد يحمل عنوانَ عميل مُمرَّراً من موضع آخر» (warp/warpctl/config.go:1243-1254).
  • وسجلُّ الأخطاء لا يمكن تنسيقه، فيُزال حقلُ nginx الخاص , client: في مرحلة لاحقة بدلاً من ذلك: فموزّعُ الحمل يشغّل nginx مع تغليف مخرج stderr الخاص به بـClientAddrScrubber ‏(warp/nginx.go:98-103) الذي يجرّد ذلك الحقل بتعبير نمطي ثم يستبدل كلَّ قيمة حرفية متبقية لعنوان IPv4 أو IPv6 في أي موضع من المدخل بـ[scrubbed]، مع إبقاء المنفذ (warp/nginx.go:111,116,176-232). ولا يُستثنى شيء بحسب النطاق، فلا يمكن أن تخطئ أي قاعدة بشأن أيّ العناوين تخص المستخدمين.

واختبارُ انحدار يثبّت ذلك: فـTestNginxLogsOmitClientAddr ‏(warp/warpctl/config_test.go:1208) يفشل إن لم يسمِّ أيُّ access_log صيغةً، لأن الصيغة الاحتياطية المدمجة في nginx، «combined»، تبدأ بـ$remote_addr.

حاوياتُ الخدمات تُنقّى عند الواصف، لا عند موضع الاستدعاء. يشغّل warp حاوياتِ الخدمات بـ--log-driver=journald ‏(warp/warpctl/run.go:200) ولا يرشّح مخرجاتها، فظلّ أيُّ سطر كتبته خدمةٌ وفيه عنوان يصل إلى journald كما كُتب، لفترة من الزمن. وكانت مواضعُ الاستدعاء حقيقيةً وعديدة: يبني server/http.go:413,453 كائنَ http.Server بلا ErrorLog، فتكتب مكتبةُ Go القياسية http: TLS handshake error from عند كل اتصال نصف مفتوح — وهي الثغرة التي يغلقها proxy/http.go:199,273 بـErrorLog: discardLog؛ وSNI الذي يزوّده العميل يُسجَّل عند مستوى ERROR ‏(server/tls.go:111)؛ وعنوانُ متصل خام عند server/proxy/proxy_device.go:858؛ وهويةُ نِدّ WireGuard عند server/proxy/server.go:699.

ولم تعد تلك هي الحدّ، لأن الحدّ لم يعد موضعَ الاستدعاء. فكلُّ خدمة تستبدل واصفات الملفات الخاصة بمخرجَيها stdout وstderr بأنابيب عند أول عبارة في main، وتنقّي كلَّ ما يُكتب إليها (server.ScrubProcessLogs، server/scrub.go:62، ويُستدعى من الخدمات الثماني كلها — server/cli/{api,connect,alt,proxy,taskworker,gossip,monitor,mcp}/main.go). والعملُ على الواصف بدل الكاتب يعني أنه يغطي log وglog وErrorLog الافتراضي في net/http وأيَّ اعتمادية وأيَّ موضع استدعاء يُضاف لاحقاً، من دون أن يضطر أحد إلى العثور عليها. وهو يستخدم warp.ScrubAddrs نفسه (warp/nginx.go:207) الذي يستخدمه موزّع الحمل لـnginx، فهناك تنفيذٌ واحد للمُنقّي لا تنفيذان قد ينحرفان أحدهما عن الآخر.

وحدّان مقصودان، ينبغي للمدقّق أن يزن كليهما لا أن يأخذهما على الثقة:

  • تفريغاتُ الانهيار تمرّ بلا تنقية. فسطرُ panic: أو fatal error: أو signal SIG أو goroutine يُقفل المُنقّي على وضع التمرير لبقية عمر تلك العملية (scrubPassthroughMarkers، server/scrub.go:48). فالتفريغُ نادر، وهو أثمن ما في السجل حين يقع، فيُحفظ كاملاً — مع التسليم بأن إطار مكدّس أو سجلَّ معالج قد يحمل عنواناً. ويكتب المزلاجُ إشعاراً ظاهراً حين ينطلق، فالعمليةُ التي توقفت عن التنقية ليست أمراً يضطر المشغّل إلى استنتاجه.
  • وهو ينقّي كلَّ ما يُحلَّل بوصفه عنواناً. فسلاسلُ الإصدارات والمعرّفاتُ المفصولة بنقطتين رأسيتين التي تُحلَّل بوصفها عناوين تُحجب هي الأخرى. وأوضحُ نتيجة لذلك أن server/monitor/tailer.go، الذي يكشط ip:port من أسطر السجل، صار يرى [scrubbed]:443.

فعبارةُ «الخدمة لا تكتب عنوانك في سجلاتها» صارت الآن خاصيةً للعملية لا لمواضع الاستدعاء فيها. أما ما لا يثبته هذا فهو ما يفعله journald بعد ذلك بما يتلقاه؛ و§12 يسجّل ذلك بوصفه مفتوحاً. التفصيلُ الكامل عن مواضع الاستدعاء الأصلية: review/verified/PRIVACY-ENFORCEMENT.md §2.4.

ما يتحكم فيه المشغّل ويسهل إغفاله. اكتشافُ المزوّدين على جانب المشغّل كلياً: فـFindProviders2 هي نقطة النهاية الحية الوحيدة (server/api/api.go:140)، والمنصةُ تسجّل درجات المرشحين وترتّبهم (server/model/network_client_location_model.go:5056)، والعميلُ يأخذ المجموعة المرتّبة التي تُعطى له. والضابطُ الوحيد على جانب المستهلك قائمةُ حجب مواقع (server/api/api.go:149). ولا سبيل لدى العميل للتحقق من أن المزوّدين المعروضين عليه مستقلون بعضهم عن بعض أو عن المشغّل.

6. المزوّدون الذين يشغّلهم المشغّل، والتواطؤ بين المشغّل والمزوّد

6.1 هل يستطيع المشغّل تشغيل مزوّدين؟

نعم، ولا شيء في النظام يسم ذلك أو يمنعه. فالتزويدُ مفتوح لأي أحد بلا تصديق ولا رهن، ولا يوجد في الكود أي مفهوم لمزوّد من الطرف الأول أو رسمي أو مملوك للمشغّل — فالبحوثُ عبر server وconnect وsdk عن مثل هذا المفهوم لا تعيد شيئاً. والمزوّدُ الذي يشغّله المشغّل لا يمكن تمييزه عن مزوّد عضو.

ومع نقطة §5 بأن المشغّل يرتّب مجموعة المزوّدين ويعيدها، يعني هذا أن المشغّل يستطيع، من حيث المبدأ، وضع مزوّديه هو في نافذة مستخدم. ولا يوجد فحصُ تنوّع على جانب العميل، ولا تصديقٌ على الاستقلال، ولا قياسٌ منشور للأسطول من طرف خارجي. والموازناتُ الموجودة حقيقية لكنها جزئية: فالنافذة تحمل عدة مزوّدين في آن واحد — نافذةُ جودة من 2–6 (بحدّ أقصى صارم 12) ونافذةُ سرعة من 1–2 (بحدّ أقصى صارم 4)، وكلتاهما حيّة في الملف الافتراضي (connect/ip_remote_multi_client.go:160-189) — والمزوّدون يُزالون على إحصاءات غير صحية، وكشفِ الثقب الأسود، وفشلِ النبض، وتدويرِ عمر القناة (:37-54، :610-616). وتلك تحدّ كم يرى أيُّ مزوّد واحد. لكنها لا تحدّ مجموعةً من المزوّدين يختارها طرفٌ واحد.

6.2 ماذا يعيد المشغّل والمزوّد بناءه معاً، بحسب كل وضع

افترض أن المشغّل ومزوّداً أو أكثر في نافذتك يتشاركون ما يحمله كلٌّ منهم.

مُرحَّل قياسيمُرحَّل مختوممباشر
هويتك (الحساب، والبريد الإلكتروني/المحفظة/الدفع)المشغّلالمشغّلالمشغّل
عنوانك الحقيقي لحظة الاتصالالمشغّلالمشغّلالمشغّل، ومعه المزوّد مباشرةً
الوجهات التي زرتهاالمشغّل (الحزم الداخلية) ومعه المزوّدالمزوّد وحدهالمزوّد
الرابط بين الاثنينبديهي — فطرفٌ واحد يحمل الأمرين أصلاًالعقد: audit_contract_event يقرن هوية العميل والمزوّد لكل عقد (server/db_migrations.go:325-347)بديهي
النتيجةنسبةٌ كاملة للتصفح لكل ما حمله المزوّدون المتواطئوننسبةٌ كاملة للتصفح لكل ما حمله المزوّدون المتواطئوننسبةٌ كاملة للتصفح، ومع ذلك يعرف المزوّد عنوانك بلا معونة

الخلاصة الأمينة: الجلسة المختومة لا تدافع ضد التواطؤ بين المشغّل والمزوّد. فهي تزيل قدرة المشغّل المستقلة على قراءة حركة بياناتك؛ ولا تمنع مزوّداً يرى وجهاتك أصلاً من تسليمها إلى مشغّل يعرف من أنت أصلاً. وسجلُّ العقد الذي يصل الاثنين ليس تسريباً — إنه المحاسبة التي تقوم عليها الشبكة.

والذي يشتريه الختم أمام هذا الخصم هو حدُّ نطاق: فمع تفعيله، لا تحتوي رؤيةُ المشغّل نفسه أي وجهات، فيتطلب التواطؤُ تعاونَ المزوّدين المعيّنين الذين حملوا الحركة المعيّنة، في وقت حملها، بدل استعلام عن سجلات يحملها المشغّل وحده. وذلك فرقٌ ذو معنى في الجهد وفي ما يستطيع الإفصاحُ المُكرَه بلوغه بأثر رجعي (§10). وهو ليس حصانة، ولا يمنح الحصانةَ أيُّ ترتيب لثلاثة أطراف يختار فيه أحدُهم الآخرين.

الجواب البنيوي في التصميم، وهو غير منشور في الشبكة. يدعم بروتوكولُ السلك والعميلُ سَلسلة وسطاء مزوّدين إضافيين — MaxMultihopLength = 8 ‏(connect/connect.go:15-18)، وIntermediaryIds في CreateContract ‏(connect/protocol/transfer.proto:547-561)، وقبولٌ عند العميل في connect/ip_remote_multi_client_api.go:661-662، وسباكةٌ عند الخادم في server/controller/connect_controller.go:915. ونقطةُ نهاية الاكتشاف الحية لا تملأ الحقل أبداً — فـFindProvidersProvider يُبنى في موضعين بالضبط ولا يضبطه أيٌّ منهما (server/model/network_client_location_model.go:5044,5093؛ والحقلُ مُعلَن عند :3693 ولا يُسنَد في أيٍّ منهما) — فالشبكةُ المشحونة تُسند سلاسل بطول واحد. فسَلسلةُ المزوّدين قدرةٌ في البروتوكول لا خاصيةٌ في النشر، واقتباسُ الرقم 8 بوصفه عددَ قفزات مضلِّل.

7. مفاتيح هوية المزوّدين، وهل يستطيع المشغّل استبدال واحد منها

هذا هو القسم الأجدر بالهجوم، لأن الجواب مزعج والمصدرُ نفسه يقول ذلك.

7.1 الإصدار والربط

كلُّ connect.Client — أي عميلُ المزوّد، وكلُّ عميل من عملاء نافذتك — يحمل زوج مفاتيح Ed25519 مولَّداً داخل العملية بـClientKeyManager (connect/transfer_key.go:87-143، وهو استدعاءُ ed25519.GenerateKey الوحيد في الأشجار الثلاث). وهو طويلُ العمر حيث تُحفظ البذرة وتُعاد قراءتها، وذلك هو عميل المزوّد، وطازجٌ لكل عملية حيث لا تُحفظ، وذلك هو عملاء النافذة؛ ويغطي §9 الفرق ولماذا يهمّ. والنصفُ الخاص لا يغادر العملية أبداً؛ وبذرةٌ بحجم خاطئ خطأُ بناءٍ قاطع لا هويةٌ طازجة صامتة (:84-94). والنصفُ العام يُنشر إلى المنصة في رسالة تحكم ClientKey (connect/protocol/transfer.proto:684-700) ويُخزَّن على جانب الخادم في Redis عند ckey_ بلا انتهاء صلاحية — وRedis هو مصدر الحقيقة، ولا يوجد جدول SQL ‏(server/model/network_client_key_model.go:19-65).

فالربطُ إذن هو client_id → public key، والمشغّلُ يصدر client_id ويحمل الخريطة. ولا يوجد مرساة خارجية: فالمعرّفات لا تُشتق من المفاتيح، ولا يوجد سجلُّ شفافية ولا DHT. والبديلان كلاهما مسجَّلان في التصميم بوصفهما دُرسا وأُجّلا (connect/DESIGNNOTES.md §3.7، «البدائل المؤجَّلة»).

7.2 التدوير والإبطال

إعادةُ النشر تكتب فوق القديم — فتعليقُ SetClientKey نفسه يقول: «مفتاحُه client_id (التدوير يكتب فوقه)» (server/controller/connect_controller.go:1193-1199). ويُحذف المدخل حين يُقلَّم معرّفُ العميل (network_client_key_model.go:67-78، ويُستدعى من RemoveDisconnectedNetworkClients). ولا يوجد تدوير مجدول، ولا انتهاء صلاحية، ولا قائمة إبطال. وعميلُ المزوّد يحفظ مادة مفتاحه ويعيد قراءتها، فهويته مستقرة عبر إعادات التشغيل (sdk/device_local.go:3592-3597؛ sdk/local_state.go:788) — وعلى Android، مستقرة عن قصد عبر مسح الخروج التلقائي من الحساب كذلك (§9.2). والعميلُ الذي لا بذرة محفوظة له يولّد هوية طازجة عند كل بدء عملية، وهو ما يفعله عملاء نافذتك. وتغييراتُ المفاتيح في منتصف الجلسة مرفوضة: فـSetPeerClientPublicKey أولُ كتابة تفوز، والمفتاحُ اللاحق المختلف يُسجَّل ويُتجاهَل (connect/transfer_encrypt.go:2051-2096).

7.3 التحقق، والدفاعان

  • الدفاع 1، ربطُ الشهادة الموقَّع. يوقّع المزوّد سلسلةَ شهادات TLS العابرة الخاصة به بمفتاح Ed25519 وينشر الاثنين (connect/protocol/transfer.proto:494-522). وتُرفق المنصةُ السلسلةَ والتوقيعَ ومفتاحَ المزوّد العام بكل عقد يسمّي ذلك المزوّد (:340-368، :388-406). ولا يقبل العميلُ السلسلة إلا إذا تحقّق التوقيع تحت مفتاح المزوّد العام، ثم يفحص الشهادة المقدَّمة في المصافحة في مقابل السلسلة المقبولة (connect/transfer.go:9560-9583، ويُفحص عند :11928).
  • الدفاع 2، إثباتُ الهوية داخل المصافحة. بعد مصافحة TLS يوقّع كلُّ جانب مخرجَ المصدِّر بحسب RFC 5705 تحت الوسم urnetwork-sequence-identity-proof ويرسله بوصفه EncryptedControl (connect/protocol/transfer.proto:615-660,701-712). ويُحجب AEAD عن مسار التغليف حتى يتحقق إثباتُ النِّد (connect/transfer_encrypt.go:1719-1726، :1719-1756)، فمن يعترض في الوسط وينهي TLS على ساق ويعيد المصافحة على الأخرى يُنتج مصدِّرَين غير متطابقين ولا يستطيع تزوير التوقيع.

والدفاعان كلاهما يتحققان في مقابل peerClientPublicKey — وتلك القيمة مأخوذة من العقد الذي حرّرته المنصة (connect/transfer.go:9560-9583؛ connect/transfer_encrypt.go:2051-2096).

7.4 هل يستطيع المشغّل استبدال مفتاح مزوّد؟ موقَّعٌ وقابلٌ للنسبة وأصعبُ بكثير — لكنه غير مستحيل.

تحديث 2026-09-17. كان هذا القسم يقول «اليوم: نعم». ولم يعد ذلك هو الجواب كله، وهذا التغيير ثاني أهمّ تقسية في تاريخ هذه الوثيقة بعد إصلاح الفشل المغلق.

الهجوم، وشكلُه لم يتغيّر. فالمشغّلُ الذي يستبدل الشهادة، وتوقيعَ الشهادة، وdestination_client_public_key في تناسق واحد يهزم الدفاعين المذكورين في §7.3 معاً، لأن كليهما يتحقق في مقابل مفتاحٍ زوّده العقدُ نفسه. وملاحظاتُ التصميم تقرّر ذلك بكلمات المستودع نفسه (connect/DESIGNNOTES.md §3.7).

ما يقف في الطريق الآن. فمع تفعيل تشفير ما بعد الكم (EncryptionModeRequired)، يحجب العميلُ شفرةَ الجلسة حتى يُعضَّد مفتاحُ الهوية الذي يزوّده العقد في مقابل تاريخِ تسجيلٍ موقَّع نشره المشغّل، والخلافُ المتحقَّق منه قاطعٌ بالنسبة إلى ذلك المزوّد — فلا تجري إليه أي حركة، ويُسقَط من النافذة.

وكلُّ تسجيل (connect/transfer_key_history.go:200-213) يحمل نطاقَ النشر، ورقمَ جيلٍ رتيبَ التزايد، ومفتاحَ الهوية، ورابطاً إلى تجزئة محتوى سابقه، وتوقيعَ secp256k1 من الموقِّع الجذري للمشغّل. ويفحص العميلُ الترميزَ القياسي، واسترجاعَ الموقِّع من التوقيع، وتتابعَ السلسلة بلا انقطاع منذ الجيل 1، وسلطةَ النطاق والموقِّع، والتقدّمَ إلى الأمام فقط في مقابل رأس مثبَّت (VerifyClientKeyHistory، :422). والبوابةُ تسكن في Cipher() ‏(connect/transfer_encrypt.go:2699-2716)، وجدولُ القرار في connect/transfer_key_history_session.go:157. وعلى جانب الخادم: يخزّن server/model/st_client_key_history.go السلسلةَ الموقَّعة، ويخدمها server/controller/st_client_key_history_public.go:77، وتوجيهُها عند server/api/api.go:226.

فلم يعد المشغّل يستطيع أن يكون غير متسق وقادراً على الإنكار معاً. فكي يستبدل، عليه أن يلتزم بسلسلة في صيغة موقَّعة، وذلك الأثرُ دائم، ويُنسَب إليه، ويفترق عما يراه كل قارئ آخر لمعرّف العميل ذاك — ومنهم المتحقِّقون المستقلون الذين يدقّقون هذا السجل أصلاً. والفحصُ المتقاطع الأقدم غير الموقَّع في مقابل GET /key/ لا يزال يعمل إلى جانبه، ولا يزال استشارياً، ويسجّل CONTRACT vs FETCHED ... MISMATCH ... (today: log only) عند connect/transfer_encrypt.go:2131-2134.

وأربعُ دقائق ينبغي للمدقّق أن يمسك بها.

  1. الموقَّعُ ليس عصيّاً على التزوير من الموقِّع نفسه. فالمشغّلُ الخبيث منذ أول اتصال لك والمتسقُ مع نفسه في ذلك يوقّع سلسلةً واحدة متماسكة تسمّي مفتاحه هو. والتحققُ من السلسلة يفحص السلسلة، لا السلطةَ التي خلفها؛ والبروتوكولُ نفسه يقول ذلك — «على المستدعي أن يصادق على حدة على الموقِّع المتوقَّع في نسخة السلسلة المثبَّتة لدى المشغّل» (sn/protocol/client_key_history.go:166-167). ويردّ العميلُ بموقّعين مثبَّتين في البناء إضافةً إلى الثقة عند أول استخدام (connect/transfer_key_history.go:491)، وهو ما يمسك مشغّلاً ينقلب خبيثاً، أو يستهدف مجموعةً فرعية، أو يتشعّب — لا مشغّلاً لم يكن أميناً قط. وإغلاقُ ذلك يتطلب القراءةَ التعددية عبر مشغّلين يعمل كلٌّ منهم باستقلال، وهي مؤجَّلة (connect/DESIGNNOTES3.md §10).
  2. الإغفالُ لا يزال الحركةَ الأرخص، والسقّاطةُ (ratchet) هي ما يحدّه. فالتحققُ من الشهادة يُتخطّى من دون إقفال حين تكون المجموعة الموثوقة فارغة (connect/transfer.go:11915-11926)، وإثباتُ الهوية لا يمكن التحقق منه إطلاقاً حين يغيب مفتاح النِّد (connect/transfer_encrypt.go:1947-1950). وحجبُ التاريخ الموقَّع هو الحركةُ نفسها على مستوى أعلى. ويحدّه سقّاطةُ مستويات: فالنِّدُّ الذي تُحُقِّق منه مرةً في مقابل دليل موقَّع لا يُقبل بعدها أبداً من دونه، ومتى قدّم المشغّلُ دليلاً موقَّعاً إلى نسخةِ تطبيقٍ ما، رُفض عندها النِّدُّ الذي لا دليل له (connect/transfer_key_history_session.go:168-185، ويُحفظ في sdk/peer_client_key_pin_store.go). وخطأُ الجلب لا يُعامَل رفضاً عن قصد — وإلا لأقصى انقطاعٌ واحد في واجهة API كلَّ مزوّد عن كل مستخدم دفعةً واحدة — فالمشغّلُ الذي يعطّل نقطة نهايته بنفسه يُنزِل العملاءَ إلى مفتاح العقد. وتلك مقايضةُ توافر مقبولة، وبقيةٌ حقيقية.
  3. وكلُّ ذلك محصور بالمفتاح. فالإنفاذ لا يجري إلا تحت EncryptionModeRequired ‏(connect/transfer_key_history_session.go:91)، لأن الرفض تحت Opportunistic يتدهور إلى النص الصريح، وهو ما أراده المشغّلُ المستبدِل أصلاً. وما دام المفتاح يُشحن متوقفاً (§2.2)، فلا ينال العميلُ بالإعداد الافتراضي شيئاً من هذا.
  4. أصالةُ العقد مرساتُها المشغّل كذلك. فالمزوّد يتحقق من HMAC العقد بمفتاح سرّ تزويد يولّده المزوّد وينشره إلى المنصة (server/controller/connect_controller.go:727,840؛ sdk/device_local.go:3592-3597). والمشغّلُ يحمل نسخة منه، وهو ما يتيح له تحرير العقود أصلاً. وذلك متوقَّع من سلطة محاسبة، لكنه يعني أن «العقد أصيل» ليست عبارةً مستقلة عن المشغّل.

ماذا يقوّض هذا وماذا لا يقوّض. إنه لا يمسّ عمى المزوّد عن الهوية، فذاك لا يتوقف على أي توزيع مفاتيح. وعمى المشغّل عن المحتوى على المسار المختوم لم يعد خاصيةَ سياسة مجرّدة: فالاستبدالُ صار يكلّف المشغّل أثراً موقَّعاً ودائماً يستطيع طرفٌ ثالث كشفه. لكنه لا يزال ليس خاصيةً تصمد في وجه مشغّل مصمِّم لم يكن أميناً قط، وأي وثيقة من وثائق URnetwork تصف الجلسة المختومة بأنها تجعل عمى المشغّل غير مشروط فهي تبالغ. وهذه الوثيقة تنسخ تلك الصياغة.

8. التوقيت وربط الحركة، والمراقب السلبي العالمي

8.1 لا توجد دفاعات ضد تحليل الحركة. ولا واحد.

لا تشحن URnetwork أي حركة تمويه، ولا حشواً، ولا خلطاً، ولا تأخيرَ تجميع، ولا تشكيلاً للحركة على بيانات المستخدم. وقد فُحص هذا فحصاً مستوفياً عبر connect وsdk وserver:

  • لا يوجد في أي موضع أي مولّد لحركة زائفة أو خادعة أو صورية أو تمويهية.
  • وAEAD حافظٌ للطول بالبناء: فطولُ النص المشفّر هو nonce + النص الصريح + الوسم (connect/transfer_encrypt.go:303-336). ولا يحمل أي protobuf في connect/protocol/ حقلَ حشو، والتأطيرُ بادئةُ طول مجرَّدة من 4 بايتات (connect/message_framer.go:29-31).
  • والدمجُ على جانب الإرسال بلا تأخير صراحةً: «لا يوجد انتظارُ تجميع: فالتسلسل لا يأخذ Pack ثانية إلا وهي مصطفّة أصلاً» (connect/transfer.go:2971). وكلُّ jitter في الأشجار تذبذبُ تراجعٍ في إعادة الاتصال أو توزيعٌ لعمر القناة، لا تشكيلاً للحركة. وضغطُ الإقرارات (connect/transfer.go:272) يؤخّر الإقرارات وحدها، لخفض حجم رسائل الترحيل.
  • والموضعُ الوحيد الذي تُنظَّم فيه سرعة حركة المستخدم أصلاً هو حدُّ WritePacketsPerSecond في النقل بهيئة DNS (connect/transport_pt.go:68,359-361)، وهو موجود ليكون لطيفاً بالمحلِّلات وينطبق على أقل أوضاع النقل تفضيلاً وحده. واستعلاماتُ «الضخ» الخاملة فيه بطول متميّز عن الحاملة للبيانات، فهو لا يخفي حجماً ولا معدلاً.

ولذلك: الخصمُ الذي يراقب الحركة الداخلة إلى جهازك والحركة الخارجة من المزوّدين الذين يحملونها يستطيع ربط الاثنين بالحجم والتوقيت. وURnetwork لا تدافع ضد ذلك الخصم. وهذه هي العبارة نفسها التي تقولها Tor عن الربط من طرف إلى طرف، وهي تنطبق هنا بهامش أقل، لأن هدف تصميم URnetwork هو انخفاض زمن الاستجابة — وهو بالضبط ما يجعل الربط أيسر. فمسارٌ بأربع سيقان محدودُ زمن الاستجابة مقايضةٌ مقصودة أمام تأخير شبكة الخلط المضاف، وهذا هو الجانب الذي يدفعه المستخدم من تلك المقايضة. وعدُّ ساق الموسّع لا يغيّر ذلك: فتلك القفزة تمرّر الجلسة المشفّرة إلى المشغّل من دون أن تنتهي عندها، فهي لا تضيف طبقةً يقشرها المراقب ولا تأخيراً يضيّعك فيه.

وأمران يُخلط بينهما وبين الدفاعات أحياناً وليسا كذلك. فالموسّعاتُ ووسائلُ النقل المشكَّلة (connect/net_resilient.go:114-215، connect/transport_pt.go:37,73) تستهدف الحجب المبني على الفحص العميق للحزم، والطبقةُ المرِنة تعطّل نفسها بمجرد قيام المجرى (net_resilient.go:102,146-161) — فهي لا تمسّ سجلات طور البيانات أبداً. ومخازنُ تذاكر جلسات TLS لا تُشارَك عن قصد بين مسارات الخروج كي لا يستطيع خادمٌ ربطها عبر تذكرة مستهلَكة (connect/net_tls.go:38-42,99-103)؛ وذلك عدمُ قابلية التذاكر للربط، لا تحليلَ حركة.

ولا شيء في المصدر يقرّ بربط الحركة بوصفه حداً: فالبحث عن traffic analysis وtiming correlation وtraffic correlation وglobal adversary عبر الأشجار الثلاث كلها يعيد صفر نتيجة. ونموذجُ التهديد داخل المستودع (connect/DESIGNNOTES.md §3.7) كله عن اعتراض المشغّل في الوسط. وهذه الوثيقة هي أول موضع تُكتب فيه الثغرة.

8.2 نافذة المزوّدين المتعددين ليست آلية مضادة للربط

تخرج الحركةُ عادةً عبر عدة مزوّدين في آن واحد — بين ثلاثة وثمانية شائعاً عبر النافذتين — والتثبيتُ لكل موقع يُبقي موقعاً معيّناً على مزوّد واحد (connect/ip_remote_multi_client.go:160-189,837). وذلك يحدّ فعلاً كم يرى أيُّ مخرج واحد. لكن مسوّغ تصميم النافذة في المصدر هو الموثوقية من أوله إلى آخره — تخفيفُ الوجهات السيئة، وإعادةُ التحجيم الموزونة بالصحة، وكشفُ الثقب الأسود (:27-52) — والتعليقُ الوحيد الذي يقول «ألفةٌ أقل أكثرُ خصوصية» يجلس على ClientAffinityTimeout، وهو مقبضٌ مُعلَّق كتعليق (:587-591، ومرة أخرى في الافتراضيات عند :220)، بينما يُشحن DestinationAffinity بقيمة true لأسباب موثوقية (:270). فتعددُ هوية الخروج فائدةٌ حقيقية ومن العدل ادّعاؤها؛ أما ادّعاؤها بوصفها دفاعاً مصمَّماً ضد الربط فلا يسنده الكود.

8.3 المراقب السلبي العالمي: خارج النطاق

الخصمُ السلبي العالمي — القادر على مراقبة حصة كبيرة من وصلات الإنترنت في آن واحد — خارج النطاق، وURnetwork لا توفّر أي دفاع ضده. فمع انعدام الحشو وانعدام حركة التمويه، يربط ذلك الخصمُ التدفقات عبر المرحّل والمزوّدين مباشرةً. وهذا هو الموقف نفسه الذي تتخذه Tor، وخلافاً لـTor، لا تضيف URnetwork حتى تأخيراً لكل قفزة يرفع الكلفة.

والمختلفُ فعلاً، والجدير بالذكر من دون تضخيمه: أن مجموعة الخروج جمهورٌ متبدّل من اتصالات سكنية مستقلة لا نطاقاتُ عناوين منشورة لمشغّل واحد، فعلى الخصم أن يراقب مجموعة أوسع ومتحركة من النقاط ليغطي مستخدماً واحداً، ولا يمكن تعدادُ تلك النقاط من قائمة خوادم. وذلك يرفع الكلفة. ولا يغيّر النتيجة لخصم يستطيع أصلاً رؤية الطرفين.

9. معرّفات الأجهزة وقابلية الربط

9.1 ما يستطيع المزوّد ملاحظته عنك

قيمةُ SourceId في العقد معرّفُ عميل لكل خانة في النافذة، لا جهازك. فـStoredContract.SourceId هو client_id للمتصل المصادَق عليه (server/controller/connect_controller.go:816-830؛ ويحلّله المزوّد عند connect/transfer.go:6088-6106). والمولّدُ يسكّ معرّف عميل ورمز jwt ومعرّف نسخة طازجة لكل دخول نافذة ويزيلها عند التفكيك — والمصدرُ يقول ذلك مباشرةً: «يسكّ مولّدُ الـapi معرّفَ عميل منصة عابراً (مع معرّف نسخة طازج) لكل دخول نافذة ويزيله عند التفكيك» (connect/ip_remote_multi_client_identity.go:10-15؛ ويُسكّ عبر AuthNetworkClient عند connect/ip_remote_multi_client_api.go:691-727، ويُطلَق عبر RemoveNetworkClient عند :802-832). وفوق ذلك تتبدّل عضويةُ النافذة: فالمزوّدون يغادرون على إحصاءات غير صحية، وكشفِ الثقب الأسود، وفشلِ النبض، وتدويرِ عمر القناة (connect/ip_remote_multi_client.go:37-54)، فمعرّفُ الخانة قصيرُ العمر بحكم البناء لا بحكم السياسة.

حركتُك موزّعة على عدة مزوّدين في آن واحد، فلا يرى مخرجٌ واحد العميلَ بأكمله. فالملفُّ الافتراضي يشغّل نافذةَ جودة من ستة ونافذةَ سرعة من واحد إلى اثنين (connect/ip_remote_multi_client.go:160-189). وكلُّ مزوّد يحمل شريحةً من نشاطك تحت معرّف خانته هو، والتثبيتُ لكل موقع يُبقي موقعاً معيّناً على مزوّد واحد بدل أن يبعثره عليهم جميعاً (:837). وذلك يحدّ فعلاً ما يستطيع مخرجٌ واحد أمينٌ لكنه فضولي أن يعيد بناءه. وهو ليس دفاعاً مضاداً للربط، و§8.2 يقول لماذا.

معرّفاتُ النافذة ومعرّفُ دورك مزوّداً فضاءا هوية منفصلان، فلا يستطيع مزوّدٌ أن يعكس معرّف خانة ليصل إليك. إن كنت تشارك اتصالك أيضاً، فعميلُ المزوّد لديك هو العميلُ الأعلى مستوى الذي يحمل بذرةَ المفتاح الدائمة المحفوظة (§9.2). أما عملاء النافذة فعملاء آخرون: يُبنَون من DefaultClientSettingsWithBufferSize طازجة (sdk/device_local.go:4349) لا تحمل ClientKeySeed، فيولّد كلٌّ منهم زوجَ مفاتيحه الخاص لكل عملية، ومعرّفُ عميلك أنت مستبعَدٌ صراحةً من مجموعة مرشّحيك أنت (sdk/device_local.go:4338). فالمزوّدُ الذي يحمل معرّف خانة ويبحث عنه عبر GET /key/ غير المصادَق عليه يحصل إذن على مفتاح طازج لتلك العملية لا يرتبط بأي شيء دائم — لا بهويتك مزوّداً، ولا عبر إعادات التشغيل، ولا عبر الخانات. والمفتاحُ الدائم القابل للحلّ علناً في §9.2 يخص دورَ المزوّد، ولا شيء على مسار الخروج يكشفه.

معرّفُ العميل الخاص بالجهاز لا يصل إلى مزوّد على مسار الخروج، والتتبّعُ قصير بما يكفي لفحصه. صحيحٌ أن DeviceLocal.sendPacket يضبط source := connect.SourceId(self.clientId) — وهو المعرّف الأعلى مستوى للجهاز — ويسلّمه إلى العميل المتعدد (sdk/device_local.go:4870، وصيغةُ الدفعات عند :4968). لكن تلك القيمة لا تُستعمل إلا للمحاسبة المحلية: مفتاحاً لإعادة تجميع أجزاء الخروج، ولفحص علاقة provideMode ‏(connect/ip_remote_multi_client.go:5876-5906). أما الإرسال على السلك فيقوم به عميلُ النافذة الخاص بكل خانة — self.client.SendMultiHopWithTimeoutDetailed(frame, self.args.Destination, …) ‏(connect/ip_remote_multi_client.go:14941-14948)، على Client سكّه المولّد لتلك الخانة (:13094) — ومعرّفُ مصدر الجهاز ليس بين وسائطه. وبحثُ grep عن source عبر منطقة الإرسال تلك لا يعيد شيئاً.

والهويةُ الوحيدة الدائمة هي دورُ المزوّد، وهي تشير في الاتجاه الآخر. فالجهازُ الذي يشارك اتصاله يشغّل عميلاً ثانياً يُبنى عند sdk/device_local_provider.go:198 مع ProviderStreamPolicy = true ‏(:191) وبذرةِ المفتاح المحفوظة. وعلاقاتُ أنداد الشبكة وحركةُ العودة على جانب المزوّد تركب ذلك العميل، فتحمل client_id الأعلى مستوى المستقر والمفتاحَ الدائم. وتلك هي الهوية التي يَخدم بها الجهاز، لا الهوية التي يتصفح بها: فهي مكشوفة للمستهلكين الذين يخدمهم مزوّد، وهو ما يوثّقه §9.2 كاملاً، ولا تُسلَّم أبداً إلى المخارج التي تحمل حركةَ المستهلك نفسه. وفضاءا الهوية هما الموصوفان أعلاه، ولا يتقاطعان.

لكن تلك الهويات يُعاد استعمالها عن قصد حتى أربع ساعات، على كل مسار — لا على المستضاف وحده. فالجهاز يركّب مخزن هوية محلياً خاصاً به ما لم يكن مستضافاً (sdk/device_local.go:824-830)، وهويةُ النافذة المخزَّنة تُعاد أمام الوجهة نفسها حتى تبيت: windowIdentitiesStaleAfter = 4 * time.Hour (sdk/window_identity_store.go:42-45). والغرضُ إبقاء تدفقات NAT لدى مزوّد حيّة عبر إعادة تشغيل (connect/ip_remote_multi_client_identity.go:17-23). والكلفةُ أن المزوّد يستطيع أن يرى SourceId نفسه يظهر منك مرة أخرى عبر إعادات تشغيل التطبيق داخل تلك النافذة. قل أربع ساعات، لا «عابر».

ومفتاحُ هوية العميل Ed25519 طازجٌ لكل عميل نافذة، على مسار الخروج. فإعداداتُ عميل النافذة تُبنى من DefaultClientSettingsWithBufferSize طازجة (sdk/device_local.go:7105)، وهي لا تحمل ClientKeySeed (connect/transfer.go:1547-1558)، فيولّد ClientKeyManager زوجَ مفاتيح جديداً (connect/transfer_key.go:113). واللقطةُ المحفوظة للنافذة تخزّن معرّف العميل ورمز jwt ومعرّف النسخة فقط — بلا بذرة مفتاح (sdk/window_identity_store.go:42-45)، فالهويةُ المستعادة تنشر مفتاحاً جديداً. ومع إيقاف تشفير ما بعد الكم، لا يُقدَّم أي إثبات هوية إطلاقاً (connect/ip_remote_multi_client.go:13080-13091).

وعنوانُ مصدر النفق لكل جلسة ومنخفض العشوائية. فـSDK تُسند لواجهة TUN عنوان RFC1918 عشوائياً — «عنوان 10.x.y.h عشوائي (بحسب RFC1918، وبهيئة DHCP)» (sdk/device_local.go:7105) — يُولَّد بثُماني مضيف بين 2 و254 على أصغر 10.a.b.0/24 غير متعارض (connect/tun.go:517-523). ويُحسب في المُنشئ ولا يُحفظ أبداً، فهو يتغير مع كل جلسة. والمزوّدُ يراه فعلاً: فهو حقلُ المصدر في كل حزمة منفَّقة، والمزوّدُ يبني حالة NAT على مفتاحه (connect/ip.go:731,784). ونحو 253 قيمة بصمةٌ ضعيفة لكل جلسة، لا معرّفاً عبر الجلسات.

9.2 مفتاح هوية دور المزوّد دائم

إن كنت تشارك اتصالك أيضاً، فعميلُ المزوّد لديك يحمل زوج مفاتيح Ed25519 طويل العمر تُحفظ بذرته في التخزين المحلي (sdk/local_state.go:788، .device_local_key_material، وبصيغة JSON client_key_seed؛ ويُطبَّق عند sdk/device_local_key_material.go:32,90). وخلافاً لعملاء النافذة، يُحفَظ ذلك المفتاح عن قصد عبر مسح الخروج التلقائي من الحساب — وتعليقُ Android صريح: «امسح حالة مصادقة بائتة أو جزئية من دون تدوير هوية الجهاز. فمادةُ مفتاح الهوية مرتبطة بالجهاز، لا بالجلسة» (android/.../MainApplication.kt ‏(clearStaleAuthState)). فتجديدُ الرمز، والتعافي من المصادقة الجزئية، وإعادةُ تسجيل الدخول بعد 30 يوماً من الخمول، تنتج كلها client_id جديداً وdevice_id جديداً بينما يبقى مفتاح Ed25519 العام كما هو. ولا يدوّره إلا خروجُ المستخدم الصريح من الحساب (sdk/local_state.go:788).

وذلك المفتاح منشور، ومختوم في كل عقد يسمّي هذا العميل وجهةً (server/controller/connect_controller.go:744-770,816-830)، وقابل للقراءة من أي أحد: فـGET /key/ غير مصادَق عليه بحكم التصميم (server/api/api.go:222-223؛ server/controller/connect_controller.go:1227). فيستطيع أي طرف — لا مزوّدٌ خدمته أنت وحده — أن يحلّ أي معرّف عميل إلى مفتاحه العام وأن يجمّع معرّفات العملاء التي تتشارك مفتاحاً واحداً. وبالنسبة لحساب مزوّد، ذلك مقبضٌ دائم عبر الجلسات وعبر client_id وعبر device_id. وهذه كلفةٌ حقيقية للتزويد، وهي غير موثَّقة في أي موضع آخر من هذه المجموعة.

9.3 عند المشغّل، كل شيء يترابط

  • client_id لا يُبطَل أبداً. وتعليقُ الكود نفسه: «معرّفات العملاء عناوين فريدة عالمياً تعادل IPv6 / ولا تُبطَل أبداً بعد تخصيصها، حفاظاً على الأمان وعلى سجلات التدقيق» (server/model/network_client_model.go:74). والعميلُ الأعلى مستوى يُعطَّل بعد 30 يوماً من الخمول (TopLevelClientIdleExpiration، :2576) ويُحذف حذفاً قاطعاً بعد 30 يوماً من ذلك (NetworkClientReapAfterDeactivate، :2556)، متتالياً إلى صف الجهاز وإلى ckey في Redis ‏(:2537).
  • device_id هو لكل تسجيل دخول، لا لكل عتاد ولا لكل تثبيت: فيُسكّ واحد طازج مع كل عميل أعلى مستوى (:332-352)، والكودُ يشير إلى ما ينتج عن ذلك من «تبدّل هوية (device_id طازج لكل تسجيل دخول)» (:2280). وعملاءُ النافذة يرثونه (:356-380) ولا يُرسَل إلى مزوّد أبداً.
  • ورمزُ الشبكة رمزُ JWT مدته 24 ساعة (server/jwt/by_jwt.go:55-67,238)، تجدّده SDK عند منتصف عمره — نحو 12 ساعة — مع تذبذب (sdk/device_token_manager.go:172-217). وتجديدُ الرمز لا يدوّر معرّف العميل؛ فالمطالبة نفسها يُعاد سكّها (server/model/network_client_model.go:74).
  • وaudit_contract_event يسجّل هوية العميل والمزوّد لكل عقد (server/db_migrations.go:325-347). وclient_reliability يصل تجزئةَ كتلة العنوان بمفتاح مباشرةً بمعرّف عميل، لكل كتلة: فالمفتاحُ الأساسي الأصلي ذو الأعمدة الأربعة (server/db_migrations.go:325-347) اختُصر لاحقاً إلى (block_number, client_address_hash, client_id) ‏(:2191-2195)، والجدولُ المقسَّم الحي يستخدم الثلاثة نفسها (server/model/network_client_reliability_partition_model.go:194)، مع بقاء network_id حمولةَ فهرس (:63,631-634). والاحتفاظُ 30 يوماً (server/model/network_client_reliability_partition_model.go:52-69؛ الأقسامُ اليومية تُسقط كاملة).
  • وnetwork_client.auth_time آخرُ ظهور دائم يُحتفظ به 30 يوماً (server/model/network_client_model.go:2556,2576)؛ وصفوفُ الاتصالات المقطوعة تُقلَّم عند 8 ساعات (server/taskworker/work/network_client_work.go:85).
  • والمعرّفاتُ على مستوى الحساب، عند المشغّل وحده: بريدٌ إلكتروني أو هاتف صريحاً في network_user.user_auth، ورمزُ JWT الكامل لمزوّد الهوية الخارجي في تسجيلات الدخول بـSSO، ومعرّفاتٌ صريحة عند كل محاولة تسجيل دخول في user_auth_attempt، وعناوينُ محافظ، وصفوفُ دفع، وحافّةُ إحالة دائمة network_referral تقول من دعا من، وصفوفُ audit_provider_event تحمل نصوصاً حرفية للدولة والمنطقة والمدينة في مقابل network_id وdevice_id، غير مرتبطة بتقليم الاتصالات عند 8 ساعات (review/verified/PRIVACY-ENFORCEMENT.md §1.4).
  • وUser-Agent تسجّله قائمةُ السماح ذات الترويسات الخمس (server/http_log.go:13-29). وهو المدخل الوحيد بين الخمسة الذي يشكّل سطح بصمات.

9.4 جسر WireGuard: عنوانُ النفق يمرّ عبر NAT قبل الخروج

يُخصَّص لكل عميل WireGuard عنوانُ نفق خاص ثابت من مجمّع مخلوط من 10,000,000 عنوان RFC1918 ‏(server/model/network_client_proxy_model.go:774)، يُكتب في الإعداد بوصفه عنوان الواجهة (:756-771). وذلك العنوان دائم: فهو جزء من إعداد النِّد ويبقى عبر كل جلسة.

وهو لا يصل إلى المزوّدين. فالجهازُ المستضاف يستبدل به عنواناً خاصاً بالجهاز عند الخروج، ويعيد عنوانَ النِّد نفسه عند العودة. ويُؤخذ البديلُ مرة واحدة عند بناء الجهاز من مجمّع 169.254.0.0/16 نفسه الذي تستخدمه واجهةُ tun في مسار التطبيق (server/proxy/proxy_device.go:954، عبر connect.TakeLocalIpv4Address، connect/tun.go:471) ويُعاد عند الإغلاق، فهو لكل جهاز لا لكل حساب، ومسحوبٌ من الفضاء نفسه الذي يُسحب منه كل عنوان نفق محلي آخر بدل أن يكون مميَّزاً. وإعادةُ الكتابة هي natRewriteEgress ‏(server/proxy/proxy_device.go:1297) وnatRewriteReturn ‏(:1314)، فوق connect.RewriteIpv4Source/RewriteIpv4Destination ‏(connect/ip_nat_rewrite.go:71,77)، وهما تُصلحان المجموعَ الاختباري لترويسة IPv4 وأيَّ مجموع اختباري للنقل قائم على الترويسة الزائفة إصلاحاً تزايدياً. وتُطابَق حزمُ العودة على العنوان البديل، إذ هو ما وجّهها المزوّد إليه (receiveWithNotifyNat، server/proxy/proxy_device.go:1339).

وثلاثةُ حدود جديرة بالذكر، لأن هذا يزيل رابطاً واحداً لا غيره. فلا تُعاد كتابةُ غير حركة النِّد الموصول نفسه، فالجهازُ الذي يخدم HTTP أو SOCKS أيضاً يترك تلك التدفقات بلا مساس. وإن نفد المجمّع المحلي عطّلت إعادةُ الكتابة نفسها بدل أن تُسقط الحركة، ما يعني أن السلوك القديم لا يزال هو نمطَ الإخفاق — وهو خيارُ توافر، وسببُ أن الخاصية «تصمد عادةً» لا «يستحيل أن تفشل». وكلُّ مزوّد في نافذة واحدة لا يزال يرى العنوانَ البديل نفسه طوال مدة ذلك الجهاز، تماماً كما يُتشارَك عنوانُ tun في مسار التطبيق بين مزوّدي جلسة واحدة (§9.1)؛ فالمُزال هو المقبضُ العابر للجلسات والدائم بدوام الحساب، لا الربطُ داخل الجلسة.

والسلوكُ مثبَّتٌ بـTestWgNatReplacesThePeerAddressOnEgress وTestWgNatRoundTripIsExact وTestWgNatLeavesOtherSourcesAlone وTestWgNatReturnIsMatchedOnTheNatAddress ‏(server/proxy/proxy_device_nat_test.go)، وحسابُ المجموع الاختباري مثبَّتٌ باثني عشر اختباراً في connect/ip_nat_rewrite_test.go تتحقق في مقابل إعادة حساب كاملة بدل أن تستنسخ الرياضيات التزايدية.

ومع المحلِّل الثابت 1.1.1.1 ‏(§3.2) وغيابِ الجلسة المختومة على ذلك المسار (§2.4)، يبقى جسرُ WireGuard أقلَّ الطرق خصوصيةً لاستخدام الشبكة. والتطبيقاتُ وSDK هي الأكثر خصوصية.

10. المطالب القانونية

الاختصاص القضائي. الطرفُ المقابل هو BringYour, Inc.، وهي شركةٌ مساهمة من فئة C في ولاية ديلاوير (docs/legal/terms.md:8-9 يسمّي الكيان؛ أما التأسيس فلا يُذكر هناك)، بعنوان إشعارات في 2261 Market Street #5245, San Francisco, CA 94114 ‏(docs/legal/terms.md:265-271). والقانونُ الحاكم قانونُ تكساس، ومقرُّ التقاضي مقاطعة هاريس بولاية تكساس (docs/legal/terms.md:314-321) — وهو اختيارٌ مقصود، لا تناقضٌ مع العنوان في كاليفورنيا: فالمستشار القانوني للشركة في تكساس. والثغرةُ الوحيدة التي يصطدم بها القارئ أن الشروط المنشورة لا تذكر ولايةَ التأسيس ولا وكيلاً مسجَّلاً، فلا يمكن إثبات الوقائع المؤسسية اللازمة لتبليغ الإجراءات القضائية منها وحدها. وكلُّ ذلك أمريكي، وكلُّه في متناول الإجراءات الإلزامية الأمريكية، بما فيها إجراءٌ يصل مع أمر بمنع الإفصاح. وتنص الشروط على أن المشغّل «قد يراقب المعلومات ويفصح عنها حيث يُلزَم بذلك بحكم القانون أو بأمر حكومي» (docs/legal/terms.md:204-206).

ماذا يبلغ طلبٌ على المشغّل. بترتيب تنازلي في قوة كشف الهوية: من هو الحساب — بريدٌ إلكتروني أو هاتف صريحاً، ورمزُ JWT لمزوّد الهوية الخارجي في تسجيلات الدخول بـSSO، وعناوينُ المحافظ، وسجلاتُ الدفع التي ترتبط بهوية حقيقية عبر Stripe أو Apple أو Google Play أو tx_signature على السلسلة؛ ومتى اتصل ومن أي مدينة، لكل اتصال؛ وأي المزوّدين استخدم وكم بايت؛ وتجزئةٌ بمفتاح لكتلة /29 أو /56 التي اتصل منها، مع منفذ المصدر صريحاً. والأطرافُ الثالثة تحمل أكثر: فمعالِجات الدفع المسمّاة في §1 تحمل هويةً لا يخزّنها المشغّل أبداً، وtx_signature في Solana يُحلّ إلى محفظة على سلسلة عامة.

ماذا لا يبلغ الطلب، لأنه غير موجود. لا يحتوي سجلُّ النقل أي حقل وجهة ولا مضيف ولا URL ولا SNI ولا منفذ ولا نطاق في أي موضع (review/verified/PRIVACY-ENFORCEMENT.md §1.2). وسجلاتُ الدعم المرفوعة تُتلَف عند الاستلام. ولا يوجد سجلُّ تصفح يُقدَّم، على أي مسار، مختوماً كان أو غير مختوم — وهذه أقوى خاصية بنيوية في هذه الوثيقة، وهي قائمة سواء أحسن أحدٌ التصرف أم لم يحسن.

ماذا يبلغ الطلب استشرافاً. كلُّ ما سبق عن سجلات مخزَّنة. أما الطلبُ الذي يُكرِه على سلوك مستقبلي فأمرٌ آخر، و§7.4 هو موضع عضّه: فالمشغّلُ المُكرَه على اعتراض مستخدم بعينه يستطيع اليوم أن يستبدل مادة مفاتيح مزوّد أو يغفلها ويقرأ حركة ذلك المستخدم حتى مع تفعيل تشفير ما بعد الكم، ولا تكون الإشارةُ الوحيدة إلا سطر سجل على الجهاز. فالجلسةُ المختومة ترفع كلفة الإفصاح بأثر رجعي. وهي لا تقاوم، في صورتها الحالية، مشغّلاً مُكرَهاً من الآن فصاعداً.

حذف الحساب جزئي. فـRemoveNetwork قائمةُ حذف مكتوبة يدوياً، لا تتالياً في قاعدة البيانات (server/model/account_model.go:108). وهي تزيل صفوف network_user وسجلاتِ مصادقتها (كلمة المرور، وSSO، والمحفظة، وعبارة الاستعادة)، وصفَّ network ومدخلَ فهرس الاسم، وتجدول إلغاء اشتراك Stripe. وهي لا تحذف account_payment ولا stripe_customer ولا apple_subscription_transaction ولا صفوف solana_payment_intent المكتملة ولا network_referral ولا صفوف الأجهزة ولا صفوف التدقيق؛ فتلك تشيخ على جداولها الخاصة — أحداثُ التدقيق عند 180 يوماً (server/model/audit_model.go:1018,1052) — أو، للمدفوعات المكتملة على السلسلة، لا تشيخ إطلاقاً (server/model/solana_payment_intent_model.go:96,137 لا يحذف إلا النوايا التي tx_signature فيها معدوم). والحذفُ يكتب صفاً أيضاً: حدثَ AuditEventTypeNetworkDeleted مفتاحُه network_id المحذوف (account_model.go:108). وعبارةُ سياسة الخصوصية «حذف حسابهم والمعلومات الشخصية المرتبطة به» (docs/legal/privacy.md:69) أوسع مما يفعله الكود.

لا توجد شمعة إنذار، ولا تقرير شفافية، ولا إجراء منشور لإنفاذ القانون. وقد تحقّق غيابُ ذلك عبر docs/ وعبر مصدر موقع ur.io. ولا يوجد أي عنوان للإجراءات القانونية إطلاقاً: فـ[email protected] نطاقُه بلاغات الثغرات (docs/legal/vdp.md:42)، و[email protected] هو عنوان الإشعارات التعاقدية العام (docs/legal/terms.md:265)، ولا واحد منهما قناةٌ لتبليغ الإجراءات القضائية. والطرفُ الذي يريد تبليغ الشركة لا يجد في الشروط المنشورة ما يتصرف بناءً عليه سوى عنوان الإشعارات في سان فرانسيسكو. ووثائقُ مقارنة URnetwork نفسها تقرّ أصلاً بغياب الشمعة وتقرير الشفافية أمام منافسين ينشرون واحداً منهما، وهذه الوثيقة تكرّر ذلك بدل تليينه.

سياسة الخصوصية صامتة حيث ينبغي أن تكون محدَّدة. فهي تنص على أن «كل المعلومات الشخصية التي نجمعها من المستخدمين وعنهم محصورة في عنوان البريد الإلكتروني أو رقم الهاتف» وأن الجمع يحدث «منك مباشرةً حين تقدّمها» (docs/legal/privacy.md:29-37).

مسودةٌ سابقة من هذا القسم سمّت ذلك وصفاً ناقصاً بحجة أن الكود يخزّن فئات أكثر مما تسمّيه السياسة. وقد سُحب ذلك التأطير في 2026-08-09 بوصفه خاطئاً. فكلُّ ما تجرده هذه الوثيقة عدا ذلك إما بياناتُ حساب ينشئها المستخدم بالتسجيل أو بالدفع (فمفتاحُ الوصل مع Stripe موجود لأن أحداً وافق على أن يُفوتَر)، وإما ليس معلومات شخصية أصلاً: منفذُ مصدر، وتجزئةُ كتلة بمفتاح لا يعكسها المشغّل. أما هل يمكن من حيث المبدأ أن يعكس تجزئةً بمفتاح حائزُها نفسُه فذلك تحفّظ هندسي حقيقي — §5 يذكره، والتدويرُ الغائب ضعفٌ حقيقي — لكن قيمةً لا يبحث عنها أحد ليست فئةً من المعلومات الشخصية قصّرت السياسةُ في إعلانها.

أما ما ينقص السياسةَ فعلاً فمختلفٌ وأضيق: لا مدة احتفاظ، ولا بيان تسجيل، ولا قسم عن إنفاذ القانون أو الإفصاح الحكومي. فالكلماتُ الاحتفاظ والسجل وعنوان IP وأمر الإحضار والإجراء القانوني لا تظهر فيها. وذلك مهم لأن انضباط الاحتفاظ موجودٌ في الكود وأقوى مما تدّعيه السياسة — و§5.1 يعرض النوافذ المتحقَّق منها. فقارئُ السياسة اليوم لا يستطيع أن يُلزم المشغّل بأيٍّ منها.

11. ما لا تدافع URnetwork ضده

ثلاثةُ أمور قائمة، ويستحق ذكرها قبل القائمة التالية، كي تُقرأ القائمةُ حداً لا حكماً. فعلى مسار مُرحَّل لا يعرف المزوّد أبداً من أنت، ولا يغيّر ذلك أي إعداد ولا أي إصدار مزوّد (§2). ولا يوجد حقل وجهة ولا مضيف ولا URL ولا SNI ولا منفذ ولا نطاق في أي موضع من سجل النقل، فلا يوجد سجلُّ تصفح يُكرَه على تقديمه أو يُسرَّب أو يُباع (§5 و§10). وكلُّ ادّعاء من هذه الادعاءات قابل للقراءة في مصدر عام، ولهذا تستطيع هذه الوثيقة أن تكون محددة في إخفاقاتها هي.

وكلُّ ما دون ذلك إخفاق. مذكورٌ بلا مواربة؛ وكلُّ بند مشروح أعلاه.

  1. الربط بالتوقيت وبتحليل الحركة من طرف إلى طرف. لا حشو، ولا حركة تمويه، ولا خلط. والخصمُ الذي يراقب شبكة وصولك والمزوّدين الذين يحملون حركتك يستطيع ربطهما. §8.1.
  2. خصمٌ سلبي عالمي. خارج النطاق كلياً. §8.3.
  3. التواطؤ بين المشغّل والمزوّد. المشغّل يعرف من أنت، والمزوّد يعرف إلى أين ذهبت، وسجلُّ العقد يصلهما. والختمُ يضيّق ما يحمله المشغّل وحده؛ لكنه لا يكسر الوصل. §6.2.
  4. مشغّلٌ لم يكن أميناً قط، يستبدل مادة مفاتيح مزوّد. فالاستبدالُ صار يكلّف أثراً موقَّعاً ودائماً يستطيع طرفٌ ثالث كشفه، والمشغّلُ الذي ينقلب خبيثاً أو يكون غير متسق يُمسَك. أما المشغّلُ الخبيث والمتسق مع نفسه منذ أول اتصال لك فلا يزال يفوز، لأن العميل لا يملك رؤيةً مستقلة عن المشغّل لأيّ الموقّعين صاحبُ السلطة. §7.4.
  5. لا ختم إطلاقاً مع إيقاف تشفير ما بعد الكم، وهو الافتراضيُّ المشحون. ففي ذلك الوضع لا يبدأ العميل جلسةً أبداً، فتكون حركته غير مختومة منذ البداية، بلا أي إشارة يراها المستخدم. وأُصلح للوضع الذي يطلبه، في 2026-08-10: فمع تفعيل المفتاح يعمل العميل بالفشل المغلق ويرفض إرسال بيانات التطبيقات نصاً صريحاً أو قبولها. والذي يبقى في الوضعين معاً هو المؤشر الغائب — فلا تطبيق يبيّن أمختومٌ اتصالٌ بعينه أم لا. §2.2.
  6. مزوّدٌ خبيث يعبث بالحركة غير المشفّرة. فـHTTP نصاً صريحاً يُمرَّر من دون تغيير؛ والمزوّد في موضع نقطة اتصال معادية. §3.
  7. اختراق الطرف. البرمجيات الخبيثة، أو نظامُ تشغيل مخترَق، أو امتدادُ متصفح معادٍ، أو أيُّ شخص بجهازك غير المقفل. ولا شيء في منتج شبكي يعالج هذا.
  8. التعرّف من جانب الوجهة. فملفات تعريف الارتباط، وتسجيلات الدخول، وبصمُ المتصفح، وسلوكُ الحساب، تعرّفك للمواقع التي تزورها بغضّ النظر عن كيفية وصول الحزم. وتغييرُ عنوان خروجك لا يجعلك مجهولاً لخدمة تسجّل الدخول إليها.
  9. هوية قناة الدفع. فالبطاقاتُ وقنواتُ متاجر التطبيقات تضع هويتك لدى المعالِج وإن كان المشغّل لا يخزّنها. وUSDC على السلسلة تترك tx_signature عاماً.
  10. مزوّدو Sybil. يستطيع أي أحد أن يزوّد، بلا تصديق ولا رهن. ويستطيع طرفٌ واحد تشغيل مزوّدين كثر، ومنهم المشغّل.
  11. غيابُ الختم على مسارَي المتصفح والبروكسي. فامتدادُ المتصفح بلا إعداد لتشفير ما بعد الكم، وعلى تلك المسارات يشغّل المشغّلُ العميل. §2.4.
  12. الربطُ داخل الجلسة على جسر WireGuard. فعنوانُ النفق الدائم للنِّد يمرّ عبر NAT قبل الخروج، فلم يعد يتبع حساباً عبر الجلسات — لكن كل مزوّد في نافذة واحدة لا يزال يرى العنوانَ البديل نفسه، تماماً كما يرون عنوانَ tun واحداً في مسار التطبيق. وإن نفد مجمّعُ العناوين المحلي عطّلت إعادةُ الكتابة نفسها بدل أن تُسقط الحركة، فيبقى سلوكُ ما قبل NAT هو نمطَ الإخفاق. §9.4.
  13. مقبضُ هوية دائم لكل من يزوّد أيضاً. فمفتاحُ Ed25519 لعميل المزوّد يبقى بعد تدوير معرّف العميل ومعرّف الجهاز بحكم التصميم، وGET /key/ غير مصادَق عليه، فيستطيع أي طرف تجميع معرّفات العملاء التي تتشارك مفتاحاً. §9.2.
  14. الحركةُ التي تراها الشبكة المحلية أثناء نافذة DNS عند البدء. موثَّقة في المصدر بوصفها كلفةً مقبولة. §3.2.
  15. أيُّ شيء كان تدقيقٌ من طرف ثالث سيجده. فلم يُجرَ أي تدقيق على البروتوكول ولا على كود الخادم.

12. ما تعذّر على هذه الوثيقة إثباته

الادّعاءُ غير المثبَت في نموذج تهديد ادّعاءٌ لا ينبغي أن يكون في الوثائق كذلك. وهذه مفتوحة.

  • احتفاظُ journald وإعادةُ توجيهه على المضيفات. فسجلاتُ الخدمات صارت تُنقّى عند الواصف قبل أن تغادر العملية (§5)، فما يبلغ journald ينبغي ألا يحمل عناوين خارج تفريغات الانهيار. أما ما لا تستطيع مراجعةُ المصدر هذه إثباته فكم يحتفظ journald بما يتلقاه، وإلى أين يعيد توجيهه، وهل يُحتفظ بأي تفريغ انهيار — وهو يمرّ بلا تنقية بحكم التصميم — مدةً أطول من البقية. وذلك سؤالٌ يخص النشر، لا الكود.
  • هل يمكن أن يحدث حلُّ الأسماء على مسار المتصفح محلياً أبداً. فالامتدادُ يضبط بروكسي HTTPS CONNECT افتراضياً، وهو يحلّ عن بُعد، وحلُّ أسماء SOCKS يمر عبر طالب DoH في النفق (connect/tun.go:517-523). ولم يُختبر سلوكُ DNS للبروكسي في كل متصفح تحت كل إعداد.
  • الإسهابُ الفعلي في الإنتاج. BY_LOG_V قيمته الافتراضية 0 (server/env.go:46)، وهذا يكبت تسجيل الوجهات المقيَّد بـV(1) على العميل والخادم — لكن بيان النشر الوحيد في الشجرة يضبطه على 2 (xops/gitops-unused/.../api/deployment.yaml:47-48)، في دليل اسمه gitops-unused. فعامِل الإسهاب بوصفه علماً في وقت التشغيل، لا ضماناً بنيوياً أبداً.
  • استقلالُ الأسطول. ينشر المشغّل أعداد المدن والدول الحية من تجميعه هو على المزوّدين المتصلين حالياً والصالحين (server/model/network_client_location_model.go:3525)، والموقعُ يشتقّه المشغّل من الاتصال الذي يلاحظه بدل أن يعلنه المزوّد (review/verified/ARCHITECTURE.md §6.4). لكن لم يقس أي طرف مستقل الأسطول من الخارج، ولا شيء يتحقق من أن المزوّدين المعروضين على عميل بعينه مستقلون بعضهم عن بعض أو عن المشغّل. فالآليةُ قابلة للفحص في المصدر؛ أما الجمهور فليس قابلاً للفحص من طرف ثالث اليوم.
  • ربطُ مفتاح الهوية عند إنشاء الحساب. ينص التصميم على توقّع أن «يُسجَّل ربطُ (ClientId، المفتاح العام) عند إنشاء الحساب» (connect/transfer_key.go:60-85). أما المشحون فعميلٌ ينشر مفتاحه إلى Redis يتحكم فيه المشغّل، ويُخدَم مرة أخرى من واجهة API يتحكم فيها المشغّل. وهل يوجد تسجيلٌ أقوى تشغيلياً لم يمكن إثباته من المصدر؛ فافترض أنه لا يوجد.
  • هل يُملأ Roles وPrincipal أبداً لعملاء المستهلكين العاديين. فهما نصّان يُسنِدهما المشغّل ويُختمان في بايتات العقد الموقَّعة ولا يُضبطان إلا لـProvideMode_Network ‏(connect/protocol/transfer.proto:547-561؛ server/controller/connect_controller.go:831-838). ولم يُعثر على دليل على أن التطبيقات تملؤهما، ولم يُدقَّق كلُّ مستدعٍ. ولو مُلئا يوماً لحركة المستهلكين لصارا معرّفَين عبر الجلسات من الدرجة الأولى مرئيَّين للمزوّد.
  • بقاءُ مادة المفاتيح على المضيفات غير المحمولة. لا يُشار إلى ClientKeySeed إلا من sdk/local_state.go وsdk/device_local_key_material.go وsdk/device_local.go وsdk/cgo/. والمضيفاتُ التي لا تستدعي تلك تحصل على مفتاح طازج لكل عملية؛ ولم يُتحقق من سلوك Linux وWindows والامتداد وبروكسي الخادم كلٍّ على حدة.

13. التبليغ

الثغرات: [email protected] وسياسةُ الإفصاح على ur.io/vdp. والتصحيحاتُ على هذه الوثيقة، ومنها الاعتراض على أي حكم فيها، مرحَّبٌ بها بالطريق نفسه — فنتيجةٌ مستقلة تناقض ادّعاءً هنا أنفعُ من الادّعاء.