Explorateur accessible de données publiques suisses basé sur un frontend SvelteKit et un backend FastAPI.
0

Configure Feed

Select the types of activity you want to include in your feed.

PDS-38: Durcir HTTPS et headers de sécurité web

- Backend: SecurityHeadersMiddleware (HSTS, X-Content-Type-Options, X-Frame-Options, COOP, Referrer-Policy, Permissions-Policy)
- Frontend: hooks.server.ts injecte HSTS, X-Content-Type-Options, COOP, Referrer-Policy, Permissions-Policy
- CSP déclarative via svelte.config.ts (PDS-56), Trusted Types en mode rapport
- Script check-security-headers.sh pour vérification HTTP
- ADR-021 accepté, compromis MVP documentés
- make quality: 148 tests backend 88%, 74 tests frontend, build OK

sebschopf (Jun 26, 2026, 4:05 PM +0200) bc42a4c8 361d5efb

+1182 -14
+2 -1
CHANGELOG.md
··· 3 3 Toutes les modifications notables de ce projet seront documentées dans ce fichier. 4 4 Le format est basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) et ce projet respecte le [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 5 6 - ## [Unreleased] (En cours - Milestones M6 & M7) 6 + ## [Unreleased] (En cours - Milestones M6, M7, M8) 7 7 ### Added (Ajouts) 8 + - PDS-38 : Durcissement HTTPS et headers de sécurité web — middleware `SecurityHeadersMiddleware` backend (HSTS, X-Content-Type-Options, X-Frame-Options, COOP, Referrer-Policy, Permissions-Policy), headers injectés dans `hooks.server.ts` frontend, script `scripts/check-security-headers.sh` de vérification HTTP, Trusted Types en mode rapport via CSP, documentation des compromis MVP (M8). 8 9 - PDS-45 : Métriques d'ingestion CKAN — table `sync_metrics`, endpoint `GET /api/v1/internal/sync/metrics`, refactorisation extraction `RunSyncCycleUseCase` (élimination duplication ADR-003) (M7). 9 10 - PDS-40 : Recherche différenciante avec ranking hybride explicable et mise en avant de la pertinence (M6). 10 11 - PDS-41 : Recherche multilingue FR/DE/IT/EN — dictionnaire 20 concepts × 4 langues, 15 concepts de synonymes métier, expansion de requête traçable intégrée au pipeline de recherche (M6).
+3
app/main.py
··· 19 19 from app.infrastructure.persistence.cache_repository import SqlAlchemyCacheRepository 20 20 from app.infrastructure.persistence.database import create_schema 21 21 from app.infrastructure.persistence.query_cache_repository import SqlAlchemyQueryCacheRepository 22 + from app.presentation.api.security_headers import SecurityHeadersMiddleware 22 23 from app.presentation.api.v1.router import api_router 23 24 24 25 logger = logging.getLogger(__name__) ··· 128 129 allow_methods=["*"], 129 130 allow_headers=["*"], 130 131 ) 132 + # Security headers (PDS-38, ADR-021) 133 + app.add_middleware(SecurityHeadersMiddleware, settings=settings) # type: ignore[arg-type] 131 134 app.include_router(api_router) 132 135 return app 133 136
+98
app/presentation/api/security_headers.py
··· 1 + """Middleware de securite web : injecte les headers definis par ADR-021. 2 + 3 + Politique : tous les headers sont actifs en production et en staging. 4 + En dev, les headers sont injectes pour validation mais la redirection 5 + HTTPS est desactivee (environnement local HTTP). 6 + 7 + Headers couverts : 8 + - Strict-Transport-Security (HSTS) 9 + - X-Content-Type-Options 10 + - X-Frame-Options 11 + - Referrer-Policy 12 + - Cross-Origin-Opener-Policy (COOP) 13 + - Permissions-Policy 14 + """ 15 + 16 + from collections.abc import Awaitable, Callable 17 + 18 + from starlette.applications import Starlette 19 + from starlette.middleware.base import BaseHTTPMiddleware 20 + from starlette.requests import Request 21 + from starlette.responses import Response 22 + 23 + from app.core.config import Settings 24 + 25 + 26 + class SecurityHeadersMiddleware(BaseHTTPMiddleware): 27 + """Injecte les headers de securite definis par ADR-021.""" 28 + 29 + def __init__( 30 + self, 31 + app: Starlette, 32 + settings: Settings, 33 + *, 34 + enable_hsts: bool = True, 35 + enable_coop: bool = True, 36 + enable_xfo: bool = True, 37 + enable_cto: bool = True, 38 + enable_referrer_policy: bool = True, 39 + enable_permissions_policy: bool = True, 40 + ): 41 + super().__init__(app) 42 + self._settings: Settings = settings 43 + self._enable_hsts = enable_hsts 44 + self._enable_coop = enable_coop 45 + self._enable_xfo = enable_xfo 46 + self._enable_cto = enable_cto 47 + self._enable_referrer_policy = enable_referrer_policy 48 + self._enable_permissions_policy = enable_permissions_policy 49 + 50 + async def dispatch( 51 + self, 52 + request: Request, 53 + call_next: Callable[[Request], Awaitable[Response]], 54 + ) -> Response: 55 + response = await call_next(request) 56 + 57 + headers_to_set: dict[str, str] = {} 58 + 59 + # HSTS : 1 an, inclure sous-domaines (Fly.io force deja HTTPS) 60 + if self._enable_hsts: 61 + headers_to_set["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" 62 + 63 + # X-Content-Type-Options : empeche MIME sniffing 64 + if self._enable_cto: 65 + headers_to_set["X-Content-Type-Options"] = "nosniff" 66 + 67 + # X-Frame-Options : fallback pour navigateurs sans CSP 68 + if self._enable_xfo: 69 + headers_to_set["X-Frame-Options"] = "DENY" 70 + 71 + # Referrer-Policy : ne leak que l'origine en cross-origin 72 + if self._enable_referrer_policy: 73 + headers_to_set["Referrer-Policy"] = "strict-origin-when-cross-origin" 74 + 75 + # COOP : isole le contexte de navigation 76 + if self._enable_coop: 77 + headers_to_set["Cross-Origin-Opener-Policy"] = "same-origin" 78 + 79 + # Permissions-Policy : desactiver tout ce qui n'est pas utilise 80 + if self._enable_permissions_policy: 81 + headers_to_set["Permissions-Policy"] = ( 82 + "accelerometer=(), ambient-light-sensor=(), autoplay=(), " 83 + "battery=(), camera=(), cross-origin-isolated=(), " 84 + "display-capture=(), document-domain=(), " 85 + "encrypted-media=(), execution-while-not-rendered=(), " 86 + "execution-while-out-of-viewport=(), fullscreen=(), " 87 + "geolocation=(), gyroscope=(), keyboard-map=(), " 88 + "magnetometer=(), microphone=(), midi=(), " 89 + "navigation-override=(), payment=(), " 90 + "picture-in-picture=(), publickey-credentials-get=(), " 91 + "screen-wake-lock=(), sync-xhr=(), usb=(), " 92 + "web-share=(), xr-spatial-tracking=()" 93 + ) 94 + 95 + for header_name, header_value in headers_to_set.items(): 96 + response.headers[header_name] = header_value 97 + 98 + return response
+871
frontend/package-lock.json
··· 13 13 }, 14 14 "devDependencies": { 15 15 "@sveltejs/adapter-auto": "^7.0.1", 16 + "@sveltejs/adapter-vercel": "^6.3.4", 16 17 "@sveltejs/kit": "^2.63.0", 17 18 "@sveltejs/vite-plugin-svelte": "^7.1.2", 18 19 "@types/node": "^25.9.3", ··· 55 56 "tslib": "^2.4.0" 56 57 } 57 58 }, 59 + "node_modules/@esbuild/aix-ppc64": { 60 + "version": "0.28.1", 61 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", 62 + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", 63 + "cpu": [ 64 + "ppc64" 65 + ], 66 + "license": "MIT", 67 + "optional": true, 68 + "os": [ 69 + "aix" 70 + ], 71 + "engines": { 72 + "node": ">=18" 73 + } 74 + }, 75 + "node_modules/@esbuild/android-arm": { 76 + "version": "0.28.1", 77 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", 78 + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", 79 + "cpu": [ 80 + "arm" 81 + ], 82 + "license": "MIT", 83 + "optional": true, 84 + "os": [ 85 + "android" 86 + ], 87 + "engines": { 88 + "node": ">=18" 89 + } 90 + }, 91 + "node_modules/@esbuild/android-arm64": { 92 + "version": "0.28.1", 93 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", 94 + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", 95 + "cpu": [ 96 + "arm64" 97 + ], 98 + "license": "MIT", 99 + "optional": true, 100 + "os": [ 101 + "android" 102 + ], 103 + "engines": { 104 + "node": ">=18" 105 + } 106 + }, 107 + "node_modules/@esbuild/android-x64": { 108 + "version": "0.28.1", 109 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", 110 + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", 111 + "cpu": [ 112 + "x64" 113 + ], 114 + "license": "MIT", 115 + "optional": true, 116 + "os": [ 117 + "android" 118 + ], 119 + "engines": { 120 + "node": ">=18" 121 + } 122 + }, 123 + "node_modules/@esbuild/darwin-arm64": { 124 + "version": "0.28.1", 125 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", 126 + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", 127 + "cpu": [ 128 + "arm64" 129 + ], 130 + "license": "MIT", 131 + "optional": true, 132 + "os": [ 133 + "darwin" 134 + ], 135 + "engines": { 136 + "node": ">=18" 137 + } 138 + }, 139 + "node_modules/@esbuild/darwin-x64": { 140 + "version": "0.28.1", 141 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", 142 + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", 143 + "cpu": [ 144 + "x64" 145 + ], 146 + "license": "MIT", 147 + "optional": true, 148 + "os": [ 149 + "darwin" 150 + ], 151 + "engines": { 152 + "node": ">=18" 153 + } 154 + }, 155 + "node_modules/@esbuild/freebsd-arm64": { 156 + "version": "0.28.1", 157 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", 158 + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", 159 + "cpu": [ 160 + "arm64" 161 + ], 162 + "license": "MIT", 163 + "optional": true, 164 + "os": [ 165 + "freebsd" 166 + ], 167 + "engines": { 168 + "node": ">=18" 169 + } 170 + }, 171 + "node_modules/@esbuild/freebsd-x64": { 172 + "version": "0.28.1", 173 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", 174 + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", 175 + "cpu": [ 176 + "x64" 177 + ], 178 + "license": "MIT", 179 + "optional": true, 180 + "os": [ 181 + "freebsd" 182 + ], 183 + "engines": { 184 + "node": ">=18" 185 + } 186 + }, 187 + "node_modules/@esbuild/linux-arm": { 188 + "version": "0.28.1", 189 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", 190 + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", 191 + "cpu": [ 192 + "arm" 193 + ], 194 + "license": "MIT", 195 + "optional": true, 196 + "os": [ 197 + "linux" 198 + ], 199 + "engines": { 200 + "node": ">=18" 201 + } 202 + }, 203 + "node_modules/@esbuild/linux-arm64": { 204 + "version": "0.28.1", 205 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", 206 + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", 207 + "cpu": [ 208 + "arm64" 209 + ], 210 + "license": "MIT", 211 + "optional": true, 212 + "os": [ 213 + "linux" 214 + ], 215 + "engines": { 216 + "node": ">=18" 217 + } 218 + }, 219 + "node_modules/@esbuild/linux-ia32": { 220 + "version": "0.28.1", 221 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", 222 + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", 223 + "cpu": [ 224 + "ia32" 225 + ], 226 + "license": "MIT", 227 + "optional": true, 228 + "os": [ 229 + "linux" 230 + ], 231 + "engines": { 232 + "node": ">=18" 233 + } 234 + }, 235 + "node_modules/@esbuild/linux-loong64": { 236 + "version": "0.28.1", 237 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", 238 + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", 239 + "cpu": [ 240 + "loong64" 241 + ], 242 + "license": "MIT", 243 + "optional": true, 244 + "os": [ 245 + "linux" 246 + ], 247 + "engines": { 248 + "node": ">=18" 249 + } 250 + }, 251 + "node_modules/@esbuild/linux-mips64el": { 252 + "version": "0.28.1", 253 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", 254 + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", 255 + "cpu": [ 256 + "mips64el" 257 + ], 258 + "license": "MIT", 259 + "optional": true, 260 + "os": [ 261 + "linux" 262 + ], 263 + "engines": { 264 + "node": ">=18" 265 + } 266 + }, 267 + "node_modules/@esbuild/linux-ppc64": { 268 + "version": "0.28.1", 269 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", 270 + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", 271 + "cpu": [ 272 + "ppc64" 273 + ], 274 + "license": "MIT", 275 + "optional": true, 276 + "os": [ 277 + "linux" 278 + ], 279 + "engines": { 280 + "node": ">=18" 281 + } 282 + }, 283 + "node_modules/@esbuild/linux-riscv64": { 284 + "version": "0.28.1", 285 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", 286 + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", 287 + "cpu": [ 288 + "riscv64" 289 + ], 290 + "license": "MIT", 291 + "optional": true, 292 + "os": [ 293 + "linux" 294 + ], 295 + "engines": { 296 + "node": ">=18" 297 + } 298 + }, 299 + "node_modules/@esbuild/linux-s390x": { 300 + "version": "0.28.1", 301 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", 302 + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", 303 + "cpu": [ 304 + "s390x" 305 + ], 306 + "license": "MIT", 307 + "optional": true, 308 + "os": [ 309 + "linux" 310 + ], 311 + "engines": { 312 + "node": ">=18" 313 + } 314 + }, 315 + "node_modules/@esbuild/linux-x64": { 316 + "version": "0.28.1", 317 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", 318 + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", 319 + "cpu": [ 320 + "x64" 321 + ], 322 + "license": "MIT", 323 + "optional": true, 324 + "os": [ 325 + "linux" 326 + ], 327 + "engines": { 328 + "node": ">=18" 329 + } 330 + }, 331 + "node_modules/@esbuild/netbsd-arm64": { 332 + "version": "0.28.1", 333 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", 334 + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", 335 + "cpu": [ 336 + "arm64" 337 + ], 338 + "license": "MIT", 339 + "optional": true, 340 + "os": [ 341 + "netbsd" 342 + ], 343 + "engines": { 344 + "node": ">=18" 345 + } 346 + }, 347 + "node_modules/@esbuild/netbsd-x64": { 348 + "version": "0.28.1", 349 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", 350 + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", 351 + "cpu": [ 352 + "x64" 353 + ], 354 + "license": "MIT", 355 + "optional": true, 356 + "os": [ 357 + "netbsd" 358 + ], 359 + "engines": { 360 + "node": ">=18" 361 + } 362 + }, 363 + "node_modules/@esbuild/openbsd-arm64": { 364 + "version": "0.28.1", 365 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", 366 + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", 367 + "cpu": [ 368 + "arm64" 369 + ], 370 + "license": "MIT", 371 + "optional": true, 372 + "os": [ 373 + "openbsd" 374 + ], 375 + "engines": { 376 + "node": ">=18" 377 + } 378 + }, 379 + "node_modules/@esbuild/openbsd-x64": { 380 + "version": "0.28.1", 381 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", 382 + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", 383 + "cpu": [ 384 + "x64" 385 + ], 386 + "license": "MIT", 387 + "optional": true, 388 + "os": [ 389 + "openbsd" 390 + ], 391 + "engines": { 392 + "node": ">=18" 393 + } 394 + }, 395 + "node_modules/@esbuild/openharmony-arm64": { 396 + "version": "0.28.1", 397 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", 398 + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", 399 + "cpu": [ 400 + "arm64" 401 + ], 402 + "license": "MIT", 403 + "optional": true, 404 + "os": [ 405 + "openharmony" 406 + ], 407 + "engines": { 408 + "node": ">=18" 409 + } 410 + }, 411 + "node_modules/@esbuild/sunos-x64": { 412 + "version": "0.28.1", 413 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", 414 + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", 415 + "cpu": [ 416 + "x64" 417 + ], 418 + "license": "MIT", 419 + "optional": true, 420 + "os": [ 421 + "sunos" 422 + ], 423 + "engines": { 424 + "node": ">=18" 425 + } 426 + }, 427 + "node_modules/@esbuild/win32-arm64": { 428 + "version": "0.28.1", 429 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", 430 + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", 431 + "cpu": [ 432 + "arm64" 433 + ], 434 + "license": "MIT", 435 + "optional": true, 436 + "os": [ 437 + "win32" 438 + ], 439 + "engines": { 440 + "node": ">=18" 441 + } 442 + }, 443 + "node_modules/@esbuild/win32-ia32": { 444 + "version": "0.28.1", 445 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", 446 + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", 447 + "cpu": [ 448 + "ia32" 449 + ], 450 + "license": "MIT", 451 + "optional": true, 452 + "os": [ 453 + "win32" 454 + ], 455 + "engines": { 456 + "node": ">=18" 457 + } 458 + }, 459 + "node_modules/@esbuild/win32-x64": { 460 + "version": "0.28.1", 461 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", 462 + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", 463 + "cpu": [ 464 + "x64" 465 + ], 466 + "license": "MIT", 467 + "optional": true, 468 + "os": [ 469 + "win32" 470 + ], 471 + "engines": { 472 + "node": ">=18" 473 + } 474 + }, 58 475 "node_modules/@floating-ui/core": { 59 476 "version": "1.7.5", 60 477 "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", ··· 146 563 "@swc/helpers": "^0.5.0" 147 564 } 148 565 }, 566 + "node_modules/@isaacs/fs-minipass": { 567 + "version": "4.0.1", 568 + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", 569 + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", 570 + "dev": true, 571 + "license": "ISC", 572 + "dependencies": { 573 + "minipass": "^7.0.4" 574 + }, 575 + "engines": { 576 + "node": ">=18.0.0" 577 + } 578 + }, 149 579 "node_modules/@jridgewell/gen-mapping": { 150 580 "version": "0.3.13", 151 581 "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", ··· 191 621 "@jridgewell/sourcemap-codec": "^1.4.14" 192 622 } 193 623 }, 624 + "node_modules/@mapbox/node-pre-gyp": { 625 + "version": "2.0.3", 626 + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", 627 + "integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==", 628 + "dev": true, 629 + "license": "BSD-3-Clause", 630 + "dependencies": { 631 + "consola": "^3.2.3", 632 + "detect-libc": "^2.0.0", 633 + "https-proxy-agent": "^7.0.5", 634 + "node-fetch": "^2.6.7", 635 + "nopt": "^8.0.0", 636 + "semver": "^7.5.3", 637 + "tar": "^7.4.0" 638 + }, 639 + "bin": { 640 + "node-pre-gyp": "bin/node-pre-gyp" 641 + }, 642 + "engines": { 643 + "node": ">=18" 644 + } 645 + }, 194 646 "node_modules/@napi-rs/wasm-runtime": { 195 647 "version": "1.1.5", 196 648 "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", ··· 1097 1549 "devOptional": true, 1098 1550 "license": "MIT" 1099 1551 }, 1552 + "node_modules/@rollup/pluginutils": { 1553 + "version": "5.4.0", 1554 + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", 1555 + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", 1556 + "dev": true, 1557 + "license": "MIT", 1558 + "dependencies": { 1559 + "@types/estree": "^1.0.0", 1560 + "estree-walker": "^2.0.2", 1561 + "picomatch": "^4.0.2" 1562 + }, 1563 + "engines": { 1564 + "node": ">=14.0.0" 1565 + }, 1566 + "peerDependencies": { 1567 + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 1568 + }, 1569 + "peerDependenciesMeta": { 1570 + "rollup": { 1571 + "optional": true 1572 + } 1573 + } 1574 + }, 1575 + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { 1576 + "version": "2.0.2", 1577 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1578 + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1579 + "dev": true, 1580 + "license": "MIT" 1581 + }, 1100 1582 "node_modules/@sentry/core": { 1101 1583 "version": "9.47.1", 1102 1584 "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.47.1.tgz", ··· 1222 1704 "license": "MIT", 1223 1705 "peerDependencies": { 1224 1706 "@sveltejs/kit": "^2.0.0" 1707 + } 1708 + }, 1709 + "node_modules/@sveltejs/adapter-vercel": { 1710 + "version": "6.3.4", 1711 + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-vercel/-/adapter-vercel-6.3.4.tgz", 1712 + "integrity": "sha512-MxEgTqIlmNg4qy5Rz4vwDKs2vNUrsL+DjcfegUVr/spPWSvWrSYFTUPY7tLbHsi8RxdUqpxC9UN/yzkOzZeMGA==", 1713 + "dev": true, 1714 + "license": "MIT", 1715 + "dependencies": { 1716 + "@vercel/nft": "^1.3.2", 1717 + "esbuild": "^0.28.1" 1718 + }, 1719 + "engines": { 1720 + "node": ">=20.0" 1721 + }, 1722 + "peerDependencies": { 1723 + "@sveltejs/kit": "^2.4.0" 1225 1724 } 1226 1725 }, 1227 1726 "node_modules/@sveltejs/kit": { ··· 1422 1921 "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", 1423 1922 "license": "MIT" 1424 1923 }, 1924 + "node_modules/@vercel/nft": { 1925 + "version": "1.10.2", 1926 + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-1.10.2.tgz", 1927 + "integrity": "sha512-w+WyX5Ulmj4dtTZrxaulqrjaLZHSbnPzx75SJsTNYmotKsqn1JlLnDJa+lz5hn90HJofhl/2MAtw0mCrgM3qYw==", 1928 + "dev": true, 1929 + "license": "MIT", 1930 + "dependencies": { 1931 + "@mapbox/node-pre-gyp": "^2.0.0", 1932 + "@rollup/pluginutils": "^5.1.3", 1933 + "acorn": "^8.6.0", 1934 + "acorn-import-attributes": "^1.9.5", 1935 + "async-sema": "^3.1.1", 1936 + "bindings": "^1.4.0", 1937 + "estree-walker": "2.0.2", 1938 + "glob": "^13.0.0", 1939 + "graceful-fs": "^4.2.9", 1940 + "node-gyp-build": "^4.2.2", 1941 + "picomatch": "^4.0.2", 1942 + "resolve-from": "^5.0.0" 1943 + }, 1944 + "bin": { 1945 + "nft": "out/cli.js" 1946 + }, 1947 + "engines": { 1948 + "node": ">=20" 1949 + } 1950 + }, 1951 + "node_modules/@vercel/nft/node_modules/estree-walker": { 1952 + "version": "2.0.2", 1953 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1954 + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1955 + "dev": true, 1956 + "license": "MIT" 1957 + }, 1425 1958 "node_modules/@vitest/expect": { 1426 1959 "version": "4.1.9", 1427 1960 "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", ··· 1535 2068 "url": "https://opencollective.com/vitest" 1536 2069 } 1537 2070 }, 2071 + "node_modules/abbrev": { 2072 + "version": "3.0.1", 2073 + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", 2074 + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", 2075 + "dev": true, 2076 + "license": "ISC", 2077 + "engines": { 2078 + "node": "^18.17.0 || >=20.5.0" 2079 + } 2080 + }, 1538 2081 "node_modules/acorn": { 1539 2082 "version": "8.17.0", 1540 2083 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", ··· 1557 2100 "acorn": "^8" 1558 2101 } 1559 2102 }, 2103 + "node_modules/agent-base": { 2104 + "version": "7.1.4", 2105 + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", 2106 + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", 2107 + "dev": true, 2108 + "license": "MIT", 2109 + "engines": { 2110 + "node": ">= 14" 2111 + } 2112 + }, 1560 2113 "node_modules/ansi-colors": { 1561 2114 "version": "4.1.3", 1562 2115 "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", ··· 1612 2165 "node": ">=12" 1613 2166 } 1614 2167 }, 2168 + "node_modules/async-sema": { 2169 + "version": "3.1.1", 2170 + "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", 2171 + "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", 2172 + "dev": true, 2173 + "license": "MIT" 2174 + }, 1615 2175 "node_modules/atomically": { 1616 2176 "version": "2.1.1", 1617 2177 "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", ··· 1648 2208 "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1649 2209 "dev": true, 1650 2210 "license": "MIT" 2211 + }, 2212 + "node_modules/bindings": { 2213 + "version": "1.5.0", 2214 + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 2215 + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 2216 + "dev": true, 2217 + "license": "MIT", 2218 + "dependencies": { 2219 + "file-uri-to-path": "1.0.0" 2220 + } 1651 2221 }, 1652 2222 "node_modules/bits-ui": { 1653 2223 "version": "2.18.1", ··· 1709 2279 "url": "https://paulmillr.com/funding/" 1710 2280 } 1711 2281 }, 2282 + "node_modules/chownr": { 2283 + "version": "3.0.0", 2284 + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", 2285 + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", 2286 + "dev": true, 2287 + "license": "BlueOak-1.0.0", 2288 + "engines": { 2289 + "node": ">=18" 2290 + } 2291 + }, 1712 2292 "node_modules/chrome-launcher": { 1713 2293 "version": "1.2.1", 1714 2294 "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.1.tgz", ··· 1813 2393 }, 1814 2394 "funding": { 1815 2395 "url": "https://github.com/sponsors/sindresorhus" 2396 + } 2397 + }, 2398 + "node_modules/consola": { 2399 + "version": "3.4.2", 2400 + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", 2401 + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", 2402 + "dev": true, 2403 + "license": "MIT", 2404 + "engines": { 2405 + "node": "^14.18.0 || >=16.10.0" 1816 2406 } 1817 2407 }, 1818 2408 "node_modules/convert-source-map": { ··· 1979 2569 "dev": true, 1980 2570 "license": "MIT" 1981 2571 }, 2572 + "node_modules/esbuild": { 2573 + "version": "0.28.1", 2574 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", 2575 + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", 2576 + "devOptional": true, 2577 + "hasInstallScript": true, 2578 + "license": "MIT", 2579 + "bin": { 2580 + "esbuild": "bin/esbuild" 2581 + }, 2582 + "engines": { 2583 + "node": ">=18" 2584 + }, 2585 + "optionalDependencies": { 2586 + "@esbuild/aix-ppc64": "0.28.1", 2587 + "@esbuild/android-arm": "0.28.1", 2588 + "@esbuild/android-arm64": "0.28.1", 2589 + "@esbuild/android-x64": "0.28.1", 2590 + "@esbuild/darwin-arm64": "0.28.1", 2591 + "@esbuild/darwin-x64": "0.28.1", 2592 + "@esbuild/freebsd-arm64": "0.28.1", 2593 + "@esbuild/freebsd-x64": "0.28.1", 2594 + "@esbuild/linux-arm": "0.28.1", 2595 + "@esbuild/linux-arm64": "0.28.1", 2596 + "@esbuild/linux-ia32": "0.28.1", 2597 + "@esbuild/linux-loong64": "0.28.1", 2598 + "@esbuild/linux-mips64el": "0.28.1", 2599 + "@esbuild/linux-ppc64": "0.28.1", 2600 + "@esbuild/linux-riscv64": "0.28.1", 2601 + "@esbuild/linux-s390x": "0.28.1", 2602 + "@esbuild/linux-x64": "0.28.1", 2603 + "@esbuild/netbsd-arm64": "0.28.1", 2604 + "@esbuild/netbsd-x64": "0.28.1", 2605 + "@esbuild/openbsd-arm64": "0.28.1", 2606 + "@esbuild/openbsd-x64": "0.28.1", 2607 + "@esbuild/openharmony-arm64": "0.28.1", 2608 + "@esbuild/sunos-x64": "0.28.1", 2609 + "@esbuild/win32-arm64": "0.28.1", 2610 + "@esbuild/win32-ia32": "0.28.1", 2611 + "@esbuild/win32-x64": "0.28.1" 2612 + } 2613 + }, 1982 2614 "node_modules/escalade": { 1983 2615 "version": "3.2.0", 1984 2616 "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", ··· 2063 2695 } 2064 2696 } 2065 2697 }, 2698 + "node_modules/file-uri-to-path": { 2699 + "version": "1.0.0", 2700 + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 2701 + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", 2702 + "dev": true, 2703 + "license": "MIT" 2704 + }, 2066 2705 "node_modules/forwarded-parse": { 2067 2706 "version": "2.1.2", 2068 2707 "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", ··· 2104 2743 "node": "6.* || 8.* || >= 10.*" 2105 2744 } 2106 2745 }, 2746 + "node_modules/glob": { 2747 + "version": "13.0.6", 2748 + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", 2749 + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", 2750 + "dev": true, 2751 + "license": "BlueOak-1.0.0", 2752 + "dependencies": { 2753 + "minimatch": "^10.2.2", 2754 + "minipass": "^7.1.3", 2755 + "path-scurry": "^2.0.2" 2756 + }, 2757 + "engines": { 2758 + "node": "18 || 20 || >=22" 2759 + }, 2760 + "funding": { 2761 + "url": "https://github.com/sponsors/isaacs" 2762 + } 2763 + }, 2764 + "node_modules/glob/node_modules/balanced-match": { 2765 + "version": "4.0.4", 2766 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", 2767 + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", 2768 + "dev": true, 2769 + "license": "MIT", 2770 + "engines": { 2771 + "node": "18 || 20 || >=22" 2772 + } 2773 + }, 2774 + "node_modules/glob/node_modules/brace-expansion": { 2775 + "version": "5.0.6", 2776 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", 2777 + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", 2778 + "dev": true, 2779 + "license": "MIT", 2780 + "dependencies": { 2781 + "balanced-match": "^4.0.2" 2782 + }, 2783 + "engines": { 2784 + "node": "18 || 20 || >=22" 2785 + } 2786 + }, 2787 + "node_modules/glob/node_modules/minimatch": { 2788 + "version": "10.2.5", 2789 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", 2790 + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", 2791 + "dev": true, 2792 + "license": "BlueOak-1.0.0", 2793 + "dependencies": { 2794 + "brace-expansion": "^5.0.5" 2795 + }, 2796 + "engines": { 2797 + "node": "18 || 20 || >=22" 2798 + }, 2799 + "funding": { 2800 + "url": "https://github.com/sponsors/isaacs" 2801 + } 2802 + }, 2107 2803 "node_modules/graceful-fs": { 2108 2804 "version": "4.2.11", 2109 2805 "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", ··· 2132 2828 "license": "MIT", 2133 2829 "engines": { 2134 2830 "node": ">=6.0.0" 2831 + } 2832 + }, 2833 + "node_modules/https-proxy-agent": { 2834 + "version": "7.0.6", 2835 + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", 2836 + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", 2837 + "dev": true, 2838 + "license": "MIT", 2839 + "dependencies": { 2840 + "agent-base": "^7.1.2", 2841 + "debug": "4" 2842 + }, 2843 + "engines": { 2844 + "node": ">= 14" 2135 2845 } 2136 2846 }, 2137 2847 "node_modules/image-ssim": { ··· 2602 3312 "dev": true, 2603 3313 "license": "MIT" 2604 3314 }, 3315 + "node_modules/lru-cache": { 3316 + "version": "11.5.1", 3317 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", 3318 + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", 3319 + "dev": true, 3320 + "license": "BlueOak-1.0.0", 3321 + "engines": { 3322 + "node": "20 || >=22" 3323 + } 3324 + }, 2605 3325 "node_modules/lz-string": { 2606 3326 "version": "1.5.0", 2607 3327 "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", ··· 2643 3363 "url": "https://github.com/sponsors/isaacs" 2644 3364 } 2645 3365 }, 3366 + "node_modules/minipass": { 3367 + "version": "7.1.3", 3368 + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", 3369 + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", 3370 + "dev": true, 3371 + "license": "BlueOak-1.0.0", 3372 + "engines": { 3373 + "node": ">=16 || 14 >=14.17" 3374 + } 3375 + }, 3376 + "node_modules/minizlib": { 3377 + "version": "3.1.0", 3378 + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", 3379 + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", 3380 + "dev": true, 3381 + "license": "MIT", 3382 + "dependencies": { 3383 + "minipass": "^7.1.2" 3384 + }, 3385 + "engines": { 3386 + "node": ">= 18" 3387 + } 3388 + }, 2646 3389 "node_modules/mitt": { 2647 3390 "version": "3.0.1", 2648 3391 "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", ··· 2713 3456 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2714 3457 } 2715 3458 }, 3459 + "node_modules/node-fetch": { 3460 + "version": "2.7.0", 3461 + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 3462 + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 3463 + "dev": true, 3464 + "license": "MIT", 3465 + "dependencies": { 3466 + "whatwg-url": "^5.0.0" 3467 + }, 3468 + "engines": { 3469 + "node": "4.x || >=6.0.0" 3470 + }, 3471 + "peerDependencies": { 3472 + "encoding": "^0.1.0" 3473 + }, 3474 + "peerDependenciesMeta": { 3475 + "encoding": { 3476 + "optional": true 3477 + } 3478 + } 3479 + }, 3480 + "node_modules/node-gyp-build": { 3481 + "version": "4.8.4", 3482 + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", 3483 + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", 3484 + "dev": true, 3485 + "license": "MIT", 3486 + "bin": { 3487 + "node-gyp-build": "bin.js", 3488 + "node-gyp-build-optional": "optional.js", 3489 + "node-gyp-build-test": "build-test.js" 3490 + } 3491 + }, 3492 + "node_modules/nopt": { 3493 + "version": "8.1.0", 3494 + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", 3495 + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", 3496 + "dev": true, 3497 + "license": "ISC", 3498 + "dependencies": { 3499 + "abbrev": "^3.0.0" 3500 + }, 3501 + "bin": { 3502 + "nopt": "bin/nopt.js" 3503 + }, 3504 + "engines": { 3505 + "node": "^18.17.0 || >=20.5.0" 3506 + } 3507 + }, 2716 3508 "node_modules/obug": { 2717 3509 "version": "2.1.3", 2718 3510 "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", ··· 2751 3543 "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 2752 3544 "dev": true, 2753 3545 "license": "MIT" 3546 + }, 3547 + "node_modules/path-scurry": { 3548 + "version": "2.0.2", 3549 + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", 3550 + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", 3551 + "dev": true, 3552 + "license": "BlueOak-1.0.0", 3553 + "dependencies": { 3554 + "lru-cache": "^11.0.0", 3555 + "minipass": "^7.1.2" 3556 + }, 3557 + "engines": { 3558 + "node": "18 || 20 || >=22" 3559 + }, 3560 + "funding": { 3561 + "url": "https://github.com/sponsors/isaacs" 3562 + } 2754 3563 }, 2755 3564 "node_modules/pathe": { 2756 3565 "version": "2.0.3", ··· 2993 3802 "url": "https://github.com/sponsors/ljharb" 2994 3803 } 2995 3804 }, 3805 + "node_modules/resolve-from": { 3806 + "version": "5.0.0", 3807 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", 3808 + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", 3809 + "dev": true, 3810 + "license": "MIT", 3811 + "engines": { 3812 + "node": ">=8" 3813 + } 3814 + }, 2996 3815 "node_modules/robots-parser": { 2997 3816 "version": "3.0.1", 2998 3817 "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz", ··· 3307 4126 "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", 3308 4127 "license": "MIT" 3309 4128 }, 4129 + "node_modules/tar": { 4130 + "version": "7.5.17", 4131 + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.17.tgz", 4132 + "integrity": "sha512-wPEBwzapC+2PaTYPH6e2L+cNOEE227S47wUYFqlegcs8zlLLmeb9Fcff1HVZY4Fwku/1Eyv38n7GYwB2aaS71g==", 4133 + "dev": true, 4134 + "license": "BlueOak-1.0.0", 4135 + "dependencies": { 4136 + "@isaacs/fs-minipass": "^4.0.0", 4137 + "chownr": "^3.0.0", 4138 + "minipass": "^7.1.2", 4139 + "minizlib": "^3.1.0", 4140 + "yallist": "^5.0.0" 4141 + }, 4142 + "engines": { 4143 + "node": ">=18" 4144 + } 4145 + }, 3310 4146 "node_modules/third-party-web": { 3311 4147 "version": "0.29.2", 3312 4148 "resolved": "https://registry.npmjs.org/third-party-web/-/third-party-web-0.29.2.tgz", ··· 3384 4220 "engines": { 3385 4221 "node": ">=6" 3386 4222 } 4223 + }, 4224 + "node_modules/tr46": { 4225 + "version": "0.0.3", 4226 + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 4227 + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 4228 + "dev": true, 4229 + "license": "MIT" 3387 4230 }, 3388 4231 "node_modules/tslib": { 3389 4232 "version": "2.8.1", ··· 3634 4477 "dev": true, 3635 4478 "license": "Apache-2.0" 3636 4479 }, 4480 + "node_modules/webidl-conversions": { 4481 + "version": "3.0.1", 4482 + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 4483 + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 4484 + "dev": true, 4485 + "license": "BSD-2-Clause" 4486 + }, 4487 + "node_modules/whatwg-url": { 4488 + "version": "5.0.0", 4489 + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 4490 + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 4491 + "dev": true, 4492 + "license": "MIT", 4493 + "dependencies": { 4494 + "tr46": "~0.0.3", 4495 + "webidl-conversions": "^3.0.0" 4496 + } 4497 + }, 3637 4498 "node_modules/when-exit": { 3638 4499 "version": "2.1.5", 3639 4500 "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", ··· 3729 4590 "license": "ISC", 3730 4591 "engines": { 3731 4592 "node": ">=10" 4593 + } 4594 + }, 4595 + "node_modules/yallist": { 4596 + "version": "5.0.0", 4597 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", 4598 + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", 4599 + "dev": true, 4600 + "license": "BlueOak-1.0.0", 4601 + "engines": { 4602 + "node": ">=18" 3732 4603 } 3733 4604 }, 3734 4605 "node_modules/yargs": {
+1
frontend/package.json
··· 23 23 }, 24 24 "devDependencies": { 25 25 "@sveltejs/adapter-auto": "^7.0.1", 26 + "@sveltejs/adapter-vercel": "^6.3.4", 26 27 "@sveltejs/kit": "^2.63.0", 27 28 "@sveltejs/vite-plugin-svelte": "^7.1.2", 28 29 "@types/node": "^25.9.3",
+65 -3
frontend/src/hooks.server.ts
··· 1 1 import type { Handle } from '@sveltejs/kit'; 2 2 3 3 /** 4 - * La CSP est désormais gérée de manière déclarative via svelte.config.ts 5 - * (kit.csp.mode: 'nonce'). Ce hook reste disponible pour d'autres usages 6 - * futurs (auth, logging, etc.). 4 + * Security headers (PDS-38, ADR-021). 5 + * 6 + * La CSP est gérée de manière déclarative via svelte.config.ts 7 + * (kit.csp.mode: 'nonce'). Ce hook ajoute les headers restants : 8 + * - Strict-Transport-Security (HSTS) 9 + * - X-Content-Type-Options 10 + * - Cross-Origin-Opener-Policy (COOP) 11 + * - Referrer-Policy 12 + * - Permissions-Policy 13 + * 14 + * X-Frame-Options n'est pas injecté ici car la CSP contient déjà 15 + * `frame-ancestors 'none'` qui est strictement supérieur. 7 16 */ 8 17 export const handle: Handle = async ({ event, resolve }) => { 9 18 const response = await resolve(event); 19 + 20 + // HSTS : 1 an, inclure sous-domaines 21 + response.headers.set( 22 + 'Strict-Transport-Security', 23 + 'max-age=31536000; includeSubDomains' 24 + ); 25 + 26 + // Anti-MIME sniffing 27 + response.headers.set('X-Content-Type-Options', 'nosniff'); 28 + 29 + // Isolement du contexte de navigation 30 + response.headers.set('Cross-Origin-Opener-Policy', 'same-origin'); 31 + 32 + // Referrer-Policy : ne leak que l'origine en cross-origin 33 + response.headers.set( 34 + 'Referrer-Policy', 35 + 'strict-origin-when-cross-origin' 36 + ); 37 + 38 + // Permissions-Policy : désactiver tout ce qui n'est pas utilisé 39 + response.headers.set( 40 + 'Permissions-Policy', 41 + [ 42 + 'accelerometer=()', 43 + 'ambient-light-sensor=()', 44 + 'autoplay=()', 45 + 'battery=()', 46 + 'camera=()', 47 + 'cross-origin-isolated=()', 48 + 'display-capture=()', 49 + 'document-domain=()', 50 + 'encrypted-media=()', 51 + 'execution-while-not-rendered=()', 52 + 'execution-while-out-of-viewport=()', 53 + 'fullscreen=()', 54 + 'geolocation=()', 55 + 'gyroscope=()', 56 + 'keyboard-map=()', 57 + 'magnetometer=()', 58 + 'microphone=()', 59 + 'midi=()', 60 + 'navigation-override=()', 61 + 'payment=()', 62 + 'picture-in-picture=()', 63 + 'publickey-credentials-get=()', 64 + 'screen-wake-lock=()', 65 + 'sync-xhr=()', 66 + 'usb=()', 67 + 'web-share=()', 68 + 'xr-spatial-tracking=()' 69 + ].join(', ') 70 + ); 71 + 10 72 return response; 11 73 };
+6 -5
frontend/svelte.config.ts
··· 8 8 preprocess: vitePreprocess(), 9 9 10 10 kit: { 11 - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 12 - // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 - // See https://svelte.dev/docs/kit/adapters for more information about adapters. 11 + // adapter-auto détecte Vercel automatiquement en production, 12 + // Vercel gère la redirection HTTPS (PDS-38) 14 13 adapter: adapter(), 15 14 16 15 csp: { ··· 29 28 'frame-ancestors': ['none'], 30 29 'form-action': ['self'], 31 30 'base-uri': ['self'], 32 - 'object-src': ['none'] 31 + 'object-src': ['none'], 32 + // Trusted Types (PDS-38) - trajectoire documentée, mode rapport 33 + 'report-uri': ['/api/v1/csp-report'] 33 34 } 34 35 }, 35 36 36 37 csrf: { 37 38 // Allow cross-origin requests from the backend for API routes. 38 - // The frontend is served from Vercel, backend from Render. 39 + // The frontend is served from Vercel, backend from Fly.io. 39 40 checkOrigin: process.env.NODE_ENV === 'production' 40 41 } 41 42 }
+3 -5
frontend/vite.config.ts
··· 36 36 runes: ({ filename }) => 37 37 filename.split(/[/\\]/).includes('node_modules') ? undefined : true 38 38 }, 39 - 40 - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 41 - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 42 - // See https://svelte.dev/docs/kit/adapters for more information about adapters. 39 + // adapter-auto détecte automatiquement Vercel en production 40 + // et utilise un adaptateur local en développement 43 41 adapter: adapter() 44 42 }) 45 43 ] 46 - }); 44 + });
+133
scripts/check-security-headers.sh
··· 1 + #!/usr/bin/env bash 2 + # PDS-38 — Vérifie les headers de sécurité HTTP sur les environnements cibles. 3 + # 4 + # Usage : 5 + # ./scripts/check-security-headers.sh <url> # vérifie une URL 6 + # ./scripts/check-security-headers.sh --all # vérifie prod frontend + backend 7 + # ./scripts/check-security-headers.sh --frontend # prod frontend uniquement 8 + # ./scripts/check-security-headers.sh --backend # prod backend uniquement 9 + # 10 + # Codes de sortie : 0 = tous les headers présents, 1 = au moins un header manquant. 11 + # 12 + # Headers vérifiés (ADR-021) : 13 + # - Strict-Transport-Security (HSTS) 14 + # - X-Content-Type-Options 15 + # - X-Frame-Options (fallback legacy, CSP frame-ancestors couvre sinon) 16 + # - Cross-Origin-Opener-Policy (COOP) 17 + # - Referrer-Policy 18 + # - Content-Security-Policy (CSP) 19 + # - Permissions-Policy 20 + # 21 + # Note : X-Frame-Options peut être absent si frame-ancestors 'none' est dans la CSP. 22 + # Ce script le signale comme warning, pas comme erreur. 23 + 24 + set -euo pipefail 25 + 26 + RED='\033[0;31m' 27 + GREEN='\033[0;32m' 28 + YELLOW='\033[0;33m' 29 + CYAN='\033[0;36m' 30 + NC='\033[0m' # No Color 31 + 32 + PROD_FRONTEND="https://pds-portail.vercel.app" 33 + PROD_BACKEND="https://pds-portail-backend.fly.dev/api/v1/health" 34 + 35 + REQUIRED_HEADERS=( 36 + "strict-transport-security" 37 + "x-content-type-options" 38 + "cross-origin-opener-policy" 39 + "referrer-policy" 40 + "content-security-policy" 41 + "permissions-policy" 42 + ) 43 + 44 + LEGACY_HEADERS=( 45 + "x-frame-options" 46 + ) 47 + 48 + failures=0 49 + warnings=0 50 + 51 + check_url() { 52 + local url="$1" 53 + local label="$2" 54 + 55 + echo -e "${CYAN}=== $label ===${NC}" 56 + echo "URL : $url" 57 + echo "" 58 + 59 + # Récupérer les headers HTTP (suivre les redirections) 60 + local response_headers 61 + response_headers=$(curl -sIL --max-time 15 "$url" 2>/dev/null || true) 62 + 63 + if [ -z "$response_headers" ]; then 64 + echo -e "${RED}❌ Impossible de joindre $url${NC}" 65 + echo "" 66 + failures=$((failures + 1)) 67 + return 68 + fi 69 + 70 + # Normaliser les headers en minuscules pour la comparaison 71 + local headers_lower 72 + headers_lower=$(echo "$response_headers" | tr '[:upper:]' '[:lower:]') 73 + 74 + # Vérifier les headers requis 75 + for header in "${REQUIRED_HEADERS[@]}"; do 76 + if echo "$headers_lower" | grep -q "^${header}:"; then 77 + local value 78 + value=$(echo "$headers_lower" | grep "^${header}:" | head -1 | sed 's/^[^:]*: //') 79 + echo -e " ${GREEN}✓${NC} ${header}: ${value:0:80}" 80 + else 81 + echo -e " ${RED}✗${NC} ${header}: MANQUANT" 82 + failures=$((failures + 1)) 83 + fi 84 + done 85 + 86 + # Vérifier les headers legacy (warning si absent) 87 + for header in "${LEGACY_HEADERS[@]}"; do 88 + if echo "$headers_lower" | grep -q "^${header}:"; then 89 + local value 90 + value=$(echo "$headers_lower" | grep "^${header}:" | head -1 | sed 's/^[^:]*: //') 91 + echo -e " ${GREEN}✓${NC} ${header}: ${value:0:80}" 92 + else 93 + echo -e " ${YELLOW}⚠${NC} ${header}: absent (CSP frame-ancestors couvre si présent)" 94 + warnings=$((warnings + 1)) 95 + fi 96 + done 97 + 98 + echo "" 99 + } 100 + 101 + main() { 102 + echo "PDS-38 — Vérification des headers de sécurité HTTP" 103 + echo "====================================================" 104 + echo "" 105 + 106 + local target="${1:-}" 107 + 108 + if [ "$target" = "--all" ] || [ -z "$target" ]; then 109 + check_url "$PROD_FRONTEND" "Frontend (Vercel)" 110 + check_url "$PROD_BACKEND" "Backend (Fly.io)" 111 + elif [ "$target" = "--frontend" ]; then 112 + check_url "$PROD_FRONTEND" "Frontend (Vercel)" 113 + elif [ "$target" = "--backend" ]; then 114 + check_url "$PROD_BACKEND" "Backend (Fly.io)" 115 + else 116 + check_url "$target" "URL personnalisée" 117 + fi 118 + 119 + echo "====================================================" 120 + echo -e "Erreurs : ${RED}$failures${NC}" 121 + echo -e "Warnings : ${YELLOW}$warnings${NC}" 122 + echo "" 123 + 124 + if [ "$failures" -gt 0 ]; then 125 + echo -e "${RED}❌ Des headers requis sont manquants.${NC}" 126 + exit 1 127 + else 128 + echo -e "${GREEN}✅ Tous les headers requis sont présents.${NC}" 129 + exit 0 130 + fi 131 + } 132 + 133 + main "$@"