> **قانون گزارش:** هیچ یافتهای بدون بازتولید ثبت نشده. leadهای درایور که پس از خواندن کد «مثبت کاذب» یا «طراحی امن» بودند، در بخش «رد شد» با دلیل آمدهاند.
---
## خلاصهٔ وضعیت
سطح امنیتی پایه **خوب** است. مسیرهای حساس (access control پنل ادمین، پرداخت، آپلود فایل) دفاع درست دارند و شواهد قوی از تستمحوری امنیتی (تستهای amount/replay پرداخت) دیده شد. یافتهها عمدتاً **سختسازی (hardening)** هستند، نه رخنهٔ فعال. هیچ Critical/High قطعیای که در محیط زنده قابل بهرهبرداری باشد پیدا نشد.
| # | یافته | شدت | وضعیت |
|---|---|---|---|
| 1 | نبود CSP روی SPA ادمین (`/admin`) | 🟨 Medium | ✅ رفع شد |
| 3 | `APP_SECRET` واقعی commitشده در `.env.dev` | 🟦 Low | ✅ رفع شد |
| 4 | فلگهای session cookie پیننشده (secure/samesite/httponly) | 🟦 Low | ✅ رفع شد |
| 5 | پسورد sandbox درگاه ملت هاردکد | ⬜ Info | باز (کماهمیت) |
### وضعیت رفع (۲۰۲۶-۰۷-۱۹)
- **۱ — CSP:** `src/Shared/EventSubscriber/AdminCspSubscriber.php` افزوده شد؛ CSP فقط روی `/admin/*`. تأیید زنده: هدر `content-security-policy` روی `/admin` حاضر است. تست: `tests/Shared/AdminCspSubscriberTest.php` (۲ تست سبز).
- **۳ — APP_SECRET:** مقدار واقعی از `.env.dev` برداشته و placeholder شد (مقدار واقعی باید در `.env.local` خارج از git بماند + rotate).
-`lodash` با `overrides` به خط نگهداریشده پین شد. **نکتهٔ مهم:** advisory `GHSA-r5fr-rjxr-66jc` نسخهٔ patched ندارد، اما lodash اینجا **فقط build-time** است (زیر `@symfony/webpack-encore` → `pretty-error`) و **به باندل مرورگر نمیرود** — ریسک runtime عملاً صفر. تأیید: `npm ls lodash` تنها مسیرش webpack-encore است.
- **۶۱ moderate CKEditor باز است:** `@ckeditor/ckeditor5-build-classic@44.3.0` آخرین نسخهٔ همان پکیج است و **deprecated** — نسخهٔ بالاتری برای bump وجود ندارد. رفع واقعی نیازمند **مهاجرت از build-classic به پکیج umbrella `ckeditor5` (v45+)** است که refactor ادیتور بلاگ و تست دستی UI میخواهد؛ عمداً در این پاس انجام نشد (ریسک شکستن ادیتور). این XSSها moderate و admin-only هستند (ادمین باید HTML مخرب در ادیتور خودش paste کند). → تسک جدا پیشنهاد میشود.
### آنچه سالم بود (تأییدشده، یافته نیست)
- **Access control پنل ادمین:** روی ۸ مسیر `admin/*` الگوی سالم `anon 401 / admin 200 / doctor 403`. هیچ Broken Access Control.
- **هدرهای API:** HSTS، `x-content-type-options`، `x-frame-options: DENY`، `referrer-policy`، `permissions-policy`، و `content-security-policy: default-src 'none'` روی `/api`.
### محدودیت پوشش (صادقانه)
- پس از ریست DB فقط دو کاربر `admin` و `doctor` وجود دارند؛ `clinic`/`secretary`/`representation` نبودند (`ERR_NOT_FOUND_001` در login و شکست fallback lexik). ماتریس کامل ۵-نقشه و IDOR بیننقشی بهطور زنده تست نشد. اما `doctor` (نقش احرازشدهٔ کمدسترسی) همچنان تأیید میکند غیر-ادمین روی `admin/*` کد `403` میگیرد.
- IDOR بین دو کاربر همنقش زنده تست نشد (فقط یک کاربر در هر نقش موجود بود).
-`ratelimit` روی مسیرهای auth عمداً اجرا نشد (سوزاندن پنجرهٔ نرخ).
---
## یافتهها
### 1. 🟨 Medium — نبود Content-Security-Policy روی SPA ادمین
**۱. عنوان:** صفحهٔ `/admin` (React SPA) هیچ هدر `content-security-policy` ندارد.
**۲. شدت:** Medium — CVSS-وار ~5.4 (AV:N/AC:L/PR:N/UI:R). blast-radius یک XSS احتمالی در پنلی که CKEditor و محتوای پویا سرو میکند.
**۴. توضیح:** برخلاف `/api` که CSP سخت دارد، خودِ SPA بدون CSP سرو میشود. اگر هر ورودی کاربر (نام دکتر، محتوای بلاگ CKEditor، فیلد پروفایل) در DOM بدون escape بنشیند، CSP لایهٔ دفاعی دومی نیست تا اجرای اسکریپت تزریقی را متوقف کند. با وجود لودر شخصثالث سنگین (CKEditor)، این ریسک واقعی است.
# → content-security-policy: default-src 'none' ✓ (روی API هست، روی admin نیست)
```
**۶. راهکار:** یک CSP مناسب SPA به پاسخ `/admin` اضافه کن (kernel response listener یا هدر nginx). چون Encore اسکریپتها را از `public/build` میدهد، از nonce یا `'self'` استفاده کن، نه `unsafe-inline` برای اسکریپت.
**۷. کد اصلاحشده** (Symfony response subscriber، فقط برای مسیرهای `/admin`):
**۸. علت انتخاب:** response subscriber مسیرمحور، بدون دستزدن به کانفیگ nginx (که در ddev و prod متفاوت است) و بدون افزودن باندل CSP کامل (nelmio/security). اگر بعداً nonce لازم شد، همینجا تزریق میشود. گزینهٔ رد شده: هدر سراسری در nginx — چون CSP سختِ `/api` را میشکند و مدیریت دو محیط را سخت میکند.
**۴. توضیح:**`npm audit --omit=dev` یک High گزارش میدهد: `lodash vulnerable to Code Injection via _.template imports key names`. اگر جایی `_.template` روی رشتهٔ کنترلشدهٔ کاربر اجرا شود، منجر به اجرای کد در مرورگر میشود. در این کدبیس سینک مستقیمی پیدا نشد (احتمالاً وابستگی گذرا)، ولی وجودش در باندل prod ریسک است.
**۵. بازتولید:**
```bash
cd clinicpro && npm audit --omit=dev
# → critical=0 high=1 moderate=61
# High: lodash vulnerable to Code Injection via `_.template` imports key names
**۶. راهکار:**`lodash` را به نسخهٔ وصلهشده ارتقا بده؛ اگر وابستگی گذراست، از `overrides` در `package.json` استفاده کن. سپس درخت moderate مربوط به CKEditor را با ارتقای `ckeditor5` به آخرین minor امن همراستا کن.
**۷. کد اصلاحشده** (`package.json`):
```jsonc
{
"overrides":{
"lodash":"^4.17.21"
}
}
```
```bash
cd clinicpro && npm install && npm audit --omit=dev # باید high=0 شود
```
**۸. علت انتخاب:**`overrides` وابستگی گذرا را بدون تغییر مستقیم آن پکیج پین میکند — کمریسکترین مسیر. گزینهٔ رد شده: حذف lodash — چون گذرا و ناشناخته است کدام پکیج آن را میکشد؛ ابتدا override، اگر شکست، `npm ls lodash` برای ریشهیابی.
---
### 3. 🟦 Low — `APP_SECRET` واقعی commitشده در `.env.dev`
**۱. عنوان:**`.env.dev` مقدار secret واقعی دارد و در git ترک شده است.
**۲. شدت:** Low. فقط در `APP_ENV=dev` بارگذاری میشود؛ prod (`.env.coolify.example`/`.env.liara.example`) `APP_SECRET` خالی برای پرشدن دارد، پس این مقدار به prod نمیرسد.
**۳. محل:**`.env.dev:3` → `APP_SECRET=42f34156531bad221462ff02bd43f42b` (git-tracked). همچنین `.env.test:3` مقدار تستی دارد (بیخطر).
**۴. توضیح:**`APP_SECRET` برای CSRF token، signed URI و امضای کوکی remember-me استفاده میشود. لو رفتنش در git تاریخچه ماندگار است. چون prod سکرت جدا میگیرد، تأثیر محدود به محیط dev است؛ ولی اگر روزی deploy بهاشتباه با `APP_ENV=dev` بالا بیاید، این سکرتِ عمومی فعال میشود.
**۵. بازتولید:**
```bash
git -C clinicpro show HEAD:.env.dev | grep APP_SECRET
# → 🟨 MEDIUM Secret-shaped values in a git-tracked env file (.env.dev: APP_SECRET)
```
**۶. راهکار:** مقدار واقعی را از `.env.dev` بردار و به `.env.local` (git-ignored) منتقل کن، یا در `.env.dev` یک placeholder غیرحساس بگذار. سکرت فعلی را rotate کن (چون در تاریخچه مانده).
**۷. کد اصلاحشده** (`.env.dev`):
```dotenv
# مقدار واقعی در .env.local (خارج از git) بماند
APP_SECRET=change_me_in_env_local
```
**۸. علت انتخاب:** Symfony بهطور استاندارد `.env.local` را برای overrideهای حساس دارد و آن را ignore میکند. گزینهٔ رد شده: پاککردن از تاریخچه با `git filter-repo` — پرهزینه و برای یک سکرت dev غیرضروری؛ فقط rotate کافی است.
---
### 4. 🟦 Low — فلگهای session cookie پیننشده
**۱. عنوان:**`session.cookie_secure`، `cookie_samesite` و `cookie_httponly` صریحاً ست نشدهاند.
**۲. شدت:** Low. احراز هویت اپ **JWT/stateless** است و روی session تکیه نمیکند؛ ولی هر سطح stateful (مثل صفحات Twig پرداخت) از دفاع کوکی بهره میبرد.
**۴. توضیح:** پیشفرض `cookie_secure: auto` است؛ در مسیر HTTP کوکی روی plain مینشیند. `cookie_samesite` ستنشده یعنی دفاع CSRF ضعیفتر. `httponly` پیشفرض `true` است ولی پیننشدن آن را شکننده میکند.
**۴. توضیح:** اسکنر آن را «hardcoded credential» علامت زد. این پسورد محیط تست عمومی شاپرک است و راز نیست؛ اما ثابتبودنش در کد بین «راز» و «مقدار تست» ابهام میسازد.
# → 🟧 HIGH src/Payment/Gateway/MellatGateway.php:23 private const SANDBOX_PASSWORD = '17384843';
```
**۶. راهکار:** اگر واقعاً sandbox عمومی است، یک کامنت روشن بگذار که راز نیست؛ اعتبار prod همیشه از env بیاید. اعتبار prod را هرگز هاردکد نکن.
**۷. کد اصلاحشده:**
```php
// مقدار عمومی sandbox شاپرک — راز نیست. اعتبار prod از env میآید.
privateconstSANDBOX_PASSWORD='17384843';
```
**۸. علت انتخاب:** حذف false-positive آینده در آدیتها بدون تغییر رفتار. اعتبار prod همان الگوی موجود (env) را نگه میدارد.
---
## leadهای درایور که پس از بازرسی رد شدند
| lead | چرا رد شد |
|---|---|
| `FileUploadService`/`FileValidatorService` rename/fopen «filesystem mutation» | نام مقصد از `sanitizeFilename` → `bin2hex(random_bytes(16)).ext`؛ محتوا با magic-byte (jpeg/png/webp) تأیید؛ `..` مسدود. طراحی امن. |
| `templates/payment/result.html.twig:140` — `redirect_to\|json_encode\|raw` | `redirect_to` = آدرس frontend که در همهٔ مسیرها با `isAllowedFrontend()` (allowlist host) اعتبارسنجی میشود؛ `json_encode` در context جاوااسکریپت escape امن میدهد. نه XSS نه open-redirect. |
| `payment_callback` با `security: false` | callback از redirect مرورگر میآید (نه server-to-server)؛ امنیت با verify مبلغ سمت بانک + ضد-replay (`RefId/SaleOrderId`) تأمین میشود — تستهای `PaymentCallbackAmountTest::testUnderpaymentIsRejected` و `testReplayedGatewayReferenceIsRejected` این را پوشش میدهند. |
| `guards`: دو مسیر `/api/v1/altcha/challenge` و `/altcha/config` بدون `#[IsGranted]` | endpointهای عمومی CAPTCHA؛ باید برای anon قابلدسترس باشند. صحیح. |
| `DoctorImportService.php:88` — `md5` «weak hash» | برای تولید شناسهٔ مصنوعی import (غیرامنیتی)، نه هش رمز. بیخطر. |
| `SeedDemoDataCommand` — `mt_rand` «non-crypto RNG» | فقط دادهٔ دمو seed میکند. بیخطر. |
---
## پیشنهادهای معماری / امنیت / کیفیت
1.**پوشش تست نقشها:** اسکریپت بازسازی کاربران تست (`create_test_users.php` که در ریشهٔ `clinicpro/` نبود) را بازگردان یا یک Doctrine fixture برای هر ۵ نقش بساز تا آدیتهای بعدی ماتریس کامل authz + IDOR بیننقشی را زنده تست کنند. الان فقط `admin`+`doctor` در DB هستند.
2.**IDOR دستی:** برای منابع `{uuid}` (پرداخت، نوبت، رکورد بیمار) با دو کاربر همنقش تست مالکیت انجام شود — این آدیت بهخاطر تککاربربودن هر نقش آن را زنده پوشش نداد. (نکته مثبت: `initiateAppointment` چک `getUser()->getId()` دارد.)
3.**CSP نهایی با nonce:** پس از یافتهٔ ۱، اگر اسکریپت inline لازم شد، nonce بهجای `unsafe-inline`.
4.**CI dependency gate:**`npm audit --omit=dev` و `composer audit` را در CI با آستانهٔ fail-on-high اضافه کن تا lodash/CKEditor دوباره برنگردند.
5.**Rate limit مثبت:** پیکربندی rate limiter قوی است؛ فقط مطمئن شو روی `payment/callback` هم یک سقف منطقی هست (ضد flood).
---
*پایان گزارش — تولیدشده با درایور `symfony-security-audit` + بازرسی دستی. هر یافته بازتولید شده است.*
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.