--- name: monitor-unauthorized description: >- Monitor and report unauthorized WebSocket gateway connections. Parses logs every 30 minutes, detects new and returning IPs, and manages per-IP Discord threads for tracking unauthorized access attempts. metadata: openclaw: emoji: "🚨" --- # Monitor Unauthorized Gateway Connections Detects and tracks unauthorized WebSocket connection attempts to the OpenClaw gateway. Maintains per-IP Discord threads for ongoing tracking. ## Usage ```bash # Cron tool (every 30 minutes) bash skills/monitor-unauthorized/scripts/cron-wrapper.sh ``` ## Architecture ### Scripts (data layer — no Discord/OpenClaw calls) | Script | Purpose | |--------|---------| | `scripts/log-splitter.sh` | Extracts unauthorized connection entries from the gateway log into `/tmp/openclaw/unauthorized-connections.log` (incremental, byte-offset tracked) | | `scripts/check.sh` | Reads new entries from the unauthorized log, categorizes IPs as `new_ips` or `returning_ips`, updates `seen-connections.json` | | `scripts/index-threads.sh` | Manages the thread index — lookup, record, and staleness checks. The index maps IPs to their Discord thread session keys | | `scripts/cron-wrapper.sh` | Orchestrates the above scripts and outputs structured ACTION blocks for the agent | ### Agent (action layer — thread management via OpenClaw tools) The agent parses the cron-wrapper output and handles all Discord thread operations using OpenClaw's built-in session and thread tools. ## Cron Behavior 1. `log-splitter.sh` extracts new unauthorized connection log lines (incremental) 2. `check.sh` processes new entries, outputs JSON with `new_ips[]` and `returning_ips[]` 3. `cron-wrapper.sh` formats ACTION blocks the agent must parse and act on 4. **If no new activity**: script produces no output → agent replies `NO_REPLY` ## Parsing Cron Output The cron-wrapper outputs ACTION blocks. Parse them as follows: ### `ACTION:INDEX_THREADS` The thread index is missing or stale. You must refresh it before processing connections. **Steps:** 1. List all threads in the security Discord channel (`1471181304782389381`) 2. For each thread whose name starts with `🚨` and contains `unauthorized gateway access`: - Extract the IP address from the thread name (format: `🚨 — unauthorized gateway access`) - Record it: `bash scripts/index-threads.sh record "" "" ""` 3. The session key format is: `agent:security:discord:channel:1471181304782389381:thread:` ### `ACTION:NEW_THREAD` A new unauthorized IP was detected. Create a thread and report. **Format:** ``` ACTION:NEW_THREAD IP: --- ---END_ACTION--- ``` **Steps:** 1. Construct the thread name: `🚨 — unauthorized gateway access` 2. Construct the session key: `agent:security:discord:channel:1471181304782389381:thread:` 3. Use `sessions_send` to send the report content (between `---` and `---END_ACTION---`) to that session key. This creates the thread if it doesn't exist. 4. Record the thread in the index: `bash scripts/index-threads.sh record "" ""` ### `ACTION:UPDATE_THREAD` A previously-seen IP has new connection attempts. Update the existing thread. **Format:** ``` ACTION:UPDATE_THREAD IP: SESSION_KEY: --- ---END_ACTION--- ``` **Steps:** 1. If `SESSION_KEY` is provided and non-empty, use it directly 2. If `SESSION_KEY` is empty, construct it: `agent:security:discord:channel:1471181304782389381:thread:🚨 — unauthorized gateway access` 3. Use `sessions_send` to post the update content to that session key 4. Update the index: `bash scripts/index-threads.sh record "" ""` ## Thread Index Management The thread index (`memory/thread-index.json`) maps IPs to their Discord thread session keys. This avoids redundant thread creation and enables reliable updates. ### One-Time Bootstrap On first run (or when the index is missing), the agent must: 1. List all existing threads in channel `1471181304782389381` 2. Identify threads matching the `🚨 — unauthorized gateway access` pattern 3. Record each one via `scripts/index-threads.sh record` ### Ongoing Maintenance - After creating a new thread (`ACTION:NEW_THREAD`), always `record` it in the index - After sending an update (`ACTION:UPDATE_THREAD`), always `record` it to refresh timestamps - The index auto-expires after 24 hours; the cron-wrapper will emit `ACTION:INDEX_THREADS` when a refresh is needed ### Index Script Commands ```bash # Check if index needs refresh bash scripts/index-threads.sh needs-refresh # Returns: "fresh" (exit 1), "stale" (exit 0), or "missing" (exit 0) # Look up a thread by IP bash scripts/index-threads.sh lookup "1.2.3.4" # Returns: JSON with session_key, thread_name, etc. (exit 0 = found, exit 1 = not found) # Record/update a thread entry bash scripts/index-threads.sh record "1.2.3.4" "agent:security:discord:channel:...:thread:..." "🚨 1.2.3.4 — unauthorized gateway access" # Check index status bash scripts/index-threads.sh status # Returns: JSON with entry count, age, staleness ``` ## Storage Files | File | Location | Purpose | |------|----------|---------| | `seen-connections.json` | Skill directory | All IPs ever seen — first_seen, last_seen, total_attempts, metadata | | `authorized-ips.json` | Skill directory | Whitelist — these IPs are silently skipped | | `memory/thread-index.json` | State directory | Maps IPs to Discord thread session keys | | `memory/unauth-splitter-offset` | State directory | Byte offset for log-splitter (gateway log) | | `memory/unauth-check-offset` | State directory | Byte offset for check (unauthorized log) | ## Authorized IPs (Whitelist) Edit `authorized-ips.json` to suppress reporting for known IPs: ```json { "whitelist": ["127.0.0.1", "::1", "localhost", "192.168.1.100"] } ``` ## Log Files | Log | Path | Contents | |-----|------|----------| | Gateway log | `/tmp/openclaw/openclaw.log` | Full OpenClaw gateway log (source) | | Unauthorized log | `/tmp/openclaw/unauthorized-connections.log` | Extracted unauthorized connection entries only | ## What This Monitors Gateway WebSocket authorization failures containing `forwardedFor` IP addresses. Specifically: - Entries with `"forwardedFor"` in the JSON log - Entries with cause `"unauthorized"` or `"pairing-required"` ## Example Agent Flow ``` 1. Cron fires → bash scripts/cron-wrapper.sh 2. Output contains ACTION:INDEX_THREADS → agent lists threads, records them 3. Output contains ACTION:NEW_THREAD for IP 203.0.113.42 → a. agent constructs session key b. agent calls sessions_send with report content c. agent runs: bash scripts/index-threads.sh record "203.0.113.42" "" 4. Output contains ACTION:UPDATE_THREAD for IP 198.51.100.7 → a. agent uses SESSION_KEY from output (or constructs it) b. agent calls sessions_send with update content c. agent runs: bash scripts/index-threads.sh record "198.51.100.7" "" 5. No output → agent replies NO_REPLY ```