The verdict helper — a minimal varlink server wrapping terok-shield.
One process, one socket, one method (Apply). In the
per-container-supervisor model the helper runs inside the
Sandbox supervisor alongside the hub, on a
per-container socket; the standalone terok-clearance-hub serve-verdict
entry point is kept for integration testing.
Stateless: no authz decisions, no request-id binding, no fan-out.
The hub already validated the verdict triple before forwarding; the
helper exists solely to isolate the hostile exec path from the
hardened receive path.
VerdictServer(*, socket_path=None, shield_binary=None, socket_context=None)
Per-process wrapper around the Apply varlink interface.
The hub is the only legitimate client; SO_PEERCRED on the unix
socket rejects peers with a different UID, and
bind_hardened leaves the
socket mode 0600 for the lifetime of the server.
Configure the socket + shield executable path.
socket_context — see
ClearanceHub;
forwarded to
bind_hardened so
the verdict socket can carry a custom SELinux type.
Source code in src/terok_clearance/verdict/server.py
| def __init__(
self,
*,
socket_path: Path | None = None,
shield_binary: str | None = None,
socket_context: Callable[[], AbstractContextManager[None]] | None = None,
) -> None:
"""Configure the socket + shield executable path.
*socket_context* — see
[`ClearanceHub`][terok_clearance.ClearanceHub];
forwarded to
[`bind_hardened`][terok_clearance.wire.socket.bind_hardened] so
the verdict socket can carry a custom SELinux type.
"""
self._socket_path = socket_path or default_verdict_socket_path()
self._shield_binary = shield_binary or find_shield_binary()
self._socket_context = socket_context
self._server: VarlinkUnixServer | None = None
|
start()
async
Bind the varlink server and start accepting hub verdict calls.
Source code in src/terok_clearance/verdict/server.py
| async def start(self) -> None:
"""Bind the varlink server and start accepting hub verdict calls."""
registry = VarlinkInterfaceRegistry()
registry.register_interface(Verdict1Interface(apply_verdict=self._apply))
registry.register_interface(
VarlinkServiceInterface(
vendor="terok",
product="terok-clearance-verdict",
version=_own_version(),
url="https://github.com/terok-ai/terok-clearance",
registry=registry,
)
)
async def _factory(path: str) -> object:
return await create_unix_server(registry.protocol_factory, path=path)
self._server = await bind_hardened(
_factory,
self._socket_path,
"verdict",
socket_context=self._socket_context,
)
_log.info("verdict helper online at %s", self._socket_path)
|
stop()
async
Close the varlink server; existing in-flight Apply calls finish first.
Source code in src/terok_clearance/verdict/server.py
| async def stop(self) -> None:
"""Close the varlink server; existing in-flight Apply calls finish first."""
if self._server is None:
return
self._server.close()
with contextlib.suppress(AttributeError):
self._server.close_clients()
with contextlib.suppress(TimeoutError, Exception):
await asyncio.wait_for(self._server.wait_closed(), timeout=1.0)
self._server = None
|
run_shield(shield_binary, container, dest, action)
async
Invoke terok-shield <action> <container> <dest>; return (ok, snippet).
Bounded by _SHIELD_CLI_TIMEOUT_S. Spawn errors, non-zero
exit, and timeouts all fold into (False, reason) so callers
see one shape regardless of how shield misbehaved. snippet
is capped at _STDERR_CAP_BYTES.
Source code in src/terok_clearance/verdict/server.py
| async def run_shield(
shield_binary: str | None, container: str, dest: str, action: str
) -> tuple[bool, str]:
"""Invoke ``terok-shield <action> <container> <dest>``; return ``(ok, snippet)``.
Bounded by `_SHIELD_CLI_TIMEOUT_S`. Spawn errors, non-zero
exit, and timeouts all fold into ``(False, reason)`` so callers
see one shape regardless of how shield misbehaved. ``snippet``
is capped at `_STDERR_CAP_BYTES`.
"""
if not shield_binary:
return False, "terok-shield not found on PATH"
try:
proc = await asyncio.create_subprocess_exec(
shield_binary,
action,
container,
dest,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.PIPE,
)
except OSError as exc:
_log.exception("failed to spawn terok-shield")
return False, f"spawn failed: {exc}"
try:
_, stderr_bytes = await asyncio.wait_for(proc.communicate(), timeout=_SHIELD_CLI_TIMEOUT_S)
except TimeoutError:
proc.kill()
with contextlib.suppress(Exception):
await proc.communicate()
_log.warning("shield %s timed out after %gs", action, _SHIELD_CLI_TIMEOUT_S)
return False, f"timed out after {_SHIELD_CLI_TIMEOUT_S}s"
snippet = (stderr_bytes[:_STDERR_CAP_BYTES] or b"").decode(errors="replace").strip()
ok = proc.returncode == 0
if not ok:
_log.warning("shield %s failed: %s", action, snippet)
return ok, snippet
|
find_shield_binary()
Locate terok-shield — sibling venv first, then PATH, then None.
The sibling check handles the pipx / poetry case where terok-shield
ships in the same venv as terok-clearance; we prefer it over PATH
so a shell-rc PATH shim can't redirect verdicts through a
different installation. is_file alone would happily return a
non-executable artifact, so the exec-bit check prevents a broken
install from failing every verdict instead of falling through to
PATH's working copy.
Source code in src/terok_clearance/verdict/server.py
| def find_shield_binary() -> str | None:
"""Locate ``terok-shield`` — sibling venv first, then PATH, then ``None``.
The sibling check handles the pipx / poetry case where terok-shield
ships in the same venv as terok-clearance; we prefer it over PATH
so a shell-rc ``PATH`` shim can't redirect verdicts through a
different installation. ``is_file`` alone would happily return a
non-executable artifact, so the exec-bit check prevents a broken
install from failing every verdict instead of falling through to
PATH's working copy.
"""
sibling = Path(sys.executable).parent / "terok-shield"
if sibling.is_file() and os.access(sibling, os.X_OK):
return str(sibling)
return shutil.which("terok-shield")
|
serve()
async
Bring the verdict helper online and stay up until SIGINT/SIGTERM.
Mirrors terok_clearance.hub.server.serve so the CLI layer
can dispatch both entrypoints through the same asyncio.run
pattern.
Source code in src/terok_clearance/verdict/server.py
| async def serve() -> None:
"""Bring the verdict helper online and stay up until SIGINT/SIGTERM.
Mirrors [`terok_clearance.hub.server.serve`][terok_clearance.hub.server.serve] so the CLI layer
can dispatch both entrypoints through the same ``asyncio.run``
pattern.
"""
from terok_clearance.runtime.service import configure_logging, wait_for_shutdown_signal
configure_logging()
server = VerdictServer()
await server.start()
try:
await wait_for_shutdown_signal()
finally:
await server.stop()
|