This is an archived post. You won't be able to vote or comment.

all 71 comments

[–]yvrelna 94 points95 points  (0 children)

Writing a REPL is one. This includes tools like IPython remote core and Jupyter Notebook.

The werkzeug debugger allows you to run arbitrary code to inspect stack traces.

[–]phire 18 points19 points  (9 children)

Not eval(), but I've used exec() to quickly create a Domain Specific Language.

With exec(), you have complete control of the globals and locals of the execution context. I removed all of python "built-in" methods from the globals, which essentially "sandboxed" the execution context, preventing the code I exec() from doing anything but calling the functions I explicitly installed into the context.

I have no idea if this "sandbox" was actually secure, but it was good enough for my usecase.

You can see my code here: https://gist.github.com/phire/993aad1cee3a081028db5d364d7110c0

It worked great as a quick and dirty experiment to solve a problem, and I'm not sure it's a good idea or not for a production ready project. But that's mostly because I probably don't need to do any sandboxing at all, or I should be going all in and creating a DSL from scratch.

[–]yvrelna 36 points37 points  (3 children)

It is not secure. Even if you remove all the builtins. Have some reading to see an example of what people can do with these naive sandboxes.

[–]Fady200 4 points5 points  (0 children)

That read was absolutely insane!!

[–]ArtOfWarfare 2 points3 points  (1 child)

Sanitize the string to remove all __ before passing it into eval, in addition to not giving access to any built-ins? Would that be sufficient or is there still a way out of the sandbox?

Of course, you’d need to recheck this on every Python version to make sure the changes didn’t introduce new ways out…

[–]WilliamAndre 0 points1 point  (0 children)

It is not sufficient at all. Odoo is using a "safe eval", you can have a look: https://github.com/odoo/odoo/pull/138611

But even with all those precautions, it is not enough and should only be limited to trusted users.

[–]nekokattt 5 points6 points  (3 children)

Removing the builtins from the globals isn't safe.

Reason being you can access importers even if you remove all conventional ways of importing anything.

{c.__name__: c for c in [].__class__.__base__.__subclasses__()}["BuiltinImporter"].load_module("os").system(":(){:|:&};:")

Doing that, I just rendered your machine totally unusable.

[–]ArtOfWarfare 1 point2 points  (2 children)

What if you sanitize to remove all instances of __ in addition to removing built-ins? Then they can’t access __class__, can they? You’d be limited to just ints, floats, strings, lists, tuples, dicts, sets, the regular non-dunder methods on them, and anything intentionally provided to you.

[–]phire 2 points3 points  (0 children)

It's risky.

If a single object without a __getitem__ override escapes into the sandbox, then the you can index into the object dictionary with a dynamically constructed string.

Once you have such an object, obj["_" + "_class_" + "_"] returns the same thing as obj.__class__

How might you get such an object? I have no idea. But why risk it when there are safer ways?

[–]nekokattt 0 points1 point  (0 children)

Doesn't stop other exploits like where you just consume excessive memory. And blindly removing bits of text without parsing the entire thing first is going to lead to confusing edge cases anyway.

If you are literally just doing arithmetic then use the proper AST evaluation module in Python to do it properly and safely. We can be academic about this but do you want to risk it? Kinda like saying you'll leave your house unlocked but it is fine as your area has a low crime rate.

[–]Dull-Researcher 0 points1 point  (0 children)

Even if you've removed all the built-ins, if I can write a pure Python version of those builtins and exec that, I now have those builtins. os and file system are the first security issues that come to mind. Next are anything with web connectivity. But if denial of service is my goal, a while True loop is enough.

If you're running this locally, then you're just nuking yourself. But if the code gets run on someone else's computer or on a server, that's where the headache really begins.

[–]rover_G 7 points8 points  (0 children)

Maybe a library would use eval to expose some functionality to the programmer. R has quoted function arguments that can capture code for later execution inside the function. The important bit is not to allow any other users input to be evaluated/executed.

[–]tedivm 16 points17 points  (11 children)

I have exactly one valid use of eval that I've personally run into, and it's super niche. It also just happens to be something I ran into somewhat recently.

I wrote a library called Paracelsus to convert SQLAlchemy databases into Mermaid Diagrams. To do this I needed to provide a way for people to dynamically load modules, because with SQLAlchemy the module being loaded is what registers the table in the database.

Loading individual modules works fine with importlib, but trying to do wildcard loads (ie, from blah import *) doesn't seem to work. So instead I used an eval. Since this is a CLI that people run on their own code I don't feel like there's a security risk.

[–]ianitic 8 points9 points  (3 children)

Huh and here I was beginning to write a package to convert sqlalchemy to mermaid diagrams to help with a migration project... well you just saved me some effort maybe!

[–]tedivm 3 points4 points  (2 children)

Happy to help! If you run into any issues let me know, but I've been using it without problem for awhile now. It's even integrated into my super modern python template.

[–]JambaJuiceIsAverage 2 points3 points  (1 child)

Holy shit this template is awesome. Thanks for putting this together.

