Skip to content

Configuration Reference

Complete YAML reference for /etc/openshield/openshield.yaml. Every field is listed with its Go type, default value as set in defaults.go, valid range, and a description of what it controls.

Fields marked 🔄 Runtime-Safe can be updated via the Unix socket without restarting the XDP program. Fields marked 🔒 Requires Reload need openshield fix && openshield load to take effect.

Configuration File Location

The active config lives at /etc/openshield/openshield.yaml. An annotated example ships at /opt/openshield/share/openshield.example.yaml.

Run openshield config to generate a fresh defaults file.

Top-Level

FieldTypeDefaultRangeDescriptionSafe?
interfacestring"eno1"any netdev nameNetwork interface for XDP attachment🔒
xdp_modestring"auto"auto / native / generic / offloadXDP attachment mode🔒

static — Rate Thresholds & Scoring

FieldTypeDefaultRangeDescriptionSafe?
static.enabledbooltruetrue / falseEnable per-IP rate threshold checks🔄
static.pps_thresholdint850110,000,000Max packets/s per IP before suspicion🔄
static.bps_thresholdint8912896102410,737,418,240Max bytes/s per IP before suspicion (~8.5 MiB/s)🔄
static.tcp_pps_thresholdint680110,000,000Max TCP packets/s per IP🔄
static.udp_pps_thresholdint425110,000,000Max UDP packets/s per IP🔄
static.icmp_pps_thresholdint85110,000,000Max ICMP packets/s per IP🔄
static.syn_pps_thresholdint170110,000,000Max SYN packets/s per IP🔄
static.suspicion_thresholdint100110,000Score at which IP is banned🔄
static.ban_durationint3600186,400How long bans last (seconds)🔄
static.pps_scoreint2001000Score added for PPS violation🔄
static.bps_scoreint2001000Score added for BPS violation🔄
static.tcp_pps_scoreint1501000Score added for TCP PPS violation🔄
static.udp_pps_scoreint1501000Score added for UDP PPS violation🔄
static.icmp_pps_scoreint2501000Score added for ICMP PPS violation🔄
static.syn_pps_scoreint3001000Score added for SYN PPS violation🔄
static.suspicion_decayfloat640.50.01.0Score retention per window (0.5 = keep 50%)🔄
static.rate_limit_modestring"threshold"threshold / token_bucketRate limiting algorithm🔄
static.token_rateuint320010,000,000Tokens refilled per second per IP (token_bucket mode)🔄
static.token_burstuint3200100,000,000Max burst tokens per IP (token_bucket mode)🔄
static.enable_connection_trackingbooltruetrue / falseDrop blind SYN-ACK/RST/ACK (no prior SYN seen)🔄
static.ct_syn_timeout_secint30003600Seconds a connection stays proven after its SYN (0=disable). Keep ≥ app keepalive interval🔄
static.ct_server_port_maxint32768065535Only track connections to destination ports ≤ this (0 = track all ports; breaks outbound traffic like apt/curl)🔄
static.ct_established_exemptbooltruetrue / falseExempt sources with a proven TCP session (data within ct_syn_timeout_sec of SYN) from PPS/BPS/TCP_PPS scoring. SYN-rate, conn-rate, UDP/ICMP scoring and attack-mode caps still apply. Works even when enable_connection_tracking is false (v2.0+)🔄
static.port_thresholds[]PortThreshold[]max 8 entriesPer-port/range overrides replacing global PPS/BPS thresholds, peacetime AND attack mode (v2.0+) — see below🔄
static.star_duration_multiplicators[]int[1,2,4,8,16,32]array of 6 intsBan duration multipliers per star level (repeat-offender escalation)🔄
static.star_decay_secondsint3600186,400Seconds before star rating decays by 1🔄
static.ban_subnets[]string[]CIDR stringsHardcoded subnet bans (e.g., ["10.0.0.0/8"])🔄
static.auto_subnet_banboolfalsetrue / falseAutomatically ban /24 subnets when too many single-IP bans occur🔄
static.auto_subnet_prefixes[]int[24]prefix lengthsCIDR prefix lengths for auto-subnet-ban🔄
static.subnet_ban_durationint7200186,400Duration for auto subnet bans (seconds)🔄

Port Threshold Entry Fields

