{"record":{"id":"53a6799074f0accf","repo":"JuliusBrussee/caveman","slug":"postgres-unsafe-runtime-identity-q-session-user","errorCode":null,"errorMessage":"postgres: unsafe runtime identity %q (session_user=%q superuser=%t bypassrls=%t owns_tenant_table=%t)","messagePattern":"postgres: unsafe runtime identity %q \\(session_user=%q superuser=%t bypassrls=%t owns_tenant_table=%t\\)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"shared/platform/postgresconfig/postgresconfig.go","lineNumber":179,"sourceCode":"\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\n\tpolicyAppliesToPublic  bool\n\tpolicyUsingExpression  string\n\tpolicyCheckExpression  string","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/postgresconfig/postgresconfig.go#L161-L197","documentation":"Even when current_user equals session_user, the runtime identity must not be superuser, must not have BYPASSRLS, and must not own the tenant tables — any of which would defeat row-level security. The error reports the full privilege fingerprint.","triggerScenarios":"NewRuntimePool/ValidateRuntimeIdentity connects as a superuser, a role with BYPASSRLS attribute, or the owner of the tenant table being validated.","commonSituations":"Developers pointing the runtime pool at the postgres superuser during local testing; migrations run with the same credentials as the app; the app role accidentally created WITH SUPERUSER or BYPASSRLS; tables created by the app role itself (making it the owner).","solutions":["Create a dedicated low-privilege runtime role: CREATE ROLE app_runtime NOLOGIN; GRANT membership to it, and connect as that role.","Remove dangerous attributes: ALTER ROLE app_role NOSUPERUSER NOBYPASSRLS;","Ensure tenant tables are owned by a separate owner role (e.g. via an admin/migration role), not the runtime role.","Confirm attributes with SELECT rolname, rolsuper, rolbypassrls FROM pg_roles WHERE rolname = current_user;"],"exampleFix":"-- before\nCREATE ROLE app_user LOGIN SUPERUSER;\n-- after\nCREATE ROLE app_owner NOLOGIN; -- owns tables\nCREATE ROLE app_user LOGIN IN ROLE app_owner_settings;\nALTER ROLE app_user NOSUPERUSER NOBYPASSRLS;","handlingStrategy":"validation","validationCode":"var isSuper, isBypass bool\nerr := pool.QueryRow(ctx,\n\t\"SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname=current_user\").Scan(&isSuper, &isBypass)\nif isSuper || isBypass { return errors.New(\"runtime role must not be superuser or BYPASSRLS\") }","typeGuard":null,"tryCatchPattern":"if err := postgresconfig.ValidateRuntimeIdentity(ctx, pool, role); err != nil {\n\tif strings.Contains(err.Error(), \"unsafe runtime identity\") {\n\t\tlog.Fatalf(\"provision a low-privilege role; %v\", err)\n\t}\n\treturn err\n}","preventionTips":["Separate roles for table ownership, migrations, and runtime access.","Assert NOSUPERUSER NOBYPASSRLS in role provisioning migrations.","Never use the postgres superuser for application pools, even locally."],"tags":["postgres","security","rls","privileges"],"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"}