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

  1. Create a dedicated low-privilege runtime role: CREATE ROLE app_runtime NOLOGIN; GRANT membership to it, and connect as that role.
  2. Remove dangerous attributes: ALTER ROLE app_role NOSUPERUSER NOBYPASSRLS;
  3. Ensure tenant tables are owned by a separate owner role (e.g. via an admin/migration role), not the runtime role.
  4. 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

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


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(&currentUser, &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  string

View on GitHub (pinned to 3ee70a1026)