• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / AccessControl / 8200137766

08 Mar 2024 07:28AM UTC coverage: 81.291% (+0.2%) from 81.124%
8200137766

Pull #147

github

web-flow
Merge cd7e5680a into 38a89f0f9
Pull Request #147: Make dict views behave like their unrestricted versions

945 of 1469 branches covered (64.33%)

Branch coverage included in aggregate %.

75 of 76 new or added lines in 3 files covered. (98.68%)

11 existing lines in 1 file now uncovered.

5051 of 5907 relevant lines covered (85.51%)

5.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.01
/src/AccessControl/ZopeGuards.py
1
#############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE
11
#
12
##############################################################################
13

14

15
import collections.abc
6✔
16
import math
6✔
17
import random
6✔
18
import string
6✔
19
from functools import reduce
6✔
20

21
import RestrictedPython
6✔
22
from RestrictedPython.Eval import RestrictionCapableEval
6✔
23
from RestrictedPython.Guards import full_write_guard
6✔
24
from RestrictedPython.Guards import guarded_iter_unpack_sequence
6✔
25
from RestrictedPython.Guards import guarded_unpack_sequence
6✔
26
from RestrictedPython.Guards import safe_builtins
6✔
27
from RestrictedPython.Utilities import utility_builtins
6✔
28
from zExceptions import Unauthorized
6✔
29

30
from AccessControl.SecurityInfo import ModuleSecurityInfo
6✔
31
from AccessControl.SecurityInfo import secureModule
6✔
32
from AccessControl.SecurityManagement import getSecurityManager
6✔
33
from AccessControl.SimpleObjectPolicies import ContainerAssertions
6✔
34
from AccessControl.SimpleObjectPolicies import Containers
6✔
35
from AccessControl.SimpleObjectPolicies import allow_type
6✔
36

37

38
_marker = []  # Create a new marker object.
6✔
39

40
safe_builtins = safe_builtins.copy()
6✔
41
safe_builtins.update(utility_builtins)
6✔
42

43
# Allow access to unprotected attributes
44
string.__allow_access_to_unprotected_subobjects__ = 1
6✔
45
math.__allow_access_to_unprotected_subobjects__ = 1
6✔
46
random.__allow_access_to_unprotected_subobjects__ = 1
6✔
47

48
# Mark some unprotected module attributes as private, these should not be
49
# used in untrusted Python code such as Scripts (Python)
50
string_modsec = ModuleSecurityInfo('string')
6✔
51
for name in ('Formatter', 'Template'):
6✔
52
    string_modsec.declarePrivate(name)  # NOQA: D001
6✔
53
secureModule('string')
6✔
54

55
# AccessControl.Implementation inserts these names into this module as
56
# module globals:  aq_validate, guarded_getattr
57

58

59
def initialize(impl):
6✔
60
    # Called by AccessControl.Implementation.setImplementation()
61
    # whenever the selected implementation changes.
62
    global guarded_getattr
63
    guarded_getattr = impl.guarded_getattr
6✔
64
    safe_builtins['getattr'] = guarded_getattr
6✔
65
    _safe_globals['_getattr_'] = guarded_getattr
6✔
66

67

68
def guarded_hasattr(object, name):
6✔
69
    try:
6✔
70
        guarded_getattr(object, name)
6✔
71
    except (AttributeError, Unauthorized, TypeError):
6✔
72
        return 0
6✔
73
    return 1
6✔
74

75

76
safe_builtins['hasattr'] = guarded_hasattr
6✔
77

78

79
SliceType = type(slice(0))
6✔
80

81

82
def guarded_getitem(object, index):
6✔
83
    if isinstance(index, SliceType):
6!
84
        if index.step is not None:
×
85
            v = object[index]
×
86
        else:
87
            start = index.start
×
88
            stop = index.stop
×
89
            if start is None:
×
90
                start = 0
×
91
            if stop is None:
×
92
                v = object[start:]
×
93
            else:
94
                v = object[start:stop]
×
95
        # We don't guard slices.
96
        return v
×
97
    v = object[index]
6✔
98
    if Containers(type(object)) and Containers(type(v)):
6✔
99
        # Simple type.  Short circuit.
