Native Imports Guard
Activated by --block-native (CLI) or block_native=True (API).
Implemented in hermetic/guards/imports_guard.py.
Why a guard for native imports?
If you block network and subprocess at the Python level but allow
native code interop, the bypass is trivial: a few lines of ctypes
calling system(3) or connect(2) skips every Python guard in
hermetic. (See exploits/a.py in the repo for a worked example.)
--block-native raises the bar by:
- Refusing to import the FFI modules (
ctypes,_ctypes,cffi,_cffi_backend). - Replacing
importlib.machinery.ExtensionFileLoaderwith a subclass that refuses to load.so/.pydextensions. - Patching the dangerous attributes on already-loaded
ctypes/cffimodules to raisePolicyViolationwhen called.
What it patches
Import-time denial
The top-level package import is blocked for:
| Always | When --no-subprocess is also on |
|---|---|
ctypes |
sh |
_ctypes |
pexpect |
cffi |
plumbum |
_cffi_backend |
sarge |
delegator |
The right-hand list is for subprocess-replacement libraries that
capture subprocess references at import time, making them harder
to patch after the fact.
Native extension loader
importlib.machinery.ExtensionFileLoader is replaced with a
subclass whose create_module raises PolicyViolation. Importer
caches are invalidated so the new class is consulted for any
subsequent import.
Already-loaded ctypes / cffi
If ctypes or cffi was imported before hermetic installed
(very common — many libraries pull them in transitively), the
top-level import block doesn't help. Hermetic also patches the
following attributes on the loaded modules:
| Module | Attributes patched |
|---|---|
ctypes |
CDLL, PyDLL, WinDLL, OleDLL, LibraryLoader, cdll, pydll, windll, oledll |
ctypes.util |
find_library, find_msvcrt |
cffi |
FFI, dlopen, verify |
Each is replaced with a callable that raises PolicyViolation.
What it does not catch
- Already-loaded native extension modules.
numpy,lxml,cryptography, etc. — if they're already imported when hermetic runs, their C code is in memory and reachable via Python. Hermetic cannot evict them. - Captured references.
from ctypes import CDLLbefore hermetic installs gives the caller the unpatched class. Same class of bypass as the other guards. _ctypesprivate internals. Some internal types (_ctypes._SimpleCData,_FuncPtr) are not in the deny list. An attacker who knows them can build aCDLL-equivalent. The spec/secure_secure.md document tracks this as a known partially-mitigated bypass.- Cached
FileFinderinstances onsys.path_hooks. We invalidate the importer cache, but aFileFinderconstructed before our subclass install holds the originalExtensionFileLoaderclass in its loaders tuple. Newly-imported native modules go through the new path; modules whose finders were already cached may not.
Tracing
[hermetic] blocked import name=ctypes
[hermetic] blocked native import spec=lxml._elementpath
Composition with other guards
--block-native on its own only stops native loads — it doesn't
imply network or subprocess blocking. Combine flags explicitly:
# A reasonable LLM-tool sandbox.
hermetic --no-network --no-subprocess --block-native -- python tool_runner.py
# Or, equivalently:
hermetic --profile block-all -- python tool_runner.py
Combining --no-subprocess with --block-native activates the
extra import deny-list (sh, pexpect, plumbum, sarge,
delegator).
Examples
# Run a tool that should not use any FFI.
hermetic --block-native -- python my_pure_tool.py
from hermetic import with_hermetic
@with_hermetic(block_native=True)
def main():
# If something tries `import ctypes`, this raises PolicyViolation.
...