Programmatic API
Use hermetic from inside Python without going through the CLI. Two
forms — a context manager and a decorator — both backed by the same
implementation in hermetic.blocker.
hermetic_blocker(...)
A reentrant, thread-safe context manager that installs guards on
entry and removes them on exit (unless sealed=True).
from hermetic import hermetic_blocker
with hermetic_blocker(block_network=True, block_subprocess=True):
...
It is also a decorator (it inherits from contextlib.ContextDecorator):
@hermetic_blocker(block_network=True)
def main():
...
And it works in async contexts:
async with hermetic_blocker(block_network=True):
...
Keyword arguments
All arguments are keyword-only.
| Argument | Type | Default | Meaning |
|---|---|---|---|
block_network |
bool |
False |
Same as --no-network. |
block_subprocess |
bool |
False |
Same as --no-subprocess. |
fs_readonly |
bool |
False |
Same as --fs-readonly. |
fs_root |
str \| None |
None |
Same as --fs-readonly=ROOT; requires fs_readonly=True. |
block_environment |
bool |
False |
Same as --no-environment. |
block_code_exec |
bool |
False |
Same as --no-code-exec. |
block_interpreter_mutation |
bool |
False |
Same as --no-interpreter-mutation. |
block_native |
bool |
False |
Same as --block-native. |
allow_localhost |
bool |
False |
Same as --allow-localhost. |
allow_domains |
Iterable[str] |
() |
Same as repeated --allow-domain. |
deny_imports |
Iterable[str] |
() |
Same as repeated --deny-import. |
trace |
bool |
False |
Same as --trace. |
sealed |
bool |
False |
Same as --seal. See Sealed Mode. |
with_hermetic(...)
A decorator factory with the same kwargs as hermetic_blocker.
It is a thin alias kept for readability:
from hermetic import with_hermetic
@with_hermetic(block_network=True, allow_domains=["api.internal.com"])
def process_data():
import requests
# Blocked: not on the allow-list.
# requests.get("https://example.com")
# Allowed.
return requests.get("https://api.internal.com/data")
Nesting and reentrancy
Guards are global monkey-patches — they affect the whole interpreter,
including all threads. hermetic_blocker is reference-counted:
nested entries combine their policies (the union wins, never the
intersection), and guards are removed only when the outermost
context exits.
from hermetic import hermetic_blocker
with hermetic_blocker(block_network=True):
# Network blocked.
with hermetic_blocker(block_subprocess=True):
# Network AND subprocess blocked.
...
# Back to network blocked only.
# All guards removed.
This means an inner block cannot weaken an outer block. You
cannot with hermetic_blocker(...): ... your way back to a
permissive policy.
Catching policy violations
When a guard blocks an action, it raises hermetic.PolicyViolation
(a subclass of RuntimeError). You can catch it like any other
exception:
from hermetic import hermetic_blocker
from hermetic.errors import PolicyViolation
with hermetic_blocker(block_subprocess=True):
try:
import os
os.system("echo hi")
except PolicyViolation as e:
print(f"blocked: {e}")
If you don't catch it, the exception propagates normally. The CLI
runner catches it at the top level and exits with code 2.
Threading caveats
Because guards patch global state, all threads in the interpreter
see the same policy. If thread A is inside hermetic_blocker(...)
and thread B is making a network call, thread B will hit the guard
too.
This is rarely a problem in practice — most uses of hermetic are single-threaded scripts, single-threaded tests, or async event loops. But be aware: there is no per-thread or per-coroutine sandbox.
Async usage
hermetic_blocker implements both the sync and async context
manager protocols. The async variants delegate to the sync ones —
guard installation is fast and synchronous, so there's nothing to
await.
import asyncio
from hermetic import hermetic_blocker
async def main():
async with hermetic_blocker(block_network=True):
# asyncio.create_subprocess_* is also blocked when
# block_subprocess=True.
...
asyncio.run(main())
Examples
A pytest fixture that blocks network for one test:
import pytest
from hermetic import hermetic_blocker
@pytest.fixture
def no_network():
with hermetic_blocker(block_network=True, allow_localhost=True):
yield
def test_offline_path(no_network):
# Anything that hits the network in here will raise PolicyViolation.
...
A guarded entry point for an LLM tool runner:
from hermetic import with_hermetic
@with_hermetic(
block_network=True,
allow_domains=["api.openai.com", "api.anthropic.com"],
block_subprocess=True,
fs_readonly=True,
fs_root="/tmp/agent-workspace",
block_environment=True,
block_code_exec=True,
block_interpreter_mutation=True,
deny_imports=["pickle", "marshal"],
block_native=True,
trace=True,
)
def run_agent_tool(tool_call):
return tool_call()