Files
MathApp/docs/security-before-production.md
Zareian feaa5eb540 Postgres connection pool, clean 503 under overload, time indexes, 4 workers
Postgres here allows 60 connections, but each of the 96 gunicorn threads held its
own persistent one: at 100 concurrent users (local Postgres with the same limit)
21% of requests were 500 "remaining connection slots are reserved". Now each worker
has a psycopg pool (8), so the same load gives 0 errors and 714 req/s (was 460).

- A full pool answers 503 + Retry-After (DRF handler and middleware), not 500.
- The AI tutor and the OTP SMS call return their connection before the network wait.
- statement_timeout 30 s, idle-in-transaction 60 s; bad pool settings and the old
  DB_CONN_MAX_AGE stop startup instead of failing every request.
- /api/health dbPool: per-worker pool stats and workers x pool + 6 vs max_connections.
- Indexes on the columns global counters filter by time (weekly leaderboard,
  global SMS cap, global AI cap); the leaderboard recomputes once per expiry.
- Worker count moves into gunicorn.conf.py (4 on a shared 8-core box).
- security doc: Redis item closed (server uses our own Redis since 2026-10-08).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:21:35 +03:30

29 KiB
Raw Blame History

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

آخرین بازبینی با کد: ۲۰۲۶-۱۰-۰۷ (۱۵ مهر ۱۴۰۵)؛ با سرورِ زنده: ۲۰۲۶-۱۰-۰۴ برای بقیه‌ی بندها؛ آنچه ۱۰-۰۷ دیپلو شد از بیرون و با آزمونِ زنده سنجیده شد: کلیدِ امضا، سقفِ نرخ، پایانِ جزیره/درس، سلامت.

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

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

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


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

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

۲. حساب‌های دمو با کدِ ورودِ ثابت (TEST_LOGIN_PHONES)

  • چیست: ۷ شماره‌ی 09120000101 تا 09120000107 با کدِ ثابت در .env سرور. شماره‌ها در ریپو (docs/operations.md، CLAUDE.md) آمده‌اند؛ کدها فقط در راهنمای ارائه‌ی صاحبِ پروژه‌اند.
  • خطر: کسی که کد را از راهنما، اسکرین‌شات یا ارائه ببیند وارد آن حساب‌ها می‌شود. این کدها با OTP_MAX_FAILED_ATTEMPTS باطل نمی‌شوند (ردیفِ OTPCode ندارند)؛ فقط سقفِ per-workerِ تلاشِ ورود جلویشان است.
  • رفع: TEST_LOGIN_PHONES را از .env سرور پاک کنید و ری‌استارت کنید.
  • چطور بسنجید: /api/health دیگر هشدارِ «شماره‌های تستِ ورود با کدِ ثابت فعال‌اند» ندارد.

۳. داده‌ی دمو روی دیتابیسِ پروداکشن

  • چیست: کاربرهای ۱۰ تا ۱۵ و ۲۵ (حساب‌های دمو با کدِ ثابت، بند ۲) با درس، گفتگو، تیکت و سفارش روی دیتابیسِ پروداکشن ساخته شده‌اند. حسابِ ۱۰ («علی رضایی»، با ۷ سفارش) حسابِ نمایشیِ اصلی است.
  • خطر: داده‌ی ساختگی در آمار و جدولِ رتبه‌بندیِ واقعی.
  • رفع: پیش از کاربرِ واقعی با delete_users --ids … --yes (پیش از آن بکاپ) پاک شوند. purge_demo_data این‌ها را نمی‌گیرد (از مسیرِ واقعیِ viewها ساخته شده‌اند).
  • چطور بسنجید: /api/health → traffic.users فقط کاربرانِ واقعی را بشمارد.

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

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

