simpleeval 1.0.4__tar.gz → 1.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simpleeval-1.0.4 → simpleeval-1.0.5}/PKG-INFO +50 -3
- {simpleeval-1.0.4 → simpleeval-1.0.5}/README.rst +49 -2
- {simpleeval-1.0.4 → simpleeval-1.0.5}/pyproject.toml +1 -1
- {simpleeval-1.0.4 → simpleeval-1.0.5}/simpleeval.py +108 -12
- {simpleeval-1.0.4 → simpleeval-1.0.5}/test_simpleeval.py +545 -19
- {simpleeval-1.0.4 → simpleeval-1.0.5}/.gitignore +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.5}/.v2/lib/python3.13/site-packages/docutils/parsers/rst/include/README.rst +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.5}/.v2/lib/python3.13/site-packages/docutils/writers/s5_html/themes/README.rst +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.5}/.v2/lib/python3.13/site-packages/pip/_vendor/README.rst +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.5}/.v314/lib/python3.14/site-packages/simpleeval-1.0.3.dist-info/licenses/LICENCE +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.5}/.v314/lib/python3.14/site-packages/simpleeval.py +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.5}/LICENCE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: simpleeval
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: A simple, safe single expression evaluator library.
|
|
5
5
|
Project-URL: Source code, https://github.com/danthedeckie/simpleeval
|
|
6
6
|
Author-email: Daniel Fairhead <danthedeckie@gmail.com>
|
|
@@ -460,6 +460,11 @@ A few builtin functions are listed in ``simpleeval.DISALLOW_FUNCTIONS``. ``type
|
|
|
460
460
|
If you need to give access to this kind of functionality to your expressions, then be very
|
|
461
461
|
careful. You'd be better wrapping the functions in your own safe wrappers.
|
|
462
462
|
|
|
463
|
+
Accessing modules as attributes is disallowed too.
|
|
464
|
+
|
|
465
|
+
Allowlist recommendation
|
|
466
|
+
------------------------
|
|
467
|
+
|
|
463
468
|
There is an additional layer of protection you can add in by passing in ``allowed_attrs``, which
|
|
464
469
|
makes all attribute access based opt-in rather than opt-out - which is a lot safer design:
|
|
465
470
|
|
|
@@ -477,8 +482,8 @@ reasonably sensible defaults with BASIC_ALLOWED_ATTRS:
|
|
|
477
482
|
|
|
478
483
|
is fine - ``strip()`` should be safe on strings.
|
|
479
484
|
|
|
480
|
-
It is recommended to add ``allowed_attrs=BASIC_ALLOWED_ATTRS`` whenever possible,
|
|
481
|
-
be the default for 2.x.
|
|
485
|
+
It is strongly recommended to add ``allowed_attrs=BASIC_ALLOWED_ATTRS`` whenever possible,
|
|
486
|
+
and it will be the default for 2.x.
|
|
482
487
|
|
|
483
488
|
You can add your own classes & limit access to attrs:
|
|
484
489
|
|
|
@@ -499,6 +504,48 @@ You can add your own classes & limit access to attrs:
|
|
|
499
504
|
|
|
500
505
|
will now allow access to `foo.bar` but not allow anything else.
|
|
501
506
|
|
|
507
|
+
Module Access
|
|
508
|
+
-------------
|
|
509
|
+
|
|
510
|
+
By default, module access is not allowed in simpleeval to prevent accidental or
|
|
511
|
+
malicious access to dangerous functions. However, if you need to expose modules,
|
|
512
|
+
(eg. `numpy` or similar) you can use ``ModuleWrapper`` to do so safely.
|
|
513
|
+
|
|
514
|
+
``ModuleWrapper`` allows explicit opt-in to module access while still enforcing
|
|
515
|
+
restrictions on dangerous methods and private attributes:
|
|
516
|
+
|
|
517
|
+
.. code-block:: pycon
|
|
518
|
+
|
|
519
|
+
>>> from simpleeval import SimpleEval, ModuleWrapper
|
|
520
|
+
>>> import os.path
|
|
521
|
+
>>> s = SimpleEval(names={'path': ModuleWrapper(os.path)})
|
|
522
|
+
>>> s.eval("path.exists('/etc/passwd')")
|
|
523
|
+
True
|
|
524
|
+
|
|
525
|
+
You can also restrict which attributes are accessible by passing an
|
|
526
|
+
``allowed_attrs`` set:
|
|
527
|
+
|
|
528
|
+
.. code-block:: pycon
|
|
529
|
+
|
|
530
|
+
>>> s = SimpleEval(names={
|
|
531
|
+
... 'path': ModuleWrapper(os.path, allowed_attrs={'exists', 'join'})
|
|
532
|
+
... })
|
|
533
|
+
>>> s.eval("path.exists('/etc/passwd')")
|
|
534
|
+
True
|
|
535
|
+
>>> s.eval("path.dirname('/etc/passwd')") # Not in allowed_attrs
|
|
536
|
+
simpleeval.FeatureNotAvailable: Access to 'dirname' is not allowed...
|
|
537
|
+
|
|
538
|
+
Private attributes (starting with ``_``) and methods in ``DISALLOW_METHODS``
|
|
539
|
+
are always blocked, even if not using an allowlist:
|
|
540
|
+
|
|
541
|
+
.. code-block:: pycon
|
|
542
|
+
|
|
543
|
+
>>> s = SimpleEval(names={'path': ModuleWrapper(os.path)})
|
|
544
|
+
>>> s.eval("path.__file__")
|
|
545
|
+
simpleeval.FeatureNotAvailable: Access to private attribute '__file__'...
|
|
546
|
+
|
|
547
|
+
If you really really need that - you can make your own wrappers and overrides.
|
|
548
|
+
But I advise against it.
|
|
502
549
|
|
|
503
550
|
Other...
|
|
504
551
|
--------
|
|
@@ -443,6 +443,11 @@ A few builtin functions are listed in ``simpleeval.DISALLOW_FUNCTIONS``. ``type
|
|
|
443
443
|
If you need to give access to this kind of functionality to your expressions, then be very
|
|
444
444
|
careful. You'd be better wrapping the functions in your own safe wrappers.
|
|
445
445
|
|
|
446
|
+
Accessing modules as attributes is disallowed too.
|
|
447
|
+
|
|
448
|
+
Allowlist recommendation
|
|
449
|
+
------------------------
|
|
450
|
+
|
|
446
451
|
There is an additional layer of protection you can add in by passing in ``allowed_attrs``, which
|
|
447
452
|
makes all attribute access based opt-in rather than opt-out - which is a lot safer design:
|
|
448
453
|
|
|
@@ -460,8 +465,8 @@ reasonably sensible defaults with BASIC_ALLOWED_ATTRS:
|
|
|
460
465
|
|
|
461
466
|
is fine - ``strip()`` should be safe on strings.
|
|
462
467
|
|
|
463
|
-
It is recommended to add ``allowed_attrs=BASIC_ALLOWED_ATTRS`` whenever possible,
|
|
464
|
-
be the default for 2.x.
|
|
468
|
+
It is strongly recommended to add ``allowed_attrs=BASIC_ALLOWED_ATTRS`` whenever possible,
|
|
469
|
+
and it will be the default for 2.x.
|
|
465
470
|
|
|
466
471
|
You can add your own classes & limit access to attrs:
|
|
467
472
|
|
|
@@ -482,6 +487,48 @@ You can add your own classes & limit access to attrs:
|
|
|
482
487
|
|
|
483
488
|
will now allow access to `foo.bar` but not allow anything else.
|
|
484
489
|
|
|
490
|
+
Module Access
|
|
491
|
+
-------------
|
|
492
|
+
|
|
493
|
+
By default, module access is not allowed in simpleeval to prevent accidental or
|
|
494
|
+
malicious access to dangerous functions. However, if you need to expose modules,
|
|
495
|
+
(eg. `numpy` or similar) you can use ``ModuleWrapper`` to do so safely.
|
|
496
|
+
|
|
497
|
+
``ModuleWrapper`` allows explicit opt-in to module access while still enforcing
|
|
498
|
+
restrictions on dangerous methods and private attributes:
|
|
499
|
+
|
|
500
|
+
.. code-block:: pycon
|
|
501
|
+
|
|
502
|
+
>>> from simpleeval import SimpleEval, ModuleWrapper
|
|
503
|
+
>>> import os.path
|
|
504
|
+
>>> s = SimpleEval(names={'path': ModuleWrapper(os.path)})
|
|
505
|
+
>>> s.eval("path.exists('/etc/passwd')")
|
|
506
|
+
True
|
|
507
|
+
|
|
508
|
+
You can also restrict which attributes are accessible by passing an
|
|
509
|
+
``allowed_attrs`` set:
|
|
510
|
+
|
|
511
|
+
.. code-block:: pycon
|
|
512
|
+
|
|
513
|
+
>>> s = SimpleEval(names={
|
|
514
|
+
... 'path': ModuleWrapper(os.path, allowed_attrs={'exists', 'join'})
|
|
515
|
+
... })
|
|
516
|
+
>>> s.eval("path.exists('/etc/passwd')")
|
|
517
|
+
True
|
|
518
|
+
>>> s.eval("path.dirname('/etc/passwd')") # Not in allowed_attrs
|
|
519
|
+
simpleeval.FeatureNotAvailable: Access to 'dirname' is not allowed...
|
|
520
|
+
|
|
521
|
+
Private attributes (starting with ``_``) and methods in ``DISALLOW_METHODS``
|
|
522
|
+
are always blocked, even if not using an allowlist:
|
|
523
|
+
|
|
524
|
+
.. code-block:: pycon
|
|
525
|
+
|
|
526
|
+
>>> s = SimpleEval(names={'path': ModuleWrapper(os.path)})
|
|
527
|
+
>>> s.eval("path.__file__")
|
|
528
|
+
simpleeval.FeatureNotAvailable: Access to private attribute '__file__'...
|
|
529
|
+
|
|
530
|
+
If you really really need that - you can make your own wrappers and overrides.
|
|
531
|
+
But I advise against it.
|
|
485
532
|
|
|
486
533
|
Other...
|
|
487
534
|
--------
|
|
@@ -63,6 +63,7 @@ Contributors:
|
|
|
63
63
|
- cedk (Cédric Krier) <ced@b2ck.com> Allow running tests with Werror
|
|
64
64
|
- decorator-factory <decorator-factory@protonmail.com> More security fixes
|
|
65
65
|
- lkruitwagen (Lucas Kruitwagen) Adding support for dict comprehensions
|
|
66
|
+
- ByamB4 (Byambadalai) Reported breakout via module & disallowed functions as object attrs
|
|
66
67
|
|
|
67
68
|
-------------------------------------
|
|
68
69
|
Basic Usage:
|
|
@@ -104,10 +105,12 @@ well:
|
|
|
104
105
|
|
|
105
106
|
import ast
|
|
106
107
|
import operator as op
|
|
108
|
+
import os
|
|
107
109
|
import sys
|
|
110
|
+
import types
|
|
108
111
|
import warnings
|
|
109
112
|
from random import random
|
|
110
|
-
from typing import Type, Dict, Set, Union
|
|
113
|
+
from typing import Type, Dict, Set, Union, Hashable
|
|
111
114
|
|
|
112
115
|
########################################
|
|
113
116
|
# Module wide 'globals'
|
|
@@ -136,7 +139,21 @@ DISALLOW_METHODS = [
|
|
|
136
139
|
# their functionality is required, then please wrap them up in a safe container. And think
|
|
137
140
|
# very hard about it first. And don't say I didn't warn you.
|
|
138
141
|
# builtins is a dict in python >3.6 but a module before
|
|
139
|
-
DISALLOW_FUNCTIONS = {
|
|
142
|
+
DISALLOW_FUNCTIONS = {
|
|
143
|
+
type,
|
|
144
|
+
isinstance,
|
|
145
|
+
eval,
|
|
146
|
+
getattr,
|
|
147
|
+
setattr,
|
|
148
|
+
repr,
|
|
149
|
+
compile,
|
|
150
|
+
open,
|
|
151
|
+
exec,
|
|
152
|
+
globals,
|
|
153
|
+
locals,
|
|
154
|
+
os.popen,
|
|
155
|
+
os.system,
|
|
156
|
+
}
|
|
140
157
|
if hasattr(__builtins__, "help") or (
|
|
141
158
|
hasattr(__builtins__, "__contains__") and "help" in __builtins__ # type: ignore
|
|
142
159
|
):
|
|
@@ -369,6 +386,54 @@ class MultipleExpressions(UserWarning):
|
|
|
369
386
|
pass
|
|
370
387
|
|
|
371
388
|
|
|
389
|
+
# Sentinal used during attr access
|
|
390
|
+
_ATTR_NOT_FOUND = object()
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class ModuleWrapper:
|
|
394
|
+
"""Wraps a module to safely expose it in expressions.
|
|
395
|
+
|
|
396
|
+
By default, modules are not allowed in simpleeval names to prevent
|
|
397
|
+
accidental or malicious access to dangerous functions. ModuleWrapper
|
|
398
|
+
allows explicit opt-in to module access while still enforcing
|
|
399
|
+
restrictions on dangerous methods and functions.
|
|
400
|
+
|
|
401
|
+
Example:
|
|
402
|
+
>>> from simpleeval import SimpleEval, ModuleWrapper
|
|
403
|
+
>>> import os.path
|
|
404
|
+
>>> s = SimpleEval(names={'path': ModuleWrapper(os.path)})
|
|
405
|
+
>>> s.eval('path.exists("/etc/passwd")') # Works
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
def __init__(self, module, allowed_attrs=None):
|
|
409
|
+
"""
|
|
410
|
+
Args:
|
|
411
|
+
module: The module to wrap
|
|
412
|
+
allowed_attrs: Optional set of allowed attribute names.
|
|
413
|
+
If None, all public attributes are allowed
|
|
414
|
+
(but still subject to DISALLOW_METHODS checks).
|
|
415
|
+
"""
|
|
416
|
+
if not isinstance(module, types.ModuleType):
|
|
417
|
+
raise TypeError(f"ModuleWrapper requires a module, got {type(module)}")
|
|
418
|
+
self._module = module
|
|
419
|
+
self._allowed_attrs = allowed_attrs
|
|
420
|
+
|
|
421
|
+
def __getattr__(self, name):
|
|
422
|
+
# Block private/magic attributes
|
|
423
|
+
if name.startswith("_"):
|
|
424
|
+
raise FeatureNotAvailable(f"Access to private attribute '{name}' is not allowed")
|
|
425
|
+
|
|
426
|
+
# Check if attribute is in disallowed methods list
|
|
427
|
+
if name in DISALLOW_METHODS:
|
|
428
|
+
raise FeatureNotAvailable(f"Method '{name}' is not allowed on modules")
|
|
429
|
+
|
|
430
|
+
# Check allowed_attrs whitelist if specified
|
|
431
|
+
if self._allowed_attrs is not None and name not in self._allowed_attrs:
|
|
432
|
+
raise FeatureNotAvailable(f"Access to '{name}' is not allowed on this wrapped module")
|
|
433
|
+
|
|
434
|
+
return getattr(self._module, name)
|
|
435
|
+
|
|
436
|
+
|
|
372
437
|
########################################
|
|
373
438
|
# Default simple functions to include:
|
|
374
439
|
|
|
@@ -546,6 +611,28 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
546
611
|
def __del__(self):
|
|
547
612
|
self.nodes = None
|
|
548
613
|
|
|
614
|
+
def _check_disallowed_items(self, item):
|
|
615
|
+
"""Check if item contains disallowed functions or modules.
|
|
616
|
+
Recursively checks containers (list, dict, tuple).
|
|
617
|
+
Raises FeatureNotAvailable if forbidden content found.
|
|
618
|
+
ModuleWrapper instances are allowed (explicit opt-in to module access).
|
|
619
|
+
"""
|
|
620
|
+
# Allow ModuleWrapper (explicit opt-in to module access)
|
|
621
|
+
if isinstance(item, ModuleWrapper):
|
|
622
|
+
return
|
|
623
|
+
|
|
624
|
+
if isinstance(item, types.ModuleType):
|
|
625
|
+
raise FeatureNotAvailable("Sorry, modules are not allowed")
|
|
626
|
+
if isinstance(item, Hashable) and item in DISALLOW_FUNCTIONS:
|
|
627
|
+
raise FeatureNotAvailable("This function is forbidden")
|
|
628
|
+
|
|
629
|
+
if isinstance(item, (list, tuple)):
|
|
630
|
+
for element in item:
|
|
631
|
+
self._check_disallowed_items(element)
|
|
632
|
+
elif isinstance(item, dict):
|
|
633
|
+
for value in item.values():
|
|
634
|
+
self._check_disallowed_items(value)
|
|
635
|
+
|
|
549
636
|
@staticmethod
|
|
550
637
|
def parse(expr):
|
|
551
638
|
"""parse an expression into a node tree"""
|
|
@@ -580,7 +667,9 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
580
667
|
"Sorry, {0} is not available in this evaluator".format(type(node).__name__)
|
|
581
668
|
)
|
|
582
669
|
|
|
583
|
-
|
|
670
|
+
result = handler(node)
|
|
671
|
+
self._check_disallowed_items(result)
|
|
672
|
+
return result
|
|
584
673
|
|
|
585
674
|
def _eval_expr(self, node):
|
|
586
675
|
return self._eval(node.value)
|
|
@@ -759,18 +848,25 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
759
848
|
f"Sorry, '.{node.attr}' access not allowed on '{type_to_check}'"
|
|
760
849
|
)
|
|
761
850
|
|
|
851
|
+
item = _ATTR_NOT_FOUND
|
|
852
|
+
|
|
762
853
|
# Maybe the base object is an actual object, not just a dict
|
|
763
854
|
try:
|
|
764
|
-
|
|
855
|
+
item = getattr(node_evaluated, node.attr)
|
|
765
856
|
except (AttributeError, TypeError):
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
857
|
+
# TODO: is this a good idea? Try and look for [x] if .x doesn't work?
|
|
858
|
+
if self.ATTR_INDEX_FALLBACK:
|
|
859
|
+
try:
|
|
860
|
+
item = node_evaluated[node.attr]
|
|
861
|
+
except (KeyError, TypeError):
|
|
862
|
+
pass
|
|
863
|
+
|
|
864
|
+
if item is not _ATTR_NOT_FOUND:
|
|
865
|
+
if isinstance(item, types.ModuleType):
|
|
866
|
+
raise FeatureNotAvailable("Sorry, modules are not allowed in attribute access")
|
|
867
|
+
if isinstance(item, Hashable) and item in DISALLOW_FUNCTIONS:
|
|
868
|
+
raise FeatureNotAvailable("This function is forbidden")
|
|
869
|
+
return item
|
|
774
870
|
|
|
775
871
|
# If it is neither, raise an exception
|
|
776
872
|
raise AttributeDoesNotExist(node.attr, self.expr)
|
|
@@ -25,6 +25,7 @@ from simpleeval import (
|
|
|
25
25
|
FeatureNotAvailable,
|
|
26
26
|
FunctionNotDefined,
|
|
27
27
|
InvalidExpression,
|
|
28
|
+
ModuleWrapper,
|
|
28
29
|
NameNotDefined,
|
|
29
30
|
OperatorNotDefined,
|
|
30
31
|
SimpleEval,
|
|
@@ -590,6 +591,527 @@ class TestTryingToBreakOut(DRYTest):
|
|
|
590
591
|
|
|
591
592
|
simpleeval.DISALLOW_PREFIXES = dis
|
|
592
593
|
|
|
594
|
+
def test_breakout_via_module_access(self):
|
|
595
|
+
import os.path
|
|
596
|
+
|
|
597
|
+
s = SimpleEval(names={"path": os.path})
|
|
598
|
+
|
|
599
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
600
|
+
s.eval("path.os.popen('id').read()")
|
|
601
|
+
|
|
602
|
+
def test_breakout_via_module_access_attr(self):
|
|
603
|
+
import os.path
|
|
604
|
+
|
|
605
|
+
class Foo:
|
|
606
|
+
p = os.path
|
|
607
|
+
|
|
608
|
+
s = SimpleEval(names={"thing": Foo()})
|
|
609
|
+
|
|
610
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
611
|
+
s.eval("thing.p.os.popen('id').read()")
|
|
612
|
+
|
|
613
|
+
def test_breakout_via_disallowed_functions_as_attrs(self):
|
|
614
|
+
class Foo:
|
|
615
|
+
p = exec
|
|
616
|
+
|
|
617
|
+
s = SimpleEval(names={"thing": Foo()})
|
|
618
|
+
|
|
619
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
620
|
+
s.eval("thing.p('exit')")
|
|
621
|
+
|
|
622
|
+
def test_breakout_forbidden_function_in_list(self):
|
|
623
|
+
"""Disallowed functions in lists should be blocked"""
|
|
624
|
+
s = SimpleEval(names={"funcs": [exec, eval]})
|
|
625
|
+
|
|
626
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
627
|
+
s.eval("funcs[0]('exit')")
|
|
628
|
+
|
|
629
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
630
|
+
s.eval("funcs[1]('1+1')")
|
|
631
|
+
|
|
632
|
+
def test_breakout_module_in_list(self):
|
|
633
|
+
"""Modules in lists should be blocked"""
|
|
634
|
+
import os.path
|
|
635
|
+
|
|
636
|
+
s = SimpleEval(names={"things": [os.path, os.system]})
|
|
637
|
+
|
|
638
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
639
|
+
s.eval("things[0].os.popen('id').read()")
|
|
640
|
+
|
|
641
|
+
def test_breakout_forbidden_function_in_dict_value(self):
|
|
642
|
+
"""Disallowed functions as dict values should be blocked"""
|
|
643
|
+
s = SimpleEval(names={"funcs": {"bad": exec, "evil": eval}})
|
|
644
|
+
|
|
645
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
646
|
+
s.eval("funcs['bad']('exit')")
|
|
647
|
+
|
|
648
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
649
|
+
s.eval("funcs['evil']('1+1')")
|
|
650
|
+
|
|
651
|
+
def test_breakout_module_in_dict_value(self):
|
|
652
|
+
"""Modules as dict values should be blocked"""
|
|
653
|
+
import os.path
|
|
654
|
+
|
|
655
|
+
s = SimpleEval(names={"things": {"p": os.path, "s": os.system}})
|
|
656
|
+
|
|
657
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
658
|
+
s.eval("things['p'].os.popen('id').read()")
|
|
659
|
+
|
|
660
|
+
def test_breakout_function_returning_forbidden_function(self):
|
|
661
|
+
"""Functions returning disallowed functions should be blocked"""
|
|
662
|
+
|
|
663
|
+
def get_evil():
|
|
664
|
+
return exec
|
|
665
|
+
|
|
666
|
+
s = SimpleEval(names={}, functions={"get_evil": get_evil})
|
|
667
|
+
|
|
668
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
669
|
+
s.eval("get_evil()('exit')")
|
|
670
|
+
|
|
671
|
+
def test_breakout_function_returning_module(self):
|
|
672
|
+
"""Functions returning modules should be blocked"""
|
|
673
|
+
import os.path
|
|
674
|
+
|
|
675
|
+
def get_module():
|
|
676
|
+
return os.path
|
|
677
|
+
|
|
678
|
+
s = SimpleEval(names={}, functions={"get_module": get_module})
|
|
679
|
+
|
|
680
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
681
|
+
s.eval("get_module().os.popen('id').read()")
|
|
682
|
+
|
|
683
|
+
def test_dunder_all_in_module(self):
|
|
684
|
+
"""__all__ should be blocked (starts with _)"""
|
|
685
|
+
import os
|
|
686
|
+
|
|
687
|
+
s = SimpleEval(names={"os": os})
|
|
688
|
+
|
|
689
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
690
|
+
s.eval("os.__all__")
|
|
691
|
+
|
|
692
|
+
def test_dunder_dict_in_module(self):
|
|
693
|
+
"""__dict__ should be blocked (starts with _)"""
|
|
694
|
+
import os
|
|
695
|
+
|
|
696
|
+
s = SimpleEval(names={"os": os})
|
|
697
|
+
|
|
698
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
699
|
+
s.eval("os.__dict__")
|
|
700
|
+
|
|
701
|
+
def test_forbidden_method_in_tuple(self):
|
|
702
|
+
"""Disallowed functions in tuples should be blocked"""
|
|
703
|
+
s = SimpleEval(names={"funcs": (exec, eval)})
|
|
704
|
+
|
|
705
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
706
|
+
s.eval("funcs[0]('exit')")
|
|
707
|
+
|
|
708
|
+
def test_module_in_tuple(self):
|
|
709
|
+
"""Modules in tuples should be blocked"""
|
|
710
|
+
import os
|
|
711
|
+
|
|
712
|
+
s = SimpleEval(names={"mods": (os.path, os.system)})
|
|
713
|
+
|
|
714
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
715
|
+
s.eval("mods[0].os.popen('id').read()")
|
|
716
|
+
|
|
717
|
+
def test_breakout_via_nested_container_forbidden_func(self):
|
|
718
|
+
"""Disallowed functions nested in containers should be blocked"""
|
|
719
|
+
s = SimpleEval(names={"data": {"nested": {"funcs": [exec]}}})
|
|
720
|
+
|
|
721
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
722
|
+
s.eval("data['nested']['funcs'][0]('exit')")
|
|
723
|
+
|
|
724
|
+
def test_breakout_via_nested_container_module(self):
|
|
725
|
+
"""Modules nested in containers should be blocked"""
|
|
726
|
+
import os
|
|
727
|
+
|
|
728
|
+
s = SimpleEval(names={"data": {"mods": {"p": os.path}}})
|
|
729
|
+
|
|
730
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
731
|
+
s.eval("data['mods']['p'].os.popen('id').read()")
|
|
732
|
+
|
|
733
|
+
def test_forbidden_methods_on_allowed_attrs(self):
|
|
734
|
+
"""Disallowed methods listed in DISALLOW_METHODS should be
|
|
735
|
+
blocked"""
|
|
736
|
+
s = SimpleEval()
|
|
737
|
+
|
|
738
|
+
# format and format_map are in DISALLOW_METHODS
|
|
739
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
740
|
+
s.eval("'test {0}'.format")
|
|
741
|
+
|
|
742
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
743
|
+
s.eval("'test'.format_map({0: 'x'})")
|
|
744
|
+
|
|
745
|
+
# __mro__ is in DISALLOW_METHODS
|
|
746
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
747
|
+
s.eval("'test'.mro")
|
|
748
|
+
|
|
749
|
+
def test_function_returning_forbidden_method(self):
|
|
750
|
+
"""Functions returning disallowed methods should be blocked"""
|
|
751
|
+
|
|
752
|
+
def get_exec_module():
|
|
753
|
+
import os
|
|
754
|
+
|
|
755
|
+
return os
|
|
756
|
+
|
|
757
|
+
s = SimpleEval(names={}, functions={"get_os": get_exec_module})
|
|
758
|
+
|
|
759
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
760
|
+
s.eval("get_os().__name__")
|
|
761
|
+
|
|
762
|
+
def test_compound_module_submodule_access(self):
|
|
763
|
+
"""Accessing submodules of a passed module should be blocked"""
|
|
764
|
+
import os.path
|
|
765
|
+
|
|
766
|
+
s = SimpleEval(names={"path": os.path})
|
|
767
|
+
|
|
768
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
769
|
+
s.eval("path.os")
|
|
770
|
+
|
|
771
|
+
def test_forbidden_func_via_class_method(self):
|
|
772
|
+
"""Accessing forbidden functions via class methods should be
|
|
773
|
+
blocked"""
|
|
774
|
+
|
|
775
|
+
class Container:
|
|
776
|
+
@staticmethod
|
|
777
|
+
def get_exec():
|
|
778
|
+
return exec
|
|
779
|
+
|
|
780
|
+
s = SimpleEval(names={"c": Container()})
|
|
781
|
+
|
|
782
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
783
|
+
s.eval("c.get_exec()('exit')")
|
|
784
|
+
|
|
785
|
+
def test_module_via_class_method(self):
|
|
786
|
+
"""Accessing modules via class methods should be blocked"""
|
|
787
|
+
import os
|
|
788
|
+
|
|
789
|
+
class Container:
|
|
790
|
+
@staticmethod
|
|
791
|
+
def get_os():
|
|
792
|
+
return os
|
|
793
|
+
|
|
794
|
+
s = SimpleEval(names={"c": Container()})
|
|
795
|
+
|
|
796
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
797
|
+
s.eval("c.get_os().popen('id').read()")
|
|
798
|
+
|
|
799
|
+
def test_forbidden_func_via_property(self):
|
|
800
|
+
"""Accessing forbidden functions via properties should be
|
|
801
|
+
blocked"""
|
|
802
|
+
|
|
803
|
+
class Container:
|
|
804
|
+
@property
|
|
805
|
+
def evil(self):
|
|
806
|
+
return exec
|
|
807
|
+
|
|
808
|
+
s = SimpleEval(names={"c": Container()})
|
|
809
|
+
|
|
810
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
811
|
+
s.eval("c.evil('exit')")
|
|
812
|
+
|
|
813
|
+
def test_module_via_property(self):
|
|
814
|
+
"""Accessing modules via properties should be blocked"""
|
|
815
|
+
import os
|
|
816
|
+
|
|
817
|
+
class Container:
|
|
818
|
+
@property
|
|
819
|
+
def mod(self):
|
|
820
|
+
return os
|
|
821
|
+
|
|
822
|
+
s = SimpleEval(names={"c": Container()})
|
|
823
|
+
|
|
824
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
825
|
+
s.eval("c.mod.popen('id').read()")
|
|
826
|
+
|
|
827
|
+
def test_forbidden_function_direct_from_names(self):
|
|
828
|
+
"""Forbidden functions passed directly in names should
|
|
829
|
+
be blocked when accessed"""
|
|
830
|
+
s = SimpleEval(names={"evil": exec})
|
|
831
|
+
|
|
832
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
833
|
+
s.eval("evil")
|
|
834
|
+
|
|
835
|
+
def test_module_direct_from_names(self):
|
|
836
|
+
"""Modules passed directly in names should be blocked
|
|
837
|
+
when accessed"""
|
|
838
|
+
import os
|
|
839
|
+
|
|
840
|
+
s = SimpleEval(names={"m": os})
|
|
841
|
+
|
|
842
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
843
|
+
s.eval("m")
|
|
844
|
+
|
|
845
|
+
def test_forbidden_function_via_callable_name_handler(self):
|
|
846
|
+
"""Forbidden functions from callable name handlers should
|
|
847
|
+
be blocked"""
|
|
848
|
+
|
|
849
|
+
def name_handler(node):
|
|
850
|
+
if node.id == "evil":
|
|
851
|
+
return exec
|
|
852
|
+
raise simpleeval.NameNotDefined(node.id, "")
|
|
853
|
+
|
|
854
|
+
s = SimpleEval(names=name_handler)
|
|
855
|
+
|
|
856
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
857
|
+
s.eval("evil")
|
|
858
|
+
|
|
859
|
+
def test_module_via_callable_name_handler(self):
|
|
860
|
+
"""Modules from callable name handlers should be blocked"""
|
|
861
|
+
import os
|
|
862
|
+
|
|
863
|
+
def name_handler(node):
|
|
864
|
+
if node.id == "m":
|
|
865
|
+
return os
|
|
866
|
+
raise simpleeval.NameNotDefined(node.id, "")
|
|
867
|
+
|
|
868
|
+
s = SimpleEval(names=name_handler)
|
|
869
|
+
|
|
870
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
871
|
+
s.eval("m")
|
|
872
|
+
|
|
873
|
+
def test_forbidden_function_passed_to_custom_function(self):
|
|
874
|
+
"""Passing forbidden functions to custom functions should be
|
|
875
|
+
blocked - they can be executed by the custom function"""
|
|
876
|
+
|
|
877
|
+
def evil_caller(func):
|
|
878
|
+
return func("print('pwned')")
|
|
879
|
+
|
|
880
|
+
s = SimpleEval(names={"evil": exec}, functions={"evil_caller": evil_caller})
|
|
881
|
+
|
|
882
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
883
|
+
s.eval("evil_caller(evil)")
|
|
884
|
+
|
|
885
|
+
def test_module_passed_to_custom_function(self):
|
|
886
|
+
"""Passing modules to custom functions should be blocked - they
|
|
887
|
+
can be used by the custom function"""
|
|
888
|
+
import os
|
|
889
|
+
|
|
890
|
+
def os_caller(mod):
|
|
891
|
+
return mod.system("id")
|
|
892
|
+
|
|
893
|
+
s = SimpleEval(names={"m": os}, functions={"os_caller": os_caller})
|
|
894
|
+
|
|
895
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
896
|
+
s.eval("os_caller(m)")
|
|
897
|
+
|
|
898
|
+
def test_forbidden_function_in_list_passed_to_custom_function(self):
|
|
899
|
+
"""Forbidden functions in containers passed to custom functions
|
|
900
|
+
should be blocked"""
|
|
901
|
+
|
|
902
|
+
def extract_and_call(items):
|
|
903
|
+
return items[0]("print('pwned')")
|
|
904
|
+
|
|
905
|
+
s = SimpleEval(names={"funcs": [exec, eval]}, functions={"extract": extract_and_call})
|
|
906
|
+
|
|
907
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
908
|
+
s.eval("extract(funcs)")
|
|
909
|
+
|
|
910
|
+
def test_module_in_list_passed_to_custom_function(self):
|
|
911
|
+
"""Modules in containers passed to custom functions should be
|
|
912
|
+
blocked"""
|
|
913
|
+
import os
|
|
914
|
+
|
|
915
|
+
def extract_and_use(items):
|
|
916
|
+
return items[0].system("id")
|
|
917
|
+
|
|
918
|
+
s = SimpleEval(names={"mods": [os.path, os]}, functions={"extract": extract_and_use})
|
|
919
|
+
|
|
920
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
921
|
+
s.eval("extract(mods)")
|
|
922
|
+
|
|
923
|
+
def test_forbidden_function_in_dict_passed_to_custom_function(self):
|
|
924
|
+
"""Forbidden functions in dicts passed to custom functions should
|
|
925
|
+
be blocked"""
|
|
926
|
+
|
|
927
|
+
def extract_and_call(d):
|
|
928
|
+
return d["bad"]("print('pwned')")
|
|
929
|
+
|
|
930
|
+
s = SimpleEval(
|
|
931
|
+
names={"funcs": {"bad": exec, "good": print}}, functions={"extract": extract_and_call}
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
935
|
+
s.eval("extract(funcs)")
|
|
936
|
+
|
|
937
|
+
def test_module_in_dict_passed_to_custom_function(self):
|
|
938
|
+
"""Modules in dicts passed to custom functions should be blocked"""
|
|
939
|
+
import os
|
|
940
|
+
|
|
941
|
+
def extract_and_use(d):
|
|
942
|
+
return d["m"].system("id")
|
|
943
|
+
|
|
944
|
+
s = SimpleEval(
|
|
945
|
+
names={"mods": {"m": os, "p": os.path}}, functions={"extract": extract_and_use}
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
949
|
+
s.eval("extract(mods)")
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
class TestModuleWrapper(unittest.TestCase):
|
|
953
|
+
"""Test the ModuleWrapper class itself"""
|
|
954
|
+
|
|
955
|
+
def test_module_wrapper_requires_module(self):
|
|
956
|
+
"""ModuleWrapper should reject non-module types"""
|
|
957
|
+
with self.assertRaises(TypeError):
|
|
958
|
+
ModuleWrapper("not a module")
|
|
959
|
+
|
|
960
|
+
with self.assertRaises(TypeError):
|
|
961
|
+
ModuleWrapper(42)
|
|
962
|
+
|
|
963
|
+
with self.assertRaises(TypeError):
|
|
964
|
+
ModuleWrapper({})
|
|
965
|
+
|
|
966
|
+
def test_module_wrapper_allows_valid_module(self):
|
|
967
|
+
"""ModuleWrapper should accept valid modules"""
|
|
968
|
+
import os.path
|
|
969
|
+
|
|
970
|
+
wrapper = ModuleWrapper(os.path)
|
|
971
|
+
self.assertIsNotNone(wrapper)
|
|
972
|
+
|
|
973
|
+
def test_module_wrapper_blocks_private_attrs(self):
|
|
974
|
+
"""ModuleWrapper should block access to private attributes"""
|
|
975
|
+
import os.path
|
|
976
|
+
|
|
977
|
+
wrapper = ModuleWrapper(os.path)
|
|
978
|
+
|
|
979
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
980
|
+
wrapper.__all__
|
|
981
|
+
|
|
982
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
983
|
+
wrapper._internal
|
|
984
|
+
|
|
985
|
+
def test_module_wrapper_allows_public_attrs(self):
|
|
986
|
+
"""ModuleWrapper should allow access to public attributes"""
|
|
987
|
+
import os.path
|
|
988
|
+
|
|
989
|
+
wrapper = ModuleWrapper(os.path)
|
|
990
|
+
# Should not raise
|
|
991
|
+
_ = wrapper.exists
|
|
992
|
+
|
|
993
|
+
def test_module_wrapper_blocks_disallowed_methods(self):
|
|
994
|
+
"""ModuleWrapper should block access to methods in DISALLOW_METHODS"""
|
|
995
|
+
import os
|
|
996
|
+
|
|
997
|
+
wrapper = ModuleWrapper(os)
|
|
998
|
+
|
|
999
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1000
|
+
wrapper.mro
|
|
1001
|
+
|
|
1002
|
+
def test_module_wrapper_with_allowed_attrs_allows_whitelisted(self):
|
|
1003
|
+
"""ModuleWrapper with allowed_attrs should allow whitelisted
|
|
1004
|
+
attributes"""
|
|
1005
|
+
import os.path
|
|
1006
|
+
|
|
1007
|
+
wrapper = ModuleWrapper(os.path, allowed_attrs={"exists", "join"})
|
|
1008
|
+
|
|
1009
|
+
# Should not raise
|
|
1010
|
+
_ = wrapper.exists
|
|
1011
|
+
_ = wrapper.join
|
|
1012
|
+
|
|
1013
|
+
def test_module_wrapper_with_allowed_attrs_blocks_non_whitelisted(self):
|
|
1014
|
+
"""ModuleWrapper with allowed_attrs should block non-whitelisted
|
|
1015
|
+
attributes"""
|
|
1016
|
+
import os.path
|
|
1017
|
+
|
|
1018
|
+
wrapper = ModuleWrapper(os.path, allowed_attrs={"exists"})
|
|
1019
|
+
|
|
1020
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1021
|
+
wrapper.join
|
|
1022
|
+
|
|
1023
|
+
def test_module_wrapper_getattr_returns_actual_attribute(self):
|
|
1024
|
+
"""ModuleWrapper.__getattr__ should return the actual module
|
|
1025
|
+
attribute"""
|
|
1026
|
+
import os.path
|
|
1027
|
+
|
|
1028
|
+
wrapper = ModuleWrapper(os.path)
|
|
1029
|
+
result = wrapper.exists
|
|
1030
|
+
|
|
1031
|
+
# Should be the actual function
|
|
1032
|
+
self.assertEqual(result, os.path.exists)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
class TestModuleWrapperAccess(DRYTest):
|
|
1036
|
+
"""Test ModuleWrapper integration with SimpleEval"""
|
|
1037
|
+
|
|
1038
|
+
def test_unwrapped_module_blocked(self):
|
|
1039
|
+
"""Unwrapped modules in names should be blocked"""
|
|
1040
|
+
import os.path
|
|
1041
|
+
|
|
1042
|
+
s = SimpleEval(names={"path": os.path})
|
|
1043
|
+
|
|
1044
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1045
|
+
s.eval("path")
|
|
1046
|
+
|
|
1047
|
+
def test_wrapped_module_allowed(self):
|
|
1048
|
+
"""ModuleWrapper should allow module access in eval"""
|
|
1049
|
+
import os.path
|
|
1050
|
+
|
|
1051
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path)})
|
|
1052
|
+
|
|
1053
|
+
result = s.eval("path.exists('/etc/passwd')")
|
|
1054
|
+
self.assertTrue(isinstance(result, bool))
|
|
1055
|
+
|
|
1056
|
+
def test_wrapped_module_private_attrs_blocked(self):
|
|
1057
|
+
"""ModuleWrapper should block private attrs in eval"""
|
|
1058
|
+
import os.path
|
|
1059
|
+
|
|
1060
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path)})
|
|
1061
|
+
|
|
1062
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1063
|
+
s.eval("path.__all__")
|
|
1064
|
+
|
|
1065
|
+
def test_wrapped_module_with_whitelist(self):
|
|
1066
|
+
"""ModuleWrapper with whitelist should allow whitelisted attrs"""
|
|
1067
|
+
import os.path
|
|
1068
|
+
|
|
1069
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path, allowed_attrs={"exists"})})
|
|
1070
|
+
|
|
1071
|
+
result = s.eval("path.exists('/etc/passwd')")
|
|
1072
|
+
self.assertTrue(isinstance(result, bool))
|
|
1073
|
+
|
|
1074
|
+
def test_wrapped_module_with_whitelist_blocks_others(self):
|
|
1075
|
+
"""ModuleWrapper with whitelist should block non-whitelisted
|
|
1076
|
+
attrs"""
|
|
1077
|
+
import os.path
|
|
1078
|
+
|
|
1079
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path, allowed_attrs={"exists"})})
|
|
1080
|
+
|
|
1081
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1082
|
+
s.eval("path.join('a', 'b')")
|
|
1083
|
+
|
|
1084
|
+
def test_wrapped_module_passed_to_function(self):
|
|
1085
|
+
"""ModuleWrapper can be passed to custom functions"""
|
|
1086
|
+
|
|
1087
|
+
def process_path(path_mod):
|
|
1088
|
+
return path_mod.exists("/etc/passwd")
|
|
1089
|
+
|
|
1090
|
+
import os.path
|
|
1091
|
+
|
|
1092
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path)}, functions={"process": process_path})
|
|
1093
|
+
|
|
1094
|
+
result = s.eval("process(path)")
|
|
1095
|
+
self.assertTrue(isinstance(result, bool))
|
|
1096
|
+
|
|
1097
|
+
def test_wrapped_module_in_container(self):
|
|
1098
|
+
"""ModuleWrapper can be stored in containers"""
|
|
1099
|
+
import os.path
|
|
1100
|
+
|
|
1101
|
+
s = SimpleEval(names={"items": [ModuleWrapper(os.path), 1, 2]})
|
|
1102
|
+
|
|
1103
|
+
result = s.eval("items")
|
|
1104
|
+
self.assertEqual(len(result), 3)
|
|
1105
|
+
|
|
1106
|
+
def test_wrapped_module_in_dict_container(self):
|
|
1107
|
+
"""ModuleWrapper can be stored in dicts"""
|
|
1108
|
+
import os.path
|
|
1109
|
+
|
|
1110
|
+
s = SimpleEval(names={"data": {"path": ModuleWrapper(os.path), "value": 42}})
|
|
1111
|
+
|
|
1112
|
+
result = s.eval("data['value']")
|
|
1113
|
+
self.assertEqual(result, 42)
|
|
1114
|
+
|
|
593
1115
|
|
|
594
1116
|
class TestCompoundTypes(DRYTest):
|
|
595
1117
|
"""Test the compound-types edition of the library"""
|
|
@@ -1250,33 +1772,37 @@ class TestShortCircuiting(DRYTest):
|
|
|
1250
1772
|
|
|
1251
1773
|
|
|
1252
1774
|
class TestDisallowedFunctions(DRYTest):
|
|
1253
|
-
def
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1775
|
+
def test_functions_in_disallowed_functions_list(self):
|
|
1776
|
+
# a bit of double-entry testing. probably pointless.
|
|
1777
|
+
assert simpleeval.DISALLOW_FUNCTIONS.issuperset(
|
|
1778
|
+
{
|
|
1779
|
+
type,
|
|
1780
|
+
isinstance,
|
|
1781
|
+
eval,
|
|
1782
|
+
getattr,
|
|
1783
|
+
setattr,
|
|
1784
|
+
help,
|
|
1785
|
+
repr,
|
|
1786
|
+
compile,
|
|
1787
|
+
open,
|
|
1788
|
+
exec,
|
|
1789
|
+
os.popen,
|
|
1790
|
+
os.system,
|
|
1791
|
+
}
|
|
1792
|
+
)
|
|
1258
1793
|
|
|
1259
|
-
|
|
1794
|
+
def test_functions_are_disallowed_at_init(self):
|
|
1795
|
+
for dangerous_function in simpleeval.DISALLOW_FUNCTIONS:
|
|
1260
1796
|
with self.assertRaises(FeatureNotAvailable):
|
|
1261
|
-
SimpleEval(functions={"foo":
|
|
1797
|
+
SimpleEval(functions={"foo": dangerous_function})
|
|
1262
1798
|
|
|
1263
1799
|
def test_functions_are_disallowed_in_expressions(self):
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
for f in simpleeval.DISALLOW_FUNCTIONS:
|
|
1267
|
-
assert f in DISALLOWED
|
|
1268
|
-
|
|
1269
|
-
DF = simpleeval.DEFAULT_FUNCTIONS.copy()
|
|
1270
|
-
|
|
1271
|
-
for x in DISALLOWED:
|
|
1272
|
-
simpleeval.DEFAULT_FUNCTIONS = DF.copy()
|
|
1800
|
+
for dangerous_function in simpleeval.DISALLOW_FUNCTIONS:
|
|
1273
1801
|
with self.assertRaises(FeatureNotAvailable):
|
|
1274
1802
|
s = SimpleEval()
|
|
1275
|
-
s.functions["foo"] =
|
|
1803
|
+
s.functions["foo"] = dangerous_function
|
|
1276
1804
|
s.eval("foo(42)")
|
|
1277
1805
|
|
|
1278
|
-
simpleeval.DEFAULT_FUNCTIONS = DF.copy()
|
|
1279
|
-
|
|
1280
1806
|
def test_breakout_via_generator(self):
|
|
1281
1807
|
# Thanks decorator-factory
|
|
1282
1808
|
class Foo:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simpleeval-1.0.4 → simpleeval-1.0.5}/.v2/lib/python3.13/site-packages/pip/_vendor/README.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|