# چک‌لیست امنیت پرمی‌استار

این سند دو بخش دارد: **(الف)** کارهایی که باید پیش از راه‌اندازی انجام دهید، و **(ب)** آنچه در
خود برنامه پیاده‌سازی شده است (تا بدانید هر کنترل کجاست و چطور بررسی‌اش کنید).

مسیر مرجع: پنل مدیریت → **مرکز امنیت** (`/admin/security`) که ۹ بررسی خودکار، امتیاز از ۱۰۰
و فرم تنظیمات امنیتی را یک‌جا نشان می‌دهد.

---

## الف) چک‌لیست راه‌اندازی

### ۱. سرور و دسترسی‌ها

- [ ] **document root** روی پوشه `public` تنظیم شده باشد (مهم‌ترین کنترل).
- [ ] `storage/`, `config/`, `app/`, `database/`, `routes/`, `bootstrap/` از طریق وب قابل خواندن نباشند
      (فایل‌های `.htaccess` داخل `storage/` و `storage/private/` اجرای اسکریپت را مسدود می‌کنند).
- [ ] `.env` با دسترسی `0640` و مالک کاربر وب‌سرور باشد؛ هرگز در ریپازیتوری commit نشود.
- [ ] پس از نصب، دسترسی نوشتن ریشه و `config/` به `755` برگردد.
- [ ] `Options -Indexes` فعال باشد (در `public/.htaccess` هست) تا فهرست پوشه‌ها نمایش داده نشود.
- [ ] فایل‌های `docs/` و `tests/` در صورت عدم نیاز از هاست حذف شوند.

### ۲. HTTPS و هدرها

- [ ] گواهی SSL معتبر نصب و **اجبار HTTPS** فعال باشد (`force_https` در مرکز امنیت).
- [ ] پس از فعال‌شدن HTTPS، هدر `Strict-Transport-Security` به‌صورت خودکار ارسال می‌شود.
- [ ] در مرکز امنیت، بررسی «HTTPS» سبز باشد.
- [ ] `Content-Security-Policy` فعال باشد (`CSP_ENABLED=true` در `.env`).
      اگر اسکریپت/فونت خارجی اضافه کردید، CSP را در `app/Security/Security.php::headers()` به‌روز کنید.

### ۳. نصب‌کننده

- [ ] فایل `storage/installed.lock` وجود داشته باشد (پس از گام ۷ خودکار ساخته می‌شود).
- [ ] `GET /install` صفحه «قفل شده» با کد **۴۰۳** بدهد.
- [ ] در صورت تمایل `routes/install.php` و `app/Controllers/InstallController.php` حذف شوند
      (بدون آن‌ها هم سایت کار می‌کند؛ فقط مسیر `/install` باید از `routes` برداشته شود).

### ۴. حساب‌های مدیریتی

- [ ] حداقل یک حساب **مدیر کل** با رمز قوی (۱۲+ نویسه، ترکیب حرف/عدد/نماد) و موبایل در دسترس.
- [ ] برای هر نفر حساب جدا ساخته شود؛ از اشتراک‌گذاری حساب خودداری کنید.
- [ ] اصل **حداقل دسترسی**: به پشتیبان فقط `tickets.*` و `dashboard.view` بدهید.
- [ ] مجوزهای حساس (`payments.refund`, `customers.manage`, `admins.manage`, `backup.manage`,
      `logs.view`, `security.center`, `settings.site|payment|sms`, `system.info`) فقط به افراد مورد اعتماد.
- [ ] در مرکز امنیت، **فهرست IP مدیران** (`admin_ip_allowlist`) را در صورت داشتن IP ثابت تنظیم کنید
      (برنامه اجازه نمی‌دهد IP فعلی خودتان را از فهرست خارج کنید).
- [ ] `login_otp_required` را در صورت نیاز فعال کنید تا ورود مدیران دو مرحله‌ای شود.
- [ ] حداقل طول رمز (`password_min_length`) و عمر نشست (`session_lifetime`) را در مرکز امنیت تنظیم کنید.

### ۵. پرداخت و سرویس‌ها

- [ ] مرچنت کد واقعی زرین‌پال وارد و **حالت sandbox خاموش** شود (پنل → تنظیمات پرداخت).
- [ ] آدرس بازگشت در پنل زرین‌پال دقیقاً برابر `https://your-domain/payment/zarinpal/callback` باشد.
- [ ] اطلاعات ملی‌پیامک وارد و «حالت آزمایشی» خاموش شود؛ سپس «ارسال پیامک آزمایشی» موفق باشد.
- [ ] مبلغ‌ها فقط از پایگاه داده محاسبه می‌شوند؛ هیچ مبلغی از مرورگر پذیرفته نمی‌شود
      (پس از تغییر قیمت، سفارش‌های قدیمی دست‌نخورده می‌مانند).

