Search code examples
graphqlgraphql-jsapollo-server

Sanitize/reject user inputs containing unsafe html in graphql(JS)


I've been searching up and down about this, but I can't find anything relevant about this, but is there a good solution for sanitizing html characters in user input in a graphql mutation?

I know for a fact that an input like name: "<script>{alert('foo')}</script>" is accepted by graphql(apollo-server-express in my case) as is, but that's not ok. Sure, frameworks like React won't set that string as html, so it's "safe" there, but what if the graphql endpoint is consumed by a site which does embed strings as html?

So anyone know of a good solution for this? I've looked at a couple of packages, but they're mostly pretty small, with little activity on them.

Another solution would be to sanitize at the database level, with something like sanitize-html, but I wanted to see if there was a proper solution at the schema level


Solution

  • Ended up just sanitizing at the model level, created a general Model class which sanitizes everything in args:

    export default class Model {
      constructor(parent, args, context, info) {
        this.db = db;
        this.parent = null;
        this.args = null;
        this.context = null;
        this.info = null;
    
        this.#init(parent, args, context, info);
      }
    
      #init = (parent, args, context, info) => {
        this.parent = parent;
        this.args = this.#sanitizeObject(args);
        this.context = context;
        this.info = info;
      };
    
      #sanitizeObject = (args) => {
        let sanitizedArgs = {};
    
        Object.entries(args).forEach(([key, value]) => {
          if (Array.isArray(value)) {
            sanitizedArgs[key] = this.#sanitizeArray(value);
          } else if (typeof value === 'object') {
            sanitizedArgs[key] = this.#sanitizeObject(args[key]);
          } else {
            sanitizedArgs[key] = this.#sanitizeInput(value);
          }
        });
    
        return sanitizedArgs;
      };
    
      #sanitizeArray = (args) => {
        return args.map((value) => {
          if (Array.isArray(value)) {
            return this.#sanitizeArray(value);
          } else if (typeof value === 'object') {
            return this.#sanitizeObject(value);
          } else {
            return this.#sanitizeInput(value);
          }
        });
      };
    
      #sanitizeInput = (input) => {
        return DOMPurify.sanitize(input);
      };
    }