100
        return v
6✔
101
    if getSecurityManager().validate(object, object, None, v):
6!
102
        return v
6✔
103
    raise Unauthorized('unauthorized access to element %s' % index)
×
104

105

106
# Create functions using nested scope to store state
107
# This is less expensive then instantiating and calling instances
108
def get_dict_get(d, name):
6✔
109
    def guarded_get(key, default=None):
6✔
110
        try:
6✔
111
            return guarded_getitem(d, key)
6✔
112
        except KeyError:
6✔
113
            return default
6✔
114
    return guarded_get
6✔
115

116

117
def get_dict_pop(d, name):
6✔
118
    def guarded_pop(key, default=_marker):
6✔
119
        try:
6✔
120
            v = guarded_getitem(d, key)
6✔
121
        except KeyError:
6✔
122
            if default is not _marker:
6✔
123
                return default
6✔
124
            raise
6✔
125
        else:
126
            del d[key]
6✔
127
            return v
6✔
128
    return guarded_pop
6✔
129

130

131
def get_mapping_view(c, name):
6✔
132

133
    view_class = {
6✔
134
        'keys': SafeKeysView,
135
        'items': SafeItemsView,
136
        'values': SafeValuesView,
137
    }
138

139
    def guarded_mapping_view():
6✔
140
        return view_class[name](c)
6✔
141

142
    return guarded_mapping_view
6✔
143

144

145
def get_list_pop(lst, name):
6✔
146
    def guarded_pop(index=-1):
6✔
147
        # XXX This is not thread safe, but we don't expect
148
        # XXX thread interactions between python scripts <wink>
149
        v = guarded_getitem(lst, index)
6✔
150
        del lst[index]
6✔
151
        return v
6✔
152
    return guarded_pop
6✔
153

154

155
# See comment in SimpleObjectPolicies for an explanation of what the
156
# dicts below actually mean.
157
_dict_white_list = {
6✔
158
    'clear': 1,
159
    'copy': 1,
160
    'fromkeys': 1,
161
    'get': get_dict_get,
162
    'items': get_mapping_view,
163
    'keys': get_mapping_view,
164
    'pop': get_dict_pop,
165
    'popitem': 1,
166
    'setdefault': 1,
167
    'update': 1,
168
    'values': get_mapping_view,
169
}
170

171

172
def _check_dict_access(name, value):
6✔
173
    # Check whether value is a dict method
174
    self = getattr(value, '__self__', None)
6✔
175
    if self is None:  # item
6✔
176
        return 1
6✔
177
    # Disallow spoofing
178
    if not isinstance(self, dict):
6!
179
        return 0
×
180
    if getattr(value, '__name__', None) != name:
6!
181
        return 0
×
182
    return _dict_white_list.get(name, 0)
6✔
183

184

185
ContainerAssertions[type({})] = _check_dict_access
6✔
186

187

188
# Allow iteration over the result of `dict.{keys, values, items}`
189
d = {}
6✔
190
for attr in ("keys", "values", "items"):
6✔
191
    allow_type(type(getattr(d, attr)()))
6✔
192

193

194
_list_white_list = {
6✔
195
    'append': 1,
196
    'count': 1,
197
    'extend': 1,
198
    'index': 1,
199
    'insert': 1,
200
    'pop': get_list_pop,
201
    'remove': 1,
202
    'reverse': 1,
203
    'sort': 1,
204
}
205

206

207
def _check_list_access(name, value):
6✔
208
    # Check whether value is a dict method
209
    self = getattr(value, '__self__', None)
6✔
210
    if self is None:  # item
6✔
211
        return 1
6✔
212
    # Disallow spoofing
213
    if not isinstance(self, list):
6!
214
        return 0
×
215
    if getattr(value, '__name__', None) != name:
6!
216
        return 0
×
217
    return _list_white_list.get(name, 0)
6✔
218

219

220
ContainerAssertions[type([])] = _check_list_access
6✔
221

222