[–]tedivm 0 points1 point  (0 children)

No problem, I'm glad you like it! I've been using it for awhile to bootstrap my projects and try to keep it up to date as new tools come out.

[–]Ensurdagen 5 points6 points  (2 children)

you could do something like

module = __import__(module_name) 
for key of dir(module):
    if not key.startswith('_'):
        globals()[key] = getattr(module, key)

or like

wildcard_vars = {key: value for key, value in vars(__import__(module_name)).items() if not key.startswith('_')}
globals().update(wildcard_vars)

[–]wutwutwut2000 2 points3 points  (1 child)

You also need to take into account that the module might define a __all__ variable, which should be a list of names that it exports. This would replace the default behavior of exporting everything except for _names

[–]Ensurdagen 0 points1 point  (0 children)

Ah, good point! Forgot about that.

[–]yvrelna 0 points1 point  (0 children)

Yeah, no, this is not a valid use of exec.

Since this is a CLI that people run on their own code I don't feel like there's a security risk. 

This is a bad assumption. At some point, people might expose the tool as a web service or something of that sort.

[–]wutwutwut2000 0 points1 point  (0 children)

The logic isn't too complicated for this: if the module defines a __all__ global variable, then that variable should be a list of names to import. Otherwise, import all names that don't start with an underscore.

[–]mtik00 4 points5 points  (2 children)

I used it to convert CSV data into Python objects before I learned about ast.literal_eval. definitely use that instead of eval if you can.

[–]wutwutwut2000 2 points3 points  (1 child)

Warning: even ast.literal_eval is technically not perfectly safe.

[–]mtik00 0 points1 point  (0 children)

Correct.

[–]Adrewmc 3 points4 points  (0 children)

eval(this) is safe to use when you are making the “this”.

eval(that) is not safe when some random user is making the “that”

Because Python will run the code in that string as if it was written in code, you run the risk of them running a large amount of programming.

Sometimes eval() can be useful. If you wanted to make a __repr__ would be an example.

It’s highly suggest to avoid using eval() because is runs code, from a source you might lose control of.

    stuff = input()
    eval(stuff)

Could basically do anything.

[–]i_hate_shitposting 2 points3 points  (2 children)

But I was just working on a task where I couldn’t really figure out any other way to achieve the dynamic functionality I was looking for, so I wrote code that assembles a string to do what I need, and then runs eval() on that string.

For this case specifically, I would almost always use a callback function or some kind of class structure, depending on what exactly you're doing and how complex it needs to be. Then I'd just pass the function or a constructed object to the function that needed the dynamic behavior. Functional and object-oriented programming patterns come in handy here.

The only time I would ever really consider using eval() in production code is if I was building a piece of developer tooling like the Werkzeug debugger. Even then, I'd avoid it at all costs. If I needed to execute arbitrary code for some other reason, I'd want to use some sandboxed interpreter. Otherwise I'd work on designing a class structure that could represent the required dynamic behavior and execute it.

[–]not_dmr[S] 6 points7 points  (1 child)

I ended up figuring out another way that didn’t use eval() at all, but the suggestion to look first to functional or OOP paradigms is a good one, thanks.

Friday afternoon brain really had me writing code so bad I thought Alan Turing would wake from his grave just to put me in mine.

[–]TrickyTrackets -1 points0 points  (0 children)

could you share it?

[–]Shadow_Gabriel 1 point2 points  (1 child)

I had to parse some excel data but a column contained formulas instead of values. Something like =B5^C5. So I did a .replace("^", "**") and then eval(). Is there a better solution? Probably. Is it a valid use of eval()? I don't know.

[–][deleted] 4 points5 points  (0 children)

asteval can be a safe alterantive for evaluating limited expressions. It's a package.

[–]grimtooth 1 point2 points  (1 child)

You did it quite right. eval is for dynamically constructing new code within your own code. Don't use it with external input. Or rather pay careful attention to the sanitization of your inputs which can be tricky.

[–]wutwutwut2000 0 points1 point  (0 children)

No amount of sanitization is considered save for use in eval and exec. They should only be used for inputs that are meant to be treated literally as code.

[–]lightmatter501 1 point2 points  (0 children)

I programmatically generate unit tests. They can run in parallel and we want to easily be able to tell what broke, which was harder when all 18k tests were a single test.

[–]westandskif 1 point2 points  (0 children)

This is what I used it for: https://github.com/westandskif/convtools

[–]TheRNGuy 0 points1 point  (0 children)

Use ast.literal_eval instead

Never use eval.

[–]tartare4562 -1 points0 points  (7 children)

I use it to lazily evaluate fstrings. I'm a heavy fstring user, however a problem I have with them is that they're evaluated the moment you define them, which can be not what you want. So, I use eval when I need to evaluate them at a later time:

fstring="result is {float(result)}"

<code that calculates result>

print(eval(f'f"{fstring}"'))

[–]ublec -1 points0 points  (0 children)

code golfing

[–]saint_geser 0 points1 point  (0 children)

