# 💡 Techniques

You may want to use Escape in different ways, depending on your needs.

You can detect and configure specific behavior for requests coming from our scanner.


# Identifying requests coming from the scanner on your server

You might need to identify when the request you receive is coming from the security scanner.

For instance, you probably don't want the many requests being sent to your server from the scanner to appear in your monitoring tool, or, you might want to enable the introspection of your server only to the security scanner on your staging environmet.

For this purpose, the scanner of Escape sends a specific header attached to every requests it sends. The header name is x-escape-identifier and its value is an identification token attached to your application.

x-escape-identifier: {{your-escape-identifier}}

Thanks to this header you can detect incoming requests from the scanner in your server, to add any custom handling logic you might want on top of this.

You can find this token on your scan page in the CI/CD section as ESCAPE_APPLICATION_ID.

# Enabling the introspection on your application

# Using Apollo

When creating a new instance of the ApolloServer, you have to provide an object describing your resolvers, and types definitions. This object can also include an introspection parameter.

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true
});

This option is documented in the ApolloServer reference (opens new window)

# Fine-tuning

Using Apollo plugins (opens new window), you can also have a better access control over this query. Here is an example of plugin that prevents the access to the introspection query if the request does not feature the CLI header.

const secureIntrospection = {
  requestDidStart: ({ request }) => {
    if (
      request.query.includes('__schema') ||
      request.query.includes('__type')
    ) {
      if (process.env.ESCAPE_IDENTIFIER) {
        const introspectionSecureKeyHeader = request.http.headers.get(
          'x-escape-identifier'
        );
        if (introspectionSecureKeyHeader !== process.env.ESCAPE_IDENTIFIER) {
          throw new ForbiddenError('GraphQL introspection is disabled.');
        }
      }
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true,
  plugins: [secureIntrospection]
});

The value of the variable ESCAPE_IDENTIFIER could be contained in your environment or any secure storage you'd like.

# Using NestJS

NestJS provides a GraphQL module through the @nestjs/graphql package. The package is a wrapper around the Apollo GraphQL server, and it's usage is documented in the NestJS related documentation (opens new window).

Starting from now, we will assume that you have a basic GraphQL module setup in your application like so:

@Module({
  imports: [
    ...
    GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql'
    }),
    ...
  ],
  providers: []
})
export class AppModule {}

By default, the introspection is enabled as long as the NODE_ENV is not production. To change this behavior, you can just set the introspection parameter to true in the module declaration.

@Module({
  imports: [
    ...
    GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql',
      introspection: true
    }),
    ...
  ],
  providers: []
})
export class AppModule {}

For instance, you might want to manually define where to enable or not the introspection based on your environment. To do so, you can add a DISABLE_INTROSPECTION env variable (provided by a .env file or through an shell environment variable) and use it like so:

@Module({
  imports: [
    ...
    GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql',
      introspection: !process.env.DISABLE_INTROSPECTION
    }),
    ...
  ],
  providers: []
})
export class AppModule {}

This will enable introspection by default, and disable it in every environment you decide to.

# Fine-tuning

Any of the options you pass to the GraphQLModule.forRoot method is part of the Apollo configuration object. Thus, you can re-use the Apollo plugin method for a fine-tuned access control.

const secureIntrospection = ... // see the apollo technique documentation

@Module({
  imports: [
    ...
    GraphQLModule.forRoot({
      autoSchemaFile: 'schema.gql',
      introspection: !process.env.DISABLE_INTROSPECTION,
      plugins: [secureIntrospection]
    }),
    ...
  ],
  providers: []
})
export class AppModule {}

# Using Yoga

Yoga plugin system is built with Envelop. Refer to the Envelop section for more information.

# Using Envelop

Envelop provides a plugin disable-introspection that allows you to disable the introspection query. You can find more information about this plugin in the Envelop documentation (opens new window).

import { envelop } from '@envelop/core';
import { useDisableIntrospection } from '@envelop/disable-introspection';

const getEnveloped = envelop({
  plugins: [
    useDisableIntrospection({
      disableIf: ({ context }) => {
        return (
          context.request.headers.get('x-escape-identifier') !==
          process.env.ESCAPE_IDENTIFIER
        );
      }
    })
  ]
});

# Retrieving your introspection schema manually

GraphQL endpoints are introspectable (opens new window), meaning that one can query an API to get details about the API itself. This feature is enabled by default, but you may have disabled it in production. Escape needs these introspection details to scan your API, and can work with either a schema file or an introspection result. Read on to learn how to get these files.

# Retrieving a GraphQL schema

A GraphQL schema is the specification of your API. It defines the types of your API, the queries and mutations you can perform, and the relations between them. Depending on your development approach, it is either written by hand or generated from your code.

  • If you are using a schema-first approach, there should be a schema.graphql or schema.gql file committed in your repository. The name may vary depending on your configuration.
  • If you are using a code-first approach, you can have your framework save your schema to a file with configuration options like autoSchemaFile (NestJS) (opens new window) or similar.

# Sending an introspection query

The introspection result is what a GraphQL endpoint answers to a query on the __schema meta-field.

You can retrieve the introspection result by running the following query on your GraphQL endpoint. Save the result to a file named introspection-result.json and upload it to Escape.

GraphQL introspection query (92 lines)
query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    subscriptionType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      locations
      args {
        ...InputValue
      }
    }
  }
}
fragment FullType on __Type {
  kind
  name
  fields(includeDeprecated: true) {
    name
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}
fragment InputValue on __InputValue {
  name
  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
              }
            }
          }
        }
      }
    }
  }
}

You can do this automatically with a tool like Apollo Rover CLI (opens new window):

# Rover is a CLI tool to work with GraphQL APIs
npx @apollo/rover graph introspect https://example.com/graphql --output json \
    --header "Authorization: Bearer <token>" \
  > introspection-result.json

# Improving your application coverage:

The coverage statistic is available in the summary tab of successful scans. It measures the percentage of queries and mutations escape could perform among the full schema.

# Set scan type to read & write:

A read-only scan does not run mutations.

This will be checked if the scan is indeed in read & write mode.

The message: ```In this scan, x% of operations were attempted.` will be displayed if less than 100% operations were attempted and the scan is not in read and write mode.

# Provide authentication with enough permissions:

You can provide custom authentication headers in your application configuration to allow escape to run authenticated operations.

This will be checked if the application has headers configured and no operations were unauthorized or forbidden.

The message: In this scan. x% of operations were unauthorized or forbidden. will be displayed if at least one operation was unauthorized/forbidden.

# Make sure the scan is not rate limited:

You can setup rate limit in your application settings (learn more) (opens new window), though it may have an impact on the scan coverage, as operation can be rate limited.

This will be checked if no operations are rate limited.

The message: In this scan, x% of operations were discarded because of your rate limit configuration. will be displayed if at least one operation was rate limited.

# Ignoring alerts or security checks:

Please be aware that ignoring security checks or alerts may decrease the coverage statistics of your application, as our scanner will skip those tests.