223
# This implementation of a "safe" iterator uses a global guard()
224
# function to implement the actual guard.  This check is defined as a
225
# global so that it can delay imports of some module to avoid circular
226
# dependencies while also making it possible to use a faster
227
# implementation once the imports are done (by avoiding the import
228
# machinery on subsequent calls).  Use of a method on the SafeIter
229
# class is avoided to ensure the best performance of the resulting
230
# function.
231
# The NullIter class skips the guard, and can be used to wrap an
232
# iterator that is known to be safe (as in guarded_enumerate).
233

234

235
def guarded_next(iterator, default=_marker):
6✔
236
    if default is _marker:
6✔
237
        ob = next(iterator)
6✔
238
    else:
239
        ob = next(iterator, default)
6✔
240
    if not isinstance(iterator, SafeIter):
6✔
241
        guard(ob, ob)
6✔
242
    return ob
6✔
243

244

245
safe_builtins['next'] = guarded_next
6✔
246

247

248
class SafeIter:
6✔
249
    __allow_access_to_unprotected_subobjects__ = 1
6✔
250

251
    def __init__(self, ob, container=None):
6✔
252
        self._iter = iter(ob)
6✔
253
        if container is None:
6✔
254
            container = ob
6✔
255
        self.container = container
6✔
256

257
    def __iter__(self):
6✔
258
        return self
6✔
259

260
    def __next__(self):
6✔
261
        ob = next(self._iter)
6✔
262
        guard(self.container, ob)
6✔
263
        return ob
6✔
264

265
    next = __next__
6✔
266

267

268
class _SafeMappingView:
6✔
269
    def __iter__(self):
6✔
270
        for e in super().__iter__():
6✔
271
            guard(self._mapping, e)
6✔
272
            yield e
6✔
273

274

275
class SafeKeysView(_SafeMappingView, collections.abc.KeysView):
6✔
276
    pass
6✔
277

278

279
class SafeValuesView(_SafeMappingView, collections.abc.ValuesView):
6✔
280
    pass
6✔
281

282

283
class SafeItemsView(_SafeMappingView, collections.abc.ItemsView):
6✔
284
    def __iter__(self):
6✔
285
        for k, v in super().__iter__():
6✔
286
            guard(self._mapping, k)
6✔
287
            guard(self._mapping, v)
6✔
288
            yield k, v
6✔
289

290

291
class NullIter(SafeIter):
6✔
292
    def __init__(self, ob):
6✔
293
        self._iter = ob
6✔
294

295
    def __next__(self):
6✔
296
        return next(self._iter)
6✔
297

298
    next = __next__
6✔
299

300

301
def _error(index):
6✔
NEW
302
    raise Unauthorized('unauthorized access to element')
×
303

304

305
def guarded_iter(*args):
6✔
306
    if len(args) == 1:
6!
307
        i = args[0]
6✔
308
        # Don't double-wrap
309
        if isinstance(i, SafeIter):
6✔
310
            return i
6✔
311
        if not isinstance(i, range):
6✔
312
            return SafeIter(i)
6✔
313
    # Other call styles / targets don't need to be guarded
314
    return NullIter(iter(*args))
6✔
315

316

317
safe_builtins['iter'] = guarded_iter
6✔
318

319

320
def guard(container, value, index=None):
6✔
321
    if Containers(type(container)) and Containers(type(value)):
6✔
322
        # Simple type.  Short circuit.
323
        return
6✔
324
    if getSecurityManager().validate(container, container, index, value):
6!
325
        return
6✔
UNCOV
326
    _error(index)
×
327

328
# More replacement built-ins.
329

330

331
def guarded_filter(f, seq, skip_unauthorized=0):
6✔
332
    if isinstance(seq, str):
6!
UNCOV
333
        return filter(f, seq)
×
334

335
    if f is None:
6!
336
        def f(x):
6✔
337
            return x
6✔
338

339
    v = getSecurityManager().validate
6✔
340
    result = []
6✔
341
    a = result.append
6✔
342
    for el in seq:
6✔
343
        if v(seq, seq, None, el):
6!
344
            if f(el):
6✔
345
                a(el)
6✔
UNCOV
346
        elif not skip_unauthorized:
×
UNCOV
347
            raise Unauthorized('unauthorized access to element')
×
348
    return result
6✔
349

350

351
safe_builtins['filter'] = guarded_filter
6✔
352

353

