Table of contents
Kernloom IQ (kliq) — Full reference
Kernloom IQ is the controller. It reads IPv4 and IPv6 telemetry from Kernloom Shield and applies progressive enforcement per source:
OBSERVE → RATE_SOFT → RATE_HARD → BLOCK.
This page contains:
- how IQ makes decisions (engine)
- how to tune safely (recipes)
- a complete flag-by-flag reference (every argument)
Decision engine
Inputs per tick
IQ polls /sys/fs/bpf/kernloom_src4_stats & /sys/fs/bpf/kernloom_src6_stats every --interval and computes deltas:
- PPS, bytes/s
- SYN/s
- scan/s (destination-port change counter)
- DropRL/s (rate-limit drops)
Severity score
Each feature is normalized by a trigger threshold and capped:
nPPS = min(PPS / trig-pps, sev-cap)nSYN = min(SYN/s / trig-syn, sev-cap)nSCAN = min(scan/s / trig-scan, sev-cap)
Severity is the weighted sum:
severity = w-pps*nPPS + w-syn*nSYN + w-scan*nSCAN
Strikes → levels
Severity increases “strikes” using step thresholds (sev-step* and sev-delta*).
Strike thresholds map to levels:
soft-at→ RATE_SOFThard-at→ RATE_HARDblock-at→ BLOCK (optionally gated)
Hysteresis (anti-flap)
IQ prevents oscillation using:
up-need/down-needstreaksmin-hold-soft/min-hold-hardcooldown
Non-compliance escalation
If an IP is in HARD and still produces DropRL/s (keeps hitting the limiter), IQ can move to BLOCK sooner (controlled by noncomp-*).
Block gating (recommended)
Blocking is risky behind NAT. Gate blocking using:
block-min-sevblock-min-dur
If the gate fails, IQ clamps to HARD instead of BLOCK.
Autotune and clean ticks
IQ can learn trigger thresholds using Median + MAD.
Learning is limited to “clean ticks” via learn-* and optional totals drop-ratio gating.
Profiles
A profile is a starting behavior template for Kernloom IQ.
It defines the initial trigger thresholds, signal weights, rate-limit levels, hold times, cooldowns, and blocking gates that IQ uses when it sees suspicious traffic from a source IP.
In practice, a profile answers this question:
“What kind of service am I protecting, and what does normal traffic roughly look like at the beginning?”
Important: profiles are only starting points
Profiles are not static rules and they are not meant to perfectly match your environment forever.
They provide sensible seed values for:
- when packet rate (
PPS) should start to matter - how sensitive the profile is to
SYNbursts - how much port-scan style behavior should matter
- when IQ should move from observe to soft rate-limit, hard rate-limit, or block
- how long those actions should be held
After startup, autotune adapts these values to your real traffic patterns.
This means the selected profile should be understood as:
best starting fit, not final behavior.
So even if two environments both use public-api, Kernloom will gradually adapt differently depending on actual traffic, burstiness, NAT effects, client behavior, and attack patterns.
How to choose a profile
Pick the profile that is closest to the service you want to protect:
- protect a public website →
public-web - protect a public API →
public-api - protect login / auth / identity endpoints →
idp - protect internal east-west workloads →
internal-app - protect an SSH entrypoint / bastion →
ssh-bastion - protect an OpenZiti Controller →
ziti-controllerorziti-controller-bootstrap - protect an OpenZiti Router →
ziti-routerorziti-router-bootstrap
If you are onboarding a new system and want to reduce false positives at the beginning, start with a *-bootstrap profile and usually also with --dry-run=true.
Profile details
ziti-router-bootstrap
Best starting profile for an OpenZiti Router during initial learning.
Behavior:
- very tolerant trigger levels
- prefers rate-limiting first
- blocking is effectively disabled during bootstrap
- longer holds and slower escalation reduce false positives during onboarding
Use this when:
- you are attaching Kernloom to a router for the first time
- you do not yet know the normal throughput envelope
- you want Kernloom to learn safely before stronger enforcement
ziti-router
Recommended steady-state profile for an OpenZiti Router after bootstrap.
Behavior:
- high PPS tolerance
- moderate SYN sensitivity
- scan detection matters, but less than throughput
- block is allowed, but only after sustained severity and duration
Use this when:
- the router has already been observed for a while
- you want stronger protection than bootstrap
- you need a NAT-friendlier profile for busy tunnel/data traffic
ziti-controller-bootstrap
Best starting profile for an OpenZiti Controller during onboarding.
Behavior:
- more tolerant than the normal controller profile
- rate-limits earlier than it blocks
- blocking is effectively disabled
- good for building a baseline safely
Use this when:
- Kernloom is newly deployed in front of a controller
- you want to observe and autotune before enabling real enforcement
- you want a safer first step for public controller/API exposure
ziti-controller
Recommended steady-state profile for an OpenZiti Controller.
Behavior:
- relatively low trigger thresholds
- stronger weight on
SYNactivity - faster escalation than router profiles
- blocking is allowed, but still gated by severity and duration
Use this when:
- the controller is exposed to the internet
- you want to protect enrolment, management API, or edge-facing controller endpoints
- you already completed bootstrap / dry-run learning
public-web
Good default for a public website or web frontend.
Behavior:
- balanced for public web traffic
- tolerates normal browsing bursts
- scan detection contributes, but less than packet rate and connection pressure
Use this when:
- you protect a typical public website
- the application is mostly browser-driven
- traffic is bursty, but not as API-heavy as machine-to-machine workloads
public-api
Good default for a public JSON/API endpoint.
Behavior:
- higher PPS/SYN tolerance than
public-web - still reacts strongly to sustained pressure
- rate-limits and block thresholds are tuned for burstier API patterns
Use this when:
- the protected service is API-first
- traffic comes from apps, scripts, agents, or integrations
- a normal web profile would likely be too conservative
idp
Recommended for identity, authentication, login, token, or enrolment endpoints.
Behavior:
- high
SYNsensitivity - lower soft/hard thresholds
- stricter rate limits than generic web/api profiles
- blocking is possible, but still gated to avoid overreacting too fast
Use this when:
- you protect an IdP
- you protect login pages, token endpoints, OIDC/SAML frontends, enrolment APIs, or authentication gateways
- you want stronger sensitivity to early connection abuse
internal-app
Recommended for internal east-west services.
Behavior:
- high weight on scan behavior
- blocking is effectively disabled by default
- prefers soft and hard rate-limits over outright deny
- longer holds reduce state flapping
Use this when:
- you protect internal workloads
- you want to detect scanning / lateral movement pressure
- you prefer a conservative internal posture before allowing blocks
ssh-bastion
Recommended for SSH entrypoints and bastion hosts.
Behavior:
- low trigger thresholds
- strong sensitivity to SYN-heavy behavior
- tight soft/hard rate limits
- blocking is allowed, but still gated to avoid blocking on tiny accidental bursts
Use this when:
- you protect an SSH bastion
- you expose administrative access to the internet
- you want fast reaction on brute-force / scan-like behavior
Practical recommendation
A safe rollout usually looks like this:
- Start with the closest matching
*-bootstrapprofile, if available - Run with
--dry-run=true - Let Kernloom observe and autotune on real traffic
- Review state transitions and top talkers
- Move to the non-bootstrap profile
- Enable enforcement
In short:
- bootstrap profiles = safer onboarding, fewer false positives, no real blocking
- regular profiles = stronger steady-state enforcement
- autotune = adapts both to your real environment over time
Complete CLI flag reference (every argument)
Conventions
- Durations use Go syntax (
1s,10m,24h,336h). - Many flags accept “use profile” by setting 0 / NaN / -1:
0for most numeric knobs → use profile defaultsblock-min-sev=NaN→ use profile defaultsblock-min-dur=-1→ use profile defaults
Core runtime
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--interval | duration | 1s | Poll interval and decision tick. |
--top | int | 200 | Evaluate top-N sources by severity per tick. Larger values = more CPU in userspace. |
--min-pps | float | 10 | Ignore sources below this PPS unless severity/DropRL/s keeps them relevant. |
--min-sev | float | 0.0 | Include candidates with severity >= this. |
--dry-run | bool | true | If true: never write /sys/fs/bpf/kernloom_rl_policy4 or /sys/fs/bpf/kernloom_deny4_hash and /sys/fs/bpf/kernloom_rl_policy6 or /sys/fs/bpf/kernloom_deny6_hash. |
Profile + persistence
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--profile | string | controller | Initial profile name. Alias mapping may apply. |
--state-file | string | /var/lib/kernloom/iq/state.json | Persist tuned triggers, bootstrap metadata, and history. Empty disables persistence. |
--max-state-age | duration | 336h | Ignore persisted state older than this (0 disables). |
--state-history | int | 30 | Keep last N history entries. |
Whitelist (permanent exemptions)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--whitelist | string | /etc/kernloom/iq/whitelist.txt | IPv4/IPv6 or CIDR, one per line; empty disables. |
--whitelist-reload | duration | 10s | Reload if file changed (0 disables). |
--whitelist-learn | bool | false | If true, whitelisted sources may contribute to learning (riskier). |
Feedback (temporary exemptions)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--feedback-file | string | /var/lib/kernloom/iq/feedback.json | JSON array with temporary exemptions; empty disables. |
--feedback-reload | duration | 10s | Reload if file changed (0 disables). |
--feedback-learn | bool | false | If true, feedback-exempt sources may contribute to learning (riskier). |
--feedback-deenforce-cidr | bool | true | CIDR feedback may trigger periodic map scans to remove RL/deny entries. |
--feedback-cidr-every | duration | 30s | Interval for CIDR de-enforcement scans (0 disables). |
--feedback-cidr-max | int | 5000 | Deletion budget per scan (bounds CPU). |
Bootstrap schedule (self-tuning ramp)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--bootstrap | bool | true | Enable bootstrap tuning schedule. |
--bootstrap-window | duration | 336h | Total bootstrap duration (recommend ~14d). |
--bootstrap-phase1-end | duration | 48h | End of phase 1 since bootstrap start. |
--bootstrap-phase2-end | duration | 120h | End of phase 2 since bootstrap start. |
--bootstrap-every1 | duration | 1h | Autotune interval during phase 1. |
--bootstrap-every2 | duration | 6h | Autotune interval during phase 2. |
--bootstrap-every3 | duration | 24h | Autotune interval during phase 3. |
--steady-every | duration | 84h | Autotune interval after bootstrap. |
--bootstrap-k-start | float | 4.0 | Higher k early = more conservative thresholds. |
--bootstrap-k-final | float | 3.5 | Target k at end of bootstrap. |
--bootstrap-max-up1 | float | 0.10 | Phase 1 max relative increase per update. |
--bootstrap-max-down1 | float | 0.02 | Phase 1 max relative decrease per update. |
--bootstrap-max-up2 | float | 0.08 | Phase 2 max relative increase per update. |
--bootstrap-max-down2 | float | 0.03 | Phase 2 max relative decrease per update. |
--bootstrap-max-up3 | float | 0.05 | Phase 3 max relative increase per update. |
--bootstrap-max-down3 | float | 0.05 | Phase 3 max relative decrease per update. |
--bootstrap-alpha1 | float | 0.10 | Smoothing alpha for phase 1. |
--bootstrap-alpha2 | float | 0.15 | Smoothing alpha for phase 2. |
--bootstrap-alpha3 | float | 0.20 | Smoothing alpha for phase 3. |
Autotune (Median + MAD)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--autotune | bool | true | Enable learning of trig-* thresholds. |
--autotune-every | duration | 24h | Tune interval when bootstrap is off. |
--autotune-min-samples | int | 5000 | Minimum samples per feature before applying. |
--autotune-k | float | 3.5 | k for median + k*MAD (higher = more conservative). |
--autotune-max-change | float | 0.05 | Max relative change per update (±). |
--autotune-max-change-up | float | 0 | Max relative increase (0 → use max-change). |
--autotune-max-change-down | float | 0 | Max relative decrease (0 → use max-change). |
--autotune-alpha | float | 0.2 | Smoothing alpha (0 disables smoothing). |
--autotune-floor-pps | float | 100 | Minimum trig-pps floor. |
--autotune-floor-syn | float | 50 | Minimum trig-syn floor. |
--autotune-floor-scan | float | 20 | Minimum trig-scan floor. |
Clean tick gates (anti-poison)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--learn-sev-gt | float | 1.0 | A source counts as “high severity” if sev >= this. |
--learn-frac-gt | float | 0.005 | Max fraction of high-sev sources for a tick to be “clean”. |
--learn-max-sev | float | 0.8 | Only learn from sources with sev <= this. |
--learn-skip-if-blocks | bool | true | Skip learning if any IP is currently BLOCK. |
--learn-max-drop-ratio | float | 0.02 | Skip learning if global drop ratio is above this (0 disables). |
Severity model (triggers + weights)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--trig-pps | float | 0 | PPS trigger (0 → profile/state). |
--trig-syn | float | 0 | SYN/s trigger (0 → profile/state). |
--trig-scan | float | 0 | scan/s trigger (0 → profile/state). |
--w-pps | float | 0 | PPS weight (0 → profile). |
--w-syn | float | 0 | SYN weight (0 → profile). |
--w-scan | float | 0 | scan weight (0 → profile). |
--sev-cap | float | 0 | normalization cap (0 → profile). |
Strike mapping (severity → strikes)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--sev-step1 | float | 1.0 | sev >= step1 adds delta1 strikes. |
--sev-step2 | float | 2.0 | sev >= step2 adds delta2 strikes. |
--sev-step3 | float | 3.0 | sev >= step3 adds delta3 strikes. |
--sev-delta1 | int | 1 | strikes added at step1. |
--sev-delta2 | int | 2 | strikes added at step2. |
--sev-delta3 | int | 3 | strikes added at step3. |
--sev-decay-below | float | 0.25 | if sev < this (and quiet), strikes may decay. |
Level thresholds (strikes → action)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--soft-at | int | 0 | strikes >= soft-at → RATE_SOFT (0 → profile). |
--hard-at | int | 0 | strikes >= hard-at → RATE_HARD (0 → profile). |
--block-at | int | 0 | strikes >= block-at → BLOCK (0 → profile). |
Enforcement action parameters
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--soft-rate | uint64 | 0 | SOFT rate in PPS (0 → profile). |
--soft-burst | uint64 | 0 | SOFT burst tokens (0 → profile). |
--soft-ttl | duration | 0 | SOFT TTL (0 → profile). |
--hard-rate | uint64 | 0 | HARD rate in PPS (0 → profile). |
--hard-burst | uint64 | 0 | HARD burst tokens (0 → profile). |
--hard-ttl | duration | 0 | HARD TTL (0 → profile). |
--block-ttl | duration | 0 | BLOCK TTL (0 → profile). |
--cooldown | duration | 0 | Minimum time between level changes (0 → profile). |
Block safety gate
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--block-min-sev | float | NaN | Only allow BLOCK if severity >= this (NaN → profile; 0 disables). |
--block-min-dur | duration | -1 | Require sev>=block-min-sev for this long (-1 → profile; 0 disables). |
Hysteresis (anti-flap)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--up-need | int | 0 | Require N consecutive “high ticks” before escalating (0 → profile). |
--down-need | int | 0 | Require N consecutive “low ticks” before stepping down (0 → profile). |
--min-hold-soft | duration | 0 | Minimum time in SOFT before stepping down (0 → profile). |
--min-hold-hard | duration | 0 | Minimum time in HARD before stepping down (0 → profile). |
Non-compliance escalation
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--noncomp-at | int | 0 | In HARD: after N non-compliance ticks → BLOCK sooner (0 → profile). |
--noncomp-drop | float | 0 | Count non-compliance if DropRL/s >= this (0 → profile). |
--noncomp-sev | float | 0 | Count non-compliance if severity >= this (0 → profile). |
--noncomp-reset-below | float | 0 | Reset counter if sev < this and DropRL/s==0 (0 → profile). |
Housekeeping (memory bounds)
| Flag | Type | Default | Meaning / notes |
|---|---|---|---|
--prev-ttl | duration | 10m | Forget prev delta snapshot if not seen (bounds memory). |
--state-ttl | duration | 60m | Forget OBSERVE-only state if not seen for this long. |
Example commands
Conservative onboarding
sudo ./kliq \
--profile ziti-controller-bootstrap \
--interval 1s \
--top 100 \
--dry-run=true \
--bootstrap=true
Enable enforcement
sudo ./kliq --dry-run=false
NAT-friendly blocking
sudo ./kliq \
--block-min-sev 3.0 \
--block-min-dur 60s \
--noncomp-at 30 \
--noncomp-drop 50