Each entry in static.port_thresholds (max 8):

FieldTypeDescription
portsstringSingle port ("443") or inclusive range ("8000-9000") — matched on destination port
pps_thresholdintPackets/s limit for matching traffic (0 = inherit global pps_threshold)
bps_thresholdintBytes/s limit for matching traffic (0 = inherit global bps_threshold)

At least one of the two thresholds must be non-zero. Overrides apply in peacetime and during attack mode.

Scoring Model

Each per-second evaluation window, the suspicion score is multiplied by suspicion_decay (emulating exponential decay). An IP is banned when its score reaches suspicion_threshold.

validation — Packet Validation

FieldTypeDefaultRangeDescriptionSafe?
validation.filter_privateboolfalsetrue / falseDrop packets with private/bogon source IPs (enabled per-profile by stricter presets)🔄
validation.filter_bogonbooltruetrue / falseDrop packets with bogon (unallocated) source IPs🔄
validation.filter_bogus_tcpbooltruetrue / falseDrop impossible TCP flag combinations (e.g., SYN+FIN)🔄
validation.filter_malformedbooltruetrue / falseDrop malformed headers (invalid lengths, truncated options)🔄
validation.drop_fragmentsboolfalsetrue / falseDrop fragmented IP packets (MF flag or non-zero fragment offset). Off by default — enabling can break legitimate large UDP/DNS traffic🔄
yaml
validation:
  filter_private: true
  filter_bogon: true
  filter_bogus_tcp: true
  filter_malformed: true
  drop_fragments: false

dynamic — Anomaly Detection & Attack Response

FieldTypeDefaultRangeDescriptionSafe?
dynamic.enabledbooltruetrue / falseEnable attack mode and new source detection🔄
dynamic.new_source_limitint1001100,000New unique IPs/s before flood mode🔄
dynamic.new_source_ban_durationint3013,600Ban duration for new source flood🔄
dynamic.attack_threshold_multiplierfloat640.50.11Threshold multiplier during attack (0.5 = 50%)🔄
dynamic.panic_pps_rateint2000000100,000,000Per-CPU PPS that triggers panic circuit breaker (0=disabled, drops all further map lookups)🔄
dynamic.panic_drop_ratioint800100Percentage of packets to drop when in panic mode (100 = drop all before map lookups)🔄
dynamic.attack_pps_thresholdint001,000,000,000Global PPS trigger for attack state (0=disabled, uses baseline)🔄
dynamic.attack_bps_thresholdint001,000,000,000,000Global BPS trigger for attack state (0=disabled)🔄
dynamic.spike_percentageint2001010,000% above baseline that triggers spike (200 = 3x)🔄
dynamic.baseline_mad_kfloat643.5020MAD multiplier added to the spike trigger band (0=median×spike% only)🔄
dynamic.spike_recovery_timeint101600Seconds below recovery factor before clearing🔄
dynamic.attack_per_ip_ppsint100001,000,000Hard per-source pps cap while an attack is active (0=off). Drops flooders at XDP instantly — rotating spoofed sources never live long enough to be scored🔄
dynamic.attack_port_ppsint1000001,000,000Aggregate pps cap per destination port while an attack is active (0=off). Rotation-proof: throttles the attacked port as a whole regardless of how many source IPs the flood rotates through. Legit traffic on that port is throttled (not banned) until the attack clears🔄
dynamic.spike_recovery_factorfloat640.701Fraction of spike threshold below which attack state clears (< 1.0)🔄
dynamic.attack_trigger_timeint3160Consecutive seconds above threshold before attack state🔄
dynamic.attack_max_durationint3600086,400Hard cap on attack state seconds (0=disabled)🔄
dynamic.attack_warmup_secint200600Seconds after loader start with no attack declaration (0=disabled)🔄
dynamic.attack_min_ppsint100001,000,000,000Absolute floor for the attack PPS trigger threshold🔄
dynamic.attack_min_bpsint104857601,000,000,000,000Absolute floor for the attack BPS trigger threshold🔄
dynamic.baseline_alpha_minfloat640.0501Minimum EMA alpha (adaptive floor)🔄
dynamic.baseline_alpha_maxfloat640.501Maximum EMA alpha (adaptive ceiling)🔄
dynamic.baseline_alpha_variance_scalefloat640.101How much variance adjusts alpha (0=none, 1=max)🔄
dynamic.panic_global_pps_thresholdint50000000100,000,000Total PPS across all CPUs that triggers coordinated panic (0=disabled)🔄
dynamic.panic_coordination_enabledbooltruetrue / falseEnable userspace cross-CPU panic coordination🔄
dynamic.dns_amplification_enabledboolfalsetrue / falseDrop DNS amplification responses (sport=53, QR=1, large payload)🔄
dynamic.dns_amplification_payload_minint512065,535Minimum UDP payload bytes for DNS amp detection🔄
dynamic.baseline_enabledbooltruetrue / falseMaster switch for ALL learned detection (baseline learning, seasonal thresholds, changepoint onset, behavior clustering). Off = static attack_min/overrides only (applies immediately)🔄
dynamic.attack_port_bpsint2500000001,099,511,627,776Aggregate bytes/sec cap per destination port during attacks / early spike (0=off). Catches jumbo-packet floods under the pps cap🔄
dynamic.attack_icmp_ppsint1000010,000,000Aggregate ICMP packets/sec cap while an attack is declared (0=off, default 1000). Legit ICMP is tens of pps🔄
dynamic.attack_udp_ppsint1000000100,000,000Aggregate UDP packets/sec cap (attack mode or early spike trigger; 0=off, default 100000). Carpet-bomb answer; protected sources exempt🔄
dynamic.nic_tuningboolfalsetrue / falseHost-wide NIC tuning (throughput over latency; applies immediately)🔄
dynamic.udp_resp_enabledbooltruetrue / falseAnti-amplification residual heuristic: sustained excess reply rates from privileged-sport sources lose the outbound-response exemption🔄
dynamic.udp_resp_factorint4264Sustained rate above factor × early rate counts as excess🔄
dynamic.udp_resp_window_secint102300Consecutive excess seconds before the response exemption is revoked🔄
dynamic.synproxy_companion_autoboolfalsetrue / falseAuto-insert/remove the netfilter SYNPROXY companion rule trio while the SYN-cookie path is engaged (applies immediately)🔄
dynamic.baseline_windowint60Seconds to build baseline⚙️
dynamic.baseline_update_intervalint5Seconds between baseline updates⚙️
dynamic.baseline_alphafloat640.1EMA smoothing factor⚙️

L7 Drop Signature Fields

Each entry in l7_drop_signatures:

FieldTypeDefaultDescription
namestringHuman-readable rule name
protocolstringtcp or udp
portintPort to match (source or dest)
port_is_srcboolMatch source port instead of dest
offsetintByte offset into payload
patternstringHex pattern to match at offset
maskstringBitmask applied before comparison
min_payloadintMinimum payload length to trigger
max_payloadintMaximum payload length to trigger

whitelist — Trusted IPs

FieldTypeDefaultRangeDescriptionSafe?
whitelist.enabledbooltruetrue / falseEnable whitelist (bypass all mitigation)🔄
whitelist.ips[]string[]IPv4 / IPv6 addressesTrusted IP list🔄
yaml
whitelist:
  enabled: true
  ips:
    - 10.0.0.1
    - 2001:db8::1

telemetry — Monitoring

FieldTypeDefaultRangeDescriptionSafe?
telemetry.poll_intervalint1160Seconds between collector map reads🔒
telemetry.event_rate_limitint100110,000Max events/s emitted to ring buffer🔄
telemetry.top_offenders_countint2011000Top N IPs shown in TUI/stats🔄
telemetry.log_levelstring"info"debug / info / warn / errorLog verbosity🔄
telemetry.snapshot_intervalint1160Seconds between stat snapshots🔄
telemetry.attack_shareboolfalsetrue / falseShare anonymized attack fingerprints with PingLess after attacks (type/rates/duration/port classes/source count — never IPs)🔄
telemetry.attack_share_endpointstring""URLOverride fingerprint endpoint (empty = PingLess telemetry on the license server)🔄

updates — Auto-Update Channel (v2.16+)

FieldTypeDefaultRangeDescriptionSafe?
updates.enabledbooltruetrue / falseCheck for new releases every 6h + TUI update badge🔄
updates.autobooltruetrue / falseInstall new releases unattended (licensed installs; signed metadata + hash-verified zip + automatic rollback)🔄
updates.endpointstring""URLOverride the updates worker (empty = official channel)🔄

