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:
111
.claude/skills/datto-edr/references/api-reference.md
Normal file
111
.claude/skills/datto-edr/references/api-reference.md
Normal 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.)
|
||||
Reference in New Issue
Block a user