diff --git a/.claude/memory/project_edr_rmm_autoisolation_fp.md b/.claude/memory/project_edr_rmm_autoisolation_fp.md index b94edf1a..541e24f2 100644 --- a/.claude/memory/project_edr_rmm_autoisolation_fp.md +++ b/.claude/memory/project_edr_rmm_autoisolation_fp.md @@ -11,6 +11,10 @@ Incident 2026-07-07: **vwp-qbs** (Valley Wide Plastering QuickBooks server) was **This is SYSTEMIC, not a one-off.** The same rule fired + attempted auto-isolate on **tps-tina** (The Prairie Schooner) the same day — different GuruRMM script, different client. Per-command-line suppression is whack-a-mole. Note: the isolate-host response only actually cuts a host off where its endpoint policy has isolation enabled (vwp-qbs got isolated; tps-tina fired the same alert but `extensionSuccess=None`, stayed online). +**FLEET-WIDE FIX LIVE (2026-07-07):** Datto EDR suppression rule **`3365e79a`** "GuruRMM origin - Exfil-over-HTTP (fleet-wide FP)" — matches Rule Name = Exfiltration Over HTTP Protocol + Grand Parent = `gururmm-agent.exe`, org/loc empty = ALL clients. Suppresses this one rule for any GuruRMM-launched process fleet-wide (present + future scripts); every other rule still fires on RMM-origin. Created in the CONSOLE (by Howard) after the API create path proved unsafe. The two earlier narrow per-command-line rules (`e4dd55bf`, `7ea2a577`) are now redundant subsets — harmless, left in place. + +**API FOOTGUN (do NOT create suppressions via `raw POST` on this tenant):** `POST /SuppressionRules` forces `active:true` and auto-creates an EMPTY match-everything version → a live "suppress all" window (I hit this with `7c36b89f`, deleted within ~2 min). `GET`/`PATCH /SuppressionRules/{id}` 400 ("undefined is not valid JSON"); `DELETE /{id}` works. Create suppressions in the CONSOLE until an atomic API create is verified. Details in `.claude/skills/datto-edr/references/api-reference.md`. + **What's deployed (done):** - Narrow suppression rule (Datto EDR, id `e4dd55bf`) for VWP's exact script: matches Process Command Line + Grand Parent = `gururmm-agent.exe`. Does NOT blind other PowerShell. API schema for suppression is now in `.claude/skills/datto-edr/references/api-reference.md`. - **EDR isolation watcher**: `.claude/scripts/edr-isolation-watch.sh` + `register-edr-watcher.ps1`, registered as Windows Scheduled Task **"ClaudeTools - EDR Isolation Watcher"** on Howard-Home (runs every 10 min, posts new auto-isolations to #dev-alerts — see [[feedback_fire_dev_alerts_for_client_work]]). Plain script, zero tokens. State file `.edr-watch-state.json` (gitignored) dedupes; seeded with the vwp-qbs + tps-tina event ids so it stays silent for the already-triaged ones. Retire when GuruRMM EDR webhook (Feature 6) lands. diff --git a/.claude/scripts/gps-rmm-progress-hidden.vbs b/.claude/scripts/gps-rmm-progress-hidden.vbs new file mode 100644 index 00000000..4194acfc --- /dev/null +++ b/.claude/scripts/gps-rmm-progress-hidden.vbs @@ -0,0 +1,5 @@ +' gps-rmm-progress-hidden.vbs — launch the GPS->RMM progress check with no visible window. +' Run via the GPS-RMM-Progress scheduled task through wscript.exe (GUI host, no console), +' which starts bash with window style 0 (hidden) so the daily check no longer flashes a +' console window on the desktop. Runtime env is identical to a direct bash launch. +CreateObject("WScript.Shell").Run """C:\Program Files\Git\bin\bash.exe"" -lc ""cd /c/claudetools && bash .claude/scripts/gps-rmm-progress-check.sh""", 0, False diff --git a/.claude/skills/datto-edr/references/api-reference.md b/.claude/skills/datto-edr/references/api-reference.md index 272bba41..790b81b5 100644 --- a/.claude/skills/datto-edr/references/api-reference.md +++ b/.claude/skills/datto-edr/references/api-reference.md @@ -138,6 +138,17 @@ rule = fleet-wide, gated by the metadata match). **Do NOT blanket-whitelist an R grandparent by itself** — the RMM is a SYSTEM-level RCE channel and the top MSP attack path; whitelisting all of it blinds EDR exactly where it matters. +**[DANGER] API create footgun (verified live 2026-07-07).** `POST /SuppressionRules` on this +tenant (a) **ignores `active:false` and forces the rule LIVE immediately**, and (b) auto-creates +an **EMPTY-metadata version that matches EVERYTHING**. So the two-step "create rule → then add +version" path leaves a live match-all suppression window (observed ~2 min → deleted). Also note +`GET`/`PATCH /SuppressionRules/{id}` (single-resource route) **HTTP 400** "undefined is not valid +JSON" on this tenant — read via `GET /SuppressionRules?filter={"where":{"id":"..."}}` instead; +**`DELETE /SuppressionRules/{id}` DOES work** (returns null, sets `deleted:true`). Until an +**atomic** create (metadata embedded in the POST body) is verified, **create suppressions in the +CONSOLE**, not via `raw POST`. If you must POST, verify the version's active fields IMMEDIATELY +and `DELETE` on any doubt. + **Create — CONSOLE-observed, API create RUN-unverified.** Console flow: open the alert → **Create Suppression Rule** → check the desired Match fields → Save. This POSTs a `SuppressionRule` (carrying `alertId`, `name`, `description`, `organizationId`, `locationId`, diff --git a/.claude/skills/datto-edr/scripts/edr.py b/.claude/skills/datto-edr/scripts/edr.py index c488a2c0..557856c7 100644 --- a/.claude/skills/datto-edr/scripts/edr.py +++ b/.claude/skills/datto-edr/scripts/edr.py @@ -267,7 +267,7 @@ def build_parser() -> argparse.ArgumentParser: sp.add_argument("method", help="GET/POST/PUT/DELETE") sp.add_argument("path", help="e.g. Agents or Agents/scan") sp.add_argument("--filter", help="LoopBack filter JSON (GET)") - sp.add_argument("--data", help="request body JSON") + sp.add_argument("--data", help="request body JSON, or @path to read JSON from a file") sp.add_argument("--confirm", action="store_true", help="required for non-GET methods") return p @@ -408,7 +408,13 @@ def main(argv=None) -> int: print(f"[BLOCKED] {method} requires --confirm.", file=sys.stderr) return 2 filt = json.loads(args.filter) if args.filter else None - body = json.loads(args.data) if args.data else None + if args.data and args.data.startswith("@"): + with open(args.data[1:], "r", encoding="utf-8") as _f: + body = json.load(_f) + elif args.data: + body = json.loads(args.data) + else: + body = None # Same tenant-wide footgun guard the scan command has: a POST to any # */scan endpoint with no non-empty `where` scans the ENTIRE tenant. if method == "POST" and args.path.rstrip("/").lower().endswith("scan"): diff --git a/errorlog.md b/errorlog.md index aa15d19e..44d6b423 100644 --- a/errorlog.md +++ b/errorlog.md @@ -21,6 +21,10 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure · 2026-07-07 | GURU-BEAST-ROG | syncro | add_line_item and update_line_item both ignored user_id override - line item attributed to API key owner (1735) despite user_id 1750 in payload; needed GUI fix [ctx: ticket=32509 line=43169157] +2026-07-07 | Howard-Home | datto-edr | [friction] SuppressionRules POST forces active=true and auto-creates an EMPTY match-everything version; two-step create (rule then version) briefly went live matching all. Neutralized via DELETE. FIX: create atomically with metadata embedded, or use console. [ctx: tenant=azcomp4587 rule=7c36b89f impact=~2min-possible-oversuppression] + +2026-07-07 | Howard-Home | datto-edr | Datto EDR HTTP 400: {"error":{"statusCode":400,"name":"SyntaxError","message":""undefined" is not valid JSON"}} [ctx: cmd=raw] (x2) + 2026-07-07 | GURU-5070 | bash/env | [friction] appended a scratch write to /tmp/_vwpinv inside the syncro invoice block; PreToolUse block-tmp-path hook blocked the ENTIRE Bash call so no invoice ran - had to re-run. Never write scratch to /tmp on Windows; use a repo-relative path or skip it [ctx: ref=feedback_tmp_path_windows] 2026-07-07 | GURU-5070 | troubleshooting/assumption | [correction] repeatedly asserted Datto EDR was not cleanly uninstalled and pushed reinstall-to-uninstall; user had already removed it via its own uninstaller. A persistent WFP block filter can survive a clean uninstall (vendor bug) - frame it that way, don't blame the operator's removal diff --git a/session-logs/2026-07/2026-07-07-howard-gps-rmm-progress-silent-popup.md b/session-logs/2026-07/2026-07-07-howard-gps-rmm-progress-silent-popup.md new file mode 100644 index 00000000..be03f367 --- /dev/null +++ b/session-logs/2026-07/2026-07-07-howard-gps-rmm-progress-silent-popup.md @@ -0,0 +1,44 @@ +# Session — GPS-RMM-Progress console popup made silent + +## User +- **User:** Howard Enos (howard) +- **Machine:** Howard-Home +- **Role:** tech + +## Summary +Howard reported a command window flashing on the desktop then disappearing. Traced it to +the `GPS-RMM-Progress` Windows scheduled task and made it run windowless. + +## Diagnosis +- **Task:** `GPS-RMM-Progress` (root `\` folder, author `Howard`) +- **Old action:** `C:\Program Files\Git\bin\bash.exe -lc "cd /c/claudetools && bash .claude/scripts/gps-rmm-progress-check.sh"` +- **Trigger:** daily at 08:07 (single fire; no repetition interval) +- **Root cause of flash:** action launched `bash.exe` (console subsystem) with + `LogonType = Interactive` + `Hidden = False`, so a console window drew on the desktop each run. +- Script is our GPS->GuruRMM enrollment progress check: read-only against RMM `/api/agents`, + DMs Howard a daily summary via `discord-dm.sh`. No interactive desktop needed -> safe to hide. + +## Fix +- Added `.claude/scripts/gps-rmm-progress-hidden.vbs` — a wscript wrapper that runs bash with + window style `0` (hidden), `False` (no wait). `wscript.exe` is a GUI-subsystem host, so no + console is ever allocated. +- Repointed the task action to: + - Execute: `C:\Windows\System32\wscript.exe` + - Arguments: `"C:\claudetools\.claude\scripts\gps-rmm-progress-hidden.vbs"` +- Runtime environment unchanged (same interactive session, vault/SSH/env intact); only the + window is suppressed. + +## Verification +- `Start-ScheduledTask GPS-RMM-Progress` -> `LastResult 0x0`, no visible wscript/bash/conhost + windows. Flash eliminated. +- The verification run was a real (non-dry) execution, so it sent Howard the normal daily + Discord summary (expected). + +## Files changed +- `.claude/scripts/gps-rmm-progress-hidden.vbs` (new) + +## Notes / pending +- The scheduled task itself is per-machine (not stored in repo) — this fix applies to + Howard-Home only. Same change would be needed on any other machine running this task. +- Script self-reports `COMPLETE` once every tracked GPS client hits its RMM target; at that + point the task can be retired: `schtasks /Delete /TN GPS-RMM-Progress /F`.