Graphql Introspection in Cassandra
How GraphQL Introspection Manifests in Cassandra
When a GraphQL API uses Apache Cassandra as its data store, introspection queries can reveal more than just the GraphQL schema; they can indirectly expose the underlying Cassandra data model. An attacker begins by sending a standard introspection request such as:
{
__schema {
types { name kind fields { name type { name kind } } }
}
}
The response lists every GraphQL type, its fields, and the associated types. In a Cassandra‑backed implementation, each GraphQL type often maps directly to a Cassandra table (or a user‑defined type) and each field maps to a column. By enumerating the types and fields, an attacker can infer table names, column names, and even the primary‑key structure without needing any credentials.
This information becomes dangerous when the GraphQL resolvers construct CQL statements by concatenating user‑supplied input. For example, a resolver that fetches a user profile might look like this in Java using the DataStax driver:
public ResultSet getUser(String userId) {
String cql = "SELECT * FROM users WHERE user_id = '" + userId + "';
return session.execute(cql);
}
If the resolver receives the userId from a GraphQL argument, an attacker who knows the table name (users) and column (user_id) can inject additional CQL via the argument, turning a simple lookup into a data‑exfiltration or even a destructive operation. The introspection step is the reconnaissance phase that makes such injection feasible because it tells the attacker exactly which table and column to target.
Thus, GraphQL introspection in a Cassandra context does two things: (1) it leaks the logical data model, and (2) it enables attackers to craft precise, schema‑aware CQL injection payloads when resolvers are not using prepared statements or proper input validation.
Cassandra-Specific Detection
middleBrick includes GraphQL introspection as part of its Input Validation and Data Exposure checks. When scanning an endpoint, it automatically sends a set of introspection queries (including __schema, __type, and fragmented variations) and analyses the responses for:
- Presence of type definitions that map to Cassandra tables (e.g., types named after known Cassandra tables or containing fields that resemble column names).
- Absence of authentication or authorization on the introspection endpoint.
- Indicators that resolvers may be building CQL strings unsafely (detected via patterns in error messages or unexpected data in the response).
To run this check from the command line, you would use the middleBrick CLI:
middlebrick scan https://api.example.com/graphql
The tool returns a JSON report that includes a finding such as:
{
"id": "GRAPHQL-INTROSPECTION-001",
"name": "GraphQL Introspection Enabled",
"severity": "medium",
"description": "The API exposes __schema and __type fields, revealing the underlying Cassandra table/column structure.",
"remediation": "Disable introspection in production or restrict it to authenticated users."
}
In the Dashboard, the finding appears under the "Input Validation" category with a trend line showing whether the issue persists across scans. The GitHub Action can be configured to fail a build if this finding is present, preventing the deployment of an API that unintentionally leaks its Cassandra schema.
Cassandra-Specific Remediation
The primary remediation is to prevent unauthenticated introspection from revealing the Cassandra data model. This can be done at the GraphQL layer and reinforced with Cassandra‑native controls.
1. Disable or restrict introspection
If you are using Apollo Server (Node.js), set the introspection flag to false in production:
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');
const server = new ApolloServer({
typeDefs, // your GraphQL schema
resolvers,
introspection: false // disables __schema/__type
});
startStandaloneServer(server, { listen: { port: 4000 } });
If you need introspection for internal tooling, guard it with authentication or IP‑allow lists using middleware:
server.applyMiddleware({ app, path: '/graphql', cors: false });
app.use('/graphql', (req, res, next) => {
if (req.body.query && req.body.query.includes('__schema')) {
// allow only from trusted internal IPs
if (!req.ip.startsWith('10.')) {
return res.status(403).send('Introspection disabled');
}
}
next();
});
2. Use prepared statements in resolvers
Avoid string concatenation when building CQL. The DataStax driver supports prepared statements that automatically handle parameterization:
const cassandra = require('cassandra-driver');
const session = new cassandra.Client({ contactPoints: ['127.0.0.1'], localDataCenter: 'datacenter1', keyspace: 'mykeyspace' });
const getUserStmt = session.prepare('SELECT * FROM mykeyspace.users WHERE user_id = ?');
async function getUser(parent, args, context, info) {
const [result] = await session.execute(getUserStmt, [args.userId]);
return result ? result.row() : null;
}
Because the userId is bound as a parameter, even if an attacker knows the table name from introspection, they cannot inject additional CQL.
3. Apply Cassandra role‑based access control (RBAC)
Create a dedicated role for the API user and grant only the necessary permissions:
CREATE ROLE api_user WITH PASSWORD = 'StrongPass!2025' LOGIN = true;
GRANT SELECT, INSERT ON KEYSPACE mykeyspace TO api_user;
-- optionally restrict to specific tables
GRANT SELECT ON TABLE mykeyspace.users TO api_user;
Configure the DataStax driver to connect using this role:
const auth = new cassandra.auth.PlainTextAuthProvider('api_user', 'StrongPass!2025');
const session = new cassandra.Client({ contactPoints: ['127.0.0.1'], localDataCenter: 'datacenter1', keyspace: 'mykeyspace', authProvider: auth });
With RBAC in place, even if an attacker discovers a table name via introspection, they cannot read or modify data without valid credentials.
4. Enable audit logging
Turn on Cassandra’s built‑in audit log to capture any unexpected queries:
ALTER SYSTEM SET audit_logging_options = {'enabled': 'true', 'logger': 'SLF4JAuditWriter', 'retention_time': '86400'};
Reviewing the audit log helps detect attempts to query system_schema tables or to execute malformed CQL that may have originated from a GraphQL resolver.
By combining GraphQL‑level introspection restrictions, prepared‑statement resolvers, Cassandra RBAC, and audit logging, you eliminate the information leakage that introspection provides and close the path to CQL injection.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |