simpleeval 1.0.4__tar.gz → 1.0.6__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.6}/PKG-INFO +50 -3
- {simpleeval-1.0.4 → simpleeval-1.0.6}/README.rst +49 -2
- {simpleeval-1.0.4 → simpleeval-1.0.6}/pyproject.toml +1 -1
- {simpleeval-1.0.4 → simpleeval-1.0.6}/simpleeval.py +118 -11
- {simpleeval-1.0.4 → simpleeval-1.0.6}/test_simpleeval.py +552 -19
- {simpleeval-1.0.4 → simpleeval-1.0.6}/.gitignore +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.6}/.v2/lib/python3.13/site-packages/docutils/parsers/rst/include/README.rst +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.6}/.v2/lib/python3.13/site-packages/docutils/writers/s5_html/themes/README.rst +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.6}/.v2/lib/python3.13/site-packages/pip/_vendor/README.rst +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.6}/.v314/lib/python3.14/site-packages/simpleeval-1.0.3.dist-info/licenses/LICENCE +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.6}/.v314/lib/python3.14/site-packages/simpleeval.py +0 -0
- {simpleeval-1.0.4 → simpleeval-1.0.6}/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.6
|
|
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,7 +105,9 @@ 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
113
|
from typing import Type, Dict, Set, Union
|
|
@@ -129,6 +132,17 @@ DISALLOW_METHODS = [
|
|
|
129
132
|
"exec",
|
|
130
133
|
]
|
|
131
134
|
|
|
135
|
+
########################################
|
|
136
|
+
# Tiny helpers:
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def is_hashable(value):
|
|
140
|
+
try:
|
|
141
|
+
return hash(value)
|
|
142
|
+
except TypeError:
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
|
|
132
146
|
# Disallow functions:
|
|
133
147
|
# This, strictly speaking, is not necessary. These /should/ never be accessible anyway,
|
|
134
148
|
# if DISALLOW_PREFIXES and DISALLOW_METHODS are all right. This is here to try and help
|
|
@@ -136,7 +150,21 @@ DISALLOW_METHODS = [
|
|
|
136
150
|
# their functionality is required, then please wrap them up in a safe container. And think
|
|
137
151
|
# very hard about it first. And don't say I didn't warn you.
|
|
138
152
|
# builtins is a dict in python >3.6 but a module before
|
|
139
|
-
DISALLOW_FUNCTIONS = {
|
|
153
|
+
DISALLOW_FUNCTIONS = {
|
|
154
|
+
type,
|
|
155
|
+
isinstance,
|
|
156
|
+
eval,
|
|
157
|
+
getattr,
|
|
158
|
+
setattr,
|
|
159
|
+
repr,
|
|
160
|
+
compile,
|
|
161
|
+
open,
|
|
162
|
+
exec,
|
|
163
|
+
globals,
|
|
164
|
+
locals,
|
|
165
|
+
os.popen,
|
|
166
|
+
os.system,
|
|
167
|
+
}
|
|
140
168
|
if hasattr(__builtins__, "help") or (
|
|
141
169
|
hasattr(__builtins__, "__contains__") and "help" in __builtins__ # type: ignore
|
|
142
170
|
):
|
|
@@ -369,6 +397,54 @@ class MultipleExpressions(UserWarning):
|
|
|
369
397
|
pass
|
|
370
398
|
|
|
371
399
|
|
|
400
|
+
# Sentinal used during attr access
|
|
401
|
+
_ATTR_NOT_FOUND = object()
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class ModuleWrapper:
|
|
405
|
+
"""Wraps a module to safely expose it in expressions.
|
|
406
|
+
|
|
407
|
+
By default, modules are not allowed in simpleeval names to prevent
|
|
408
|
+
accidental or malicious access to dangerous functions. ModuleWrapper
|
|
409
|
+
allows explicit opt-in to module access while still enforcing
|
|
410
|
+
restrictions on dangerous methods and functions.
|
|
411
|
+
|
|
412
|
+
Example:
|
|
413
|
+
>>> from simpleeval import SimpleEval, ModuleWrapper
|
|
414
|
+
>>> import os.path
|
|
415
|
+
>>> s = SimpleEval(names={'path': ModuleWrapper(os.path)})
|
|
416
|
+
>>> s.eval('path.exists("/etc/passwd")') # Works
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
def __init__(self, module, allowed_attrs=None):
|
|
420
|
+
"""
|
|
421
|
+
Args:
|
|
422
|
+
module: The module to wrap
|
|
423
|
+
allowed_attrs: Optional set of allowed attribute names.
|
|
424
|
+
If None, all public attributes are allowed
|
|
425
|
+
(but still subject to DISALLOW_METHODS checks).
|
|
426
|
+
"""
|
|
427
|
+
if not isinstance(module, types.ModuleType):
|
|
428
|
+
raise TypeError(f"ModuleWrapper requires a module, got {type(module)}")
|
|
429
|
+
self._module = module
|
|
430
|
+
self._allowed_attrs = allowed_attrs
|
|
431
|
+
|
|
432
|
+
def __getattr__(self, name):
|
|
433
|
+
# Block private/magic attributes
|
|
434
|
+
if name.startswith("_"):
|
|
435
|
+
raise FeatureNotAvailable(f"Access to private attribute '{name}' is not allowed")
|
|
436
|
+
|
|
437
|
+
# Check if attribute is in disallowed methods list
|
|
438
|
+
if name in DISALLOW_METHODS:
|
|
439
|
+
raise FeatureNotAvailable(f"Method '{name}' is not allowed on modules")
|
|
440
|
+
|
|
441
|
+
# Check allowed_attrs whitelist if specified
|
|
442
|
+
if self._allowed_attrs is not None and name not in self._allowed_attrs:
|
|
443
|
+
raise FeatureNotAvailable(f"Access to '{name}' is not allowed on this wrapped module")
|
|
444
|
+
|
|
445
|
+
return getattr(self._module, name)
|
|
446
|
+
|
|
447
|
+
|
|
372
448
|
########################################
|
|
373
449
|
# Default simple functions to include:
|
|
374
450
|
|
|
@@ -546,6 +622,28 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
546
622
|
def __del__(self):
|
|
547
623
|
self.nodes = None
|
|
548
624
|
|
|
625
|
+
def _check_disallowed_items(self, item):
|
|
626
|
+
"""Check if item contains disallowed functions or modules.
|
|
627
|
+
Recursively checks containers (list, dict, tuple).
|
|
628
|
+
Raises FeatureNotAvailable if forbidden content found.
|
|
629
|
+
ModuleWrapper instances are allowed (explicit opt-in to module access).
|
|
630
|
+
"""
|
|
631
|
+
# Allow ModuleWrapper (explicit opt-in to module access)
|
|
632
|
+
if isinstance(item, ModuleWrapper):
|
|
633
|
+
return
|
|
634
|
+
|
|
635
|
+
if isinstance(item, types.ModuleType):
|
|
636
|
+
raise FeatureNotAvailable("Sorry, modules are not allowed")
|
|
637
|
+
if is_hashable(item) and item in DISALLOW_FUNCTIONS:
|
|
638
|
+
raise FeatureNotAvailable("This function is forbidden")
|
|
639
|
+
|
|
640
|
+
if isinstance(item, (list, tuple)):
|
|
641
|
+
for element in item:
|
|
642
|
+
self._check_disallowed_items(element)
|
|
643
|
+
elif isinstance(item, dict):
|
|
644
|
+
for value in item.values():
|
|
645
|
+
self._check_disallowed_items(value)
|
|
646
|
+
|
|
549
647
|
@staticmethod
|
|
550
648
|
def parse(expr):
|
|
551
649
|
"""parse an expression into a node tree"""
|
|
@@ -580,7 +678,9 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
580
678
|
"Sorry, {0} is not available in this evaluator".format(type(node).__name__)
|
|
581
679
|
)
|
|
582
680
|
|
|
583
|
-
|
|
681
|
+
result = handler(node)
|
|
682
|
+
self._check_disallowed_items(result)
|
|
683
|
+
return result
|
|
584
684
|
|
|
585
685
|
def _eval_expr(self, node):
|
|
586
686
|
return self._eval(node.value)
|
|
@@ -759,18 +859,25 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
759
859
|
f"Sorry, '.{node.attr}' access not allowed on '{type_to_check}'"
|
|
760
860
|
)
|
|
761
861
|
|
|
862
|
+
item = _ATTR_NOT_FOUND
|
|
863
|
+
|
|
762
864
|
# Maybe the base object is an actual object, not just a dict
|
|
763
865
|
try:
|
|
764
|
-
|
|
866
|
+
item = getattr(node_evaluated, node.attr)
|
|
765
867
|
except (AttributeError, TypeError):
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
868
|
+
# TODO: is this a good idea? Try and look for [x] if .x doesn't work?
|
|
869
|
+
if self.ATTR_INDEX_FALLBACK:
|
|
870
|
+
try:
|
|
871
|
+
item = node_evaluated[node.attr]
|
|
872
|
+
except (KeyError, TypeError):
|
|
873
|
+
pass
|
|
874
|
+
|
|
875
|
+
if item is not _ATTR_NOT_FOUND:
|
|
876
|
+
if isinstance(item, types.ModuleType):
|
|
877
|
+
raise FeatureNotAvailable("Sorry, modules are not allowed in attribute access")
|
|
878
|
+
if is_hashable(item) and item in DISALLOW_FUNCTIONS:
|
|
879
|
+
raise FeatureNotAvailable("This function is forbidden")
|
|
880
|
+
return item
|
|
774
881
|
|
|
775
882
|
# If it is neither, raise an exception
|
|
776
883
|
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,
|
|
@@ -348,6 +349,15 @@ class TestFunctions(DRYTest):
|
|
|
348
349
|
self.t("foo(mult=2, to_return=4)", 8)
|
|
349
350
|
self.t("foo(2, 10)", 20)
|
|
350
351
|
|
|
352
|
+
def test_function_with_list_args(self):
|
|
353
|
+
# Regression test, makes sure we can pass lists (non-hashable) items as
|
|
354
|
+
# kwargs to functions.
|
|
355
|
+
|
|
356
|
+
def func(*args, **kwargs):
|
|
357
|
+
return 42
|
|
358
|
+
|
|
359
|
+
simple_eval("test(boo=x)", functions={"test": func}, names={"x": [1, 2]})
|
|
360
|
+
|
|
351
361
|
|
|
352
362
|
class TestOperators(DRYTest):
|
|
353
363
|
"""Test adding in new operators, removing them, make sure it works."""
|
|
@@ -590,6 +600,525 @@ class TestTryingToBreakOut(DRYTest):
|
|
|
590
600
|
|
|
591
601
|
simpleeval.DISALLOW_PREFIXES = dis
|
|
592
602
|
|
|
603
|
+
def test_breakout_via_module_access(self):
|
|
604
|
+
import os.path
|
|
605
|
+
|
|
606
|
+
s = SimpleEval(names={"path": os.path})
|
|
607
|
+
|
|
608
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
609
|
+
s.eval("path.os.popen('id').read()")
|
|
610
|
+
|
|
611
|
+
def test_breakout_via_module_access_attr(self):
|
|
612
|
+
import os.path
|
|
613
|
+
|
|
614
|
+
class Foo:
|
|
615
|
+
p = os.path
|
|
616
|
+
|
|
617
|
+
s = SimpleEval(names={"thing": Foo()})
|
|
618
|
+
|
|
619
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
620
|
+
s.eval("thing.p.os.popen('id').read()")
|
|
621
|
+
|
|
622
|
+
def test_breakout_via_disallowed_functions_as_attrs(self):
|
|
623
|
+
class Foo:
|
|
624
|
+
p = exec
|
|
625
|
+
|
|
626
|
+
s = SimpleEval(names={"thing": Foo()})
|
|
627
|
+
|
|
628
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
629
|
+
s.eval("thing.p('exit')")
|
|
630
|
+
|
|
631
|
+
def test_breakout_forbidden_function_in_list(self):
|
|
632
|
+
"""Disallowed functions in lists should be blocked"""
|
|
633
|
+
s = SimpleEval(names={"funcs": [exec, eval]})
|
|
634
|
+
|
|
635
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
636
|
+
s.eval("funcs[0]('exit')")
|
|
637
|
+
|
|
638
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
639
|
+
s.eval("funcs[1]('1+1')")
|
|
640
|
+
|
|
641
|
+
def test_breakout_module_in_list(self):
|
|
642
|
+
"""Modules in lists should be blocked"""
|
|
643
|
+
import os.path
|
|
644
|
+
|
|
645
|
+
s = SimpleEval(names={"things": [os.path, os.system]})
|
|
646
|
+
|
|
647
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
648
|
+
s.eval("things[0].os.popen('id').read()")
|
|
649
|
+
|
|
650
|
+
def test_breakout_forbidden_function_in_dict_value(self):
|
|
651
|
+
"""Disallowed functions as dict values should be blocked"""
|
|
652
|
+
s = SimpleEval(names={"funcs": {"bad": exec, "evil": eval}})
|
|
653
|
+
|
|
654
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
655
|
+
s.eval("funcs['bad']('exit')")
|
|
656
|
+
|
|
657
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
658
|
+
s.eval("funcs['evil']('1+1')")
|
|
659
|
+
|
|
660
|
+
def test_breakout_module_in_dict_value(self):
|
|
661
|
+
"""Modules as dict values should be blocked"""
|
|
662
|
+
import os.path
|
|
663
|
+
|
|
664
|
+
s = SimpleEval(names={"things": {"p": os.path, "s": os.system}})
|
|
665
|
+
|
|
666
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
667
|
+
s.eval("things['p'].os.popen('id').read()")
|
|
668
|
+
|
|
669
|
+
def test_breakout_function_returning_forbidden_function(self):
|
|
670
|
+
"""Functions returning disallowed functions should be blocked"""
|
|
671
|
+
|
|
672
|
+
def get_evil():
|
|
673
|
+
return exec # pragma: no cover
|
|
674
|
+
|
|
675
|
+
s = SimpleEval(names={}, functions={"get_evil": get_evil})
|
|
676
|
+
|
|
677
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
678
|
+
s.eval("get_evil()('exit')")
|
|
679
|
+
|
|
680
|
+
def test_breakout_function_returning_module(self):
|
|
681
|
+
"""Functions returning modules should be blocked"""
|
|
682
|
+
import os.path
|
|
683
|
+
|
|
684
|
+
def get_module():
|
|
685
|
+
return os.path
|
|
686
|
+
|
|
687
|
+
s = SimpleEval(names={}, functions={"get_module": get_module})
|
|
688
|
+
|
|
689
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
690
|
+
s.eval("get_module().os.popen('id').read()")
|
|
691
|
+
|
|
692
|
+
def test_dunder_all_in_module(self):
|
|
693
|
+
"""__all__ 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.__all__")
|
|
700
|
+
|
|
701
|
+
def test_dunder_dict_in_module(self):
|
|
702
|
+
"""__dict__ should be blocked (starts with _)"""
|
|
703
|
+
import os
|
|
704
|
+
|
|
705
|
+
s = SimpleEval(names={"os": os})
|
|
706
|
+
|
|
707
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
708
|
+
s.eval("os.__dict__")
|
|
709
|
+
|
|
710
|
+
def test_forbidden_method_in_tuple(self):
|
|
711
|
+
"""Disallowed functions in tuples should be blocked"""
|
|
712
|
+
s = SimpleEval(names={"funcs": (exec, eval)})
|
|
713
|
+
|
|
714
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
715
|
+
s.eval("funcs[0]('exit')")
|
|
716
|
+
|
|
717
|
+
def test_module_in_tuple(self):
|
|
718
|
+
"""Modules in tuples should be blocked"""
|
|
719
|
+
import os
|
|
720
|
+
|
|
721
|
+
s = SimpleEval(names={"mods": (os.path, os.system)})
|
|
722
|
+
|
|
723
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
724
|
+
s.eval("mods[0].os.popen('id').read()")
|
|
725
|
+
|
|
726
|
+
def test_breakout_via_nested_container_forbidden_func(self):
|
|
727
|
+
"""Disallowed functions nested in containers should be blocked"""
|
|
728
|
+
s = SimpleEval(names={"data": {"nested": {"funcs": [exec]}}})
|
|
729
|
+
|
|
730
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
731
|
+
s.eval("data['nested']['funcs'][0]('exit')")
|
|
732
|
+
|
|
733
|
+
def test_breakout_via_nested_container_module(self):
|
|
734
|
+
"""Modules nested in containers should be blocked"""
|
|
735
|
+
import os
|
|
736
|
+
|
|
737
|
+
s = SimpleEval(names={"data": {"mods": {"p": os.path}}})
|
|
738
|
+
|
|
739
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
740
|
+
s.eval("data['mods']['p'].os.popen('id').read()")
|
|
741
|
+
|
|
742
|
+
def test_forbidden_methods_on_allowed_attrs(self):
|
|
743
|
+
"""Disallowed methods listed in DISALLOW_METHODS should be
|
|
744
|
+
blocked"""
|
|
745
|
+
s = SimpleEval()
|
|
746
|
+
|
|
747
|
+
# format and format_map are in DISALLOW_METHODS
|
|
748
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
749
|
+
s.eval("'test {0}'.format")
|
|
750
|
+
|
|
751
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
752
|
+
s.eval("'test'.format_map({0: 'x'})")
|
|
753
|
+
|
|
754
|
+
# __mro__ is in DISALLOW_METHODS
|
|
755
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
756
|
+
s.eval("'test'.mro")
|
|
757
|
+
|
|
758
|
+
def test_function_returning_forbidden_method(self):
|
|
759
|
+
"""Functions returning disallowed methods should be blocked"""
|
|
760
|
+
|
|
761
|
+
def get_exec_module():
|
|
762
|
+
import os # pragma: no cover
|
|
763
|
+
|
|
764
|
+
return os # pragma: no cover
|
|
765
|
+
|
|
766
|
+
s = SimpleEval(names={}, functions={"get_os": get_exec_module})
|
|
767
|
+
|
|
768
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
769
|
+
s.eval("get_os().__name__")
|
|
770
|
+
|
|
771
|
+
def test_compound_module_submodule_access(self):
|
|
772
|
+
"""Accessing submodules of a passed module should be blocked"""
|
|
773
|
+
import os.path
|
|
774
|
+
|
|
775
|
+
s = SimpleEval(names={"path": os.path})
|
|
776
|
+
|
|
777
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
778
|
+
s.eval("path.os")
|
|
779
|
+
|
|
780
|
+
def test_forbidden_func_via_class_method(self):
|
|
781
|
+
"""Accessing forbidden functions via class methods should be
|
|
782
|
+
blocked"""
|
|
783
|
+
|
|
784
|
+
class Container:
|
|
785
|
+
@staticmethod
|
|
786
|
+
def get_exec():
|
|
787
|
+
return exec # pragma: no cover
|
|
788
|
+
|
|
789
|
+
s = SimpleEval(names={"c": Container()})
|
|
790
|
+
|
|
791
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
792
|
+
s.eval("c.get_exec()('exit')")
|
|
793
|
+
|
|
794
|
+
def test_module_via_class_method(self):
|
|
795
|
+
"""Accessing modules via class methods should be blocked"""
|
|
796
|
+
import os
|
|
797
|
+
|
|
798
|
+
class Container:
|
|
799
|
+
@staticmethod
|
|
800
|
+
def get_os():
|
|
801
|
+
return os
|
|
802
|
+
|
|
803
|
+
s = SimpleEval(names={"c": Container()})
|
|
804
|
+
|
|
805
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
806
|
+
s.eval("c.get_os().popen('id').read()")
|
|
807
|
+
|
|
808
|
+
def test_forbidden_func_via_property(self):
|
|
809
|
+
"""Accessing forbidden functions via properties should be
|
|
810
|
+
blocked"""
|
|
811
|
+
|
|
812
|
+
class Container:
|
|
813
|
+
@property
|
|
814
|
+
def evil(self):
|
|
815
|
+
return exec
|
|
816
|
+
|
|
817
|
+
s = SimpleEval(names={"c": Container()})
|
|
818
|
+
|
|
819
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
820
|
+
s.eval("c.evil('exit')")
|
|
821
|
+
|
|
822
|
+
def test_module_via_property(self):
|
|
823
|
+
"""Accessing modules via properties should be blocked"""
|
|
824
|
+
import os
|
|
825
|
+
|
|
826
|
+
class Container:
|
|
827
|
+
@property
|
|
828
|
+
def mod(self):
|
|
829
|
+
return os
|
|
830
|
+
|
|
831
|
+
s = SimpleEval(names={"c": Container()})
|
|
832
|
+
|
|
833
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
834
|
+
s.eval("c.mod.popen('id').read()")
|
|
835
|
+
|
|
836
|
+
def test_forbidden_function_direct_from_names(self):
|
|
837
|
+
"""Forbidden functions passed directly in names should
|
|
838
|
+
be blocked when accessed"""
|
|
839
|
+
s = SimpleEval(names={"evil": exec})
|
|
840
|
+
|
|
841
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
842
|
+
s.eval("evil")
|
|
843
|
+
|
|
844
|
+
def test_module_direct_from_names(self):
|
|
845
|
+
"""Modules passed directly in names should be blocked
|
|
846
|
+
when accessed"""
|
|
847
|
+
import os
|
|
848
|
+
|
|
849
|
+
s = SimpleEval(names={"m": os})
|
|
850
|
+
|
|
851
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
852
|
+
s.eval("m")
|
|
853
|
+
|
|
854
|
+
def test_forbidden_function_via_callable_name_handler(self):
|
|
855
|
+
"""Forbidden functions from callable name handlers should
|
|
856
|
+
be blocked"""
|
|
857
|
+
|
|
858
|
+
def name_handler(node):
|
|
859
|
+
if node.id == "evil":
|
|
860
|
+
return exec
|
|
861
|
+
|
|
862
|
+
s = SimpleEval(names=name_handler)
|
|
863
|
+
|
|
864
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
865
|
+
s.eval("evil")
|
|
866
|
+
|
|
867
|
+
def test_module_via_callable_name_handler(self):
|
|
868
|
+
"""Modules from callable name handlers should be blocked"""
|
|
869
|
+
import os
|
|
870
|
+
|
|
871
|
+
def name_handler(node):
|
|
872
|
+
if node.id == "m":
|
|
873
|
+
return os
|
|
874
|
+
|
|
875
|
+
s = SimpleEval(names=name_handler)
|
|
876
|
+
|
|
877
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
878
|
+
s.eval("m")
|
|
879
|
+
|
|
880
|
+
def test_forbidden_function_passed_to_custom_function(self):
|
|
881
|
+
"""Passing forbidden functions to custom functions should be
|
|
882
|
+
blocked - they can be executed by the custom function"""
|
|
883
|
+
|
|
884
|
+
def evil_caller(func):
|
|
885
|
+
return func("print('pwned')") # pragma: no cover
|
|
886
|
+
|
|
887
|
+
s = SimpleEval(names={"evil": exec}, functions={"evil_caller": evil_caller})
|
|
888
|
+
|
|
889
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
890
|
+
s.eval("evil_caller(evil)")
|
|
891
|
+
|
|
892
|
+
def test_module_passed_to_custom_function(self):
|
|
893
|
+
"""Passing modules to custom functions should be blocked - they
|
|
894
|
+
can be used by the custom function"""
|
|
895
|
+
import os
|
|
896
|
+
|
|
897
|
+
def os_caller(mod):
|
|
898
|
+
return mod.system("id") # pragma: no cover
|
|
899
|
+
|
|
900
|
+
s = SimpleEval(names={"m": os}, functions={"os_caller": os_caller})
|
|
901
|
+
|
|
902
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
903
|
+
s.eval("os_caller(m)") # pragma: no cover
|
|
904
|
+
|
|
905
|
+
def test_forbidden_function_in_list_passed_to_custom_function(self):
|
|
906
|
+
"""Forbidden functions in containers passed to custom functions
|
|
907
|
+
should be blocked"""
|
|
908
|
+
|
|
909
|
+
def extract_and_call(items):
|
|
910
|
+
return items[0]("print('pwned')") # pragma: no cover
|
|
911
|
+
|
|
912
|
+
s = SimpleEval(names={"funcs": [exec, eval]}, functions={"extract": extract_and_call})
|
|
913
|
+
|
|
914
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
915
|
+
s.eval("extract(funcs)")
|
|
916
|
+
|
|
917
|
+
def test_module_in_list_passed_to_custom_function(self):
|
|
918
|
+
"""Modules in containers passed to custom functions should be
|
|
919
|
+
blocked"""
|
|
920
|
+
import os
|
|
921
|
+
|
|
922
|
+
def extract_and_use(items):
|
|
923
|
+
return items[0].system("id") # pragma: no cover
|
|
924
|
+
|
|
925
|
+
s = SimpleEval(names={"mods": [os.path, os]}, functions={"extract": extract_and_use})
|
|
926
|
+
|
|
927
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
928
|
+
s.eval("extract(mods)")
|
|
929
|
+
|
|
930
|
+
def test_forbidden_function_in_dict_passed_to_custom_function(self):
|
|
931
|
+
"""Forbidden functions in dicts passed to custom functions should
|
|
932
|
+
be blocked"""
|
|
933
|
+
|
|
934
|
+
def extract_and_call(d):
|
|
935
|
+
return d["bad"]("print('pwned')") # pragma: no cover
|
|
936
|
+
|
|
937
|
+
s = SimpleEval(
|
|
938
|
+
names={"funcs": {"bad": exec, "good": print}}, functions={"extract": extract_and_call}
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
942
|
+
s.eval("extract(funcs)")
|
|
943
|
+
|
|
944
|
+
def test_module_in_dict_passed_to_custom_function(self):
|
|
945
|
+
"""Modules in dicts passed to custom functions should be blocked"""
|
|
946
|
+
import os
|
|
947
|
+
|
|
948
|
+
def extract_and_use(d):
|
|
949
|
+
return d["m"].system("id") # pragma: no cover
|
|
950
|
+
|
|
951
|
+
s = SimpleEval(
|
|
952
|
+
names={"mods": {"m": os, "p": os.path}}, functions={"extract": extract_and_use}
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
956
|
+
s.eval("extract(mods)")
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
class TestModuleWrapper(unittest.TestCase):
|
|
960
|
+
"""Test the ModuleWrapper class itself"""
|
|
961
|
+
|
|
962
|
+
def test_module_wrapper_requires_module(self):
|
|
963
|
+
"""ModuleWrapper should reject non-module types"""
|
|
964
|
+
with self.assertRaises(TypeError):
|
|
965
|
+
ModuleWrapper("not a module")
|
|
966
|
+
|
|
967
|
+
with self.assertRaises(TypeError):
|
|
968
|
+
ModuleWrapper(42)
|
|
969
|
+
|
|
970
|
+
with self.assertRaises(TypeError):
|
|
971
|
+
ModuleWrapper({})
|
|
972
|
+
|
|
973
|
+
def test_module_wrapper_allows_valid_module(self):
|
|
974
|
+
"""ModuleWrapper should accept valid modules"""
|
|
975
|
+
import os.path
|
|
976
|
+
|
|
977
|
+
wrapper = ModuleWrapper(os.path)
|
|
978
|
+
self.assertIsNotNone(wrapper)
|
|
979
|
+
|
|
980
|
+
def test_module_wrapper_blocks_private_attrs(self):
|
|
981
|
+
"""ModuleWrapper should block access to private attributes"""
|
|
982
|
+
import os.path
|
|
983
|
+
|
|
984
|
+
wrapper = ModuleWrapper(os.path)
|
|
985
|
+
|
|
986
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
987
|
+
wrapper.__all__
|
|
988
|
+
|
|
989
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
990
|
+
wrapper._internal
|
|
991
|
+
|
|
992
|
+
def test_module_wrapper_allows_public_attrs(self):
|
|
993
|
+
"""ModuleWrapper should allow access to public attributes"""
|
|
994
|
+
import os.path
|
|
995
|
+
|
|
996
|
+
wrapper = ModuleWrapper(os.path)
|
|
997
|
+
# Should not raise
|
|
998
|
+
_ = wrapper.exists
|
|
999
|
+
|
|
1000
|
+
def test_module_wrapper_blocks_disallowed_methods(self):
|
|
1001
|
+
"""ModuleWrapper should block access to methods in DISALLOW_METHODS"""
|
|
1002
|
+
import os
|
|
1003
|
+
|
|
1004
|
+
wrapper = ModuleWrapper(os)
|
|
1005
|
+
|
|
1006
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1007
|
+
wrapper.mro
|
|
1008
|
+
|
|
1009
|
+
def test_module_wrapper_with_allowed_attrs_allows_whitelisted(self):
|
|
1010
|
+
"""ModuleWrapper with allowed_attrs should allow whitelisted
|
|
1011
|
+
attributes"""
|
|
1012
|
+
import os.path
|
|
1013
|
+
|
|
1014
|
+
wrapper = ModuleWrapper(os.path, allowed_attrs={"exists", "join"})
|
|
1015
|
+
|
|
1016
|
+
# Should not raise
|
|
1017
|
+
_ = wrapper.exists
|
|
1018
|
+
_ = wrapper.join
|
|
1019
|
+
|
|
1020
|
+
def test_module_wrapper_with_allowed_attrs_blocks_non_whitelisted(self):
|
|
1021
|
+
"""ModuleWrapper with allowed_attrs should block non-whitelisted
|
|
1022
|
+
attributes"""
|
|
1023
|
+
import os.path
|
|
1024
|
+
|
|
1025
|
+
wrapper = ModuleWrapper(os.path, allowed_attrs={"exists"})
|
|
1026
|
+
|
|
1027
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1028
|
+
wrapper.join
|
|
1029
|
+
|
|
1030
|
+
def test_module_wrapper_getattr_returns_actual_attribute(self):
|
|
1031
|
+
"""ModuleWrapper.__getattr__ should return the actual module
|
|
1032
|
+
attribute"""
|
|
1033
|
+
import os.path
|
|
1034
|
+
|
|
1035
|
+
wrapper = ModuleWrapper(os.path)
|
|
1036
|
+
result = wrapper.exists
|
|
1037
|
+
|
|
1038
|
+
# Should be the actual function
|
|
1039
|
+
self.assertEqual(result, os.path.exists)
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
class TestModuleWrapperAccess(DRYTest):
|
|
1043
|
+
"""Test ModuleWrapper integration with SimpleEval"""
|
|
1044
|
+
|
|
1045
|
+
def test_unwrapped_module_blocked(self):
|
|
1046
|
+
"""Unwrapped modules in names should be blocked"""
|
|
1047
|
+
import os.path
|
|
1048
|
+
|
|
1049
|
+
s = SimpleEval(names={"path": os.path})
|
|
1050
|
+
|
|
1051
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1052
|
+
s.eval("path")
|
|
1053
|
+
|
|
1054
|
+
def test_wrapped_module_allowed(self):
|
|
1055
|
+
"""ModuleWrapper should allow module access in eval"""
|
|
1056
|
+
import os.path
|
|
1057
|
+
|
|
1058
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path)})
|
|
1059
|
+
|
|
1060
|
+
result = s.eval("path.exists('/etc/passwd')")
|
|
1061
|
+
self.assertTrue(isinstance(result, bool))
|
|
1062
|
+
|
|
1063
|
+
def test_wrapped_module_private_attrs_blocked(self):
|
|
1064
|
+
"""ModuleWrapper should block private attrs in eval"""
|
|
1065
|
+
import os.path
|
|
1066
|
+
|
|
1067
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path)})
|
|
1068
|
+
|
|
1069
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1070
|
+
s.eval("path.__all__")
|
|
1071
|
+
|
|
1072
|
+
def test_wrapped_module_with_whitelist(self):
|
|
1073
|
+
"""ModuleWrapper with whitelist should allow whitelisted attrs"""
|
|
1074
|
+
import os.path
|
|
1075
|
+
|
|
1076
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path, allowed_attrs={"exists"})})
|
|
1077
|
+
|
|
1078
|
+
result = s.eval("path.exists('/etc/passwd')")
|
|
1079
|
+
self.assertTrue(isinstance(result, bool))
|
|
1080
|
+
|
|
1081
|
+
def test_wrapped_module_with_whitelist_blocks_others(self):
|
|
1082
|
+
"""ModuleWrapper with whitelist should block non-whitelisted
|
|
1083
|
+
attrs"""
|
|
1084
|
+
import os.path
|
|
1085
|
+
|
|
1086
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path, allowed_attrs={"exists"})})
|
|
1087
|
+
|
|
1088
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1089
|
+
s.eval("path.join('a', 'b')")
|
|
1090
|
+
|
|
1091
|
+
def test_wrapped_module_passed_to_function(self):
|
|
1092
|
+
"""ModuleWrapper can be passed to custom functions"""
|
|
1093
|
+
|
|
1094
|
+
def process_path(path_mod):
|
|
1095
|
+
return path_mod.exists("/etc/passwd")
|
|
1096
|
+
|
|
1097
|
+
import os.path
|
|
1098
|
+
|
|
1099
|
+
s = SimpleEval(names={"path": ModuleWrapper(os.path)}, functions={"process": process_path})
|
|
1100
|
+
|
|
1101
|
+
result = s.eval("process(path)")
|
|
1102
|
+
self.assertTrue(isinstance(result, bool))
|
|
1103
|
+
|
|
1104
|
+
def test_wrapped_module_in_container(self):
|
|
1105
|
+
"""ModuleWrapper can be stored in containers"""
|
|
1106
|
+
import os.path
|
|
1107
|
+
|
|
1108
|
+
s = SimpleEval(names={"items": [ModuleWrapper(os.path), 1, 2]})
|
|
1109
|
+
|
|
1110
|
+
result = s.eval("items")
|
|
1111
|
+
self.assertEqual(len(result), 3)
|
|
1112
|
+
|
|
1113
|
+
def test_wrapped_module_in_dict_container(self):
|
|
1114
|
+
"""ModuleWrapper can be stored in dicts"""
|
|
1115
|
+
import os.path
|
|
1116
|
+
|
|
1117
|
+
s = SimpleEval(names={"data": {"path": ModuleWrapper(os.path), "value": 42}})
|
|
1118
|
+
|
|
1119
|
+
result = s.eval("data['value']")
|
|
1120
|
+
self.assertEqual(result, 42)
|
|
1121
|
+
|
|
593
1122
|
|
|
594
1123
|
class TestCompoundTypes(DRYTest):
|
|
595
1124
|
"""Test the compound-types edition of the library"""
|
|
@@ -1250,33 +1779,37 @@ class TestShortCircuiting(DRYTest):
|
|
|
1250
1779
|
|
|
1251
1780
|
|
|
1252
1781
|
class TestDisallowedFunctions(DRYTest):
|
|
1253
|
-
def
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1782
|
+
def test_functions_in_disallowed_functions_list(self):
|
|
1783
|
+
# a bit of double-entry testing. probably pointless.
|
|
1784
|
+
assert simpleeval.DISALLOW_FUNCTIONS.issuperset(
|
|
1785
|
+
{
|
|
1786
|
+
type,
|
|
1787
|
+
isinstance,
|
|
1788
|
+
eval,
|
|
1789
|
+
getattr,
|
|
1790
|
+
setattr,
|
|
1791
|
+
help,
|
|
1792
|
+
repr,
|
|
1793
|
+
compile,
|
|
1794
|
+
open,
|
|
1795
|
+
exec,
|
|
1796
|
+
os.popen,
|
|
1797
|
+
os.system,
|
|
1798
|
+
}
|
|
1799
|
+
)
|
|
1258
1800
|
|
|
1259
|
-
|
|
1801
|
+
def test_functions_are_disallowed_at_init(self):
|
|
1802
|
+
for dangerous_function in simpleeval.DISALLOW_FUNCTIONS:
|
|
1260
1803
|
with self.assertRaises(FeatureNotAvailable):
|
|
1261
|
-
SimpleEval(functions={"foo":
|
|
1804
|
+
SimpleEval(functions={"foo": dangerous_function})
|
|
1262
1805
|
|
|
1263
1806
|
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()
|
|
1807
|
+
for dangerous_function in simpleeval.DISALLOW_FUNCTIONS:
|
|
1273
1808
|
with self.assertRaises(FeatureNotAvailable):
|
|
1274
1809
|
s = SimpleEval()
|
|
1275
|
-
s.functions["foo"] =
|
|
1810
|
+
s.functions["foo"] = dangerous_function
|
|
1276
1811
|
s.eval("foo(42)")
|
|
1277
1812
|
|
|
1278
|
-
simpleeval.DEFAULT_FUNCTIONS = DF.copy()
|
|
1279
|
-
|
|
1280
1813
|
def test_breakout_via_generator(self):
|
|
1281
1814
|
# Thanks decorator-factory
|
|
1282
1815
|
class Foo:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simpleeval-1.0.4 → simpleeval-1.0.6}/.v2/lib/python3.13/site-packages/pip/_vendor/README.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|