Configuration¶
terok-shield uses XDG-compliant directories and an optional YAML config file.
Config file¶
Optional. Located at ~/.config/terok/shield/config.yml
(or $XDG_CONFIG_HOME/terok/shield/config.yml).
The file is validated with Pydantic (extra="forbid"). Typos and wrong
types produce a clear error at startup instead of being silently ignored.
See Config Reference for the full field table
and annotated YAML example.
If the config file is missing, defaults are used. Unparseable YAML (syntax errors) falls back to defaults with a warning.
Library vs CLI
The config file is only read by the CLI. When using terok-shield as a
library, all configuration is passed programmatically via ShieldConfig.
Mode selection¶
Currently only hook mode is supported. Setting mode: auto or mode: hook
both resolve to hook mode. Future modes may be added for different network
topologies.
Directories¶
State directory¶
Default: ~/.local/state/terok/shield/
Override: TEROK_SHIELD_STATE_DIR or --state-dir flag
Each container gets an isolated state bundle under containers/:
~/.local/state/terok/shield/
└── containers/
└── my-container/
├── hooks/
│ ├── terok-shield-createRuntime.json
│ └── terok-shield-poststop.json
├── terok-shield-hook # OCI hook entrypoint (stdlib-only Python)
├── ruleset.nft # Pre-generated nft ruleset
├── gateway # Discovered gateway IP
├── profile.allowed # Pre-resolved IPs from DNS profiles
├── profile.domains # Domain names for dnsmasq config
├── live.allowed # IPs from runtime allow/deny
├── live.domains # Domains added at runtime
├── deny.list # Persistent deny overrides
├── denied.domains # Domains denied at runtime
├── dnsmasq.conf # Generated dnsmasq config (dnsmasq tier)
├── dnsmasq.pid # dnsmasq PID (dnsmasq tier)
├── resolv.conf # Bind-mounted /etc/resolv.conf (dnsmasq tier)
├── upstream.dns # Persisted upstream DNS address
├── dns.tier # Persisted active DNS tier
└── audit.jsonl # Per-container audit log
| File | Written by | Purpose |
|---|---|---|
hooks/ |
pre_start() |
OCI hook descriptors |
terok-shield-hook |
pre_start() |
Stdlib-only hook entrypoint script |
ruleset.nft |
pre_start() |
Pre-generated nft ruleset applied by the hook |
gateway |
OCI hook | Gateway IP discovered from /proc/{pid}/net/route |
profile.allowed |
pre_start() / resolve() |
Cached IPs from DNS resolution |
profile.domains |
pre_start() |
Domain names for dnsmasq --nftset entries |
live.allowed |
allow() / deny() |
Runtime allow/deny IP persistence |
live.domains |
allow_domain() |
Domains added at runtime |
deny.list |
deny() |
IPs denied from presets (survives up/down cycles) |
denied.domains |
deny_domain() |
Domains denied at runtime |
dnsmasq.conf |
pre_start() |
Generated dnsmasq configuration (dnsmasq tier only) |
dnsmasq.pid |
OCI hook | dnsmasq PID for lifecycle management |
resolv.conf |
pre_start() |
Redirects container DNS to 127.0.0.1:53 (dnsmasq tier) |
upstream.dns |
pre_start() |
Persisted upstream DNS forwarder address |
dns.tier |
pre_start() |
Persisted tier (dnsmasq, dig, or getent) |
audit.jsonl |
Hook + Shield methods | Per-container audit log |
Config directory¶
Default: ~/.config/terok/shield/
Override: TEROK_SHIELD_CONFIG_DIR
| Path | Contents |
|---|---|
profiles/ |
Custom allowlist profiles (override bundled ones) |
config.yml |
Shield configuration |
DNS resolution¶
DNS resolution behaviour depends on the active tier, selected automatically by
detect_dns_tier():
dnsmasq tier (preferred): a per-container dnsmasq instance is started by
the OCI hook. It uses --nftset to auto-populate the nft allow_v4/allow_v6
sets on every resolution at runtime. profile.allowed still holds the
pre-start resolved IPs, but dynamic resolution handles IP rotation
automatically — no cache expiry needed.
dig / getent tiers (fallback): resolved IPs are stored in profile.allowed,
one IP per line. The cache uses file modification time (st_mtime) for
freshness checking — entries older than 1 hour are automatically re-resolved.
Force a cache refresh (all tiers):
Environment variables¶
| Variable | Purpose |
|---|---|
TEROK_SHIELD_STATE_DIR |
Override state directory location |
TEROK_SHIELD_CONFIG_DIR |
Override config directory location |
XDG_STATE_HOME |
XDG state base (default: ~/.local/state) |
XDG_CONFIG_HOME |
XDG config base (default: ~/.config) |
OCI annotations¶
These annotations are set automatically by terok-shield run (or
pre_start() in the Python API) and read by the OCI hook:
| Annotation | Value | Purpose |
|---|---|---|
terok.shield.profiles |
Comma-separated names | Which profiles to apply |
terok.shield.name |
Container name | Audit log identification |
terok.shield.state_dir |
Absolute path | Where the hook finds its state bundle |
terok.shield.loopback_ports |
Comma-separated ints | Ports for ruleset generation |
terok.shield.version |
Integer | Bundle version (hard-fail on mismatch) |
terok.shield.audit_enabled |
true / false |
Whether to write audit logs |
terok.shield.upstream_dns |
IP address | Upstream DNS forwarder for dnsmasq |
terok.shield.dns_tier |
dnsmasq / dig / getent |
Active DNS resolution tier |