Verify Forgejo PostgreSQL operations and preserve tracing hook context
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
This commit is contained in:
tegwick 2026-10-18 03:21:56 +02:00
parent 3fb031a47f
commit 308e83fcfb
4 changed files with 521 additions and 9 deletions

View file

@ -1,6 +1,6 @@
# Forgejo release preparation (build only)
The Apps Makefile targets package the digest-bound 37-path Forgejo 11.0.3
The Apps Makefile targets package the digest-bound 40-path Forgejo 11.0.3
source candidate in `forgejo-security-extension.patch`. It combines the original
three-group any-of gate, private secret-file input, private source-operation DB
primitives and the producer-owned Access Engine client in the OIDC callback.
@ -108,6 +108,29 @@ prove an actual browser, factor, custody or native rollout.
The current integration covers this guarded OIDC callback. Ongoing application
HTTP/API/Git/SSH paths, current operation/inverse sessions, durable private
evidence and retention, PostgreSQL/native proof and exclusion of all release
evidence and retention, deployed PostgreSQL/custody acceptance and exclusion of all release
writers remain open in RAPPS-WP-0016-T08/T09 and NK-WP-0048-T43. An old image
approval or source test result cannot admit this candidate.
## PostgreSQL operation-source verification
The source primitive has now run on a disposable PostgreSQL 16.14 instance using
the actual Forgejo engine, model schemas and OAuth configuration types. Tests
cover default, explicit-public and named schemas, exact source restoration, drift
and linked-account refusals, trigger rollback, uncooperative SQL writer exclusion
and concurrent preparation. Independent client processes and an actual server
restart verify prepared/applied/inverse journal recovery. The test-only
`source_operation_postgres` tag selects the integration tests; no runtime route,
credential, session or authority callback is added. The companion Custodian
`forgejo-postgres-source-r1` packet holds the isolated fixture, commands, source
pins, counterexamples and original failed attempts.
Actual PostgreSQL diagnostics exposed a tracing-hook composition crash. Tracing
now publishes its task context to the shared XORM hook context so later hooks
retain it. Ordinary and private SQLite traces and actual PostgreSQL failure
queries exercise this path. Controlled copies without the table lock or private
query marker fail their respective regressions. These are source/database
mechanics checks; owner admission, current authenticated operation/inverse
sessions, runtime/backup custody, retention, every release writer and native
deployed-database acceptance remain separate obligations. The previous frozen
release is historical and does not contain this context correction.

View file

