{
  "_description": [
    "Bot-Guard Configuration: state-aware connection defense",
    "",
    "Protects against advertising bots that connect, never finish signon, and spray",
    "stray packets / connect floods. Detection is behaviour-based, NOT",
    "name-based: a client that never becomes a fully-joined, Steam-validated player yet",
    "still acts or persists is blocked. See docs/modules/guard.md.",
    "",
    "Why this cannot drop packets: the CS2 transport is encrypted (GameNetworkingSockets)",
    "and the container has no NET_ADMIN, so the guard reads the server log and reacts with",
    "engine console commands (kickid / addip). Every hit is also sent to the node's",
    "Host Guard when one is installed, which turns it into a real packet drop",
    "(docs/modules/guard.md). With the Host Guard installed, action 'log' is enough.",
    "",
    "Default ban tiers: what can never be legitimate bites for 24h (engine-confirmed rcon",
    "bruteforce, malformed packets); behaviour that a broken real client could show for a",
    "moment stays at 30-360 minutes.",
    "",
    "Top-level settings:",
    "  - action: 'log' (dry-run, detect only) | 'kick' | 'block' (kick + temporary ip ban)",
    "  - ban_minutes: default temporary block length in minutes (per-rule override below)",
    "  - cooldown_secs: minimum seconds between two reactions on the same ip",
    "  - stuck_grace_secs: grace period before a client that never reached FULL is suspect",
    "  - whitelist_steamids / whitelist_ips: never actioned (put your admins here)",
    "",
    "IMPORTANT: all blocks are TEMPORARY (self-expiring addip \u003cminutes\u003e). There is no",
    "permanent ban and no persistent denylist on purpose: bots use real accounts on",
    "rotating / CGNAT IPs, so a permanent block would eventually hit an innocent user.",
    "",
    "Each behavior_rule:",
    "  - name: rule id shown in logs",
    "  - type: 'counter' (per client) | 'counter_ip' | 'timeout'",
    "  - match: substring/regex fragment the engine matches against a log line",
    "  - threshold + window_secs: how many hits within how many seconds trip the rule",
    "  - ban_minutes: temporary block length for this rule",
    "  - enabled: false disables the rule without deleting it",
    "  - log: false keeps the rule active but silent: no console line when it fires",
    "",
    "Start on 'log', watch the console for KL-GRD-01 lines, confirm no real player is",
    "flagged, then switch action to 'block'.",
    "",
    "Enable by setting ENABLE_GUARD=1 in the Pterodactyl egg.",
    "",
    "Config location: /home/container/egg/configs/guard.json"
  ],
  "action": "log",
  "ban_minutes": 30,
  "behavior_rules": [
    {
      "ban_minutes": 360,
      "enabled": true,
      "log": true,
      "match": "Forcing client reconnect",
      "name": "reconnect_stuck",
      "threshold": 3,
      "type": "counter",
      "window_secs": 15
    },
    {
      "ban_minutes": 360,
      "enabled": true,
      "log": true,
      "match": "failed delta decode, discarding move msg",
      "name": "move_not_joined",
      "threshold": 5,
      "type": "counter",
      "window_secs": 15
    },
    {
      "ban_minutes": 30,
      "enabled": true,
      "log": true,
      "match": "Stray data packet from host with no connection",
      "name": "stray_no_connection",
      "threshold": 4,
      "type": "counter_ip",
      "window_secs": 10
    },
    {
      "ban_minutes": 30,
      "enabled": true,
      "log": true,
      "match": "Receiving C2S_CONNECT",
      "name": "connect_flood",
      "threshold": 6,
      "type": "counter_ip",
      "window_secs": 10
    },
    {
      "ban_minutes": 1440,
      "enabled": true,
      "log": true,
      "match": "for rcon hacking attempts",
      "name": "rcon_bruteforce",
      "threshold": 1,
      "type": "counter_ip",
      "window_secs": 10
    },
    {
      "ban_minutes": 1440,
      "enabled": true,
      "log": true,
      "match": "Invalid lead/length byte",
      "name": "malformed_packet",
      "threshold": 2,
      "type": "counter_ip",
      "window_secs": 10
    },
    {
      "ban_minutes": 60,
      "enabled": true,
      "log": true,
      "match": "Sending keepalive",
      "name": "stuck_no_full",
      "threshold": 3,
      "type": "timeout",
      "window_secs": 10
    }
  ],
  "cooldown_secs": 30,
  "stuck_grace_secs": 12,
  "version": "1.2.6",
  "whitelist_ips": [],
  "whitelist_steamids": []
}