354
def guarded_reduce(f, seq, initial=_marker):
6✔
355
    if initial is _marker:
6!
UNCOV
356
        return reduce(f, guarded_iter(seq))
×
357
    else:
358
        return reduce(f, guarded_iter(seq), initial)
6✔
359

360

361
safe_builtins['reduce'] = guarded_reduce
6✔
362

363

364
def guarded_max(item, *items, **kw):
6✔
365
    if items:
6✔
366
        item = [item]
6✔
367
        item.extend(items)
6✔
368
    return max(guarded_iter(item), **kw)
6✔
369

370

371
safe_builtins['max'] = guarded_max
6✔
372

373

374
def guarded_min(item, *items, **kw):
6✔
375
    if items:
6✔
376
        item = [item]
6✔
377
        item.extend(items)
6✔
378
    return min(guarded_iter(item), **kw)
6✔
379

380

381
safe_builtins['min'] = guarded_min
6✔
382

383

384
def guarded_map(f, *seqs):
6✔
385
    safe_seqs = []
6✔
386
    for seqno in range(len(seqs)):
6✔
387
        seq = guarded_getitem(seqs, seqno)
6✔
388
        safe_seqs.append(guarded_iter(seq))
6✔
389
    return list(map(f, *safe_seqs))
6✔
390

391

392
safe_builtins['map'] = guarded_map
6✔
393

394

395
def guarded_zip(*seqs):
6✔
396
    safe_seqs = []
6✔
397
    for seqno in range(len(seqs)):
6✔
398
        seq = guarded_getitem(seqs, seqno)
6✔
399
        safe_seqs.append(guarded_iter(seq))
6✔
400
    return list(zip(*safe_seqs))
6✔
401

402

403
safe_builtins['zip'] = guarded_zip
6✔
404

405

406
import_default_level = 0
6✔
407

408

409
def guarded_import(mname, globals=None, locals=None, fromlist=None,
6✔
410
                   level=import_default_level):
411
    if fromlist is None:
6✔
412
        fromlist = ()
6✔
413
    if globals is None:
6✔
414
        globals = {}
6✔
415
    if locals is None:
6!
416
        locals = {}
6✔
417
    # Refs https://bugs.launchpad.net/zope2/+bug/659968
418
    if level != import_default_level:
6✔
419
        raise Unauthorized("Using import with a level specification isn't "
6✔
420
                           "supported by AccessControl: %s" % mname)
421

422
    mnameparts = mname.split('.')
6✔
423
    validate = getSecurityManager().validate
6✔
424
    module = load_module(None, None, mnameparts, validate, globals, locals)
6✔
425
    if module is None:
6✔
426
        raise Unauthorized("import of '%s' is unauthorized" % mname)
6✔
427
    for name in fromlist:
6✔
428
        v = getattr(module, name, None)
6✔
429
        if v is None:
6✔
430
            v = load_module(module, mname, [name], validate,
6✔
431
                            globals, locals)
432
        if not validate(module, module, name, v):
6!
UNCOV
433
            raise Unauthorized
×
434
    else:
435
        return __import__(mname, globals, locals, fromlist)
6✔
436

437

438
safe_builtins['__import__'] = guarded_import
6✔
439

440

441
class GuardedListType:
6✔
442
    def __call__(self, *args, **kwargs):
6✔
443
        return list(*args, **kwargs)
6✔
444

445
    def sorted(self, iterable, cmp=None, key=None, reverse=False):
6✔
UNCOV
446
        return list.sorted(iterable, cmp=None, key=None, reverse=False)
×
447

448

449
safe_builtins['list'] = GuardedListType()
6✔
450

451

452
class GuardedDictType:
6✔
453
    def __call__(self, *args, **kwargs):
6✔
454
        return dict(*args, **kwargs)
6✔
455

456
    def fromkeys(self, S, v=None):
6✔
457
        return dict.fromkeys(S, v)
6✔
458

459

460
safe_builtins['dict'] = GuardedDictType()
6✔
461

462

463
def guarded_enumerate(seq):
6✔
464
    return NullIter(enumerate(guarded_iter(seq)))
6✔
465

466

467
safe_builtins['enumerate'] = guarded_enumerate
6✔
468

