Security re-check and load test 2026-10-04: 32 threads, data file permissions, patched deps

- gunicorn gthread 8 -> 32 threads per worker: each assistant stream holds a thread for
  the whole answer; with 3x8, about 20 concurrent asks already queued ordinary page loads
  for seconds (up to 23 s at 50 asks locally, 7 s at 20 asks on the live server). With 3x32,
  75 concurrent asks kept page loads under 110 ms (laptop, simulated model and Qdrant)
- deploy.sh: new step closes the project dir to other accounts and sets the database and
  backups to 0600/0700 on every release; user `nobody` on the shared server could read
  db.sqlite3 and every backup
- requirements: python-dotenv 1.2.2, urllib3>=2.8.0 (known CVEs in the installed versions),
  gunicorn pinned to the server's version (was only installed by hand)
- security-before-production.md: items 19-23 (file permissions, DEBUG=1 still in the systemd
  unit, dependencies, frontend headers, guest AI cap per shared IP) and the reproduced
  X-Forwarded-For spoof through the origin IP
- model-prices.md: 0.183 cents per answer from 50 answers in the live load test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 14:39:11 +03:30
parent f441cfa96b
commit 2cf2d14dd4
6 changed files with 108 additions and 4 deletions

View File

@@ -171,6 +171,24 @@ if 'LocMem' in settings.CACHES['default']['BACKEND'] and not settings.DEBUG:
print(' ⚠ بدونِ Redis محدودیتِ نرخ بینِ workerها مشترک نیست (CACHE_URL).')
PYCHECK
# ─── ۳.۶ مجوزِ فایل‌های داده ───────────────────────────────────────────────
# این ماشین بینِ چند تیم مشترک است. تا ۲۰۲۶-۱۰-۰۴ دیتابیس و همه‌ی بکاپ‌ها 0644 بودند و
# پوشه‌ها قابلِ پیمایش؛ کاربرِ nobody روی سرور توانست سرِ db.sqlite3 و یک بکاپ را بخواند
# (شماره‌ها، refresh tokenهای بی‌هش، گفتگوها). در هر انتشار دوباره بسته می‌شود، چون بکاپِ
# دستیِ تازه دوباره با umaskِ پیش‌فرض (0644) ساخته می‌شود. nginx فقط به سوکتِ /run وصل
# می‌شود و از این پوشه چیزی نمی‌خواند (sites-enabled/mathapp، همان روز خوانده شد)؛ اگر
# سرویسِ دیگری این دیتابیس را مستقیم می‌خواند، از این به بعد خطای دسترسی می‌گیرد.
step "مجوزِ فایل‌های داده"
chmod o-rwx . || warn "مجوزِ پوشه‌ی پروژه عوض نشد."
for f in db.sqlite3 db.sqlite3-wal db.sqlite3-shm db.sqlite3-journal; do
if [ -e "$f" ]; then chmod 600 "$f"; fi
done
if [ -d .deploy-backups ]; then
chmod 700 .deploy-backups
find .deploy-backups -type f -exec chmod 600 {} +
fi
printf ' دیتابیس و بکاپ‌ها فقط برای %s خواندنی‌اند.\n' "$(id -un)"
# ─── ۴. دیتابیس ────────────────────────────────────────────────────────────
step "اعمالِ مهاجرت‌های دیتابیس"
python manage.py migrate --noinput || fail "migrate شکست خورد؛ سرویس ری‌استارت نشد."