Files
MathApp/docs/security-before-production.md
Zareian a4e0c576bb Docs brought to the current state: CLAUDE.md rewritten, README formal, handbook and diagrams redrawn
- CLAUDE.md: 1,225 → ~430 lines, current state only (the dated status log stays in git history).
  Server rules moved to the top: what is ours on 249/247 and what is not, non-negotiable rules,
  announce-before-acting, password handling, and the full "deploy it" procedure with verification.
- README: formal entry page with current numbers (10 grades, 18,522 questions, 61 API operations,
  973 tests, PostgreSQL/Redis).
- Removed docs/frontend/ (Figma-era guide with guest login and its own api.ts; the frontend repo has
  the real client). Swagger's external-docs link now points at the frontend repo.
- architecture/operations/security/content-authoring/loadtest/textbooks: SQLite, guest login,
  demo accounts, question packs and the port-80 note replaced by what is true now.
- Technical handbook text updated throughout; all seven diagrams redrawn (PostgreSQL, phone-only
  login, subscriptions, current data model); PDF rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:45:51 +03:30

26 KiB
Raw Permalink Blame History

پیش از پروداکشنِ واقعی: چیزهایی که عمداً باز مانده‌اند

آخرین بازبینی با کد و سرورِ زنده: ۲۰۲۶-۱۰-۱۳ (۲۱ مهر ۱۴۰۵): حساب‌های دمو، nginx (فقط آروان، CSP)، جعلِ IP، سقفِ پیامک.

سرویس زنده است ولی هنوز به روی کاربرِ عمومی باز نشده. چند چیز عمداً یا به‌خاطرِ تصمیمِ بازِ طراحی باز مانده است. هر کدام برای یک سرورِ عمومی با کاربرِ واقعی (کودک، پرداختِ واقعی) خطر است و باید پیش از آمدنِ اولین کاربرِ واقعی بسته شود.

برای agentها و توسعه‌دهنده‌های بعدی: این فهرست ادعاست، نه ضمانت؛ ممکن است از این تاریخ به بعد چیزی عوض شده باشد. پیش از بستنِ هر مورد، وضعیتِ فعلی‌اش را با خودِ سرور یا کد بسنجید (ستونِ «چطور بسنجید»). مورد که بسته شد، از فهرست برداشته می‌شود و یک خط در «حفاظ‌هایی که هست» (پایینِ همین فایل) می‌گیرد، با نامِ تستی که آن را نگه می‌دارد. تاریخ و دلیلِ تغییر در پیامِ کامیت است، نه این‌جا.

هر آزمونِ «از بیرون» با curl بدونِ توکن انجام شده و در تاریخِ بالا تأیید شده است.


خلاصه، به ترتیبِ خطر

# مورد خطر کارِ کیست
۷ پنلِ /admin/ روی دامنه‌ی عمومی متوسط nginx + کد
۱۱ کدِ OTP بی‌هش در دیتابیس و در فهرستِ پنل کم کد
۱۲ هزینه‌ی دستیارِ AI با ساختِ شماره‌ی تازه (هر شماره ۲ سوالِ رایگان) قابلِ بالا بردن است کم (مالی) کد
۱۵ دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل بالا ادمینِ سرور
۱۶ فرانت توکن را در localStorage نگه می‌دارد کم فرانت
۲۴ سقفِ روزانه‌ی پیامکِ کلِ سامانه (۲٬۰۰۰ روی سرور) را یک ناشناس پر می‌کند و ورود با پیامک برای همه بسته می‌شود بالا (دسترس‌پذیری، هزینه) تصمیمِ طراحی
۲۵ با دانستنِ شماره‌ی کسی، ورودِ او تا ۲۴ ساعت بسته می‌شود متوسط تصمیمِ طراحی
۲۷ کلیدِ جوابِ هر سوال با یک حسابِ دوم درمی‌آید؛ امتیازِ هر جوابِ درست دو بار داده می‌شود کم تصمیمِ محصولی
۲۸ استفاده‌ی دوباره از توکنِ تمدیدِ دزدیده‌شده تشخیص داده نمی‌شود؛ «خروج از همه‌ی دستگاه‌ها» نیست کم کد، بعد از ارائه
۲۹ پیشنهادِ ثبت‌نام با حذفِ حساب و ورودِ دوباره با همان شماره دوباره داده می‌شود کم کد
۳۱ لپ‌تاپِ توسعه با mobile-preview روی شبکه‌ی محلی باز است و در DEBUG کدِ 0000 را می‌پذیرد کم لپ‌تاپِ توسعه

