gcli2api / internal /panel /index.go
a3216's picture
chore: 同步到上游 1.12.0-panel + 凭证同步/独立启动器/保活
6d60378 verified
Raw History Blame Contribute Delete
2.97 kB
// index.go 面板静态资源与安全响应头。
//
// 资源经 go:embed 打进二进制(随服务部署,无外部构建步骤):
// - index.html 页面骨架
// - app.js 全部前端逻辑(独立文件而非内联,为了启用无需 unsafe-inline 的严格 CSP)
//
// 安全头对"面板页面与全部 /panel/api/* 响应"统一生效:CSP 限制脚本只能来自本服务,
// 禁止被 iframe 嵌套(防点击劫持),禁 MIME 嗅探,并声明不泄露 Referer 出去。
package panel
import (
_ "embed"
"net/http"
)
//go:embed index.html
var indexHTML []byte
//go:embed app.js
var appJS []byte
// csp 内容安全策略(严格版,无需 unsafe-inline):
// - default-src 'none' 默认全禁,逐个开口
// - script-src 'self' 只跑同源脚本(app.js);页面无内联事件处理器/内联脚本
// - style-src 'self' 'unsafe-inline'
// style 的内联是设计取舍:页面有少量 style="..." 属性(进度条宽度、表格列宽),
// 允许内联样式不会导致脚本执行;仍禁止外部样式域与 @import 外链。
// - connect-src 'self' 前端 fetch 只能打本服务
// - img-src 'self' data: 图标/内联图
// - form-action 'none' 页面无表单提交目标(配置页是 JS 提交)
// - frame-ancestors 'none' 禁止被任何站点 iframe 嵌套(点击劫持)
// - base-uri 'none' 禁止注入 <base> 改写相对路径
const csp = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; " +
"connect-src 'self'; img-src 'self' data:; form-action 'none'; " +
"frame-ancestors 'none'; base-uri 'none'"
// setSecurityHeaders 写入面板统一安全响应头(页面与 API 都要,API 也含 JSON 数据)。
func setSecurityHeaders(w http.ResponseWriter) {
w.Header().Set("Content-Security-Policy", csp)
w.Header().Set("X-Content-Type-Options", "nosniff") // 禁 MIME 嗅探
w.Header().Set("X-Frame-Options", "DENY") // 老浏览器兜底(CSP frame-ancestors 的等价项)
w.Header().Set("Referrer-Policy", "no-referrer") // 不外泄面板地址给外部站点
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
}
// index 输出面板页面(静态无秘密;数据接口 /panel/api/* 才走鉴权)。
func (p *Panel) index(w http.ResponseWriter, r *http.Request) {
setSecurityHeaders(w)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(indexHTML)
}
// appScript 输出前端逻辑(同源脚本,供 CSP script-src 'self' 加载)。
func (p *Panel) appScript(w http.ResponseWriter, r *http.Request) {
setSecurityHeaders(w)
w.Header().Set("Content-Type", "text/javascript; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(appJS)
}