See Upgrade for the trust chain.

registry — Attached IPs (v2.17+)

Tracks which destination IPs belong to this host. Informational only — see Attached IPs.

FieldTypeDefaultRangeDescriptionSafe?
registry.enabledbooltruetrue / falseLearn which destination IPs belong to this host (wire-observed + local addresses; informational only, applies immediately)🔄
registry.inactive_daysint141365Days without observed traffic before an auto-learned attached IP is reaped (manual entries and pools never reap; applies immediately)🔄
registry.auto_min_packetsint10011,000,000Cumulative packets a wire-observed destination must exceed before it auto-attaches (noise filter; applies immediately)🔄

blackhole — Per-Tenant Blackhole (v2.17+)

Total per-destination drop with established-connection survival — see Blackhole. License-gated.

FieldTypeDefaultRangeDescriptionSafe?
blackhole.enabledbooltruetrue / falseArm the kernel blackhole stage (per-destination total drop; entries are kept either way, applies immediately). Manage: openshield blackhole🔄
blackhole.auto_enabledboolfalsetrue / falseAutomatically blackhole attached destinations whose inbound rate sustains over the auto_pps/auto_bps triggers (applies immediately)🔄
blackhole.auto_ppsint00100,000,000Inbound packets/sec to one attached destination that arms auto-blackhole (0 = pps leg off; applies immediately)🔄
blackhole.auto_bpsint00100,000,000,000Inbound bytes/sec to one attached destination that arms auto-blackhole (0 = bps leg off; applies immediately)🔄
blackhole.auto_sustain_secint51300Consecutive seconds over the trigger before an auto-blackhole engages (flap filter; applies immediately)🔄
blackhole.auto_duration_secint3001086,400Lifetime of one auto-blackhole entry in seconds (extended while the trigger still holds; applies immediately)🔄
blackhole.grace_minutesint1011,440Minutes an established-source exemption to a blackholed destination survives without re-proof (applies immediately)🔄

maps — BPF Map Sizing

FieldTypeDefaultRangeDescriptionSafe?
maps.ip_stats_maxint262,144100010,000,000Max entries in per-IP stats LRU (v2.16 default)🔒
maps.ban_maxint4,000,000100010,000,000Max entries in ban LRU (v2.16 default)🔒
maps.whitelist_maxint50,0001001,000,000Max entries in whitelist map (v2.16 default)🔒
maps.event_buffer_sizeint262,144 (256 KB)4096268,435,456Ring buffer size in bytes🔒
maps.bloom_filter_enabledbooltruetrue / falseUse Bloom filter fast-path for whitelist lookups🔄
maps.bloom_filter_sizeint150,000100010,000,000Number of entries in the Bloom filter map🔄

Bloom Filter

When enabled, whitelisted IPs are hashed into a Bloom filter in the BPF bloom_map (a regular ARRAY map used as a bit-vector with 3 hash functions and 64 bits per entry). Before performing a full bpf_map_lookup_elem on the whitelist HASH map, the XDP program first checks the Bloom filter — a negative result means "definitely not whitelisted" in ~60-100ns, saving a full hash map lookup.

alerter — Webhook Alerts

FieldTypeDefaultRangeDescriptionSafe?
alerter.enabledboolfalsetrue / falseEnable Discord webhook alerts🔄
alerter.webhook_urlstring""valid Discord webhook URLWebhook endpoint URL🔄
alerter.events[]string[]event type stringsEvents to alert on (empty = all)🔄
alerter.graph_enabledbooltruetrue / falseAttach traffic graph to attack-end alerts🔄
alerter.show_banned_ipsboolfalsetrue / falseInclude banned IP list inline in ban alerts (txt attached for large batches)🔄
alerter.geo_breakdownbooltruetrue / falseContinent/country share of banned IPs (requires GeoIP db)🔄
alerter.attack_updatesbooltruetrue / falseProgress embeds while an attack is ongoing🔄
alerter.generic_webhook_urlstring""http(s) URLAlso POST every event as a JSON envelope ({product, version, event, host, timestamp, data}) to this endpoint (v2.12.0+); same pacing/rate-limit handling as Discord, embeds/attachments stay Discord-only🔄

