- ops/nginx/: the two nginx sites as they now run on 249. Only ArvanCloud edges reach the app (direct hits on the IP get 403, so Arvan's DDoS/WAF can't be skipped); the frontend gets CSP (own sources only, the one inline script by hash), X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy, HSTS; API body limit 100M -> 20M plus HSTS. Checked from outside and in Chrome (all tabs and an island quiz, logged in): no CSP errors. - Demo accounts (users 11-15, 25) and TEST_LOGIN_PHONES removed from the server; the health warning for fixed-code logins now has a test and says how to test instead (an OTPCode made in the admin). - SMS daily cap on the server is 2000 (launch of ~600 users). - CLAUDE.md: never run tests with --parallel on macOS, always --noinput (both hang forever without an error). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
26 KiB
پیش از پروداکشنِ واقعی: چیزهایی که عمداً باز ماندهاند
آخرین بازبینی با کد و سرورِ زنده: ۲۰۲۶-۱۰-۱۳ (۲۱ مهر ۱۴۰۵): حسابهای دمو، 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، اعتبارِ 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حالا هست (جدولِ حفاظها) و اثرِ 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 مؤثر با 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) |
روی سرور هیچ شمارهای با کدِ ورودِ ثابت نیست (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) |