VYPR
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.

PackageAffected versionsPatched versions
github.com/openfga/openfgaGo
< 0.2.40.2.4

Affected products

1

Patches

1
c8db1ee3d2a3

Merge pull request from GHSA-f4mm-2r69-mg5f

https://github.com/openfga/openfgaJonathan WhitakerOct 21, 2022via ghsa
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

News mentions

0

No linked articles in our index yet.