@ -922,6 +922,388 @@
+ }
+}
--- /dev/null
+++ b/models/auth/source_operation_postgres_export_test.go
@@ -0,0 +1,7 @@
+//go:build source_operation_postgres
+
+package auth
+
+// Test-only seam: the production transaction body still obtains its real locks
+// and private query context. No runtime API or authority callback is added.
+var SourceOperationPostgresTransactionForTest = sourceOperationTx
--- /dev/null
+++ b/models/auth/source_operation_postgres_test.go
@@ -0,0 +1,369 @@
+//go:build source_operation_postgres
+
+package auth_test
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "runtime/trace"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ auth "forgejo.org/models/auth"
+ "forgejo.org/models/db"
+ user_model "forgejo.org/models/user"
+ "forgejo.org/modules/log"
+ "forgejo.org/modules/setting"
+ oauth2 "forgejo.org/services/auth/source/oauth2"
+
+ "github.com/lib/pq"
+ "github.com/stretchr/testify/require"
+ "xorm.io/xorm"
+)
+
+type postgresOperationLog struct {
+ log.Logger
+ mu sync.Mutex
+ entries []string
+}
+
+func (l *postgresOperationLog) Log(_ int, _ log.Level, format string, values ...any) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.entries = append(l.entries, fmt.Sprintf(format, values...))
+}
+func (l *postgresOperationLog) take() string {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ result := strings.Join(l.entries, "\n")
+ l.entries = nil
+ return result
+}
+
+// The real Forgejo engine/driver, registered model schemas and public operation
+// functions run. Only the database, application configuration and actors are
+// synthetic. No deployed owner database or authenticated operation is claimed.
+func postgresOperationDB(t *testing.T, schema string) (context.Context, *xorm.Engine, *xorm.Engine) {
+ t.Helper()
+ require.Equal(t, "/run/source-operation-postgres", os.Getenv("CUSTODIAN_PG_SOCKET"), "explicit isolated socket required")
+ oldSettings, oldContext := setting.Database, db.DefaultContext
+ oldEngine, ok := db.GetEngine(oldContext).(*xorm.Engine)
+ require.True(t, ok)
+ expectedSchema := schema
+ if expectedSchema == "" {
+ expectedSchema = "public"
+ }
+ raw, err := xorm.NewEngine("postgres", "host=/run/source-operation-postgres user=custodian_test dbname=custodian_source sslmode=disable search_path="+expectedSchema)
+ require.NoError(t, err)
+ _, err = raw.Exec(`CREATE SCHEMA IF NOT EXISTS "` + expectedSchema + `"`)
+ require.NoError(t, err)
+ setting.Database.Type = "postgres"
+ setting.Database.Host = "/run/source-operation-postgres"
+ setting.Database.Name = "custodian_source"
+ setting.Database.User = "custodian_test"
+ setting.Database.Passwd = ""
+ setting.Database.Schema = schema
+ setting.Database.SSLMode = "disable"
+ setting.Database.LogSQL = false
+ setting.Database.MaxOpenConns, setting.Database.MaxIdleConns = 8, 2
+ setting.Database.SlowQueryThreshold = 0
+ require.NoError(t, db.InitEngine(context.Background()))
+ engine, ok := db.GetEngine(db.DefaultContext).(*xorm.Engine)
+ require.True(t, ok)
+ t.Cleanup(func() {
+ _ = engine.Close()
+ _ = raw.Close()
+ setting.Database = oldSettings
+ db.SetDefaultEngine(oldContext, oldEngine)
+ })
+ version, err := engine.Query("SELECT current_setting('server_version_num')::integer / 10000 AS major, current_schema() AS schema")
+ require.NoError(t, err)
+ require.Equal(t, "16", string(version[0]["major"]))
+ require.Equal(t, expectedSchema, string(version[0]["schema"]))
+ require.NoError(t, db.SyncAllTables())
+ auth.RegisterTypeConfig(auth.OAuth2, new(oauth2.Source))
+ return db.DefaultContext, engine, raw
+}
+
+func resetPostgresOperations(t *testing.T, engine *xorm.Engine) {
+ t.Helper()
+ _, err := engine.Exec(`DROP TRIGGER IF EXISTS custodian_source_rewrite ON login_source`)
+ require.NoError(t, err)
+ _, err = engine.Exec(`TRUNCATE login_source_operation, login_source, "user", external_login_user RESTART IDENTITY CASCADE`)
+ require.NoError(t, err)
+}
+
+func TestSourceOperationPostgres(t *testing.T) {
+ for _, profile := range []struct{ name, schema string }{{"default", ""}, {"public", "public"}, {"named", "custodian_named"}} {
+ t.Run(profile.name, func(t *testing.T) {
+ ctx, engine, raw := postgresOperationDB(t, profile.schema)
+ run := func(name string, f func(*testing.T)) {
+ t.Run(name, func(t *testing.T) { resetPostgresOperations(t, engine); f(t) })
+ }
+ run("create-reconcile-inverse", func(t *testing.T) {
+ before := operationRows(t, ctx)
+ _, err := auth.PrepareSourceOperation(ctx, "postgres-create-operation", "postgres-create-case", 0, operationSource())
+ require.NoError(t, err)
+ require.Equal(t, before, operationRows(t, ctx))
+ r, err := auth.ApplySourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
+ require.NoError(t, err)
+ require.Equal(t, "applied", r.Phase)
+ require.Positive(t, r.SourceID)
+ post := operationRows(t, ctx)
+ again, err := auth.ApplySourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
+ require.NoError(t, err)
+ require.Equal(t, r, again)
+ require.Equal(t, post, operationRows(t, ctx))
+ _, err = auth.InverseSourceOperation(ctx, "postgres-create-operation", "postgres-wrong-case")
+ require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
+ r, err = auth.ReconcileSourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
+ require.NoError(t, err)
+ require.Equal(t, "applied", r.Phase)
+ r, err = auth.InverseSourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
+ require.NoError(t, err)
+ require.Equal(t, "reverted", r.Phase)
+ require.Equal(t, before, operationRows(t, ctx))
+ _, err = auth.ApplySourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
+ require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
+ })
+ run("exact-raw-preexisting-restoration", func(t *testing.T) {
+ s := operationSource()
+ require.NoError(t, auth.CreateSource(ctx, s))
+ _, err := engine.Exec(`UPDATE login_source SET cfg = ? WHERE id = ?`, `{"Provider":"openidConnect","ClientID":"synthetic-client","ClientSecret":"synthetic-private-original","FutureGuard":{"x":true}}`, s.ID)
+ require.NoError(t, err)
+ before := operationRows(t, ctx)
+ s.Cfg.(*oauth2.Source).ClientSecret = "synthetic-private-new"
+ _, err = auth.PrepareSourceOperation(ctx, "postgres-restore-operation", "postgres-restore-case", s.ID, s)
+ require.NoError(t, err)
+ _, err = auth.ApplySourceOperation(ctx, "postgres-restore-operation", "postgres-restore-case")
+ require.NoError(t, err)
+ _, err = auth.InverseSourceOperation(ctx, "postgres-restore-operation", "postgres-restore-case")
+ require.NoError(t, err)
+ require.Equal(t, before, operationRows(t, ctx))
+ })
+ run("unchanged-preexisting-survives", func(t *testing.T) {
+ s := operationSource()
+ require.NoError(t, auth.CreateSource(ctx, s))
+ before := operationRows(t, ctx)
+ _, err := auth.PrepareSourceOperation(ctx, "postgres-unchanged-operation", "postgres-unchanged-case", s.ID, s)
+ require.NoError(t, err)
+ r, err := auth.ApplySourceOperation(ctx, "postgres-unchanged-operation", "postgres-unchanged-case")
+ require.NoError(t, err)
+ require.Equal(t, "unchanged", r.Phase)
+ _, err = auth.InverseSourceOperation(ctx, "postgres-unchanged-operation", "postgres-unchanged-case")
+ require.NoError(t, err)
+ require.Equal(t, before, operationRows(t, ctx))
+ })
+ for _, stage := range []string{"before-apply", "before-inverse"} {
+ run("private-drift-"+stage, func(t *testing.T) {
+ s := operationSource()
+ require.NoError(t, auth.CreateSource(ctx, s))
+ _, err := auth.PrepareSourceOperation(ctx, "postgres-drift-operation", "postgres-drift-case", s.ID, s)
+ require.NoError(t, err)
+ if stage == "before-inverse" {
+ _, err = auth.ApplySourceOperation(ctx, "postgres-drift-operation", "postgres-drift-case")
+ require.NoError(t, err)
+ }
+ _, err = engine.Exec(`UPDATE login_source SET cfg = cfg || ' ' WHERE id = ?`, s.ID)
+ require.NoError(t, err)
+ before := operationRows(t, ctx)
+ if stage == "before-apply" {
+ _, err = auth.ApplySourceOperation(ctx, "postgres-drift-operation", "postgres-drift-case")
+ } else {
+ _, err = auth.InverseSourceOperation(ctx, "postgres-drift-operation", "postgres-drift-case")
+ }
+ require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
+ require.Equal(t, before, operationRows(t, ctx))
+ })
+ }
+ for _, kind := range []string{"user", "external"} {
+ run("linked-"+kind+"-refuses-deletion", func(t *testing.T) {
+ _, err := auth.PrepareSourceOperation(ctx, "postgres-linked-operation", "postgres-linked-case", 0, operationSource())
+ require.NoError(t, err)
+ r, err := auth.ApplySourceOperation(ctx, "postgres-linked-operation", "postgres-linked-case")
+ require.NoError(t, err)
+ if kind == "user" {
+ _, err = engine.Insert(&user_model.User{ID: 1, LowerName: "synthetic", Name: "synthetic", Email: "synthetic@invalid.test", LoginSource: r.SourceID})
+ } else {
+ _, err = engine.Exec(`INSERT INTO external_login_user (external_id, user_id, login_source_id) VALUES ('synthetic-external', 1, ?)`, r.SourceID)
+ }
+ require.NoError(t, err)
+ before := operationRows(t, ctx)
+ _, err = auth.InverseSourceOperation(ctx, "postgres-linked-operation", "postgres-linked-case")
+ require.True(t, auth.IsErrSourceInUse(err))
+ require.Equal(t, before, operationRows(t, ctx))
+ var count int64
+ if kind == "user" {
+ count, err = engine.Table("user").Where("login_source = ?", r.SourceID).Count()
+ } else {
+ count, err = engine.Table("external_login_user").Where("login_source_id = ?", r.SourceID).Count()
+ }
+ require.NoError(t, err)
+ require.EqualValues(t, 1, count)
+ })
+ }
+ run("unexpected-trigger-post-state-rolls-back", func(t *testing.T) {
+ before := operationRows(t, ctx)
+ _, err := auth.PrepareSourceOperation(ctx, "postgres-trigger-operation", "postgres-trigger-case", 0, operationSource())
+ require.NoError(t, err)
+ _, err = engine.Exec(`CREATE OR REPLACE FUNCTION custodian_rewrite_source() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.name := 'unexpected-rewrite'; RETURN NEW; END $$`)
+ require.NoError(t, err)
+ _, err = engine.Exec(`CREATE TRIGGER custodian_source_rewrite BEFORE INSERT ON login_source FOR EACH ROW EXECUTE FUNCTION custodian_rewrite_source()`)
+ require.NoError(t, err)
+ _, err = auth.ApplySourceOperation(ctx, "postgres-trigger-operation", "postgres-trigger-case")
+ require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
+ require.Equal(t, before, operationRows(t, ctx))
+ r, err := auth.ReconcileSourceOperation(ctx, "postgres-trigger-operation", "postgres-trigger-case")
+ require.NoError(t, err)
+ require.Equal(t, "prepared", r.Phase)
+ })
+ run("driver-error-rollback-and-private-diagnostics", func(t *testing.T) {
+ captured := new(postgresOperationLog)
+ engine.AddHook(&db.SlowQueryHook{Logger: captured})
+ engine.AddHook(&db.ErrorQueryHook{Logger: captured})
+ const control = "synthetic-postgres-unmarked-control"
+ _, err := db.GetEngine(ctx).Exec("SELECT missing_synthetic_function(?)", control)
+ require.Error(t, err)
+ require.Contains(t, captured.take(), control, "actual unmarked diagnostic control must be observable")
+ _, err = engine.Exec(`CREATE OR REPLACE FUNCTION custodian_fail_source() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION '%', NEW.cfg; END $$`)
+ require.NoError(t, err)
+ _, err = engine.Exec(`CREATE TRIGGER custodian_source_rewrite BEFORE INSERT ON login_source FOR EACH ROW EXECUTE FUNCTION custodian_fail_source()`)
+ require.NoError(t, err)
+ before := operationRows(t, ctx)
+ _, err = auth.PrepareSourceOperation(ctx, "postgres-error-operation", "postgres-error-case", 0, operationSource())
+ require.NoError(t, err)
+ captured.take()
+ var tracing bytes.Buffer
+ require.NoError(t, trace.Start(&tracing))
+ _, err = auth.ApplySourceOperation(ctx, "postgres-error-operation", "postgres-error-case")
+ trace.Stop()
+ require.ErrorIs(t, err, auth.ErrSourceOperationStorage)
+ messages := captured.take()
+ require.Contains(t, messages, "[Error SQL Query] [owner-confined query]")
+ require.Contains(t, messages, "[Slow SQL Query] [owner-confined query]")
+ require.NotContains(t, messages, "synthetic-private-original")
+ require.False(t, bytes.Contains(tracing.Bytes(), []byte("synthetic-private-original")), "actual PostgreSQL trace disclosed private cfg")
+ require.Equal(t, before, operationRows(t, ctx))
+ r, err := auth.ReconcileSourceOperation(ctx, "postgres-error-operation", "postgres-error-case")
+ require.NoError(t, err)
+ require.Equal(t, "prepared", r.Phase)
+ })
+ run("uncooperative-writers-excluded", func(t *testing.T) {
+ locked, release, done := make(chan struct{}), make(chan struct{}), make(chan error, 1)
+ var once sync.Once
+ releaseLock := func() { once.Do(func() { close(release) }) }
+ defer releaseLock()
+ go func() {
+ done <- auth.SourceOperationPostgresTransactionForTest(ctx, func(context.Context) error { close(locked); <-release; return nil })
+ }()
+ select {
+ case <-locked:
+ case err := <-done:
+ t.Fatalf("lock acquisition refused: %v", err)
+ case <-time.After(5 * time.Second):
+ t.Fatal("lock acquisition timed out")
+ }
+ statements := []string{`UPDATE login_source_operation SET phase=phase WHERE operation_id='absent'`, `UPDATE login_source SET name=name WHERE id=-1`, `UPDATE "user" SET login_source=login_source WHERE id=-1`, `UPDATE external_login_user SET login_source_id=login_source_id WHERE user_id=-1`}
+ for _, statement := range statements {
+ session := raw.NewSession()
+ require.NoError(t, session.Begin())
+ _, err := session.Exec(`SET LOCAL lock_timeout='150ms'`)
+ require.NoError(t, err)
+ _, err = session.Exec(statement)
+ var pgerr *pq.Error
+ blocked := errors.As(err, &pgerr) && pgerr.Code == "55P03"
+ _ = session.Rollback()
+ _ = session.Close()
+ require.True(t, blocked, "an uncooperative writer escaped the actual table lock")
+ }
+ releaseLock()
+ select {
+ case err := <-done:
+ require.NoError(t, err)
+ case <-time.After(5 * time.Second):
+ t.Fatal("transaction did not finish")
+ }
+ for _, statement := range statements {
+ _, err := raw.Exec(statement)
+ require.NoError(t, err)
+ }
+ })
+ run("concurrent-preparation-has-one-owner", func(t *testing.T) {
+ start, done := make(chan struct{}), make(chan error, 2)
+ for _, id := range []string{"postgres-concurrent-first", "postgres-concurrent-second"} {
+ go func(id string) {
+ <-start
+ _, err := auth.PrepareSourceOperation(ctx, id, "postgres-concurrent-case", 0, operationSource())
+ done <- err
+ }(id)
+ }
+ close(start)
+ ok, conflict := 0, 0
+ for range 2 {
+ select {
+ case err := <-done:
+ if err == nil {
+ ok++
+ } else if errors.Is(err, auth.ErrSourceOperationConflict) {
+ conflict++
+ } else {
+ t.Fatal("unexpected concurrent preparation error")
+ }
+ case <-time.After(5 * time.Second):
+ t.Fatal("concurrent preparation timed out")
+ }
+ }
+ require.Equal(t, 1, ok)
+ require.Equal(t, 1, conflict)
+ })
+ })
+ }
+}
+
+func TestSourceOperationPostgresProcessRecovery(t *testing.T) {
+ step := os.Getenv("CUSTODIAN_PG_STEP")
+ require.NotEmpty(t, step, "explicit synthetic recovery phase required")
+ ctx, engine, _ := postgresOperationDB(t, "custodian_recovery")
+ switch step {
+ case "prepare":
+ resetPostgresOperations(t, engine)
+ _, err := auth.PrepareSourceOperation(ctx, "postgres-process-operation", "postgres-process-case", 0, operationSource())
+ require.NoError(t, err)
+ case "crash":
+ engine.AddHook(operationCrashAfterSourceInsert{})
+ _, _ = auth.ApplySourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
+ t.Fatal("actual source insert did not reach the process-death hook")
+ case "recover-prepared":
+ r, err := auth.ReconcileSourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
+ require.NoError(t, err)
+ require.Equal(t, "prepared", r.Phase)
+ n, err := engine.Table("login_source").Count()
+ require.NoError(t, err)
+ require.Zero(t, n)
+ _, err = auth.ApplySourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
+ require.NoError(t, err)
+ case "recover-applied":
+ r, err := auth.ReconcileSourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
+ require.NoError(t, err)
+ require.Equal(t, "applied", r.Phase)
+ n, err := engine.Table("login_source").Count()
+ require.NoError(t, err)
+ require.EqualValues(t, 1, n)
+ _, err = auth.InverseSourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
+ require.NoError(t, err)
+ case "recover-inverse":
+ r, err := auth.ReconcileSourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
+ require.NoError(t, err)
+ require.Equal(t, "reverted", r.Phase)
+ n, err := engine.Table("login_source").Count()
+ require.NoError(t, err)
+ require.Zero(t, n)
+ default:
+ t.Fatal("unknown synthetic recovery phase")
+ }
+}
--- /dev/null
+++ b/models/auth/source_operation_process_test.go
@@ -0,0 +1,97 @@
+// Copyright 2026 The Forgejo Authors. All rights reserved.
@ -1276,12 +1658,15 @@
return x.Context(ctx)
--- a/models/db/engine.go
+++ b/models/db/engine.go
@@ -330,8 +330,12 @@
@@ -330,8 +330,15 @@
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)
+ // XORM passes the same ContextHook to every BeforeProcess hook. Publish
+ // the task there too, so a later hook returning c.Ctx retains it.
+ c.Ctx = ctx
+ if isSensitiveSQL(c.Ctx) {
+ trace.Log(ctx, "query", "[owner-confined query]")
+ } else {
@ -1291,7 +1676,7 @@
return ctx, nil
}
@@ -353,7 +357,11 @@
@@ -353,7 +360,11 @@
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
if c.ExecuteTime >= h.Treshold {
@ -1304,7 +1689,7 @@
}
return nil
}
@@ -370,7 +378,11 @@
@@ -370,7 +381,11 @@
func (h *ErrorQueryHook) AfterProcess(c *contexts.ContextHook) error {
if c.Err != nil && !errors.Is(c.Err, context.Canceled) {
@ -1520,6 +1905,63 @@
+ }
+ }
+}
--- /dev/null
+++ b/models/db/tracing_context_test.go
@@ -0,0 +1,54 @@
+package db
+
+import (
+ "bytes"
+ "context"
+ "runtime/trace"
+ "testing"
+
+ "xorm.io/xorm"
+ "xorm.io/xorm/contexts"
+)
+
+type followingQueryHook struct{}
+
+func (followingQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
+ return c.Ctx, nil
+}
+func (followingQueryHook) AfterProcess(*contexts.ContextHook) error { return nil }
+
+func TestTracingContextSurvivesFollowingHooks(t *testing.T) {
+ for _, sensitive := range []bool{false, true} {
+ for _, fail := range []bool{false, true} {
+ engine, err := xorm.NewEngine("sqlite3", ":memory:")
+ if err != nil {
+ t.Fatal(err)
+ }
+ engine.AddHook(TracingHook{})
+ engine.AddHook(followingQueryHook{})
+ ctx := context.Background()
+ if sensitive {
+ ctx = WithSensitiveSQL(ctx)
+ }
+ var output bytes.Buffer
+ if err := trace.Start(&output); err != nil {
+ t.Fatal(err)
+ }
+ func() {
+ defer trace.Stop()
+ query := "SELECT ?"
+ if fail {
+ query = "SELECT missing_synthetic_function(?)"
+ }
+ _, err = engine.Context(ctx).Exec(query, "synthetic-following-hook-sentinel")
+ }()
+ _ = engine.Close()
+ if (err != nil) != fail {
+ t.Fatal("unexpected real SQLite query outcome")
+ }
+ if bytes.Contains(output.Bytes(), []byte("synthetic-following-hook-sentinel")) == sensitive {
+ t.Fatal("following hook changed private or ordinary trace disclosure")
+ }
+ }
+ }
+}
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -322,6 +322,13 @@