### ۶. پشتیبان‌گیری و بازیابی

- [ ] پوشه `storage/backups/` قابل نوشتن باشد (`dirWritable` در صفحه پشتیبان‌ها).
- [ ] اولین پشتیبان ساخته و **خارج از هاست** نگهداری شود.
- [ ] برنامه منظم (هفتگی) برای پشتیبان‌گیری و یک بار **تست بازیابی** داشته باشید.
- [ ] فضای آزاد دیسک در `/admin/system` بررسی شود.

### ۷. پایش

- [ ] هر هفته `/admin/logs/security` (رویدادهای `critical`) و `/admin/logs/activity` مرور شود.
- [ ] `/admin/logs/logins` برای ورودهای ناموفق و IPهای ناشناس بررسی شود.
- [ ] لاگ خطای PHP در `storage/logs/php-error.log`؛ در صورت بزرگ‌شدن، از `purge` استفاده کنید.
- [ ] `APP_DEBUG=false` در محیط عملیاتی (نمایش جزئیات خطا به کاربر = نشت اطلاعات).

---

## ب) کنترل‌های پیاده‌سازی‌شده در برنامه

| حوزه | پیاده‌سازی | محل کد |
| --- | --- | --- |
| CSRF | توکن به ازای هر نشست، فیلد `_token` در همه فرم‌ها، اعتبارسنجی خودکار همه POSTها توسط روتر، کد **۴۱۹** در صورت نقض | `app/Core/CSRF.php`, `app/Core/Router.php` |
| رمز عبور | `argon2id` در صورت پشتیبانی، در غیر این صورت `bcrypt`؛ باز-هش خودکار هنگام ورود؛ حداقل طول قابل تنظیم (۸–۶۴) | `app/Security/Auth.php` |
| قفل حساب | ۵ بار تلاش ناموفق → ۱۵ دقیقه قفل (`LOGIN_MAX_ATTEMPTS`) | `app/Models/Admin.php::registerFailure` |
| محدودسازی نرخ | فایل‌محور به ازای کلید/IP (`storage/cache/rl_*.json`)؛ ورود مدیران ۸ بار در ۵ دقیقه؛ OTP؛ ساخت پشتیبان ۵ بار در ۱۰ دقیقه | `app/Security/Security.php::rateLimit`, `app/Middleware/RateLimitMiddleware.php` |
| OTP پیامکی | TTL (پیش‌فرض ۱۰ دقیقه)، سقف تلاش (۵)، cooldown ارسال مجدد (۱۲۰ ثانیه)، سقف روزانه IP (۲۰) و شماره (۸)؛ هش کد در پایگاه داده، نه متن آن | `app/Services/SmsService.php` |
| نشست‌ها | ذخیره در پایگاه داده (`sessions`)، پایان دادن گروهی از پنل، `session_version` برای ابطال همه جلسات یک حساب پس از تغییر رمز | `app/Core/DbSession.php`, `/admin/security/sessions` |
| RBAC | ۲۷ مجوز در ۸ گروه، ۳ نقش پیش‌فرض، مجوز اختصاصی هر حساب، ۱۰ مجوز حساس فقط با اعطای مدیر کل، ثبت `privilege_escalation_denied` | `app/Models/Admin.php`, `app/Controllers/Admin/ManagerController.php` |
| محافظت از خود | مدیر نمی‌تواند حساب خودش را غیرفعال/حذف کند؛ آخرین مدیر کل قابل حذف نیست؛ مدیر غیرکل نمی‌تواند حساب مدیر کل را ویرایش کند | `app/Controllers/Admin/ManagerController.php` |
| هدرهای امنیتی | `CSP`, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, `HSTS` (روی HTTPS), `X-XSS-Protection: 0`؛ همچنین `X-Powered-By` حذف می‌شود تا نسخه دقیق PHP لو نرود (در php.ini هم `expose_php=Off` توصیه می‌شود) | `app/Security/Security.php::headers` |
| متدها | `HEAD` دقیقاً مثل `GET` پاسخ می‌گیرد (مانیتورهای آپ‌تایم و `curl -I` خطای ۴۰۵ نمی‌گیرند) و پاسخ ۴۰۵ هدر `Allow` را بر اساس مسیر واقعی برمی‌گرداند | `app/Core/Router.php::dispatch` |
| آپلود تصویر | بررسی `is_uploaded_file`، MIME واقعی با `finfo`، اعتبارسنجی `getimagesize`، سقف ۴ مگابایت، نام تصادفی با `random_bytes`، `chmod 0644` | `app/Security/Security.php::uploadImage` |
| پیوست تیکت | ذخیره در `storage/private/tickets` (خارج از وب) و سرو شدن فقط از مسیر احراز هویت‌شده `/admin/tickets/{id}/attachment/{attId}` | `app/Security/Security.php::uploadAttachment` |
| Redirect باز | اجازه فقط به مسیرهای داخلی یا همان هاست؛ رد `//evil`, `\evil`, `javascript:` | `app/Security/Security.php::safeRedirect` |
| SQL | همه پرس‌وجوها با **prepared statement** و پارامتر بایند؛ نام ستون/جدول فقط از فهرست سفید | `app/Core/Database.php` |
| XSS | گریز همه خروجی‌ها با `helpers::e()` / `nl2br_e()`؛ هیچ `echo` خامی از ورودی کاربر | همه نماها |
| ورودی | `Validator` با قواعد `required/nullable/mobile/email/url/digits/numeric/integer/min/max/in/confirmed/accepted/unique/password`؛ ارقام فارسی فقط در فیلدهای عددی/کد نرمال می‌شوند و متن آزاد دست‌نخورده می‌ماند | `app/Core/Validator.php`, `app/Core/Request.php` |
| قیمت‌گذاری | محاسبه سمت سرور از قیمت ذخیره‌شده (تومان)، اعمال سقف/حداقل/ظرفیت کد تخفیف در مدل، ثبت `discount_usages` | `app/Services/CartService.php`, `app/Models/Discount.php` |
| پرداخت | اعتبارسنجی تراکنش فقط از طریق verify سمت زرین‌پال؛ ثبت `payments` و `transactions` با مرجع درگاه؛ جلوگیری از پرداخت دوباره | `app/Services/ZarinPalService.php`, `app/Controllers/PaymentController.php` |
| لاگ‌ها | `security_logs` (سطح info/warning/critical)، `activity_logs` (کنش مدیران)، `login_history` (موفق/ناموفق با IP و user-agent)، `purge` فقط توسط مدیر کل | `app/Security/Security.php`, `app/Controllers/Admin/LogController.php` |
| مرکز امنیت | ۹ بررسی خودکار (HTTPS، قفل نصب‌کننده، `.htaccess` آپلودها، نبود پوشه در معرض، خاموش بودن debug، واقعی بودن درگاه، فعال بودن OTP، هش رمزها، فهرست IP مدیران) + امتیاز وزنی (critical=20, high=12, medium=6, low=3) | `app/Controllers/Admin/SecurityController.php` |
| پشتیبان | فقط `super_admin`، ذخیره خارج از document root، دانلود با احراز هویت و ثبت رویداد، محدودسازی نرخ | `app/Controllers/Admin/BackupController.php`, `app/Services/BackupService.php` |
| حالت تعمیرات | `maintenance_mode` با middleware اختصاصی و صفحه اطلاع‌رسانی | `app/Middleware/MaintenanceMiddleware.php` |
| نصب‌کننده | ساخت `.env` با دسترسی `0640`، کلید تصادفی برنامه، نوشتن `storage/installed.lock`، قفل دائمی مسیر `/install` | `app/Controllers/InstallController.php` |

---

## پاسخ به حوادث

1. **نشست مشکوک** — `/admin/security/sessions` → «پایان همه جلسات» (فقط مدیر کل).
2. **لو رفتن رمز مدیر** — ویرایش حساب در `/admin/managers/{id}/edit` و تغییر رمز
   (همه جلسات آن حساب خودکار باطل می‌شوند).
3. **حمله برو‌ت‌فورس** — `/admin/logs/logins` با فیلتر `only=failed`؛ در صورت نیاز
   `admin_ip_allowlist` را محدود و `login_otp_required` را فعال کنید.
4. **نشت مرچنت کد / رمز پیامک** — بلافاصله در پنل (تنظیمات پرداخت / پیامک) جایگزین کنید؛
   تغییرات با سطح `critical` در گزارش امنیتی ثبت می‌شوند.
5. **آلودگی آپلودها** — فایل‌های `public/uploads/` را با فهرست پایگاه داده مقایسه و موارد ناشناس را حذف کنید؛
   `.htaccess` آن پوشه اجرای اسکریپت را مسدود می‌کند.
6. **بازیابی** — از `/admin/backups` نسخه را دانلود و روی پایگاه داده بازیابی کنید؛ سپس
   `storage/cache/` را خالی کنید (`/admin/security` → پاک‌سازی کش).

---

## تست‌های امنیتی خودکار

```bash
php tests/run.php Security Http Validator Request
```

این تست‌ها redirect باز، محدودسازی نرخ، رد آپلود `.php`، قواعد اعتبارسنجی، کد ۴۱۹ برای POST بدون
توکن، کد ۴۰۵ برای مسیرهای POST-only و ارسال هدرهای امنیتی را بررسی می‌کنند.