469

470
def guarded_sum(sequence, start=0):
6✔
471
    return sum(guarded_iter(sequence), start)
6✔
472

473

474
safe_builtins['sum'] = guarded_sum
6✔
475

476

477
def load_module(module, mname, mnameparts, validate, globals, locals):
6✔
478
    while mnameparts:
6✔
479
        nextname = mnameparts.pop(0)
6✔
480
        if mname is None:
6✔
481
            mname = nextname
6✔
482
        else:
483
            mname = '{}.{}'.format(mname, nextname)
6✔
484
        # import (if not already imported) and  check for MSI
485
        nextmodule = secureModule(mname, globals, locals)
6✔
486
        if nextmodule is None:  # not allowed
6✔
487
            return
6✔
488
        if module and not validate(module, module, nextname, nextmodule):
6!
UNCOV
489
            return
×
490
        module = nextmodule
6✔
491
    return module
6✔
492

493

494
# This version of apply is used by restricted Python, which transforms
495
# extended call syntax into a call of _apply_(), after tucking the callable
496
# into the first element of args.  For example,
497
#     f(3, eggs=1, spam=False)
498
# is changed to
499
#     _apply_(f, 3, eggs=1, spam=False)
500
def guarded_apply(func, *args, **kws):
6✔
501
    return builtin_guarded_apply(func, args, kws)
6✔
502

503

504
# This version is the safe_builtins apply() replacement, so needs to match the
505
# signature of __builtin__.apply.
506
def builtin_guarded_apply(func, args=(), kws={}):
6✔
507
    # Check the args elements.  args may be an arbitrary iterable, and
508
    # iterating over it may consume it, so we also need to save away
509
    # the arguments in a new list to pass on to the real apply().
510
    i, arglist = 0, []
6✔
511
    for elt in args:
6✔
512
        guard(args, elt, i)
6✔
513
        arglist.append(elt)
6✔
514
        i += 1
6✔
515
    # Check kws similarly.  Checking the keys may not be strictly necessary,
516
    # but better safe than sorry.  A new argument dict is created just in
517
    # case kws is a hostile user-defined instance that may do horrid things
518
    # as a side-effect of calling items().
519
    argdict = {}
6✔
520
    for k, v in kws.items():
6✔
521
        guard(kws, k)
6✔
522
        guard(kws, v, k)
6✔
523
        argdict[k] = v
6✔
524
    return func(*arglist, **argdict)
6✔
525

526

527
safe_builtins['apply'] = builtin_guarded_apply
6✔
528

529

530
def guarded_any(seq):
6✔
531
    return any(guarded_iter(seq))
6✔
532

533

534
safe_builtins['any'] = guarded_any
6✔
535

536

537
def guarded_all(seq):
6✔
538
    return all(guarded_iter(seq))
6✔
539

540

541
safe_builtins['all'] = guarded_all
6✔
542

543

544
# This metaclass supplies the security declarations that allow all
545
# attributes of a class and its instances to be read and written.
546
def _metaclass(name, bases, dict):
6✔
547
    for k, v in dict.items():
6✔
548
        if k.endswith('__roles__') and k[:len('__roles__')] not in dict:
6!
UNCOV
549
            raise Unauthorized("Can't override security: %s" % k)
×
550
    ob = type(name, bases, dict)
6✔
551
    ob.__allow_access_to_unprotected_subobjects__ = 1
6✔
552
    ob._guarded_writes = 1
6✔
553
    return ob
6✔
554

555

556
valid_inplace_types = (list, set)
6✔
557

558

559
inplace_slots = {
6✔
560
    '+=': '__iadd__',
561
    '-=': '__isub__',
562
    '*=': '__imul__',
563
    '/=': (1 / 2 == 0) and '__idiv__' or '__itruediv__',
564
    '//=': '__ifloordiv__',
565
    '%=': '__imod__',
566
    '**=': '__ipow__',
567
    '<<=': '__ilshift__',
568
    '>>=': '__irshift__',
569
    '&=': '__iand__',
570
    '^=': '__ixor__',
571
    '|=': '__ior__',
572
}
573

574

575
def __iadd__(x, y):
6✔
576
    x += y
