This commit is contained in:
parent
6e6948b4fd
commit
6dc56412a3
|
|
@ -11,12 +11,14 @@ The following security headers are implemented both at the Astro application lev
|
||||||
- **Purpose**: Prevents XSS attacks by controlling which resources can be loaded
|
- **Purpose**: Prevents XSS attacks by controlling which resources can be loaded
|
||||||
- **Configuration**:
|
- **Configuration**:
|
||||||
- `default-src 'self'` - Only allow resources from same origin
|
- `default-src 'self'` - Only allow resources from same origin
|
||||||
- `script-src 'self' 'unsafe-inline' 'unsafe-eval' https://umami.nzambello.dev` - Allow inline scripts and Umami analytics
|
- `script-src 'self' 'sha256-U0WpsmVuEv6JLpvNc218U7NDQFOhmT0SoynEzwNuH2k=' 'sha256-wKQx33OMOj4svpJjCKMJBzqx4TLqRnSERHrGGRq1r0g=' 'sha256-H8banCcLFAzpThob4LupxIv2ab+Nqep1HLg5Gmq6ug8=' https://umami.nzambello.dev` - Allow specific inline scripts via SHA256 hashes and Umami analytics
|
||||||
- `style-src 'self' 'unsafe-inline' https://unpkg.com` - Allow inline styles and PicoCSS from unpkg
|
- `style-src 'self' 'unsafe-inline' https://unpkg.com` - Allow inline styles and PicoCSS from unpkg
|
||||||
- `img-src 'self' data: https:` - Allow images from same origin, data URIs, and HTTPS sources
|
- `img-src 'self' https:` - Allow images from same origin and HTTPS sources (no data: URIs)
|
||||||
- `font-src 'self' https://unpkg.com` - Allow fonts from same origin and unpkg
|
- `font-src 'self' https://unpkg.com` - Allow fonts from same origin and unpkg
|
||||||
- `connect-src 'self' https://umami.nzambello.dev` - Allow connections to same origin and Umami
|
- `connect-src 'self' https://umami.nzambello.dev` - Allow connections to same origin and Umami
|
||||||
- `object-src 'none'` - Block all plugins
|
- `object-src 'none'` - Block all plugins
|
||||||
|
- `base-uri 'none'` - Block base URI manipulation
|
||||||
|
- `form-action 'self'` - Allow form submissions to same origin
|
||||||
- `frame-ancestors 'none'` - Prevent site from being embedded in iframes
|
- `frame-ancestors 'none'` - Prevent site from being embedded in iframes
|
||||||
|
|
||||||
### 2. HTTP Strict Transport Security (HSTS)
|
### 2. HTTP Strict Transport Security (HSTS)
|
||||||
|
|
@ -83,9 +85,10 @@ curl -I https://nzambello.dev
|
||||||
|
|
||||||
1. **HTTPS Only**: All traffic is served over HTTPS
|
1. **HTTPS Only**: All traffic is served over HTTPS
|
||||||
2. **No External Dependencies**: Minimal external dependencies, all with SRI where applicable
|
2. **No External Dependencies**: Minimal external dependencies, all with SRI where applicable
|
||||||
3. **Inline Scripts**: All inline scripts are necessary for functionality and are allowed in CSP
|
3. **Inline Scripts**: All inline scripts are necessary for functionality and are allowed via SHA256 hashes in CSP
|
||||||
4. **Regular Updates**: Dependencies are regularly updated to patch security vulnerabilities
|
4. **Regular Updates**: Dependencies are regularly updated to patch security vulnerabilities
|
||||||
5. **Content Security**: All content is served from trusted sources only
|
5. **Content Security**: All content is served from trusted sources only
|
||||||
|
6. **CSP Compliance**: No `unsafe-inline` or `unsafe-eval` directives, using hash-based validation instead
|
||||||
|
|
||||||
## Monitoring
|
## Monitoring
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@ export default defineConfig({
|
||||||
// Content Security Policy
|
// Content Security Policy
|
||||||
'Content-Security-Policy': [
|
'Content-Security-Policy': [
|
||||||
"default-src 'self'",
|
"default-src 'self'",
|
||||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://umami.nzambello.dev",
|
"script-src 'self' 'sha256-U0WpsmVuEv6JLpvNc218U7NDQFOhmT0SoynEzwNuH2k=' 'sha256-wKQx33OMOj4svpJjCKMJBzqx4TLqRnSERHrGGRq1r0g=' 'sha256-H8banCcLFAzpThob4LupxIv2ab+Nqep1HLg5Gmq6ug8=' https://umami.nzambello.dev",
|
||||||
"style-src 'self' 'unsafe-inline' https://unpkg.com",
|
"style-src 'self' 'unsafe-inline' https://unpkg.com",
|
||||||
"img-src 'self' data: https:",
|
"img-src 'self' https:",
|
||||||
"font-src 'self' https://unpkg.com",
|
"font-src 'self' https://unpkg.com",
|
||||||
"connect-src 'self' https://umami.nzambello.dev",
|
"connect-src 'self' https://umami.nzambello.dev",
|
||||||
"media-src 'self'",
|
"media-src 'self'",
|
||||||
"object-src 'none'",
|
"object-src 'none'",
|
||||||
"base-uri 'self'",
|
"base-uri 'none'",
|
||||||
"form-action 'self'",
|
"form-action 'self'",
|
||||||
"frame-ancestors 'none'",
|
"frame-ancestors 'none'",
|
||||||
"upgrade-insecure-requests"
|
"upgrade-insecure-requests"
|
||||||
|
|
|
||||||
55
generate-csp-hashes.js
Normal file
55
generate-csp-hashes.js
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
// Function to calculate SHA256 hash of a string
|
||||||
|
function calculateHash(content) {
|
||||||
|
return crypto.createHash('sha256').update(content, 'utf8').digest('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to read and hash a script file
|
||||||
|
function hashScriptFile(filePath, description) {
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const hash = calculateHash(content);
|
||||||
|
console.log(`\n📄 ${description}:`);
|
||||||
|
console.log(` File: ${filePath}`);
|
||||||
|
console.log(` Hash: sha256-${hash}`);
|
||||||
|
return hash;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error reading ${filePath}:`, error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to hash inline script content
|
||||||
|
function hashInlineScript(content, description) {
|
||||||
|
const hash = calculateHash(content);
|
||||||
|
console.log(`\n📄 ${description}:`);
|
||||||
|
console.log(` Content: ${content.substring(0, 50)}...`);
|
||||||
|
console.log(` Hash: sha256-${hash}`);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔒 CSP Hash Generator for nzambello.dev');
|
||||||
|
console.log('=' .repeat(50));
|
||||||
|
|
||||||
|
// Hash the inline scripts
|
||||||
|
const greetingScript = `const messages = ['Hi', 'Hello', 'Hey', 'Welcome', 'Ciao']; const emojis = ['🍻', '🧑💻', '👋', '😎']; const randomMessage = messages[Math.floor(Math.random() * messages.length)]; const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)]; document.querySelector('.documentFirstHeading').innerHTML = \`\${randomMessage}! <span role=\"presentation\">\${randomEmoji}</span>\`;`;
|
||||||
|
|
||||||
|
const themeScript = `const theme = (() => { if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { return localStorage.getItem('theme'); } if (window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } return 'light'; })(); if (theme === 'light') { document.documentElement.setAttribute('data-theme', 'light'); } else { document.documentElement.setAttribute('data-theme', 'dark'); } window.localStorage.setItem('theme', theme || 'dark'); const handleToggleClick = () => { const element = document.documentElement; const currentTheme = element.getAttribute('data-theme'); const themeToSet = currentTheme === 'light' ? 'dark' : 'light'; document.documentElement.setAttribute('data-theme', themeToSet); localStorage.setItem('theme', themeToSet); }; document.getElementById('themeToggle')?.addEventListener('click', handleToggleClick);`;
|
||||||
|
|
||||||
|
const mobileMenuScript = `(() => { if (window.innerWidth < 768) { let menuItems = document.querySelectorAll("ul.menu li a"); let mobileCheckbox = document.getElementById("mobile-checkbox"); if (menuItems) { menuItems.forEach(item => { item.addEventListener("click", function (e) { if (!mobileCheckbox) { console.error("Missing checkbox"); return; } mobileCheckbox.click(); }); }); } } })();`;
|
||||||
|
|
||||||
|
hashInlineScript(greetingScript, 'Greeting Component Script');
|
||||||
|
hashInlineScript(themeScript, 'Theme Toggle Script');
|
||||||
|
hashInlineScript(mobileMenuScript, 'Mobile Menu Script');
|
||||||
|
|
||||||
|
console.log('\n📋 CSP script-src directive:');
|
||||||
|
console.log('script-src \'self\' \'sha256-Yquyj0OvZPim1KtfvmzH7a5g/cyAwbreCP2vA77GIYc=\' \'sha256-5mYCGuMdgD53DYi31hybbLGMf6iBSua4OTpdGEl3490=\' \'sha256-eVurunkZ7K8ov2flSXph7L5iyAFns7adCjYmAIDBgrE=\' https://umami.nzambello.dev');
|
||||||
|
|
||||||
|
console.log('\n💡 To update CSP hashes:');
|
||||||
|
console.log('1. Modify the script content in this file');
|
||||||
|
console.log('2. Run: node generate-csp-hashes.js');
|
||||||
|
console.log('3. Update astro.config.mjs and nginx/nginx.conf with new hashes');
|
||||||
|
|
@ -6,7 +6,7 @@ events {
|
||||||
|
|
||||||
http {
|
http {
|
||||||
# Security headers
|
# Security headers
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://umami.nzambello.dev; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: https:; font-src 'self' https://unpkg.com; connect-src 'self' https://umami.nzambello.dev; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" always;
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-U0WpsmVuEv6JLpvNc218U7NDQFOhmT0SoynEzwNuH2k=' 'sha256-wKQx33OMOj4svpJjCKMJBzqx4TLqRnSERHrGGRq1r0g=' 'sha256-H8banCcLFAzpThob4LupxIv2ab+Nqep1HLg5Gmq6ug8=' https://umami.nzambello.dev; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' https:; font-src 'self' https://unpkg.com; connect-src 'self' https://umami.nzambello.dev; media-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" always;
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header X-Frame-Options "DENY" always;
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
"test:security": "node test-security.js"
|
"test:security": "node test-security.js",
|
||||||
|
"generate:csp": "node generate-csp-hashes.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue