diff --git a/README.md b/README.md
index a6ea5db..9f8c48b 100644
--- a/README.md
+++ b/README.md
@@ -130,7 +130,19 @@ Les traductions sont centralisées dans `src/utils/translations.ts`. Pour ajoute
6. **etsidemain.com** - Site vitrine pour conseil en transformation régénérative
7. **avopieces.fr** - Plateforme juridique IA pour procédures de divorce
-## 📱 Responsive Design
+## �️ Sécurité
+
+Ce portfolio implémente des pratiques de sécurité avancées pour protéger contre les vulnérabilités web courantes :
+
+- **Content Security Policy (CSP)** - Protection contre XSS
+- **HSTS** - Forçage HTTPS avec preload
+- **COOP/CORP/COEP** - Isolation cross-origin
+- **X-Frame-Options** - Protection contre le clickjacking
+- **Permissions Policy** - Contrôle des fonctionnalités du navigateur
+
+Pour plus de détails, consultez [SECURITY.md](./SECURITY.md).
+
+## �📱 Responsive Design
Le portfolio est entièrement responsive avec des breakpoints optimisés :
- Mobile : < 640px
diff --git a/nginx.conf b/nginx.conf
index f8147e0..6ee873a 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -6,14 +6,36 @@ server {
index index.html;
# Security headers
- add_header X-Frame-Options "SAMEORIGIN" always;
- add_header X-Content-Type-Options "nosniff" always;
- add_header X-XSS-Protection "1; mode=block" always;
- add_header Referrer-Policy "no-referrer-when-downgrade" always;
- # Performance headers
+ # Content Security Policy (CSP) - Protection contre XSS
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; require-trusted-types-for 'script';" always;
+
+ # HTTP Strict Transport Security (HSTS) - Force HTTPS
+ add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
+
+ # Cross-Origin-Opener-Policy (COOP) - Isolation de l'origine
+ add_header Cross-Origin-Opener-Policy "same-origin" always;
+
+ # Cross-Origin-Resource-Policy (CORP)
+ add_header Cross-Origin-Resource-Policy "same-origin" always;
+
+ # Cross-Origin-Embedder-Policy (COEP)
+ add_header Cross-Origin-Embedder-Policy "require-corp" always;
+
+ # Protection contre le clickjacking
+ add_header X-Frame-Options "DENY" always;
+
+ # Protection contre le MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+ # Protection XSS (legacy)
+ add_header X-XSS-Protection "1; mode=block" always;
+
+ # Referrer Policy - Contrôle des informations envoyées
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+
+ # Permissions Policy - Contrôle des fonctionnalités du navigateur
+ add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()" always;
# Gzip compression
gzip on;
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..6353510
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,85 @@
+# Apache Configuration for Security Headers
+
+
+ # Content Security Policy (CSP) - Protection contre XSS
+ Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
+
+ # HTTP Strict Transport Security (HSTS)
+ Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
+
+ # Cross-Origin-Opener-Policy (COOP)
+ Header always set Cross-Origin-Opener-Policy "same-origin"
+
+ # Cross-Origin-Resource-Policy (CORP)
+ Header always set Cross-Origin-Resource-Policy "same-origin"
+
+ # Cross-Origin-Embedder-Policy (COEP)
+ Header always set Cross-Origin-Embedder-Policy "require-corp"
+
+ # Protection contre le clickjacking
+ Header always set X-Frame-Options "DENY"
+
+ # Protection contre le MIME type sniffing
+ Header always set X-Content-Type-Options "nosniff"
+
+ # Protection XSS (legacy)
+ Header always set X-XSS-Protection "1; mode=block"
+
+ # Referrer Policy
+ Header always set Referrer-Policy "strict-origin-when-cross-origin"
+
+ # Permissions Policy
+ Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
+
+
+# Redirection HTTPS
+
+ RewriteEngine On
+ RewriteCond %{HTTPS} off
+ RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
+
+
+# SPA Fallback - Toutes les routes vers index.html
+
+ RewriteEngine On
+ RewriteBase /
+ RewriteRule ^index\.html$ - [L]
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule . /index.html [L]
+
+
+# Cache Control pour les assets statiques
+
+ ExpiresActive On
+
+ # Images
+ ExpiresByType image/jpeg "access plus 1 year"
+ ExpiresByType image/jpg "access plus 1 year"
+ ExpiresByType image/png "access plus 1 year"
+ ExpiresByType image/gif "access plus 1 year"
+ ExpiresByType image/webp "access plus 1 year"
+ ExpiresByType image/svg+xml "access plus 1 year"
+ ExpiresByType image/x-icon "access plus 1 year"
+
+ # CSS et JavaScript
+ ExpiresByType text/css "access plus 1 year"
+ ExpiresByType application/javascript "access plus 1 year"
+ ExpiresByType text/javascript "access plus 1 year"
+
+ # Fonts
+ ExpiresByType font/woff "access plus 1 year"
+ ExpiresByType font/woff2 "access plus 1 year"
+ ExpiresByType font/ttf "access plus 1 year"
+ ExpiresByType font/otf "access plus 1 year"
+ ExpiresByType application/font-woff "access plus 1 year"
+ ExpiresByType application/font-woff2 "access plus 1 year"
+
+ # HTML (no cache)
+ ExpiresByType text/html "access plus 0 seconds"
+
+
+# Compression Gzip
+
+ AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml application/rss+xml application/atom+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml
+
diff --git a/public/_headers b/public/_headers
new file mode 100644
index 0000000..92a8d26
--- /dev/null
+++ b/public/_headers
@@ -0,0 +1,61 @@
+/*
+ # Security Headers
+
+ # Content Security Policy (CSP) - Protection contre XSS
+ Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
+
+ # HTTP Strict Transport Security (HSTS) - Force HTTPS
+ Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
+
+ # Cross-Origin-Opener-Policy (COOP) - Isolation de l'origine
+ Cross-Origin-Opener-Policy: same-origin
+
+ # Cross-Origin-Resource-Policy (CORP)
+ Cross-Origin-Resource-Policy: same-origin
+
+ # Cross-Origin-Embedder-Policy (COEP)
+ Cross-Origin-Embedder-Policy: require-corp
+
+ # Protection contre le clickjacking
+ X-Frame-Options: DENY
+
+ # Protection contre le MIME type sniffing
+ X-Content-Type-Options: nosniff
+
+ # Protection XSS (legacy)
+ X-XSS-Protection: 1; mode=block
+
+ # Referrer Policy - Contrôle des informations envoyées
+ Referrer-Policy: strict-origin-when-cross-origin
+
+ # Permissions Policy - Contrôle des fonctionnalités du navigateur
+ Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()
+
+ # Cache Control
+ Cache-Control: public, max-age=0, must-revalidate
+
+/assets/*
+ # Cache les assets statiques pendant 1 an
+ Cache-Control: public, max-age=31536000, immutable
+
+/images/*
+ # Cache les images pendant 1 an
+ Cache-Control: public, max-age=31536000, immutable
+
+/*.js
+ # Cache les fichiers JS pendant 1 an
+ Cache-Control: public, max-age=31536000, immutable
+
+/*.css
+ # Cache les fichiers CSS pendant 1 an
+ Cache-Control: public, max-age=31536000, immutable
+
+/*.woff
+ # Cache les polices pendant 1 an
+ Cache-Control: public, max-age=31536000, immutable
+ Access-Control-Allow-Origin: *
+
+/*.woff2
+ # Cache les polices pendant 1 an
+ Cache-Control: public, max-age=31536000, immutable
+ Access-Control-Allow-Origin: *
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 1605689..67df977 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -128,7 +128,7 @@ export const Header = () => {
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.4 }}
>
-
@@ -103,6 +105,7 @@ export const ImageGallery = ({ images, projectTitle }: ImageGalleryProps) => {
e.stopPropagation();
nextImage();
}}
+ aria-label="Image suivante"
>
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..244d9dc
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,49 @@
+{
+ "headers": [
+ {
+ "source": "/(.*)",
+ "headers": [
+ {
+ "key": "Content-Security-Policy",
+ "value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
+ },
+ {
+ "key": "Strict-Transport-Security",
+ "value": "max-age=63072000; includeSubDomains; preload"
+ },
+ {
+ "key": "Cross-Origin-Opener-Policy",
+ "value": "same-origin"
+ },
+ {
+ "key": "Cross-Origin-Resource-Policy",
+ "value": "same-origin"
+ },
+ {
+ "key": "Cross-Origin-Embedder-Policy",
+ "value": "require-corp"
+ },
+ {
+ "key": "X-Frame-Options",
+ "value": "DENY"
+ },
+ {
+ "key": "X-Content-Type-Options",
+ "value": "nosniff"
+ },
+ {
+ "key": "X-XSS-Protection",
+ "value": "1; mode=block"
+ },
+ {
+ "key": "Referrer-Policy",
+ "value": "strict-origin-when-cross-origin"
+ },
+ {
+ "key": "Permissions-Policy",
+ "value": "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
+ }
+ ]
+ }
+ ]
+}