Update: 2026-07-29 16:36:26
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
-- ============================================================
|
||||
-- 2026_07_29_v2_migration_schema_fixes.sql
|
||||
--
|
||||
-- فجوات بين schema_primary.sql والكود الحيّ، يجب سدّها *قبل* تشغيل
|
||||
-- scripts/migrate_v2_reencrypt.php وإلا فشل الإدخال أو فشل تسجيل الدخول.
|
||||
--
|
||||
-- كل تعليمة هنا نتيجة مقارنة فعلية بين:
|
||||
-- - CREATE TABLE في backend/schema_primary.sql
|
||||
-- - أعمدة INSERT/SELECT في auth/driver/register.php, auth/passenger/register.php,
|
||||
-- serviceapp/register.php, serviceapp/login.php, Admin/auth/*, auth/otp/*
|
||||
-- - scripts/backfill_blind_index.php (خريطة الفهارس المعتمدة)
|
||||
--
|
||||
-- التنفيذ على قاعدة v3 الجديدة (الفارغة) قبل الترحيل.
|
||||
-- ============================================================
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- 1) driver — الكود يُدخل name_bidx و phone_key وهما غير موجودين
|
||||
-- (auth/driver/register.php:444) → INSERT يفشل اليوم.
|
||||
-- national_bidx مطلوب من backfill_blind_index.php.
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
ALTER TABLE `driver`
|
||||
ADD COLUMN `name_bidx` VARCHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للاسم الكامل بعد التطبيع',
|
||||
ADD COLUMN `national_bidx` VARCHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للرقم الوطني',
|
||||
ADD COLUMN `phone_key` VARCHAR(80) NULL DEFAULT NULL COMMENT 'مفتاح ربط جداول التحقق — نفس صيغة otpPhoneKey()',
|
||||
ADD KEY `idx_driver_name_bidx` (`name_bidx`),
|
||||
ADD KEY `idx_driver_national_bidx` (`national_bidx`),
|
||||
ADD KEY `idx_driver_phone_key` (`phone_key`);
|
||||
|
||||
-- UNIQUE على national_number يصبح بلا معنى تحت GCM (نفس الرقم يُنتج نصاً
|
||||
-- مختلفاً كل مرة) — بل يمنع فقط تكرار *النص المشفّر* لا تكرار الرقم.
|
||||
-- البديل الصحيح: UNIQUE على الفهرس الأعمى.
|
||||
ALTER TABLE `driver` DROP INDEX `national_number`;
|
||||
ALTER TABLE `driver` ADD UNIQUE KEY `uniq_driver_national_bidx` (`national_bidx`);
|
||||
ALTER TABLE `driver` ADD UNIQUE KEY `uniq_driver_phone_bidx` (`phone_bidx`);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- 2) passengers — نفس المشكلة (auth/passenger/register.php:133)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
ALTER TABLE `passengers`
|
||||
ADD COLUMN `name_bidx` VARCHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للاسم الكامل بعد التطبيع',
|
||||
ADD COLUMN `phone_key` VARCHAR(80) NULL DEFAULT NULL COMMENT 'مفتاح ربط جداول التحقق — نفس صيغة otpPhoneKey()',
|
||||
ADD KEY `idx_passengers_name_bidx` (`name_bidx`),
|
||||
ADD KEY `idx_passengers_phone_key` (`phone_key`);
|
||||
|
||||
-- UNIQUE (phone, email) على نصّين مشفّرين عشوائياً لا يمنع تكرار الحساب.
|
||||
ALTER TABLE `passengers` DROP INDEX `phone`;
|
||||
ALTER TABLE `passengers` ADD UNIQUE KEY `uniq_passengers_phone_bidx` (`phone_bidx`);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- 3) users — نسخة schema_primary قديمة جداً مقارنة بالكود:
|
||||
-- ينقصها fingerprint / fingerprint_hash / status / country /
|
||||
-- email_bidx / phone_bidx (serviceapp/register.php:76,
|
||||
-- serviceapp/login.php:24, Admin/Staff/add.php:71).
|
||||
-- كما أن phone VARCHAR(15) لا يتّسع لنص مشفّر (≈44 حرفاً بـ CBC،
|
||||
-- ≈90 بـ GCM) — الإدخال سيُقطع أو يفشل.
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
ALTER TABLE `users`
|
||||
MODIFY COLUMN `phone` VARCHAR(255) NULL DEFAULT NULL,
|
||||
MODIFY COLUMN `email` VARCHAR(255) NULL DEFAULT NULL,
|
||||
MODIFY COLUMN `password` VARCHAR(255) NULL DEFAULT NULL,
|
||||
MODIFY COLUMN `first_name` VARCHAR(255) NULL DEFAULT NULL,
|
||||
MODIFY COLUMN `last_name` VARCHAR(255) NULL DEFAULT NULL,
|
||||
MODIFY COLUMN `birthdate` DATE NOT NULL DEFAULT '2002-01-01',
|
||||
MODIFY COLUMN `site` VARCHAR(255) NOT NULL DEFAULT 'Syria',
|
||||
MODIFY COLUMN `gender` VARCHAR(10) NOT NULL DEFAULT 'Female',
|
||||
ADD COLUMN `fingerprint` TEXT NULL DEFAULT NULL COMMENT 'بصمة الجهاز مشفرة',
|
||||
ADD COLUMN `fingerprint_hash` VARCHAR(64) NULL DEFAULT NULL COMMENT 'sha256(fingerprint) للبحث',
|
||||
ADD COLUMN `status` VARCHAR(20) NULL DEFAULT 'pending',
|
||||
ADD COLUMN `country` VARCHAR(100) NULL DEFAULT 'Jordan',
|
||||
ADD COLUMN `phone_bidx` VARCHAR(64) NULL DEFAULT NULL,
|
||||
ADD COLUMN `email_bidx` VARCHAR(64) NULL DEFAULT NULL,
|
||||
ADD KEY `idx_users_fp_hash` (`fingerprint_hash`),
|
||||
ADD KEY `idx_users_phone_bidx` (`phone_bidx`),
|
||||
ADD KEY `idx_users_email_bidx` (`email_bidx`);
|
||||
|
||||
-- UNIQUE على نصّ مشفّر عشوائي بلا فائدة (وسيرفض NULL المتكرر فقط).
|
||||
ALTER TABLE `users` DROP INDEX `email`;
|
||||
ALTER TABLE `users` DROP INDEX `phone`;
|
||||
ALTER TABLE `users` ADD UNIQUE KEY `uniq_users_phone_bidx` (`phone_bidx`);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- 4) token_verification_admin — أخطر فجوة: الكود يكتب اليوم
|
||||
-- phone_number = otpPhoneKey() ≈ 66 حرفاً، و token = GCM ≈ 90 حرفاً
|
||||
-- (auth/otp/request.php:131) بينما العمودان VARCHAR(20)/VARCHAR(10).
|
||||
-- النتيجة: قطع صامت للقيمة ⇒ لا يمكن التحقق من أي OTP للأدمن.
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
ALTER TABLE `token_verification_admin`
|
||||
MODIFY COLUMN `phone_number` VARCHAR(120) NOT NULL,
|
||||
MODIFY COLUMN `token` VARCHAR(255) NOT NULL,
|
||||
ADD COLUMN `verified` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN `created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
-- auth/otp/verify.php:88 يحدّث verified، وrequest.php يعتمد ON DUPLICATE KEY
|
||||
-- على phone_number، فالـ UNIQUE يبقى لازماً بعد توسيع العمود.
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- 5) جداول التحقق الأخرى — أعمدة المفتاح تتّسع للـ K:hmac (66 حرفاً)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
ALTER TABLE `phone_verification`
|
||||
MODIFY COLUMN `phone_number` VARCHAR(120) NOT NULL,
|
||||
MODIFY COLUMN `token_code` VARCHAR(255) NULL DEFAULT NULL,
|
||||
MODIFY COLUMN `email` VARCHAR(255) NOT NULL DEFAULT 'yet',
|
||||
ADD KEY `idx_pv_phone_number` (`phone_number`);
|
||||
|
||||
ALTER TABLE `phone_verification_passenger`
|
||||
MODIFY COLUMN `phone_number` VARCHAR(120) NULL DEFAULT NULL,
|
||||
ADD KEY `idx_pvp_phone_number` (`phone_number`);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- 6) adminUser — العمودان status/approved_* موجودان في
|
||||
-- 2026_07_25_blind_index.sql لكن schema_primary لا يحويهما.
|
||||
-- نفّذ هذا فقط إن لم تكن المايغريشن السابقة قد طُبّقت.
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- ALTER TABLE `adminUser`
|
||||
-- ADD COLUMN `status` VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
-- ADD COLUMN `approved_by` VARCHAR(32) NULL DEFAULT NULL,
|
||||
-- ADD COLUMN `approved_at` TIMESTAMP NULL DEFAULT NULL;
|
||||
@@ -0,0 +1,208 @@
|
||||
# ترحيل intaleqDBV2 → v3 (CBC القديم → GCM + الفهارس العمياء)
|
||||
|
||||
دراسة مبنيّة على مقارنة فعلية بين `intaleqDBV2.sql` و `backend/schema_primary.sql`
|
||||
والكود الحيّ في `backend/auth/**`, `backend/serviceapp/**`, `backend/Admin/**`.
|
||||
|
||||
---
|
||||
|
||||
## 1. نطاق الترحيل — الجداول التي فيها بيانات فعلاً
|
||||
|
||||
من 91 جدولاً في `intaleqDBV2.sql`، **12 فقط** تحوي `INSERT`:
|
||||
|
||||
| الجدول | عدد عبارات INSERT | يُرحَّل تلقائياً؟ |
|
||||
|---|---|---|
|
||||
| `passengers` | 42 | ✅ |
|
||||
| `driver` | 24 | ✅ |
|
||||
| `tokens` | 20 | ⏸ opt-in |
|
||||
| `phone_verification` | 13 | ⏸ opt-in |
|
||||
| `driverToken` | 12 | ⏸ opt-in |
|
||||
| `CarRegistration` | 9 | ✅ |
|
||||
| `phone_verification_passenger` | 7 | ⏸ opt-in |
|
||||
| `token_verification_driver` | 2 | ⏸ opt-in |
|
||||
| `users` | 1 | ✅ |
|
||||
| `token_verification` | 1 | ⏸ opt-in |
|
||||
| `token_verification_admin` | 1 | ⏸ opt-in |
|
||||
| `employee` | 1 | ✅ |
|
||||
|
||||
**لماذا opt-in؟** `tokens`/`driverToken` جلسات JWT، وجداول `*_verification` رموز
|
||||
OTP صلاحيتها 5 دقائق وكلها منتهية منذ شهور. ترحيلها بلا فائدة، وفيه خطر لأن صيغة
|
||||
المفتاح تغيّرت (انظر §4). الأسلم: إهمالها وإجبار إعادة تسجيل دخول واحدة.
|
||||
من أرادها: `--table=tokens`.
|
||||
|
||||
---
|
||||
|
||||
## 2. التشفير القديم — ما هو موجود فعلاً في v2
|
||||
|
||||
فحص العيّنات يثبت **AES-256-CBC حتمي بـ IV ثابت**: نفس النص يعطي نفس الـ ciphertext
|
||||
عبر كل الصفوف (مثلاً `1CBSKV8j4EKo8w0zjY4zcf8P4nbtMK+zhKcLtuN13no=` في `driver.gender`
|
||||
لكل السائقين، و `YJdUvEUnHWCdG/NAVZ3r/GyWXSYQr49nMGabOu0KAyc=` في ستة أعمدة من
|
||||
`passengers`). لو كان الـ IV عشوائياً لاختلفت كل قيمة.
|
||||
|
||||
لكن في الكود **صيغتان** كانتا تكتبان في نفس الأعمدة:
|
||||
|
||||
| المصدر | الصيغة |
|
||||
|---|---|
|
||||
| `core/Security/EncryptionHelper::encryptDataCBC` | `base64(cipher)` — IV ثابت، بلا بادئة |
|
||||
| `encrypt_decrypt.php::encryptData` (الجذر) | `base64(iv ‖ cipher)` — IV عشوائي مُلحق |
|
||||
|
||||
⚠️ **`EncryptionHelper::decryptData` الجديد لا يعرف الصيغة الثانية إطلاقاً** — يجرّب
|
||||
الـ IV الثابت فقط ويرجع `false`. أي ترحيل يعتمد عليه وحده سيُخرج صفوفاً فارغة بصمت.
|
||||
لذلك السكربت يحمل فكّاكاً خاصاً (`LegacyDecryptor`) يجرّب الصيغتين.
|
||||
|
||||
⚠️ **الصيغتان غير قابلتين للتمييز بشكل قاطع**: في CBC يؤثّر الـ IV على البلوك الأول
|
||||
فقط، فأي قيمة من بلوكين فأكثر تمرّ في المسارين بحشو صحيح. المصفاة الوحيدة هي
|
||||
صلاحية UTF-8. لذلك السكربت **لا يخمّن لكل قيمة**، بل يعتمد صيغة مفضّلة
|
||||
(`--legacy-format=fixed` افتراضاً، مبنيّة على الدليل أعلاه) ويعدّ الحالات الملتبسة
|
||||
ويعرضها في التقرير.
|
||||
|
||||
### نتيجة الفحص الفعلي على كامل `intaleqDBV2.sql`
|
||||
|
||||
شُغّل الفكّاك على كل قيمة مشفّرة في الجداول الخمسة المستهدفة بالمفتاح والـ IV
|
||||
الفعليين (32 و16 بايت):
|
||||
|
||||
```
|
||||
driver 1447 صفاً كل الأعمدة cbc_fixed
|
||||
passengers 2891 صفاً كل الأعمدة cbc_fixed
|
||||
users 1 صف cbc_fixed
|
||||
CarRegistration 1500 صفاً cbc_fixed
|
||||
─────────────────────────────────────────────
|
||||
cbc_fixed=50364 cbc_random=0 FAILED=0
|
||||
```
|
||||
|
||||
**الخلاصة: الصيغة `fixed` هي المستعملة حصراً — لا وجود لصيغة الـ IV العشوائي في
|
||||
البيانات.** الالتباس النظري المذكور أعلاه لم يقع عملياً (المسار العشوائي لم ينجح
|
||||
في أي قيمة)، فالافتراضي `--legacy-format=fixed` صحيح ولا حاجة لتغييره.
|
||||
|
||||
**شذوذات مكتشفة (كلها يعالجها السكربت):**
|
||||
|
||||
| الحالة | العدد | المعالجة |
|
||||
|---|---|---|
|
||||
| `driver.phone` مخزَّن نصّاً عادياً | 1 | يُشفَّر ويُفهرَس كغيره |
|
||||
| `CarRegistration.car_plate` نصّ عادي | 6 | تُشفَّر |
|
||||
| `CarRegistration.vin` = `sdf` / `unknown` | 237 | `unknown` يمرّ كما هو، `sdf` يُشفَّر |
|
||||
| `driver.site` = `demascus` نصّ عادي | 56 | يُشفَّر |
|
||||
| `driver.fullNameMaritial` = `yet`/NULL | 1447 | يمرّ كما هو |
|
||||
|
||||
القيمة المتكرّرة في ستة أعمدة من `passengers` فُكّت إلى **`unknown`** — أي أن الكود
|
||||
القديم كان يكتب قيمة موحّدة، وهو ما يفعله `$unknown_encrypted` في
|
||||
`auth/passenger/register.php` اليوم. سلوك مقصود لا خلل.
|
||||
|
||||
### الأعمدة المشفّرة (مؤكَّدة بالعيّنات)
|
||||
|
||||
| الجدول | مشفّر | نصّ عادي |
|
||||
|---|---|---|
|
||||
| `driver` | phone, email, gender, national_number, name_arabic, address, birthdate, site, first_name, last_name, fullNameMaritial | password (bcrypt), id (hex), التواريخ, license_type, status |
|
||||
| `passengers` | phone, email, gender, birthdate, site, first_name, last_name, sosPhone, education, employmentType, maritalStatus | password, id, status |
|
||||
| `users` | fingerprint, phone, email, first_name, last_name | gender, birthdate, site, password, status, user_type |
|
||||
| `CarRegistration` | vin, car_plate, owner | driverID, make, model, color, fuel, … |
|
||||
| `employee` | — (كله نصّ عادي) | الكل |
|
||||
| جداول OTP | phone_number, token/token_code, email | التواريخ, verified |
|
||||
|
||||
**تنبيه:** قيَم مثل `yet` / `none` / `sos` مخزَّنة نصّاً عادياً ويقارنها الكود حرفياً
|
||||
(`WHERE email = 'yet'`). تشفيرها يكسر تلك المقارنات — السكربت يمرّرها كما هي
|
||||
(`SENTINELS`).
|
||||
|
||||
---
|
||||
|
||||
## 3. التشفير الجديد — ما الذي يكتبه النظام اليوم
|
||||
|
||||
- **التخزين:** `AES-256-GCM` ببادئة `GCM:` و IV عشوائي 12 بايت + tag 16 بايت.
|
||||
مفعّل بـ `ENCRYPTION_MODE=gcm`؛ القراءة تدعم الصيغتين تلقائياً.
|
||||
- **البحث:** `BlindIndex` = `HMAC-SHA256(scope:normalized_value, BLIND_INDEX_PEPPER)`.
|
||||
الـ scope يشمل الجدول والحقل عمداً حتى لا يُربط نفس الرقم بين `driver` و`passengers`.
|
||||
التطبيع يوحّد `07…` و `+9627…`، ويوحّد أشكال الألف/الياء/التاء المربوطة للأسماء.
|
||||
- **ربط جداول التحقق:** `otpPhoneKey()` = `'K:' . index('otp.phone', $phone)`.
|
||||
- **بصمة الجهاز:** `fingerprint` مشفّر + `fingerprint_hash = sha256(البصمة الخام)`.
|
||||
|
||||
السكربت يفرض GCM على الكتابة بغضّ النظر عن `ENCRYPTION_MODE` — هذا غرضه.
|
||||
|
||||
---
|
||||
|
||||
## 4. فجوات المخطط — يجب سدّها قبل الترحيل
|
||||
|
||||
هذه ليست ملاحظات تجميلية؛ اثنتان منها **تكسران الإنتاج اليوم** بمعزل عن الترحيل.
|
||||
|
||||
1. **`driver` ينقصه `name_bidx` و `phone_key`** — `auth/driver/register.php:444`
|
||||
يُدخلهما، فالتسجيل يفشل. نفس الشيء لـ `passengers`
|
||||
(`auth/passenger/register.php:133`).
|
||||
2. **`token_verification_admin`: `phone_number VARCHAR(20)`, `token VARCHAR(10)`** —
|
||||
بينما `auth/otp/request.php:131` يكتب مفتاح `K:` بطول 66 حرفاً و OTP بـ GCM بطول
|
||||
~90. النتيجة قطع صامت ⇒ **لا يمكن التحقق من أي OTP للأدمن**.
|
||||
3. **`users` في `schema_primary` قديم جداً** — ينقصه `fingerprint`,
|
||||
`fingerprint_hash`, `status`, `country`, `phone_bidx`, `email_bidx`
|
||||
(يستخدمها `serviceapp/register.php:76`, `serviceapp/login.php:24`,
|
||||
`Admin/Staff/add.php:71`). كما أن `phone VARCHAR(15)` لا يتّسع لنصّ مشفّر.
|
||||
4. **فهارس UNIQUE على أعمدة مشفّرة تفقد معناها تحت GCM**:
|
||||
`driver.national_number`, `passengers (phone, email)`, `users.email/phone`.
|
||||
التشفير العشوائي يجعل نفس الرقم ينتج نصاً مختلفاً كل مرة، فالـ UNIQUE يمنع
|
||||
تكرار *النص المشفّر* لا تكرار *الرقم* — أي يسمح بحسابين بنفس الهاتف.
|
||||
البديل: UNIQUE على `*_bidx`.
|
||||
|
||||
كلها في `migrations/2026_07_29_v2_migration_schema_fixes.sql`.
|
||||
|
||||
**تعارض جانبي:** `2026_07_25_blind_index.sql` يضيف `phone_bidx`/`email_bidx` لـ
|
||||
`driver`/`passengers`/`adminUser`، وهي موجودة أصلاً في `schema_primary.sql`.
|
||||
على قاعدة جديدة من `schema_primary` تخطَّ تلك المايغريشن (عدا جزء
|
||||
`adminUser.status` المعلَّق في آخر ملف الإصلاحات).
|
||||
|
||||
---
|
||||
|
||||
## 5. البيانات المفقودة عمداً
|
||||
|
||||
- `driver.api_key` / `api_secret` و `passengers.*` و `users.*` — أُسقطت من المخطط
|
||||
الجديد لصالح جدول `api_keys` المستقل. لا تُنقل.
|
||||
- `users.status` موجود في v2 (`approved`) ويُعاد إدخاله بعد إضافة العمود.
|
||||
|
||||
---
|
||||
|
||||
## 6. خطوات التنفيذ
|
||||
|
||||
```bash
|
||||
# 0) نسخة احتياطية من الهدف
|
||||
mysqldump "$DB_PRIMARY_NAME_V2" > backup_before_migration.sql
|
||||
```
|
||||
|
||||
```bash
|
||||
# 1) سدّ فجوات المخطط
|
||||
mysql "$DB_PRIMARY_NAME_V2" < backend/migrations/2026_07_29_v2_migration_schema_fixes.sql
|
||||
```
|
||||
|
||||
```bash
|
||||
# 2) تأكيد أن المفتاح والـ IV صحيحان — لا تتقدّم إن لم يكن FAILED=0
|
||||
php backend/scripts/migrate_v2_reencrypt.php --probe
|
||||
```
|
||||
|
||||
```bash
|
||||
# 3) تشغيل جاف
|
||||
php backend/scripts/migrate_v2_reencrypt.php --dry-run
|
||||
```
|
||||
|
||||
```bash
|
||||
# 4) التنفيذ الفعلي
|
||||
php backend/scripts/migrate_v2_reencrypt.php --truncate
|
||||
```
|
||||
|
||||
```bash
|
||||
# 5) التدقيق — يفحص GCM والانحراف في الفهارس
|
||||
php backend/scripts/migrate_v2_reencrypt.php --verify
|
||||
```
|
||||
|
||||
```bash
|
||||
# 6) تفعيل الكتابة بـ GCM في .env ثم إعادة تحميل PHP-FPM
|
||||
# ENCRYPTION_MODE=gcm
|
||||
```
|
||||
|
||||
السكربت قابل لإعادة التشغيل: يتخطّى الصفوف الموجودة بالمفتاح الأساسي ما لم
|
||||
تُمرَّر `--force`، ويعمل على دفعات داخل معاملات مع فاصل قصير.
|
||||
|
||||
---
|
||||
|
||||
## 7. ما يبقى للمراجعة البشرية
|
||||
|
||||
- **`BLIND_INDEX_PEPPER` لا يُغيَّر بعد الترحيل أبداً.** كل قيَم `*_bidx` و`phone_key`
|
||||
مشتقّة منه؛ تغييره لاحقاً يُبطل كل عمليات البحث وتسجيل الدخول دفعةً واحدة،
|
||||
ويتطلّب إعادة بناء الفهارس كاملة (`backfill_blind_index.php --force`).
|
||||
- **`driver.site` يحمل نفس ciphertext الخاص بـ `address`** في بعض الصفوف — يستحق
|
||||
نظرة قبل الاعتماد على `site` في التوجيه الجغرافي. ليس من شأن الترحيل إصلاحه.
|
||||
- **مفاتيح التشفير لا تُخزَّن في المستودع.** السكربت يقرأها من
|
||||
`ENCRYPTION_KEY_PATH`/`ENC_KEY` و`initializationVector` وقت التشغيل فقط.
|
||||
Reference in New Issue
Block a user