CVE-2022-39341 (Go, published 2022-10-25T17:15Z)
Description: value: OpenFGA is an authorization/permission engine. Versions prior to version 0.2.4 are vulnerable to authorization bypass under certain conditions. Users who have wildcard (`*`) defined on tupleset relations in their authorization model are vulnerable. Version 0.2.4 contains a patch for this issue.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: NVD-CWE-Other \u2014 Other
NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
Repository: https://github.com/openfga/openfga
Files changed:
server/commands/check.go
server/commands/check_utils.go
server/commands/expand.go
server/test/check.go
server/test/expand.go
Commit message: Merge pull request from GHSA-vj4m-83m8-xpw5

* fix: return error if wildcard is encountered in ttu evaluation

* chore: address one of maria's comments

* chore: fix public facing error message and test name

* fix: return error if '*' encountered in tupleset relation in Expand

Vulnerable code:
const AllUsers = "*"
	findTK := &openfgapb.TupleKey{Object: rc.tk.GetObject(), Relation: relation}
		if rt, found = u.u[AllUsers]; !found {
func (query *ExpandQuery) resolveUserset(ctx context.Context, store, modelID string, userset *openfgapb.Userset, tk *openfgapb.TupleKey, metadata *utils.ResolutionMetadata) (*openfgapb.UsersetTree_Node, error) {
		return query.resolveTupleToUserset(ctx, store, us.TupleToUserset, tk, metadata)
func (query *ExpandQuery) resolveTupleToUserset(ctx context.Context, store string, userset *openfgapb.TupleToUserset, tk *openfgapb.TupleKey, metadata *utils.ResolutionMetadata) (*openfgapb.UsersetTree_Node, error) {
		Object:   tk.GetObject(),
		Relation: userset.GetTupleset().GetRelation(),
			if test.err == nil {
				require.NoError(t, gotErr)
			}

			if test.err != nil {
				require.EqualError(t, test.err, gotErr.Error())
			}
	"github.com/go-errors/errors"
			if !errors.Is(err, test.expected) {
				t.Fatalf("'%s': Execute(), err = %v, want %v", test.name, err, test.expected)
			}

Fixed code:
	"fmt"
const Wildcard = "*"

	findTK := tupleUtils.NewTupleKey(rc.tk.GetObject(), relation, "")

		if userObj == Wildcard {
			objectType, _ := tupleUtils.SplitObject(rc.tk.GetObject())

			query.logger.WarnWithContext(
				ctx,
				fmt.Sprintf("unexpected wildcard evaluated on tupleset relation '%s'", relation),
				zap.String("store_id", rc.store),
				zap.String("authorization_model_id", rc.modelID),
				zap.String("object_type", objectType),
			)

			return serverErrors.InvalidTuple(
				fmt.Sprintf("unexpected wildcard evaluated on relation '%s#%s'", objectType, relation),
				tupleUtils.NewTupleKey(rc.tk.GetObject(), relation, Wildcard),
			)
		}

		if rt, found = u.u[Wildcard]; !found {
	"fmt"
	"go.uber.org/zap"
func (query *ExpandQuery) resolveUserset(
	ctx context.Context,
	store, modelID string,
	userset *openfgapb.Userset,
	tk *openfgapb.TupleKey,
	metadata *utils.ResolutionMetadata,
) (*openfgapb.UsersetTree_Node, error) {
		return query.resolveTupleToUserset(ctx, store, modelID, us.TupleToUserset, tk, metadata)
func (query *ExpandQuery) resolveTupleToUserset(
	ctx context.Context,
	store, modelID string,
	userset *openfgapb.TupleToUserset,
	tk *openfgapb.TupleKey,
	metadata *utils.ResolutionMetadata,
) (*openfgapb.UsersetTree_Node, error) {
	targetObject := tk.GetObject()

	tupleset := userset.GetTupleset().GetRelation()
		Object:   targetObject,
		Relation: tupleset,



		if user == Wildcard {
			objectType, _ := tupleUtils.SplitObject(targetObject)

			query.logger.WarnWithContext(
				ctx,
				fmt.Sprintf("unexpected wildcard evaluated on tupleset relation '%s'", tupleset),
				zap.String("store_id", store),
				zap.String("authorization_model_id", modelID),
				zap.String("object_type", objectType),
			)

			return nil, serverErrors.InvalidTuple(
				fmt.Sprintf("unexpected wildcard evaluated on relation '%s#%s'", objectType, tupleset),
				tupleUtils.NewTupleKey(targetObject, tupleset, Wildcard),
			)
		}






		{
			name:             "Error if * encountered in TupleToUserset evaluation",
			resolveNodeLimit: defaultResolveNodeLimit,
			request: &openfgapb.CheckRequest{
				TupleKey: tuple.NewTupleKey("document:doc1", "viewer", "user:anne"),
			},
			typeDefinitions: []*openfgapb.TypeDefinition{
				{
					Type: "document",
					Relations: map[string]*openfgapb.Userset{
						"parent": typesystem.This(),
						"viewer": typesystem.TupleToUserset("parent", "viewer"),
					},
					Metadata: &openfgapb.Metadata{
						Relations: map[string]*openfgapb.RelationMetadata{
							"parent": {
								DirectlyRelatedUserTypes: []*openfgapb.RelationReference{
									typesystem.RelationReference("folder", ""),
								},
							},
						},
					},
				},
				{
					Type: "folder",
					Relations: map[string]*openfgapb.Userset{
						"viewer": typesystem.This(),
					},
					Metadata: &openfgapb.Metadata{
						Relations: map[string]*openfgapb.RelationMetadata{
							"viewer": {
								DirectlyRelatedUserTypes: []*openfgapb.RelationReference{
									typesystem.RelationReference("user", ""),
								},
							},
						},
					},
				},
			},
			tuples: []*openfgapb.TupleKey{
				tuple.NewTupleKey("document:doc1", "parent", "*"), // wildcard not allowed on tupleset relations
				tuple.NewTupleKey("folder:folder1", "viewer", "user:anne"),
			},
			err: serverErrors.InvalidTuple(
				"unexpected wildcard evaluated on relation 'document#parent'",
				tuple.NewTupleKey("document:doc1", "parent", commands.Wildcard),
			),
		},
		{
			name:             "Error if * encountered in TTU evaluation including ContextualTuples",
			resolveNodeLimit: defaultResolveNodeLimit,
			request: &openfgapb.CheckRequest{
				TupleKey: tuple.NewTupleKey("document:doc1", "viewer", "user:anne"),
				ContextualTuples: &openfgapb.ContextualTupleKeys{
					TupleKeys: []*openfgapb.TupleKey{
						tuple.NewTupleKey("document:doc1", "parent", "*"), // wildcard not allowed on tupleset relations
						tuple.NewTupleKey("folder:folder1", "viewer", "user:anne"),
					},
				},
			},
			typeDefinitions: []*openfgapb.TypeDefinition{
				{
					Type: "document",
					Relations: map[string]*openfgapb.Userset{
						"parent": typesystem.This(),
						"viewer": typesystem.TupleToUserset("parent", "viewer"),
					},
					Metadata: &openfgapb.Metadata{
						Relations: map[string]*openfgapb.RelationMetadata{
							"parent": {
								DirectlyRelatedUserTypes: []*openfgapb.RelationReference{
									typesystem.RelationReference("folder", ""),
								},
							},
						},
					},
				},
				{
					Type: "folder",
					Relations: map[string]*openfgapb.Userset{
						"viewer": typesystem.This(),
					},
					Metadata: &openfgapb.Metadata{
						Relations: map[string]*openfgapb.RelationMetadata{
							"viewer": {
								DirectlyRelatedUserTypes: []*openfgapb.RelationReference{
									typesystem.RelationReference("user", ""),
								},
							},
						},
					},
				},
			},
			err: serverErrors.InvalidTuple(
				"unexpected wildcard evaluated on relation 'document#parent'",
				tuple.NewTupleKey("document:doc1", "parent", commands.Wildcard),
			),
		},
			require.ErrorIs(t, gotErr, test.err)
	"github.com/openfga/openfga/pkg/tuple"
		{
			name: "TupleToUserset involving wildcard returns error",
			typeDefinitions: []*openfgapb.TypeDefinition{
				{
					Type: "document",
					Relations: map[string]*openfgapb.Userset{
						"parent": typesystem.This(),
						"viewer": typesystem.Union(
							typesystem.This(), typesystem.TupleToUserset("parent", "viewer"),
						),
					},
				},
			},
			tuples: []*openfgapb.TupleKey{
				tuple.NewTupleKey("document:1", "parent", "*"),
				tuple.NewTupleKey("document:X", "viewer", "jon"),
			},
			request: &openfgapb.ExpandRequest{
				TupleKey: tuple.NewTupleKey("document:1", "viewer", ""),
			},
			expected: serverErrors.InvalidTuple(
				"unexpected wildcard evaluated on relation 'document#parent'",
				tuple.NewTupleKey("document:1", "parent", "*"),
			),
		},
			require.ErrorIs(err, test.expected)