mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-29 01:42:19 +00:00
fix(guardrails): wire _unpack_sequence_ into the custom-code sandbox
RestrictedPython rewrites every tuple-unpacking target that is not a for-loop
target into a call to _unpack_sequence_, and ships no default for it -- the
same way it ships no _inplacevar_, which this module already supplies. The
sandbox globals never defined it, so ordinary guardrail code compiled cleanly
and then raised NameError on its first run:
a, b = pair
a, (b, c) = nested
a, *rest = seq
with ctx as (a, b):
RestrictedPython.Guards.guarded_unpack_sequence is the helper the rewritten
bytecode expects, and it applies the same _getiter_ guard to each element that
guarded_iter_unpack_sequence already applies for `for a, b in x`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147HaHohHPnpFBUa4tUkvPg
This commit is contained in:
parent
2ade3e16a9
commit
6d0146a3f3
2 changed files with 36 additions and 0 deletions
|
|
@ -31,6 +31,7 @@ from RestrictedPython.Eval import default_guarded_getitem, default_guarded_getit
|
|||
from RestrictedPython.Guards import (
|
||||
full_write_guard,
|
||||
guarded_iter_unpack_sequence,
|
||||
guarded_unpack_sequence,
|
||||
safer_getattr,
|
||||
)
|
||||
|
||||
|
|
@ -115,6 +116,12 @@ def build_sandbox_globals() -> dict[str, object]:
|
|||
"_getitem_": default_guarded_getitem,
|
||||
"_getiter_": default_guarded_getiter,
|
||||
"_iter_unpack_sequence_": guarded_iter_unpack_sequence,
|
||||
# RestrictedPython emits _unpack_sequence_ for every tuple-unpacking
|
||||
# target that is not a for-loop target — ``a, b = pair``,
|
||||
# ``a, *rest = seq``, ``with x as (a, b)`` — and, like _inplacevar_,
|
||||
# ships no default. Without it those statements compile and then raise
|
||||
# NameError the first time the guardrail runs.
|
||||
"_unpack_sequence_": guarded_unpack_sequence,
|
||||
"_write_": full_write_guard,
|
||||
"_inplacevar_": _inplacevar_,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,3 +228,32 @@ def test_augmented_assignment_works():
|
|||
def test_missing_apply_guardrail_raises():
|
||||
with pytest.raises(CustomCodeCompilationError, match="apply_guardrail"):
|
||||
_compile("x = 1\n")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("body", "expected"),
|
||||
[
|
||||
# Every one of these compiles fine and then raises
|
||||
# "NameError: name '_unpack_sequence_' is not defined" at call time when
|
||||
# the guard is missing from the sandbox globals.
|
||||
(" a, b = inputs['pair']\n return a + b\n", 3),
|
||||
(" a, (b, c) = inputs['nested']\n return a + b + c\n", 6),
|
||||
(" a, *rest = inputs['seq']\n return rest\n", [2, 3]),
|
||||
(" with inputs['ctx'] as (a, b):\n return a + b\n", 15),
|
||||
],
|
||||
)
|
||||
def test_tuple_unpacking_runs(body: str, expected):
|
||||
"""Ordinary tuple unpacking must work inside a guardrail, not NameError."""
|
||||
|
||||
class _Ctx:
|
||||
def __enter__(self):
|
||||
return (7, 8)
|
||||
|
||||
def __exit__(self, *args):
|
||||
return False
|
||||
|
||||
guardrail = _compile("def apply_guardrail(inputs, request_data, input_type):\n" + body)
|
||||
fn = guardrail._compiled_function
|
||||
assert fn is not None
|
||||
inputs = {"pair": (1, 2), "nested": (1, (2, 3)), "seq": [1, 2, 3], "ctx": _Ctx()}
|
||||
assert fn(inputs, {}, "request") == expected
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue