fix(security): audit batch B — SPA CSP + vite upgrade (#21)
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 15s
Deploy Staging / deploy (push) Successful in 45s
CI / backend (pull_request) Successful in 56s
CI / frontend (pull_request) Successful in 11s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 54s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 54s

This commit was merged in pull request #21.
This commit is contained in:
2026-07-02 03:24:57 +02:00
parent c5c33f7023
commit b905c93884
5 changed files with 561 additions and 1125 deletions
+2 -10
View File
@@ -5,16 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InboxIntel — Gmail analytics & cleanup</title> <title>InboxIntel — Gmail analytics & cleanup</title>
<script> <!-- Theme applied before first paint; external file so CSP can use script-src 'self'. -->
// Apply the saved theme before first paint to avoid a flash of the wrong mode. <script src="/theme-init.js"></script>
(function () {
try {
// Dark-first: default new users to dark unless they've chosen light.
var t = localStorage.getItem('ii:theme') || 'dark';
if (t === 'dark') document.documentElement.classList.add('dark');
} catch (e) {}
})();
</script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+13
View File
@@ -4,6 +4,19 @@ server {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
# AUDIT M-2: security headers on the SPA. script-src 'self' works because the theme
# bootstrap lives in /theme-init.js (no inline scripts); style-src needs 'unsafe-inline'
# for React/Chart.js/grid-layout inline style attributes (low risk with script-src locked).
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always;
# Compress the SPA bundle (AUDIT perf note: ~680 KB JS).
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
# SPA fallback. # SPA fallback.
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
+535 -1113
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -35,10 +35,10 @@
"tailwind-merge": "^3.6.0" "tailwind-merge": "^3.6.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^6.0.3",
"autoprefixer": "^10.5.2", "autoprefixer": "^10.5.2",
"postcss": "^8.5.16", "postcss": "^8.5.16",
"tailwindcss": "^3.4.19", "tailwindcss": "^3.4.19",
"vite": "^5.3.1" "vite": "^8.1.2"
} }
} }
+9
View File
@@ -0,0 +1,9 @@
// Applied before first paint to avoid a flash of the wrong theme. Lives in a file (not
// inline) so the SPA can ship a CSP with script-src 'self' (AUDIT M-2).
(function () {
try {
// Dark-first: default new users to dark unless they've chosen light.
var t = localStorage.getItem('ii:theme') || 'dark';
if (t === 'dark') document.documentElement.classList.add('dark');
} catch (e) {}
})();