View file

@ -3,13 +3,13 @@
"forgejo_version": "11.0.3",
"source_archive_sha256": "1ddd8c926ca07d40436897971458ace7dcf1d3496a0813ab9e0d869edef2365c",
"source_commit": "b81aa4d0807cc1e2d6a2fbafd0b75c46cc0ae237",
"patch_sha256": "15cb7f13b1b1d1095cc344176ca337f705333a436a70ae803fcfd3d7f802ccf4",
"patch_sha256": "feb1079399a43989742ae0a946c1ada50113d1821e6541f97f0b5592ee1fc20c",
"go_archive_sha256": "3333f6ea53afa971e9078895eaa4ac7204a8c6b5c68c10e6bc9a33e8e391bdd8",
"go_version": "1.24.3",
"node_archive_sha256": "dafe2e8f82cb97de1bd10db9e2ec4c07bbf53389b0799b1e095a918951e78fd4",
"node_version": "22.15.0",
"source_date_epoch": 1752105237,
"release_version": "11.0.3+cust80.15cb7f13",
"release_version": "11.0.3+cust80.feb10793",
"tags": "bindata timetzdata sqlite sqlite_unlock_notify",
"runtime_base": "code.forgejo.org/forgejo/forgejo@sha256:41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b",
"runtime_index_digest": "sha256:691657c7e64af7730a2e885e6d022cf16ae60724931c5392bd61838afadcaeba",
@ -43,6 +43,14 @@
"path": "models/auth/source_operation_internal_test.go",
"sha256": "af0f424d3a1fd2b29793b64baa1fa07284fee7ea0b7721c503db4efac0d871cb"
},
{
"path": "models/auth/source_operation_postgres_export_test.go",
"sha256": "b9d1553144766b15c408295b87506106935abf4ed61460598b58dc81825e808d"
},
{
"path": "models/auth/source_operation_postgres_test.go",
"sha256": "4a37504e5af00580d114ef1bed5cb32e09e4ec2de5fd3192adef043a4ea5f7fe"
},
{
"path": "models/auth/source_operation_process_test.go",
"sha256": "40d33b11a01cb659f0bf6f8b545c4d79910e8c082a789060bcbc9cadeee965bd"
@ -57,7 +65,7 @@
},
{
"path": "models/db/engine.go",
"sha256": "43683804d0c5bc6ead1dba0344c69dd196825cf3d5eb502af8d932a7b8207b96"
"sha256": "d21a5b0b9ca37ac48222b79007467a232abc38edda99b3c5f54f1f4c5ab795e8"
},
{
"path": "models/db/sensitive_sql.go",
@ -71,6 +79,10 @@
"path": "models/db/sensitive_sql_test.go",
"sha256": "de6353c955047c3311747e4777b9f9b2195e9efb19160b971dd758dfc94cf31b"
},
{
"path": "models/db/tracing_context_test.go",
"sha256": "612b60c67a2be7e2da9173b95d5ef5abd7b9e1e377360828e39a622610bba070"
},
{
"path": "routers/web/admin/auths.go",
"sha256": "3498229e06fb9ff52396bcb5e68c0cc2d2ad24e6494533d8717d7c833f3e2923"
@ -182,5 +194,13 @@
"runtime_base_config_sha256": "bc5a3df111729582faadebd01d126ba769e2b65d0c62f306f6bc79f4e562452b",
"patch_file": "forgejo-security-extension.patch",
"candidate_review_status": "coordinator tested; independent owner review pending",
"producer_source_commit": "ca0cb75729a63c9288b7333382d0fa3eb1e8c9a3"
"producer_source_commit": "ca0cb75729a63c9288b7333382d0fa3eb1e8c9a3",
"optional_source_tests": {
"source_operation_postgres": {
"database_major": 16,
"synthetic_fixture_image": "postgres@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777",
"source_only": true,
"production_connection_or_credentials": false
}
}
}

