#!/usr/bin/env bash # edr-isolation-watch.sh — interim EDR auto-isolation alerter. # # Polls Datto EDR for detections whose automated response included `isolate-host` # (i.e. a machine was auto-isolated / "quarantined") and posts each NEW one to the # private #dev-alerts channel (Mike + Howard). Runs as a plain scheduled job — no # LLM, no tokens. Retire once the GuruRMM EDR webhook integration (Feature 6) lands. # # Schedule (every 10 min), e.g. cron: # */10 * * * * bash /path/to/claudetools/.claude/scripts/edr-isolation-watch.sh >/dev/null 2>&1 # # State: .edr-watch-state.json (gitignored) holds already-alerted alert IDs so each # isolation is announced exactly once, even though Mike may un-isolate before the poll. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" EDR="bash $ROOT/.claude/scripts/py.sh $ROOT/.claude/skills/datto-edr/scripts/edr.py" DM="bash $ROOT/.claude/scripts/discord-dm.sh" export STATE="${STATE:-$ROOT/.claude/scripts/.edr-watch-state.json}" TARGET="dev" # #dev-alerts = Mike + Howard, private # Pull last 24h of detections (list includes responseData) and emit any NEW # isolate-host events as one pipe-delimited line each; update state in-place. new_events="$($EDR --json detections --days 1 --limit 500 2>/dev/null | python -c ' import sys, json, os state_path = os.environ["STATE"] try: rows = json.load(sys.stdin) rows = rows if isinstance(rows, list) else rows.get("data", rows.get("alerts", [])) except Exception: sys.exit(0) # API hiccup: emit nothing, do not churn state try: seen = set(json.load(open(state_path))) except Exception: seen = set() fresh = [] for a in rows: rd = a.get("responseData") or [] names = [r.get("name") for r in rd] if isinstance(rd, list) else [] if "isolate-host" not in names: continue aid = a.get("id") if not aid or aid in seen: continue seen.add(aid) fresh.append("|".join(str(a.get(k, "")) for k in ("id", "hostname", "organizationName", "severity", "sourceName", "eventTime"))) # keep state bounded json.dump(sorted(seen)[-500:], open(state_path, "w")) print("\n".join(fresh)) ' 2>/dev/null)" [ -z "$new_events" ] && exit 0 while IFS='|' read -r aid host org sev rule ts; do [ -z "$aid" ] && continue msg="[EDR AUTO-ISOLATION] **$host** was auto-isolated by Datto EDR - Client: $org - Rule: $rule (severity: $sev) - Time: $ts - This machine was cut off the network by an EDR automated response. Verify it is intended before restoring. - Console: https://azcomp4587.infocyte.com (alert id: $aid)" if [ "${DRY_RUN:-0}" = "1" ]; then printf '[DRY_RUN] would post to #%s:\n%s\n\n' "$TARGET" "$msg" continue fi if ! printf '%s' "$msg" | $DM "$TARGET" 2>/dev/null; then bash "$ROOT/.claude/scripts/log-skill-error.sh" "edr-isolation-watch" "discord post failed for $host ($aid)" 2>/dev/null || true fi done <<< "$new_events"