| 88 | |
| 89 | @app.after_request |
| 90 | def add_api_headers(response): |
| 91 | # 仅对白名单 Origin 回显放行,替代通配 '*' |
| 92 | origin = request.headers.get('Origin') |
| 93 | if origin and origin in ALLOWED_ORIGINS: |
| 94 | response.headers['Access-Control-Allow-Origin'] = origin |
| 95 | response.headers['Vary'] = 'Origin' |
| 96 | response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-API-Key' |
| 97 | response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' |
| 98 | response.headers['Access-Control-Allow-Credentials'] = 'true' |
| 99 | response.headers['Cache-Control'] = 'no-store' |
| 100 | |
| 101 | # 安全响应头:缓解点击劫持、MIME 嗅探、Referrer 泄露等 |
| 102 | response.headers['X-Content-Type-Options'] = 'nosniff' |
| 103 | response.headers['X-Frame-Options'] = 'DENY' |
| 104 | response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' |
| 105 | response.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()' |
| 106 | # CSP:兼容现有前端(Cesium/Vue/lucide 走 unpkg CDN 与 worker/wasm/blob) |
| 107 | response.headers['Content-Security-Policy'] = ( |
| 108 | "default-src 'self'; " |
| 109 | "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://unpkg.com blob:; " |
| 110 | "style-src 'self' 'unsafe-inline' https://unpkg.com; " |
| 111 | "img-src 'self' data: blob: https:; " |
| 112 | "worker-src 'self' blob:; " |
| 113 | "connect-src 'self' https://unpkg.com; " |
| 114 | "font-src 'self' data: https://unpkg.com; " |
| 115 | "frame-ancestors 'none'; " |
| 116 | "base-uri 'self'" |
| 117 | ) |
| 118 | # 不泄露服务器版本细节 |
| 119 | response.headers['Server'] = 'smartnode' |
| 120 | return response |
| 121 | |
| 122 | |
| 123 | # ========================================== |