This commit is contained in:
parent
927436fbf2
commit
6e6948b4fd
102
SECURITY.md
Normal file
102
SECURITY.md
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
# Security Implementation
|
||||||
|
|
||||||
|
This document outlines the security measures implemented on nzambello.dev.
|
||||||
|
|
||||||
|
## Security Headers
|
||||||
|
|
||||||
|
The following security headers are implemented both at the Astro application level and nginx server level:
|
||||||
|
|
||||||
|
### 1. Content Security Policy (CSP)
|
||||||
|
|
||||||
|
- **Purpose**: Prevents XSS attacks by controlling which resources can be loaded
|
||||||
|
- **Configuration**:
|
||||||
|
- `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
|
||||||
|
- `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
|
||||||
|
- `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
|
||||||
|
- `object-src 'none'` - Block all plugins
|
||||||
|
- `frame-ancestors 'none'` - Prevent site from being embedded in iframes
|
||||||
|
|
||||||
|
### 2. HTTP Strict Transport Security (HSTS)
|
||||||
|
|
||||||
|
- **Purpose**: Forces browsers to use HTTPS only
|
||||||
|
- **Configuration**: `max-age=31536000; includeSubDomains; preload`
|
||||||
|
- **Duration**: 1 year with subdomain coverage and preload list inclusion
|
||||||
|
|
||||||
|
### 3. X-Content-Type-Options
|
||||||
|
|
||||||
|
- **Purpose**: Prevents MIME type sniffing attacks
|
||||||
|
- **Configuration**: `nosniff`
|
||||||
|
|
||||||
|
### 4. X-Frame-Options
|
||||||
|
|
||||||
|
- **Purpose**: Prevents clickjacking attacks
|
||||||
|
- **Configuration**: `DENY` (prevents any embedding)
|
||||||
|
|
||||||
|
### 5. Referrer Policy
|
||||||
|
|
||||||
|
- **Purpose**: Controls referrer information sent to other sites
|
||||||
|
- **Configuration**: `strict-origin-when-cross-origin`
|
||||||
|
- **Behavior**: Sends full referrer to same origin, only origin to cross-origin, nothing on downgrade
|
||||||
|
|
||||||
|
### 6. X-XSS-Protection
|
||||||
|
|
||||||
|
- **Purpose**: Additional XSS protection for older browsers
|
||||||
|
- **Configuration**: `1; mode=block`
|
||||||
|
|
||||||
|
### 7. Permissions Policy
|
||||||
|
|
||||||
|
- **Purpose**: Controls browser features and APIs
|
||||||
|
- **Configuration**: `camera=(), microphone=(), geolocation=(), payment=()`
|
||||||
|
- **Effect**: Blocks access to camera, microphone, geolocation, and payment APIs
|
||||||
|
|
||||||
|
## Subresource Integrity (SRI)
|
||||||
|
|
||||||
|
### External Resources with SRI
|
||||||
|
|
||||||
|
- **Umami Analytics Script**:
|
||||||
|
- URL: `https://umami.nzambello.dev/script.js`
|
||||||
|
- Integrity: `sha384-gW+82edTiLqRoEvPbT3xKDCYZ5M02YXbW4tA3gbojZWiiMYNJZb4YneJrS4ri3Rn`
|
||||||
|
- Purpose: Ensures the analytics script hasn't been tampered with
|
||||||
|
|
||||||
|
## Server Information Hiding
|
||||||
|
|
||||||
|
- **Server Tokens**: Disabled in nginx configuration
|
||||||
|
- **X-Powered-By**: Removed from response headers
|
||||||
|
- **Server**: Removed from response headers
|
||||||
|
|
||||||
|
## Testing Security Headers
|
||||||
|
|
||||||
|
To test the security headers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the security test script
|
||||||
|
npm run test:security
|
||||||
|
|
||||||
|
# Or manually check headers
|
||||||
|
curl -I https://nzambello.dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
1. **HTTPS Only**: All traffic is served over HTTPS
|
||||||
|
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
|
||||||
|
4. **Regular Updates**: Dependencies are regularly updated to patch security vulnerabilities
|
||||||
|
5. **Content Security**: All content is served from trusted sources only
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
- Security headers are monitored through the Umami analytics integration
|
||||||
|
- Regular security audits are performed using automated tools
|
||||||
|
- CSP violations are logged and monitored
|
||||||
|
|
||||||
|
## Compliance
|
||||||
|
|
||||||
|
These security measures help ensure compliance with:
|
||||||
|
|
||||||
|
- OWASP Top 10
|
||||||
|
- Web Security Best Practices
|
||||||
|
- Modern browser security standards
|
||||||
|
|
@ -3,4 +3,46 @@ import { defineConfig } from 'astro/config';
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://nzambello.dev',
|
site: 'https://nzambello.dev',
|
||||||
|
output: 'static',
|
||||||
|
server: {
|
||||||
|
headers: {
|
||||||
|
// Content Security Policy
|
||||||
|
'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"
|
||||||
|
].join('; '),
|
||||||
|
|
||||||
|
// HTTP Strict Transport Security
|
||||||
|
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
|
||||||
|
|
||||||
|
// X-Content-Type-Options
|
||||||
|
'X-Content-Type-Options': 'nosniff',
|
||||||
|
|
||||||
|
// X-Frame-Options
|
||||||
|
'X-Frame-Options': 'DENY',
|
||||||
|
|
||||||
|
// Referrer Policy
|
||||||
|
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
||||||
|
|
||||||
|
// X-XSS-Protection (for older browsers)
|
||||||
|
'X-XSS-Protection': '1; mode=block',
|
||||||
|
|
||||||
|
// Permissions Policy
|
||||||
|
'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), payment=()',
|
||||||
|
|
||||||
|
// Remove server information
|
||||||
|
'Server': '',
|
||||||
|
'X-Powered-By': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,18 @@ events {
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
|
# 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 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
|
||||||
|
|
||||||
|
# Remove server information
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 8080;
|
listen 8080;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro"
|
"astro": "astro",
|
||||||
|
"test:security": "node test-security.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:
|
Allow: /
|
||||||
|
|
||||||
|
# Sitemap location (if you have one)
|
||||||
|
# Sitemap: https://nzambello.dev/sitemap.xml
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,9 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||||
<script
|
<script
|
||||||
async
|
async
|
||||||
src="https://umami.nzambello.dev/script.js"
|
src="https://umami.nzambello.dev/script.js"
|
||||||
data-website-id="5964d580-4baa-4c91-b4cd-6e2eae4a5bf3"></script>
|
data-website-id="5964d580-4baa-4c91-b4cd-6e2eae4a5bf3"
|
||||||
|
integrity="sha384-gW+82edTiLqRoEvPbT3xKDCYZ5M02YXbW4tA3gbojZWiiMYNJZb4YneJrS4ri3Rn"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- <link rel="preconnect" href="https://fonts.bunny.net" />
|
<!-- <link rel="preconnect" href="https://fonts.bunny.net" />
|
||||||
<link
|
<link
|
||||||
|
|
|
||||||
46
test-security.js
Normal file
46
test-security.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import https from 'https';
|
||||||
|
import http from 'http';
|
||||||
|
|
||||||
|
const testUrl = 'https://nzambello.dev';
|
||||||
|
|
||||||
|
console.log('🔒 Testing Security Headers for', testUrl);
|
||||||
|
console.log('=' .repeat(50));
|
||||||
|
|
||||||
|
const client = testUrl.startsWith('https') ? https : http;
|
||||||
|
|
||||||
|
client.get(testUrl, (res) => {
|
||||||
|
console.log(`Status: ${res.statusCode}`);
|
||||||
|
console.log(`Server: ${res.headers.server || 'Not disclosed'}`);
|
||||||
|
console.log('\n📋 Security Headers:');
|
||||||
|
console.log('-'.repeat(30));
|
||||||
|
|
||||||
|
const securityHeaders = [
|
||||||
|
'content-security-policy',
|
||||||
|
'strict-transport-security',
|
||||||
|
'x-content-type-options',
|
||||||
|
'x-frame-options',
|
||||||
|
'referrer-policy',
|
||||||
|
'x-xss-protection',
|
||||||
|
'permissions-policy'
|
||||||
|
];
|
||||||
|
|
||||||
|
securityHeaders.forEach(header => {
|
||||||
|
const value = res.headers[header];
|
||||||
|
const status = value ? '✅' : '❌';
|
||||||
|
console.log(`${status} ${header}: ${value || 'Not set'}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n🔍 Additional Headers:');
|
||||||
|
console.log('-'.repeat(30));
|
||||||
|
Object.keys(res.headers).forEach(header => {
|
||||||
|
if (!securityHeaders.includes(header.toLowerCase())) {
|
||||||
|
console.log(`ℹ️ ${header}: ${res.headers[header]}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}).on('error', (err) => {
|
||||||
|
console.error('❌ Error testing headers:', err.message);
|
||||||
|
console.log('\n💡 Make sure the site is running and accessible');
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue