Skip to content

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_len are 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.root via namespace_state_dir);
  • the hook entrypoint filename used both under the user's containers/oci/hooks.d/ and inside each per-container state_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_dir into a later stage. Every property is a pure derivation off state_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 taking state_dir repeatedly.

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 via containers.conf.
  • network — slirp4netns CIDR/gateway and resolv.conf parsing.

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 json output. 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 CommandRunner for dig / getent subprocess 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 by HookMode.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-dir on 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.conf is patched to register that path so podman discovers the descriptors on the next container start.

Symmetric lifecycle: install writes, uninstall removes. 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 RulesetBuilder for nft generation, DnsResolver for name resolution, ProfileLoader for allowlists, dnsmasq for runtime DNS, and state for 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 dns and loopback_ports once 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_VERSIONterok_shield.state.BUNDLE_VERSION
  • ANN_STATE_DIRterok_shield.config.ANNOTATION_STATE_DIR_KEY
  • ANN_VERSIONterok_shield.config.ANNOTATION_VERSION_KEY
  • META_PATH_FILE_NAMEterok_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 the terok-clearance hub. The hub lives in the host user namespace, where it can emit the matching org.terok.Shield1 signals onto the session bus. The reader itself is stuck inside NS_ROOTLESS (that's where the container netns lives) and the session dbus-daemon rejects its SO_PEERCRED check — so the reader can't emit D-Bus directly.

  • --emit=json: write events as JSON lines on stdout. Drives the terok-shield simple-clearance terminal 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:

  1. DNS log — tails the per-container dnsmasq query log and emits events for blocked domain lookups.
  2. Audit log — tails audit.jsonl and surfaces shield lifecycle events (allow, deny, up, down, setup, teardown).
  3. NFLOG — reads denied packets via AF_NETLINK and 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 _dispatchsetup 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).