diff --git a/graphql.go b/graphql.go index a7a8113..b2a852c 100644 --- a/graphql.go +++ b/graphql.go @@ -184,11 +184,20 @@ func checkSelections(c *fiber.Ctx, selections []ast.Selection) bool { return true } } + // Check nested selections even if current field is allowed if sel.SelectionSet != nil { if checkSelections(c, sel.GetSelectionSet().Selections) { return true } } + case *ast.InlineFragment: + if sel.SelectionSet != nil { + if checkSelections(c, sel.GetSelectionSet().Selections) { + return true + } + } + case *ast.FragmentSpread: + // If we need to handle fragment spreads, additional logic would go here } } return false diff --git a/graphql_test.go b/graphql_test.go index 92d7416..335a531 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/goccy/go-json" fiber "github.com/gofiber/fiber/v2" "github.com/valyala/fasthttp" ) @@ -432,3 +433,60 @@ func createTestContext(body string) *fiber.Ctx { ctx.Request().SetBody([]byte(body)) return ctx } + +func (suite *Tests) Test_DeepIntrospectionQueries() { + tests := []struct { + name string + query string + allowed []string + expected bool + }{ + { + name: "deeply nested single introspection", + query: "query { users { profiles { settings { preferences { __typename } } } } }", + allowed: []string{}, + expected: true, + }, + { + name: "multiple nested introspections", + query: "query { users { __typename profiles { __schema settings { __type } } } }", + allowed: []string{}, + expected: true, + }, + { + name: "nested with selective allowlist", + query: "query { users { __typename profiles { __schema settings { __type } } } }", + allowed: []string{"__typename"}, + expected: true, + }, + { + name: "deeply nested with full allowlist", + query: "query { users { __typename profiles { __schema settings { __type } } } }", + allowed: []string{"__typename", "__schema", "__type"}, + expected: false, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + cfg.Security.BlockIntrospection = true + cfg.Security.IntrospectionAllowed = tt.allowed + introspectionAllowedQueries = make(map[string]struct{}) + for _, q := range tt.allowed { + introspectionAllowedQueries[strings.ToLower(q)] = struct{}{} + } + body := map[string]interface{}{ + "query": tt.query, + } + bodyBytes, _ := json.Marshal(body) + ctx := fiber.New().AcquireCtx(&fasthttp.RequestCtx{}) + ctx.Request().SetBody(bodyBytes) + parseGraphQLQuery(ctx) + if tt.expected { + suite.Equal(403, ctx.Response().StatusCode()) + } else { + suite.Equal(200, ctx.Response().StatusCode()) + } + }) + } +} \ No newline at end of file