feat: enhance security audit and CSP configuration for admin interface
This commit is contained in:
@@ -23,6 +23,8 @@
|
||||
|
||||
### وضعیت رفع (۲۰۲۶-۰۷-۱۹)
|
||||
- **۱ — CSP:** `src/Shared/EventSubscriber/AdminCspSubscriber.php` افزوده شد؛ CSP فقط روی `/admin/*`. تأیید زنده: هدر `content-security-policy` روی `/admin` حاضر است. تست: `tests/Shared/AdminCspSubscriberTest.php` (۲ تست سبز).
|
||||
- **پیگیری (۲۰۲۶-۰۷-۲۳):** دو خطای کنسول CSP رفع شد. (الف) فونت Vazirmatn از CDN گوگل حذف شد (`templates/admin/index.html.twig`)؛ فونت از قبل بهصورت لوکال با `@fontsource` باندل میشود، پس CDN هم مسدود و هم زائد بود. (ب) هاستهای نقشهٔ کلینیک به CSP اضافه شد: `img-src` += `*.tile.openstreetmap.org unpkg.com`، `connect-src` += `nominatim.openstreetmap.org *.tile.openstreetmap.org`. بدون اینها نقشهٔ Leaflet در `ClinicDetailPage` خالی میماند. تست CSP به ۹ assertion رسید.
|
||||
- **مرتبط (غیرامنیتی):** باگ Service Worker در `public/sw.js` رفع شد — هندلر navigate در حالت آفلاین وقتی shell کش نشده بود `undefined` به `respondWith` میداد → خطای `Failed to convert value to 'Response'`؛ حالا fallback واقعی (Response 503) برمیگرداند.
|
||||
- **۳ — APP_SECRET:** مقدار واقعی از `.env.dev` برداشته و placeholder شد (مقدار واقعی باید در `.env.local` خارج از git بماند + rotate).
|
||||
- **۴ — cookie flags:** `config/packages/framework.yaml` → `cookie_secure: true`، `cookie_samesite: lax`، `cookie_httponly: true`. لینت YAML سبز، ۵۶ تست Shared/Smoke سبز.
|
||||
- **۲ — deps:**
|
||||
@@ -97,9 +99,9 @@ final class AdminCspSubscriber implements EventSubscriberInterface
|
||||
"default-src 'self'; "
|
||||
. "script-src 'self'; "
|
||||
. "style-src 'self' 'unsafe-inline'; " // Tailwind/CKEditor inline styles
|
||||
. "img-src 'self' data: blob:; "
|
||||
. "img-src 'self' data: blob: https://*.tile.openstreetmap.org https://unpkg.com; " // OSM tiles + Leaflet markers (clinic map)
|
||||
. "font-src 'self' data:; "
|
||||
. "connect-src 'self'; "
|
||||
. "connect-src 'self' https://nominatim.openstreetmap.org https://*.tile.openstreetmap.org; " // nominatim geocode + OSM tiles
|
||||
. "frame-ancestors 'none'; base-uri 'self'; object-src 'none'"
|
||||
);
|
||||
}
|
||||
|
||||
+13
-2
@@ -66,13 +66,24 @@ self.addEventListener('fetch', (event) => {
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match('/admin'))
|
||||
// Offline: fall back to the cached shell. If it was never cached, return a
|
||||
// real offline Response — respondWith(undefined) throws "Failed to convert
|
||||
// value to 'Response'" and surfaces as a network error in the console.
|
||||
.catch(async () =>
|
||||
(await caches.match('/admin')) ||
|
||||
new Response('آفلاین هستید', {
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
||||
})
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything else → Network First with cache fallback
|
||||
event.respondWith(
|
||||
fetch(request).catch(() => caches.match(request))
|
||||
fetch(request).catch(async () =>
|
||||
(await caches.match(request)) || Response.error()
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -40,9 +40,11 @@ final class AdminCspSubscriber implements EventSubscriberInterface
|
||||
. "script-src 'self'; "
|
||||
. "worker-src 'self' blob:; " // ALTCHA proof-of-work solver runs in blob: Web Workers
|
||||
. "style-src 'self' 'unsafe-inline'; " // Tailwind / CKEditor inline styles
|
||||
. "img-src 'self' data: blob:; "
|
||||
// OpenStreetMap tiles + unpkg Leaflet marker icons render the clinic-location map
|
||||
. "img-src 'self' data: blob: https://*.tile.openstreetmap.org https://unpkg.com; "
|
||||
. "font-src 'self' data:; "
|
||||
. "connect-src 'self'; "
|
||||
// nominatim geocoding + OSM tile fetches for the clinic-location map
|
||||
. "connect-src 'self' https://nominatim.openstreetmap.org https://*.tile.openstreetmap.org; "
|
||||
. "frame-ancestors 'none'; "
|
||||
. "base-uri 'self'; "
|
||||
. "object-src 'none'"
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="ClinicPro">
|
||||
<link rel="apple-touch-icon" href="/logo.svg">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100..900&display=swap" rel="stylesheet">
|
||||
{# Vazirmatn is bundled locally via @fontsource in styles.css; no external font CDN (CSP-safe). #}
|
||||
{{ encore_entry_link_tags('admin') }}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -23,6 +23,11 @@ class AdminCspSubscriberTest extends WebTestCase
|
||||
// ALTCHA solves its proof-of-work inside blob: Web Workers; without this
|
||||
// directive the login captcha errors out (worker-src falls back to script-src).
|
||||
self::assertStringContainsString("worker-src 'self' blob:", $csp);
|
||||
// The clinic-location map needs OSM tiles (img/connect), unpkg marker icons (img)
|
||||
// and nominatim geocoding (connect); without these the map is blank + console errors.
|
||||
self::assertStringContainsString('https://*.tile.openstreetmap.org', $csp);
|
||||
self::assertStringContainsString('https://unpkg.com', $csp);
|
||||
self::assertStringContainsString('https://nominatim.openstreetmap.org', $csp);
|
||||
}
|
||||
|
||||
public function testNonAdminResponseDoesNotGetTheAdminCsp(): void
|
||||
|
||||
Reference in New Issue
Block a user