JuliusBrussee/caveman · critical
postgres: unsafe runtime identity
Error message
postgres: unsafe runtime identity %q (session_user=%q superuser=%t bypassrls=%t owns_tenant_table=%t)
What it means
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.
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;
Example fix
-- before CREATE ROLE app_user LOGIN SUPERUSER; -- after CREATE ROLE app_owner NOLOGIN; -- owns tables CREATE ROLE app_user LOGIN IN ROLE app_owner_settings; ALTER ROLE app_user NOSUPERUSER NOBYPASSRLS;
Defensive patterns
Strategy: validation
Validate before calling
var isSuper, isBypass bool
err := pool.QueryRow(ctx,
"SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname=current_user").Scan(&isSuper, &isBypass)
if isSuper || isBypass { return errors.New("runtime role must not be superuser or BYPASSRLS") } Try / catch
if err := postgresconfig.ValidateRuntimeIdentity(ctx, pool, role); err != nil {
if strings.Contains(err.Error(), "unsafe runtime identity") {
log.Fatalf("provision a low-privilege role; %v", err)
}
return err
} Prevention
- 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.
When it happens
Trigger: NewRuntimePool/ValidateRuntimeIdentity connects as a superuser, a role with BYPASSRLS attribute, or the owner of the tenant table being validated.
Common situations: 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).
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
Related errors
- postgres: runtime current_user
- postgres: runtime identity
- postgres: expected runtime role is required
- postgres: inspect runtime identity
- postgres: organization scope is required
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/53a6799074f0accf.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/postgresconfig/postgresconfig.go:179
)
FROM pg_roles r
WHERE r.rolname=current_user
`, expectedRole).Scan(¤tUser, &sessionUser, &superuser, &bypassRLS, &member, &ownsTenantTable)
if err != nil {
return fmt.Errorf("postgres: inspect runtime identity: %w", err)
}
return validateRuntimeIdentity(currentUser, sessionUser, expectedRole, superuser, bypassRLS, member, ownsTenantTable)
}
func validateRuntimeIdentity(currentUser, sessionUser, expectedRole string, superuser, bypassRLS, member, ownsTenantTable bool) error {
// A safe SET ROLE is not a safe login: session_user may execute SET ROLE
// NONE later and recover its original privileges. Runtime pools therefore
// require the authenticated identity and effective identity to be identical.
if currentUser != sessionUser {
return fmt.Errorf("postgres: runtime current_user %q differs from session_user %q", currentUser, sessionUser)
}
if superuser || bypassRLS || ownsTenantTable {
return fmt.Errorf("postgres: unsafe runtime identity %q (session_user=%q superuser=%t bypassrls=%t owns_tenant_table=%t)", currentUser, sessionUser, superuser, bypassRLS, ownsTenantTable)
}
if !member {
return fmt.Errorf("postgres: runtime identity %q is not a member of %q", currentUser, expectedRole)
}
return nil
}
type tenantTableSchema struct {
name string
nullableOrganizationID bool
rowSecurity bool
forceRowSecurity bool
policyExists bool
policyPermissive bool
policyCommand string
policyAppliesToPublic bool
policyUsingExpression string
policyCheckExpression stringView on GitHub (pinned to 3ee70a1026)