نحن على Product Hunt اليوم! اترك تعليقًا

أصبح Authenticator 1.16.0 متاحًا. اطّلع على الجديد

كيف يعمل

نظرة تقنية موجّهة لمن يراجع الأمان. كل ما هنا يمكن التحقق منه في الشيفرة المصدرية — بما في ذلك الجوانب التي ليست في صالحنا، ونذكرها بدل أن نغفلها.

البنية

الإضافة هي إضافة كروم بمواصفة Manifest V3 تولّد رموز TOTP محليًا باستخدام OTPAuth. توليد الرموز وتخزين الحسابات وتشفيرها لا يمرّ بأي خادم خلفي، ولا توجد حسابات مستخدمين. نشغّل خادمًا واحدًا، api.authenticator.sh، من أجل الخطة المجانية وPro — وهو موصوف أدناه — ولا يتلقى أبدًا مفتاحًا سريًا ولا اسم حساب ولا اسم خدمة ولا رمزًا.

ولا يحتفظ ذلك الخادم بأي مادة مفاتيح لحسابات أي أحد: فلا إيداع للمفاتيح، ولا مسار استرداد نتحكم به. إذا نسي المستخدم كلمة المرور وفقد رمز الاسترداد، فلا حيلة لنا — وهذا مقصود بالتصميم.

التشفير

الحماية بكلمة مرور اختيارية ومعطّلة افتراضيًا. وعند تفعيلها:

الخوارزمية
AES-256-GCM، مع متجه تهيئة عشوائي بطول 96 بت لكل سجل ولكل إعادة كتابة
اشتقاق المفتاح
PBKDF2-HMAC-SHA256، 600٬000 تكرار، ملح عشوائي بطول 128 بت (وفق توجيهات OWASP)
المفتاح الرئيسي
256 بت، من crypto.getRandomValues، يُولَّد مرة واحدة؛ وكلمة المرور تغلّفه فحسب
البصمات
HMAC-SHA256 بمفتاح فرعي مشتق عبر HKDF، ويُستخدم لدمج السجلات بين الأجهزة
تخزين المفتاح بعد فتح القفل
chrome.storage.session — في الذاكرة فقط، لا يُكتب إلى القرص أبدًا، ويُمحى عند إغلاق المتصفح، ولا يمكن الوصول إليه من سكربتات المحتوى
القفل التلقائي
عند كل فتح / بعد 5 أو 15 أو 60 دقيقة من الخمول / حتى إغلاق المتصفح
التنفيذ
WebCrypto حصرًا. لم تُكتب أي بدائية تشفير يدويًا، ولا تُحزَّم أي مكتبة تشفير مع الإضافة.

لماذا مستويان من المفاتيح

لا تُشفَّر البيانات إطلاقًا بمفتاح مشتق من كلمة المرور مباشرة. يشفّر السجلات مفتاح رئيسي عشوائي؛ أما ناتج PBKDF2 فيغلّف ذلك المفتاح الرئيسي فقط. ويترتب على ذلك أمران، وكلاهما مهم:

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

ما الذي يُشفَّر وما الذي لا يُشفَّر

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

نقولها صراحةً لأنها تهمّ تقييمكم:

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

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

يحمي من

  • سرقة مجلد ملف تعريف المتصفح أو نسخه
  • برمجيات سرقة المعلومات التي تسحب بيانات المتصفح جملةً واحدة
  • من يصل فعليًا إلى جهاز تُرك دون مراقبة
  • وصول سجلات الحسابات إلى Google عبر مزامنة كروم
  • ضياع ملف نسخة احتياطية أو سرقته (تصدير محمي بكلمة مرور)

لا يحمي من

  • برمجية خبيثة تعمل بصلاحيات المستخدم بينما الخزنة مفتوحة
  • راصد لوحة مفاتيح يلتقط كلمة المرور أثناء كتابتها
  • متصفح أو نظام تشغيل جرى اختراقه
  • اختراق الخدمات الخارجية نفسها التي تخصّها الرموز
  • تصدير غير مشفّر اخترت كتابته — ملفات JSON العادية أو روابط otpauth:// أو CXF يقرؤها كل من يفتح الملف

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

