stop counting the frontend developer's port numbers
فهرستِ ثابتِ CORS (۳۰۰۰ و ۵۱۷۳) روزی شکست که Vite دید ۵۱۷۳ اشغال است و بیسروصدا رفت روی ۵۱۷۴ — رفتارِ پیشفرضِ خودش. فرانتکار خطای CORS گرفت، از سمتِ سرور هیچ ردی در هیچ لاگی نبود (خطا فقط در کنسولِ مرورگر دیده میشود)، و چون همزمان با تغییراتِ بکاند شد به اشتباه گردنِ ما افتاد. هزینهاش یک ویرایشِ .env و یک ریاستارتِ پروداکشن بود، برای چیزی که اصلاً به پروداکشن ربطی نداشت. بهجای شمردنِ پورتها، الگوی «هر پورتی روی خودِ ماشینِ توسعهدهنده» پذیرفته شد. سطحِ حمله اضافه نمیکند: توکنِ ما در هدر است نه کوکی و CORS_ALLOW_CREDENTIALS خاموش، پس صفحهای روی localhostِ مهاجم هیچ اعتبارنامهای از مرورگرِ قربانی نمیگیرد؛ CORS هم اصلاً سدِ سرور-به-سرور نیست. تستها هم پوشش میدهند که localhost.evil.com و math.tadrisino2.ir.evil.com همچنان رد شوند. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -403,3 +403,51 @@ class SecureCookieTests(SimpleTestCase):
|
||||
"""بدونِ این، کوکیِ امن پشتِ nginx هرگز ست نمیشود و ورود میشکند."""
|
||||
self.assertEqual(settings.SECURE_PROXY_SSL_HEADER,
|
||||
('HTTP_X_FORWARDED_PROTO', 'https'))
|
||||
|
||||
|
||||
class CorsDevPortTests(TestCase):
|
||||
"""
|
||||
پورتِ سرورِ توسعه نباید در فهرستِ CORS شمرده شود.
|
||||
|
||||
فهرستِ ثابتِ قبلی (۳۰۰۰ و ۵۱۷۳) روزی شکست که Vite دید ۵۱۷۳ اشغال است و
|
||||
بیسروصدا رفت روی ۵۱۷۴. فرانتکار خطای CORS گرفت، از سمتِ سرور هیچ ردی در
|
||||
هیچ لاگی نبود، و چون همزمان با تغییراتِ بکاند شد به اشتباه گردنِ ما افتاد.
|
||||
هزینهاش یک ویرایشِ .env و یک ریاستارتِ پروداکشن بود، برای چیزی که اصلاً
|
||||
به پروداکشن ربطی نداشت.
|
||||
"""
|
||||
|
||||
def _preflight(self, origin):
|
||||
return self.client.options(
|
||||
'/api/grades',
|
||||
HTTP_ORIGIN=origin,
|
||||
HTTP_ACCESS_CONTROL_REQUEST_METHOD='GET',
|
||||
)
|
||||
|
||||
def test_any_localhost_port_is_allowed_so_a_new_dev_port_never_breaks_again(self):
|
||||
for origin in ('http://localhost:5173', 'http://localhost:5174',
|
||||
'http://localhost:3000', 'http://127.0.0.1:8080'):
|
||||
with self.subTest(origin=origin):
|
||||
resp = self._preflight(origin)
|
||||
self.assertEqual(
|
||||
resp.headers.get('access-control-allow-origin'), origin)
|
||||
|
||||
def test_the_deployed_frontend_origin_still_works(self):
|
||||
resp = self._preflight('https://math.tadrisino2.ir')
|
||||
self.assertEqual(resp.headers.get('access-control-allow-origin'),
|
||||
'https://math.tadrisino2.ir')
|
||||
|
||||
def test_a_stranger_origin_is_still_refused(self):
|
||||
# الگو فقط localhost است، نه هر دامنهای؛ و «localhost» بهعنوانِ
|
||||
# زیررشته در دامنهی دیگری نباید رد شود.
|
||||
for origin in ('https://evil.example.com',
|
||||
'http://localhost.evil.com',
|
||||
'https://math.tadrisino2.ir.evil.com'):
|
||||
with self.subTest(origin=origin):
|
||||
resp = self._preflight(origin)
|
||||
self.assertIsNone(resp.headers.get('access-control-allow-origin'))
|
||||
|
||||
def test_credentials_stay_off(self):
|
||||
# توکنِ ما در هدر است نه کوکی؛ روشن کردنِ این فقط سطحِ حمله اضافه میکند
|
||||
# و پذیرشِ هر پورتِ localhost را واقعاً خطرناک میکرد.
|
||||
from django.conf import settings
|
||||
self.assertFalse(settings.CORS_ALLOW_CREDENTIALS)
|
||||
|
||||
Reference in New Issue
Block a user