Skip to content

test(sandbox): a static AST guard pins the codec and worker import boundary (BACKLOG #346) - #252

Open
wshallwshall wants to merge 2 commits into
mainfrom
fix-346-sandbox-import-static
Open

test(sandbox): a static AST guard pins the codec and worker import boundary (BACKLOG #346)#252
wshallwshall wants to merge 2 commits into
mainfrom
fix-346-sandbox-import-static

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Fixes BACKLOG #346. Test-only.

A static AST guard pins the sandbox codec plus worker import boundary, so a future import that
crosses it fails at test time rather than at runtime in a sandboxed worker.

VERIFIED 3/3: the live guard, a POSITIVE control, and a NEGATIVE control. The negative control is the
part that matters -- it proves the guard can actually fail, rather than passing because it inspects
nothing. Commit messages glyph-free; banner gate OK.

…ry (BACKLOG #346)

The sandbox's import boundary (DEFAULT_FORBIDDEN_MODULES in pipeline/sandbox.py --
socket/ssl/asyncio, the I/O- and secret-bearing messagefoundry.* subpackages, cryptography)
is enforced only at RUNTIME and only inside the off-by-default [sandbox].mode=subprocess
child. Nothing statically pins that the two modules which run inside that boundary --
_sandbox_codec.py and _sandbox_worker.py -- do not themselves import a forbidden module. Both
are clean today; a future edit reintroducing a forbidden import would make mode=subprocess DOA
on first deployment while the default-mode suite stayed green -- the failure inverts, hitting
the most security-conscious installs hardest and quietest.

This is defence-in-depth test coverage, not a code change: neither sandbox.py nor the codec is
touched. tests/test_sandbox_import_boundary.py walks the two files' own ast import nodes
(ast.Import/ast.ImportFrom, including nested/function-level and relative imports resolved to
absolute) and asserts none resolves under a DEFAULT_FORBIDDEN_MODULES prefix. The forbidden set
is imported from the runtime constant, never copied, so the guard tracks whatever the sandbox
forbids. It ships with a positive control (each static import form the walker handles is seen,
including the load-bearing from-parent alias-append) and a negative control (benign
messagefoundry.* imports raise zero flags).

Scope is the two files' DIRECT imports, deliberately not a transitive walk: importing the codec
pulls asyncio/cryptography/store/transports/auth into sys.modules, so a transitive walker would
red on clean shipped code and prove nothing. sandbox.py is out of scope per BACKLOG #346 even
though the worker child imports it; the docstring records that residual for the owner.

Falsified: planting `import socket` into the real _sandbox_codec.py reddens the live guard
naming it; removing the walker's alias-append reddens only the alias-append positive-control
case; an over-broad matcher reddens the negative control. All plants restored before commit.
… landed (BACKLOG #346)

The #346 banner alone: OPEN -> SHIPPED, pointing at tests/test_sandbox_import_boundary.py
(the static ast guard added in the preceding commit). The completeness wording is softened from
"every forbidden import form is seen" to "each static import form the walker handles" -- a
static walker cannot see dynamic importlib/__import__ forms, and CLAUDE.md section 11 prefers a
bounded claim to an enumeration.

Only the #346 banner line changed; the ranked table, the four census distribution lines, and
every other item's banner are untouched. The census was NOT recomputed.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 6, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant