diff --git a/docs/security/AUDIT-2026-08-07.md b/docs/security/AUDIT-2026-08-07.md index 71168277..e7716000 100644 --- a/docs/security/AUDIT-2026-08-07.md +++ b/docs/security/AUDIT-2026-08-07.md @@ -19,16 +19,22 @@ authz/headers/cors/inject) + پروب‌های دستی با JWT واقعی هر | # | یافته | شدت | وضعیت | |---|-------|-----|-------| | 1 | `TreatmentProtocolController` هیچ گِیت مجوزی نداشت — منشیِ `services:false` می‌توانست پروتکل درمان را بخواند، بازنویسی و حذف کند | 🟧 High | ✅ رفع شد | -| 2 | `react-router` — ۵ advisory از جمله XSS و open redirect | 🟧 High | ⚠️ باز — نیازمند تصمیم | -| 3 | `lodash-es` — code injection در `_.template` + دو prototype pollution | 🟧 High | ⚠️ باز (از آدیت قبلی) | +| 2 | `react-router` — ۵ advisory از جمله XSS و open redirect | 🟧 High | ✅ رفع شد (مهاجرت به v8) | +| 3 | `lodash-es` — code injection در `_.template` + دو prototype pollution | 🟧 High | ✅ رفع شد (override به 4.18.1) | | 4 | ۶۲ moderate در `@ckeditor/ckeditor5-build-classic` (deprecated) | 🟨 Medium | ⚠️ risk پذیرفته‌شده — تصمیم ۲۰۲۶-۰۸-۰۷ | -| 5 | `dangerouslySetInnerHTML` روی بدنهٔ بلاگ در `BlogReviewPage` | 🟨 Medium | ⚠️ باز | -| 6 | `APP_SECRET` واقعی در `.env.test` تحت git | 🟦 Low | ⚠️ باز | -| 7 | پسورد sandbox درگاه ملت هاردکد | ⬜ Info | باز (از آدیت قبلی، کم‌اهمیت) | +| 5 | `dangerouslySetInnerHTML` روی بدنهٔ بلاگ در `BlogReviewPage` | 🟨 Medium | ✅ رفع شد (sanitize هنگام ذخیره) | +| 6 | `APP_SECRET` واقعی در `.env.test` تحت git | 🟦 Low | ✅ رفع شد | +| 7 | پسورد sandbox درگاه ملت هاردکد | ⬜ Info | ✅ رفع شد (به env منتقل شد) | +| 8 | ۱۰ روت `GET` که مجوزِ رجیستری‌شان را enforce نمی‌کنند | 🟨 Medium | ⚠️ باز — با تست baseline مهار شد | -طبق تصمیم کاربر پیش از اجرا: Critical و High **همان جلسه** رفع می‌شوند؛ Medium و پایین‌تر فقط -گزارش می‌شوند. یافتهٔ ۱ رفع شد. یافته‌های ۲ و ۳ High هستند ولی رفعشان **ارتقای وابستگی** است نه -تغییر کد این repo، و شکستنِ روتینگ پنل یا ادیتور را در پی دارد — پس تصمیم به کاربر واگذار شد. +سیاست اولیه «فقط Critical/High رفع شود» بود؛ کاربر بعداً رفعِ همهٔ یافته‌های باز را خواست، پس +یافته‌های ۲، ۳، ۵، ۶ و ۷ هم بسته شدند. یافتهٔ ۴ طبق تصمیم صریح خارج از محدوده ماند و یافتهٔ ۸ +حین همین کار کشف شد. + +```bash +npm audit --omit=dev # قبل: high=2 moderate=62 بعد: high=0 moderate=3 +ddev exec php bin/phpunit # ۱۵۵۵ تست، ۴۸۳۲ assertion، سبز +``` --- @@ -183,10 +189,32 @@ npm audit --omit=dev --json | python3 -c "import json,sys; print(json.load(sys.s # → {'info': 0, 'low': 0, 'moderate': 62, 'high': 2, 'critical': 0, 'total': 64} ``` -**۵. وضعیت:** باز. پروژه روی React Router v7 است و رفع یعنی bump به نسخه‌ای خارج از range. سه -advisory (RSC، SSR hydration، CSRF مود RSC) به این پنل که کلاینت‌ساید محض است مربوط نمی‌شوند؛ ولی -open redirect و DoS مربوط‌اند. **رفع نشد** چون ارتقای major روتینگِ ۵۰ صفحهٔ پنل تست دستی -می‌خواهد و در یک پاس امنیتی ریسکش بیشتر از خودِ یافته است. تسک جدا پیشنهاد می‌شود. +**۵. رفع اعمال‌شده:** مهاجرت از `react-router-dom@7.17.0` به `react-router@8.3.0`. + +نسخهٔ امن فقط `> 8.2.0` است و در v8 پکیج `react-router-dom` دیگر منتشر نمی‌شود (آخرین نسخه‌اش +`7.18.2` است) — یعنی bump ساده ممکن نبود و مهاجرت اجباری بود: + +```bash +npm pkg delete dependencies.react-router-dom +npm pkg set dependencies.react-router="^8.3.0" +# ۸۱ فایل: from 'react-router-dom' → from 'react-router' +``` + +**۶. چرا کم‌ریسک بود:** کل APIهای مصرف‌شده در پنل ۱۱ تاست و همه در v8 دست‌نخورده‌اند — +`BrowserRouter`, `MemoryRouter`, `Routes`, `Route`, `Link`, `NavLink`, `Navigate`, `Outlet`, +`useLocation`, `useNavigate`, `useParams`, `useSearchParams`. هیچ API حذف‌شده‌ای در پروژه استفاده +نمی‌شد، پس مهاجرت فقط تغییرِ نامِ ماژول بود نه بازنویسیِ روتینگ. + +**۷. تأیید:** + +``` +npx tsc --noEmit # بدون خطا +ddev exec yarn dev # webpack compiled successfully — 54 فایل +npx vitest --run # 802 تست فرانت‌اند +``` + +هفت تستِ فرانت‌اند در اجرای موازیِ اول timeout دادند؛ با `--no-file-parallelism` هر ۵۴ تستِ همان +فایل‌ها سبز شد. یعنی گرسنگی منابع بود نه رگرسیونِ روتینگ. --- @@ -199,7 +227,27 @@ open redirect و DoS مربوط‌اند. **رفع نشد** چون ارتقای **۳. بازتولید:** همان `npm audit` بالا — `high=2` که یکی react-router است و یکی lodash-es. -**۴. وضعیت:** باز. transitive است؛ باید ردیابی شود کدام پکیج آن را می‌کشد. +**۴. منشأ:** ردیابی شد — کاملاً transitive و فقط از یک جا می‌آید: + +``` +clinicpro +└─┬ @ckeditor/ckeditor5-build-classic@44.3.0 + └── lodash-es@4.17.21 (در ده‌ها زیرپکیج dedupe شده) +``` + +**۵. رفع اعمال‌شده:** چون هیچ dependency مستقیمی نیست، ارتقای مستقیم ممکن نبود. نسخهٔ امن +`4.18.1` است (range آسیب‌پذیر `<=4.17.23`)، پس با override اعمال شد: + +```json +"overrides": { "lodash": "^4.17.21", "lodash-es": "^4.18.1" } +``` + +**۶. چرا override و نه ارتقای CKEditor:** بستنِ این یافته از راه CKEditor یعنی همان مهاجرتی که در +یافتهٔ ۴ عمداً خارج از محدوده گذاشته شد. override همان مشکل را بدون لمس‌کردن ادیتور می‌بندد. +`lodash-es` در بازهٔ 4.17→4.18 شکستِ API ندارد و CKEditor فقط از توابع پایه‌اش استفاده می‌کند. + +**۷. تأیید:** `npm audit --omit=dev` دیگر `lodash-es` را گزارش نمی‌کند؛ build و ۸۰۲ تست فرانت‌اند +سبز. --- @@ -226,9 +274,28 @@ CKEditor بالاست: نویسنده‌ای که HTML مخرب paste کند، **۳. بازتولید:** `grep -rn "dangerouslySetInnerHTML" assets/admin/` → تنها یک hit، همین خط. -**۴. وضعیت:** باز طبق سیاست (Medium بدون تأیید رفع نمی‌شود). CSP فعلی `script-src 'self'` است، پس -`` به متنِ + `alert(1)` تبدیل می‌شد. برای این عناصر خودِ محتوا هم باید برود. + +**۶. پاک‌سازی پیش از سنجشِ خالی‌بودن:** بدنه‌ای که چیزی جز markup ناامن ندارد، بعد از پاک‌سازی خالی +می‌شود و باید همان ۴۲۲ «الزامی است» را بگیرد، نه اینکه خالی ذخیره شود. + +**۷. تست:** `tests/Blog/BlogBodySanitizerTest.php` — پنج تست: حذف `