الأذونات

يعلن الملف التعريفي خمسة أذونات ولا يعلن أي إذن مضيف:

storage
الحسابات والإعدادات في التخزين المحلي، والمفتاح المفتوح في تخزين الجلسة، ونسخة المزامنة الاختيارية
activeTab
يقرأ اسم مضيف التبويب النشط لإبراز الحساب المطابق، ويلتقط التبويب حين يمسح المستخدم رمز QR من الشاشة
contextMenus
يضيف عنصر «إدراج رمز 2FA» إلى قائمة النقر بالزر الأيمن، وعلى الحقول القابلة للتحرير في صفحات ‎http(s)‎ فقط
scripting
يحقن الدالة الوحيدة التي تضع الرمز في الحقل — لعلامة تبويب واحدة واستدعاء واحد، وبعد اختيار عنصر القائمة ذاك فقط
sidePanel
يفتح لوحة كروم الجانبية عندما تختار فتح التطبيق هناك؛ لا يمنح ذلك أي وصول إلى الصفحات

لا توجد لا توجد أي سكربتات محتوى، ولا أذونات tabs أو cookies أو webRequest، ولا web-accessible resources. لا شيء يخصنا موجود في الصفحة حتى تطلب رمزًا فيها؛ وما يعمل حينها يُحقَن لتلك العلامة وحدها ولذلك الاستدعاء وحده، بموجب الإذن الذي تمنحه نقرتك أنت، ولا يترك خلفه شيئًا.

الكاميرا

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

الخطة المجانية وPro والاتصال اليومي

منذ الإصدار 1.14.0 تتصل الإضافة بـ api.authenticator.sh مرة يوميًا — عند بدء تشغيل المتصفح، أو بعد اكتمال عرض النافذة المنبثقة، ولا يعترض ذلك أبدًا طريق عرض رمز. الطلب قائمة مغلقة من الحقول، تُبنى في دالة واحدة داخل src/billing/sync.ts، ويُلزمها اختبار بتلك القائمة:

iid
معرّف عشوائي أنشأه هذا التثبيت. غير مرتبط بحساب Google ولا بعنوان بريد إلكتروني ولا بالمتصفح — ما لم يشترِ المستخدم Pro: عندها يربط سجلّ الشراء التثبيتَ الذي اشترى منه، وأيَّ تثبيت يكون مفتاح الترخيص مفعَّلًا عليه، بعنوان البريد الإلكتروني الذي دفع به
ver, ui_lang, browser_lang
إصدار الإضافة، ولغة الواجهة، ولغة المتصفح
installed_at, legacy, legacy_src
متى ظهر التثبيت، وهل يسبق بدء الفوترة
accounts, groups
عدد الحسابات فيه وعدد المجموعات التي تتوزّع عليها — أرقام فقط، ولا أيّ الحسابات هي ولا أسماء المجموعات أبدًا
vault, sync, open_mode, view_mode, style, dark, os
هل الحماية بكلمة مرور والمزامنة مفعّلتان، وكيف تُفتح الإضافة، وطريقة عرض قائمة الحسابات (عادية أو مضغوطة أو برموز مخفية)، وأي نمط تستخدمه الإضافة، فاتحًا أو داكنًا، ونظام التشغيل (Windows أو macOS أو Linux أو ChromeOS)
pid, lid, has_key, key_hint
أيّ سياسة موقّعة وأيّ ترخيص يحملهما التثبيت حاليًا، وهل لديه مفتاح الترخيص بالفعل، وآخر أربعة أحرف من ذلك المفتاح
opens
كم مرة فُتحت الإضافة في كل يوم منذ آخر اتصال (آخر 14 يومًا على الأكثر) — عددٌ لكل يوم، ولا شيء عمّا شوهد فيها
events
أحداث فوترة تنتظر الإرسال، من قائمة مغلقة: بلوغ الحدّ، أو عرض شاشة Pro أو إغلاقها، أو اختيار خطة، أو محاولة إتمام دفع أو إدخال مفتاح

