From 71724de7dd08c7153bc1a45158296feb75a11fdd Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:39:29 +0100 Subject: [PATCH] Security - disable gql introspection for non-auth user (#16047) closes https://github.com/twentyhq/private-issues/issues/351 closes https://github.com/twentyhq/private-issues/issues/350 Before, introspection query works without token. After, fails. ``` query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } ``` --- .../graphql-config/graphql-config.service.ts | 4 ++++ .../api/graphql/metadata.module-factory.ts | 4 ++++ ...rospection-for-unauthenticated-users.hook.ts | 17 +++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 packages/twenty-server/src/engine/core-modules/graphql/hooks/use-disable-introspection-for-unauthenticated-users.hook.ts diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts index 2a3388e89a..fd891ed6d9 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts @@ -33,6 +33,7 @@ import { import { CoreEngineModule } from 'src/engine/core-modules/core-engine.module'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { useSentryTracing } from 'src/engine/core-modules/exception-handler/hooks/use-sentry-tracing'; +import { useDisableIntrospectionForUnauthenticatedUsers } from 'src/engine/core-modules/graphql/hooks/use-disable-introspection-for-unauthenticated-users.hook'; import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook'; import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; @@ -71,6 +72,9 @@ export class GraphQLConfigService i18nService: this.i18nService, twentyConfigService: this.twentyConfigService, }), + useDisableIntrospectionForUnauthenticatedUsers( + this.twentyConfigService.get('NODE_ENV') === NodeEnvironment.PRODUCTION, + ), ]; if (Sentry.isInitialized()) { diff --git a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts index b2f7c5a3c4..7154cf5073 100644 --- a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts @@ -7,6 +7,7 @@ import { useCachedMetadata } from 'src/engine/api/graphql/graphql-config/hooks/u import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphql-api.module'; import { type CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { type ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { useDisableIntrospectionForUnauthenticatedUsers } from 'src/engine/core-modules/graphql/hooks/use-disable-introspection-for-unauthenticated-users.hook'; import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook'; import { type I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { type MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; @@ -41,6 +42,9 @@ export const metadataModuleFactory = async ( cacheSetter: cacheStorageService.set.bind(cacheStorageService), operationsToCache: ['ObjectMetadataItems', 'FindAllCoreViews'], }), + useDisableIntrospectionForUnauthenticatedUsers( + twentyConfigService.get('NODE_ENV') === NodeEnvironment.PRODUCTION, + ), ], path: '/metadata', context: () => ({ diff --git a/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-disable-introspection-for-unauthenticated-users.hook.ts b/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-disable-introspection-for-unauthenticated-users.hook.ts new file mode 100644 index 0000000000..96a4c6c752 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-disable-introspection-for-unauthenticated-users.hook.ts @@ -0,0 +1,17 @@ +import { type Plugin } from 'graphql-yoga'; +import { NoSchemaIntrospectionCustomRule } from 'graphql/validation/rules/custom/NoSchemaIntrospectionCustomRule'; +import { isDefined } from 'twenty-shared/utils'; + +import { type GraphQLContext } from 'src/engine/api/graphql/graphql-config/graphql-config.service'; + +export const useDisableIntrospectionForUnauthenticatedUsers = ( + isProductionEnvironment: boolean, +): Plugin => ({ + onValidate: ({ context, addValidationRule }) => { + const isAuthenticated = isDefined(context.req.workspace); + + if (!isAuthenticated && isProductionEnvironment) { + addValidationRule(NoSchemaIntrospectionCustomRule); + } + }, +});