title: Using ast and whitelists to make python's eval() safe?
body: <p>OK. I <a href="https://stackoverflow.com/questions/3513292/python-make-eval-safe">know</a> <a href="https://stackoverflow.com/questions/1994071/how-safe-is-expression-evaluation-using-eval">the</a> <a href="https://stackoverflow.com/questions/28369/is-safe-eval-really-safe">experts</a> <a href="https://stackoverflow.com/questions/5351059/is-this-a-safe-use-of-python-eval">have</a> <a href="http://me.veekun.com/blog/2012/03/24/on-principle/" rel="nofollow noreferrer">spoken</a> and you should not <em>ever</em> use python's <code>eval()</code> on untrusted data, ever. I'm not smarter than the rest of the world, and shouldn't even try this. But! I'm going to, anyhow.</p>
  
  <p>My basic problem is that I'm looking to write a little calculator evaluator program that'll take untrusted input, using a subset of python's syntax. I know: use <a href="http://www.dabeaz.com/ply/" rel="nofollow noreferrer">ply</a> or <a href="http://pyparsing.wikispaces.com/" rel="nofollow noreferrer">pyparsing</a> and write a parser and there we go. Screwing around with passing globals and locals to <code>eval()</code> will not do the trick.</p>
  
  <p>All the approaches I've seen (and been leery about) try to enumerate evil. Here, I'm trying to enumerate <em>good</em> -- get an AST, allow only a few node types, and then verify that any calls are to one of a set of whitelisted functions. Here's a mini-implementation (and <a href="https://gist.github.com/3759435" rel="nofollow noreferrer">a gist</a>):</p>
  
  <pre class="lang-py prettyprint-override"><code>import ast
  import math
  
  SAFE_FX = {
      'exp': math.exp,
  }
  
  SAFE_NODES = set(
      (ast.Expression,
      ast.Num,
      ast.Call,
      ast.Name,
      ast.Load,
      ast.BinOp,
      ast.Add,
      ast.Sub,
      ast.Mult,
      ast.Div,)
  )
  
  class CleansingNodeVisitor(ast.NodeVisitor):
      def generic_visit(self, node):
          if type(node) not in SAFE_NODES:
              raise Exception("%s not in SAFE_NODES" % type(node))
          super(CleansingNodeVisitor, self).generic_visit(node)
  
      def visit_Call(self, call):
          if call.func.id not in SAFE_FX:
              raise Exception("Unknown function: %s" % call.func.id)
  
  def my_safe_eval(s):
      tree = ast.parse(s, mode='eval')
      cnv = CleansingNodeVisitor()
      cnv.visit(tree)
      compiled = compile(tree, s, "eval")
      return(eval(compiled, SAFE_FX))
  </code></pre>
  
  <p>So, <code>my_safe_eval('2*(4+exp(1.3))')</code> works, while <code>my_safe_eval('[].__class__')</code> tricks and <code>my_safe_eval('open("/something/evil")')</code> is likewise forbidden -- without forbidding <code>__builtins__</code> or <code>__locals__</code> or anything.</p>
  
  <p>I... I think this works. Am I mad?</p>