See the Alerter docs for webhook format and event types.

behavior — Adaptive Behavior Engine (v2.0+)

FieldTypeDefaultRangeDescriptionSafe?
behavior.enabledbooltruetrue / falseMaster switch: learns per-port baselines and flags anomalous source clusters🔒
behavior.auto_blockbooltruetrue / falseAuto-ban members of malicious clusters (≥85% confidence) for 1 hour. Default since v2.1.0; set false for report-only mode🔄

The engine freezes learning while an attack is declared, so it mainly catches slow-burn botnets. Review clusters in the TUI behavior tab or via openshield behavior.

metrics — HTTP Metrics API (v2.0+)

FieldTypeDefaultRangeDescriptionSafe?
metrics.enabledboolfalsetrue / falseHTTP JSON endpoint serving everything the TUI shows (default: off)🔄
metrics.listenstring"127.0.0.1:9100"host:portBind address (0.0.0.0:9100 = remote)🔄
metrics.api_keystringrandom per installmin 8 charsBearer token; auto-generated if empty. Manage via openshield key set / key regen (hot-applied)🔄
metrics.rate_limit_per_secint100100,000Max requests/s per source IP (0 = unlimited)🔄
metrics.whitelist[]string[]IPs/CIDRsClient allowlist (empty = any IP, key still required)🔒

Full guide: Metrics API.

auto_fetch — Remote Blocklist Feeds (v2.2+)

FieldTypeDefaultRangeDescriptionSafe?
auto_fetch.enabledbooltruetrue / falsePeriodically download threat-intel IP feeds into the ban maps (TUI access tab: f)🔄
auto_fetch.interval_secint360060Seconds between fetch cycles; fetched bans expire after 2× this (floor 10 min)🔄
auto_fetch.modestring"vps"vps / dedicateddedicated skips major cloud/hosting provider ranges (AWS, Azure, GCP, Cloudflare, OVH, Hetzner, …) so hosted VPS clients keep outbound connectivity🔄
auto_fetch.categories[]stringc2, botnets, malware, scanners, abuse, bruteforce, ssh-attackers, credential-stuffing, web-exploit-scanners, exploited-infrastructure, high-risk-networkscategory slugsOfficial openshield-blocklists categories to pull🔄
auto_fetch.urls[]string[]http(s) URLsExtra raw list URLs (one IP/CIDR per line, # comments OK)🔄
auto_fetch.never_block[]string[]IPs / CIDRsExempt from fetched bans only — NOT a firewall whitelist; entries are still scored and mitigated🔄
auto_fetch.provider_urls[]string[]http(s) URLsExtra provider-range sources for dedicated mode (plain-text lists or AWS/GCP-style JSON). Empty = built-in defaults🔄

Full guide: Auto-Fetch Blocklists.

reports — Scheduled Reports

FieldTypeDefaultRangeDescriptionSafe?
reports.enabledboolfalsetrue / falseEnable scheduled network analysis reports🔄
reports.webhook_urlstring""webhook URLDelivery target (falls back to alerter.webhook_url)🔄
reports.dispatch_timestring"00:00"HH:MMLocal time for the daily report🔄
reports.geo_breakdownbooltruetrue / falseTop attacker + top legit-user countries in daily/weekly/monthly reports (requires GeoIP data; v2.2+)🔄

pcap — Attack Forensics Capture

FieldTypeDefaultRangeDescriptionSafe?
pcap.enabledbooltruetrue / falsePacket capture during attacks (requires tcpdump)🔄
pcap.modestring"rolling"attack / rollingattack = only during attacks, rolling = continuous🔄

Since v2.0, forensics bundles also include config_snapshot.txt (mitigation config at attack start, secrets stripped) and config_changes.txt (timestamped config changes during the attack).

forensics — Storage, Disk Cap & Collection Switch (v2.10.0+)

FieldTypeDefaultRangeDescriptionSafe?
forensics.dirstring/var/lib/openshield/attacksabsolute pathWhere attack forensics (reports, pcaps, history) are stored. Applies at load; existing data is not moved⚠️ restart
forensics.collectbooltruetrue / falseMaster switch for forensics collection. The disk-pressure guard can auto-pause/resume; a manual false is never overridden🔄
forensics.max_size_mbint307201024–1048576Disk cap for all forensics data. Over cap → oldest completed attack bundles deleted (whole dirs only) until cleanup_percent of the cap is freed🔄
forensics.cleanup_percentint5010–90How much of max_size_mb to free when the cap is hit🔄

The active attack's bundle is never deleted. If a live capture alone exceeds the cap, collection halts (pcap stops mid-attack, future attacks skip forensics) and auto-resumes once usage drops back under the cap-minus-cleanup level. Status is on GET /metrics/forensics.

ovh — Edge Mitigation Module (v2.11.0+)

Optional module for OVH-hosted servers: banned attacker IPs are pushed to OVH's network firewall (VAC) and dropped at the edge. Best configured via the installer (guided credential + service + IP setup).

FieldTypeDefaultRangeDescriptionSafe?
ovh.enabledboolfalsetrue / falsePush banned sources to OVH's edge firewall🔄
ovh.endpointstring"ovh-eu"ovh-eu / ovh-us / ovh-caOVH API region⚠️ restart
ovh.application_key / application_secret / consumer_keystring""API credentials (from the installer flow)⚠️ restart
ovh.service_namestring""Selected dedicated/VPS service⚠️ restart
ovh.protected_ipslist[]IPs of the serviceIPs edge rules are pushed for⚠️ restart
ovh.modestring"confirmed"confirmed / allconfirmed = verified-heavy/repeat offenders only; all = every detected attacker🔄
ovh.max_rules_per_ipint201–20Edge rules per IP (OVH hard limit: 20; worst offenders first)🔄
ovh.requests_per_secfloat20.5–10OVH API pacing (429s back off automatically)🔄
ovh.sync_interval_secint3010–3600Reconcile period🔄

Feed/blocklist bans are never pushed (they are preemptive, not detected attackers). Status on GET /metrics/ovh.

tenant — Per-Tenant Visibility (v2.12.0+)

FieldTypeDefaultRangeDescriptionSafe?
tenant.modestring"auto"auto / on / offPer-hosted-IP attack state (normal / elevated / under_attack) in the TUI targets panel, attack reports, and GET /metrics/targets. auto activates only on multi-IP dedicated hosts — single-IP VPS installs are never treated as dedicated🔄

geoip — Geo Blocking

FieldTypeDefaultRangeDescriptionSafe?
geoip.enabledbooltrue (since v2.2.0; was false)true / falseMaxMind GeoLite2 geo-blocking and attack/report geo analytics🔒
geoip.license_keystring""MaxMind keyLicense key for GeoLite2 downloads — empty uses the built-in default key🔄
geoip.db_pathstring"/var/lib/openshield/GeoLite2-City.mmdb"pathGeoLite2 database location🔒
geoip.modestring"block"block / allowBlock listed countries, or allow only listed countries🔄
geoip.countries[]string[]ISO codesCountry list applied per mode (managed by the TUI geo tab, key 0)🔄
geoip.update_hoursint168hoursDatabase update interval🔄

Full guide: Geo Blocking.

license — Licensing

FieldTypeDefaultDescription
license.keystring""License key from your Altis dashboard (PL-XXXX-XXXX-XXXX-XXXX)
license.server_urlstring"https://pingless-license-system.vercel.app"License server base URL (override for self-hosting)
license.product_slugstring"openshield-xdp"Product slug registered in the Altis dashboard
license.public_keystring(embedded)Ed25519 public key for offline signature verification
license.cache_pathstring"/var/lib/openshield/license.json"Local cache of the last successful license response
license.check_intervalint3600Seconds between periodic re-checks (0 disables)
license.grace_periodint86400Seconds premium features survive a validation failure
license.fqdnstring""Optional FQDN sent for access-rule evaluation
license.enforcebooltrueSet false only for emergency debugging
license.hard_failbooltrueRefuse to load / auto-unload when the license is invalid or missing

Complete Example

yaml
interface: eno1
xdp_mode: auto

static:
  enabled: true
  pps_threshold: 850
  bps_threshold: 8912896
  tcp_pps_threshold: 680
  udp_pps_threshold: 425
  icmp_pps_threshold: 85
  syn_pps_threshold: 170
  suspicion_threshold: 100
  ban_duration: 3600
  pps_score: 20
  bps_score: 20
  tcp_pps_score: 15
  udp_pps_score: 15
  icmp_pps_score: 25
  syn_pps_score: 30
  suspicion_decay: 0.5
  rate_limit_mode: threshold
  token_rate: 0
  token_burst: 0
  enable_connection_tracking: true
  ct_syn_timeout_sec: 300
  ct_server_port_max: 32768
  ct_established_exempt: true
  port_thresholds: []
  star_duration_multiplicators: [1, 2, 4, 8, 16, 32]
  star_decay_seconds: 3600
  ban_subnets: []
  auto_subnet_ban: false
  auto_subnet_prefixes: [24]
  subnet_ban_duration: 7200

validation:
  filter_private: true
  filter_bogon: true
  filter_bogus_tcp: true
  filter_malformed: true
  drop_fragments: false

dynamic:
  enabled: true
  baseline_window: 60
  baseline_update_interval: 5
  baseline_alpha: 0.1
  baseline_alpha_min: 0.05
  baseline_alpha_max: 0.50
  baseline_alpha_variance_scale: 0.1
  spike_percentage: 200
  spike_recovery_factor: 0.7
  spike_recovery_time: 10
  new_source_limit: 100
  new_source_ban_duration: 30
  attack_threshold_multiplier: 0.5
  attack_pps_threshold: 0
  attack_bps_threshold: 0
  attack_min_pps: 1000
  attack_min_bps: 1048576
  attack_trigger_time: 3
  attack_max_duration: 300
  attack_per_ip_pps: 1000
  attack_port_pps: 10000
  panic_pps_rate: 200000
  panic_drop_ratio: 80
  panic_global_pps_threshold: 5000000
  panic_coordination_enabled: true
  dns_amplification_enabled: true
  dns_amplification_payload_min: 512
  udp_amplification_enabled: true
  udp_amp_ports: [53, 123, 1900, 11211, 17, 19, 520, 69]
  udp_amp_payload_min: [512, 90, 256, 50, 50, 50, 50, 50]
  syn_fin_ratio_enabled: true
  syn_fin_ratio_threshold: 100
  entropy_spoof_enabled: true
  entropy_spoof_threshold: 12
  ttl_anomaly_enabled: true
  ttl_expected: 64
  ttl_tolerance: 5
  pkt_anomaly_enabled: true
  pkt_size_min_threshold: 64
  pkt_size_max_threshold: 1024
  conn_rate_enabled: true
  conn_rate_limit: 5000
  auto_escalation_enabled: true
  auto_escalation_threshold: 5
  mac_filter_enabled: false
  mac_filter_mode: 0
  mac_filter_entries: []
  synproxy_enabled: false
  l7_drop_signatures: []

whitelist:
  enabled: true
  ips: []

telemetry:
  poll_interval: 1
  event_rate_limit: 100
  top_offenders_count: 20
  log_level: info
  snapshot_interval: 1

maps:
  ip_stats_max: 100000
  ban_max: 50000
  whitelist_max: 10000
  event_buffer_size: 262144
  bloom_filter_enabled: true
  bloom_filter_size: 150000

alerter:
  enabled: false
  webhook_url: ""
  generic_webhook_url: ""
  events: []
  graph_enabled: true
  show_banned_ips: false
  geo_breakdown: true
  attack_updates: true

behavior:
  enabled: true
  auto_block: true

metrics:
  enabled: false
  listen: "127.0.0.1:9100"
  api_key: ""                # auto-generated on first load
  rate_limit_per_sec: 10
  whitelist: []

reports:
  enabled: false
  webhook_url: ""
  dispatch_time: "00:00"
  geo_breakdown: true

pcap:
  enabled: true
  mode: rolling

geoip:
  enabled: true
  license_key: ""        # empty = built-in default key
  db_path: "/var/lib/openshield/GeoLite2-City.mmdb"
  mode: block
  countries: []
  update_hours: 168

auto_fetch:
  enabled: true
  interval_sec: 3600
  mode: vps
  categories:
    - c2
    - botnets
    - malware
    - scanners
    - abuse
    - bruteforce
    - ssh-attackers
    - credential-stuffing
    - web-exploit-scanners
    - exploited-infrastructure
    - high-risk-networks
  urls: []
  never_block: []
  provider_urls: []