sync: auto-sync from HOWARD-HOME at 2026-07-07 11:45:40

Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 11:45:40
This commit is contained in:
2026-07-07 11:46:10 -07:00
parent 1e1bae772d
commit a507678254
6 changed files with 76 additions and 2 deletions

View File

@@ -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`,

View File

@@ -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"):