diff --git a/static/fsys.py b/static/fsys.py index 9c5bdc7..934890d 100644 --- a/static/fsys.py +++ b/static/fsys.py @@ -13,6 +13,7 @@ import sys # pymsg 日志客户端(集中式日志 → dbv2 Logs 表) sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from log_client import logs # noqa: E402 +from security_middleware import apply_security_headers # noqa: E402 # 基准目录:编译后取可执行文件所在目录,开发时取脚本所在目录 # 解决 Nuitka onefile 解压到 /tmp 后相对路径失效的问题 @@ -38,6 +39,7 @@ args = parser.parse_args() app = Flask(__name__) CORS(app, origins=["https://qsont.me", "https://yhdet.top", "http://localhost:9200"]) +apply_security_headers(app) # ==================== 配置文件加载 ==================== CONFIG_FILE = args.config diff --git a/waf/internal/proxy/proxy.go b/waf/internal/proxy/proxy.go index 861a9a7..d5abe66 100644 --- a/waf/internal/proxy/proxy.go +++ b/waf/internal/proxy/proxy.go @@ -48,11 +48,33 @@ func New(upstream string, waf *engine.Engine) (*Proxy, error) { rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { logclient.Error("waf", fmt.Sprintf("upstream_error path=%s err=%v", r.URL.Path, err), map[string]any{"ip": r.RemoteAddr}) + setSecurityHeaders(w.Header()) http.Error(w, "upstream unavailable", http.StatusBadGateway) } + // 统一注入安全响应头:在边缘单点覆盖所有出站响应。 + // Header.Set 覆盖下游同名头,不会产生重复字段 + rp.ModifyResponse = func(res *http.Response) error { + setSecurityHeaders(res.Header) + return nil + } return p, nil } +// setSecurityHeaders 设置安全响应头基线(与 static/security_middleware.py 保持一致)。 +// CSP 暂保留 unsafe-inline:forgot.html 与 games/*.html 含内联脚本。 +// HSTS 半年期、不含子域,站点全量 HTTPS 后可按需收紧。 +func setSecurityHeaders(h http.Header) { + h.Set("X-Frame-Options", "DENY") + h.Set("X-Content-Type-Options", "nosniff") + h.Set("X-XSS-Protection", "1; mode=block") + h.Set("Referrer-Policy", "strict-origin-when-cross-origin") + h.Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()") + h.Set("Strict-Transport-Security", "max-age=15552000") + h.Set("Content-Security-Policy", + "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "+ + "img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'") +} + // ServeHTTP 实现 http.Handler。 // 流程:ProcessRequest → 命中则 403 → 否则转发 → ProcessResponseHeaders。 func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -126,6 +148,7 @@ func (p *Proxy) block(w http.ResponseWriter, r *http.Request, ruleID int, start "ua": r.UserAgent(), "cost_ms": time.Since(start).Milliseconds(), }) + setSecurityHeaders(w.Header()) w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte("blocked by WAF\n")) }