// VULNERABLE: GraphQL server with exposed introspection
const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');
// Sensitive schema with internal operations
const typeDefs = gql`
type User {
id: ID!
email: String!
passwordHash: String! # Sensitive field
adminLevel: Int! # Internal authorization field
apiKeys: [String!]! # Sensitive API keys
internalNotes: String # Confidential data
}
type Query {
users: [User!]!
# Internal admin queries exposed
getUserSecrets(userId: ID!): UserSecrets
systemMetrics: SystemStatus
databaseHealth: DbStatus
}
type Mutation {
# Dangerous admin operations exposed
promoteToAdmin(userId: ID!): User
deleteUserCompletely(userId: ID!): Boolean
backdoorAccess(secret: String!): AuthToken
resetAllPasswords: Boolean
}
# Internal types that shouldn't be discoverable
type UserSecrets {
encryptionKey: String!
backupCodes: [String!]!
recoveryEmail: String!
}
`;
const resolvers = {
Query: {
users: () => User.find(),
getUserSecrets: (_, { userId }) => UserSecrets.findByUserId(userId),
systemMetrics: () => getSystemMetrics(),
},
Mutation: {
promoteToAdmin: (_, { userId }) => promoteUser(userId),
backdoorAccess: (_, { secret }) => createBackdoorSession(secret),
}
};
// PROBLEM: No security configuration
const server = new ApolloServer({
typeDefs,
resolvers,
// introspection: true (default)
// playground: true (default)
});
const app = express();
server.applyMiddleware({ app });
app.listen(4000, () => {
console.log('GraphQL server running on http://localhost:4000/graphql');
});
// SECURE: GraphQL server with comprehensive security
const { ApolloServer, gql } = require('apollo-server-express');
const { NoIntrospection, specifiedRules } = require('graphql');
const depthLimit = require('graphql-depth-limit');
const costAnalysis = require('graphql-query-complexity').costAnalysisValidator;
const express = require('express');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const isProduction = process.env.NODE_ENV === 'production';
// Sanitized schema - sensitive fields removed or protected
const typeDefs = gql`
type User {
id: ID!
email: String!
# passwordHash removed from schema
role: Role! # Enum instead of internal levels
createdAt: DateTime!
# Sensitive fields removed
}
enum Role {
USER
ADMIN
}
type Query {
# Public queries only
me: User # Current user only
publicUsers: [User!]! # Limited public data
# Internal admin queries removed from public schema
}
type Mutation {
# Safe operations only
updateProfile(input: ProfileInput!): User
changePassword(currentPassword: String!, newPassword: String!): Boolean
# Dangerous admin operations removed
}
input ProfileInput {
firstName: String
lastName: String
bio: String
}
`;
// Rate limiting for GraphQL
const graphqlLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: isProduction ? 100 : 1000, // Stricter in production
message: {
error: 'Too many GraphQL requests',
retryAfter: 60
},
keyGenerator: (req) => {
// Rate limit by user ID if authenticated, otherwise by IP
return req.user?.id || req.ip;
}
});
const resolvers = {
Query: {
me: (_, __, { user }) => {
if (!user) throw new Error('Authentication required');
return User.findById(user.id).select('-passwordHash -internalNotes');
},
publicUsers: () => {
return User.find({ isPublic: true })
.select('id email role createdAt')
.limit(50);
}
},
Mutation: {
updateProfile: async (_, { input }, { user }) => {
if (!user) throw new Error('Authentication required');
return User.findByIdAndUpdate(
user.id,
{ $set: input },
{ new: true }
).select('-passwordHash');
}
}
};
// Security configuration
const server = new ApolloServer({
typeDefs,
resolvers,
// SECURITY: Disable introspection and playground in production
introspection: !isProduction,
playground: !isProduction,
// Query validation and complexity analysis
validationRules: [
...specifiedRules,
...(isProduction ? [NoIntrospection] : []),
depthLimit(10),
costAnalysis({
maximumCost: 1000,
defaultCost: 1,
createError: (max, actual) => {
throw new Error(`Query cost ${actual} exceeds maximum ${max}`);
}
})
],
// Context with authentication
context: ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');
const user = token ? verifyToken(token) : null;
return {
user,
isProduction,
userAgent: req.headers['user-agent'],
ip: req.ip
};
},
// Error formatting - hide sensitive information
formatError: (error) => {
// Log full error server-side
console.error('GraphQL Error:', {
message: error.message,
locations: error.locations,
path: error.path,
source: error.source?.body
});
if (isProduction) {
// Hide internal errors in production
if (error.message.includes('introspection')) {
return new Error('Query not allowed');
}
if (error.originalError?.code === 'INTERNAL_ERROR') {
return new Error('An internal error occurred');
}
}
return error;
},
// Monitoring and security plugins
plugins: [
{
requestDidStart() {
return {
didResolveOperation(requestContext) {
const query = requestContext.request.query;
// Monitor for introspection attempts
if (query?.includes('__schema') || query?.includes('__type')) {
console.warn('Introspection attempt detected:', {
query: query.substring(0, 200),
userAgent: requestContext.request.http?.headers?.get('user-agent'),
ip: requestContext.request.ip,
timestamp: new Date().toISOString()
});
if (isProduction) {
throw new Error('Introspection is disabled');
}
}
// Monitor for complex queries
if (query?.length > 2000) {
console.warn('Large query detected:', {
size: query.length,
userAgent: requestContext.request.http?.headers?.get('user-agent'),
ip: requestContext.request.ip
});
}
}
};
}
}
]
});
const app = express();
// Additional security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
}));
// Apply rate limiting
app.use('/graphql', graphqlLimiter);
// Apply GraphQL middleware
server.applyMiddleware({
app,
path: '/graphql',
cors: {
origin: isProduction ? process.env.ALLOWED_ORIGINS?.split(',') : true,
credentials: true
}
});
app.listen(4000, () => {
console.log(`GraphQL server running on http://localhost:4000${server.graphqlPath}`);
console.log(`Introspection: ${!isProduction ? 'enabled' : 'disabled'}`);
console.log(`Playground: ${!isProduction ? 'enabled' : 'disabled'}`);
});