۷. پنلِ ادمین روی دامنه‌ی عمومی

  • چیست: /admin/ روی mathapi.tadrisino2.ir، با ورودِ نام‌کاربری/رمزِ جنگو. سقفِ ۱۰ تلاش در ۱۵ دقیقه برای هر IP دارد (AdminLoginThrottleMiddleware)، IP جعل‌پذیر نیست (فقط از آروان)، ولی ۲FA و محدودیتِ IP ندارد.
  • خطر: ادمین همه‌ی محتوا، پلن‌ها، کدهای تخفیف، سفارش‌ها و کاربرها را کنترل می‌کند.
  • رفع: محدود کردنِ /admin/ در nginx به IPهای تیم یا VPN؛ تغییرِ مسیرِ پیش‌فرض؛ ۲FA؛ رمزهای قوی و حسابِ staffِ جدا برای هر نفر.

۱۱. کدِ OTP بی‌هش در دیتابیس

  • چیست: OTPCode.code متنِ ساده است و در فهرستِ پنلِ ادمین (OTPCodeAdmin) دیده می‌شود. توکنِ تمدید (Session.refresh_token) هش‌شده است و این‌جا شامل نیست.
  • چرا باز مانده: تیم برای آزمایش بی‌پیامک یک ردیفِ OTPCode در پنل می‌سازد و کدش را می‌بیند.
  • خطر: کسی که پنل یا دیتابیس را ببیند، در چند دقیقه‌ی عمرِ هر کد می‌تواند با آن وارد شود.
  • رفع: ذخیره‌ی هش و حذفِ ستون از فهرستِ پنل، همراه با راهِ دیگری برای ورودِ آزمایشی.

۱۲. هزینه‌ی دستیارِ AI

  • چیست: هر کاربرِ بی‌اشتراک روزی ۲ سؤالِ رایگان دارد (AI_DAILY_QUOTA_FREE) و مشترک ۴؛ ۲۰ در ساعت. هر شماره‌ی تازه ۲ سؤالِ رایگان می‌گیرد و ساختنِ شماره با سقفِ پیامک (بند ۲۴) محدود است. سقفِ روزانه‌ی کلِ سامانه (AI_GLOBAL_DAILY_MAX، ۳۰۰۰) بیشترین خرجِ روزانه را محدود می‌کند. طولِ سؤال حداکثر ۲۰۰۰ نویسه است.
  • خطر: با چند IP و چند شماره، تا سقفِ سراسری خرج می‌شود (~۲ میلیون تومان در روز) و سقفِ سراسری برای کاربرانِ واقعی هم پر می‌شود.
  • رفع: هشدارِ فعال (نه فقط در /api/health) وقتی aiProvider.state برابرِ quota_exhausted شد یا سقفِ سراسری از نیمه گذشت؛ سهمیه‌ی کمتر برای حسابِ تازه‌ساخته.

۱۵. دسترسیِ سرور

  • چیست: ورود با رمز به حسابِ مشترکِ administrator با sudo (ALL : ALL) ALL و عضوِ docker و lxd، روی ماشینی که Gitea و ایمیلِ شرکت هم رویش است. رمز یک بار در گفتگو با agent گذاشته شده و روی دسکتاپِ صاحبِ پروژه در یک فایلِ متنی نگه داشته می‌شود.
  • خطر: هر نشتِ رمز یعنی دسترسیِ root به کلِ ماشینِ شرکت، نه فقط این پروژه. ۲۰۲۶-۱۰-۰۴: PasswordAuthentication yes در sshd_config و پورتِ ۲۲ از اینترنت باز است؛ پس رمز از هر جای اینترنت حدس‌زدنی است (fail2ban سنجیده نشد).
  • رفع (ادمینِ سرور): کلیدِ SSH به‌جای رمز، تعویضِ رمز، حسابِ جدا برای این پروژه با دسترسیِ فقط به ~/mathapp-v2 و یک خطِ NOPASSWD فقط برای systemctl restart zareian-mathapp؛ پاک کردنِ فایلِ رمز از دسکتاپ (Keychain جایگزین است).

۱۶. نگه‌داریِ توکن در فرانت

  • چیست: کلاینتِ فرانت (src/api/index.ts در مخزنِ فرانت) توکن و refresh را در localStorage نگه می‌دارد.
  • خطر: هر XSS در سایت هر دو را می‌خواند.
  • رفع: CSPِ سخت‌گیرانه روی math.tadrisino2.ir حالا هست (جدولِ حفاظ‌ها) و اثرِ XSS را کم می‌کند؛ مانده: refresh در کوکیِ HttpOnly.

۲۴. پر کردنِ سقفِ سراسریِ پیامک

  • چیست: OTP_SMS_GLOBAL_DAILY_MAX (روی سرور ۲٬۰۰۰ در ۲۴ ساعت؛ پیش‌فرضِ کد ۳۰۰) جلوی خرجِ بی‌سقفِ پیامک را می‌گیرد. هر ناشناسی با درخواستِ کد برای شماره‌های تصادفی آن را پر می‌کند؛ از یک IP بی‌جعل ساعتی ۱۰۰ درخواست؛ با چند IP سقف پر می‌شود.
  • بازتولید شد: ۳۰۰ درخواست پذیرفته و ۳۰۰ پیامک فرستاده شد؛ بعد درخواستِ کاربرِ واقعی ۵۰۳.
  • خطر: هیچ ثبت‌نامِ تازه، ورودِ دوباره یا حذفِ حسابی ممکن نیست؛ هزینه‌ی ۳۰۰ پیامک در روز.
  • رفع (تصمیمِ طراحی): بعد از گذشتن از یک حد، CAPTCHA یا مانعِ مشابه پیش از ارسال؛ شمارشِ سقف بر اساسِ IP/شبکه؛ هشدار در نیمه‌ی سقف.

۲۵. بستنِ ورودِ یک شماره‌ی مشخص

  • چیست: ۱۰ درخواستِ کد در ~۲ ساعت سقفِ روزانه‌ی آن شماره را پر می‌کند (صاحبش تا ۲۴ ساعت کد نمی‌گیرد)، و ۵ حدسِ غلطِ هر کسی کدِ تازه‌ی او را باطل می‌کند. هر دو بازتولید شد.
  • رفع (تصمیمِ طراحی): کد به درخواست‌کننده گره بخورد (درخواستِ کد یک شناسه‌ی موقت برگرداند که ورود لازمش دارد) و حدسِ غلط برای هر درخواست‌کننده جدا شمرده شود.

۲۷. کلیدِ جواب با حسابِ دوم، امتیازِ دوباره

  • چیست: جوابِ غلط correctOptionId را برمی‌گرداند و برای همه یکی است؛ پایانِ درس و پایانِ جزیره هر کدام جدا امتیاز می‌دهند. یک حسابِ دومِ دورریختنی کلید را درمی‌آورد و حسابِ اصلی همه را درست می‌زند (۴ سوال، ۸۰ امتیاز). رتبه‌بندی و آمارِ درصدِ غلط آلوده می‌شوند.
  • رفع (تصمیمِ محصولی): امتیازِ هر سوال یک بار؛ گزینه‌ی درست فقط بعد از پایانِ آزمون.

۲۸. توکنِ تمدیدِ دزدیده‌شده

  • چیست: استفاده‌ی دوباره از یک توکنِ تمدیدِ باطل‌شده فقط ۴۰۱ می‌گیرد؛ اگر دزد اول تمدید کند، صاحبِ حساب بیرون می‌افتد و دزد می‌ماند. «خروج از همه‌ی دستگاه‌ها» هم نیست.
  • چرا باز مانده: در دورانِ ارائه حساب‌های دمو را چند نفر هم‌زمان به کار می‌بردند و باطل کردنِ زنجیره بیرونشان می‌انداخت. آن حساب‌ها پاک شده‌اند؛ مانعی نمانده، فقط نوشته نشده است.
  • رفع: شناسه‌ی خانواده روی Session؛ با استفاده‌ی دوباره کلِ خانواده باطل شود؛ گزینه‌ی خروج از همه‌جا.

