Sonnet-panel review of round-4 surfaced seven concrete bypass classes
in the static gate. All seven are now closed (528 tests passing, 55
new R4 / R5 regression tests):
1. ``os.path.join`` alias bypasses. ``import os as o; o.path.join(...)``,
``from os.path import join`` (and ``as j``), ``from os import path``
(and ``as op``), ``import posixpath as pp``, ``from posixpath import
join`` -- previously the FQ match was literal-only (`os.path.join`,
`posixpath.join`, `ntpath.join`). A pre-pass walk collects every
alias of ``os`` / ``os.path`` / ``posixpath`` / ``ntpath`` and every
from-import of ``join`` / ``expanduser``; the resolver checks
``<alias>.join`` and bare aliased names too.
2. ``shutil`` alias bypasses. ``import shutil as sh; sh.copy(...)``,
``from shutil import copyfile``, ``from shutil import move as mv``,
etc. -- the file-copy gate matched only the literal ``shutil.X`` FQ.
The pre-pass now tracks shutil module aliases and from-import
aliases for ``copyfile`` / ``copy`` / ``copy2`` / ``copytree`` /
``move``; the gate canonicalises any matched alias to ``shutil.X``
so the error message identifies the operation.
3. First-assignment-wins binding bypass. ``p = '/tmp/safe'; p =
'/etc/shadow'; open(p)`` previously slipped because the pre-pass
guard ``_target.id not in string_bindings`` ignored every
reassignment, and the AST walk picked the safe value while Python
uses last-wins at runtime. New ``string_bindings_all`` tracks every
literal ever bound to a name; ``_record_string_binding`` biases the
representative value toward sensitive-shaped paths via a substring
hint set covering the credential / process-state root tokens. The
reverse order (``shadow`` then ``safe``) is also caught.
4. Brace-expansion off-by-one. ``cat ~/.aws/{x0,...,x62,credentials}``
exploited that ``_expand_brace_projections`` started with
``out = {original}`` (1 item) so a cap of 64 only left 63
alternative slots. The inner loop also broke per-alternative on
the cap, so the sensitive name at position 64+ was never reached.
Raised the cap to 1024 and the inner loop now expands all
alternatives of a brace in one pass before the outer cap can stop
the queue.
5. ``thread-self`` in shell-expansion regex. ``cat /proc/thread-self/
$(echo environ)`` was missed because ``_SENSITIVE_ROOT_WITH_EXPANSION_RE``
only listed ``self|\d+`` in the ``/proc/...`` alternation, while
``_ABSOLUTE_SENSITIVE`` correctly included ``thread-self``. One
alternation entry restores symmetry.
6. Eval / exec pre-pass not re-run. ``exec("p='/etc/shadow'\nopen(p)")``
slipped because the inner AST visit ran without the string-binding
pre-pass. Extracted the pre-pass into ``_run_string_binding_prepass``
and call it on each inner literal payload before the visitor
recurses, so payload-local variable assignments are visible.
7. Pathlib name binding pre-pass. ``p = Path('/etc/shadow');
p.read_text()`` slipped because the pre-pass only resolved string
literals -- pathlib constructor calls returned None and the bound
name remained unresolved. Pre-pass now falls back to
``_extract_pathlib_target`` using per-tree alias sets so
``import pathlib as pl; p = pl.Path(...)`` and ``from pathlib import
Path as P; p = P(...)`` both resolve. ``NamedExpr`` (walrus) is also
surfaced by the pre-pass so walrus-inside-eval expressions are
visible.
Pre-pass call order. The initial pre-pass invocation moves to AFTER
``_extract_pathlib_target`` is defined so the closure cell binds
correctly (Python looks up free variables in the enclosing scope at
CALL time, not at function-definition time).
Full sandbox suite: 528 passed (455 prior + 73 R4 / R5 regression tests).