release: prepare v0.1.131

This commit is contained in:
kone
2026-05-14 05:18:31 +08:00
parent 066ceb823e
commit 41e60b20d6
20 changed files with 2818 additions and 337 deletions
@@ -91,8 +91,17 @@ func SecurityHeaders(cfg config.CSPConfig, getFrameSrcOrigins func() []string) g
}
}
embeddedMode := isEmbeddedUIRequest(c)
if embeddedMode {
finalPolicy = setDirective(finalPolicy, "frame-ancestors", "'self'")
}
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
if embeddedMode {
c.Header("X-Frame-Options", "SAMEORIGIN")
} else {
c.Header("X-Frame-Options", "DENY")
}
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
if isAPIRoutePath(c) {
c.Next()
@@ -127,6 +136,13 @@ func isAPIRoutePath(c *gin.Context) bool {
strings.HasPrefix(path, "/images")
}
func isEmbeddedUIRequest(c *gin.Context) bool {
if c == nil || c.Request == nil || c.Request.URL == nil {
return false
}
return strings.EqualFold(strings.TrimSpace(c.Query("ui_mode")), "embedded")
}
// enhanceCSPPolicy 确保 CSP 策略包含 nonce 支持和支付 SDK 必需域名。
// 这样旧配置文件没有及时补域名时,前端支付组件仍能正常加载。
func enhanceCSPPolicy(policy string) string {
@@ -195,3 +211,31 @@ func addToDirective(policy, directive, value string) string {
insertPos := idx + endIdx
return policy[:insertPos] + " " + value + policy[insertPos:]
}
func setDirective(policy, directive, value string) string {
directives := strings.Split(policy, ";")
replaced := false
for i, rawDirective := range directives {
fields := strings.Fields(strings.TrimSpace(rawDirective))
if len(fields) == 0 {
continue
}
if fields[0] == directive {
directives[i] = directive + " " + value
replaced = true
}
}
if !replaced {
directives = append(directives, directive+" "+value)
}
result := make([]string, 0, len(directives))
for _, rawDirective := range directives {
trimmed := strings.TrimSpace(rawDirective)
if trimmed == "" {
continue
}
result = append(result, trimmed)
}
return strings.Join(result, "; ")
}
@@ -151,6 +151,24 @@ func TestSecurityHeaders(t *testing.T) {
assert.Empty(t, GetNonceFromContext(c))
})
t.Run("embedded_ui_allows_same_origin_frame", func(t *testing.T) {
cfg := config.CSPConfig{
Enabled: true,
Policy: "default-src 'self'; frame-ancestors 'none'",
}
middleware := SecurityHeaders(cfg, nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, "/docs/api.html?ui_mode=embedded", nil)
middleware(c)
assert.Equal(t, "SAMEORIGIN", w.Header().Get("X-Frame-Options"))
assert.Contains(t, w.Header().Get("Content-Security-Policy"), "frame-ancestors 'self'")
assert.NotContains(t, w.Header().Get("Content-Security-Policy"), "frame-ancestors 'none'")
})
t.Run("csp_enabled_with_nonce_placeholder", func(t *testing.T) {
cfg := config.CSPConfig{
Enabled: true,
@@ -410,6 +428,23 @@ func TestAddToDirective(t *testing.T) {
})
}
func TestSetDirective(t *testing.T) {
t.Run("replaces_existing_directive", func(t *testing.T) {
policy := "default-src 'self'; frame-ancestors 'none'; script-src 'self'"
result := setDirective(policy, "frame-ancestors", "'self'")
assert.Contains(t, result, "frame-ancestors 'self'")
assert.NotContains(t, result, "frame-ancestors 'none'")
})
t.Run("adds_directive_when_missing", func(t *testing.T) {
policy := "default-src 'self'; script-src 'self'"
result := setDirective(policy, "frame-ancestors", "'self'")
assert.Contains(t, result, "frame-ancestors 'self'")
})
}
// Benchmark tests
func BenchmarkGenerateNonce(b *testing.B) {
for i := 0; i < b.N; i++ {