188 lines
5.6 KiB
Diff
188 lines
5.6 KiB
Diff
--- a/models/db/engine.go
|
|
+++ b/models/db/engine.go
|
|
@@ -330,8 +330,12 @@
|
|
func (TracingHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
|
|
ctx, task := trace.NewTask(c.Ctx, "sql")
|
|
ctx = context.WithValue(ctx, sqlTask{}, task)
|
|
- trace.Log(ctx, "query", c.SQL)
|
|
- trace.Logf(ctx, "args", "%v", c.Args)
|
|
+ if isSensitiveSQL(c.Ctx) {
|
|
+ trace.Log(ctx, "query", "[owner-confined query]")
|
|
+ } else {
|
|
+ trace.Log(ctx, "query", c.SQL)
|
|
+ trace.Logf(ctx, "args", "%v", c.Args)
|
|
+ }
|
|
return ctx, nil
|
|
}
|
|
|
|
@@ -353,7 +357,11 @@
|
|
|
|
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
|
|
if c.ExecuteTime >= h.Treshold {
|
|
- h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime)
|
|
+ if isSensitiveSQL(c.Ctx) {
|
|
+ h.Logger.Log(8, log.WARN, "[Slow SQL Query] [owner-confined query] - %v", c.ExecuteTime)
|
|
+ } else {
|
|
+ h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime)
|
|
+ }
|
|
}
|
|
return nil
|
|
}
|
|
@@ -370,7 +378,11 @@
|
|
|
|
func (h *ErrorQueryHook) AfterProcess(c *contexts.ContextHook) error {
|
|
if c.Err != nil && !errors.Is(c.Err, context.Canceled) {
|
|
- h.Logger.Log(8, log.ERROR, "[Error SQL Query] %s %v - %v", c.SQL, c.Args, c.Err)
|
|
- }
|
|
- return nil
|
|
-}
|
|
+ if isSensitiveSQL(c.Ctx) {
|
|
+ h.Logger.Log(8, log.ERROR, "[Error SQL Query] [owner-confined query]")
|
|
+ } else {
|
|
+ h.Logger.Log(8, log.ERROR, "[Error SQL Query] %s %v - %v", c.SQL, c.Args, c.Err)
|
|
+ }
|
|
+ }
|
|
+ return nil
|
|
+}
|
|
--- a/models/db/context.go
|
|
+++ b/models/db/context.go
|
|
@@ -72,6 +72,12 @@
|
|
// GetEngine will get a db Engine from this context or return an Engine restricted to this context
|
|
func GetEngine(ctx context.Context) Engine {
|
|
if e := getEngine(ctx); e != nil {
|
|
+ // A private-query marker must reach XORM's real query hooks, including
|
|
+ // inside an existing engine/transaction context. Wrapping the Go
|
|
+ // context alone does not change XORM's stored session context.
|
|
+ if isSensitiveSQL(ctx) {
|
|
+ return e.Context(ctx)
|
|
+ }
|
|
return e
|
|
}
|
|
return x.Context(ctx)
|
|
--- /dev/null
|
|
+++ b/models/db/sensitive_sql.go
|
|
@@ -0,0 +1,18 @@
|
|
+// Copyright 2026 The Forgejo Authors. All rights reserved.
|
|
+// SPDX-License-Identifier: MIT
|
|
+package db
|
|
+
|
|
+import "context"
|
|
+
|
|
+type sensitiveSQLKey struct{}
|
|
+
|
|
+// WithSensitiveSQL suppresses argument, statement and driver-error disclosure
|
|
+// in the application's query hooks. The caller must separately refuse raw XORM
|
|
+// SQL logging; custom driver/server auditing remains an owner custody concern.
|
|
+func WithSensitiveSQL(ctx context.Context) context.Context {
|
|
+ return context.WithValue(ctx, sensitiveSQLKey{}, true)
|
|
+}
|
|
+func isSensitiveSQL(ctx context.Context) bool {
|
|
+ sensitive, _ := ctx.Value(sensitiveSQLKey{}).(bool)
|
|
+ return sensitive
|
|
+}
|
|
--- /dev/null
|
|
+++ b/models/db/sensitive_sql_test.go
|
|
@@ -0,0 +1,103 @@
|
|
+// Copyright 2026 The Forgejo Authors. All rights reserved.
|
|
+// SPDX-License-Identifier: MIT
|
|
+package db
|
|
+
|
|
+import (
|
|
+ "bytes"
|
|
+ "context"
|
|
+ "errors"
|
|
+ "fmt"
|
|
+ "runtime/trace"
|
|
+ "strings"
|
|
+ "testing"
|
|
+ "time"
|
|
+
|
|
+ "forgejo.org/modules/log"
|
|
+ "xorm.io/xorm"
|
|
+ "xorm.io/xorm/contexts"
|
|
+)
|
|
+
|
|
+type sensitiveSQLLog struct {
|
|
+ log.Logger
|
|
+ entries []string
|
|
+}
|
|
+
|
|
+func (l *sensitiveSQLLog) Log(_ int, _ log.Level, f string, v ...any) {
|
|
+ l.entries = append(l.entries, fmt.Sprintf(f, v...))
|
|
+}
|
|
+func TestSensitiveSQLHooks(t *testing.T) {
|
|
+ const secret = "synthetic-query-private-sentinel"
|
|
+ for _, sensitive := range []bool{false, true} {
|
|
+ ctx := context.Background()
|
|
+ if sensitive {
|
|
+ ctx = WithSensitiveSQL(ctx)
|
|
+ }
|
|
+ h := &contexts.ContextHook{Ctx: ctx, SQL: "SELECT '" + secret + "'", Args: []any{secret}, Err: errors.New(secret), ExecuteTime: time.Second}
|
|
+ logger := new(sensitiveSQLLog)
|
|
+ if err := (&SlowQueryHook{Logger: logger}).AfterProcess(h); err != nil {
|
|
+ t.Fatal(err)
|
|
+ }
|
|
+ if err := (&ErrorQueryHook{Logger: logger}).AfterProcess(h); err != nil {
|
|
+ t.Fatal(err)
|
|
+ }
|
|
+ found := strings.Contains(strings.Join(logger.entries, "\n"), secret)
|
|
+ if found == sensitive {
|
|
+ t.Fatal("query hook disclosure/redaction mismatch")
|
|
+ }
|
|
+ var out bytes.Buffer
|
|
+ if err := trace.Start(&out); err != nil {
|
|
+ t.Fatal(err)
|
|
+ }
|
|
+ next, err := (TracingHook{}).BeforeProcess(h)
|
|
+ if err != nil {
|
|
+ trace.Stop()
|
|
+ t.Fatal(err)
|
|
+ }
|
|
+ h.Ctx = next
|
|
+ err = (TracingHook{}).AfterProcess(h)
|
|
+ trace.Stop()
|
|
+ if err != nil {
|
|
+ t.Fatal(err)
|
|
+ }
|
|
+ if bytes.Contains(out.Bytes(), []byte(secret)) == sensitive {
|
|
+ t.Fatal("trace disclosure/redaction mismatch")
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+func TestSensitiveSQLActualContextPropagation(t *testing.T) {
|
|
+ engine, ok := GetEngine(DefaultContext).(*xorm.Engine)
|
|
+ if !ok {
|
|
+ t.Fatal("missing actual fixture engine")
|
|
+ }
|
|
+ logger := new(sensitiveSQLLog)
|
|
+ engine.AddHook(&SlowQueryHook{Logger: logger})
|
|
+ engine.AddHook(&ErrorQueryHook{Logger: logger})
|
|
+ engine.AddHook(TracingHook{})
|
|
+ const secret = "synthetic-actual-xorm-private-sentinel"
|
|
+ for _, sensitive := range []bool{false, true} {
|
|
+ logger.entries = nil
|
|
+ ctx := context.Context(DefaultContext)
|
|
+ if sensitive {
|
|
+ ctx = WithSensitiveSQL(ctx)
|
|
+ }
|
|
+ var out bytes.Buffer
|
|
+ if err := trace.Start(&out); err != nil {
|
|
+ t.Fatal(err)
|
|
+ }
|
|
+ _, err := GetEngine(ctx).Exec("SELECT missing_synthetic_function(?)", secret)
|
|
+ trace.Stop()
|
|
+ if err == nil {
|
|
+ t.Fatal("expected actual SQLite function refusal")
|
|
+ }
|
|
+ if len(logger.entries) < 2 {
|
|
+ t.Fatal("actual query hooks did not execute")
|
|
+ }
|
|
+ if strings.Contains(strings.Join(logger.entries, "\n"), secret) == sensitive {
|
|
+ t.Fatal("actual XORM query context did not preserve redaction")
|
|
+ }
|
|
+ if bytes.Contains(out.Bytes(), []byte(secret)) == sensitive {
|
|
+ t.Fatal("actual XORM trace context did not preserve redaction")
|
|
+ }
|
|
+ }
|
|
+}
|