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

zopefoundation / AccessControl / 8203781742

08 Mar 2024 01:09PM UTC coverage: 81.316% (+0.2%) from 81.124%
8203781742

Pull #147

github

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

951 of 1475 branches covered (64.47%)

Branch coverage included in aggregate %.

76 of 77 new or added lines in 3 files covered. (98.7%)

10 existing lines in 1 file now uncovered.

5055 of 5911 relevant lines covered (85.52%)

5.12 hits per line

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

89.03
/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
    __allow_access_to_unprotected_subobjects__ = 1
6✔
270

271
    def __iter__(self):
6✔
272
        for e in super().__iter__():
6✔
273
            guard(self._mapping, e)
6✔
274
            yield e
6✔
275

276

277
class SafeKeysView(_SafeMappingView, collections.abc.KeysView):
6✔
278
    pass
6✔
279

280

281
class SafeValuesView(_SafeMappingView, collections.abc.ValuesView):
6✔
282
    pass
6✔
283

284

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

292

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

297
    def __next__(self):
6✔
298
        return next(self._iter)
6✔
299

300
    next = __next__
6✔
301

302

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

306

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

318

319
safe_builtins['iter'] = guarded_iter
6✔
320

321

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

330
# More replacement built-ins.
331

332

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

337
    if f is None:
6!
338
        def f(x):
6✔
339
            return x
6✔
340

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

352

353
safe_builtins['filter'] = guarded_filter
6✔
354

355

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

362

363
safe_builtins['reduce'] = guarded_reduce
6✔
364

365

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

372

373
safe_builtins['max'] = guarded_max
6✔
374

375

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

382

383
safe_builtins['min'] = guarded_min
6✔
384

385

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

393

394
safe_builtins['map'] = guarded_map
6✔
395

396

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

404

405
safe_builtins['zip'] = guarded_zip
6✔
406

407

408
import_default_level = 0
6✔
409

410

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

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

439

440
safe_builtins['__import__'] = guarded_import
6✔
441

442

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

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

450

451
safe_builtins['list'] = GuardedListType()
6✔
452

453

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

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

461

462
safe_builtins['dict'] = GuardedDictType()
6✔
463

464

465
def guarded_enumerate(seq):
6✔
466
    return NullIter(enumerate(guarded_iter(seq)))
6✔
467

468

469
safe_builtins['enumerate'] = guarded_enumerate
6✔
470

471

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

475

476
safe_builtins['sum'] = guarded_sum
6✔
477

478

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

495

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

505

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

528

529
safe_builtins['apply'] = builtin_guarded_apply
6✔
530

531

532
def guarded_any(seq):
6✔
533
    return any(guarded_iter(seq))
6✔
534

535

536
safe_builtins['any'] = guarded_any
6✔
537

538

539
def guarded_all(seq):
6✔
540
    return all(guarded_iter(seq))
6✔
541

542

543
safe_builtins['all'] = guarded_all
6✔
544

545

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

557

558
valid_inplace_types = (list, set)
6✔
559

560

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

576

577
def __iadd__(x, y):
6✔
578
    x += y
6✔
579
    return x
6✔
580

581

582
def __isub__(x, y):
6✔
583
    x -= y
6✔
584
    return x
6✔
585

586

587
def __imul__(x, y):
6✔
588
    x *= y
6✔
589
    return x
6✔
590

591

592
def __idiv__(x, y):
6✔
593
    x /= y
6✔
594
    return x
6✔
595

596

597
def __ifloordiv__(x, y):
6✔
598
    x //= y
6✔
599
    return x
6✔
600

601

602
def __imod__(x, y):
6✔
603
    x %= y
6✔
604
    return x
6✔
605

606

607
def __ipow__(x, y):
6✔
608
    x **= y
6✔
609
    return x
6✔
610

611

612
def __ilshift__(x, y):
6✔
613
    x <<= y
6✔
614
    return x
6✔
615

616

617
def __irshift__(x, y):
6✔
618
    x >>= y
6✔
619
    return x
6✔
620

621

622
def __iand__(x, y):
6✔
623
    x &= y
6✔
624
    return x
6✔
625

626

627
def __ixor__(x, y):
6✔
628
    x ^= y
6✔
629
    return x
6✔
630

631

632
def __ior__(x, y):
6✔
633
    x |= y
6✔
634
    return x
6✔
635

636

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

652

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

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

670

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

701
get_safe_globals = _safe_globals.copy
6✔
702

703
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