لا يُرسَل أبدًا: المفاتيح السرية، وأسماء الحسابات، وأسماء الخدمات، والرموز، والمواقع التي تُستخدم عليها. يستخلص الخادم البلد من الطلب عند طرف شبكته (edge) ولا يخزّن عنوان IP. وتتلقى صفحة الإزالة معرّف التثبيت نفسه في المعامل ?i=: فتُبلغ الخادم بأن التثبيت قد أُزيل، وتربط به إجابة الاستبيان الاختيارية.

ما يُحفظ لهذا الغرض في chrome.storage.local: معرّف التثبيت، وآخر سياسة وآخر ترخيص موقّعين، ومفتاح الترخيص الذي يحمله هذا التثبيت (سواء كتبه المستخدم أو وصله بعد الشراء منه)، وعدد مرات الفتح في كل يوم إلى أن يتسلّمه الخادم، والأحداث المنتظرة — ولا شيء من بيانات الحسابات. وحين تكون المزامنة مفعّلة، يذهب أيضًا إلى Chrome Sync ما يبيّن إن كان التثبيت يسبق الفوترة، ومفتاح الترخيص ذاك، لكي تلتقطهما ملفات تعريف Chrome الأخرى لدى المستخدم فيُفعَّل Pro فيها تلقائيًا؛ وإيقاف المزامنة يزيلهما من Chrome Sync، وإعادة تشغيلها تعيدهما.

ما يعود من الخادم، وكيف تتعامل الإضافة معه:

الرموز المميّزة
السياسة (حدّ الحسابات في الخطة المجانية) والترخيص (Pro) كلاهما JWS موقّع بـ ES256 ‏(ECDSA P-256)
التحقق
عبر WebCrypto، مقابل المفاتيح العامة المضمّنة في البناء (src/billing/keys.ts). نسخة Chrome Web Store لا تثق إلا بمفتاح الإنتاج، وفحص البناء يرفض أي نسخة تحمل مفتاحًا غيره
دون اتصال
يُفحص الحدّ والترخيص محليًا، من آخر رموز مميّزة موقّعة. وإلى أن تُستلم سياسة موقّعة لا يُقيَّد شيء — فالخادم الذي يتعذّر الوصول إليه لا يستطيع أبدًا أن يُغلق الباب في وجه أحد
ما يستطيعه الحدّ
منع إضافة حساب، ولا شيء غير ذلك. لا يستطيع إخفاء حساب أو قفله أو حذفه أو تعديله، ولا حجب الرموز أو التصدير أو النسخ الاحتياطية أو الاستعادة أو المزامنة
المشتريات
Stripe Checkout في تبويب جديد، وLink هي البائع الرسمي (merchant of record). يصلنا البريد الإلكتروني للمشتري من Stripe، لإصدار مفتاح الترخيص واسترداده؛ وتُخزَّن المفاتيح على هيئة تجزئة (hash)، مع نسخة مشفّرة تتيح إرسال المفتاح المفقود من جديد
مفتاح الترخيص
يحمل ردّ الاتصال اليومي المفتاح نفسه إلى التثبيت الذي اشترى Pro فقط، وإلى أن تطابق الأحرف الأربعة التي يُبلغ بها في key_hint ذلك المفتاح — لذا يحلّ مفتاح اشتُري لاحقًا محلّ مفتاح أقدم؛ أما التثبيت الذي كُتب فيه المفتاح يدويًا فلا يُرسَل إليه أبدًا

الحدّ ليس DRM: الشيفرة مفتوحة المصدر، ويمكن لأي نسخة متفرّعة (fork) أن تزيله. أما ما يجب ألا يكون ممكنًا أبدًا فهو أن يحصل شخص على ترخيص شخص آخر أو يستخدمه، أو أن يجعل تثبيت غيره محدودًا — فتلك ثغرة، وتقع ضمن نطاق سياسة الأمان لدينا.

حركة الشبكة الصادرة

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

