{"record":{"id":"6512cab43a38cb6d","repo":"JuliusBrussee/caveman","slug":"postgres-organization-scope-is-required","errorCode":null,"errorMessage":"postgres: organization scope is required","messagePattern":"postgres: organization scope is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/postgresconfig/postgresconfig.go","lineNumber":767,"sourceCode":"\t\t)\n\t\tSELECT coalesce(string_agg(violation, '; ' ORDER BY violation), '') FROM violations\n\t`).Scan(&violations)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"postgres: inspect resolver schema: %w\", err)\n\t}\n\tif violations != \"\" {\n\t\treturn fmt.Errorf(\"postgres: resolver isolation incomplete: %s\", violations)\n\t}\n\treturn nil\n}\n\n// WithOrg runs fn inside a transaction whose tenant GUC is transaction-local.\n// Empty scopes are rejected: tenant work must never degrade to an unscoped\n// query when RLS is the hard boundary.\nfunc WithOrg(ctx context.Context, pool *pgxpool.Pool, orgID string, fn func(pgx.Tx) error) error {\n\torgID = strings.TrimSpace(orgID)\n\tif orgID == \"\" {\n\t\treturn errors.New(\"postgres: organization scope is required\")\n\t}\n\ttx, err := pool.Begin(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() { _ = tx.Rollback(ctx) }()\n\tif _, err := tx.Exec(ctx, `SELECT set_config('app.current_organization_id', $1, true)`, orgID); err != nil {\n\t\treturn fmt.Errorf(\"postgres: set organization scope: %w\", err)\n\t}\n\tif err := fn(tx); err != nil {\n\t\treturn err\n\t}\n\treturn tx.Commit(ctx)\n}\n","sourceCodeStart":749,"sourceCodeEnd":782,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/postgresconfig/postgresconfig.go#L749-L782","documentation":"WithOrg opens a transaction and sets a transaction-local tenant GUC that RLS policies read; it rejects empty org IDs up front. Passing an empty scope would run tenant queries without the RLS boundary, silently exposing cross-tenant data, so the library refuses to degrade to an unscoped transaction.","triggerScenarios":"Calling WithOrg(ctx, pool, \"\", fn) or with a whitespace-only orgID; orgID sourced from an unauthenticated request context, a missing JWT claim, or a struct field left at its zero value.","commonSituations":"Background jobs that skip tenant extraction from the request; API handlers reading orgID from an optional claim that some tokens lack; test helpers passing \"\" because a fixture forgot to set the tenant.","solutions":["Pass a validated, non-empty orgID from an authenticated tenant context","Reject requests earlier: return 401/400 at the handler when the org claim is absent","In jobs, load the org ID explicitly and fail the job if it is missing rather than calling WithOrg unscoped"],"exampleFix":"// before\nerr := postgresconfig.WithOrg(ctx, pool, req.OrgID, func(tx pgx.Tx) error {...}) // OrgID == \"\"\n// after\nif strings.TrimSpace(req.OrgID) == \"\" {\n    return echo.NewHTTPError(http.StatusBadRequest, \"organization scope required\")\n}\nerr := postgresconfig.WithOrg(ctx, pool, req.OrgID, func(tx pgx.Tx) error {...})","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(orgID) == \"\" {\n    return errors.New(\"organization scope required before tenant transaction\")\n}","typeGuard":"func hasOrgScope(ctx context.Context) (string, bool) {\n    orgID, ok := ctx.Value(orgIDKey{}).(string)\n    return orgID, ok && strings.TrimSpace(orgID) != \"\"\n}","tryCatchPattern":"err := postgresconfig.WithOrg(ctx, pool, orgID, func(tx pgx.Tx) error {...})\nif err != nil && strings.Contains(err.Error(), \"organization scope is required\") {\n    return status.Error(codes.FailedPrecondition, \"missing tenant scope\")\n}","preventionTips":["Extract orgID from an authenticated context and reject requests missing the claim","Never read orgID from optional/untrusted input for tenant-scoped work","In background jobs, load the org explicitly and abort when absent","Write a lint/test that every WithOrg call site sources orgID from a validated tenant context"],"tags":["postgres","rls","multi-tenancy","validation"],"backgroundTag":"empty-required-field","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"}