6✔
577
    return x
6✔
578

579

580
def __isub__(x, y):
6✔
581
    x -= y
6✔
582
    return x
6✔
583

584

585
def __imul__(x, y):
6✔
586
    x *= y
6✔
587
    return x
6✔
588

589

590
def __idiv__(x, y):
6✔
591
    x /= y
6✔
592
    return x
6✔
593

594

595
def __ifloordiv__(x, y):
6✔
596
    x //= y
6✔
597
    return x
6✔
598

599

600
def __imod__(x, y):
6✔
601
    x %= y
6✔
602
    return x
6✔
603

604

605
def __ipow__(x, y):
6✔
606
    x **= y
6✔
607
    return x
6✔
608

609

610
def __ilshift__(x, y):
6✔
611
    x <<= y
6✔
612
    return x
6✔
613

614

615
def __irshift__(x, y):
6✔
616
    x >>= y
6✔
617
    return x
6✔
618

619

620
def __iand__(x, y):
6✔
621
    x &= y
6✔
622
    return x
6✔
623

624

625
def __ixor__(x, y):
6✔
626
    x ^= y
6✔
627
    return x
6✔
628

629

630
def __ior__(x, y):
6✔
631
    x |= y
6✔
632
    return x
6✔
633

634

635
inplace_ops = {
6✔
636
    '+=': __iadd__,
637
    '-=': __isub__,
638
    '*=': __imul__,
639
    '/=': __idiv__,
640
    '//=': __ifloordiv__,
641
    '%=': __imod__,
642
    '**=': __ipow__,
643
    '<<=': __ilshift__,
644
    '>>=': __irshift__,
645
    '&=': __iand__,
646
    '^=': __ixor__,
647
    '|=': __ior__,
648
}
649

650

651
def protected_inplacevar(op, var, expr):
6✔
652
    """Do an inplace operation
653

654
    If the var has an inplace slot, then disallow the operation
655
    unless the var an instance of ``valid_inplace_types``.
656
    """
657
    if hasattr(var, inplace_slots[op]) and \
6✔
658
       not isinstance(var, valid_inplace_types):
659
        try:
6✔
660
            cls = var.__class__
6✔
UNCOV
661
        except AttributeError:
×
UNCOV
662
            cls = type(var)
×
663
        raise TypeError(
6✔
664
            "Augmented assignment to %s objects is not allowed"
665
            " in untrusted code" % cls.__name__)
666
    return inplace_ops[op](var, expr)
6✔
667

668

669
# AccessControl clients generally need to set up a safe globals dict for
670
# use by restricted code.  The get_safe_globals() function returns such
671
# a dict, containing '__builtins__' mapped to our safe bulitins, and
672
# bindings for all the special functions inserted into Python code by
673
# RestrictionMutator transformations.  A client may wish to add more
674
# bindings to this dict.  It's generally safe to do so, as
675
# get_safe_globals returns a (shallow) copy of a canonical safe globals
676
# dict.
677
# Exception:  For obscure technical reasons, clients have to import
678
# guarded_getattr from this module (ZopeGuards) and plug it into the
679
# dict themselves, with key '_getattr_'.
680
_safe_globals = {
6✔
681
    '__builtins__': safe_builtins,
682
    '__metaclass__': _metaclass,
683
    '_apply_': guarded_apply,
684
    '_getitem_': guarded_getitem,
685
    '_getiter_': guarded_iter,
686
    '_iter_unpack_sequence_': guarded_iter_unpack_sequence,
687
    '_unpack_sequence_': guarded_unpack_sequence,
688
    '_print_': RestrictedPython.PrintCollector,
689
    '_write_': full_write_guard,
690
    '_inplacevar_': protected_inplacevar,
691
    # The correct implementation of _getattr_, aka
692
    # guarded_getattr, isn't known until
693
    # AccessControl.Implementation figures that out, then
694
    # stuffs it into *this* module's globals bound to
695
    # 'guarded_getattr'.  We can't know what that is at
696
    # '_getattr_': guarded_getattr,
697
}
698

699
get_safe_globals = _safe_globals.copy
6✔
700

701
RestrictionCapableEval.globals.update(_safe_globals)
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc