Module Map¶
Generated: 2026-06-03 21:38 UTC
Module and class docstrings grouped by architectural layer.
foundation¶
terok_shield._hub_events¶
Best-effort JSON event emitter for the terok-clearance hub.
Shield CLI calls (up/down) notify the hub so desktop/TUI consumers
can reflect the state change — in particular, the hub closes pending
block notifications for a container whose shield just dropped. Stays
stdlib-only so the reader script resource (which bypasses the package)
can mirror the same wire format without importing this module.
Every emit targets the per-container ingester socket under
$XDG_RUNTIME_DIR/terok/events/<short_id>.sock, where <short_id>
is the 12-char prefix of the container id — the same addressing the
NFLOG reader uses, and deliberately distinct from the varlink
subscriber socket at terok/clearance/<id>.sock that operator UIs
glob. Callers therefore supply container_id (the full podman
container UUID) on every call: the destination socket is per-container,
so the id is what selects it.
Fails silent when the hub isn't listening: flipping shield state must never be held up by a desktop-side daemon being absent.
HubEventEmitter — One-shot writer of JSON-line events to the hub's unix ingester.
Each
emit_*call opens a fresh connection to the per-container socket, sends a single line, and closes. The hub stays up across many CLI invocations while each CLI invocation is short-lived — pooling would save nothing and would complicate the fail-silent semantics.
terok_shield._wire_sanitize¶
Producer-side wire-format invariant — printable ASCII, length-capped.
WIRE_SPEC(safe-string): keep in sync with¶
terok_clearance/src/terok_clearance/wire/sanitize.py — same rule,¶
same character class, same length cap. grep WIRE_SPEC finds¶
every copy across the producer/consumer boundary; clearance owns¶
the canonical version (it's the wire-format consumer).¶
The threat model: container processes can craft DNS names, hostnames, or annotation bytes that flow through the shield's watchers and the NFLOG reader straight onto the hub socket. A consumer downstream (notification daemon, terminal TUI, audit listener) sees those bytes verbatim unless someone trims them along the way.
Sanitising at every emit point — here, in _hub_events, in the
reader resource — is belt-and-braces: clearance also applies the
same rule on the receive side, so a regression on either side keeps
the contract. Producer-side sanitisation specifically protects the
container-out path that's the primary attack surface; consumer-side
catches every other event source.
Rule (single, simple):
- Printable ASCII (
[\x20, \x7E]) passes through unchanged. - Anything else — control bytes, non-ASCII, RTLO/LRO bidi overrides, the lot — collapses to a single space, position-preserving.
- Strings longer than
max_lenare truncated with a trailing...ASCII marker.
Stdlib-only by design — no external imports — so this module sits in
the foundation tach layer alongside _hub_events.
terok_shield.config (catalog)¶
Shield configuration types, enums, and mode protocol.
Defines the vocabulary shared across the entire codebase: what a shield configuration looks like, what modes and states exist, and what contract a mode backend must satisfy.
| Type | Description |
|---|---|
DnsTier |
DNS resolution tier for egress control. |
ShieldMode |
Operating mode for the shield firewall. |
ShieldState |
Per-container shield state, derived from the live nft ruleset. |
ShieldRuntime |
Container runtime category — drives DNS-reachability assumptions. |
ShieldConfig |
Per-container shield configuration. |
AuditFileConfig |
Audit section of config.yml. |
ShieldFileConfig |
Validated schema for config.yml. |
ShieldModeBackend |
Strategy protocol for shield mode implementations. |
terok_shield.paths¶
Host-wide filesystem paths and filenames for terok-shield artifacts.
Per-container state paths live in terok_shield.state. This
module is the single source of truth for artifacts shared across
containers or installed into host-wide locations:
- the NFLOG reader script's canonical install path (under
paths.rootvianamespace_state_dir); - the hook entrypoint filename used both under the user's
containers/oci/hooks.d/and inside each per-containerstate_dir.
The reader-script path is computed once in this module. The
resources/reader_hook.py script cannot import from terok_shield
at runtime (it runs under /usr/bin/python3 outside any venv); the
installer rewrites that script's _READER_SCRIPT_PATH placeholder
with the resolved path at install time, so the on-disk hook always
points at wherever reader_script_path() resolved when terok-shield
setup ran.
terok_shield.prereqs¶
Host binary prerequisite checks for the shield runtime.
Exported for higher layers (terok-sandbox aggregator, operator diagnostics) so the place that owns each binary — shield — is the place that publishes the list of binaries it depends on. Keeps the install-time preflight and the runtime failure sites honest about what the shield actually needs.
Pure probes: every check is shutil.which or a sbin-aware variant.
No subprocess invocation, no side effects.
BinaryCheck — Result of probing for a single prerequisite binary.
terok_shield.run (catalog)¶
Subprocess execution boundary for all external commands.
Every shell-out in terok-shield flows through the CommandRunner
protocol. Production code uses SubprocessRunner; tests inject
fakes. This keeps external dependencies auditable and mockable in one
place.
| Type | Description |
|---|---|
CommandRunner |
Protocol for executing external commands. |
SubprocessRunner |
Default CommandRunner implementation using subprocess.run. |
ExecError |
Raised when a subprocess fails. |
NftNotFoundError |
Raised when the nft binary is not found on the host. |
DigNotFoundError |
Raised when the dig binary is not found on the host. |
ShieldNeedsSetup |
Raised when global OCI hooks are not installed. |
terok_shield.state¶
Per-container state bundle layout contract.
Every shielded container gets an isolated state directory. This module
is the single source of truth for where files live within it — all
paths are derived from a single state_dir root through
StateBundle. Zero dependencies
beyond pathlib.
Bundle layout::
{state_dir}/
├── hooks/
│ ├── terok-shield-createRuntime.json
│ └── terok-shield-poststop.json
├── {HOOK_ENTRYPOINT_NAME} # entrypoint script (stdlib-only Python)
├── ruleset.nft # pre-generated nft ruleset (gateways baked in)
├── upstream.dns # upstream DNS address
├── dns.tier # active DNS tier (dig/getent/dnsmasq)
├── loopback.ports # per-container host-loopback TCP ports (newline-separated)
├── profile.allowed # IPs from DNS resolution
├── profile.domains # domain names for dnsmasq config
├── live.allowed # IPs from allow/deny
├── live.domains # domain overrides from allow_domain
├── deny.list # persistent deny overrides
├── denied.domains # denied domains from deny_domain
├── dnsmasq.conf # generated dnsmasq configuration
├── dnsmasq.pid # dnsmasq PID (in container netns)
├── dnsmasq.log # dnsmasq query log (for shield watch)
├── resolv.conf # bind-mounted over /etc/resolv.conf (dnsmasq tier)
├── container.id # podman container ID (short, 12-char hex)
└── audit.jsonl # per-container audit log
StateBundle — File-layout contract for a single shielded container's state_dir.
Frozen so the per-task instance is safe to pass through hook callbacks without anyone smuggling a mutated
state_dirinto a later stage. Every property is a pure derivation offstate_dir; the IO methods (read_allowed_ips,read_denied_ips,read_effective_ips,ensure_dirs) bundle the small handful of read-and-merge / setup helpers that previously floated as free functions takingstate_dirrepeatedly.
terok_shield.util¶
IPv4 / IPv6 address and CIDR validation helpers.
terok_shield.validation¶
Input validators for container names, profile names, and allowlist files.
Pure functions with no internal dependencies — safe to import from any module.
Nft¶
terok_shield.nft.constants¶
nftables table names, network defaults, and log prefixes.
Pure literals with no logic — safe for import by the nft.py security boundary.
Podman Info¶
terok_shield.podman_info (waypoint)¶
Podman environment detection.
Grouped into three submodules so each concern stands on its own:
info— version + capability parsing.hooks_dir— global hook directory discovery viacontainers.conf.network— slirp4netns CIDR/gateway andresolv.confparsing.
Public names are re-exported here for convenience; new code is welcome to import from the specific submodule when intent is clearer.
terok_shield.podman_info._conf¶
Shared containers.conf location resolution.
Both hooks_dir and
network read fields from podman's
containers.conf files; this module owns the system + user search paths
so each consumer parses its own key from the same well-defined set of files.
terok_shield.podman_info.hooks_dir¶
OCI hooks directory discovery.
Reads containers.conf to figure out where podman would look for
global hook descriptors, and reports whether shield's own hook is
installed in any of them.
terok_shield.podman_info.info¶
Podman version and capability detection.
Parses podman info -f json into a structured
PodmanInfo dataclass, with
just enough metadata for shield to choose a network mode and decide
whether per-container --hooks-dir will survive a restart.
This module is stateless — callers cache the result.
PodmanInfo — Parsed podman environment information.
Constructed from
podman info -f jsonoutput. Stateless — the caller manages caching.
terok_shield.podman_info.network¶
Rootless networking helpers.
The slirp4netns gateway and resolv.conf parsing live here so
nothing in the firewall path has to reach into the broader podman_info
package just to derive a host address or DNS server.
core¶
Dns¶
terok_shield.dns (waypoint)¶
DNS resolution and caching subsystem.
Collaborators: resolver — stateless DNS resolution with dig/getent dnsmasq — dnsmasq configuration generation, lifecycle, and nftset integration
terok_shield.dns.apparmor¶
AppArmor awareness for the per-container dnsmasq DNS tier.
Some distros (Arch/Manjaro, the apparmor.d profile set) ship an
enforcing AppArmor profile for /usr/sbin/dnsmasq that forbids the
shield state directory under the operator's home, so the per-container
dnsmasq cannot read its config and the container would fail to launch.
This module probes for that confinement behaviourally (via dnsmasq
--test — no root needed) and drives a fallback to the dig tier.
The profile addendum that lets operators keep the dnsmasq tier is
documented in docs/apparmor.md.
terok_shield.dns.dnsmasq¶
Per-container dnsmasq config generation, reload, and domain management.
dnsmasq runs inside the container's network namespace (via nsenter)
on a runtime-dependent listen address — 127.0.0.1:53 for ordinary
runtimes that share the netns loopback, a link-local address under
krun whose guest can't reach netns 127.0.0.1. --nftset
auto-populates nft allow sets on every DNS resolution to handle IP
rotation that static pre-start resolution cannot.
This module is the single package-side owner of dnsmasq config format
and CLI args; the per-container start/stop dance is owned by the OCI
hook resource (resources/nft_hook.py), which has its own stdlib-
only copy because hook scripts run outside the package venv.
terok_shield.dns.resolver¶
DNS resolution with timestamp-based caching.
Resolves domain names from allowlist profiles via dig and caches
the results so containers do not block on DNS at every start. Profiles
prefer domain names over raw IPs because CDN addresses rotate.
Falls back to getent hosts when dig is not installed — fewer
IPs are captured (no parallel A + AAAA query), but resolution still
works. When the dnsmasq tier is active, domain resolution happens at
runtime via --nftset; this module then only handles raw IPs.
DnsResolver — Stateless DNS resolver — all persistence lives in the cache file.
The only dependency is a
CommandRunnerfordig/getentsubprocess calls.
Hooks¶
terok_shield.hooks (waypoint)¶
OCI hook system — installation and per-container lifecycle.
Collaborators: install — hook file generation and installation into hooks directories mode — HookMode strategy: per-container nft ruleset lifecycle via OCI hooks
terok_shield.hooks.install¶
OCI hook file generation and installation.
Writes two role-specific entrypoint scripts (nft-hook and
reader-hook) plus a shared _oci_state.py ballast module to the
target hooks directory, alongside the JSON descriptors that tell
podman to invoke each one at createRuntime and poststop.
Scripts and descriptors both land in
namespace_state_dir("shield") / "hooks" under the operator's
paths.root. containers.conf is patched so podman scans that
path. Each sibling package owns its own subtree under paths.root
the same way (see terok_sandbox.supervisor.install).
Public entry points:
HooksInstaller— global installation lifecycle (install + uninstall).install_hooks— per-container install used byHookMode.pre_start.
Pure file I/O — no runtime container interaction.
HooksInstaller — Persistent installation of terok-shield's OCI hook pair.
The createRuntime/poststop hook pair must persist across container restarts: podman ≥ 5.x drops per-container
--hooks-diron stop/start (containers/podman#17935), so global hooks are the only reliable activation path until that upstream regression is fixed.Scripts, ballast, and JSON descriptors all land in target_dir (default:
namespace_state_dir("shield") / "hooks").containers.confis patched to register that path so podman discovers the descriptors on the next container start.Symmetric lifecycle:
installwrites,uninstallremoves. Both are idempotent.
terok_shield.hooks.mode¶
Hook mode: OCI hooks + per-container netns.
Uses OCI hooks to apply per-container nftables rules inside each container's network namespace. No root required — only podman and nft.
Orchestrates collaborators per lifecycle phase:
- RulesetBuilder (
nft.rules) — generates and verifies nft rulesets - DnsResolver (
dns.resolver) — pre-start domain resolution - ProfileLoader (
profiles) — allowlist profile composition - AuditLogger (
audit) — event logging - CommandRunner (
run) — subprocess execution (nft, nsenter) - dnsmasq (
dns.dnsmasq) — runtime DNS with nftset auto-population - hook_install (
hooks.install) — OCI hook file generation - state (
state) — per-container state bundle I/O
HookMode — Hook-mode shield backend (Strategy, implements ShieldModeBackend).
Coordinates the full lifecycle of OCI-hook-based container firewalling. Delegates to
RulesetBuilderfor nft generation,DnsResolverfor name resolution,ProfileLoaderfor allowlists,dnsmasqfor runtime DNS, andstatefor per-container persistence.
terok_shield.hooks.reader_install¶
Installer for the standalone NFLOG reader resource.
Copies terok_shield/resources/nflog_reader.py out of the installed
package to the canonical on-disk location, where the OCI bridge hook
can execute it with /usr/bin/python3. The destination survives
terok-shield reinstalls (the OCI hook references it by absolute path
regardless of the package's virtual-environment location).
Nft¶
terok_shield.nft (waypoint)¶
nftables security boundary — ruleset generation and constants.
Collaborators: constants — pure-literal nft table/chain/set names (foundation) rules — ruleset generation, nft command building, input validation (core)
terok_shield.nft.rules¶
nftables ruleset generation and verification.
Generates per-container nftables rulesets (deny-all and bypass modes), provides set operations for runtime allowlist/denylist management, and verifies applied rulesets against security invariants.
Security boundary: only stdlib + nft_constants.py imports allowed. All inputs are validated before interpolation into nft commands.
RulesetBuilder — Builder for nftables ruleset generation and verification.
Security boundary: only stdlib + nft_constants imports. All inputs validated before interpolation.
Binds
dnsandloopback_portsonce at construction so callers do not repeat them on every generation or verification call.
support¶
terok_shield (waypoint)¶
terok-shield: nftables-based egress firewalling for Podman containers.
Public API facade. The Shield class coordinates collaborators:
- HookMode (
hooks.mode) — per-container nft ruleset lifecycle - DnsResolver (
dns.resolver) — domain resolution and caching - ProfileLoader (
profiles) — allowlist profile composition - RulesetBuilder (
nft.rules) — nftables ruleset generation - AuditLogger (
audit) — per-container JSONL audit trail - CommandRunner (
run) — subprocess execution boundary
Core and support modules are imported lazily — from terok_shield
import ShieldConfig does not pull in nft, dnsmasq, or subprocess
helpers. Heavy imports are deferred until Shield is instantiated.
- EnvironmentCheck — Result of
Shield.check_environment. - Shield — Public API facade — coordinates collaborators per container.
terok_shield.audit¶
Per-container JSON-lines audit logging.
Writes structured events (setup, teardown, allow, deny) to a single file per container. Can be toggled on/off at runtime without losing the file handle.
AuditLogger — JSON-lines audit logger for a single container.
Writes to a single file (
audit_path). When disabled, all write operations are no-ops.
terok_shield.commands (waypoint)¶
Every subcommand terok-shield exposes — arguments, handler, and help text.
The COMMANDS tuple is the single source of truth consumed by both the
standalone CLI and the terok integration layer. Handler functions accept
(shield, container?, **kwargs) and print to stdout, making them
reusable across different CLI frontends.
CommandDef and ArgDef are re-exported from
terok_util — the unified vocabulary every sibling
package shares. Shield-specific flags (needs_container,
standalone_only) ride along in
CommandDef.extras; the
shield CLI dispatcher reads them via the
needs_container and
standalone_only helpers
defined below.
terok_shield.container¶
Bottom-up container→state_dir resolution via podman annotations.
Shielded containers are launched with a terok.shield.state_dir
annotation that points at the per-container state directory written by
pre_start(). The OCI hook already reads that annotation out of the
runtime-provided OCI state JSON (see resources/hook_entrypoint.py).
This module does the same lookup for consumers that only have a
container name and no in-process ShieldConfig — the clearance
hub's verdict path, ad-hoc CLI invocations against a live container,
anything that enters from the podman side of the handoff rather than
from terok's task orchestration.
The annotation is the single source of truth for a shielded
container's state directory: both the OCI hook (via crun's stdin) and
the CLI (via this module) converge on the same string. In-process
callers (terok-sandbox.make_shield) supply state_dir at
construction and don't need to do a lookup.
On hosts where podman inspect isn't reachable (no podman on PATH,
no rootless user namespace, container simply doesn't exist), the
resolver returns None and callers fall back to whatever legacy
behaviour they had.
terok_shield.profiles¶
Allowlist profile loading and composition.
Finds, reads, and merges .txt allowlist profiles from user and
bundled directories. User profiles override bundled ones with the
same name, so site-specific customisation works without forking.
ProfileLoader — Loads and composes .txt allowlist profiles.
Searches user profiles first (overriding bundled), then falls back to the bundled profiles shipped with the package.
terok_shield.simple_clearance¶
Terminal-based clearance fallback for hosts without the D-Bus hub.
simple-clearance is the stripped-down sibling of the full Clearance
flow: instead of desktop notifications and a TUI sharing signals over
org.terok.Shield1, it streams blocked-connection events from a
subprocess NFLOG reader and prompts the operator on a terminal.
Verdicts are applied by shelling out to the audited
terok-shield allow|deny CLI, so the trust boundary is identical.
Refuses to run when the D-Bus hub is active on the session bus — concurrent application from both paths would race on the same verdict, so only one is enabled at a time.
_Pending — A blocked connection awaiting the operator's verdict.
ClearanceSession — Drive the terminal clearance loop for a single container.
Owns the reader subprocess, the operator prompt UI, and the verdict subprocess calls. Lives until the reader exits or the operator interrupts with Ctrl-C.
terok_shield.subprocess_env¶
Environment helper for spawned terok_shield child processes.
Centralises the PYTHONPATH shim every subprocess.run /
Popen of sys.executable must use, so adding a new spawn site
can't silently regress the Nix-wrapped-Python fix.
terok_shield.watch¶
shield watch — stream blocked-access events as JSON lines.
Tails the dnsmasq query log, per-container audit log, and (optionally) the NFLOG netlink socket. Only works when the dnsmasq DNS tier is active. Clean exit on SIGINT or SIGTERM.
Resources¶
terok_shield.resources (waypoint)¶
Bundled resources for terok-shield.
terok_shield.resources._oci_state¶
Shared OCI-hook ballast — used by both nft_hook and reader_hook.
This module is shipped verbatim alongside the two role-specific hook
scripts and imported by them at runtime. The role scripts add
Path(__file__).parent to sys.path (Python does this implicitly
when python3 script.py is invoked, but the relative-import contract
is what the isolation test checks against), and from there from
_oci_state import … resolves to this file.
Stdlib-only by design (audited by test_hook_entrypoint_isolation):
the OCI runtime executes us with /usr/bin/python3 outside any
virtualenv, so a dependency on terok_shield would fail to import.
Keep in sync with the package-side definitions:
BUNDLE_VERSION↔terok_shield.state.BUNDLE_VERSIONANN_STATE_DIR↔terok_shield.config.ANNOTATION_STATE_DIR_KEYANN_VERSION↔terok_shield.config.ANNOTATION_VERSION_KEYMETA_PATH_FILE_NAME↔terok_shield.state.meta_path_file
terok_shield.resources.dns (waypoint)¶
Bundled DNS allowlists.
terok_shield.resources.nflog_reader (catalog)¶
Stream blocked-connection events out of one container for the clearance flow.
Subscribes to the kernel's NFLOG group inside a single container's network namespace, deduplicates by destination IP, and publishes each unique block as an event. Events always travel as JSON; the reader itself never speaks D-Bus. Two destinations are supported:
-
--emit=socket(default): write events to a unix socket served by theterok-clearancehub. The hub lives in the host user namespace, where it can emit the matchingorg.terok.Shield1signals onto the session bus. The reader itself is stuck insideNS_ROOTLESS(that's where the container netns lives) and the sessiondbus-daemonrejects itsSO_PEERCREDcheck — so the reader can't emit D-Bus directly. -
--emit=json: write events as JSON lines on stdout. Drives theterok-shield simple-clearanceterminal fallback, which runs in the operator's own userns and parses the stream directly.
The OCI bridge hook spawns one reader per shielded container at
createRuntime and SIGTERMs it at poststop — the process tree is what
ties the reader's lifetime to the container's.
Stdlib-only by design: shipped as a resource that /usr/bin/python3 can
execute anywhere without depending on the terok-shield virtual environment.
| Type | Description |
|---|---|
BlockedEvent |
A packet the kernel dropped at the interactive-deny rule — one per unique dest IP. |
ReaderSession |
Orchestrates the container's block-event stream for the clearance flow. |
EventEmitter |
The two publishing channels a reader can speak — hub socket or JSON stdout. |
SocketEmitter |
Stream JSON events to the hub's unix-socket ingester. |
JsonEmitter |
Publish events as JSON lines on stdout — drives the terminal fallback CLI. |
_RawBlockEvent |
Pre-enrichment fields pulled straight from one NFLOG packet. |
_DomainCache |
Reverse-lookup from resolved IP back to the dnsmasq-observed domain. |
terok_shield.resources.nft_hook¶
OCI hook: apply pre-generated terok-shield nft ruleset.
Applies ruleset.nft (written by pre_start()) and optionally
starts dnsmasq if dnsmasq.conf is present in the state directory.
Gateway addresses are baked into the ruleset at generation time — no
runtime /proc discovery needed.
Stdlib-only by design, except for a sibling-module import of
_oci_state shipped to the same hooks directory at install time.
The OCI runtime executes us with /usr/bin/python3 outside any
virtualenv, so a dependency on terok_shield would fail to import.
The hook is invoked by crun, which runs inside podman's rootless user
namespace (NS_ROOTLESS). Inside NS_ROOTLESS os.getuid() ==
0 and CAP_NET_ADMIN is already available, so nsenter -n -t
<pid> is used directly. When run from a normal shell
(NS_INIT, uid != 0), podman unshare nsenter -n -t <pid> is
used instead — see _oci_state.nsenter for the dispatch.
terok_shield.resources.reader_hook¶
OCI hook: spawn / reap the per-container NFLOG reader.
Soft-fails on every error path: a missing reader script, an unreachable session bus, or a failed Popen all log and return normally so the container still starts. The clearance UI degrades gracefully to "no events, no desktop notifications" in those cases.
Stdlib-only by design, except for a sibling-module import of
_oci_state shipped to the same hooks directory at install time.
terok_shield.resources.shield_probe¶
Probe network reachability and report the exact ICMP error code.
Standalone diagnostic script designed to run INSIDE containers. Uses
IP_RECVERR + MSG_ERRQUEUE on a regular UDP socket to retrieve
the kernel's sock_extended_err struct, which preserves the original
ICMP type and code — unlike connect() errno, which maps several
distinct ICMP codes to the same EHOSTUNREACH.
No special capabilities (CAP_NET_RAW, CAP_NET_ADMIN) are needed.
Usage::
shield_probe.py HOST [PORT]
Exit codes:
- 0: probe completed (check JSON output for result)
- 1: usage error or unexpected failure
Output is a single JSON object on stdout.
Watchers¶
terok_shield.watchers (waypoint)¶
Live blocked-access event stream for shield watch.
Multiplexes three event sources into a single JSON-lines stream:
- DNS log — tails the per-container dnsmasq query log and emits events for blocked domain lookups.
- Audit log — tails
audit.jsonland surfaces shield lifecycle events (allow, deny, up, down, setup, teardown). - NFLOG — reads denied packets via
AF_NETLINKand emits events for raw-IP connections that bypassed DNS. Optional — graceful degradation when netlink is unavailable.
terok_shield.watchers._event¶
Shared event type emitted by all watchers.
WatchEvent — A single watch event emitted to the output stream.
Core fields (always present):
ts,source,action,container. DNS-specific:domain,query_type. Audit/NFLOG:dest,detail,port,proto.
terok_shield.watchers.audit_log¶
Tail audit.jsonl and emit events for shield lifecycle changes.
Watches for new JSON-lines entries written by
AuditLogger and surfaces them as
WatchEvent instances with source="audit".
AuditLogWatcher — Tail audit.jsonl and yield events for shield lifecycle changes.
terok_shield.watchers.dns_log¶
Tail the dnsmasq query log and emit events for blocked domain lookups.
Watches for new query[A] / query[AAAA] lines and classifies
each domain by suffix-matching against the merged allowed domain set
(profile + live - denied). Requires the dnsmasq DNS tier.
DnsLogWatcher — Tail the dnsmasq query log and yield events for blocked domains.
Opens the log file, seeks to the end, and watches for new query lines.
terok_shield.watchers.domain_cache¶
IP-to-domain reverse lookup from the dnsmasq query log.
Parses reply / cached lines to build a mapping from resolved
IPs to their domain names. Used by shield watch and the
interactive session for NFLOG event enrichment.
DomainCache — IP-to-domain reverse lookup cache.
terok_shield.watchers.nflog¶
Read denied packets via AF_NETLINK NFLOG and emit watch events.
Subscribes to the kernel's nflog group to receive copies of packets that
matched log group rules in the nft ruleset. Extracts destination IP,
port, and log prefix from each message.
Optional — NflogWatcher.create() returns None if netlink is
unavailable (missing kernel module, insufficient permissions).
NflogWatcher — Read NFLOG messages via AF_NETLINK and yield events for denied packets.
cli¶
terok_shield.cli (waypoint)¶
CLI and tool entry points for terok-shield.
This subpackage contains presentation-layer code: argument parsing,
command dispatch, interactive verdict loops, and the shield watch
event-streaming entry point. Library consumers should import from
terok_shield (the public API facade) rather than from here.
terok_shield.cli.__main__ (waypoint)¶
Package entry point so python -m terok_shield.cli works.
simple_clearance shells out to
python -m terok_shield.cli to apply operator verdicts, threading
the parent's sys.path through
child_process_env
so Nix-wrapped interpreters still resolve the package. Without an
explicit __main__ module the -m switch refuses to execute the
package and every verdict fails as ! verdict failed for <dest>.
Delegates verbatim to main; the
launch round-trip is regression-tested in
tests/unit/test_cli_main_module.py.
terok_shield.cli.main (waypoint)¶
Standalone CLI — parses argv, builds a Shield, and dispatches commands.
Constructs ShieldConfig from config.yml, XDG conventions, and
environment variables, then routes each subcommand through the
COMMANDS registry (terok_shield.commands). Five commands with standalone
CLI logic are handled directly by _dispatch — setup and logs
(which bypass Shield entirely) and prepare, run, resolve
(which need Shield but carry extra CLI concerns). All others delegate
to their registry handler via Shield (the public API facade).