HIGH graphql introspectionexpressbasic auth

Graphql Introspection in Express with Basic Auth

Graphql Introspection in Express with Basic Auth — how this specific combination creates or exposes the vulnerability

GraphQL introspection in an Express server becomes a security risk when endpoints are unintentionally exposed, especially when protected only by Basic Auth. Introspection queries (query { __schema { queryType { name } } }) reveal the full type model, including queries, mutations, subscriptions, input types, and directives. This metadata can expose sensitive business logic, field names, and relationships that an attacker can use to craft further attacks such as IDOR or property-level authorization abuse.

When Basic Auth is used but not strictly enforced for introspection endpoints, the combination creates a weak boundary: authentication is present, but the schema is still readable without needing valid user credentials for the introspection operation itself. If the Express route handling GraphQL requests does not differentiate between authenticated administrative actions and schema discovery, an unauthenticated or low-privilege actor can retrieve the schema simply by sending an introspection query over HTTP.

In a black-box scan, middleBrick tests unauthenticated attack surfaces and checks whether introspection is allowed by sending an introspection query to the GraphQL endpoint. If the endpoint returns schema details without enforcing stricter authorization, this finding is surfaced with severity and guidance. Attack patterns like this align with OWASP API Top 10 risks such as excessive data exposure and broken object level authorization, because introspection can reveal object identifiers and fields that become targets for BOLA/IDOR.

Real-world examples include endpoints mounted at paths like /graphql or /api/graphql where middleware validates a username and password for mutation requests but does not block introspection queries for any request that passes the basic credentials. Even when Basic Auth is implemented, if the server does not explicitly disable introspection or scope it to privileged roles, the schema remains readable, increasing the attack surface.

middleBrick’s LLM/AI Security checks are relevant here because schema exposure can indirectly aid prompt injection or data exfiltration attempts if the API is coupled with AI-driven tooling. The scanner runs active prompt injection probes and system prompt leakage detection, but it also flags overly open introspection as a discoverability risk that can assist downstream attacks.

To detect this behavior, the scanner compares the OpenAPI/Swagger specification (including full $ref resolution) against runtime responses. If the spec indicates a protected endpoint but runtime introspection is allowed without elevated scopes, a finding is generated with remediation steps tied to the framework and compliance mappings such as OWASP API Top 10 and SOC2 controls.

Basic Auth-Specific Remediation in Express — concrete code fixes

To secure GraphQL introspection in Express with Basic Auth, explicitly disable introspection in production or scope it to authorized requests. Below are concrete, working code examples that demonstrate how to implement these controls.

Example 1: Disable introspection entirely in production

const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
const schema = buildSchema(`
  type Query {
    hello: String
  }
`);
const rootValue = {
  hello: () => 'world',
};

const app = express();
const isProduction = process.env.NODE_ENV === 'production';

app.use('/graphql', graphqlHTTP((req, res) => ({
  schema,
  rootValue,
  graphiql: !isProduction,
  customFormatErrorFn: (error) => ({
    message: error.message,
    code: error.originalError && error.originalError.code,
  }),
  validationRules: isProduction ? [] : [], // keep dev tools in dev
  // Disable introspection in production by overriding the default behavior
  allowedRequestMethods: isProduction ? ['POST'] : ['GET', 'POST'],
})));

app.listen(4000, () => console.log('Server running on port 4000'));

Example 2: Enforce Basic Auth and restrict introspection to admin requests

const auth = require('basic-auth');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
const schema = buildSchema(`
  type Query {
    public: String
    secret: String
  }
`);
const rootValue = {
  public: () => 'public data',
  secret: () => 'secret data',
};

function checkAdmin(credentials) {
  return credentials && credentials.name === 'admin' && credentials.pass === 'securepassword';
}

const app = express();

app.use('/graphql', (req, res, next) => {
  const user = auth(req);
  if (!user) {
    res.set('WWW-Authenticate', 'Basic realm="example"');
    return res.status(401).send('Authentication required.');
  }
  if (!checkAdmin(user)) {
    return res.status(403).send('Insufficient privileges.');
  }
  // Attach user context for use in GraphQL resolvers or validation
  req.user = user;
  next();
});

app.use('/graphql', graphqlHTTP((req, res) => ({
  schema,
  rootValue,
  graphiql: false,
  // Optionally disable introspection unless explicitly allowed for admin context
  customValidationRules: () => [
    // Introspection queries can be blocked by validation rules in custom setups
  ],
})));

app.listen(4000, () => console.log('Server running on port 4000'));

Example 3: Use a request filter to block introspection queries

const { graphqlHTTP } = require('express-graphql');
const { parse, visit } = require('graphql');
const auth = require('basic-auth');

function isIntrospectionOperation(operation) {
  return operation.operation === 'query' &&
    operation.selectionSet &&
    operation.selectionSet.selections.some(
      (sel) => sel.name && sel.name.value === '__schema'
    );
}

function stripIntrospection(document) {
  return visit(document, {
    OperationDefinition(node) {
      if (isIntrospectionOperation(node)) {
        throw new Error('Introspection queries are not allowed');
      }
    },
  });
}

const app = express();

app.use('/graphql', (req, res, next) => {
  const user = auth(req);
  if (!user || user.name !== 'admin') {
    res.set('WWW-Authenticate', 'Basic');
    return res.status(401).send('Authentication required.');
  }
  req.rawBody = '';
  req.setEncoding('utf8');
  req.on('data', (chunk) => { req.rawBody += chunk; });
  req.on('end', () => {
    try {
      const document = parse(req.rawBody);
      stripIntrospection(document);
      next();
    } catch (e) {
      res.status(400).send(e.message);
    }
  });
});

app.use('/graphql', graphqlHTTP((req, res) => ({
  schema,
  rootValue,
  graphiql: false,
})));

app.listen(4000, () => console.log('Server running on port 4000'));

Operational guidance

  • Prefer POST-only methods for production introspection settings to reduce accidental exposure via browser preflight or logs.
  • Combine Basic Auth with HTTPS to protect credentials in transit; Basic Auth sends credentials in an easily decoded header.
  • Use environment-based configuration to toggle introspection and graphiql, ensuring development features are not present in production.
  • Consider additional authorization checks inside resolvers for field-level security, even when introspection is disabled.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Does enabling Basic Auth alone prevent GraphQL introspection attacks?
No. Basic Auth can ensure requests include credentials, but if the GraphQL endpoint allows introspection without checking authorization per operation, an authenticated or unauthenticated attacker can still retrieve the schema. You must explicitly disable introspection or gate it behind role checks.
How can I verify that introspection is properly restricted in my Express GraphQL endpoint?
Send an introspection query (POST { query: '{ __schema { queryType { name } }' }) to your endpoint without privileged credentials. If you receive schema details, introspection is too permissive. Use the provided code examples to disable or scope introspection and re-test.