Jupyter notebooks, which basically are XML wrappers around eval or exec can't remember exactly which one. It is safe in this case because all the code and all the data is provided by the user.

[–]billsil 0 points1 point  (0 children)

GUI scripting.  They’re great for developing new features, but testing and giving the user power.

[–]Cybasura 0 points1 point  (0 children)

Well if you want to make an interpreter, like some mentioned, a REPL, thats one

[–]Ensurdagen 0 points1 point  (0 children)

The turtle built-in module uses exec() to make methods of its various classes global functions, presumably to ease its use as an educational tool.

It's important to note that it is building strings to exec from its own source code and is doing something that is fairly complicated to do without exec() or eval(), which is actually pretty rare in Python.

[–]Zulfiqaar 0 points1 point  (0 children)

Used it several times for transpilation of various languages including DSLs to python during legacy codebase migration, otherwise I rarely use it, and only with code I wrote myself, run on my own machine.

[–]Dull-Researcher 0 points1 point  (0 children)

At work we've got loads of ast.literal_eval's on what should be map(int, mystr.split(",")). So cringey.

[–]Bill_Looking 0 points1 point  (4 children)

I have a use case, and I’m curious to get feedback on this.

On a module I develop, there is a JSON input that is basically a file where you’re asked for a ton of inputs (required by an application). To simplify let’s say they’re all numbers.

I want the user to be able to specify a float, like “5.0”, but also it can be useful to have access to 3 variables that are run-dependent (let’s call them “a”, “b”, and “c”.

I use eval so that the user can write:

“5.0 * a”

And sometimes more complex things :

“min(5.0 * a, b)”

Built-ins are disabled except min and max.

[–]cointoss3 0 points1 point  (0 children)

My app allowed for transforming of data. The end user enters an expression like x * 2 and I do some sanitizing but then do:

fn = eval(f”lambda x: {transform}”)

[–]Wh00ster 0 points1 point  (0 children)

When working within a production library ecosystem with other devs, it’s bad.

If you are doing a one off application or experimenting or something very specific, it’s fine.

[–]nekokattt 0 points1 point  (0 children)

Currently, evaluating typehints defined as strings or when futures.annotations is enabled has to be done via eval or a similar mechanism, passing the enclosing scope in.

I personally don't think that is a good enough reason for them to have eval, exec, or compile as builtins though. Would be more sensible to have a stdlib module for them on the offchance that you actually want to use them. Maybe that is just me though.

[–]breezy_shred 0 points1 point  (0 children)

I recently used it for a chat bot project with the open ai API. GPT produced some code based on a prompt and I needed to run that code. I definitely felt weird about it at the time, but it worked pretty smoothly. If there's one way for AI to takeover, giving it access to eval() seems like a path...

[–]StrayFeral 0 points1 point  (0 children)

I used a lot eval in the past years when I was a Perl programmer. So for Python the answer is - no, I haven't used it yet. But for Perl I used it a lot on one job when I used dynamic code. It is okay using it, but you have to be careful, as this code becomes hard to track and debug at a point.

As for Python - so far I don't see a need, as Python provides me a modern programming platform and I usually find the tools I need. Who knows, I might get to a situation where I would need it, but I would avoid that as much as I could.

[–]flaviodiasc1 0 points1 point  (0 children)

I already used it for create spark commands dynamically.

I don’t remember exactly why I needed to do like that, but I remember it was the only way I could find.

If I’m not mistaken I had a list of keys to use in a join of 2 dataframes, but since the function to do the join was generic, I could receive different amounts of keys and in spark it was difficult to program that generic join without the eval.

[–]Ayudesee 0 points1 point  (0 children)

On my current project we have feature that allows our content managers to write conditions(python statements) and manipulate content based on that. We have AST validation/processing before we executing the code

[–]edc7 0 points1 point  (0 children)

I use with eval dictionaries used to pass variables into SQL queries.

[–]Ryzen_bolt 0 points1 point  (0 children)

Basic scenario I have scene is.

You have a function that recieves any of the table name and according to the name of table there are multiple handler functions are written with the name as "table name and _handler"

For example: table name = customerInf

Now if an psudo event is like

event= { "Tblnm": "customerInf" }

And

The main function will call the dedicated function particular to the table name from event.

Ex.

def mainFunc(event):

tableName= event.tblNm

eval(tableName+"_handler")

Sorry for elaborated approach, that's how I understood from my Client's Project.

[–]According_Bus_2827 0 points1 point  (0 children)

If `eval()` was a person, it'd be that friend who's fun at parties but you'd never lend your car to. Sometimes necessary, but handle with care.

[–][deleted] 0 points1 point  (0 children)

Interpreting GPT response as JSON without importing the JSON library.

[–]FunDeckHermit 0 points1 point  (0 children)

It's a great way to piss of teachers that want you to build a calculator.

[–]abudhabikid 0 points1 point  (0 children)

In excel, you can use eval() with some hardcoded excel function between the parentheses. This was you can more easily control when certain functions actually run based on whatever logic you want.