skills: add datto-edr (Datto EDR / Infocyte HUNT) + syncro-rmm memory

New /datto-edr skill — standalone CLI for the Datto EDR REST API (azcomp4587,
rebranded Infocyte HUNT, raw-token LoopBack). Live-verified reads across the whole
MSP fleet: orgs/sites/agents/detections/sweep + deploy-cmd. Scan/isolate gated
behind --confirm (shape-verified, not run against prod). Token vaulted at
msp-tools/datto-edr.sops.yaml.

Also: reference_syncro_rmm_api_gui_only memory (Syncro RMM policy mgmt is
GUI-only) and the guru-rmm submodule pointer bump (Feature 6 EDR research).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 12:39:42 -07:00
parent e61b39b5c8
commit bd1e84d32c
7 changed files with 377 additions and 30 deletions

View File

@@ -0,0 +1,111 @@
# Datto EDR (Infocyte HUNT) API reference
Datto EDR == rebranded Infocyte HUNT. The API is a per-tenant **LoopBack** REST
service. Everything below was verified live against `azcomp4587.infocyte.com` on
2026-06-25 unless marked otherwise.
## Base URL & auth
- Base: `https://<instance>.infocyte.com/api` (this tenant: `azcomp4587`).
- Self-documenting LoopBack explorer: `https://<instance>.infocyte.com/explorer`.
- **Auth: raw 64-char token in the `Authorization` header.** NO `Bearer` prefix, no
Basic, no OAuth. `Authorization: <token>`. (Source: KaseyaDEDR PowershellTools
`requestHelpers.ps1`; confirmed live — `Bearer` prefix would fail.)
- Token created in console: username menu → **Admin → Users & Tokens → API Tokens →
Create new token**. Shown once. **Expires 1 year after creation.**
- Vault: `msp-tools/datto-edr.sops.yaml` field `credentials.api_token`.
## LoopBack conventions
- Models are PascalCase collections: `Organizations`, `Locations`, `Targets`,
`Agents`, `Alerts`, `Boxes`, `Reports`, `deviceGroups` (lowercase), `agentKeys`.
- List with a filter: `GET /<Model>?filter=<urlencoded JSON>` where the JSON supports
`{"where":{...},"limit":N,"order":"field DIR","fields":{"x":true}}`.
- `where` operators: `{"field":{"gt":"..."}}`, `{"field":{"inq":[...]}}`, equality
`{"field":val}`.
- Count: `GET /<Model>/count?where=<urlencoded JSON>``{"count":N}`.
- Detail: `GET /<Model>/{id}`.
- A model whose route doesn't exist returns `{"error":{"statusCode":404,"message":
"There is no method to handle GET /..."}}`.
## Data hierarchy
```
Organization (client) GET /Organizations [id,name,agentCount,alertCount,locationCount,tenantId]
└─ Location (site) GET /Locations [id,name,organizationId,agentCount,activeAgentCount,alertCount,lastScannedOn]
└─ Agent (endpoint) GET /Agents (Agent.locationId -> Location.id)
Target (SCAN group) GET /Targets [id,name,organizationId,agentCount,activeAgentCount,lastScannedOn] (scannable unit)
deviceGroup (global category) GET /deviceGroups [id,name,deviceType] ("Servers"/"Workstations" — spans all orgs)
Alert (detection) GET /Alerts (carries organizationId/Name, targetGroupId/Name, severity, mitre*)
```
To list **agents for a client**: resolve org → its Locations (`where organizationId`)
→ `GET /Agents?filter={"where":{"locationId":{"inq":[<locIds>]}}}`. (Agents do NOT
carry organizationId directly; `deviceGroupId` is a global category, not the client.)
## Agent object (key fields)
`id, hostname, name, os, osWindows/osLinux/osOsx/osOther, version, ip/ipPub,
active (online), heartbeat, isolated (containment state), dattoAvEnabled,
markedForUninstall, markedForUpdate, locationId, deviceGroupId, deviceId, authorized,
eppData, rwdInfo`.
## Alert object (key fields)
`id, name, description, severity (0 info,1 low,2 medium,3 high,4 critical),
mitreTactic, mitreId, hostname, organizationId, organizationName, targetGroupId,
targetGroupName, deviceId, agentId, hostId, eventTime, createdOn, sourceType,
sourceName, responseData, signed, managed, archived`.
## Reads (verified)
| Op | Method/path |
|---|---|
| Tenant counts | `GET /Organizations/count`, `/Targets/count`, `/Agents/count`, `/Alerts/count` |
| Organizations | `GET /Organizations` |
| Locations (sites) | `GET /Locations` (filter `where organizationId`) |
| Scan target groups | `GET /Targets` |
| Agents | `GET /Agents` (filter `where locationId` / `inq`) |
| Agent detail | `GET /Agents/{id}` |
| Alerts | `GET /Alerts` (filter `where` org/severity/createdOn) |
| Alert detail | `GET /Alerts/{id}` |
| Extensions | `GET /Extensions` |
| Agent reg keys | `GET /agentKeys` (for deploy one-liner) |
## Mutating (shape-verified from the InfocyteHUNTAPI module; gate behind --confirm)
| Op | Method/path | Body |
|---|---|---|
| Scan a target group | `POST /targets/{targetGroupId}/scan` | `{"options":{...ScanOptions}, "where":{...}?}` |
| Scan a single target | `POST /targets/scan` | `{"target":"ip/host","targetGroup":{"id":"..."},"options":{...}}` |
| Response extension (isolate/kill/quarantine) | `POST /targets/scan` | `{"target":"...","targetGroup":{"id":"..."},"options":{"extensions":[{"id"|"name":...}]}}` |
**ScanOptions** booleans: `process, module, driver, memory, account, artifact,
autostart, application, installed, hook, network, events`, plus
`extensions:[{id,args,order}]`.
These were **not executed** against the prod tenant during build (they scan/isolate
real client endpoints). Confirm the response-extension id/name via `GET /Extensions`
and run one deliberate test on an ACG-internal machine before relying on them.
## Deployment (not a REST call)
The agent installs by running the binary on the endpoint:
`agent.exe --key <RegKey> --url https://<instance>.infocyte.com`, or via the official
PowerShell wrapper one-liner (`Install-EDR -InstanceName <cname> -RegKey <key>`). Pull
a live key from `GET /agentKeys`. Push via GuruRMM `/rmm` script execution or any
remote-exec channel. Nothing ties install to Datto RMM.
## Liftable client
`github.com/KaseyaDEDR/PowershellTools` — the `InfocyteHUNTAPI` module (Apache-2.0) is
a complete REST wrapper and the de-facto API spec. `extension-docs` (Lua 5.3)
documents the agent response actions (host isolation `isolator:isolate()`, process
kill, quarantine).
## Webhooks (for the GuruRMM add-on)
Admin → Webhooks → Add Webhook: EDR POSTs full alert JSON over HTTPS on each detection,
with custom auth headers supported. This is the recommended near-real-time feed for a
GuruRMM "EDR add-on" dashboard, complemented by REST polling here. (Not wired in this
skill yet — Feature 6 work.)