shield
shield
¶
Adapter for terok-shield egress firewall.
Two classes carry the sandbox-side policy layer over terok-shield:
ShieldManager— per-task wrapper aroundShield. Caches the underlying instance. Kill-switch-aware methods (pre_start,up,down,check_environment) short-circuit whenshield_disabledis set; always-on methods (quarantine,state) always hit the live shield because panic overrides the kill-switch and state probes report what nft actually sees.statusis config-level only and surfaces the kill-switch flag in its dict rather than short-circuiting.ShieldHooks— the host-wide OCI hooks installer, scoped to the root/user dual-scope flag pair theterok setupandterok-sandboxCLIs expose. Delegates to terok-shield'sHooksInstallerfor the actual file writes; terok-shield owns the on-disk install layout, so sandbox carries no private mirror of it.
ShieldManager(task_dir, cfg=None, *, runtime=ShieldRuntime.DEFAULT, loopback_ports_override=None)
¶
Per-task wrapper around Shield.
Holds the (task_dir, cfg, runtime) tuple a Shield is built from
and caches the constructed instance — the previous free-function
surface rebuilt a Shield on every call, which paid the
ShieldConfig + collaborator-wiring cost twice for every
transition pair (pre_start → up, up → down, …).
Kill-switch-aware methods (pre_start, up, down)
short-circuit when shield_disabled is set on the configuration.
Always-on methods (quarantine, state) always run — panic
overrides the kill-switch, and state probes report what nft
actually sees regardless of operator intent.
Bind the manager to a task directory and shield configuration.
runtime selects the container runtime category — DEFAULT
for crun/runc/youki (dnsmasq on netns 127.0.0.1), KRUN
for the libkrun microVM path (dnsmasq on a link-local address
the guest can reach via passt). Callers that drive the launch
path map their runtime string (RunSpec.runtime) to the
enum.
loopback_ports_override replaces the cfg-derived
(gate_port, token_broker_port, ssh_signer_port) triple — the
per-container launch path passes the freshly-allocated broker
and signer ports so shield's nft rules allow the actual host
ports the supervisor binds.
Source code in src/terok_sandbox/integrations/shield.py
state_dir
property
¶
Per-task shield state directory: {task_dir}/shield.
disabled
property
¶
True when shield_disabled is set on the sandbox configuration.
shield
cached
property
¶
Lazily constructed Shield instance.
Built from a ShieldConfig whose
loopback_ports reflect the actual gate/broker/signer
ports — auto-allocated configs default those fields to None,
which would otherwise silently produce an empty tuple and a
shield ruleset with no
tcp dport <p> ip daddr 169.254.1.2 accept rules, causing
container→host TCP traffic to fall through to the
private-range reject (#156 regression follow-up).
dns_tier
property
¶
The DNS tier this task launched with; None when none was recorded.
Reads only the recorded tier file — like
status, it
pays no Shield wire-up cost. The tier says what it provides: whether
its allow sets are live, and a hint for the operator when not.
pre_start(container, *, security_deny=(), provider_allow=(), project_allow=(), override=())
¶
Return extra podman run args for egress firewalling.
The four tier arguments are the orchestrator's generated policy tiers, which shield writes into the bundle so this layer only carries the data: security_deny → t20 (deny direct-to-vault-host), provider_allow → t30 (provider egress), project_allow → t40 (git remote + custom, merged with the composed profiles), override → t10 (break-glass allow above the deny). Shield owns each tier outright: an empty tuple (the default) clears that tier, so every call must carry the full current data — never rely on a previous launch's content surviving.
Returns an empty list (no firewall args) when the dangerous
disable_firewall_no_protection override is active.
Propagates SetupRequiredError when
the podman environment requires one-time hook installation.
Source code in src/terok_sandbox/integrations/shield.py
refresh(container, *, security_deny=(), provider_allow=(), project_allow=(), override=())
¶
Recompute an existing container's policy bundle before a plain restart.
Same tier data and owns-and-clears semantics as
pre_start,
but for a container that already exists: shield rewrites the tiers and
regenerates the pre-applied artifacts (ruleset.nft, dnsmasq
config) so the next podman start enforces current policy
instead of the bundle frozen at creation. No podman args are
produced — the container keeps its launch-time configuration.
Source code in src/terok_sandbox/integrations/shield.py
up(container, container_id)
¶
Set shield to deny-all mode for a running container.
container is the operator-facing podman name (audit-log key);
container_id is the full podman UUID — terok-shield's per-
container hub socket is keyed on it. Both are mandatory:
terok-shield removed the global-hub fallback in
feat/per-container-supervisor.
Source code in src/terok_sandbox/integrations/shield.py
down(container, container_id, *, disengaged=False)
¶
Switch shield to the DOWN posture (allow egress) for a running container.
container / container_id — see
up. When
disengaged is True, the container takes the DISENGAGED posture
instead: nothing is enforced — no deny set, no private-range or
hard-deny reject.
Source code in src/terok_sandbox/integrations/shield.py
quarantine(container)
¶
Total network blackout — drop all traffic, log dropped traffic.
Ignores shield_disabled because panic overrides the kill-switch.
state(container)
¶
Return the live shield state for a running container.
Queries actual nft state even when the kill-switch is set, because containers started before it was enabled may still have active rules.
Source code in src/terok_sandbox/integrations/shield.py
status()
¶
Return shield status dict from the sandbox configuration.
Reads only the sandbox configuration — does not instantiate the underlying Shield, so callers that only want configuration-level shape don't pay the Shield wire-up cost.
Source code in src/terok_sandbox/integrations/shield.py
check_environment()
¶
Check the podman environment for shield compatibility.
Returns a synthetic EnvironmentCheck
flagging the kill-switch when the dangerous disable override is active.
Source code in src/terok_sandbox/integrations/shield.py
interactive_session(container)
¶
Run the terminal clearance fallback for this task's shield.
Thin wrapper that spares callers from reaching into
terok_shield.simple_clearance
and rebuilding the state_dir themselves. Refuses to run
when the D-Bus clearance hub is already handling the session.
Source code in src/terok_sandbox/integrations/shield.py
watch_session(container)
¶
Stream shield blocked-access events for this task as JSON lines.
Thin wrapper that spares callers from reaching into
terok_shield.watch and rebuilding the
state_dir themselves.
Source code in src/terok_sandbox/integrations/shield.py
ShieldHooks
¶
Host-wide OCI hooks installer — no task context.
Thin pass-through to terok-shield's
HooksInstaller. Kept as a class
so the sandbox setup aggregator can swap it out in tests without
poking around terok-shield internals.
check_setup(*, live=False)
staticmethod
¶
Delegate readiness to Shield, which owns its installation.
install()
staticmethod
¶
Install global OCI hooks for shield egress firewalling.
Global hooks are required on all podman versions to survive
container stop/start cycles (terok-shield#122). Single
layout: scripts, ballast, and JSON descriptors all land in
namespace_state_dir("shield") / "hooks";
containers.conf is patched to register that path.
Source code in src/terok_sandbox/integrations/shield.py
check_environment(cfg=None)
¶
Probe the podman environment with no task context.
Returns a synthetic EnvironmentCheck
when shield_disabled is set; otherwise constructs a throwaway
ShieldManager
bound to a temp directory and delegates to its
check_environment.
Kept as a free function because the setup CLI runs before any
task directory exists.