GraphQL-Specific Vulnerabilities

GraphQL-Specific Vulnerabilities

GraphQL's flexible query language introduces unique SQL injection risks:

// Vulnerable GraphQL resolver
const resolvers = {
    Query: {
        // Dangerous: Dynamic field selection based on GraphQL query
        users: async (parent, args, context) => {
            const { where, orderBy } = args;
            
            // Building SQL from GraphQL input - VULNERABLE!
            let sql = 'SELECT * FROM users';
            
            if (where) {
                const conditions = Object.entries(where)
                    .map(([field, value]) => `${field} = '${value}'`)
                    .join(' AND ');
                sql += ` WHERE ${conditions}`;
            }
            
            if (orderBy) {
                sql += ` ORDER BY ${orderBy.field} ${orderBy.direction}`;
            }
            
            return await context.db.query(sql);
        }
    }
};

// Secure GraphQL implementation with proper validation
import { GraphQLScalarType } from 'graphql';
import { Kind } from 'graphql/language';

// Custom scalar for safe SQL identifiers
const SafeSQLIdentifier = new GraphQLScalarType({
    name: 'SafeSQLIdentifier',
    serialize: value => value,
    parseValue: value => {
        if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
            throw new Error('Invalid SQL identifier');
        }
        return value;
    },
    parseLiteral: ast => {
        if (ast.kind !== Kind.STRING) {
            throw new Error('SQL identifier must be a string');
        }
        return ast.value;
    }
});

const secureResolvers = {
    Query: {
        users: async (parent, args, context) => {
            const { where, orderBy, limit = 100 } = args;
            
            // Use query builder with parameterization
            const query = context.db('users').select('*');
            
            // Safe where clause construction
            if (where) {
                const allowedFields = ['id', 'username', 'email', 'status'];
                
                Object.entries(where).forEach(([field, value]) => {
                    if (allowedFields.includes(field)) {
                        if (value.contains) {
                            query.where(field, 'like', `%${value.contains}%`);
                        } else if (value.equals) {
                            query.where(field, '=', value.equals);
                        } else if (value.in) {
                            query.whereIn(field, value.in);
                        }
                    }
                });
            }
            
            // Safe ordering
            if (orderBy && ['username', 'created_at'].includes(orderBy.field)) {
                query.orderBy(orderBy.field, orderBy.direction || 'asc');
            }
            
            // Safe pagination
            query.limit(Math.min(limit, 1000));
            
            return await query;
        }
    }
};