I want to attach functional stubs to a data process job I've written, and it would be convenient to be able to apply these via config file.
I can load and run these by means of the eval
function, but want to be able to control the available namespace "sandbox" in which the evaluated functions can operate, so I can avoid malicious code injection.
In the python docs, it suggests blocking off __builtins__
and then populating either (or is it both? it's not clear) globals and locals as dictionaries containing the objects in the execution namespace.
When I do this, the code I had been running successfully stops working.
I think this is because one of my test lambdas is referencing functions normally imported from the datetime
module - but it's not clear to me how to get these to successfully attach to the namespace.
from datetime import datetime
now = datetime.now()
lambdas = { "Report Date" : "lambda x : now.strftime(\"%d/%m/%Y\")",
"Scary Test" : "lambda x : os.curdir " }
compiled_funcs = {k:eval(v) for k,v in lambdas.items()}
compiled_funcs ['Report Date'](1)
>>> '15/04/2019'
compiled_funcs ['Scary Test'](1)
>>> '.'
Now, I want to edit the eval()
function to limit the available scope so that the datetime function continues to work, but the os
module fails (if I can call an os command, then I could do something scary like wipe the disk, or worse)
I have tried constructions like:
compiled_funcs = {k:eval(v,{'__builtins__':None, "now" : now, "datetime" : datetime, } , { }) for k,v in lambdas.items()}
But when I do this, I get the following error:
AttributeError: 'NoneType' object has no attribute '__import__'
Which suggests that somewhere/somehow, the function I want to apply is trying to call/import some content down the line - and (presumably) this is correctly being blocked by having borked the __builtins__
content. Is there a way to pre-package such functions and inject them into the eval globals, or locals dictionaries to enable a pre-defined set of functional tools?
Once I've got this working, I should be able to extend it so I can curate my own subset of safe function calls to be exposed to this run-time collection from configuration files.
N.B. I know in the above, I could define my lambdæ with no arguments - but in my real code, it would be normal to feed a single parameter, so have built my test code accordingly.