API middleware: - SecurityHeaders now sets Content-Security-Policy from config/security.php - Default API policy: "default-src 'none'; frame-ancestors 'none'" - Supports report-only mode via CSP_REPORT_ONLY env var - Policy value configurable via CSP_POLICY env var Nginx deployment configs (deploy/nginx/): - security-headers.conf: shared headers for all server blocks - csp-api.conf: restrictive JSON-only policy for api.crewli.app - csp-spa.conf: SPA policy for app/admin (self + unsafe-inline styles) - csp-portal.conf: portal policy matching SPA Development: - CSP meta tags added to all three index.html files - Includes 'unsafe-inline' + 'unsafe-eval' for Vite HMR/loader script - Each app allows its own ws:// port for HMR websocket Resolves security finding A13-9. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
56 lines
2.6 KiB
HTML
56 lines
2.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<link rel="icon" href="/favicon.ico" />
|
|
<meta name="robots" content="noindex, nofollow" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Crewli Admin</title>
|
|
<!-- CSP for local development — mirrors production Nginx policy -->
|
|
<meta http-equiv="Content-Security-Policy"
|
|
content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' http://localhost:8000 ws://localhost:5173; frame-ancestors 'none'; form-action 'self'; base-uri 'self'">
|
|
<link rel="stylesheet" type="text/css" href="/loader.css" />
|
|
</head>
|
|
|
|
<body>
|
|
<div id="app">
|
|
<div id="loading-bg">
|
|
<div class="loading-logo">
|
|
<!-- SVG Logo -->
|
|
<svg width="86" height="48" viewBox="0 0 34 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M0.00183571 0.3125V7.59485C0.00183571 7.59485 -0.141502 9.88783 2.10473 11.8288L14.5469 23.6837L21.0172 23.6005L19.9794 10.8126L17.5261 7.93369L9.81536 0.3125H0.00183571Z"
|
|
fill="var(--initial-loader-color)" />
|
|
<path opacity="0.06" fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M8.17969 17.7762L13.3027 3.75173L17.589 8.02192L8.17969 17.7762Z" fill="#161616" />
|
|
<path opacity="0.06" fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M8.58203 17.2248L14.8129 5.24231L17.6211 8.05247L8.58203 17.2248Z" fill="#161616" />
|
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
d="M8.25781 17.6914L25.1339 0.3125H33.9991V7.62657C33.9991 7.62657 33.8144 10.0645 32.5743 11.3686L21.0179 23.6875H14.5487L8.25781 17.6914Z"
|
|
fill="var(--initial-loader-color)" />
|
|
</svg>
|
|
</div>
|
|
<div class=" loading">
|
|
<div class="effect-1 effects"></div>
|
|
<div class="effect-2 effects"></div>
|
|
<div class="effect-3 effects"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script type="module" src="/src/main.ts"></script>
|
|
<script>
|
|
const loaderColor = localStorage.getItem('vuexy-initial-loader-bg') || '#FFFFFF'
|
|
const primaryColor = localStorage.getItem('vuexy-initial-loader-color') || '#7367F0'
|
|
|
|
if (loaderColor)
|
|
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
|
|
if (loaderColor)
|
|
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
|
|
|
|
if (primaryColor)
|
|
document.documentElement.style.setProperty('--initial-loader-color', primaryColor)
|
|
</script>
|
|
</body>
|
|
|
|
</html> |