المضيفمتىهل تحمل بيانات المستخدم؟
time.akamai.com
timeapi.io
cloudflare.com
فحص انحراف الساعة، مع تخزين مؤقت؛ وإذا تعذّر الوصول إلى أي مصدر تذكر الإعدادات ذلك. انحراف الساعة يعطّل TOTP.لا
authenticator.shصفحة الترحيب عند التثبيت؛ وصفحة الملاحظات عند الإزالة، وفي عنوانها معرّف التثبيت؛ وصفحتا المساعدة والدعم عند فتحهما من النافذة المنبثقة.معرّف التثبيت، في صفحة الإزالة فقط؛ وفيما عدا ذلك لا (سجلات خادم ويب اعتيادية)
api.authenticator.shالاتصال الخاص بالفوترة مرة يوميًا؛ واستدعاءات الدفع والترخيص والبوابة حين يشتري المستخدم Pro، أو يُدخل مفتاحًا، أو يستعيد عملية شراء، أو يدير اشتراكًا.الحقول المذكورة أعلاه — ولا بيانات حسابات أبدًا. ولا يُرسَل مفتاح ترخيص أو عنوان بريد إلكتروني إلا حين يكتبه المستخدم
checkout.stripe.com
billing.stripe.com
Stripe Checkout وبوابة العملاء، تُفتحان في تبويب حين يشتري المستخدم Pro أو يدير اشتراكًا.ما يُدخله المشتري هناك. وتسري شروط Stripe وLink الخاصة بهما
chromewebstore.google.comصفحة المتجر، تُفتح في تبويب عند النقر فقط: نموذج المراجعة من طلب التقييم.لا (ترى Google زيارةً لصفحة متجر كأي زيارة أخرى)

لا تُحمَّل أي خطوط أو سكربتات أو أنماط أو صور من مضيفات خارجية. وكل ما يلزم لعرض الواجهة مضمّن داخل الحزمة.

التحقق مما ننشره

لستم مضطرين لتصديق أي مما سبق. الإضافة مفتوحة المصدر، وكل إصدار قابل لإعادة الإنتاج:

  1. نزّلوا ملف .crx المنشور من متجر كروم الإلكتروني وفكّوا ضغطه
  2. انتقلوا إلى وسم git المطابق
  3. شغّلوا npm ci && npm run build على Node 20 LTS
  4. قارنوا مجلد dist/ الناتج بالحزمة المفكوكة

يُنشر مع كل إصدار على GitHub تجزئة SHA-256 لكل ملف داخل dist/ المُنتَج، باسم SHA256SUMS-v<version>.txt وبصيغة sha256sum، فتصبح الخطوة الرابعة تشغيلًا واحدًا للأمر sha256sum -c. ويُفترض أن تقتصر الفروق على ترتيب الملفات داخل الأرشيفات وعلى المسافات في المخرجات المصغّرة بين إصدارات Node التصحيحية.

اعتماديات وقت التشغيل قليلة عن قصد — OTPAuth وReact وjsQR وZustand وFramer Motion وLucide — ومثبّتة عبر ملف قفل محفوظ في المستودع. ونوفّر قائمة مكوّنات SBOM بصيغة CycloneDX عند الطلب.

اطّلعوا على الشيفرة

ما لا نملكه

نحن مزوّد صغير ومستقل. إن كان أي مما يلي شرطًا لازمًا في عمليتكم، فمن الأفضل أن تعرفوه الآن لا بعد ثلاثة أسابيع من التقييم:

  • لا شهادة SOC 2 ولا ISO 27001 ولا ما يعادلهما
  • لم يُطلب حتى اليوم أي اختبار اختراق من جهة خارجية
  • لا برنامج مكافآت ثغرات مدفوع

وما نقدّمه بدلًا من ذلك: شفافية كاملة للشيفرة المصدرية، وبناء قابل للتحقق، وأقل قدر ممكن من الأذونات، وخادم لا تُرسَل إليه أسراركم ولا أسماء حساباتكم ولا رموزكم من الأساس.

التواصل معنا

بلاغات الثغرات وأسئلة الأمان: security@authenticator.sh. سياسة الإفصاح لدينا ونطاقها وأزمنة الاستجابة وشروط الملاذ الآمن موجودة في صفحة سياسة الأمان؛ أما ما يُخزَّن وما يغادر الجهاز فمبيَّن في سياسة الخصوصية.

يسعدنا الإجابة على استبيان تقييم مورّد مكتوب، أو مراجعة الشيفرة خطوة بخطوة مع فريق أمني.

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