Close seven bypasses Codex found on the round-50 branch.
- dynamic PATH assignment: os.environ['PATH'] = '.:' + os.environ['PATH'] (or an
f-string) was accepted because the value is non-literal. Fold what we can and fail
closed when a COMPLETE, fully-literal PATH entry the value contributes is a
relative / cwd / empty entry; a dynamic ABSOLUTE extension ('/usr/local/bin:' +
$PATH, venv + ':' + $PATH) stays allowed.
- os.environb mutations: os.environb[b'PATH'] = b'.:...' updates the same inherited
environment, but only os.environ[...] was recognized. Match environ / environb
(attribute and bare) and decode a bytes key / value before the policy check.
- os.environ.update / setdefault: a mapping mutator
(os.environ.update({'PATH': '.:...'}), .update(PATH=...), .setdefault('PATH', ...))
never hit the subscript check. Run each (key, value) pair through the mutation
policy in visit_Call.
- sqlite URI percent-decode: sqlite3.connect('file:%2Ftmp%2Fescape.db', uri=True)
passed the runtime guard as a relative-looking string while SQLite decodes the
filename and opens /tmp/escape.db. Percent-decode the URI path (with the guard's
captured chr/int) before the workdir check.
- sqlite shell / pipe dot-commands: the CLI scanner only path-checked file
dot-commands, but .shell CMD / .system CMD run a system shell and .output |CMD
opens a pipe. Block a .shell / .system / .excel dot-command and an .output/.once
target that begins with '|'.
- getattr gadget dunders in the workdir vetter: a helper module could call
getattr(open, '__closure__') / getattr(cell, 'cell_contents') to recover the guard
wrapper's original unguarded open, because the getattr branch only rejected a few
sensitive receivers. Reject a gadget-dunder name on ANY receiver (mirrors the
direct-attribute check).
- find -exec in subprocess argv: the read scanner flattened the argv and checked
each element independently, missing subprocess.run(['find','/etc',...,'-exec',
'cat','{}',';']) reading /etc/passwd (the {} placeholder loses the escaping search
root). Reconstruct a find child-exec argv into a shell string and run it through
the read scanner, which carries the find-root + -exec logic.
Regression coverage: TestRound51Bypasses in tests/test_sandbox_tools.py (dynamic /
environb / update PATH mutations, sqlite .shell/.system/.output-pipe, find -exec
argv, plus a round51 benign-allowed set: absolute dynamic PATH, benign env vars,
local sqlite .output/.dump, workdir find -exec) and, in
tests/test_sandbox_runtime_backstop.py, the sqlite percent-encoded URI escape (with
a benign local URI) and the getattr gadget-dunder workdir-module denial.