۲۹. پیشنهادِ ثبت‌نام بعد از حذفِ حساب

  • چیست: پیشنهادِ خودکارِ ثبت‌نام به user_id گره خورده است؛ حذفِ حساب و ورودِ دوباره با همان شماره یک کاربرِ تازه می‌سازد و پیشنهاد دوباره داده می‌شود. فقط وقتی اثر دارد که یک Offerِ ثبت‌نام فعال باشد.
  • رفع: گره زدنِ پیشنهادِ ثبت‌نام به هشِ شماره.

۳۰. مسیرهای بی‌throttle

  • وضع: با پیمایشِ resolverِ جنگو هر مسیرِ /api/ که throttle_scope نداشت فهرست شد. پانزده مسیر سقف گرفتند (جدولِ حفاظ‌ها: content/tests_read_throttles.py). دو مسیر عمداً ماندند چون با X-Internal-Key محافظت می‌شوند و عمومی نیستند: rag/chunks و payment/orders (POST). /api/docs/ و /api/redoc/ هم سقفِ جدا ندارند (صفحه‌ی ایستا؛ بند ۵).
  • نسنجیده: رفتارِ سقفِ read (۱۲۰ در دقیقه) روی ترافیکِ واقعی؛ پشتِ IPِ مشترکِ مدرسه یا CGNAT ممکن است کاربرِ عادی هم به آن برسد (به بندِ «نرخ‌های throttle» در چک‌لیستِ لانچ نگاه کنید).

۳۱. لپ‌تاپِ توسعه روی شبکه‌ی محلی

  • چیست: mobile-preview/serve.py عمداً روی کلِ شبکه گوش می‌دهد (برای گوشیِ روی همان وای‌فای) و /api/ را به runserverِ محلی می‌فرستد؛ در DEBUG کدِ 0000 هر شماره‌ای را وارد می‌کند و کلیدِ GapGPTِ لپ‌تاپ همان حسابِ سرور است. (docker compose فقط روی 127.0.0.1 باز است و کلیدِ سرویسِ داخلی دیگر پیش‌فرض ندارد.)
  • رفع: روی شبکه‌ی عمومی (کافه، مدرسه) اجرا نشود؛ برای توسعه کلیدهای جدای GapGPT و Qdrant.

حفاظ‌هایی که هست (تا دوباره باز نشوند)

هر تغییری در این مسیرها باید تست‌های نام‌برده را سبز نگه دارد. جزئیاتِ پرداخت و تخفیف در docs/architecture.md.

حفاظ تست
ورودِ مهمان وجود ندارد؛ مدلِ User ستونِ is_guest/device_id ندارد و migrationِ حذفشان تا مهمانی مانده باشد می‌شکند accounts/migrations/0008_remove_guest_login.py
پلنِ هر خرید روی سفارش؛ خرید اشتراکِ فعال را دست نمی‌زند؛ یک کدِ تخفیف پیش از درگاه؛ امتیاز فقط از جوابِ ثبت‌شده payment/tests_audit_fixes.py، accounts/tests_audit_fixes.py
لینکِ پرداخت (/api/payment/orders/{id}/pay) فقط با امضای کوتاه‌عمرِ همراهِ paymentUrl باز می‌شود payment/tests.py (PaymentRedirectViewTests)
توکنِ تمدید فقط به‌صورتِ هشِ SHA-256 ذخیره می‌شود accounts/tests.py (RefreshTokenStoredHashedTests)
/api/health بی‌کلید فقط ok/error می‌دهد؛ جزئیات با هدرِ X-Internal-Key (کلید در .env سرور). بی‌کلیدِ تنظیم‌شده هیچ‌کس جزئیات نمی‌بیند config/tests.py (HealthIsMinimalForThePublicTests)
Swagger، ReDoc و schema فقط برای ادمینِ واردشده به /admin/ config/tests.py (ApiDocsRtlShellTests.test_docs_are_closed_to_anonymous_visitors)
جدولِ رتبه‌بندی فقط برای کاربرِ واردشده progress/tests.py (LeaderboardPeriodValidationTests)
شماره‌ی موبایل نرمال می‌شود؛ cooldown، سقفِ روزانه‌ی هر شماره و سقفِ کلِ پیامک در دیتابیس شمرده می‌شوند accounts/tests_otp_limits.py
OTPِ ۶ رقمی با ۵ حدس برای هر کد؛ خروج از حساب توکن‌های قبلی را باطل می‌کند accounts/tests_audit_fixes.py
قلبِ صفر جوابِ سوال را می‌بندد؛ چکش داخلِ تراکنش است (بی‌جواب‌ماندن و جزیره‌ی قفل جداگانه در ردیفِ بعدی بسته شده‌اند) content/tests_hearts_gate.py
select_for_update مؤثر (قفلِ ردیفِ Postgres؛ روی SQLiteِ محلی transaction_mode: IMMEDIATE)؛ مقایسه‌ی زمان‌ثابتِ X-Internal-Key payment/tests_audit_fixes.py
deploy.sh پیش از migrate بکاپِ db-before-deploy-* می‌گیرد (pg_dump -Fc روی Postgres) و اگر نشد migrate را اجرا نمی‌کند مرحله‌ی «بکاپِ دیتابیس» در خروجیِ هر دیپلو
دیتابیس Postgresِ اختصاصی است و هر تغییرش لحظه‌ای روی سرورِ دیگری (۲۴۷) کپی می‌شود، با نسخه‌ی کاملِ روزانه و بازیابی تا هر لحظه از ۷ روزِ اخیر؛ قطعیِ ۲۴۷ اپ را نمی‌خواباند و بیش از ۱ گیگ روی دیسکِ ۲۴۹ جمع نمی‌شود (docs/operations.md «بکاپ و بازیابی») config/tests_offsite_backup.py برای هشدارِ /api/health؛ روی سرور: بازیابی روی ۲۴۷ با شمارش‌های برابرِ ۲۴۹ و یک ردیفِ آزمایشی که ۲ ثانیه بعد آن‌جا بود
.env، کلیدِ امضا، لاگ‌ها و بکاپ‌ها فقط برای صاحبِ سرویس خواندنی‌اند (مرحله‌ی «مجوزِ فایل‌های داده» در deploy.sh) روی سرور sudo -u nobody ls ~/mathapp-v2/.deploy-backups باید خطای دسترسی بدهد
پانزده مسیرِ خواندنی و نوشتنیِ کم‌خطر سقفِ نرخ دارند (بانکِ سوال و intro بی‌ورود، progress/nodes، user/hearts، اعلان، تیکت، پلن‌ها، وضعیتِ اشتراک، سفارش، پیشنهادها، affiliate، تاریخچه و فیدبکِ دستیار، خروج). با نرخِ ۳ در دقیقه درخواستِ چهارم ۴۲۹ می‌گیرد content/tests_read_throttles.py
ساختنِ دستیِ OTPCode و Session در پنل فقط برای ابرکاربر (کارمندِ عادی ۴۰۳) accounts/tests_security_review.py (StaffCannotMintLoginsTests)
پایانِ درس و پایانِ آزمونِ جزیره جوابِ ثبت‌شده به همه‌ی سوال‌ها می‌خواهند (۴۰۹ answers_missing)، روی جزیره‌ی قفل ۴۰۳ node_locked می‌دهند (همان قاعده‌ی نقشه)، جزیره یا درسِ بی‌سوال چیزی برای جواب‌دادن ندارد و اگر باز است بی‌امتیاز رد می‌شود (قفل بودنش هنوز چک می‌شود)، و تعدادِ سوالِ آزمون را کلاینت نمی‌تواند کمتر از آزمونِ استانداردِ ۴ تایی بگوید (ستاره‌ی دروغین) content/tests_finish_requires_answers.py، content/tests_node_completion.py
کلیدِ امضای توکن تصادفی و فقط روی سرور است (.django_secret_key، ۶۰۰)؛ نه در .env، نه در یونیت؛ توکنِ ساخته‌شده با کلیدِ داخلِ ریپو 401 می‌گیرد؛ DJANGO_DEBUG هم در یونیت نیست دستی (۲۰۲۶-۱۰-۰۷): توکنِ جعلی 200 ← 401؛ تستِ واحد ندارد
با CACHE_URL ست‌شده، قطعیِ Redis API را ۵۰۰ نمی‌کند (fail-open، ERROR در لاگ، health ۵۰۳؛ شرح در config/cache.py). روی سرور به Redisِ اختصاصیِ خودمان (zareian-redis) ست است، پس سقف‌های مبتنی بر کش بینِ workerها مشترک‌اند؛ بک‌اندِ ResilientRedisCache روی سرور دیده شد، رگبارِ موازی برای سنجیدنِ سقفِ واحد زده نشد config/tests_cache_resilience.py (۱۱ تست، با redis-serverِ محلی که وسطِ کار می‌میرد)
بدنهٔ درخواست بزرگ‌تر از ≈ ۲٫۵ مگابایت 413 می‌گیرد و ERRORِ لاگ نمی‌سازد (پیش‌تر 500 + traceback در لاگ؛ RequestDataTooBig). nginx هنوز تا ۱۰۰ مگابایت بافر می‌کند (بند ۱۸) config/tests_request_size.py
کتابخانه‌ها بدونِ آسیب‌پذیریِ شناخته‌شده (urllib3>=2.8.0، python-dotenv 1.2.2) پایگاهِ OSV روی pip freeze
actionهای لغو/بازپرداختِ سفارش، علامتِ «بررسی‌شده» و لینکِ بازار اجازه‌ی تغییر می‌خواهند؛ کارمندِ فقط‌خواندنی (Supervisor) اجرایشان نمی‌کند payment/tests_security_review.py، content/tests_security_review.py
ورودِ فایلِ سوال/برنامه‌ی درسی اجازه‌ی افزودن (و برای جایگزینی، حذف) می‌خواهد؛ مرورگرِ محتوا اجازه‌ی دیدنِ سوال content/tests_security_review.py
حذفِ حساب سابقه‌ی کدِ تخفیف، پیشنهادها و کدهای پیامکیِ شماره را نگه می‌دارد؛ حسابِ دمو حذف نمی‌شود accounts/tests_security_review.py
صافیِ اطلاعاتِ تماس در نامِ نمایشی (شماره با هر جداکننده، آیدیِ شبکه‌ی اجتماعی) accounts/tests_security_review.py
کلیدِ کاوه‌نگار و توکنِ خریدِ بازار در لاگِ خطای شبکه نمی‌آیند accounts/tests_security_review.py
برگشت از زرین‌پال بی‌Authority ۴۰۰؛ خریدِ دومِ بازار روی سفارشِ پرداخت‌شده ۴۰۹؛ آنچه داده یا پس گرفته می‌شود از خودِ سفارش است، نه پلنِ فعلی payment/tests_security_review.py
شکلِ خرابِ مدل جواب را رایگان نمی‌کند؛ علامتِ «خارج از ریاضی» در هر جای استریم جواب را رد می‌کند و در کش نمی‌رود؛ بستنِ صفحه جوابِ ناقص را ثبت می‌کند؛ collectionِ بیرون از KNOWN_COLLECTIONS جستجو نمی‌شود ai_tutor/tests_security_review.py
ورودیِ عددیِ غیرِ ASCII (مثلِ ²) ۴۰۰ است نه ۵۰۰؛ متنِ تیکت/گزارشِ باگ حداکثر ۵۰۰۰ نویسه content/tests_security_review.py
INTERNAL_SERVICE_KEY در هیچ حالتی پیش‌فرض ندارد payment/tests.py (test_debug_mode_has_no_default_internal_key)
کلیدِ امضای خودساخته (.django_secret_key) یک بار و اتمی ساخته می‌شود؛ سه workerِ هم‌زمان یک کلید دارند دستی: سه پروسه‌ی هم‌زمان که config.settings را import می‌کنند، بی‌فایلِ کلید؛ ۲۰ از ۲۰ یک کلید
فرانت: شماره‌ی سفارشِ برگشتی از درگاه فقط عدد است، مسیرها encode می‌شوند و «پرداخت موفق» فقط از پاسخِ سرور می‌آید؛ دو تبِ هم‌زمان با تمدیدِ توکن خارج نمی‌شوند مخزنِ فرانت: src/app/launchParams.ts، src/api/http.ts
سفارش و تراکنش در پنل کاملاً فقط‌خواندنی‌اند (کارمندِ لغو/بازپرداخت مبلغ یا اعتبارِ سفارش را عوض نمی‌کند) و از پنل ساخته نمی‌شوند payment/tests_security_review.py (OrderAdminMoneyFieldsReadOnlyTests)
ردِ بازار توکنِ خرید را نمی‌سوزاند؛ سفارشِ بازپرداخت‌شده توکنِ تازه نمی‌پذیرد payment/tests_security_review.py (BazaarRejectedVerifyDoesNotBurnTokenTests)
کدِ تخفیفِ وسطِ رفتن به درگاه، مبلغِ authority را با سفارش ناهمخوان نمی‌کند payment/tests_security_review.py (DiscountDuringPaymentRequestTests)
پرداختِ بسته‌ای که پلنش پاک شده از عددهای ثبت‌شده‌ی سفارش اعتبار می‌دهد (و اگر نداشت ERROR لاگ می‌شود)؛ پلن فقط با IRT ذخیره می‌شود subscription/tests_security_review.py (PaidPackWithDeletedPlanTests، PlanCurrencyIsTomanOnlyTests)
توکنِ بازار (refresh_token، pending_state) با فیلترِ querystringِ پنل حرف‌به‌حرف درنمی‌آید payment/tests_security_review.py (BazaarTokenNotGuessableByAdminFilterTests)
شماره‌ی کاربر را فقط ابرکاربر عوض می‌کند؛ حساب‌های کارمند و گروه‌ها فقط با ابرکاربر ساخته/تغییر می‌کنند accounts/tests_security_review.py (StaffCannotSwapLoginPhoneTests، StaffAccountsAdminSuperuserOnlyTests)
لاگِ تحویلِ کمپین اجازه‌ی view می‌خواهد (هر کارمندی نمی‌بیند) campaigns/tests_security_review.py
هر رقمِ یونیکد (تمام‌عرض، ریاضی…) در شماره به لاتین برمی‌گردد؛ کدِ غیرِ ASCII، پایه‌ی ناموجود یا عظیم و تاریخِ تولدِ با «²» ۴۰۰ است نه ۵۰۰؛ صافیِ نامِ نمایشی ارقامِ یونیکد و جداکننده‌ی دلخواه را می‌گیرد accounts/tests_security_review.py (UnicodeInputsAreNormalisedOrRejectedTests)
روی سرور هیچ شماره‌ای با کدِ ورودِ ثابت نیست (TEST_LOGIN_PHONES خالی)؛ حساب‌های دمو و داده‌شان پاک شدند؛ اگر کسی آن را دوباره پر کند /api/health هشدار می‌دهد config/tests.py (هشدارِ health)؛ روی سرور: /api/health بی‌هشدار
فقط ArvanCloud به nginxِ دو سایتِ ما می‌رسد؛ درخواستِ مستقیم به IPِ سرور ۴۰۳ (ops/nginx/zareian-arvan-only.conf) از بیرون: پورتِ ۸۰ و ۴۴۳ مستقیم ۴۰۳، از راهِ آروان ۲۰۰
فرانت CSP (فقط منابعِ خودمان، اسکریپتِ درون‌خطی با هش)، frame-ancestors 'none'، X-Frame-Options، nosniff، HSTS و Permissions-Policy دارد؛ سقفِ بدنه‌ی nginxِ API ۲۰ مگابایت curl -sI؛ اپ در Chrome با و بی ورود: صفر خطای CSP
X-Forwarded-For فقط وقتی پذیرفته می‌شود که peerِ TCP (هدرِ X-Real-IPِ nginx) در TRUSTED_PROXY_NETWORKS (رنج‌های ArvanCloud) باشد؛ درخواستِ مستقیم به IP با XFFِ جعلی دیگر سقف‌های IP-محور را دور نمی‌زند config/tests_trusted_proxy.py؛ روی سرور: ۶۵ درخواستِ health با XFFِ چرخان، بعد از ۶۰ تا ۴۲۹
/api/home?gradeId= ناموجود کش نمی‌شود (کشِ پُر شمارنده‌ی سقفِ ورودِ ادمین را پاک می‌کرد)؛ /api/progress/nodes یک کوئری است و unit عظیم ۴۰۰ content/tests_security_review.py (JunkGradeIdsDoNotFlushCacheTests، ProgressNodesInputAndQueriesTests)