۸. دسترسیِ مستقیم به پورتِ ۸۰ (دور زدنِ ArvanCloud)

  • چیست: http://78.38.35.249/api/health با Host: mathapi.tadrisino2.ir پاسخ می‌دهد (تأیید: HTTP 200). NUM_PROXIES=2 فرض می‌کند همیشه دو پروکسی جلوی ماست.

  • خطر: کسی که IP را بداند X-Forwarded-For را جعل می‌کند و محدودیت‌های IP-محور (OTP، ادمین) را دور می‌زند.

  • رفع (ادمینِ سرور): پورتِ ۸۰/۴۴۳ فقط برای رنج‌های ArvanCloud، یا set_real_ip_from + real_ip_header در nginx.

  • بازتولید شد ۲۰۲۶-۱۰-۰۴: curl -H 'Host: mathapi.tadrisino2.ir' -H 'X-Forwarded-For: 1.2.3.4' http://78.38.35.249/api/health جواب داد clientIp: 1.2.3.4. یعنی هر سقفِ IP-محور (ورودِ ادمین ۱۰ در ۱۵ دقیقه، درخواست و ورودِ OTP) با عوض کردنِ این هدر در هر درخواست دور زده می‌شود، و حفاظتِ DDoS/WAFِ آروان هم برای این مسیر وجود ندارد. سقف‌های per-phone و per-code که در دیتابیس شمرده می‌شوند متأثر نیستند. پورت‌های ۸۰ و ۴۴۳ هر دو باز بودند.

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

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

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

  • چیست: هر کاربر روزی ۲ سؤالِ رایگان دارد (AI_DAILY_QUOTA_FREE) و بعد فقط با اعتبارِ خریده‌شده؛ ۲۰ در ساعت. هر شماره‌ی تازه ۲ سؤالِ رایگان می‌گیرد و ساختنِ شماره با سقفِ پیامک (بند ۲۴) محدود است. سقفِ روزانه‌ی کلِ سامانه (AI_GLOBAL_DAILY_MAX، ۳۰۰۰) خرجِ کل را محدود می‌کند. طولِ سؤال حداکثر ۲۰۰۰ نویسه است.
  • خطر: با چند IP، اعتبارِ GapGPT در چند ساعت تمام می‌شود (اعتبار یک بار در ۲۰۲۶-۰۹-۲۵ به صفر رسید، البته به دلیلِ دیگر).
  • رفع: سقفِ سراسریِ روزانه برای کلِ اپ؛ دستیار فقط برای کاربرِ واردشده با شماره یا با سقفِ بسیار کمتر برای مهمان؛ هشدار وقتی aiProvider.state در /api/health برابرِ 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؛ در بلندمدت refresh در کوکیِ HttpOnly.

۲۲. هدرهای امنیتیِ فرانت

  • چیست: https://math.tadrisino2.ir/ فقط x-xss-protection دارد؛ نه CSP، نه X-Frame-Options/ frame-ancestors، نه HSTS، نه X-Content-Type-Options. API همه‌ی این‌ها را جز HSTS و CSP دارد.
  • خطر: صفحه‌ی اپ را می‌شود در iframeِ سایتِ دیگری گذاشت (clickjacking)؛ و چون توکن در localStorage است (مورد ۱۶)، نبودِ CSP اثرِ هر XSSِ احتمالی را کامل می‌کند. در خودِ فرانت سینکِ XSS پیدا نشد (تنها dangerouslySetInnerHTML روی SVGِ ثابتِ شکلک‌هاست).
  • رفع: در nginxِ mathapp-front یا در آروان: Content-Security-Policy با frame-ancestors 'none' و connect-src به دامنه‌ی API، و X-Content-Type-Options: nosniff.

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

  • چیست: OTP_SMS_GLOBAL_DAILY_MAX (۳۰۰ در ۲۴ ساعت) جلوی خرجِ بی‌سقفِ پیامک را می‌گیرد. هر ناشناسی با درخواستِ کد برای شماره‌های تصادفی آن را پر می‌کند؛ از یک 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 مؤثر با transaction_mode: IMMEDIATE؛ مقایسه‌ی زمان‌ثابتِ X-Internal-Key payment/tests_audit_fixes.py
deploy.sh پیش از migrate بکاپِ db-before-deploy-* می‌گیرد، integrity_check می‌کند و اگر نشد migrate را اجرا نمی‌کند مرحله‌ی «بکاپِ دیتابیس»: ۷ بار روی لپ‌تاپ و یک بار روی سرور (۶ مگابایت، integrity_check: ok)
دیتابیس Postgresِ اختصاصی است و هر تغییرش لحظه‌ای روی سرورِ دیگری (۲۴۷) کپی می‌شود، با نسخه‌ی کاملِ روزانه و بازیابی تا هر لحظه از ۷ روزِ اخیر؛ قطعیِ ۲۴۷ اپ را نمی‌خواباند و بیش از ۱ گیگ روی دیسکِ ۲۴۹ جمع نمی‌شود (docs/operations.md «بکاپ و بازیابی») config/tests_offsite_backup.py برای هشدارِ /api/health؛ روی سرور: بازیابی روی ۲۴۷ با شمارش‌های برابرِ ۲۴۹ و یک ردیفِ آزمایشی که ۲ ثانیه بعد آن‌جا بود
دیتابیس و بکاپ‌ها فقط برای صاحبِ سرویس خواندنی‌اند (مرحله‌ی «مجوزِ فایل‌های داده» در deploy.sh) با sudo -u nobody head -c 15 ~/mathapp-v2/db.sqlite3 روی سرور باید خطای دسترسی بدهد
پانزده مسیرِ خواندنی و نوشتنیِ کم‌خطر سقفِ نرخ دارند (بانکِ سوال و 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)
/api/home?gradeId= ناموجود کش نمی‌شود (کشِ پُر شمارنده‌ی سقفِ ورودِ ادمین را پاک می‌کرد)؛ /api/progress/nodes یک کوئری است و unit عظیم ۴۰۰ content/tests_security_review.py (JunkGradeIdsDoNotFlushCacheTests، ProgressNodesInputAndQueriesTests)