Moderate severityNVD Advisory· Published Oct 25, 2022· Updated Apr 23, 2025
OpenFGA Authorization Bypass
CVE-2022-39342
Description
OpenFGA is an authorization/permission engine. Versions prior to version 0.2.4 are vulnerable to authorization bypass under certain conditions. Users whose model has a relation defined as a tupleset (the right hand side of a ‘from’ statement) that involves anything other than a direct relationship (e.g. ‘as self’) are vulnerable. Version 0.2.4 contains a patch for this issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/openfga/openfgaGo | < 0.2.4 | 0.2.4 |
Affected products
1Patches
1c8db1ee3d2a3Merge pull request from GHSA-f4mm-2r69-mg5f
2 files changed · +225 −25
pkg/typesystem/typesystem.go+44 −9 modified@@ -2,6 +2,7 @@ package typesystem import ( "fmt" + "reflect" "github.com/go-errors/errors" openfgapb "go.buf.build/openfga/go/openfga/api/openfga/v1" @@ -233,21 +234,41 @@ func containsDuplicateType(model *openfgapb.AuthorizationModel) bool { func validateRelationRewrites(model *openfgapb.AuthorizationModel) error { typeDefinitions := model.GetTypeDefinitions() - allRelations := map[string]struct{}{} - typeToRelations := map[string]map[string]struct{}{} + relations := map[string]*openfgapb.Relation{} + typerels := map[string]map[string]*openfgapb.Relation{} + for _, td := range typeDefinitions { objectType := td.GetType() - typeToRelations[objectType] = map[string]struct{}{} - for relation := range td.GetRelations() { - typeToRelations[objectType][relation] = struct{}{} - allRelations[relation] = struct{}{} + + typerels[objectType] = map[string]*openfgapb.Relation{} + + for relation, rewrite := range td.GetRelations() { + relationMetadata := td.GetMetadata().GetRelations() + md, ok := relationMetadata[relation] + + var typeinfo *openfgapb.RelationTypeInfo + if ok { + typeinfo = &openfgapb.RelationTypeInfo{ + DirectlyRelatedUserTypes: md.GetDirectlyRelatedUserTypes(), + } + } + + r := &openfgapb.Relation{ + Name: relation, + Rewrite: rewrite, + TypeInfo: typeinfo, + } + + typerels[objectType][relation] = r + relations[relation] = r } } for _, td := range typeDefinitions { objectType := td.GetType() + for relation, rewrite := range td.GetRelations() { - err := isUsersetRewriteValid(allRelations, typeToRelations[objectType], objectType, relation, rewrite) + err := isUsersetRewriteValid(relations, typerels[objectType], objectType, relation, rewrite) if err != nil { return err } @@ -259,7 +280,12 @@ func validateRelationRewrites(model *openfgapb.AuthorizationModel) error { // isUsersetRewriteValid checks if a particular userset rewrite is valid. The first argument is all the relations in // the typeSystem, the second argument is the subset of relations on the type where the rewrite occurs. -func isUsersetRewriteValid(allRelations map[string]struct{}, relationsOnType map[string]struct{}, objectType, relation string, rewrite *openfgapb.Userset) error { +func isUsersetRewriteValid( + allRelations map[string]*openfgapb.Relation, + relationsOnType map[string]*openfgapb.Relation, + objectType, relation string, + rewrite *openfgapb.Userset, +) error { if rewrite.GetUserset() == nil { return InvalidRelationError(objectType, relation) } @@ -275,10 +301,19 @@ func isUsersetRewriteValid(allRelations map[string]struct{}, relationsOnType map } case *openfgapb.Userset_TupleToUserset: tupleset := t.TupleToUserset.GetTupleset().GetRelation() - if _, ok := relationsOnType[tupleset]; !ok { + + tuplesetRelation, ok := relationsOnType[tupleset] + if !ok { return RelationDoesNotExistError(objectType, tupleset) } + // tupleset relations must only be direct relationships, no rewrites + // are allowed on them + tuplesetRewrite := tuplesetRelation.GetRewrite() + if reflect.TypeOf(tuplesetRewrite.GetUserset()) != reflect.TypeOf(&openfgapb.Userset_This{}) { + return errors.Errorf("the '%s#%s' relation is referenced in at least one tupleset and thus must be a direct relation", objectType, tupleset) + } + computedUserset := t.TupleToUserset.GetComputedUserset().GetRelation() if _, ok := allRelations[computedUserset]; !ok { return RelationDoesNotExistError("", computedUserset)
server/test/write_authzmodel.go+181 −16 modified@@ -5,11 +5,12 @@ import ( "fmt" "testing" + "github.com/go-errors/errors" "github.com/openfga/openfga/pkg/id" "github.com/openfga/openfga/pkg/logger" "github.com/openfga/openfga/pkg/typesystem" "github.com/openfga/openfga/server/commands" - "github.com/openfga/openfga/server/errors" + serverErrors "github.com/openfga/openfga/server/errors" "github.com/openfga/openfga/storage" "github.com/stretchr/testify/require" openfgapb "go.buf.build/openfga/go/openfga/api/openfga/v1" @@ -109,7 +110,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor StoreId: storeID, TypeDefinitions: items, }, - err: errors.ExceededEntityLimit("type definitions in an authorization model", datastore.MaxTypesInTypeDefinition()), + err: serverErrors.ExceededEntityLimit("type definitions in an authorization model", datastore.MaxTypesInTypeDefinition()), }, { name: "empty relations is valid", @@ -151,7 +152,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.ErrDuplicateTypes), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.ErrDuplicateTypes), }, { name: "ExecuteWriteFailsIfEmptyRewrites", @@ -166,7 +167,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "owner")), }, { name: "ExecuteWriteFailsIfUnknownRelationInComputedUserset", @@ -188,7 +189,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), }, { name: "ExecuteWriteFailsIfUnknownRelationInTupleToUserset", @@ -215,7 +216,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")), }, { name: "ExecuteWriteFailsIfUnknownRelationInUnion", @@ -250,7 +251,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), }, { name: "ExecuteWriteFailsIfUnknownRelationInDifferenceBaseArgument", @@ -285,7 +286,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), }, { name: "ExecuteWriteFailsIfUnknownRelationInDifferenceSubtractArgument", @@ -320,7 +321,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), }, { name: "ExecuteWriteFailsIfUnknownRelationInTupleToUsersetTupleset", @@ -349,7 +350,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), }, { name: "ExecuteWriteFailsIfUnknownRelationInTupleToUsersetComputedUserset", @@ -378,7 +379,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("", "owner")), }, { name: "ExecuteWriteFailsIfTupleToUsersetReferencesUnknownRelation", @@ -414,7 +415,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("bar", "writer")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("bar", "writer")), }, { name: "ExecuteWriteFailsIfUnknownRelationInIntersection", @@ -447,7 +448,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.RelationDoesNotExistError("repo", "owner")), }, { name: "ExecuteWriteFailsIfDifferenceIncludesSameRelationTwice", @@ -477,7 +478,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")), }, { name: "ExecuteWriteFailsIfUnionIncludesSameRelationTwice", @@ -504,7 +505,7 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")), }, { name: "ExecuteWriteFailsIfIntersectionIncludesSameRelationTwice", @@ -530,7 +531,171 @@ func WriteAuthorizationModelTest(t *testing.T, datastore storage.OpenFGADatastor }, }, }, - err: errors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")), + err: serverErrors.InvalidAuthorizationModelInput(typesystem.InvalidRelationError("repo", "viewer")), + }, + { + name: "Union Rewrite Contains Repeated Definitions", + request: &openfgapb.WriteAuthorizationModelRequest{ + StoreId: storeID, + TypeDefinitions: []*openfgapb.TypeDefinition{ + { + Type: "document", + Relations: map[string]*openfgapb.Userset{ + "parent": typesystem.This(), + "viewer": typesystem.Union( + typesystem.ComputedUserset("editor"), + typesystem.ComputedUserset("editor"), + ), + "editor": typesystem.Union(typesystem.This(), typesystem.This()), + "manage": typesystem.Union( + typesystem.TupleToUserset("parent", "manage"), + typesystem.TupleToUserset("parent", "manage"), + ), + }, + }, + }, + }, + }, + { + name: "Intersection Rewrite Contains Repeated Definitions", + request: &openfgapb.WriteAuthorizationModelRequest{ + StoreId: storeID, + TypeDefinitions: []*openfgapb.TypeDefinition{ + { + Type: "document", + Relations: map[string]*openfgapb.Userset{ + "parent": typesystem.This(), + "viewer": typesystem.Intersection( + typesystem.ComputedUserset("editor"), + typesystem.ComputedUserset("editor"), + ), + "editor": typesystem.Intersection(typesystem.This(), typesystem.This()), + "manage": typesystem.Intersection( + typesystem.TupleToUserset("parent", "manage"), + typesystem.TupleToUserset("parent", "manage"), + ), + }, + }, + }, + }, + }, + { + name: "Exclusion Rewrite Contains Repeated Definitions", + request: &openfgapb.WriteAuthorizationModelRequest{ + StoreId: storeID, + TypeDefinitions: []*openfgapb.TypeDefinition{ + { + Type: "document", + Relations: map[string]*openfgapb.Userset{ + "parent": typesystem.This(), + "viewer": typesystem.Difference( + typesystem.ComputedUserset("editor"), + typesystem.ComputedUserset("editor"), + ), + "editor": typesystem.Difference(typesystem.This(), typesystem.This()), + "manage": typesystem.Difference( + typesystem.TupleToUserset("parent", "manage"), + typesystem.TupleToUserset("parent", "manage"), + ), + }, + }, + }, + }, + }, + { + name: "Tupleset relation involves ComputedUserset rewrite", + request: &openfgapb.WriteAuthorizationModelRequest{ + StoreId: storeID, + TypeDefinitions: []*openfgapb.TypeDefinition{ + { + Type: "document", + Relations: map[string]*openfgapb.Userset{ + "ancestor": typesystem.This(), + "parent": typesystem.ComputedUserset("ancestor"), + "viewer": typesystem.TupleToUserset("parent", "viewer"), + }, + }, + }, + }, + err: serverErrors.InvalidAuthorizationModelInput( + errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"), + ), + }, + { + name: "Tupleset relation involves Union rewrite", + request: &openfgapb.WriteAuthorizationModelRequest{ + StoreId: storeID, + TypeDefinitions: []*openfgapb.TypeDefinition{ + { + Type: "document", + Relations: map[string]*openfgapb.Userset{ + "ancestor": typesystem.This(), + "parent": typesystem.Union(typesystem.This(), typesystem.ComputedUserset("ancestor")), + "viewer": typesystem.TupleToUserset("parent", "viewer"), + }, + }, + }, + }, + err: serverErrors.InvalidAuthorizationModelInput( + errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"), + ), + }, + { + name: "Tupleset relation involves Intersection rewrite", + request: &openfgapb.WriteAuthorizationModelRequest{ + StoreId: storeID, + TypeDefinitions: []*openfgapb.TypeDefinition{ + { + Type: "document", + Relations: map[string]*openfgapb.Userset{ + "ancestor": typesystem.This(), + "parent": typesystem.Intersection(typesystem.This(), typesystem.ComputedUserset("ancestor")), + "viewer": typesystem.TupleToUserset("parent", "viewer"), + }, + }, + }, + }, + err: serverErrors.InvalidAuthorizationModelInput( + errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"), + ), + }, + { + name: "Tupleset relation involves Exclusion rewrite", + request: &openfgapb.WriteAuthorizationModelRequest{ + StoreId: storeID, + TypeDefinitions: []*openfgapb.TypeDefinition{ + { + Type: "document", + Relations: map[string]*openfgapb.Userset{ + "restricted": typesystem.This(), + "parent": typesystem.Difference(typesystem.This(), typesystem.ComputedUserset("restricted")), + "viewer": typesystem.TupleToUserset("parent", "viewer"), + }, + }, + }, + }, + err: serverErrors.InvalidAuthorizationModelInput( + errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"), + ), + }, + { + name: "Tupleset relation involves TupleToUserset rewrite", + request: &openfgapb.WriteAuthorizationModelRequest{ + StoreId: storeID, + TypeDefinitions: []*openfgapb.TypeDefinition{ + { + Type: "document", + Relations: map[string]*openfgapb.Userset{ + "ancestor": typesystem.This(), + "parent": typesystem.TupleToUserset("ancestor", "viewer"), + "viewer": typesystem.TupleToUserset("parent", "viewer"), + }, + }, + }, + }, + err: serverErrors.InvalidAuthorizationModelInput( + errors.New("the 'document#parent' relation is referenced in at least one tupleset and thus must be a direct relation"), + ), }, }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-f4mm-2r69-mg5fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-39342ghsaADVISORY
- github.com/openfga/openfga/commit/c8db1ee3d2a366f18e585dd33236340e76e784c4ghsaWEB
- github.com/openfga/openfga/releases/tag/v0.2.4ghsaWEB
- github.com/openfga/openfga/security/advisories/GHSA-f4mm-2r69-mg5fghsaWEB
News mentions
0No linked articles in our index yet.