Safety#
Astevel aims at being a safer and safe-ish replacement for Python’s
built-in eval() function. It is perfectly reasonable to ask “how
safe?”. Unfortunately, the answer is complicated.
For reference on the dangers of using eval() see, Eval is
really dangerous
and the comments and links therein. From this discussion it is
apparent that not only is eval() unsafe, but that it is a
difficult prospect to make any program that takes user input perfectly
safe. In particular, if a user can cause Python to crash with a
segmentation fault, safety cannot be guaranteed.
Asteval avoids all of the exploits listed in that discussion, and all
other problems that we know about that make eval() dangerous.
Asteval explicitly forbids the exploits described in the above link,
and works hard to prevent malicious code from crashing Python or
accessing the underlying operating system. That said, we cannot
guarantee that asteval is completely safe from malicious code. We
claim only that it is safer than the builtin eval(), and that
you might find it useful. We also note that several other Python
libraries that evaluate user-supplied expressions, including numexpr
and sympy use the builtin eval() as part of their
processing.
Prohibited Python statements and attributes#
Some of the things not allowed in the asteval interpreter for safety reasons include:
importing modules. Neither
importnor__import__are supported by default. If you do want to supportimportandimport from, you have to explicitly enable these.creating classes or modules.
using
string.format(). String formatting with f-strings and the%operator are supported.accessing Python’s
eval(),getattr(),hasattr(),setattr(), anddelattr().accessing object attributes that begin and end with
__, the so-calleddunderattributes. This will include (but is not limited to__globals__,__code__,__func__,__self__,__module__,__dict__,__class__,__call__, and__getattribute__. None of these can be accessed for any object.writing to an object’s
dunderattributes (new in version 1.0.10).
In addition (and following the discussion in the link above), the following attributes are blacklisted for all objects, and cannot be accessed:
func_globals,func_code,func_closure,im_class,im_func,im_self,gi_code,gi_frame,f_locals,__mro__,_mro
[Note: this list may be incomplete - there may be other disallowed
attributes]. While this approach of making a blacklist cannot be
guaranteed to be complete, it does eliminate entire classes of attacks
known to be able to seg-fault the Python interpreter or give access to
the operating system. Similarly, preventing writing to any dunder
method can greatly reduce a malicious user changing the internal state
of Python objects.
An important caveat is that by default asteval will use numpy and
import and expose numpy ufuncs from the numpy module. Several of
these can seg-fault Python without much difficulty. In addition,
several numpy objects, including the basic ndarray have methods to
write data to disk. If you safety from user input causing segmentation
fault is a primary concern, you may want to consider disabling the use
of numpy, or take extra care to specify what numpy functions can be
used.
In 2024, an independent security audit of asteval done by Andrew
Effenhauser, Ayman Hammad, and Daniel Crowley in the X-Force Security
Research division of IBM showed insecurities with string.format,
so that access to this and string.format_map method were removed.
In addition, this audit showed that the numpy submodules
linalg, fft, and polynomial expose many exploitable
objects, so these submodules were removed by default. If needed,
these modules can be added to any Interpreter either using the
user_symbols argument when creating it, or adding the needed
symbols to the symbol table after the Interpreter is created.
In 2025, William Khem Marquez demonstrated two vulnerabilities: one from leaving some AST objects exposed within the interpreter for user-defined functions (“Procedures”), and one with f-string formatting. Both of these were fixed for version 1.0.6.
In 2026, for version 1.0.9, the abiilty to raise some kinds exceptions
(including SystemExit, KeyboardInterrupt, and a few others) that
could cause the calling process to exit were either removed from the
symbol table completely or captured to raise a more benign
RuntimeError instead. In addition, for Numpy ndarrays, the ctypes
attribute and tofile and dump methods were made inaccessible.
For version 1.0.10, the ability to write to any dunder attribute
was removed.
Avoiding resource hogging#
There are other categories of safety that asteval may attempt to address, but cannot guarantee success. The most important of these is resource hogging, which might be used for a denial-of-service attack. There is no guaranteed timeout on any calculation, and so a reasonable looking calculation such as:
from asteval import Interpreter
aeval = Interpreter()
txt = """
nmax = 1e8
a = sqrt(arange(nmax)) # using numpy.sqrt() and numpy.arange()
"""
aeval.eval(txt)
can take a noticeable amount of CPU time - if it does not, increasing
that value of nmax almost certainly will, and can even crash the
Python shell.
As another illustration of the fundamental challenge, consider the
Python expression a = x**y**z. With values x=y=z=5, the run
time will be well under 0.001 seconds. Even with x=y=z=8, run
time will be under 1 sec. Changing to x=8, y=9, z=9, Python will
take several seconds (the value is \(\sim 10^{350,000,000}\)) With
x=y=z=9, executing that statement may take more than 1 hour on
some machines. The result is that it is not hard to come up with
short program that would run for hundreds of years, which probably
exceeds everyones threshold for an acceptable run-time. The point
here is that there simply is not a good way to predict runtime for any
code from the text of the code alone: run time cannot be determined
lexically.
To be clear, for the x**y**z exponentiation example, asteval will
raise a runtime error, telling you that an exponent > 10,000 is not
allowed. Several other attempts are also made to prevent long-running
operations or memory exhaustion. These checks will prevent the
following ways to hog resources:
statements longer than 50,000 bytes.
values of exponents (
pinx**p) > 10,000.string operations with strings longer than 262144 bytes
shift operations with shifts (
pinx << p) > 1000.more than 262144 open buffers
opening a file with a mode other than
'r','rb', or'ru'.
These checks happen at runtime, not by analyzing the text of the code,
but the values for these operations. Still, as with the example above
using numpy.arange, very large arrays and lists can be created
that can approach memory limits. There are countless “clever ways” to
have very long run times that cannot be readily predicted from the
text of the code.
File access#
By default, the list of supported functions does include Python’s
open() which will allow disk access to the untrusted user. By
default, Asteval limits open() to work in read-only mode, and with
the permissions of the calling program.
When numpy is supported, its load() and loadtxt() functions
will also normally be supported to read data. But it should be noted
that numpy ndarrays do have a tofile() method that will write to
disk, limited by the permissions of the calling program.
If writing to disk is a concern, numpy should be disabled. If
reading from disk must be forbidden, you will want to overwrite the
open() function from the symbol table, or re-implement this to
restrict access. to information on disk that should be kept private.
Monitoring Runtime and interrupting processes#
The exponential example also highlights the issue that there is not a
good way to check for a long-running calculation within a single
Python process. That calculation is not stuck within the Python
interpreter, but in C code (no doubt the pow() function) called by
the Python interpreter itself. That call will not return from the C
library to the Python interpreter or allow other threads to run until
that call is done. That means that from within a single process,
there is not a reliable way to tell asteval (or really, even Python)
when a calculation has taken too long: Denial of Service is hard to
detect before it happens, and even challenging to detect while it is
happening. The only reliable way to limit run time is at the level of
the operating system, with a second process watching the execution
time of the asteval process and either try to interrupt it or kill it.
For a limited range of problems, you can try to avoid asteval taking too long. For example, you may try to limit the recursion limit when executing expressions, with a code like this:
import contextlib
@contextlib.contextmanager
def limited_recursion(recursion_limit):
old_limit = sys.getrecursionlimit()
sys.setrecursionlimit(recursion_limit)
try:
yield
finally:
sys.setrecursionlimit(old_limit)
with limited_recursion(100):
Interpreter().eval(...)
In summary, while asteval attempts to be safe and is definitely safer than
using eval(), there may be ways that using asteval could lead to
increased risk of malicious use. Recommendations for how to improve this
situation would be greatly appreciated.