{"record":{"id":"184d93f14fe6dac3","repo":"JuliusBrussee/caveman","slug":"postgres-runtime-current-user-q-differs-from-ses","errorCode":null,"errorMessage":"postgres: runtime current_user %q differs from session_user %q","messagePattern":"postgres: runtime current_user %q differs from session_user %q","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/postgresconfig/postgresconfig.go","lineNumber":176,"sourceCode":"\t\t         WHERE n.nspname='public'\n\t\t           AND c.relkind IN ('r','p')\n\t\t           AND pg_get_userbyid(c.relowner)=current_user\n\t\t       )\n\t\tFROM pg_roles r\n\t\tWHERE r.rolname=current_user\n\t`, expectedRole).Scan(&currentUser, &sessionUser, &superuser, &bypassRLS, &member, &ownsTenantTable)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"postgres: inspect runtime identity: %w\", err)\n\t}\n\treturn validateRuntimeIdentity(currentUser, sessionUser, expectedRole, superuser, bypassRLS, member, ownsTenantTable)\n}\n\nfunc validateRuntimeIdentity(currentUser, sessionUser, expectedRole string, superuser, bypassRLS, member, ownsTenantTable bool) error {\n\t// A safe SET ROLE is not a safe login: session_user may execute SET ROLE\n\t// NONE later and recover its original privileges. Runtime pools therefore\n\t// require the authenticated identity and effective identity to be identical.\n\tif currentUser != sessionUser {\n\t\treturn fmt.Errorf(\"postgres: runtime current_user %q differs from session_user %q\", currentUser, sessionUser)\n\t}\n\tif superuser || bypassRLS || ownsTenantTable {\n\t\treturn fmt.Errorf(\"postgres: unsafe runtime identity %q (session_user=%q superuser=%t bypassrls=%t owns_tenant_table=%t)\", currentUser, sessionUser, superuser, bypassRLS, ownsTenantTable)\n\t}\n\tif !member {\n\t\treturn fmt.Errorf(\"postgres: runtime identity %q is not a member of %q\", currentUser, expectedRole)\n\t}\n\treturn nil\n}\n\ntype tenantTableSchema struct {\n\tname                   string\n\tnullableOrganizationID bool\n\trowSecurity            bool\n\tforceRowSecurity       bool\n\tpolicyExists           bool\n\tpolicyPermissive       bool\n\tpolicyCommand          string","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/postgresconfig/postgresconfig.go#L158-L194","documentation":"For a runtime pool, the authenticated identity (session_user) and the effective identity (current_user) must be identical. If they differ, something executed SET ROLE, which could later be undone with SET ROLE NONE to regain elevated privileges — so the library fails the validation.","triggerScenarios":"ValidateRuntimeIdentity (via NewRuntimePool) connects as role A but current_user reports role B — typically because the connection string includes `options=-c role=...`, a search_path/SET ROLE hook, a pooled proxy (pgbouncer) that re-authenticates differently, or a login trigger issuing SET ROLE.","commonSituations":"Reusing a pool URL that was hardened for an admin role with SET ROLE semantics; connecting through PgBouncer configured with a different user; database-side `ALTER ROLE ... SET role` behavior or startup scripts switching roles.","solutions":["Connect directly as the intended low-privilege role: put that role in the database URL/user field and remove any `options=-c%20role=...` from the connection string.","Remove any server-side login triggers or role settings that execute SET ROLE on connection.","If using PgBouncer, ensure the frontend and backend usernames match (no user emulation) or bypass the proxy for runtime pools.","Verify with `SELECT current_user, session_user;` using the same connection parameters."],"exampleFix":"// before\nurl := \"postgres://admin:pw@db/app?options=-c%20role=tenant_role\"\n// after\nurl := \"postgres://tenant_role:pw@db/app\" // direct login as the role","handlingStrategy":"validation","validationCode":"var cu, su string\nerr := pool.QueryRow(ctx, \"SELECT current_user, session_user\").Scan(&cu, &su)\n// require cu == su before building runtime pools","typeGuard":null,"tryCatchPattern":"if err := postgresconfig.ValidateRuntimeIdentity(ctx, pool, role); err != nil {\n\tif strings.Contains(err.Error(), \"differs from session_user\") {\n\t\tlog.Fatal(\"remove SET ROLE/options from the runtime connection string\")\n\t}\n\treturn err\n}","preventionTips":["Never put `options=-c role=...` in runtime pool connection strings.","Avoid login triggers or ALTER ROLE settings that switch roles on connect.","Do not front runtime pools with pgbouncer configs that substitute users."],"tags":["postgres","security","identity","rls"],"backgroundTag":"insufficient-permissions","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}