View file

@ -242,6 +242,33 @@ If links prevent removal, retain the extended image and reconcile. The earlier
T02 single-Helm-rollback statement is not sufficient recovery for this extension;
its historical text is retained and corrected here.
2026-10-18 PostgreSQL/source-context continuation for T08: actual Forgejo model
schemas, OAuth configuration types and production DB initialization run on a
disposable PostgreSQL 16.14 instance. All 37 test/subtest results pass across
default, explicit-public and named schemas. Separate client processes prove
rollback after process death immediately following INSERT, committed apply
survival through a real server restart, and subsequent reconciliation/inverse.
Exact raw restoration, private drift, linked-user/external-link refusal, trigger
rollback, table-writer exclusion and concurrent preparation are covered.
Controlled copies with the table lock or private-query context removed fail the
corresponding tests in all three schema profiles. All owned fixture containers
and volumes were removed; no deployed database or real credential was used.
The diagnostics check exposed a real tracing-hook composition panic. The new
40-path build candidate publishes the task context into XORM's shared query
context so subsequent hooks preserve it. Actual private/ordinary SQLite traces
and PostgreSQL driver-error hooks cover the correction. Six focused SQLite DB
checks and all 94 selected composed package results pass without skips. The
previous panic, fixture/compilation failures and refinement history remain in
Custodian forgejo-postgres-source-r1. Complete release evidence uses the new
feb10793 patch; the prior frozen artifact is historical.
This proves bounded source/database mechanics. T08 remains progress for current
authenticated initiating/inverse sessions, release-wide writer exclusion, private
evidence/custody/retention and native deployed-database acceptance. Independent
owner review and exact package/type/runtime admission remain open. T07/T09 and
all original task UUIDs and statuses are preserved.
## T09 — Enforce authenticated Access Engine decisions in the application path
```task