diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 836bd7f..ae11b5a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -14,7 +14,7 @@ "name": "s1-secops-skills", "source": "./plugins/s1-secops-skills", "description": "SentinelOne SecOps skills for Claude: PowerQuery threat hunting and STAR/Custom Detection rules; Management Console API; Singularity Data Lake API; SDL dashboards; log parsing (OCSF); Hyperautomation SOAR; z-score anomaly baselining; autonomous DFIR alert investigation (soc-investigator); and one-prompt SDL solutions: source onboarding, asset enrichment, UEBA, ingest health, detection exclusions, Risk-Based Alerting, alert noise reduction, and Detection as Code.", - "version": "1.3.0", + "version": "1.3.1", "author": { "name": "Prithvi Moses", "email": "prithvi.moses@sentinelone.com" diff --git a/mcp/docker/README.md b/mcp/docker/README.md index 3a18479..76c9a9a 100644 --- a/mcp/docker/README.md +++ b/mcp/docker/README.md @@ -6,7 +6,7 @@ End-user reference: [`docs/docker.md`](../docs/docker.md). This file is for imag ## Layout -``` +```bash docker/ ├── Dockerfile # multi-arch, all 3 MCPs at pinned versions ├── entrypoint.sh # dispatcher: argv[1] selects which MCP to run @@ -29,7 +29,7 @@ When bumping a pin, edit both. They are checked once via `grep` in CI; a mismatc | What | Source | Current pin | |---|---|---| | Image version (`IMAGE_VERSION`) | this repo | `1.3.1` | -| `@pmoses-s1/s1-secops-mcp` | npm | `1.3.1` | +| `@pmoses-s1/s1-secops-mcp` | npm | `1.3.3` | | `@burtthecoder/mcp-virustotal` | npm | `1.0.21` | | `purple-mcp` | git | `07d4992` (Sentinel-One/purple-mcp `v0.7.0`, 2026-06-26) | diff --git a/mcp/docker/build.sh b/mcp/docker/build.sh index 346a3f4..55130e3 100755 --- a/mcp/docker/build.sh +++ b/mcp/docker/build.sh @@ -21,7 +21,7 @@ set -euo pipefail IMAGE_VERSION="${IMAGE_VERSION:-1.3.1}" # ── Pinned MCP versions ────────────────────────────────────────────────────── -S1_MCP_VERSION="${S1_MCP_VERSION:-1.3.1}" +S1_MCP_VERSION="${S1_MCP_VERSION:-1.3.3}" VT_MCP_PACKAGE="${VT_MCP_PACKAGE:-@burtthecoder/mcp-virustotal}" VT_MCP_VERSION="${VT_MCP_VERSION:-1.0.21}" # purple-mcp v0.7.0 (2026-06-26). Pinned to the release commit, not a floating diff --git a/mcp/s1-secops-mcp/CHANGELOG.md b/mcp/s1-secops-mcp/CHANGELOG.md index 5bf4802..6a6a622 100644 --- a/mcp/s1-secops-mcp/CHANGELOG.md +++ b/mcp/s1-secops-mcp/CHANGELOG.md @@ -1,10 +1,112 @@ # Changelog +## 1.3.3 - 2026-08-07 + +Fixes a user-facing regression in 1.3.2 found by running the live MCP tools +against a tenant for the first time. **Upgrade from 1.3.2 is recommended.** + +### Fixed + +- **Every successful `sdl_delete_file` reported an error in 1.3.2.** The delete + verification added in 1.3.2 re-reads the file to confirm removal, but the server + reports absence as a GraphQL error rather than a null result, so the confirming + read threw on exactly the success path. The delete itself always worked; only the + reported outcome was wrong. Verified live: three deletes across `/dashboards/`, + `/datatables/` and `/logParsers/` all removed their file and all three surfaced + as errors. +- **`sdl_get_file` on a missing path returned a raw GraphQL error** instead of the + actionable "this may be udoId-addressed, list it and retry" hint. The hint branch + was unreachable because the library threw before returning. +- **Absence is now detected reliably on both address forms.** The error text differs: + a missing name gives "Config file with name X not found.", a missing `udoId` gives + the generic "Something went wrong. Please try again...", which is also what a + version conflict returns. `configFile` normalises the explicit form and + disambiguates the generic one against the file listing, so a deleted dashboard + reads as absent while a genuine server error still propagates. +- **An out-of-range numeric `udoId` is no longer swallowed by the absence path.** + Validation now runs before the lookup, so a caller bug surfaces as a caller bug. +- **A transport error is never read as "file absent".** Absence detection now requires a + GraphQL-layer error, so a 404 page or WAF block whose body contains the words "not found" + no longer satisfies it. Without this a delete could confirm itself against a file it + never checked. +- **A failing listing during absence disambiguation keeps the original error** instead of + replacing it with the listing failure. +- **The duplicate guard is no longer bypassed by case.** Its namespace test was + case-sensitive while its name comparison was not, so `/Dashboards/AI Usage` skipped the + guard entirely. Both now share one normaliser. +- **`v1Query` keeps its backoff.** Restricting status retry to idempotent methods removed it + from this read-only POST, which schema discovery iterates once per data source. + +### Python client (`sdl-api/scripts/sdl_client.py`) + +Brought to parity with the JS client: + +- Status retry is restricted to idempotent methods. The Python client was retrying POST + mutations, which is the mechanism that duplicates a dashboard on a re-sent write. +- `Retry-After` is capped at 30s; an unbounded value parked the process. +- The duplicate guard fails closed on an empty listing. +- Absence detection, name normalisation and delete verification match the JS behaviour. + +### Tests + +- `tests/sdl-graphql.test.mjs`: 86 cases, adding the transport-error-is-not-absence case, + listing-failure error preservation, the case-variant guard bypass, and `v1Query` retry. +- `sdl-api/tests/test_client.py`: new. 19 cases over a stubbed session, so the Python client + is no longer invisible to CI. Runs in ~0.01s with no network. +- Live regression through the real MCP stdio protocol: handshake, 26 tools, and full + create/read/update/stale-reject/delete/confirm-absent cycles for `/datatables/` and + `/dashboards/`, plus the duplicate guard, the notFound hint, and `/automaticLookups`. + +## 1.3.2 - 2026-08-07 + +Config-file operations move from the legacy REST endpoints to GraphQL. Tool count unchanged at +26, and all four tool names are unchanged, so no caller needs to change. + +### Fixed + +- **`sdl_list_files` no longer returns an incomplete listing.** The REST `/sdl/api/listFiles` + endpoint omits every udoId-addressed dashboard. Measured live on `usea1-purple`: REST returned + 1,914 paths against `configFiles`' 2,264, a 350-file gap consisting entirely of `/dashboards/` + files that carry a `udoId`. REST `getFile` on any of them returns `success/noSuchFile`. The + practical impact was a false negative: a dashboard that existed in the console was reported as + not found. All four config-file tools now run on `POST /sdl/v2/graphql`. +- **`sdl_list_files` description no longer claims to return "all" files.** It did not, and the + claim was load-bearing: an agent reading it had no reason to look further after an empty result. +- **`sdl_get_file` / `sdl_put_file` / `sdl_delete_file` can now address dashboards.** New `udoId` + parameter. The console's Configuration Files grid displays a dashboard as + `/dashboards/id//`; that string is not a path, and reading it as one returns + `no file exists at path`. + +### Added + +- **`lib/sdl.js`: `configFiles`, `configFile`, `putConfigFile`, `deleteConfigFile`** over + `POST /sdl/v2/graphql`. GraphQL reports failure as HTTP 200 with an `errors` array, so the + wrapper raises on that array rather than trusting the status code. +- **Duplicate guardrail on dashboard writes.** `addConfigFile(name:)` updates in place for a + name-addressed file but creates a duplicate for a dashboard (both verified live). `sdl_put_file` + now refuses a name-addressed write to an *existing* dashboard and names the `udoId`s already + holding it, while still allowing the initial create, which has no `udoId` yet. The tenant this + was found on already carries 256 surplus dashboard copies from this behaviour, including 152 of + `/dashboards/AI Usage`. +- **`pathPrefix` filter on `sdl_list_files`**, so callers can scope to `/dashboards/` or + `/logParsers/` without pulling the full listing into context. + +### Notes + +- `udoId` is assigned by namespace, verified live: only `/dashboards/` files get one. `/lookups/`, + `/datatables/`, `/logParsers/` and `/automaticLookups` are name-addressed with `udoId` null. +- `expectedVersion` is enforced on both address forms. A stale value is rejected with + "There are conflicting changes in the file." and the stored content is left untouched. +- A `deleteConfigFile` returning `null` with no `errors` array is success, not failure. +- The scoped SDL keys (`SDL_CONFIG_READ_KEY` and friends) are retired; the console API token + covers every SDL operation. + ## 1.2.4 - 2026-07-31 Hardening release from the 2026-07-31 code review. Tool count unchanged at 26. ### Fixed + - **Missing `Retry-After` header no longer sleeps 0ms before retrying.** `Number(null)` is 0, so `lib/s1.js`, `lib/hec.js`, and `lib/uam-ingest.js` treated an absent header as "wait 0ms" and hammered the backend. All three now use the validated pattern from `lib/sdl.js`: honor the header only when present and parseable as a finite number of seconds (capped at 30s), otherwise fall back to the exponential delay. Numeric headers behave exactly as before. - **`uam_set_status` no longer reports silent success.** The `alertTriggerActions` mutation selected only `__typename`, so a skipped or failed action still returned as if it worked (observed live: status unchanged after a "successful" call). The selection now mirrors the full `actions { success failure skip }` shape and the client throws when the backend reports a failure entry, skips the action without a success, or returns an empty actions list (nothing applied, e.g. the filter matched no alert). `uam_add_note` was audited for the same pattern; it already verifies via the returned note list. - **LRQ polling tolerates transient poll errors.** A single 429/5xx poll response used to throw and cancel the running query. Transient statuses now keep polling (interval doubles up to 5s) until the existing 5-minute deadline; other 4xx responses remain fatal. @@ -16,6 +118,7 @@ Hardening release from the 2026-07-31 code review. Tool count unchanged at 26. - **SDL `config_read` key chain reordered to least-privilege first** (config_read, config_write, console JWT), matching the Python `SDLClient` the header claims to mirror. ### Changed + - **Removed dead exports `purpleAiQuery` and `purpleAiInvestigate`** from `lib/s1.js`; their MCP tools were removed 2026-05-03 (browser-session teamToken requirement) and nothing referenced the library functions. Corrected stale doc text that pointed at a nonexistent `uam_set_analyst_verdict` tool: the analyst verdict is set via a raw `alertTriggerActions` mutation with the `analystVerdictUpdate` action through `s1_api_post`. - **Deploy docs: credentials.json changes require `systemctl restart`.** SIGHUP reload only re-reads bearer tokens; the installer output and systemd unit comment said reload was enough. `deploy/install.sh` also sets `umask 077` so token/credential files are never world-readable at creation (the explicit `chmod 600` lines remain). - **Claude Desktop bridge hardening:** 120s fetch timeout (`AbortSignal.timeout`), notification responses are drained so keep-alive sockets are released, and the URL constant no longer shadows the global `URL` constructor. @@ -23,6 +126,7 @@ Hardening release from the 2026-07-31 code review. Tool count unchanged at 26. - **`const status = response.error ? 200 : 200`** simplified; JSON-RPC errors still return HTTP 200 with an error envelope. ### Tests + - New `tests/regressions-2026-07-31.test.mjs` (mocked fetch, no network): missing `Retry-After` uses the exponential delay, `uamSetStatus` throws on failure/skip results, inline `?isLegacy=` is honored. Both regression suites are now part of `npm test`. - Transport and smoke tests read the expected version from `package.json` instead of a hardcoded string. @@ -31,12 +135,14 @@ Hardening release from the 2026-07-31 code review. Tool count unchanged at 26. Correctness release from the 2026-07-29 defect review. Fixes two bugs that produced plausible-but-wrong query results, hardens the SDL auth chain and HTTP retry paths, corrects the HEC `/event` content type, and adds a regression suite. Tool count unchanged at 26. (The Docker bundle image moved to 1.2.4 pinning `S1_MCP_VERSION=1.2.3`; the image tag stays ahead of the npm tag as it has since the split.) ### Fixed + - **`powerquery_run` no longer collapses a caller-supplied time window.** The old code overwrote BOTH `startTime` and `endTime` with the last-`hours` default whenever either was missing, so a startTime-only call silently ran over the last 24 hours. Each bound now defaults independently. Live A/B: a startTime-only 7.4-day query returned 73,755 events on the fixed server vs 12,911 (the 24h control) on the old one. - **`powerquery_run` now reports `matchCount`.** It was read from the top level of the LRQ response and came back `null` on every call; it lives inside the `data` block on current engines. - **SDL auth chain falls through on 401/403.** `lib/sdl.js` treated an auth failure on the first configured key as fatal even when a later key (e.g. the console JWT) would work. It now advances through the chain and raises only when exhausted. - **HEC `/event` ingestion uses `application/json`,** so per-event `time` backdating is honored instead of the envelope being indexed as opaque text at receive time. ### Changed + - **Write requests no longer auto-retry on 5xx.** Retry is restricted to idempotent methods (GET/HEAD); read-only POSTs (GraphQL queries, Purple AI launches) opt back in via `allowRetry`. HEC raw ingest no longer retries 5xx (no idempotency key); UAM ingest still retries because `metadata.uid` dedupes. - **`Retry-After` parsing hardened:** an HTTP-date value no longer collapses to `sleep(NaN)`; waits are validated and capped at 30s. - **`uam_add_note` returns the correct note** (matches by text, tiebreaks on newest `createdAt`) instead of assuming newest-last ordering. @@ -45,15 +151,18 @@ Correctness release from the 2026-07-29 defect review. Fixes two bugs that produ - **`powerquery_schema_discover` escapes single quotes** in the data-source name before building the V1 filter. ### Tests + - New `tests/regressions-2026-07-29.test.mjs` (mocked fetch, no network): independent time-bound defaults, `matchCount` extraction, SDL 401/403 fall-through, HEC per-endpoint content type, write-vs-read retry semantics. ## 1.2.2 - 2026-06-13 ### Changed + - **Renamed `ha_archive_workflow` to `ha_delete_workflow`.** The old tool hit `POST /hyper-automate/api/v1/workflows/archive`, which returns HTTP 500 on this tenant. The replacement uses the validated `DELETE /hyper-automate/api/v1/workflows/{id}` endpoint (a soft, recoverable delete equivalent to clicking Delete in the Hyperautomation UI). Scope the call with `accountIds` or `siteIds`; a 404 "Object not found" means the id is not under that scope or is already deleted. Updated `README.md`, the tools-table regenerator, and the smoke test in lockstep. - **`powerquery_run` description now documents the `datasource` and `savelookup` capabilities** (querying SentinelOne-managed inventory such as assets/alerts/vulnerabilities/misconfigurations, and persisting a result as a reusable lookup table), pointing at the new `powerquery/references/datasource-command.md`. ### Notes + - Tool count unchanged at 26 (the Hyperautomation tool was renamed, not added or removed). - `SERVER_INFO.version` bumped in lockstep with `package.json` (the drift that forced the 1.2.0 -> 1.2.1 re-release). @@ -62,22 +171,26 @@ Correctness release from the 2026-07-29 defect review. Fixes two bugs that produ Supersedes 1.2.0, which was deprecated on npm. The 1.2.0 build shipped with a stale internal `SERVER_INFO.version` of `1.1.0` despite a `1.2.0` package version, so the server announced the wrong version on `initialize`. 1.2.1 is identical in features and corrects the reported runtime version. The content below is unchanged from the 1.2.0 work. ### Added + - **`hec_ingest` tool**: raw-log/event ingestion into the Singularity Data Lake via the HEC (HTTP Event Collector) endpoint (`/services/collector/raw` and `/services/collector/event`). Supports `parser` (-> `?sourcetype=`), custom `fields` (query params), **required** `scope` (S1-Scope header), gzip compression, and `isParsed` (-> `?isParsed=true`, indexes already-structured JSON with no SDL parser). Replaces the removed `sdl_upload_logs`. Validated live across the full HEC matrix (both endpoints, gzip on/off, parser field extraction, multi-line, batched, reserved-field handling, scope enforcement, isParsed). Grounded in the S-26.1 HEC docs (p.4723-4726). ### Removed + - **`sdl_upload_logs` tool** plus the underlying SDL `uploadLogs`/`addEvents` library functions and `SDL_LOG_WRITE_KEY` plumbing. SDL raw-log ingestion moves to the HEC path (`hec_ingest`). The `sdl-api` skill is now query + configuration only; the `sdl-log-parser` validation loop uses HEC ingest. ### Changed + - Tool count unchanged at 26 (removed `sdl_upload_logs`, added `hec_ingest`). - Skill docs corrected: scheduled detection rules bind the Target Asset via `entityMappings` ("Entity column mapping"); the full scheduled-rule option set (UI <-> API) is catalogued in `powerquery/references/detection-rules.md`. - ## 1.1.0 - 2026-05-28 (rebuilt 2026-05-31) ### Fixed (rebuild) + - **`s1_api_get` now auto-injects `isLegacy=false` for `/cloud-detection/rules` listings.** Without `isLegacy=false` the S1 API silently omits `queryType="scheduled"` PowerQuery rules from the response; no error, no warning, the response just lies by omission. The handler now guards against this when the caller forgets, and the tool description loudly flags the requirement. This eliminates the "I see zero scheduled detections" failure mode that was producing wrong verdicts when listing Custom Detection rules. Same `1.1.0` version per the rebuild request. ### Added + - **Streamable HTTP transport.** New `--transport http` mode (default stays `stdio`). Single-endpoint POST `/mcp` per the MCP 2024-11-05 spec, plus `/healthz` for load balancer probes. Implementation is pure `node:http`, no new dependencies. - **Per-user bearer token auth.** New `MCP_BEARER_TOKENS_FILE` env var pointing at a `{ "": "" }` JSON file gives each team member a stable name in audit logs and supports rotation. SIGHUP reloads tokens without dropping connections. `MCP_BEARER_TOKENS` env var (comma-separated raw tokens) is a fallback for small or quick-test setups. - **Audit logging.** Every authenticated HTTP request emits `[audit] | | | | ` to stderr; systemd captures it via journald. @@ -94,12 +207,14 @@ Supersedes 1.2.0, which was deprecated on npm. The 1.2.0 build shipped with a st - **README auto-regenerator** at `scripts/regen-readme-tools-table.mjs`. `npm run regen:readme` keeps the README table in sync with `ALL_TOOLS`. `npm run regen:readme -- --check` fails when stale (suitable for CI). ### Fixed + - **README tool table.** Previous count was 19; actual is 26. Auto-generated now. - **Header comment in `index.js`.** Previously said 21; updated to 26. - **`purple_ai_query`** removed from the documentation. The tool itself was removed 2026-05-03 because the underlying API requires a browser-session `teamToken` that service-account API tokens never obtain. The README, `index.js`, and `docs/mcp-tools.md` no longer reference it. - **`uam_set_status` documentation.** Doc previously said valid status values include `CLOSED`. The source enum is `NEW`, `IN_PROGRESS`, `RESOLVED`; doc now matches. ### Changed + - **Refactored** dispatch out of `index.js` into `lib/server-core.js` so both transports use one code path. `lib/stdio-transport.js` is the extracted stdio loop; `lib/http-transport.js` is new. - **package.json**: - `version` 1.0.0 → 1.1.0 @@ -107,6 +222,7 @@ Supersedes 1.2.0, which was deprecated on npm. The 1.2.0 build shipped with a st - new files included in the npm tarball: `deploy/`, `scripts/`, `CHANGELOG.md` ### Compatibility + - Default invocation is unchanged: `npx -y @pmoses-s1/s1-secops-mcp` still produces a stdio MCP server with identical behaviour to 1.0.0. - Existing `claude_desktop_config.json` and `.mcp.json` configs work without modification. - The 26 tools, 2 resources, and 2 prompts are unchanged from the late-1.0.0 line; only the documentation now matches reality. @@ -114,6 +230,7 @@ Supersedes 1.2.0, which was deprecated on npm. The 1.2.0 build shipped with a st ## 1.0.0 - 2026-05-07 Initial public release. + - 19 tools across PowerQuery, S1 Mgmt REST, UAM, SDL API, Hyperautomation. - stdio transport only. - Credentials via env vars or auto-discovered `credentials.json`. diff --git a/mcp/s1-secops-mcp/README.md b/mcp/s1-secops-mcp/README.md index e10ff8c..e2f9f9c 100644 --- a/mcp/s1-secops-mcp/README.md +++ b/mcp/s1-secops-mcp/README.md @@ -43,10 +43,12 @@ See **[deploy/README.md](./deploy/README.md)** for the full deployment walkthrou **2 resources:** + - `sentinelone://soc-context`: `CLAUDE.md`, the Principal SOC Analyst operating instructions. - `sentinelone://credentials-status`: which credentials are configured and which API surfaces are available. **2 prompts:** + - `soc_analyst`: embeds `CLAUDE.md` as a system prompt; call at session start. - `session_init`: structured init: enumerate sources + triage alerts in parallel. @@ -65,11 +67,11 @@ Add this to `claude_desktop_config.json` (or `.mcp.json` for Claude Code): "mcpServers": { "s1-secops-mcp": { "command": "npx", - "args": ["-y", "@pmoses-s1/s1-secops-mcp@1.3.1"], + "args": ["-y", "@pmoses-s1/s1-secops-mcp@1.3.3"], "env": { "S1_CONSOLE_URL": "https://usea1-yourorg.sentinelone.net", "S1_CONSOLE_API_TOKEN": "eyJ...", - "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net", + "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net" } } } @@ -126,16 +128,16 @@ Cmd+Q and reopen Claude Desktop. SentinelOne credentials live on the VM in `/etc Credential keys, where to get each one, and the two token types are documented canonically in **[docs/credentials.md](../../plugins/s1-secops-skills/docs/credentials.md)**. This section adds the MCP-server-specific detail: which tools each key gates, and the server's full credential-resolution order. -`S1_CONSOLE_URL` and `S1_CONSOLE_API_TOKEN` are sufficient for the PowerQuery, Mgmt Console REST, Purple AI summary, and UAM tools (16 of the 26). +`S1_CONSOLE_URL` and `S1_CONSOLE_API_TOKEN` are sufficient for the PowerQuery, Mgmt Console REST, Purple AI summary, UAM, Hyperautomation, and SDL config-file tools (22 of the 26). -`S1_HEC_INGEST_URL` is **required** for the three UAM Ingest tools (`uam_ingest_alert`, `uam_post_indicators`, `uam_post_alert`) and for `hec_ingest`. Without it those tools error at call time; the rest still work. +`S1_HEC_INGEST_URL` is **required** for the three UAM Ingest tools (`uam_ingest_alert`, `uam_post_indicators`, `uam_post_alert`) and for `hec_ingest`, the only four tools that need it. Without it those tools error at call time; the rest still work. -`SDL_*` keys gate the SDL tools as follows: +The SDL config-file tools (`sdl_list_files`, `sdl_get_file`, `sdl_put_file`, `sdl_delete_file`) are authorised by `S1_CONSOLE_API_TOKEN` against `POST /sdl/v2/graphql`. The scoped SDL keys (`SDL_CONFIG_READ_KEY`, `SDL_CONFIG_WRITE_KEY`, `SDL_LOG_READ_KEY`, `SDL_LOG_WRITE_KEY`, `SDL_XDR_URL`) are retired and are no longer read. | Variable | Description | Required for | |----------|-------------|--------------| -| `S1_CONSOLE_URL` | Console URL, e.g. `https://usea1-acme.sentinelone.net` | All Mgmt + PowerQuery tools | -| `S1_CONSOLE_API_TOKEN` | Mgmt Console API token (Settings → Users → Service Users) | All Mgmt + PowerQuery + UAM tools | +| `S1_CONSOLE_URL` | Console URL, e.g. `https://usea1-acme.sentinelone.net` | All Mgmt + PowerQuery + SDL tools | +| `S1_CONSOLE_API_TOKEN` | Mgmt Console API token (Settings → Users → Service Users) | All Mgmt + PowerQuery + UAM + SDL config-file tools | | `S1_HEC_INGEST_URL` | HEC ingest host, e.g. `https://ingest.us1.sentinelone.net` | `uam_ingest_alert`, `uam_post_indicators`, `uam_post_alert`, `hec_ingest` | ### Credential resolution order (highest priority wins) @@ -204,7 +206,7 @@ If neither env var is set, HTTP transport runs **without** authentication and th Every authenticated HTTP request emits a structured stderr line that systemd captures via journald: -``` +```json [audit] 2026-05-28T15:01:22.413Z | alice | tools/call | name=powerquery_run | 200 ok [audit] 2026-05-28T15:01:34.221Z | bob | tools/list | - | 200 ok [audit] 2026-05-28T17:03:11.221Z | - | - | - | 401 unauthorized @@ -434,7 +436,7 @@ The `maxRows` (`powerquery_run`) and `first` (`uam_list_alerts`) parameters are ## CLI reference -``` +```text s1-secops-mcp [options] OPTIONS @@ -448,7 +450,7 @@ OPTIONS ## Architecture -``` +```text s1-secops-mcp/ index.js Entry: flag parsing + transport selection lib/ @@ -485,6 +487,7 @@ s1-secops-mcp/ | Purple AI GraphQL | `Authorization: ApiToken ` | `S1_CONSOLE_API_TOKEN` | | UAM GraphQL | `Authorization: ApiToken ` | `S1_CONSOLE_API_TOKEN` | | UAM HEC ingest | `Authorization: Bearer ` | `S1_CONSOLE_API_TOKEN` | +| SDL config files (`POST /sdl/v2/graphql`) | `Authorization: Bearer `, an `s1-scope` header is ignored, not rejected | `S1_CONSOLE_API_TOKEN` | ## Testing diff --git a/mcp/s1-secops-mcp/deploy/README.md b/mcp/s1-secops-mcp/deploy/README.md index b65f0a4..74a6e9d 100644 --- a/mcp/s1-secops-mcp/deploy/README.md +++ b/mcp/s1-secops-mcp/deploy/README.md @@ -21,6 +21,7 @@ bash /tmp/s1-mcp-install.sh --user ``` That runs `install.sh --user`, which: + 1. Confirms Node 18+ is present (errors out with install hints if not). 2. Sets up a per-user npm prefix at `~/.npm-global` if one isn't configured. 3. Installs `@pmoses-s1/s1-secops-mcp` globally for your user. @@ -33,7 +34,7 @@ Then edit `~/.config/sentinelone/credentials.json` with your real values: { "S1_CONSOLE_URL": "https://usea1-yourorg.sentinelone.net", "S1_CONSOLE_API_TOKEN": "eyJ...", - "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net", + "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net" } ``` @@ -56,7 +57,7 @@ Or, equivalently, by package name without the install: "mcpServers": { "s1-secops-mcp": { "command": "npx", - "args": ["-y", "@pmoses-s1/s1-secops-mcp@1.3.1"] + "args": ["-y", "@pmoses-s1/s1-secops-mcp@1.3.3"] } } } @@ -117,11 +118,13 @@ Team members connect from their Claude clients with their own bearer token. Audi 1. **Provision the VM.** Anything that runs systemd is fine: Ubuntu 22.04 LTS, Debian 12, Rocky/Alma 9, etc. 2. **Install Node 18+.** Pick one: + ```bash # Ubuntu / Debian curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs ``` + ```bash # Rocky / Alma curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash - @@ -129,12 +132,15 @@ Team members connect from their Claude clients with their own bearer token. Audi ``` 3. **Run the installer in server mode:** + ```bash curl -fsSL https://raw.githubusercontent.com/pmoses-s1/claude-skills/main/s1-secops-mcp/deploy/install.sh | sudo bash -s -- --server ``` + It creates the `mcp` user, drops `/etc/s1-secops-mcp/credentials.json` (placeholder) and `/etc/s1-secops-mcp/bearer-tokens.json` (one freshly-generated admin token, printed once to stdout), installs the systemd unit, and starts the service. 4. **Fill in real SentinelOne credentials:** + ```bash sudo vim /etc/s1-secops-mcp/credentials.json sudo systemctl reload s1-secops-mcp @@ -142,15 +148,18 @@ Team members connect from their Claude clients with their own bearer token. Audi ``` 5. **Put TLS in front with Caddy** (the recommended option): + ```bash sudo apt install -y caddy sudo cp /usr/lib/node_modules/@pmoses-s1/s1-secops-mcp/deploy/caddy/Caddyfile.example /etc/caddy/Caddyfile sudo vim /etc/caddy/Caddyfile # change mcp.s1.internal to your DNS name sudo systemctl reload caddy ``` + Default Caddyfile uses `tls internal` which signs with Caddy's own CA. Distribute `/var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt` to your team for trust, or use `tls ` with a publicly resolvable hostname for Let's Encrypt. 6. **Add team members.** Generate a token per person and append to the file: + ```bash sudo bash -c 'cat > /etc/s1-secops-mcp/bearer-tokens.json' < | bridge | <---------------------------> | VM | +----------------+ +--------+ Bearer auth, JSON in/out +------+ diff --git a/mcp/s1-secops-mcp/lib/sdl.js b/mcp/s1-secops-mcp/lib/sdl.js index 38c6a4f..403b6b8 100644 --- a/mcp/s1-secops-mcp/lib/sdl.js +++ b/mcp/s1-secops-mcp/lib/sdl.js @@ -37,14 +37,44 @@ function retryAfterMs(res, fallback) { return fallback; } -async function sdlFetch(method, path, { body, extraHeaders = {}, rawBody = null, contentType = 'application/json' } = {}, retries = 3) { - const url = `${sdlBase()}${path}`; +/** + * Origin-pin the SDL request path, mirroring safeUrl() in lib/s1.js. + * + * Every current caller passes a literal, so this is defence in depth: the next + * caller to thread a tool-supplied path through sdlFetch would otherwise be + * able to rewrite the authority ("//evil.example/x", "@evil.example/x") and + * send the tenant bearer token to an attacker-chosen origin. + */ +function safeSdlUrl(path) { + if (typeof path !== 'string' || !path.startsWith('/') || path.startsWith('//')) { + throw new Error( + `SDL API path must be a string starting with a single "/" (got: ${JSON.stringify(path)?.slice(0, 80)})` + ); + } + const origin = new URL(sdlBase()).origin; + const u = new URL(sdlBase() + path, origin); + if (u.origin !== origin) { + throw new Error(`SDL API path may not change the request origin (resolved to ${u.origin})`); + } + return u.toString(); +} + +async function sdlFetch(method, path, { body, extraHeaders = {}, rawBody = null, contentType = 'application/json', allowRetry = null } = {}, retries = 3) { + const url = safeSdlUrl(path); const headers = { Authorization: `Bearer ${sdlToken()}`, 'Content-Type': contentType, ...extraHeaders, }; + // Status-based retry is restricted to idempotent methods, mirroring lib/s1.js. + // A 5xx received *after* the server committed a write would otherwise be + // re-sent, and a re-sent addConfigFile(name:) against /dashboards/ creates a + // duplicate. Callers opt in with allowRetry:true for read-only POSTs + // (GraphQL queries). Network-layer rejections are still always retried: + // those mean the request may never have reached the server at all. + const methodRetryable = allowRetry !== null ? allowRetry : (method === 'GET' || method === 'HEAD'); + let delay = 500; for (let attempt = 0; attempt <= retries; attempt++) { let res; @@ -61,7 +91,7 @@ async function sdlFetch(method, path, { body, extraHeaders = {}, rawBody = null, continue; } - if ((res.status === 429 || res.status >= 500) && attempt < retries) { + if (methodRetryable && (res.status === 429 || res.status >= 500) && attempt < retries) { await sleep(retryAfterMs(res, delay)); delay = Math.min(delay * 2, 8000); continue; @@ -80,38 +110,253 @@ async function sdlFetch(method, path, { body, extraHeaders = {}, rawBody = null, throw new Error(`SDL API ${method} ${path}: request failed after retries`); } -// ─── Config file operations ─────────────────────────────────────────────────── +// ─── Config file operations (GraphQL, canonical) ────────────────────────────── +// +// `POST /sdl/v2/graphql` is the canonical config-file surface. It is a strict +// superset of the legacy REST `/api/*File` endpoints: +// +// * REST listFiles omits every udoId-addressed dashboard. Measured on a live +// tenant: REST returned 1,914 paths, GraphQL configFiles returned 2,264. +// The 350-file gap is entirely /dashboards/ files that carry a udoId, and +// REST getFile on any of them returns `success/noSuchFile`. +// * The console's Configuration Files grid renders a udoId dashboard as the +// display string `/dashboards/id//`. That is NOT a path. Reading +// it as one returns "no file exists at path". Address it by udoId; its real +// name is `/dashboards/`. +// +// udoId assignment is by namespace, verified live: only `/dashboards/` files get +// a udoId. `/lookups/`, `/datatables/`, `/logParsers/` and `/automaticLookups` +// all come back with udoId `null` and are addressed by name. +// +// Write rule, verified live: addConfigFile(name:) UPDATES IN PLACE for a +// name-addressed file, but CREATES A DUPLICATE for a dashboard. Always write a +// dashboard by udoId. Skipping that rule is how one tenant accumulated 152 +// copies of `/dashboards/AI Usage`. -/** POST /api/listFiles: list every configuration file path on the SDL tenant. */ -export async function listFiles() { - return sdlFetch('POST', '/api/listFiles', { body: {} }); +/** Marks an error as originating from the GraphQL layer rather than the + * transport. Absence detection keys off this: a 404 page whose body contains + * "not found" must never be read as "the file does not exist". */ +class SdlGraphqlError extends Error { + constructor(message) { + super(message); + this.name = 'SdlGraphqlError'; + this.graphql = true; + } } -/** POST /api/getFile: read a configuration file by path. - * Returns { path, content, version, ...status }. */ -export async function getFile(path) { - return sdlFetch('POST', '/api/getFile', { - body: { path, prettyprint: true }, - }); +/** + * POST /sdl/v2/graphql. Returns `data`; throws on the GraphQL `errors` array. + * + * GraphQL reports failure as HTTP 200 with an `errors` array, so the status + * code alone never proves success. Three things this must not do, each of which + * would resurface the exact false-negative class this module exists to remove: + * + * 1. Accept a non-JSON 200. sdlFetch falls back to raw text when JSON.parse + * fails, so a proxy interstitial or WAF page arrives as a string. Left + * unchecked, `payload.errors` is undefined and every caller returns its + * empty default: "no files", "not found", "deleted". + * 2. Require `errors` to be an array. A bare `{errors: {...}}` object would + * slip through an Array.isArray gate. + * 3. Return a payload carrying neither `data` nor `errors`. + * + * `readOnly` opts into status-based retry; only pass it for queries. + */ +async function sdlGraphql(opname, query, variables, { readOnly = false } = {}) { + const body = { query }; + if (variables) body.variables = variables; + const payload = await sdlFetch( + 'POST', + `/v2/graphql?opname=${encodeURIComponent(opname)}`, + { body, allowRetry: readOnly } + ); + + if (typeof payload !== 'object' || payload === null) { + throw new Error( + `SDL GraphQL ${opname}: expected a JSON object, got ${typeof payload}. ` + + 'This usually means a proxy or auth interstitial answered instead of the API. ' + + `First 200 chars: ${String(payload).slice(0, 200)}` + ); + } + if (payload.errors) { + const errs = Array.isArray(payload.errors) ? payload.errors : [payload.errors]; + const correlationId = payload.extensions?.correlationId ?? errs[0]?.extensions?.correlationId; + const msg = errs[0]?.message || 'unknown GraphQL error'; + throw new SdlGraphqlError(`SDL GraphQL ${opname} → ${msg}${correlationId ? ` (correlationId=${correlationId})` : ''}`); + } + if (!('data' in payload)) { + throw new SdlGraphqlError(`SDL GraphQL ${opname}: response carried neither data nor errors.`); + } + return payload.data; } -/** POST /api/putFile: create or update a configuration file. - * Pass expectedVersion (from a prior getFile) to enable optimistic locking. */ -export async function putFile(path, content, expectedVersion) { - const body = { path, content }; - if (expectedVersion !== undefined && expectedVersion !== null) { - body.expectedVersion = expectedVersion; +/** udoIds are 16 digits, within ~1.4x of Number.MAX_SAFE_INTEGER. A caller that + * sends one as a JSON number has already lost precision before we stringify. */ +function assertSafeUdoId(udoId) { + if (typeof udoId === 'number' && !Number.isSafeInteger(udoId)) { + throw new Error( + `udoId ${udoId} exceeds the JS safe-integer range and has already lost precision. Pass it as a string.` + ); } - return sdlFetch('POST', '/api/putFile', { body }); + return String(udoId); } -/** POST /api/putFile with deleteFile:true deletes a config file. */ -export async function deleteFile(path, expectedVersion) { - const body = { path, deleteFile: true }; - if (expectedVersion !== undefined) body.expectedVersion = expectedVersion; - return sdlFetch('POST', '/api/putFile', { body }); +const CONFIG_FIELDS = 'udoId name readOnly version'; + +/** SDL config names are case-insensitive and tolerate stray whitespace, so the + * absence check and the duplicate guard must normalise identically. */ +function normaliseName(n) { + return String(n ?? '').trim().toLowerCase(); +} +function matchesName(file, name) { + return normaliseName(file?.name) === normaliseName(name); +} + +/** Every config file on the tenant, including udoId-addressed dashboards. */ +export async function configFiles() { + const data = await sdlGraphql( + 'getConfigurationFiles', + `query getConfigurationFiles { configFiles { ${CONFIG_FIELDS} } }`, + undefined, + { readOnly: true } + ); + return data?.configFiles ?? []; } +/** + * Read one config file by name (plain files) or udoId (dashboards). + * Returns null when the file does not exist. + * + * Absence is a normal outcome of a lookup, but the server reports it as a + * GraphQL error, and the message differs by address form (verified live): + * + * by name : "Config file with name /x/y not found." <- explicit + * by udoId: "Something went wrong. Please try again..." <- generic, and the + * SAME text a version conflict returns, so it cannot be trusted + * on message alone. + * + * So the explicit form is normalised directly, and the ambiguous one is + * disambiguated against the file listing: absent from configFiles means the + * file is genuinely gone, otherwise the error was real and is rethrown. The + * extra listing only happens on the error path. + */ +export async function configFile({ name, udoId }) { + if (!name && !udoId) throw new Error('configFile requires either name or udoId'); + // Validate before the try: an invalid udoId is a caller bug, not a signal + // that the file is absent, and must never be swallowed by the absence path. + const safeUdoId = udoId ? assertSafeUdoId(udoId) : null; + try { + const data = safeUdoId + ? await sdlGraphql('configFile', `query f($udoId: ID!) { configFile(udoId: $udoId) { ${CONFIG_FIELDS} content } }`, { udoId: safeUdoId }, { readOnly: true }) + : await sdlGraphql('configFile', `query f($id: ID!) { configFile(id: $id) { ${CONFIG_FIELDS} content } }`, { id: name }, { readOnly: true }); + return data?.configFile ?? null; + } catch (err) { + // Only a GraphQL-layer error can mean "absent". A transport failure whose + // body happens to contain the words "not found" (a 404 page, a WAF block) + // must never be read as absence: that is how a delete gets confirmed + // against a file that was never checked. + if (!err.graphql) throw err; + if (/config file with (name|id) .* not found/i.test(err.message)) return null; + + // The udoId form returns a generic message that a version conflict also + // returns, so settle it against the listing. If the listing itself fails, + // surface the ORIGINAL error with the listing failure attached rather than + // replacing it. + let all; + try { + all = await configFiles(); + } catch (listErr) { + err.message += ` (absence check failed: ${listErr.message})`; + throw err; + } + const present = udoId + ? all.some(f => String(f.udoId) === String(udoId)) + : all.some(f => matchesName(f, name)); + if (!present) return null; + throw err; + } +} + +/** + * Create or update a config file. + * - udoId given → updates that file in place; pass expectedVersion to lock. + * - name given → updates in place for plain files, but CREATES A DUPLICATE + * for /dashboards/. Never write a dashboard by name. + */ +export async function putConfigFile({ name, udoId, content, expectedVersion }) { + if (!name && !udoId) throw new Error('putConfigFile requires either name or udoId'); + // Creating a dashboard must go by name (no udoId exists yet); only an + // *existing* dashboard is at risk of being duplicated by a name-addressed + // write. So refuse only when a file of that name already exists. + if (!udoId && normaliseName(name).startsWith('/dashboards/')) { + const all = await configFiles(); + // An empty listing means the check could not run, not that the name is + // free. Failing open here would silently disable the guard. + if (!all.length) { + throw new Error( + `Refusing to write "${name}" by name: the configFiles listing came back empty, so the ` + + 'duplicate check could not run. Retry, or pass an explicit udoId.' + ); + } + const existing = all.filter(f => matchesName(f, name)); + if (existing.length) { + const ids = existing.map(f => f.udoId).filter(Boolean).join(', '); + throw new Error( + `Refusing to write "${name}" by name: ${existing.length} dashboard(s) already use that name, ` + + 'and a name-addressed write to /dashboards/ creates another duplicate rather than updating. ' + + `Pass one of these udoIds instead: ${ids || '(none, file has no udoId)'}.` + ); + } + } + // expectedVersion is honoured on BOTH address forms. Verified live 2026-08-07: + // a stale expectedVersion on a name-addressed /datatables/ write was rejected + // with "There are conflicting changes in the file." and the content was left + // untouched. Omitting it here would silently downgrade every parser, lookup, + // datatable and /automaticLookups write to last-write-wins. + const data = udoId + ? await sdlGraphql('addConfigFile', + `mutation f($udoId: ID, $content: String!, $expectedVersion: Long) { addConfigFile(udoId: $udoId, content: $content, expectedVersion: $expectedVersion) { ${CONFIG_FIELDS} } }`, + { udoId: assertSafeUdoId(udoId), content, expectedVersion }) + : await sdlGraphql('addConfigFile', + `mutation f($name: String, $content: String!, $expectedVersion: Long) { addConfigFile(name: $name, content: $content, expectedVersion: $expectedVersion) { ${CONFIG_FIELDS} } }`, + { name, content, expectedVersion }); + return data?.addConfigFile ?? null; +} + +/** + * Delete a config file. Dashboards delete by udoId, plain files by name. + * A null return with no errors array is SUCCESS; the deleted object is not + * echoed back. Treating that null as a failure is the classic mistake here. + */ +export async function deleteConfigFile({ name, udoId, expectedVersion }) { + if (!name && !udoId) throw new Error('deleteConfigFile requires either name or udoId'); + const raw = udoId + ? await sdlGraphql('deleteConfigFile', + 'mutation f($udoId: ID, $expectedVersion: Long) { deleteConfigFile(udoId: $udoId, expectedVersion: $expectedVersion) { udoId } }', + { udoId: assertSafeUdoId(udoId), expectedVersion }) + : await sdlGraphql('deleteConfigFile', + 'mutation f($id: ID, $expectedVersion: Long) { deleteConfigFile(id: $id, expectedVersion: $expectedVersion) { udoId } }', + { id: name, expectedVersion }); + + // The mutation returns null on success and does not echo the deleted object, + // so its response cannot distinguish "deleted" from "matched nothing". Confirm + // by re-reading. This is the house rule established by uamSetStatus in + // lib/s1.js: never treat a mutation response as proof, re-get and verify. + const still = await configFile({ name, udoId }); + if (still) { + throw new Error( + `deleteConfigFile: ${udoId ? `udoId ${udoId}` : name} still exists after the delete mutation ` + + `(version ${still.version}). The mutation reported no errors but nothing was removed.` + ); + } + return { status: 'success', deleted: udoId ? { udoId: String(udoId) } : { name }, raw: raw?.deleteConfigFile ?? null }; +} + +// The legacy REST config-file endpoints (/api/listFiles, /api/getFile, +// /api/putFile) are not wrapped here. They cannot see or modify a +// udoId-addressed dashboard, so their listing is unsafe for any "does this file +// exist" decision. The GraphQL operations above cover every namespace, +// including parsers, lookups, datatables and /automaticLookups. + // ─── V1 Query (schema discovery) ───────────────────────────────────────────── // Deprecated Feb 15 2027 but still the only way to get full event JSON per-event. // Use for schema discovery; use LRQ for hunting. @@ -126,5 +371,7 @@ export async function v1Query(filter, { maxCount = 5, startTime = '24h', endTime startTime, }; if (endTime) body.endTime = endTime; - return sdlFetch('POST', '/api/query', { body }); + // Read-only POST: opt back into status retry. Schema discovery iterates this + // once per data source, which is the workload that trips the SDL QPS cap. + return sdlFetch('POST', '/api/query', { body, allowRetry: true }); } diff --git a/mcp/s1-secops-mcp/lib/server-core.js b/mcp/s1-secops-mcp/lib/server-core.js index dc9741d..f52bd74 100644 --- a/mcp/s1-secops-mcp/lib/server-core.js +++ b/mcp/s1-secops-mcp/lib/server-core.js @@ -94,7 +94,7 @@ const PROMPTS = [ export const SERVER_INFO = { name: 's1-secops-mcp-server', - version: '1.3.1', + version: '1.3.3', }; export const PROTOCOL_VERSION = '2024-11-05'; diff --git a/mcp/s1-secops-mcp/package.json b/mcp/s1-secops-mcp/package.json index d435f35..46f9946 100644 --- a/mcp/s1-secops-mcp/package.json +++ b/mcp/s1-secops-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@pmoses-s1/s1-secops-mcp", - "version": "1.3.1", + "version": "1.3.3", "description": "MCP server orchestrating SentinelOne skills, APIs, and SOC analyst context. Stdio or Streamable HTTP transport with per-user bearer auth for team deployments.", "type": "module", "main": "index.js", @@ -20,7 +20,7 @@ "start": "node index.js", "start:http": "node index.js --transport http", "dev": "node --watch index.js", - "test": "node --test tests/smoke.test.mjs tests/stdio-transport.test.mjs tests/http-transport.test.mjs tests/ssrf-path.test.mjs tests/http-origin-guard.test.mjs tests/regressions-2026-07-29.test.mjs tests/regressions-2026-07-31.test.mjs", + "test": "node --test tests/smoke.test.mjs tests/stdio-transport.test.mjs tests/http-transport.test.mjs tests/ssrf-path.test.mjs tests/http-origin-guard.test.mjs tests/sdl-graphql.test.mjs tests/regressions-2026-07-29.test.mjs tests/regressions-2026-07-31.test.mjs", "regen:readme": "node scripts/regen-readme-tools-table.mjs" }, "engines": { diff --git a/mcp/s1-secops-mcp/tests/sdl-graphql.test.mjs b/mcp/s1-secops-mcp/tests/sdl-graphql.test.mjs new file mode 100644 index 0000000..ae913d3 --- /dev/null +++ b/mcp/s1-secops-mcp/tests/sdl-graphql.test.mjs @@ -0,0 +1,272 @@ +/** + * Behavioural tests for the GraphQL config-file layer in lib/sdl.js. + * + * These stub global.fetch, so they need no tenant and no credentials beyond + * the two env vars the module reads at call time. Every case here corresponds + * to a defect found in the 1.3.2 pre-release review; they exist so those + * defects cannot come back silently. + */ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; + +process.env.S1_CONSOLE_URL ||= 'https://tenant.sentinelone.net'; +process.env.S1_CONSOLE_API_TOKEN ||= 'test-token'; + +const { configFiles, configFile, putConfigFile, deleteConfigFile, v1Query } = await import('../lib/sdl.js'); + +/** Queue up canned responses; records every outbound request. */ +function stubFetch(responses) { + const calls = []; + global.fetch = async (url, opts) => { + calls.push({ url, body: opts.body ? JSON.parse(opts.body) : undefined, method: opts.method }); + const next = responses.shift(); + if (!next) throw new Error('stubFetch: ran out of queued responses'); + return { + ok: next.status === undefined || (next.status >= 200 && next.status < 300), + status: next.status ?? 200, + headers: new Map(), + text: async () => (typeof next.body === 'string' ? next.body : JSON.stringify(next.body)), + }; + }; + return calls; +} + +const gql = data => ({ body: { data } }); + +test('non-JSON 200 throws instead of reporting an empty listing', async () => { + stubFetch([{ body: 'SSO interstitial' }]); + await assert.rejects(configFiles(), /expected a JSON object/); +}); + +test('non-JSON 200 does not let a delete report success', async () => { + stubFetch([{ body: 'proxy' }]); + await assert.rejects(deleteConfigFile({ name: '/lookups/x.csv' }), /expected a JSON object/); +}); + +test('a non-array errors object still throws', async () => { + stubFetch([{ body: { errors: { message: 'boom' } } }]); + await assert.rejects(configFiles(), /boom/); +}); + +test('a payload with neither data nor errors throws', async () => { + stubFetch([{ body: { extensions: {} } }]); + await assert.rejects(configFiles(), /neither data nor errors/); +}); + +test('correlationId is surfaced from per-error extensions too', async () => { + stubFetch([{ body: { errors: [{ message: 'conflict', extensions: { correlationId: 'abc123' } }] } }]); + await assert.rejects(configFiles(), /abc123/); +}); + +test('mutations are NOT retried on 5xx (a retried create duplicates a dashboard)', async () => { + const calls = stubFetch([ + // Non-empty listing so the duplicate guard passes and we reach the mutation. + gql({ configFiles: [{ udoId: '1', name: '/dashboards/Other', version: 1 }] }), + { status: 502, body: 'bad gateway' }, + ]); + await assert.rejects(putConfigFile({ name: '/dashboards/New', content: '{}' }), /502/); + const mutations = calls.filter(c => c.body?.query?.startsWith('mutation')); + assert.equal(mutations.length, 1, 'the addConfigFile mutation must be sent exactly once'); +}); + +test('read-only queries ARE still retried on 5xx', async () => { + const calls = stubFetch([ + { status: 503, body: 'unavailable' }, + gql({ configFiles: [{ udoId: null, name: '/lookups/a.csv', version: 1 }] }), + ]); + const files = await configFiles(); + assert.equal(files.length, 1); + assert.equal(calls.length, 2, 'the query should have been retried once'); +}); + +test('expectedVersion is sent on name-addressed writes (server honours it)', async () => { + const calls = stubFetch([gql({ addConfigFile: { udoId: null, name: '/logParsers/P', version: 2 } })]); + await putConfigFile({ name: '/logParsers/P', content: 'x', expectedVersion: 1168977232 }); + const sent = calls[0].body; + assert.match(sent.query, /\$expectedVersion: Long/, 'mutation must declare $expectedVersion'); + assert.equal(sent.variables.expectedVersion, 1168977232, 'expectedVersion must be transmitted'); +}); + +test('expectedVersion is sent on udoId-addressed writes', async () => { + const calls = stubFetch([gql({ addConfigFile: { udoId: '123', name: '/dashboards/D', version: 2 } })]); + await putConfigFile({ udoId: '123', content: 'x', expectedVersion: 99 }); + assert.equal(calls[0].body.variables.expectedVersion, 99); +}); + +test('duplicate guard fails closed when the listing comes back empty', async () => { + stubFetch([gql({ configFiles: [] })]); + await assert.rejects( + putConfigFile({ name: '/dashboards/Existing', content: '{}' }), + /came back empty/, + ); +}); + +test('duplicate guard blocks a name-addressed write to an existing dashboard', async () => { + stubFetch([gql({ configFiles: [{ udoId: '777', name: '/dashboards/Existing', version: 1 }] })]); + await assert.rejects( + putConfigFile({ name: '/dashboards/Existing', content: '{}' }), + /already use that name.*777/s, + ); +}); + +test('duplicate guard is case- and whitespace-insensitive', async () => { + stubFetch([gql({ configFiles: [{ udoId: '777', name: '/dashboards/Existing', version: 1 }] })]); + await assert.rejects( + putConfigFile({ name: '/dashboards/existing ', content: '{}' }), + /already use that name/, + ); +}); + +test('a create of a genuinely new dashboard is allowed through the guard', async () => { + stubFetch([ + gql({ configFiles: [{ udoId: '777', name: '/dashboards/Other', version: 1 }] }), + gql({ addConfigFile: { udoId: '888', name: '/dashboards/Brand New', version: 1 } }), + ]); + const created = await putConfigFile({ name: '/dashboards/Brand New', content: '{}' }); + assert.equal(created.udoId, '888'); +}); + +test('delete verifies removal and throws when the file survives', async () => { + stubFetch([ + gql({ deleteConfigFile: null }), + gql({ configFile: { udoId: '5', name: '/dashboards/D', version: 9, content: '{}' } }), + ]); + await assert.rejects(deleteConfigFile({ udoId: '5' }), /still exists after the delete mutation/); +}); + +test('delete reports success only once the file is gone', async () => { + stubFetch([ + gql({ deleteConfigFile: null }), + gql({ configFile: null }), + ]); + const res = await deleteConfigFile({ udoId: '5' }); + assert.equal(res.status, 'success'); + assert.deepEqual(res.deleted, { udoId: '5' }); +}); + +test('configFile returns null (not a throw) when the server says "not found"', async () => { + // The server signals absence as a GraphQL error. Absence is a normal lookup + // outcome, so configFile normalises it to null; every other error propagates. + stubFetch([{ body: { errors: [{ message: 'Config file with name /lookups/x.csv not found.' }] } }]); + assert.equal(await configFile({ name: '/lookups/x.csv' }), null); +}); + +test('a deleted udoId returns null despite the generic error (listing disambiguates)', async () => { + // Live: configFile(udoId:) on a deleted dashboard returns "Something went + // wrong...", not "not found", and that is the same text a version conflict + // returns. The listing is what settles it. + stubFetch([ + { body: { errors: [{ message: 'Something went wrong. Please try again and if the issue persists contact Support.' }] } }, + gql({ configFiles: [{ udoId: '999', name: '/dashboards/Other', version: 1 }] }), + ]); + assert.equal(await configFile({ udoId: '5' }), null); +}); + +test('a generic error on a udoId that IS still present rethrows', async () => { + stubFetch([ + { body: { errors: [{ message: 'Something went wrong. Please try again.' }] } }, + gql({ configFiles: [{ udoId: '5', name: '/dashboards/Still Here', version: 1 }] }), + ]); + await assert.rejects(configFile({ udoId: '5' }), /Something went wrong/); +}); + +test('configFile still throws on a non-"not found" error when the file exists', async () => { + stubFetch([ + { body: { errors: [{ message: 'internal server explosion' }] } }, + gql({ configFiles: [{ udoId: null, name: '/lookups/x.csv', version: 1 }] }), + ]); + await assert.rejects(configFile({ name: '/lookups/x.csv' }), /internal server explosion/); +}); + +test('delete succeeds when the confirming read reports the file absent', async () => { + stubFetch([ + gql({ deleteConfigFile: null }), + { body: { errors: [{ message: 'Config file with name /lookups/x.csv not found.' }] } }, + ]); + const res = await deleteConfigFile({ name: '/lookups/x.csv' }); + assert.equal(res.status, 'success'); +}); + +test('delete still propagates a non-"not found" error from the confirming read', async () => { + stubFetch([ + gql({ deleteConfigFile: null }), + { body: { errors: [{ message: 'internal server explosion' }] } }, + gql({ configFiles: [{ udoId: null, name: '/lookups/x.csv', version: 1 }] }), + ]); + await assert.rejects(deleteConfigFile({ name: '/lookups/x.csv' }), /internal server explosion/); +}); + +test('a numeric udoId beyond the safe-integer range is rejected, not silently truncated', async () => { + stubFetch([]); + await assert.rejects(configFile({ udoId: 9007199254740993 }), /safe-integer range/); +}); + +test('no caller-supplied value is interpolated into the query document', async () => { + const calls = stubFetch([gql({ configFile: null })]); + await configFile({ name: '/lookups/a"}) { evil }' }); + assert.ok(!calls[0].body.query.includes('evil'), 'name must travel in variables, not the document'); + assert.equal(calls[0].body.variables.id, '/lookups/a"}) { evil }'); +}); + +// ─── transport vs GraphQL: absence must never be inferred from a transport error ─── + +test('a transport 404 whose body says "not found" is NOT treated as absence', async () => { + // The highest-value case in this file. sdlFetch builds the message as + // "SDL API POST ... -> 404: ", so a 404 page or WAF block containing + // the words "not found" used to satisfy the absence regex, and a delete + // would then accept it as proof the file was gone. + stubFetch([{ status: 404, body: { error: 'not found' } }]); + await assert.rejects(configFile({ name: '/lookups/x.csv' }), /404/); +}); + +test('a transport 404 does not let a delete report success', async () => { + stubFetch([ + gql({ deleteConfigFile: null }), + { status: 404, body: 'nginx: not found' }, + ]); + await assert.rejects(deleteConfigFile({ name: '/lookups/x.csv' }), /404/); +}); + +test('a listing failure during disambiguation preserves the ORIGINAL error', async () => { + stubFetch([ + { body: { errors: [{ message: 'There are conflicting changes in the file.' }] } }, + { status: 500, body: 'listing exploded' }, + { status: 500, body: 'listing exploded' }, + { status: 500, body: 'listing exploded' }, + { status: 500, body: 'listing exploded' }, + ]); + await assert.rejects( + configFile({ udoId: '5' }), + err => /conflicting changes/.test(err.message) && /absence check failed/.test(err.message), + ); +}); + +// ─── duplicate guard: namespace test must be case-insensitive ─── + +test('a case-variant /Dashboards/ path does NOT bypass the duplicate guard', async () => { + stubFetch([gql({ configFiles: [{ udoId: '777', name: '/dashboards/AI Usage', version: 1 }] })]); + await assert.rejects( + putConfigFile({ name: '/Dashboards/AI Usage', content: '{}' }), + /already use that name/, + ); +}); + +test('the guard still runs (and fails closed) for a case-variant path', async () => { + stubFetch([gql({ configFiles: [] })]); + await assert.rejects( + putConfigFile({ name: '/DASHBOARDS/Something', content: '{}' }), + /came back empty/, + ); +}); + +// ─── v1Query is a read-only POST and must keep its backoff ─── + +test('v1Query retries on 5xx (schema discovery is the workload that hits the QPS cap)', async () => { + const calls = stubFetch([ + { status: 503, body: 'unavailable' }, + { body: { matches: [{ attributes: { a: 1 } }] } }, + ]); + const res = await v1Query("dataSource.name='X'"); + assert.equal(res.matches.length, 1); + assert.equal(calls.length, 2, 'v1Query must back off and retry, not fail on the first 503'); +}); diff --git a/mcp/s1-secops-mcp/tools/sdl-api.js b/mcp/s1-secops-mcp/tools/sdl-api.js index 0b9349e..4e7d9ea 100644 --- a/mcp/s1-secops-mcp/tools/sdl-api.js +++ b/mcp/s1-secops-mcp/tools/sdl-api.js @@ -2,48 +2,76 @@ * SDL API tools: sdl-api, sdl-dashboard, sdl-log-parser skills * * Tools: - * sdl_list_files List all config files on the SDL tenant - * sdl_get_file Get file content and version (parsers, dashboards, alerts, lookups) + * sdl_list_files List every config file on the SDL tenant (GraphQL configFiles) + * sdl_get_file Get file content and version, by path or udoId * sdl_put_file Deploy or update a config file (with optimistic locking) * sdl_delete_file Delete a config file * hec_ingest Ingest raw logs/events into SDL via the HEC endpoint (replaces uploadLogs) + * + * All four config-file tools run on `POST /sdl/v2/graphql`. The legacy REST + * `/sdl/api/*File` endpoints are NOT used: they silently omit every + * udoId-addressed dashboard (1,914 vs 2,264 files on a live tenant) and return + * `success/noSuchFile` for any of them. */ -import { listFiles, getFile, putFile, deleteFile } from '../lib/sdl.js'; +import { configFiles, configFile, putConfigFile, deleteConfigFile } from '../lib/sdl.js'; import { hecIngest } from '../lib/hec.js'; +const UDOID_NOTE = + 'Dashboards are addressed by udoId, everything else by path. The console shows a dashboard as ' + + '"/dashboards/id//" in its Configuration Files grid; that display string is NOT a path, ' + + 'the number in it is the udoId and the real path is "/dashboards/". ' + + 'udoId assignment is by namespace: only /dashboards/ files have one, /lookups/, /datatables/, ' + + '/logParsers/ and /automaticLookups are all name-addressed with udoId null.'; + export const tools = [ // ─── sdl_list_files ─────────────────────────────────────────────────────── { name: 'sdl_list_files', - description: `List all configuration files stored in the SDL tenant. Returns all paths organized by type: /logParsers/, /dashboards/, /alerts/, /lookups/, /datatables/. Use this to discover what parsers and dashboards are already deployed, or to find a file path before calling sdl_get_file or sdl_put_file.`, + description: `List every configuration file on the SDL tenant via the GraphQL configFiles query: /logParsers/, /dashboards/, /alerts/, /lookups/, /datatables/, /automaticLookups. Returns {udoId, name, readOnly, version} per file. ${UDOID_NOTE} Use this to discover what is deployed, and to resolve a dashboard name to the udoId that sdl_get_file/sdl_put_file need. Never conclude a file is absent from a listing produced any other way; the legacy REST listing omits ~350 dashboards.`, inputSchema: { type: 'object', - properties: {}, + properties: { + pathPrefix: { + type: 'string', + description: 'Optional filter, e.g. "/dashboards/" or "/logParsers/". Applied client-side to the full listing.', + }, + }, required: [], }, - async handler() { - const result = await listFiles(); - return JSON.stringify(result, null, 2); + async handler({ pathPrefix } = {}) { + let files = await configFiles(); + if (pathPrefix) files = files.filter(f => (f.name || '').startsWith(pathPrefix)); + return JSON.stringify({ count: files.length, files }, null, 2); }, }, // ─── sdl_get_file ───────────────────────────────────────────────────────── { name: 'sdl_get_file', - description: `Get the content and current version number of a SDL configuration file. Use before sdl_put_file to read the current version for optimistic locking (pass the returned version as expectedVersion). Supports any file type: parsers (/logParsers/), dashboards (/dashboards/), alerts (/alerts/), lookups (/lookups/), datatables (/datatables/). Always read before overwriting; this prevents concurrent-edit conflicts.`, + description: `Get the content and current version of a SDL configuration file. Pass "path" for name-addressed files (parsers, lookups, datatables, alerts, /automaticLookups) or "udoId" for a dashboard. ${UDOID_NOTE} Read this before sdl_put_file and pass the returned version as expectedVersion so the write is optimistically locked. If a lookup by path returns nothing, the file is probably udoId-addressed: list it with sdl_list_files and retry with its udoId rather than reporting it as missing.`, inputSchema: { type: 'object', properties: { path: { type: 'string', - description: 'Full SDL config path, e.g. "/logParsers/FortiGate" or "/dashboards/SOC-Overview". Get the path from sdl_list_files.', + description: 'Full SDL config path, e.g. "/logParsers/FortiGate" or "/lookups/assets.csv".', + }, + udoId: { + type: 'string', + description: 'Dashboard udoId, e.g. "3559330396332032". Get it from sdl_list_files. Required for /dashboards/ files.', }, }, - required: ['path'], + required: [], }, - async handler({ path }) { - const result = await getFile(path); + async handler({ path, udoId }) { + const result = await configFile({ name: path, udoId }); + if (!result) { + return JSON.stringify({ + status: 'notFound', + hint: 'No file at that address. If this is a dashboard, it is udoId-addressed: run sdl_list_files with pathPrefix "/dashboards/" and retry with its udoId.', + }, null, 2); + } return JSON.stringify(result, null, 2); }, }, @@ -51,13 +79,17 @@ export const tools = [ // ─── sdl_put_file ───────────────────────────────────────────────────────── { name: 'sdl_put_file', - description: `Deploy or update a SDL configuration file. Always call sdl_get_file first to obtain the current expectedVersion; this prevents overwriting concurrent edits. If creating a new file, omit expectedVersion. File type conventions: parsers go to /logParsers/, dashboards to /dashboards/, alerts to /alerts/, lookups to /lookups/. Authorised by S1_CONSOLE_API_TOKEN.`, + description: `Create or update a SDL configuration file. To create, pass "path". To update, pass the file's current address plus expectedVersion from sdl_get_file. ${UDOID_NOTE} CRITICAL for dashboards: update by udoId, never by path. A path-addressed write to /dashboards/ does not update, it creates a duplicate file sharing the name; that is how one tenant accumulated 152 copies of "/dashboards/AI Usage". This tool refuses path-addressed writes to /dashboards/ on an existing file for that reason. Name-addressed writes to every other namespace update in place normally.`, inputSchema: { type: 'object', properties: { path: { type: 'string', - description: 'Full SDL config path, e.g. "/logParsers/MyParser" or "/dashboards/SOC-Ops".', + description: 'Full SDL config path, e.g. "/logParsers/MyParser". Use for creates, and for updates to non-dashboard files.', + }, + udoId: { + type: 'string', + description: 'Dashboard udoId. REQUIRED to update an existing dashboard; a path-addressed dashboard write duplicates instead of updating.', }, content: { type: 'string', @@ -65,13 +97,13 @@ export const tools = [ }, expectedVersion: { type: 'number', - description: 'Current file version from sdl_get_file. Required for updates to enable optimistic locking. Omit only when creating a new file.', + description: 'Current file version from sdl_get_file, for optimistic locking. Enforced on BOTH address forms, path and udoId: a stale value is rejected and nothing is written. Omit only when creating a new file.', }, }, - required: ['path', 'content'], + required: ['content'], }, - async handler({ path, content, expectedVersion }) { - const result = await putFile(path, content, expectedVersion); + async handler({ path, udoId, content, expectedVersion }) { + const result = await putConfigFile({ name: path, udoId, content, expectedVersion }); return JSON.stringify(result, null, 2); }, }, @@ -79,23 +111,27 @@ export const tools = [ // ─── sdl_delete_file ────────────────────────────────────────────────────── { name: 'sdl_delete_file', - description: `Delete a SDL configuration file (parser, dashboard, alert, lookup, datatable). Use with caution; deletion is permanent. Always read the file with sdl_get_file first to confirm you have the right path and version.`, + description: `Delete a SDL configuration file (parser, dashboard, alert, lookup, datatable). Deletion is permanent. Pass "udoId" for dashboards, "path" for everything else. ${UDOID_NOTE} Always read the file with sdl_get_file first to confirm the address and to get expectedVersion.`, inputSchema: { type: 'object', properties: { path: { type: 'string', - description: 'Full SDL config path to delete.', + description: 'Full SDL config path to delete (non-dashboard files).', + }, + udoId: { + type: 'string', + description: 'Dashboard udoId to delete. Required for /dashboards/ files.', }, expectedVersion: { type: 'number', description: 'Current file version for optimistic locking (from sdl_get_file). Strongly recommended.', }, }, - required: ['path'], + required: [], }, - async handler({ path, expectedVersion }) { - const result = await deleteFile(path, expectedVersion); + async handler({ path, udoId, expectedVersion }) { + const result = await deleteConfigFile({ name: path, udoId, expectedVersion }); return JSON.stringify(result, null, 2); }, }, diff --git a/plugins/s1-secops-skills/.claude-plugin/plugin.json b/plugins/s1-secops-skills/.claude-plugin/plugin.json index b05f36c..2782182 100644 --- a/plugins/s1-secops-skills/.claude-plugin/plugin.json +++ b/plugins/s1-secops-skills/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "s1-secops-skills", - "version": "1.3.0", + "version": "1.3.1", "description": "SentinelOne SecOps skills for Claude: PowerQuery threat hunting and STAR/Custom Detection rules; Management Console API; Singularity Data Lake API; SDL dashboards; log parsing (OCSF); Hyperautomation SOAR; z-score anomaly baselining; autonomous DFIR alert investigation (soc-investigator); and one-prompt SDL solutions: source onboarding, asset enrichment, UEBA, ingest health, detection exclusions, Risk-Based Alerting, alert noise reduction, and Detection as Code.", "author": { "name": "Prithvi Moses", diff --git a/plugins/s1-secops-skills/CLAUDE.md b/plugins/s1-secops-skills/CLAUDE.md index 6a9789d..e97738c 100644 --- a/plugins/s1-secops-skills/CLAUDE.md +++ b/plugins/s1-secops-skills/CLAUDE.md @@ -9,6 +9,7 @@ You are a **Principal SOC Analyst** in a SentinelOne SecOps environment. Mission Discovery is mandatory before querying any source, but it runs ONCE PER PROJECT and is REUSED from a cached, versioned file. Tenant-wide per-source schema discovery is the single most expensive part of an investigation; caching it is the primary cost optimization. **Step 0, cache check (FIRST, before any enumeration or discovery):** + - Look for `s1_sdl_schema_cache.json` in the project root. - If it EXISTS and `schema_cache_version` (ISO-8601 UTC timestamp) is within `ttl_days` (default 30): load it, treat its `data_source_enumeration` + `schemas` as source of truth, SKIP Steps 1-2 (no re-enumeration, no re-discovery), go straight to triage. - If MISSING or STALE (older than `ttl_days`): run Steps 1-2, write/overwrite the cache, set `schema_cache_version` to the current UTC timestamp. @@ -17,7 +18,8 @@ Discovery is mandatory before querying any source, but it runs ONCE PER PROJECT **This Step 0 rule supersedes any "every session" / "discover fresh each session" phrasing elsewhere in this document (Core Mindset, Sections 6-7):** enumerate and discover once per project, reuse from the versioned cache, refresh on expiry or per-source on demand. Schemas still drift (parser edits, reserved-field rewrites, ingestion changes), which is exactly what `ttl_days` and per-source re-discovery guard against. **Step 1, enumerate data sources (ONLY on cache miss/stale):** -``` + +```text | group UniqueDataSourceNames = array_agg_distinct( dataSource.name ), UniqueVendors = array_agg_distinct( dataSource.vendor ), UniqueCategories = array_agg_distinct( dataSource.category ) @@ -25,6 +27,7 @@ Discovery is mandatory before querying any source, but it runs ONCE PER PROJECT ``` **Step 2, from the returned list:** + - For EVERY source returned, including S1 internal streams (`alert`, `vulnerability`, `misconfiguration`, `asset`, `finding`, `ActivityFeed`, `Identity`, `indicator`) and every third-party source, use the field schema recorded in the project cache under the Step 0 trust/refresh rules. - Do NOT assume any field namespace (vendor-prefixed `..*`, OCSF `src.ip.address` / `dst.ip.address` / `actor.user.name`, `unmapped.*`, or anything else) applies to a source unless confirmed by the cache or fresh discovery. - **Trailing-underscore convention:** field names ending in `_` (e.g. `severity_`, `status_`, `classification_`) are SDL's auto-rename when source data collides with an SDL reserved name. The underscored form IS the canonical, queryable field, not a sparse alternate. Numeric OCSF variants (`severity_id`, `status_id`, `class_uid`) live alongside the underscored string fields. @@ -125,6 +128,7 @@ A SOC peer should be able to paste your queries and reproduce the answer. ## Investigation Workflow ### 1. Triage & Context Gathering + - `get_alert`: read severity, classification, detection source, analyst verdict. - **CRITICAL CHECK: read `get_alert_notes` and `get_alert_history` BEFORE proceeding.** An MDR/analyst verdict of False Positive, Benign, or Resolved takes precedence; do NOT override it without new evidence they did not have. If FP, note it and move on, do not escalate. - `get_inventory_item`: OS, role, location, criticality, agent health. @@ -146,6 +150,7 @@ Every IP, domain, URL, or file hash encountered MUST be enriched before a verdic #### Relationship pivot tools (EXPAND the investigation after initial reports) **File: `get_file_relationship(hash, relationship)`, 41 pivot types.** + - Behavioural (`behaviours`, `dropped_files`, `contacted_domains`, `contacted_ips`, `contacted_urls`): what the file DOES when executed; C2 infrastructure, payloads dropped, network footprint. - Execution chain (`execution_parents`, `bundled_files`, `compressed_parents`, `email_parents`, `email_attachments`): how it arrived; archive bundle, email attachment, or parent process. - Embedded content (`embedded_domains`, `embedded_ips`, `embedded_urls`, `urls_for_embedded_js`): IOCs hardcoded in the binary; C2 addresses, download URLs, exfil endpoints. @@ -178,19 +183,23 @@ The critical decision point: systematically determine true positive, suspicious, | **First/Last Submission** | Recently submitted (fresh IOC, active campaign) | Very old with no recent activity | #### Step 2: Behavioural correlation (files) + For any suspicious hash ALWAYS pivot: `behaviours` (what it does), `contacted_domains` and `contacted_ips` (where it calls home), `dropped_files` (what it deploys), `execution_parents` (what launched it). TP confidence boosters: contacts known malicious IPs/domains; drops additional executables/scripts; behaviour shows credential access, persistence installation, or lateral movement; execution chain traces to a phishing email or exploit. #### Step 3: Infrastructure pivoting (network IOCs) + Pivot to the full attack infrastructure: `get_ip_relationship` with `communicating_files`, `resolutions`, `historical_ssl_certificates`; `get_domain_report(domain, relationships=["subdomains", "siblings", "resolutions", "communicating_files"])`. Correlation signals: multiple malicious files communicating with the same IP = confirmed C2 server; domain registered < 30 days ago with privacy-protected WHOIS = suspicious; SSL certificate shared across multiple domains = attacker infrastructure cluster; subdomain patterns `update.`, `cdn.`, `api.`, `mail.` = mimicking legitimate services. #### Step 4: Threat actor attribution + For EVERY confirmed malicious IOC check `related_threat_actors` (file, IP, and URL relationship pivots; domain via `relationships=["related_threat_actors"]`). If a group is identified: research their TTPs and map to MITRE ATT&CK; hunt their OTHER known IOCs environment-wide via `purple_ai` + `powerquery`; check their typical persistence, lateral movement, and exfiltration methods; assess whether the group typically targets your industry/region. #### Step 5: Cross-reference with SentinelOne telemetry + Correlate findings back into the environment: hunt other endpoints contacting the same C2 (`purple_ai`); same hash on other endpoints; similar behaviour patterns (process trees, registry modifications, scheduled tasks); check the asset for exploitable vulnerabilities (`search_vulnerabilities`) aligning with the actor's known exploitation techniques. #### Verdict Decision Matrix @@ -220,6 +229,7 @@ With none of these, the maximum classification is **SUSPICIOUS, Pending Confirma --- ### 4. Threat Hunting with Purple AI & PowerQuery + - Use `purple_ai` to generate PowerQueries from natural language; do NOT hand-write PowerQuery syntax. - Always use `get_timestamp_range` for time windows (default: 24 hours). - Hunt lateral movement, persistence, privilege escalation, data staging, and exfiltration related to the initial finding; check the same IOCs/TTPs on other endpoints. @@ -243,6 +253,7 @@ Breadth (fleet-wide hash/TTP sweep, cross-source correlation) is necessary but n Every item above is a query to execute against this session's telemetry, not a topic to consider. Deliver depth AND breadth (this checklist plus the Section 4/6 fleet sweep and cross-source correlation); one without the other is incomplete. ### 5. Vulnerability & Misconfiguration Correlation + - `search_vulnerabilities` / `get_vulnerability` on the affected asset, especially active exploits or high EPSS. Prioritize `exploitedInTheWild: true` or `kevAvailable: true`. - `search_misconfigurations` / `get_misconfiguration` on the same asset for enablers of the attack. - Cross-reference: if a threat actor is attributed, check for vulnerabilities that group commonly exploits. @@ -283,7 +294,7 @@ Use `purple_ai` for OCSF sources and confirmed namespaces for non-OCSF. After ea When a suspicious IOC (IP, domain, hash, user, hostname) appears in any one source, immediately hunt it across ALL other sources with an `OR` clause over every confirmed-populated IP / hostname / username field: -``` +```text | filter( == "SUSPICIOUS_IP" OR == "SUSPICIOUS_IP" OR src.ip.address == "SUSPICIOUS_IP" @@ -314,19 +325,22 @@ Many third-party sources (firewalls, SIEMs, appliances forwarding raw syslog, CE ### Schema discovery workflow (any unknown source) **Step 1, confirm the source is ingesting and get its exact name:** -``` + +```text | group UniqueDataSourceNames = array_agg_distinct( dataSource.name ) | limit 100 ``` **Step 2, probe for field population:** -``` + +```text | filter( dataSource.name == "TARGET_SOURCE_NAME" ) | group Fields = array_agg_distinct( dataSource.name ), Vendors = array_agg_distinct( dataSource.vendor ) | limit 5 ``` **Step 3, attempt namespace variants one at a time until non-null results**, using the shape `| filter( dataSource.name == "TARGET_SOURCE_NAME" ) | columns timestamp, | filter( == * ) | limit 10`: + 1. Vendor-prefixed `..` (most common for syslog sources) 2. Unmapped: `unmapped.src, unmapped.dst, unmapped.proto, unmapped.action, unmapped.msg` 3. Generic SDL network: `src.ip.address, dst.ip.address, dst.port.number, ipProtocol, networkAction, direction` @@ -338,7 +352,7 @@ Many third-party sources (firewalls, SIEMs, appliances forwarding raw syslog, CE After discovery confirms the action, source-IP, destination-IP, destination-port, protocol, and direction fields: -``` +```text | filter( dataSource.name == "" ) | filter( == * ) | columns timestamp, , , , @@ -397,7 +411,8 @@ Apply to every identity source query; flag matches for threat-intel enrichment a | **Lateral movement via legitimate credentials** | User authenticates to systems never accessed before | T1021 Remote Services | High | PQ pattern, auth outside business hours (any identity source): -``` + +```text | filter( dataSource.name == "" ) | filter( == "" ) | columns timestamp, actor.user.name, actor.user.email_addr, src_endpoint.ip, @@ -574,7 +589,7 @@ Use installed skills eagerly; do not hand-author SDL config files, Hyperautomati - `s1-secops-skills:powerquery`: author/optimise/debug/explain any PowerQuery (STAR rule body, dashboard panel, hunt, alert). LRQ runner, syntax reference, performance rules (filter early, group narrow, `top` over `group` for huge ranges, `transpose` LAST, escape regex, percentile rules). - `s1-secops-skills:mgmt-console-api`: site / agent / threat / IOC / Custom Detection rule console operations; deploying STAR rules; UAM alert triage. `S1Client`, endpoint index, UAM GraphQL wrapper, `pq.py` LRQ runner, IOC lifecycle test, asset linkage ref. -- `s1-secops-skills:sdl-api`: SDL configuration files (parsers, dashboards, lookups), custom log ingestion, V1 query for ad-hoc <24h stats. `SDLClient.put_file` / `get_file` / `list_files`, ingestion methods, key-matrix auto-routing. +- `s1-secops-skills:sdl-api`: SDL configuration files (parsers, dashboards, lookups), custom log ingestion, V1 query for ad-hoc <24h stats. `SDLClient.config_files` / `config_file` / `put_config_file` / `delete_config_file` over `POST /sdl/v2/graphql`; the legacy `list_files` / `get_file` / `put_file` REST methods cannot see udoId-addressed dashboards. One console token authorises everything. - `s1-secops-skills:sdl-dashboard`: building or editing any SDL dashboard JSON. Panel-type cheatsheet, community examples, query performance rules, parameters & filters. - `s1-secops-skills:hyperautomation`: authoring SOAR / playbook / alert-response workflow JSON. Workflow envelope, building blocks, action types, integration warnings, examples. - `s1-secops-skills:sdl-log-parser`: authoring/debugging an SDL `/logParsers/` parser (CEF, syslog, key=value, multi-line). Parser DSL, end-to-end validation via `putFile → hec_ingest → query`. @@ -592,7 +607,7 @@ For any investigation culminating in deliverables: 3. **Schema for every source you'll query** via the cache / Section 7 workflow; persist dumps to `outputs/sdl_schemas_.json`. 4. **Hunt, enrich, correlate:** Purple MCP hunts, threat-intel MCP for every IOC, cross-source correlation. 5. **Build deliverables:** dashboard JSON via `sdl-dashboard`, workflows via `hyperautomation`, detection rules via `mgmt-console-api`, report via `docx`. -6. **Deploy live:** `SDLClient.put_file('/dashboards/')` for dashboards, `POST /web/api/v2.1/cloud-detection/rules` for STAR rules. Read the existing version first; pass `expected_version` on overwrite. +6. **Deploy live:** dashboards via `SDLClient.put_config_file()` on `POST /sdl/v2/graphql`, creating by name once and addressing by `udo_id` with `expected_version` on every write after that; a name-addressed write to an existing dashboard is refused because it duplicates. STAR rules via `POST /web/api/v2.1/cloud-detection/rules`. Read the existing version first; pass `expected_version` on overwrite. 7. **Verify:** re-fetch deployed artifacts, confirm versions, run a sample query against each rule's PQ body to confirm it parses. --- @@ -629,7 +644,7 @@ Common across tenants and platform versions; apply preemptively. ### Endpoint and wire format -``` +```text POST https://.sentinelone.net/sdl/v2/api/queries GET https://.sentinelone.net/sdl/v2/api/queries/{id}?lastStepSeen={n} DELETE https://.sentinelone.net/sdl/v2/api/queries/{id} @@ -640,6 +655,7 @@ DELETE https://.sentinelone.net/sdl/v2/api/queries/{id} **Auth:** `Authorization: Bearer `, the same token the Mgmt API uses with `ApiToken` prefix. Using `ApiToken` prefix on `/sdl/v2/api/queries` returns HTTP 500. **Launch body:** + ```json { "queryType": "PQ", @@ -650,6 +666,7 @@ DELETE https://.sentinelone.net/sdl/v2/api/queries/{id} "tenant": true } ``` + Query string goes inside `pq.query`, NOT top level. `queryType` must be uppercase `"PQ"`; omitting it returns HTTP 400. **`X-Dataset-Query-Forward-Tag` is mandatory.** Capture it from the POST response header and echo it on every GET and DELETE; without it the routing layer rejects the request. Session-scoped: one client's tag cannot be used by another. @@ -715,7 +732,8 @@ This file improves itself. At the END of every session, before signing off: 5. **Apply on approval** here, and for schema/field facts also to `s1_sdl_schema_cache.json` with a `schema_cache_version` bump. ### Session Learnings Ledger -_Compounded and lean. Promote stable items into formal sections, then prune. Newest appended; merge rather than restate._ + +_Compounded and lean. Promote stable items into formal sections, then prune. Newest appended; merge rather than restate.* - **Windows auth triage:** `winEventLog.description` holds the full human-readable Target/Source/Status block; use it when `winEventLog.data.event.eventData.*` field names are uncertain. Core IDs: 4625 fail, 4624 success, 4768/4769 Kerberos, 4771 pre-auth fail, 4776 NTLM, 4740 lockout. - **4625 SubStatus decode:** `0xC000006A` wrong password (user exists), `0xC0000064` no such user, `0xC0000234` locked, `0xC0000072` disabled, `0xC000006F`/`0xC0000070` time/workstation restriction. diff --git a/plugins/s1-secops-skills/README.md b/plugins/s1-secops-skills/README.md index 5cf6052..7ca94de 100644 --- a/plugins/s1-secops-skills/README.md +++ b/plugins/s1-secops-skills/README.md @@ -396,7 +396,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o "env": { "S1_CONSOLE_URL": "https://usea1-yourorg.sentinelone.net", "S1_CONSOLE_API_TOKEN": "eyJ...your-api-token...", - "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net", + "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net" } }, "purple-mcp": { @@ -446,7 +446,7 @@ Full key reference, token types, and resolution order: **[docs/credentials.md](. **Step 3: Install the plugin (all eight skills)** -Download the latest plugin, [`s1-secops-skills-v1.2.5.plugin`](./dist/), from the `dist/` folder. In Claude Desktop: **Cowork → Customize → Browse plugins**, then upload the `.plugin` file. All seven skills install in one step. +Download the latest plugin, [`s1-secops-skills-v1.3.1.plugin`](./dist/), from the `dist/` folder. In Claude Desktop: **Cowork → Customize → Browse plugins**, then upload the `.plugin` file. All seven skills install in one step. Then create a Cowork project named `PrincipalSOCAnalyst` and select a folder for it. The Docker image ships a default CLAUDE.md, so dropping your own [`CLAUDE.md`](./CLAUDE.md) into the folder is only needed if you want to customise the persona. diff --git a/plugins/s1-secops-skills/dist/hyperautomation.skill b/plugins/s1-secops-skills/dist/hyperautomation.skill index 11d1b5c..012d8e3 100644 Binary files a/plugins/s1-secops-skills/dist/hyperautomation.skill and b/plugins/s1-secops-skills/dist/hyperautomation.skill differ diff --git a/plugins/s1-secops-skills/dist/mgmt-console-api.skill b/plugins/s1-secops-skills/dist/mgmt-console-api.skill index 6010d3a..89d837d 100644 Binary files a/plugins/s1-secops-skills/dist/mgmt-console-api.skill and b/plugins/s1-secops-skills/dist/mgmt-console-api.skill differ diff --git a/plugins/s1-secops-skills/dist/powerquery.skill b/plugins/s1-secops-skills/dist/powerquery.skill index cc9b076..f5e8f4c 100644 Binary files a/plugins/s1-secops-skills/dist/powerquery.skill and b/plugins/s1-secops-skills/dist/powerquery.skill differ diff --git a/plugins/s1-secops-skills/dist/s1-secops-skills-v1.3.0.plugin b/plugins/s1-secops-skills/dist/s1-secops-skills-v1.3.1.plugin similarity index 76% rename from plugins/s1-secops-skills/dist/s1-secops-skills-v1.3.0.plugin rename to plugins/s1-secops-skills/dist/s1-secops-skills-v1.3.1.plugin index 4c8cfde..f5e80cb 100644 Binary files a/plugins/s1-secops-skills/dist/s1-secops-skills-v1.3.0.plugin and b/plugins/s1-secops-skills/dist/s1-secops-skills-v1.3.1.plugin differ diff --git a/plugins/s1-secops-skills/dist/sdl-api.skill b/plugins/s1-secops-skills/dist/sdl-api.skill index 83acbcd..845d0f7 100644 Binary files a/plugins/s1-secops-skills/dist/sdl-api.skill and b/plugins/s1-secops-skills/dist/sdl-api.skill differ diff --git a/plugins/s1-secops-skills/dist/sdl-dashboard.skill b/plugins/s1-secops-skills/dist/sdl-dashboard.skill index 3b3d012..5697206 100644 Binary files a/plugins/s1-secops-skills/dist/sdl-dashboard.skill and b/plugins/s1-secops-skills/dist/sdl-dashboard.skill differ diff --git a/plugins/s1-secops-skills/dist/sdl-log-parser.skill b/plugins/s1-secops-skills/dist/sdl-log-parser.skill index aae8046..4de53e2 100644 Binary files a/plugins/s1-secops-skills/dist/sdl-log-parser.skill and b/plugins/s1-secops-skills/dist/sdl-log-parser.skill differ diff --git a/plugins/s1-secops-skills/dist/sdl-solutions.skill b/plugins/s1-secops-skills/dist/sdl-solutions.skill index fafaf5d..4ac5b13 100644 Binary files a/plugins/s1-secops-skills/dist/sdl-solutions.skill and b/plugins/s1-secops-skills/dist/sdl-solutions.skill differ diff --git a/plugins/s1-secops-skills/dist/soc-investigator.skill b/plugins/s1-secops-skills/dist/soc-investigator.skill index e6b41be..0ba8b2a 100644 Binary files a/plugins/s1-secops-skills/dist/soc-investigator.skill and b/plugins/s1-secops-skills/dist/soc-investigator.skill differ diff --git a/plugins/s1-secops-skills/docs/architecture.md b/plugins/s1-secops-skills/docs/architecture.md index c6ca878..f062125 100644 --- a/plugins/s1-secops-skills/docs/architecture.md +++ b/plugins/s1-secops-skills/docs/architecture.md @@ -110,7 +110,7 @@ The skills are read-only procedural knowledge. They do not execute API calls dir ## Authentication flow -All four API surfaces use a single service user token (`S1_CONSOLE_API_TOKEN`) plus surface-specific keys for SDL write operations. +All four API surfaces use a single service user token (`S1_CONSOLE_API_TOKEN`), including every SDL read and write operation. ``` S1_CONSOLE_API_TOKEN ──► S1 Mgmt REST API (Authorization: ApiToken ) diff --git a/plugins/s1-secops-skills/docs/credentials.md b/plugins/s1-secops-skills/docs/credentials.md index 008dad5..fa89f0c 100644 --- a/plugins/s1-secops-skills/docs/credentials.md +++ b/plugins/s1-secops-skills/docs/credentials.md @@ -10,7 +10,7 @@ This is the canonical credentials reference for every install path (Docker quick { "S1_CONSOLE_URL": "https://usea1-yourorg.sentinelone.net", "S1_CONSOLE_API_TOKEN": "eyJ...your-api-token...", - "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net", + "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net" } ``` @@ -20,7 +20,7 @@ This is the canonical credentials reference for every install path (Docker quick | `S1_CONSOLE_API_TOKEN` | Mgmt Console REST, PowerQuery LRQ, UAM GraphQL, Purple AI GraphQL, SDL config ops (Management Z SP5+) | Settings → Users → Service Users → Create Service User → copy the API token. | | `S1_HEC_INGEST_URL` | UAM alert/indicator ingest, SDL log ingest | Region-specific HEC host, e.g. `https://ingest.us1.sentinelone.net`. Look up yours at [SentinelOne Endpoint URLs by Region](https://community.sentinelone.com/s/article/000004961). | -`S1_CONSOLE_URL` and `S1_CONSOLE_API_TOKEN` are the minimum required for most operations. Add the SDL keys only when you need log ingest or parser/dashboard deployment. +`S1_CONSOLE_URL` and `S1_CONSOLE_API_TOKEN` are the minimum required, and between them they authorise every SDL operation including parser and dashboard deployment. Add `S1_HEC_INGEST_URL` only when you need HEC log or alert ingest. The scoped SDL keys (`SDL_CONFIG_READ_KEY`, `SDL_CONFIG_WRITE_KEY`, `SDL_LOG_READ_KEY`, `SDL_LOG_WRITE_KEY`, `SDL_XDR_URL`) are retired and are no longer read. ```python ``` @@ -54,7 +54,7 @@ The skills auto-detect and fall back to this key when the primary token is rejec Credentials are resolved in this priority order (highest wins): -1. Environment variables (`S1_CONSOLE_URL`, `S1_CONSOLE_API_TOKEN`, `SDL_*`) +1. Environment variables (`S1_CONSOLE_URL`, `S1_CONSOLE_API_TOKEN`, `S1_HEC_INGEST_URL`) 2. `credentials.json` in the Cowork project folder (auto-discovered by the plugin's SessionStart hook, and by s1-secops-mcp walking up the directory tree) 3. `~/.config/sentinelone/credentials.json` (fallback for terminal/Claude Code sessions) @@ -76,7 +76,7 @@ JSON ${EDITOR:-nano} "$PROJECT_DIR/credentials.json" ``` -Add any `SDL_*` keys you need alongside these (full list in the [keys table](#credentialsjson-keys) above). +Add `S1_HEC_INGEST_URL` alongside these if you need HEC ingest (full list in the [keys table](#credentialsjson-keys) above). When creating the project in Cowork, add `credentials.json` and `CLAUDE.md` under **Add files** so Claude has access to both in every session. diff --git a/plugins/s1-secops-skills/docs/docker.md b/plugins/s1-secops-skills/docs/docker.md index fbbb729..71900de 100644 --- a/plugins/s1-secops-skills/docs/docker.md +++ b/plugins/s1-secops-skills/docs/docker.md @@ -7,7 +7,7 @@ This page is the full Docker reference for everything beyond those three steps: One Docker image bundles all three MCPs (`s1-secops-mcp`, `purple-mcp`, `virustotal-mcp`) so you only need Docker on the host: no Node, Python, or `uv`. This is the recommended path for most users, and the only option on machines where IT policy blocks `npm install -g` or `pip install`. Image: `ghcr.io/pmoses-s1/s1-mcps` -Tags: `latest` (newest published), `1` / `1.2` / `1.2.3` (pinned semver, current), `sha-` (any commit). Pin an explicit version for reproducible, forensically consistent installs. +Tags: `latest` (newest published), `1` / `1.3` / `1.3.1` (pinned semver, current), `sha-` (any commit). Pin an explicit version for reproducible, forensically consistent installs. - [Prerequisites](#prerequisites) - [Troubleshooting](#troubleshooting) diff --git a/plugins/s1-secops-skills/docs/installation.md b/plugins/s1-secops-skills/docs/installation.md index c7e51e0..fe0ce81 100644 --- a/plugins/s1-secops-skills/docs/installation.md +++ b/plugins/s1-secops-skills/docs/installation.md @@ -51,7 +51,7 @@ All three servers run from public package registries: `s1-secops-mcp` and `@burt "env": { "S1_CONSOLE_URL": "https://usea1-yourorg.sentinelone.net", "S1_CONSOLE_API_TOKEN": "eyJ...your-api-token...", - "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net", + "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net" } }, "purple-mcp": { diff --git a/plugins/s1-secops-skills/docs/mcp-tools.md b/plugins/s1-secops-skills/docs/mcp-tools.md index 5fc77be..8dab2d4 100644 --- a/plugins/s1-secops-skills/docs/mcp-tools.md +++ b/plugins/s1-secops-skills/docs/mcp-tools.md @@ -69,16 +69,16 @@ Post OCSF-formatted threat intelligence indicators (file, network, process obser ### SDL tools **`sdl_list_files`** -List configuration files on the SDL tenant (parsers, dashboards, lookups, datatables). Filter by path prefix, e.g. `/logParsers/` or `/dashboards/`. +List configuration files on the SDL tenant (parsers, dashboards, lookups, datatables) via `POST /sdl/v2/graphql`. Returns each file's `name`, `version` and `udoId`. Optional `pathPrefix` scopes the listing, e.g. `/logParsers/` or `/dashboards/`, so a caller does not have to pull all 2,264 files into context. **`sdl_get_file`** -Download the content of a specific SDL configuration file by path. +Download the content of a single SDL configuration file. Address it by `path` for name-addressed namespaces (`/logParsers/`, `/lookups/`, `/datatables/`, `/automaticLookups`) or by `udoId` for a dashboard. The console's Configuration Files grid displays a dashboard as `/dashboards/id//`; that is a display string, not a path. Pass the number as `udoId` and the file's real name is `/dashboards/`. **`sdl_put_file`** -Upload or update a configuration file on SDL. Used for deploying parsers and dashboards. Authorised by `S1_CONSOLE_API_TOKEN`. +Upload or update a configuration file on SDL. Used for deploying parsers and dashboards. Takes `path` or `udoId`, plus optional `expectedVersion`, which is enforced on both address forms. A path-addressed write to an existing dashboard is refused, because `addConfigFile(name:)` creates a duplicate rather than updating in place; the tool names the `udoId`s already holding that name. Create a dashboard by name once, then address it by `udoId`. Authorised by `S1_CONSOLE_API_TOKEN`. **`sdl_delete_file`** -Delete a configuration file from SDL by path. +Delete a configuration file from SDL by `path` or `udoId`. The tool verifies removal by re-reading and returns `{status, deleted, raw}`. **`hec_ingest`** Ingest raw logs/events into SDL via the HEC (HTTP Event Collector) endpoint. Applies a named parser via `?sourcetype` and lands the data for Event Search, PowerQuery, and detection rules. Posts to `S1_HEC_INGEST_URL` with `Authorization: Bearer `; the `S1-Scope` header (accountId or accountId:siteId) is required. Replaces the removed `sdl_upload_logs`. Used for ingesting custom telemetry or test events during parser development. diff --git a/plugins/s1-secops-skills/docs/release-notes/RELEASE_NOTES_v1.3.1.md b/plugins/s1-secops-skills/docs/release-notes/RELEASE_NOTES_v1.3.1.md index 7feb85b..a8aeedc 100644 --- a/plugins/s1-secops-skills/docs/release-notes/RELEASE_NOTES_v1.3.1.md +++ b/plugins/s1-secops-skills/docs/release-notes/RELEASE_NOTES_v1.3.1.md @@ -5,9 +5,10 @@ authorises every SentinelOne API surface this project touches, the SDL base URL is derived from the console URL, and every identity in the repo matches the `ai-siem` monorepo. Tool count unchanged at 26. -Versioning: npm package **@pmoses-s1/s1-secops-mcp 1.3.1** (renamed from -`@pmoses-s1/s1-secops-mcp`, last published as 1.2.4). Docker bundle image -**1.3.1**, pinning `S1_MCP_VERSION=1.3.1`. Plugin **1.3.0**. +Versioning: npm package **@pmoses-s1/s1-secops-mcp 1.3.3**. Docker bundle image +**1.3.1**, pinning `S1_MCP_VERSION=1.3.3`. Plugin **1.3.1**. The image version and +the npm version are independent: the image version tracks the Dockerfile, the +dispatcher and the bundled `CLAUDE.md`; the npm version tracks the server itself. ### Breaking diff --git a/plugins/s1-secops-skills/docs/skills.md b/plugins/s1-secops-skills/docs/skills.md index 1181fcf..3d263c4 100644 --- a/plugins/s1-secops-skills/docs/skills.md +++ b/plugins/s1-secops-skills/docs/skills.md @@ -64,7 +64,7 @@ Full field reference: `mgmt-console-api/SKILL.md` **What it provides:** - SDL log ingest via HEC (`hec_ingest`), uses the console JWT (`S1_CONSOLE_API_TOKEN`) posted to `S1_HEC_INGEST_URL` -- SDL config file CRUD (`sdl_list_files`, `sdl_get_file`, `sdl_put_file`, `sdl_delete_file`) +- SDL config file CRUD over `POST /sdl/v2/graphql` (`sdl_list_files`, `sdl_get_file`, `sdl_put_file`, `sdl_delete_file`; Python client `config_files`, `config_file`, `put_config_file`, `delete_config_file`). Dashboards are addressed by `udoId`, every other namespace by name - SDL V1 query (full-event JSON, used for schema discovery) ```python @@ -81,9 +81,9 @@ Full field reference: `mgmt-console-api/SKILL.md` - Complete SDL dashboard JSON schema: tabs, panels, parameters, time range controls - Panel type reference: timeseries, count, table, honeycomb, pie, bar, single value - PowerQuery integration: panel query validation against tenant sources before deployment -- Dashboard deployment via `sdl_put_file` to `/dashboards/` +- Dashboard deployment via `sdl_put_file`: create by name at `/dashboards/` once, then update by `udoId` forever after, since a name-addressed write to an existing dashboard creates a duplicate -**Workflow:** Author dashboard JSON → validate queries against live tenant sources → deploy via SDL API → confirm via `sdl_list_files`. +**Workflow:** Author dashboard JSON → validate queries against live tenant sources → deploy via the SDL config-file GraphQL surface → confirm via `sdl_list_files` with `pathPrefix` `/dashboards/` and record the returned `udoId`. --- diff --git a/plugins/s1-secops-skills/docs/upgrading.md b/plugins/s1-secops-skills/docs/upgrading.md index d3f7355..73bc3ee 100644 --- a/plugins/s1-secops-skills/docs/upgrading.md +++ b/plugins/s1-secops-skills/docs/upgrading.md @@ -41,8 +41,8 @@ you will have two binaries on `PATH`: ```bash npm uninstall -g @pmoses-s1/sentinelone-mcp -npm install -g @pmoses-s1/s1-secops-mcp@1.3.1 -s1-secops-mcp --version # expect 1.3.1 +npm install -g @pmoses-s1/s1-secops-mcp@1.3.3 +s1-secops-mcp --version # expect 1.3.3 ``` --- @@ -157,7 +157,7 @@ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion": | docker run -i --rm ghcr.io/pmoses-s1/s1-mcps:1.3.1 s1-secops-mcp ``` -Expect `serverInfo.name = "s1-secops-mcp-server"`, `version = "1.3.1"`, and +Expect `serverInfo.name = "s1-secops-mcp-server"`, `version = "1.3.3"`, and `Tools: 26 registered` on stderr. --- diff --git a/plugins/s1-secops-skills/docs/vm-deployment.md b/plugins/s1-secops-skills/docs/vm-deployment.md index 2c374cf..37c5824 100644 --- a/plugins/s1-secops-skills/docs/vm-deployment.md +++ b/plugins/s1-secops-skills/docs/vm-deployment.md @@ -76,7 +76,7 @@ sudo vim /etc/s1-secops-mcp/credentials.json { "S1_CONSOLE_URL": "https://usea1-yourorg.sentinelone.net", "S1_CONSOLE_API_TOKEN": "eyJ...", - "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net", + "S1_HEC_INGEST_URL": "https://ingest.us1.sentinelone.net" } ``` @@ -86,7 +86,7 @@ Apply without restart (full restart needed for credentials): sudo systemctl restart s1-secops-mcp ``` -`S1_CONSOLE_URL` + `S1_CONSOLE_API_TOKEN` are enough for most tools. `S1_HEC_INGEST_URL` is required only for `uam_ingest_alert`, `uam_post_indicators`, `uam_post_alert`. `SDL_*` keys gate the SDL tools per the table in [the MCP README](../../../mcp/s1-secops-mcp/README.md#credentials). +`S1_CONSOLE_URL` + `S1_CONSOLE_API_TOKEN` are enough for every tool except HEC ingest, including the SDL config-file tools. `S1_HEC_INGEST_URL` is required only for `uam_ingest_alert`, `uam_post_indicators`, `uam_post_alert` and `hec_ingest`. Full key table in [the MCP README](../../../mcp/s1-secops-mcp/README.md#credentials). ## TLS in front (Caddy) diff --git a/plugins/s1-secops-skills/docs/zero-to-hero.md b/plugins/s1-secops-skills/docs/zero-to-hero.md index ddc7b87..b5f00b6 100644 --- a/plugins/s1-secops-skills/docs/zero-to-hero.md +++ b/plugins/s1-secops-skills/docs/zero-to-hero.md @@ -111,7 +111,7 @@ The recommended install is the Docker quick start: one image bundles all three M 1. **Pull the image** (all three MCPs in one). 2. **Configure credentials** in `claude_desktop_config.json`. The README has the copy-paste config block and a table of where to get each token/key; the full key reference is [`docs/credentials.md`](./credentials.md). -3. **Install the plugin** (`s1-secops-skills-v1.2.5.plugin`) via Cowork → Customize → Browse plugins. +3. **Install the plugin** (`s1-secops-skills-v1.3.1.plugin`) via Cowork → Customize → Browse plugins. Then create a Cowork project named `PrincipalSOCAnalyst`, select a folder for it, and (optionally) drop your own [`CLAUDE.md`](https://raw.githubusercontent.com/Sentinel-One/ai-siem/main/plugins/s1-secops-skills/CLAUDE.md) into the folder to customise the persona; the Docker image ships a default, so this is optional (to override it, mount the folder read-only and set `S1_CLAUDE_MD_PATH`, see [`docs/docker.md`](./docker.md#claudemd-customization)). diff --git a/plugins/s1-secops-skills/skills/hyperautomation/SKILL.md b/plugins/s1-secops-skills/skills/hyperautomation/SKILL.md index d1f2562..60f6532 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/SKILL.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/SKILL.md @@ -310,6 +310,10 @@ Use this when the workflow contains integration-backed actions: ✅ Bind the built-in **integration (action-pack) id**, the value `discover`/list returns as the workflow action's `integration_id` (e.g. the SentinelOne SDL action-pack id). A connection created via `POST /web/api/v2.1/hyper-automate/api/v1/connections` returns a *connection* id; do NOT bind that, bind the integration id it was created under, and rely on a connection existing under that integration. - ❌ **Trusting activation (204) as proof a flow works.** Activation validates neither connection binding nor `{{Function.JQ}}` references. ✅ Always run-now (or the per-action **Test Action**) after activating and confirm state `Completed` with empty `error_actions`. +- ❌ **Reading an http_request's `status` field to decide whether the call worked.** `status` is `"success"` whenever ANY response arrives, including `404`/`4xx`. A flow that branches on it treats an error body as data. Tenant-validated 2026-08-09. + ✅ Branch on `{{action-slug.status_code}}` (top level of the action output, NOT under `.body`). `continue_on_fail` governs transport failures, not HTTP status; only `retry_on_status_codes` reacts to codes, and it cannot help with a terminal 4xx. +- ❌ **Triggering run-now on several flows concurrently.** Executions park in `Running` with `executed_actions: 0` indefinitely, and abandoned parked executions accumulate and hold scheduler slots (10 observed on one tenant after a day of killed harnesses, blocking new runs). Tenant-validated 2026-08-09. + ✅ Serialise run-now. To clear a parked execution: `deactivate` → `activate` → run again (a full delete of the workflow also releases it). Before blaming a flow that will not start, check `workflow-execution` for `state: Running` + `executed_actions: 0`. Calibrate any "is it stuck?" timeout against measured healthy start latency: a heavy flow legitimately sat at 0 actions for ~82s before completing with 40. - ❌ **`select((ARR | index(.field)) != null)` in `Function.JQ`.** The `| index(...)` pipe rebinds `.` to `ARR`, so `.field` then indexes the array → `Cannot index array with string "field"`. ✅ Bind first: `select(.field as $n | (ARR | index($n)) != null)`. And when building HTML inside a `Function.JQ` string, use SINGLE-quoted HTML attributes so the only double quotes are jq string delimiters (pre-escaping `\"` inside collides with the wrapper's single quote-escape and the platform reports "Invalid References"). - ❌ Guarding a destructive action (block, isolate, disable) with a fail-OPEN approval gate (`... not_equals "dismissed"`). A `wait_for_slack` / `wait_for_interaction` timeout yields an empty value that passes `not_equals`, so the action auto-runs with **no** approval. @@ -348,6 +352,38 @@ the query is done. Required pattern (tenant-validated 2026-06-25): neither doc was wrong about the key existing; use `stepsTotal` for consistency with pq.py and the SDL docs). TRUE → consume results + `break_loop`. FALSE → a short `delay` (~5s) as the leaf of the false branch; the loop then re-iterates and re-polls. + + **Read both counters through JQ WITH DEFAULTS, and gate on `> 0`.** A bare + `{{poll-slug.body.stepsTotal}}` ERRORS the whole run when a poll response omits the attribute + ("Attribute totalSteps not found in Action poll-silent-pairs"), which kills a watchdog whose + query was fine. Use `{{Function.JQ(poll-slug.body, "(.stepsCompleted // -1)", true)}}` against + `{{Function.JQ(poll-slug.body, "(.stepsTotal // 0)", true)}}`, plus a second condition + `stepsTotal greater_than 0`. Without the `> 0` gate a pre-assignment 0/0 first poll satisfies + `equals` and the flow processes an EMPTY result set as complete, which for a baseline refresh + silently writes a stub that then suppresses every detection reading it. + + **Gate the loop on a 4xx, or a killed query spins to the workflow timeout.** The backend can + terminate a running LRQ; the poll then answers `404 {"code":"not_found"}` for the rest of the + run. HA does NOT treat that as a failed action, its `status` is `"success"` whenever a response + arrives whatever the code, so with the JQ defaults above the equality never matches and the loop + iterates until the run times out (17+ iterations observed live). `continue_on_fail: false` does + NOT help, verified live: status codes are actionable only via `retry_on_status_codes`, and + retrying a dead token is pointless. Put a condition BETWEEN the poll and the done-check: + + ```jsonc + {"input_value": "{{poll-slug.status_code}}", "compared_value": "400", + "comparison_operator": "greater_than_or_equals"} + // true -> an action that ENDS the run + // false -> the existing done-check + ``` + + `status_code` sits at the TOP level of the action output, not under `.body`. To end the run + deliberately, reference an attribute that cannot exist (e.g. + `{{poll-slug.body.LRQ_TERMINATED_BY_BACKEND}}`): a missing attribute is the one mechanism + observed to fail an HA run outright, and the attribute name becomes the console error text, so + name it descriptively. Ending in Error is also what a health-notifier flow keys on. Silent + completion is strictly worse than a loud failure here. Verified live: the flow that had been + spinning errored in 103s after 6 actions with the sentinel named in `error_actions`. 3. **Loop-scoped outputs are NOT visible outside the loop.** Every action that reads a poll result (`{{poll-slug.body...}}`), extract/read, branch, notify, break, MUST live INSIDE the loop (`parent_action` = the loop's export_id). An action placed after the loop that references a diff --git a/plugins/s1-secops-skills/skills/hyperautomation/references/api-integration.md b/plugins/s1-secops-skills/skills/hyperautomation/references/api-integration.md index 9b0a743..9bb9d2b 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/references/api-integration.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/references/api-integration.md @@ -14,7 +14,7 @@ stop and tell the user what went wrong before proceeding with any workflow opera ### Step 1: Validate `S1_CONSOLE_URL` (no auth required) -``` +```text GET {S1_CONSOLE_URL}/web/api/v2.1/system ``` @@ -24,13 +24,14 @@ or the console is unreachable. ### Step 2: Validate `S1_CONSOLE_API_TOKEN` (auth + permission check) -``` +```text GET {S1_CONSOLE_URL}/web/api/v2.1/hyper-automate/api/public/workflows?limit=1 Authorization: ApiToken {S1_CONSOLE_API_TOKEN} ``` A `200` response confirms the token is valid and has `Hyper Automate.view` permission. Failure responses: + - `401`: token is missing or expired - `403`: token lacks `Hyper Automate.view` permission @@ -66,6 +67,7 @@ deactivate, trigger, list, and monitor executions. **Content-Type**: `application/json` for POST requests All POST request bodies follow the S1 envelope pattern: + ```json { "data": { /* payload */ } } ``` @@ -98,6 +100,7 @@ problem**. ## Endpoint Reference ### 1. Import Workflow + `POST /hyper-automate/api/public/workflow-import-export/import` **Permission**: `Hyper Automate.workflowsCreateEdit` @@ -199,6 +202,7 @@ version. Select on `state == "active"` (as above) when you want the activation-r `version_id`; fall back to `[0]` only for never-activated workflows. Sample response: + ```json { "id": "", @@ -215,6 +219,7 @@ workflows that exist. Only the `/workflows/versions/list/{id}` path is guarantee to work for post-import retrieval. **Notes**: + - Imported workflows are created as **Private Draft**, visible only to the owner of the token used for import. - **Use a Console User (personal) token, not a Service User token.** The Hyperautomation API @@ -226,6 +231,7 @@ to work for post-import retrieval. --- ### 2. Batch Import Workflows + `POST /hyper-automate/api/public/workflow-import-export/import/batch` **Permission**: `Hyper Automate.workflowsCreateEdit` @@ -235,6 +241,7 @@ to work for post-import retrieval. Accepts multiple workflow objects. Use for bulk deployments. **Body params** (multipart/form-data): + - `file` (required): the batch import file (workflow JSON or ZIP archive) - `filter` (required): JSON-encoded string. Send `{}` for "no filter". ⚠️ Sending `filter` as an **empty string** (`""`) triggers a server-side @@ -243,6 +250,7 @@ Accepts multiple workflow objects. Use for bulk deployments. - `body` (optional): `{ "data": {...}, "filter": {"type": "JsonPath"|"JsonSchema", "value": "..."} }` Example: + ```bash curl -X POST "$S1_CONSOLE/web/api/v2.1/hyper-automate/api/public/workflow-import-export/import/batch?siteIds=$SITE_ID" \ -H "Authorization: ApiToken $TOKEN" \ @@ -257,6 +265,7 @@ don't trust a 500 from a malformed `filter` as evidence about permissions). --- ### 3. Export Workflow (single) + `GET /hyper-automate/api/public/workflow-import-export/export/{workflow_id}/{version_id}` **Permission**: `Hyper Automate.workflowsExport` @@ -269,11 +278,13 @@ Returns the full workflow JSON for re-import or inspection. --- ### 4. Batch Export Workflows + `GET /hyper-automate/api/public/workflow-import-export/export` **Permission**: `Hyper Automate.workflowsExport` **Query params** (all optional, combine to filter): + | Param | Type | Description | |-------|------|-------------| | `workflow_ids` | string | Comma-separated workflow IDs | @@ -291,11 +302,13 @@ Returns the full workflow JSON for re-import or inspection. --- ### 5. List Workflows + `GET /hyper-automate/api/public/workflows` **Permission**: `Hyper Automate.view` **Query params**: + | Param | Type | Description | |-------|------|-------------| | `integrations` | string | Filter by integration | @@ -320,6 +333,7 @@ Returns the full workflow JSON for re-import or inspection. --- ### 6. List Workflow Versions + `GET /hyper-automate/api/public/workflows/versions/list/{workflow_id}` **Permission**: `Hyper Automate.view` @@ -331,6 +345,7 @@ Returns all versions (draft, active, inactive) for a given workflow. --- ### 7. Activate a Workflow Version + `POST /hyper-automate/api/public/workflows/{workflow_id}/{version_id}/activation` **Permission**: `Hyper Automate.workflowsActivateDeactivate` @@ -338,6 +353,7 @@ Returns all versions (draft, active, inactive) for a given workflow. **Query params**: `accountIds`, `siteIds`, `groupIds` for scope. **Body** (all fields optional): + ```json { "data": { @@ -359,6 +375,7 @@ Returns all versions (draft, active, inactive) for a given workflow. --- ### 8. Deactivate a Workflow + `POST /hyper-automate/api/public/workflows/{workflow_id}/deactivate` **Permission**: `Hyper Automate.workflowsActivateDeactivate` @@ -374,6 +391,7 @@ to deactivate the currently active version (you do not need to look the version --- ### 8a. Publish a Workflow (Share with team) + `POST /hyper-automate/api/v1/workflows/{workflow_id}/publish` Transitions a Private Draft to a Shared Draft so the workflow is visible to the team in the UI @@ -389,6 +407,7 @@ importing user until it is published or activated. --- ### 8b. Delete a Workflow + `DELETE /hyper-automate/api/v1/workflows/{workflow_id}?accountIds=` Soft, recoverable delete (the UI offers a "Restore workflow" action). This is the correct delete @@ -409,6 +428,7 @@ found"` means the id is not under that scope (or already deleted). --- ### 9. Trigger a Manual Workflow + `POST /hyper-automate/api/public/workflow-execution/manual/{workflow_id}/{version_id}` **Permission**: `Hyper Automate.workflowsRun` @@ -416,6 +436,7 @@ found"` means the id is not under that scope (or already deleted). **Query params**: `accountIds`, `siteIds`, `groupIds` for scope. **Body** (all fields optional): + ```json { "data": { @@ -442,11 +463,13 @@ found"` means the id is not under that scope (or already deleted). --- ### 10. List Workflow Executions + `GET /hyper-automate/api/public/workflow-execution` **Permission**: `Hyper Automate.view` **Query params**: + | Param | Type | Description | |-------|------|-------------| | `workflow_id` | string | Filter by specific workflow | @@ -473,11 +496,13 @@ found"` means the id is not under that scope (or already deleted). --- ### 11. Get Execution Detail + `GET /hyper-automate/api/public/workflow-execution/{workflow_execution_id}` **Permission**: `Hyper Automate.view` **Response fields**: + - Required: `id`, `mgmt_id`, `scope_id`, `singularity_response_event_id`, `version_id`, `workflow_id` - Optional: `created_at`, `duration`, `error_actions` (array), `executed_actions` (integer), `scope_level` (enum), `singularity_response_event_type` (enum), `state` (enum), @@ -486,6 +511,7 @@ found"` means the id is not under that scope (or already deleted). --- ### 12. Enable Agent PNA for Hyperautomation + `POST /agents/enable-hyper-automation-pna` **Permission**: `Endpoints.edit` + `Hyper Automate.connectionsEdit` @@ -495,6 +521,7 @@ Enables the Private Network Access agent integration for use in Hyperautomation --- ### 13. Disable Agent PNA for Hyperautomation + `POST /agents/disable-hyper-automation-pna` **Permission**: `Endpoints.edit` + `Hyper Automate.connectionsEdit` @@ -502,11 +529,13 @@ Enables the Private Network Access agent integration for use in Hyperautomation --- ### 14. Evaluate Expression + `POST /hyper-automate/api/public/workflow-action-expressions/{base_action_id}/evaluate-expression` Evaluates a Hyperautomation expression string against a given context. **Body**: + ```json { "data": { @@ -521,6 +550,7 @@ Evaluates a Hyperautomation expression string against a given context. --- ### 15. Expression Breakdown + `POST /hyper-automate/api/public/workflow-action-expressions/{base_action_id}/expression-breakdown` Same body as Evaluate Expression. Returns a parsed breakdown of expression components. @@ -591,6 +621,7 @@ const triggerRes = await fetch( | `422` | Validation error | Schema mismatch, duplicate `export_id`, invalid `type` value, missing required field | Common import `422` messages: + - `"Invalid action type"`: typo in `type` field - `"export_id conflict"`: duplicate `export_id` values in actions array - `"Invalid target"`: `connected_to.target` references non-existent `export_id` diff --git a/plugins/s1-secops-skills/skills/hyperautomation/references/building-blocks-catalog.md b/plugins/s1-secops-skills/skills/hyperautomation/references/building-blocks-catalog.md index 629e707..f096895 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/references/building-blocks-catalog.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/references/building-blocks-catalog.md @@ -88,6 +88,7 @@ and **when to reach for it**. activity is created or updated. **Real shape, high/critical EDR alert filter (most common in corpus)**: + ```json { "type": "singularity_response_trigger", @@ -133,6 +134,7 @@ console; set `true` for fully automated response. **Purpose**: run-on-demand. Two flavors: static (no input) and dynamic (analyst fills a form). **Static shape (151 of 151 manual triggers in active flows use this)**: + ```json { "type": "manual_trigger", @@ -148,6 +150,7 @@ console; set `true` for fully automated response. ``` **Dynamic shape (form-driven)**: + ```json { "type": "manual_trigger", @@ -169,6 +172,7 @@ console; set `true` for fully automated response. } } ``` + Reference: `{{manual-trigger.data.Hostname}}`. **Reach for it when**: building an investigation playbook, a one-off remediation, or a @@ -182,6 +186,7 @@ parameterized hunt that an analyst kicks off from the console. (6 with units `hours` 3/6/12 and `minutes` 10/15). **Daily at a fixed local time**: + ```json { "type": "scheduled_trigger", @@ -206,6 +211,7 @@ parameterized hunt that an analyst kicks off from the console. ``` **Every N hours (interval)**: + ```json { "schedule_method": "interval", @@ -235,6 +241,7 @@ queries that drop results into a ticket. generates a unique `url_identifier` (UUID) baked into the URL. **Real shape**: + ```json { "type": "http_trigger", @@ -283,6 +290,7 @@ a Condition action that checks `{{http-trigger.headers.X-Auth-Token}}` to gate a } } ``` + Reference: `{{email-trigger.subject}}`, `{{email-trigger.body}}`, `{{email-trigger.from}}`, `{{email-trigger.attachments}}`. @@ -299,6 +307,7 @@ Most-used pattern is **single-variable per action** (97 of the 100 most-named Va are simply called "Variable" with one entry). **Single literal**: + ```json { "type": "variable", @@ -315,6 +324,7 @@ are simply called "Variable" with one entry). ``` **Expression (most common in corpus, `Function.DEFAULT` for safe field access)**: + ```json { "data": { @@ -332,6 +342,7 @@ are simply called "Variable" with one entry). ``` **Multi-variable in one action, only safe when none of the values reference each other**: + ```json { "data": { @@ -369,6 +380,7 @@ universally** (1,697 of 1,697), even for single comparisons. Stick with multi. | `in` | 1 | **Real shape**: + ```json { "type": "condition", @@ -387,6 +399,7 @@ universally** (1,697 of 1,697), even for single comparisons. Stick with multi. ``` **Multiple AND clauses**: + ```json "conditions": [ { "input_value": "{{search-for-file-hash.status_code}}", "compared_value": "200", "comparison_operator": "equals" }, @@ -408,6 +421,7 @@ result of an enrichment lookup. `break_loop` fires (`while`). **Dynamic loop (the only flavor seen in active flows, 498/498)**: + ```json { "type": "loop", @@ -482,6 +496,7 @@ job (RemoteOps, scan) finish before polling its status; staging notifications. scratchpad to assemble payloads from multiple sources. **Real shape (most-cloned: "Generate UUID", 75 occurrences)**: + ```json { "type": "data_formation", @@ -507,6 +522,7 @@ custom note bodies, normalized event objects for SDL ingest. auth, retry, SSL, proxy. **Real shape (most generic, note `tag: "core_action"`, all integration fields null)**: + ```json { "type": "http_request", @@ -556,6 +572,7 @@ you want to inline-template the URL with `{{...}}`. pre-configured in `Hyperautomation → Integrations`. 90% of HTTP requests in active flows. **Real shape, SentinelOne integration (Create Device Control Rule)**: + ```json { "type": "http_request", @@ -603,6 +620,7 @@ official integrations. (`list_1`). **Real shape (HTML body with templated alert fields)**: + ```json { "type": "send_email", @@ -629,11 +647,13 @@ or a `{{...}}` expression). **Attachments use `file_name` / `file_content` (NOT `name` / `content`)**, confirmed against the live import API on 2026-06-11: + ```json "attachments": [ { "file_name": "report.csv", "file_content": "{{Function.BASE64_ENCODE(local_var.csv)}}" } ] ``` + `file_content` is base64. Using `name`/`content` returns `422 "Field required"` on `attachments.0.file_name` / `attachments.0.file_content`. @@ -645,6 +665,7 @@ live import API on 2026-06-11: instead of being duplicated across flows. Full authoring + calling + lifecycle: `references/snippets.md`. **Calling node is `snippet_20`** (not `snippet`). Static call, pinned to a version: + ```json { "type": "snippet_20", "tag": "core_action", @@ -678,6 +699,7 @@ instead of being duplicated across flows. Full authoring + calling + lifecycle: message, or until the timeout elapses. **Real shape**: + ```json { "type": "wait_for_slack", @@ -706,6 +728,7 @@ request, the wait correlates the analyst's button click back to that specific me required). **Create**: + ```json { "type": "create_interaction", @@ -721,6 +744,7 @@ required). ``` **Wait**: + ```json { "type": "wait_for_interaction", @@ -775,13 +799,14 @@ Always pair with a Condition that branches on `{{local_var.alert-hash}} != "no-h **Where**: 79 conditions named "Get Response on Time", 92 named "Is File Suspicious in VT". -``` +```text HTTP Request → Condition (status_code == 200) TRUE → Variable (success_note) → HTTP (Add Note "Enriched") FALSE → Variable (fail_note) → HTTP (Add Note "Enrichment failed") ``` Body of each condition: + ```json "conditions": [{ "input_value": "{{my-action.status_code}}", "compared_value": "200", "comparison_operator": "equals" }], "conditions_relationship": "and" @@ -797,7 +822,7 @@ after every external API call that the analyst needs receipts for. **Where**: 75 occurrences of "Set Default Cursor" / "Set Filtered Channels" / "Last Page" trio across Slack channel-discovery flows. -``` +```text Variable (cursor = "") ↓ Loop (while) @@ -856,7 +881,7 @@ APPEND has nothing to append to. **Where**: 186 occurrences of `Function.JQ`. Per `functions-reference.md`, store the JQ expression in a Variable first to avoid escaping pain. -``` +```text Variable (jq_filter = ".data.matches[] | {host: .endpoint.name, count: .count}") ↓ Variable (filtered = "{{Function.JQ(my-pq.body, local_var.jq_filter)}}") @@ -867,11 +892,13 @@ Variable (filtered = "{{Function.JQ(my-pq.body, local_var.jq_filter)}}") ## B6. Add Note to Unified Alert (UAM GraphQL, observed twice in two flavors) **Modern flavor (cleaner GraphQL)**: + ```json "payload": "{\n \"query\": \"mutation AddAlertNote { addAlertNote(text: \\\"{{Function.HTML_ENCODE(local_var.note_body)}}\\\", alertId: \\\"{{singularity-response-trigger.data.id}}\\\") { data { alertId } } }\"\n}" ``` **Legacy flavor (alertTriggerActions wrapper, used in older flows)**: + ```json "payload": "{\n \"query\": \"mutation AddNoteToAlert { alertTriggerActions(actions: [{ id: \\\"S1/alert/addNote\\\", payload: { note: { value: \\\"{{Function.HTML_ENCODE(local_var.note)}}\\\" } } }], filter: { or: [{ and: [{ fieldId: \\\"id\\\", stringEqual: { value: \\\"{{singularity-response-trigger.data.id}}\\\" } }] }] }) { ... on ActionsTriggered { actions { actionId success { id } failure { id } skip { id } } } } }\"\n}" ``` @@ -948,7 +975,7 @@ Most flows then check `body.data.attributes.last_analysis_stats.malicious > 0` i ## B11. Slack post + interactive wait + branch on choice -``` +```text HTTP (chat.postMessage with Block Kit buttons) → captures {{post.body.ts}} ↓ Wait for Slack (message_ts = {{post.body.ts}}, value = 30 minutes) @@ -1006,7 +1033,7 @@ starting templates. ## C1. Alert → enrich-with-VT → add-note (the workhorse, ~100 active flows) -``` +```text Singularity Response Trigger (severity ∈ HIGH/CRITICAL, EDR) → Variable (alert-hash via DEFAULT chain) [B1] → Condition (alert-hash != "no-hash") @@ -1023,7 +1050,7 @@ Singularity Response Trigger (severity ∈ HIGH/CRITICAL, EDR) ## C2. Scheduled IOC ingest (TOR / AbuseIPDB / GitHub list → S1 TI) -``` +```text Scheduled Trigger (daily 08:00) [A3] → HTTP GET [A12] → Variable (ioc_list = parsed lines) @@ -1037,7 +1064,7 @@ Scheduled Trigger (daily 08:00) [A3] ## C3. Webhook → device-control rule + analyst email -``` +```text HTTP Trigger (POST/GET, dedup 180s) [A4] → HTTP POST /web/api/v2.1/device-control [A13] with serial/site from {{http-trigger.query_params.*}} @@ -1051,7 +1078,7 @@ zero-trigger-input automation. ## C4. Manual investigation playbook (analyst-driven hunt) -``` +```text Manual Trigger (dynamic, Hostname text input) [A2] → HTTP POST PowerQuery (filter src.endpoint.name = ...) [B8] → Variable (rows = body.matches) @@ -1065,7 +1092,7 @@ Manual Trigger (dynamic, Hostname text input) [A2] ## C5. Approval gate via Slack (analyst confirms a remediation) -``` +```text Singularity Response Trigger [A1] → HTTP POST chat.postMessage (Block Kit Approve/Deny) → Wait for Slack (message_ts, 30 minutes) [A16] @@ -1081,7 +1108,7 @@ Singularity Response Trigger [A1] ## C6. Periodic posture report (UEBA-style) -``` +```text Scheduled Trigger (interval, every 6 hours) [A3] → HTTP POST PowerQuery (logon counts per user) [B8] → Variable (rows = MAP_TABLE(columns, matches)) @@ -1127,6 +1154,7 @@ moment the note contains a quote, ampersand, or angle bracket. Wrap, always. The corpus has IOC payloads with literal `accountIds: [""]`, fine for single-tenant flows, broken on transfer. Prefer: + - Pull from a Variable (set up-front from a Manual Trigger / Webhook param), or - Pull from `singularity-response-trigger.data.scopeId` if the alert carries it. diff --git a/plugins/s1-secops-skills/skills/hyperautomation/references/building-blocks.md b/plugins/s1-secops-skills/skills/hyperautomation/references/building-blocks.md index 3f7acbb..7ea1003 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/references/building-blocks.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/references/building-blocks.md @@ -13,6 +13,7 @@ Complete data payloads for every action type in SentinelOne Hyperautomation. ## TRIGGERS ### Manual Trigger (static: no user input) + ```json { "type": "manual_trigger", @@ -37,6 +38,7 @@ Complete data payloads for every action type in SentinelOne Hyperautomation. ``` ### Manual Trigger (dynamic: prompts user for input) + ```json { "type": "manual_trigger", @@ -62,10 +64,12 @@ Complete data payloads for every action type in SentinelOne Hyperautomation. } } ``` + Input types: `"text"`, `"number"`, `"json"`, `"email"`, `"date"`, `"time"`, `"checkbox"` Reference: `{{manual-trigger.data.AssetName}}` ### Scheduled Trigger + ```json { "type": "scheduled_trigger", @@ -91,10 +95,12 @@ Reference: `{{manual-trigger.data.AssetName}}` } } ``` + `week_day`: 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday `schedule_method` options: `"daily"`, `"weekly"`, `"monthly"`, `"interval"` ### HTTP Trigger (Webhook) + ```json { "type": "http_trigger", @@ -113,9 +119,11 @@ Reference: `{{manual-trigger.data.AssetName}}` } } ``` + Reference incoming data: `{{http-trigger.body.someField}}` ### Singularity Response Trigger + ```json { "type": "singularity_response_trigger", @@ -158,6 +166,7 @@ Reference incoming data: `{{http-trigger.body.someField}}` > be a **JSON-encoded string of an array** (e.g. `"[\"HIGH\",\"CRITICAL\"]"`), not a raw JSON > array. Every active production flow uses this string-encoded format. Common trigger data references: + - `{{singularity-response-trigger.data.id}}`: alert ID - `{{singularity-response-trigger.data.name}}`: alert name - `{{singularity-response-trigger.data.severity}}`: severity @@ -171,6 +180,7 @@ Common trigger data references: - `{{singularity-response-trigger.data.indicators[0].eventTime}}`: first indicator event time ### Email Trigger + ```json { "type": "email_trigger", @@ -182,6 +192,7 @@ Common trigger data references: } } ``` + Reference: `{{email-trigger.body}}`, `{{email-trigger.subject}}` --- @@ -189,6 +200,7 @@ Reference: `{{email-trigger.body}}`, `{{email-trigger.subject}}` ## CORE ACTIONS ### Variable + ```json { "type": "variable", @@ -218,6 +230,7 @@ Reference: `{{email-trigger.body}}`, `{{email-trigger.subject}}` } } ``` + `variables_scope`: `"local"` (default) or `"global"` Reference: `{{local_var.myVar}}` or `{{global_var.myVar}}` `should_use_as_output: true` exposes the variable as a workflow-level output (visible in the @@ -236,6 +249,7 @@ should TTL out; leave null for local. > workflow, give each such variable its own dedicated Variable action. > > ❌ **Wrong**: `fullPath` silently resolves to empty because `baseUrl` is not yet available: +> > ```json > { > "name": "Set Vars", @@ -249,6 +263,7 @@ should TTL out; leave null for local. > ``` > > ✅ **Right**: two separate actions, each with one variable: +> > ```json > { > "name": "Set Base URL", @@ -259,7 +274,9 @@ should TTL out; leave null for local. > "variables_scope": "local" > } > ``` +> > *(connected_to → next action)* +> > ```json > { > "name": "Set Full Path", @@ -276,6 +293,7 @@ should TTL out; leave null for local. > literals, trigger fields, or external action outputs). ### Loop (dynamic: iterates over an array) + ```json { "type": "loop", @@ -290,12 +308,14 @@ should TTL out; leave null for local. } } ``` + Current item reference: `{{loop-items.item}}` or `{{loop-items.item.fieldName}}` Current index: `{{loop-items.index}}` Connect loop to first inner action using `custom_handle: "inner"`. Actions inside the loop have `"parent_action": `. ### Loop (while: indefinite, until Break) + ```json { "type": "loop", @@ -312,6 +332,7 @@ Actions inside the loop have `"parent_action": `. ``` ### Loop (fixed: runs N times) + ```json { "type": "loop", @@ -328,7 +349,9 @@ Actions inside the loop have `"parent_action": `. ``` ### Condition + See workflow-schema.md for simple vs. multi style details. + ```json { "type": "condition", @@ -349,6 +372,7 @@ See workflow-schema.md for simple vs. multi style details. } } ``` + Operators (with usage counts in active corpus): `"equals"` (1,105), `"not_equals"` (271), `"greater_than_or_equals"` (194), `"greater_than"` (168), `"contains"` (34), `"in"` (1), `"less_than_or_equals"` (1), `"less_than"` (1). @@ -361,6 +385,7 @@ Also valid but unused in corpus: `"not_contains"`, `"is_empty"`, `"is_not_empty" > (e.g. `"[\"HIGH\",\"CRITICAL\"]"`), not a raw array. ### Delay + ```json { "type": "delay", @@ -373,9 +398,11 @@ Also valid but unused in corpus: `"not_contains"`, `"is_empty"`, `"is_not_empty" } } ``` + `time_unit` options: `"seconds"`, `"minutes"`, `"hours"` ### Break Loop + ```json { "type": "break_loop", @@ -386,9 +413,11 @@ Also valid but unused in corpus: `"not_contains"`, `"is_empty"`, `"is_not_empty" } } ``` + Must have `"parent_action": `. `"connected_to": []`. ### Send Email + ```json { "type": "send_email", @@ -408,6 +437,7 @@ Must have `"parent_action": `. `"connected_to": []`. } } ``` + `mime_type`: `"text/plain"` or `"text/html"`. HTML is more common in active flows (analyst- friendly tables, embedded SentinelOne logo banner). For attachments, use the keys **`file_name`** and **`file_content`** (NOT `name`/`content`): @@ -423,6 +453,7 @@ For attachments, use the keys **`file_name`** and **`file_content`** (NOT `name` element can be a literal or a `{{...}}` expression. ### Data Formation + ```json { "type": "data_formation", @@ -436,6 +467,7 @@ element can be a literal or a `{{...}}` expression. } } ``` + Builds a structured object on the fly without paying for a Variable action. `data` is an object whose values can be literals or `{{...}}` expressions; reference downstream as `{{generate-uuid.data.uuid}}`. Real corpus uses include: building Slack channel-config dicts, @@ -444,6 +476,7 @@ actions in the corpus are exactly the "Generate UUID" template above**, copy it when you need a correlation ID. ### HTTP Request (core: no integration) + ```json { "type": "http_request", @@ -479,6 +512,7 @@ when you need a correlation ID. } } ``` + `method` (count in active corpus): `"post"` (1,962), `"get"` (664), `"put"` (55), `"delete"` (13), `"patch"` (5). Reference response: `{{action-slug.body}}`, `{{action-slug.status_code}}`, @@ -495,7 +529,9 @@ Reference response: `{{action-slug.body}}`, `{{action-slug.status_code}}`, requires `application/x-www-form-urlencoded` or `multipart/form-data`. ### HTTP Request (integration-backed) + Same as above but: + - `"tag": "integration"` - `"connection_id": null` (set to null for import; user configures) - `"connection_name": ""` @@ -506,8 +542,9 @@ Same as above but: When generating workflows for import, always set `connection_id`, `connection_name`, and `integration_id` to null/""; these are resolved from the user's configured connections. -### URL pattern for integration-backed SentinelOne actions: -``` +### URL pattern for integration-backed SentinelOne actions + +```json "url": "{{Connection.protocol}}{{Connection.url}}/web/api/v2.1/" ``` @@ -535,6 +572,7 @@ The calling node type is **`snippet_20`**, not `snippet` (see `building-blocks-c Snippets are reusable groups of actions. `data.inputs` is a JSON string mapping the snippet's input parameters to parent references; `use_latest_snippet_version: true` auto-tracks the newest published version. Connect it like any ordinary action (`custom_handle: null`); `custom_handle: "inner"` is only for a loop's first inner action. Authoring a snippet uses `snippet_trigger` + `snippet_output` nodes; full authoring, calling, and lifecycle detail lives in `snippets.md`. ### Create Interaction + ```json { "type": "create_interaction", @@ -548,6 +586,7 @@ Snippets are reusable groups of actions. `data.inputs` is a JSON string mapping } } ``` + `interaction_type: "choice"` is the only flavor seen in active flows. `options` is the array of button labels presented to the analyst in the Hyperautomation console; `form_schema` (when non-null) defines a structured form for free-text/multi-field input. @@ -555,6 +594,7 @@ Reference interaction URLs: `{{create-interaction.interaction_url.} Reference interaction ID: `{{create-interaction.interaction_id}}`. ### Wait for Interaction + ```json { "type": "wait_for_interaction", @@ -571,11 +611,13 @@ Reference interaction ID: `{{create-interaction.interaction_id}}`. } } ``` + **Field name trap**: this action uses `identifier` (not `interaction_id`) and `time_value` (not `value`). Earlier docs got this wrong; the corpus is unambiguous. `expected_respondents: N` blocks until N analysts have responded; default to 1. ### Wait for Slack + ```json { "type": "wait_for_slack", @@ -589,6 +631,7 @@ Reference interaction ID: `{{create-interaction.interaction_id}}`. } } ``` + Reference response: `{{wait-for-slack.body}}`, `{{wait-for-slack.timeout}}`, `{{wait-for-slack.body.actions[0].value}}` @@ -635,13 +678,15 @@ action by name from a fixed list (see `autonomous-soc-template.md`). It is rare ## COMMON PATTERNS ### Success/Fail branch pattern (used throughout M365 workflows) -``` + +```text Action → Condition (Is Success, checks status_code) → TRUE: Variable (success note) → HTTP (add note) FALSE: Variable (fail note) → HTTP (add note) ``` ### Add Note to Unified Alert (GraphQL mutation: most common pattern) + ```json { "name": "Add Note to Alert", @@ -653,6 +698,7 @@ Action → Condition (Is Success, checks status_code) → ``` ### SDL Query (AI SIEM Singularity Data Lake) + ```json { "method": "post", @@ -662,6 +708,7 @@ Action → Condition (Is Success, checks status_code) → ``` ### PowerQuery (SDL Power Query) + ```json { "method": "post", @@ -671,6 +718,7 @@ Action → Condition (Is Success, checks status_code) → ``` ### TI Ingestion (Threat Intelligence IOC) + ```json { "method": "post", diff --git a/plugins/s1-secops-skills/skills/hyperautomation/references/connections.md b/plugins/s1-secops-skills/skills/hyperautomation/references/connections.md index 2fa3dcb..3a6daee 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/references/connections.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/references/connections.md @@ -8,7 +8,7 @@ supplied by the human operator; do not hard-code, echo, or store it. ## Endpoint -``` +```text POST /web/api/v2.1/hyper-automate/api/v1/connections?siteIds= (or ?accountIds=) ``` diff --git a/plugins/s1-secops-skills/skills/hyperautomation/references/functions-reference.md b/plugins/s1-secops-skills/skills/hyperautomation/references/functions-reference.md index e8461be..9fe22b3 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/references/functions-reference.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/references/functions-reference.md @@ -9,6 +9,7 @@ this avoids escaping headaches: `{{Function.JQ(input, local_var.myJqExpression)} --- ## Arithmetic + | Function | Description | Usage | |----------|-------------|-------| | `ADD(n1, n2)` | Sum | `{{Function.ADD(5, 3)}}` | @@ -19,6 +20,7 @@ this avoids escaping headaches: `{{Function.JQ(input, local_var.myJqExpression)} --- ## Date & Time + | Function | Description | Usage | |----------|-------------|-------| | `DATETIME_NOW()` | Current datetime as string | `{{Function.DATETIME_NOW()}}` | @@ -35,6 +37,7 @@ this avoids escaping headaches: `{{Function.JQ(input, local_var.myJqExpression)} --- ## Array & Object Operations + | Function | Description | Usage | |----------|-------------|-------| | `APPEND(array, item)` | Add item to array | `{{Function.APPEND(local_var.myList, loop.item)}}` | @@ -58,20 +61,24 @@ this avoids escaping headaches: `{{Function.JQ(input, local_var.myJqExpression)} --- ## JQ (JSON Query) -The most powerful function. Reference: https://jqlang.org/manual -``` +The most powerful function. Reference: + +```json {{Function.JQ(input, jq_filter)}} {{Function.JQ(input, jq_filter, true)}} ← raw output (no JSON wrapping) ``` **Best practice**: Store the JQ expression in a Variable first: + ```json { "name": "jqFilter", "value": ".[] | select(.score > 50) | {ip: .ipAddress, score: .abuseConfidenceScore}" } ``` + Then use: `{{Function.JQ(local_var.data, local_var.jqFilter)}}` Common patterns: + ```jq # Extract field from array .[].name @@ -95,6 +102,7 @@ Common patterns: --- ## Extraction + | Function | Description | Usage | |----------|-------------|-------| | `EXTRACT_IPS(string)` | Extract all IPs from text | `{{Function.EXTRACT_IPS(response.body)}}` | @@ -112,6 +120,7 @@ Common patterns: --- ## String Manipulation + | Function | Description | Usage | |----------|-------------|-------| | `REPLACE(str, find, replace)` | Replace all occurrences | `{{Function.REPLACE(text, "]", "")}}` | @@ -158,6 +167,7 @@ Common patterns: --- ## Validation & Conditions + | Function | Description | Usage | |----------|-------------|-------| | `DEFAULT(value, fallback)` | Use fallback if value is null/empty | `{{Function.DEFAULT(action.body.field, "")}}` | @@ -167,6 +177,7 @@ Common patterns: --- ## File Handling + | Function | Description | Usage | |----------|-------------|-------| | `COMPRESS(files_array, type?, password?)` | Compress files | `{{Function.COMPRESS([local_var.csvData], "zip")}}` | @@ -179,6 +190,7 @@ For Send Email attachments: pass in `[local_var.fileDataVar]` (in square bracket --- ## Hashing & Cryptography + | Function | Description | Usage | |----------|-------------|-------| | `MD5(input)` | MD5 hash | `{{Function.MD5(text)}}` | @@ -195,6 +207,7 @@ For Send Email attachments: pass in `[local_var.fileDataVar]` (in square bracket --- ## Workflow Metadata + | Function | Description | Usage | |----------|-------------|-------| | `GET_WORKFLOW_ID()` | Current workflow ID | `{{Function.GET_WORKFLOW_ID()}}` | @@ -207,6 +220,7 @@ For Send Email attachments: pass in `[local_var.fileDataVar]` (in square bracket --- ## Variable Retrieval + | Function | Description | Usage | |----------|-------------|-------| | `GET_LOCAL_VAR(name)` | Get local variable | `{{Function.GET_LOCAL_VAR("myVar")}}` | diff --git a/plugins/s1-secops-skills/skills/hyperautomation/references/integration-catalog.md b/plugins/s1-secops-skills/skills/hyperautomation/references/integration-catalog.md index d5332f3..3471bb2 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/references/integration-catalog.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/references/integration-catalog.md @@ -881,4 +881,3 @@ users to a built-in equivalent where one exists. | Vattelappesca | 0 | 1 | - | | brodsky_paladin1 | 0 | 1 | - | | xyz | 0 | 1 | - | - diff --git a/plugins/s1-secops-skills/skills/hyperautomation/references/validation-rules.md b/plugins/s1-secops-skills/skills/hyperautomation/references/validation-rules.md index c6b561a..28752ea 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/references/validation-rules.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/references/validation-rules.md @@ -83,13 +83,16 @@ Run this checklist before outputting any workflow JSON. silently resolve to empty. ❌ Wrong: `fullPath` will be empty because `baseUrl` is resolved in the same pass: + ```json { "variables": [ { "name": "baseUrl", "value": "https://api.example.com" }, { "name": "fullPath", "value": "{{local_var.baseUrl}}/v1/alerts" } ]} ``` + ✅ Right: split into two sequential Variable actions: + ```json // Action 1 { "variables": [{ "name": "baseUrl", "value": "https://api.example.com" }] } diff --git a/plugins/s1-secops-skills/skills/hyperautomation/references/workflow-schema.md b/plugins/s1-secops-skills/skills/hyperautomation/references/workflow-schema.md index f362363..fc7fdcd 100644 --- a/plugins/s1-secops-skills/skills/hyperautomation/references/workflow-schema.md +++ b/plugins/s1-secops-skills/skills/hyperautomation/references/workflow-schema.md @@ -51,12 +51,15 @@ Every item in the `actions` array is an **action object**: ## Key fields explained ### `export_id` + A unique integer within the workflow, used as the node ID in the graph. Assign sequentially: 0, 1, 2, 3... starting from the LAST action (the terminal node gets 0). The trigger typically gets the highest export_id. ### `connected_to` + Defines edges (connections) to downstream actions. + - `target`: the `export_id` of the next action - `custom_handle`: - `null`: default (only one outgoing connection) @@ -64,22 +67,26 @@ Defines edges (connections) to downstream actions. - `"inner"`: used for the body of a loop (the first action inside the loop) ### `parent_action` + - `null`: action is at the top level of the workflow - ``: `export_id` of the loop that contains this action ### `tag` + - `"core_action"`: built-in actions (Variable, Loop, Condition, HTTP Request without integration, Send Email, Delay, Break Loop, Scheduled/Manual/HTTP/Email/Singularity Trigger, Snippet, Wait for Slack, Create Interaction, Wait for Interaction) - `"integration"`: actions backed by a pre-configured integration connection ### `connection_id` / `connection_name` / `use_connection_name` + - For core actions: all null/false - For integration actions: `connection_id` is the UUID of the configured connection. When generating for import, set `connection_id: null`, `connection_name: ""`, `use_connection_name: false`, the user will configure the connection after import. ### `integration_id` + - For core actions: `null` - For integration actions: the UUID of the integration type (e.g., SentinelOne = `"ef645af9-ed60-4efd-882e-bf534442ce86"`, M365/Entra = `"73475bd9-3762-4f17-aab5-c544ec5ec31b"`) When generating for a new workflow, set to `null`; it will be resolved from the connection. @@ -89,6 +96,7 @@ Defines edges (connections) to downstream actions. ## Position / layout (client_data) Position fields affect the visual canvas only. Use these conventions for readability: + - First action (trigger): `{ "x": 0, "y": 0 }` - Each subsequent step: increment `y` by ~177 (height 76 + gap 101) - Parallel branches: offset `x` by ±160-300 @@ -102,7 +110,8 @@ Loop containers: wider/taller to contain children, `{ "width": 760, "height": 75 ## Dynamic variable reference syntax Reference values from previous actions using double curly braces: -``` + +```json {{action-slug.field.subfield}} {{action-slug.body.data[0].computerName}} {{action-slug.status_code}} @@ -162,7 +171,8 @@ For `"in"` operator, `compared_value` is a JSON array string: `"[\"HIGH\",\"CRIT ## Condition action: two styles -### Simple condition (nested `condition` object, `condition_type: "simple"`): +### Simple condition (nested `condition` object, `condition_type: "simple"`) + ```json "condition_type": "simple", "condition": { @@ -188,7 +198,8 @@ For `"in"` operator, `compared_value` is a JSON array string: `"[\"HIGH\",\"CRIT "conditions": null ``` -### Multi condition (flat `conditions` array, `condition_type: "multi"`): +### Multi condition (flat `conditions` array, `condition_type: "multi"`) + ```json "condition_type": "multi", "condition": null, diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/SKILL.md b/plugins/s1-secops-skills/skills/mgmt-console-api/SKILL.md index 6edbafd..461ee45 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/SKILL.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/SKILL.md @@ -89,198 +89,7 @@ If you are about to call `s1_api_post` for a read/count/export operation, stop a These are fields where the obvious guess is wrong. All verified against the live tenant. -### Custom Detection Rules and STAR Rules: POST /cloud-detection/rules - -**STAR rules** ("streaming threat assessment rules") are the product name for real-time -detection rules that evaluate every matching event as it arrives. There is no separate -`/star-rules` API path; that path returns 404. STAR rules are `cloud-detection/rules` -with `queryType=events`, the same endpoint as all other Custom Detection Rules. - -`queryType` enum (from swagger): `events`, `scheduled`, `correlation`, `uebafirstseen`. - ---- - -#### ⚠️ LISTING DETECTION RULES: read this BEFORE calling GET /cloud-detection/rules - -The list endpoint hides `queryType: "scheduled"` rules by default. **Without `isLegacy=false` in the query string, scheduled rules return 0 results even though they exist and are visible in the console UI.** This is the single most common drift between API output and console reality. There is no error, no warning, no hint, the response simply omits scheduled rules. Re-confirmed against the live API, 2026-05. - -**Always pass `isLegacy=false` when listing.** The only time you can omit it is if you are 100% sure you only care about events-type STAR rules and want to filter scheduled rules out. - -| Goal | Correct query | Anti-pattern (silently wrong) | -|---|---|---| -| List all detection rules | `GET /cloud-detection/rules?isLegacy=false&limit=200` | `GET /cloud-detection/rules?limit=200`: drops scheduled | -| List only scheduled detections | `GET /cloud-detection/rules?isLegacy=false&queryType=scheduled&limit=200` | `GET /cloud-detection/rules?queryType=scheduled`: returns 0 | -| List only events (STAR) rules | `GET /cloud-detection/rules?queryType=events&limit=200` | (this one is safe; events rules are not gated by `isLegacy`) | -| Search by name across all types | `GET /cloud-detection/rules?isLegacy=false&name__contains=Foo` | `GET /cloud-detection/rules?name__contains=Foo`: drops scheduled | - -**Verdict-language note for analysts:** if you query without `isLegacy=false` and the response is empty, the correct statement is "no events rules came back; I haven't yet checked scheduled rules", not "this tenant has zero scheduled detections". Promoting the absence of evidence to "confirmed zero" is the failure mode this gotcha exists to prevent. - -`queryType` accepts a single value or an array (comma-separated): `queryType=scheduled` or `queryType=events,scheduled` both work. The combination `queryType=` + `nameSubstring=` returns HTTP 500; use one filter at a time, or use `name__contains` instead of `nameSubstring` (which works with both). - ---- - -**⚠️ Pick the right `queryType` for your rule body BEFORE composing the POST. Mismatches return HTTP 400.** - -| Rule body language | Correct `queryType` | Correct `queryLang` | Field carrying the query | -|---|---|---|---| -| **PowerQuery (pipe syntax `\|`)** | **`scheduled`** | **`"2.0"`** | `data.scheduledParams.query` | -| S1QL log-search / event search | `events` | `"1.0"` (default, omit) | `data.s1ql` | -| EUEBA first-seen | `uebafirstseen` | n/a | per swagger schema | -| Correlation | `correlation` | **`"2.0"`** | `data.correlationParams` (`entity`, `matchInOrder`, `subQueries[]`) | - -**Any time the rule body has the pipe character `\|` (i.e. PowerQuery), the only working path is `queryType: "scheduled"` + `queryLang: "2.0"`.** `queryType: "events"` rejects pipe syntax (HTTP 400 `Don't understand [|]`), and `queryLang: "2.1"` is not in the enum (HTTP 400 `queryLang: "2.1" is not a valid choice`). Confirmed against the live API, 2026-05. - -**Correlation rules also require `queryLang: "2.0"`.** Confirmed against the live API, 2026-06: a `queryType: "correlation"` POST without `queryLang` (or with `"1.0"`) returns HTTP 400 `query lang must be 2.0`, even though the subquery bodies themselves can be boolean S1QL (e.g. `EventType = "Logon" AND LogonResult = "Fail"`). So only single-event `events` rules use 1.0; both `scheduled` and `correlation` require 2.0. `correlationParams` requires `entity` (`user`/`process`/`ip`/`endpoint`/`storyline`/`custom`/`none`) and `matchInOrder`, with 1 to 10 `subQueries[]` (each `{subQuery, matchesRequired}`); `timeWindow.windowMinutes` is one of {1,5,10,30,60,240,480,720}. - -**Operational learnings, validated live (2026-07, custom detection rules + alerts):** - -- **1.0 operators do not evaluate under `queryLang 2.0`.** An events rule whose body used the S1QL-1.0 operator `ContainsCIS` was accepted at create time but never fired; rewriting it to the 2.0 operator `contains:anycase` fired immediately. `scheduled` and `correlation` rules are always 2.0, so their bodies must use 2.0 operators (`contains:anycase`, `in:anycase`, etc.), not 1.0 forms (`ContainsCIS`, `In`). For `events` rules the story is more subtle: omitting `queryLang` stores `"1.0"` (live-verified 2026-07-29: a POST without `queryLang` came back `queryLang: "1.0"`), but a fleet audit found 99 of 100 live events rules carry `"2.0"` because creators set it explicitly to get the 2.0 operator set. Decide deliberately: leave it defaulted to 1.0 and write 1.0 operators, OR set `queryLang: "2.0"` explicitly and write 2.0 operators. Mixing (2.0 rule, 1.0 operator, or vice versa) stores fine and silently matches nothing. Lint rule bodies for operator/lang mismatch before deploy. -- **Alerts inherit the rule's `description`.** The generated alert carries the rule description verbatim (`ruleInfo.description` on `GET /cloud-detection/alerts`, and `description` on UAM alerts). Because the rule schema has no native MITRE/tag/custom-attribute field, embedding metadata in the description (e.g. a `[DaC] MITRE: ... | Tags: ... | Owner: ...` footer) is the way to surface MITRE/tags/owner on a custom-rule alert. -- **Alert-surface split by rule type.** `events` and `correlation` alerts appear in `GET /cloud-detection/alerts`; **scheduled** (PowerQuery) detection alerts do NOT surface there, they appear only in **UAM** (`unifiedalerts` / `uam_list_alerts`). Look for scheduled-rule alerts in UAM, not the REST alerts path. Also `sortBy=createdAt` on `/cloud-detection/alerts` returns `400 "not a valid choice"`, and a free-text `searchText` on `uam_list_alerts` can throw `Field * does not exist or not supported` (use structured filters / `viewType` instead). -- **Scheduled-rule activation latency, and PUT resets it.** After enable, a scheduled rule sits in `Activating` ("will become Active within an hour") before its first run, then runs on `runIntervalMinutes`. Every PUT/update re-enters `Activating`, so repeated re-syncs delay firing. Enable once and avoid churn when waiting for a scheduled rule to fire. - -See the **PowerQuery Scheduled Detections** subsection below for the full scheduled-rule body, gotchas, and the feature-flag fallback path if the tenant has not enabled Scheduled Detections. - -`expirationMode` valid values: `"Permanent"` or `"Temporary"`. `"Never"` is not valid and returns 400. - -**Events (STAR) rule, confirmed CREATE body (live API, 2026-05):** - -```json -{ - "data": { - "name": "my-star-rule", - "description": "optional", - "severity": "Low", - "expirationMode": "Permanent", - "queryType": "events", - "status": "Draft", - "s1ql": "EventType = \"Process Creation\" AND ProcessName = \"suspicious.exe\"", - "treatAsThreat": "UNDEFINED", - "networkQuarantine": false - }, - "filter": {"siteIds": [""]} -} -``` - -Events rule gotchas confirmed against live API: - -- `"activeResponse"` in the body returns HTTP 400 "Unknown field": omit it. -- `queryLang` defaults to `"1.0"` for events rules; do not set it explicitly (unlike scheduled rules which require `"2.0"`). -- `treatAsThreat="UNDEFINED"` is accepted on input but stored as `null` in the response. -- `status="Draft"` creates a rule that never fires even if live telemetry matches. -- GET `nameSubstring` + `queryType` combined returns HTTP 500: use one filter at a time. -- DELETE body is top-level `{filter: {ids: [...], siteIds: [...]}}` with no `"data"` wrapper. -- `isLegacy=false` is NOT needed for events rules (only required for scheduled rules). - -`status` valid values: `"Draft"`, `"Activating"`, `"Active"`, `"Disabling"`, `"Disabled"`. Use `"Disabled"` to create a non-firing rule; the API returns `"Draft"` in the response for a rule that has never been activated. - -**PUT /cloud-detection/rules/{id}**, `status` is required in the PUT body even though it is read-only in practice. Omitting it returns 400 "Missing data for required field." - -### Saved Filters: POST /filters - -The body field for filter criteria is `filterFields`, not `filters`: - -```json -{ - "data": { - "name": "my-filter", - "filterFields": {"infected": true, "networkStatuses": ["connected"]} - }, - "filter": {"accountIds": [""]} -} -``` - -**PUT /filters/{id}**, does NOT accept a top-level `filter` scope wrapper. Pass only `{"data": {...}}`: - -```json -{"data": {"name": "updated-name", "filterFields": {"infected": false}}} -``` - -### PowerQuery Scheduled Detections: POST/PUT/GET/DELETE /cloud-detection/rules - -`queryType: "scheduled"` rules are PowerQuery-based detections. **This is the only path that accepts pipe-syntax PowerQuery in a detection rule body.** Events rules reject pipe syntax even with `queryLang: "2.0"`. They use a different schema from `events` and `correlation` rules and have several non-obvious requirements confirmed against the live API (2026-05). - -**If the POST fails with a feature-not-enabled / not-licensed / unauthorized response on a tenant where the schema is otherwise correct, stop and tell the user to enable the Scheduled Detections feature on the tenant before retrying.** Do not silently downgrade the rule body to S1QL or re-attempt with `queryType: "events"`. The console path is typically *Settings → Account → Detection / SDL Add-Ons → Scheduled Detections* but varies by platform version, so phrase the ask in terms of capability ("please enable Scheduled Detections on this account") rather than the exact click path. - -#### CREATE: POST /web/api/v2.1/cloud-detection/rules - -All five `data` fields are required. `queryLang: "2.0"` is mandatory; omitting it returns HTTP 400 "query lang must be 2.0". `filter` accepts `accountIds` or `siteIds` (account-level rules cover all sites under that account): - -```json -{ - "data": { - "name": "My Scheduled Detection", - "queryType": "scheduled", - "queryLang": "2.0", - "severity": "Medium", - "expirationMode": "Permanent", - "status": "Disabled", - "scheduledParams": { - "query": "dataSource.name='Proofpoint' event.type='Click' unmapped.classification='malware' | group hits=count(), first_seen=oldest(timestamp), last_seen=newest(timestamp) by clickIP | filter hits >= 1 | sort -hits | limit 100", - "runIntervalMinutes": 60, - "lookbackWindowMinutes": 60, - "threshold": {"value": 0, "operator": "Greater"} - } - }, - "filter": {"accountIds": [""]} -} -``` - -**Scheduled-rule gotchas confirmed against live API (2026-05):** - -- `disableAgentMitigation` is **not** part of the scheduled schema. Including it returns HTTP 400 `Unknown field`. Cloud-source PQ rules do not need it; mitigation actions are not supported on scheduled rules anyway. -- `treatAsThreat: "Malicious"` is for events rules with EDR telemetry. Scheduled rules accept `treatAsThreat: "UNDEFINED"` (or omit) and `networkQuarantine: false`. The verdict surfaces via the rule's `severity`, not via mitigation. -- New rules are created in `Draft` status regardless of the requested `status` in the POST. To enable, call `PUT /web/api/v2.1/cloud-detection/rules/enable` with `{"filter": {"ids": [...], "accountIds": [...]}}` after creation. The response transitions to `Activating` and then `Active` within the hour. -- The PowerQuery in `scheduledParams.query` must NOT use `nolimit`, `compare`, or subqueries. The 1,000-row intermediate cap documented in the `powerquery` skill at `references/detection-rules.md` applies. - -**GET requires `isLegacy=false`**, without this query parameter, the list endpoint returns 0 results for scheduled rules even though they exist and are visible in the console UI. Always include it. **When listing all rules regardless of type (e.g. searching by name), always pass `isLegacy=false`, without it you will only see events-type rules and silently miss all scheduled rules:** - -```text -GET /web/api/v2.1/cloud-detection/rules?isLegacy=false&limit=100 -GET /web/api/v2.1/cloud-detection/rules?siteIds=&queryType=scheduled&isLegacy=false -GET /web/api/v2.1/cloud-detection/rules?ids=&siteIds=&isLegacy=false -``` - -Note: `name__contains` filter silently returns 0 for scheduled rules when `isLegacy=false` is omitted. Always pair name searches with `isLegacy=false`. - -**PUT requires `filter.siteIds`** in the body even though swagger marks `filter` as optional. Also requires all five data fields (name, queryType, severity, expirationMode, status). Additional PUT gotchas confirmed 2026-05-26: - -- `activeResponse` in the PUT body returns HTTP 400 "Unknown field": omit it (same as POST). -- `treatAsThreat: null` in the PUT body returns HTTP 400 "Field may not be null": use `"UNDEFINED"` even though the GET response shows `null`. The API stores it as `null` but rejects `null` as input. -- `status` must be included (e.g. `"Active"`) even though it is effectively read-only on PUT. - -```json -{ - "data": { - "name": "...", "queryType": "scheduled", "queryLang": "2.0", - "severity": "High", "expirationMode": "Permanent", "status": "Active", - "networkQuarantine": false, "treatAsThreat": "UNDEFINED", - "scheduledParams": {...} - }, - "filter": {"siteIds": [""]} -} -``` - -**ENABLE/DISABLE**, dedicated PUT endpoints. The filter takes `ids` plus an optional scope (`siteIds` or `accountIds`); `ids` alone works because rule IDs are globally unique. Do NOT include `isLegacy` in the body, it returns `400 filter: isLegacy: Unknown field` (`isLegacy` is a GET-listing param only). Tenant-validated 2026-06-16: `{"filter": {"ids": [...]}}` returned `{"affected": N}`. - -```text -PUT /web/api/v2.1/cloud-detection/rules/enable body: {"filter": {"ids": [""], "siteIds": [""]}} -PUT /web/api/v2.1/cloud-detection/rules/disable body: {"filter": {"ids": [""], "siteIds": [""]}} -``` - -**DELETE body; use `json_body=` keyword arg in s1_client.py.** The method signature is `delete(path, params=None, json_body=None)`. Passing the filter dict positionally sends it as query params and returns HTTP 400: - -```python -# Correct -client.delete("/web/api/v2.1/cloud-detection/rules", - json_body={"filter": {"ids": [rule_id], "accountIds": [acct_id]}}) - -# Wrong: sends filter as query string -client.delete("/web/api/v2.1/cloud-detection/rules", - {"filter": {"ids": [rule_id], "accountIds": [acct_id]}}) -``` - -**Verify deletion with GET (expect 0 hits), not a second DELETE.** A second DELETE returns HTTP 400 "Could not find rule with id: ..." rather than `affected: 0`. +The full request-body schema gotchas for Custom Detection / STAR rules, Saved Filters, and PowerQuery Scheduled Detections (the `queryType` / `queryLang` matrix, the `isLegacy=false` listing trap, confirmed CREATE / PUT / DELETE bodies, and the enable / disable endpoints) live in [references/detection-rules.md](references/detection-rules.md). Read it before composing any POST or PUT to `/cloud-detection/rules` or `/filters`. ### Alert notes: UAM GraphQL @@ -325,48 +134,27 @@ It enumerates every GET plus a curated allow-list of read-only query POSTs, reco - `references/tags/.md`: per-tag reference with parameters, descriptions, and required permissions. Load only the files you need. - `references/common_params.md`: shared query params (`skip`, `limit`, `cursor`, `sortBy`, etc.) and the pagination pattern. - `references/POWERQUERY_RECIPES.md`: PowerQuery / SDL query recipes tested on-tenant: indicator prevalence, PowerShell outbound to public IPs, failed-login triage, storyline activity summary, UAM-indicator SDL crosscheck, endpoint heartbeat. For full PQ language reference use the dedicated `powerquery` skill. +- `references/detection-rules.md`: request-body schema gotchas for Custom Detection / STAR rules, Saved Filters, and PowerQuery Scheduled Detections (queryType / queryLang matrix, `isLegacy=false` listing trap, confirmed CREATE / PUT / DELETE bodies, enable / disable endpoints). +- `references/purple-ai.md`: Purple AI GraphQL reference (hosts, endpoints, request shapes, operation flows, Python CLI notes, domain boundary, and the confirmed API-token limitations table). +- `references/querying-logs.md`: the foolproof PowerQuery / LRQ procedure via `scripts/pq.py`, plus the UAM, UAM Alert Interface, data-source and schema discovery, baseline anomaly, CTO reporting, common-workflow, `s1-secops-mcp`, and Hyperautomation operational playbooks. +- `references/python-client.md`: full `S1Client` Python usage examples (single page, `iter_items`, parallel `get_many` fan-out, action POSTs). - `spec/swagger_2_1.json`, the original full Swagger spec (14 MB). Use only when the per-tag reference is insufficient, e.g. to resolve a deeply nested request-body schema by `$ref`. Never read this whole file into context. - `tests/test_ioc_lifecycle.py`: reversible CREATE → LIST → DELETE → VERIFY round-trip for Threat Intelligence IOCs. Uses a unique run-tag per invocation, scopes to a single account, and cleans up before exit. Covers the one "create content" path against the S1 detection surface. - `tests/test_alerts_dual_api.py`: dual-API round-trip for alerts: GraphQL list/detail/addNote/notes/deleteNote plus a parallel REST `/cloud-detection/alerts` read. Demonstrates that UAM GraphQL is the PRIMARY alert surface and REST is SECONDARY, with the note mutation cleaned up before exit (handles the `mgmt_note_id` propagation delay). - `scripts/pq.py`: foolproof PowerQuery runner over the LRQ API. Wraps launch/poll/cancel, auth flip to `Bearer`, `X-Dataset-Query-Forward-Tag` capture, exponential backoff on 5xx/429/connection errors, and a best-effort cancel. One call: `run_pq(client, "", hours=24)` returns `{row_count, columns, rows, matchCount, ...}`. Also exposes `list_data_sources(client, hours=24)` for the first-response "does this data source actually exist on this tenant?" check. Use this any time a user says "query logs", "run a PQ", "search for events" via the mgmt console API. -- `scripts/inspect_source.py`: source-agnostic schema discovery. For any `dataSource.name`, samples raw events via the LRQ `LOG` queryType (or sync `/sdl/api/query` when available) and classifies every attribute the parser emits into `principal_user` / `principal_host` / `principal_ip` / `action` / `temporal` / `network` / `file` / `process` / `grouping_candidate` / `other`. Picks `prim_key` + `action_key` from whatever the source actually carries, so downstream code never hardcodes field names. Exports `discover_schema(client, source, hours, sample, extra_filter, backend, escalate)` and `pick_keys(schema)`; CLI: `python scripts/inspect_source.py --source "" --window 24h`. See "Data source + schema discovery" below. +- `scripts/inspect_source.py`: source-agnostic schema discovery. For any `dataSource.name`, samples raw events via the LRQ `LOG` queryType (or sync `/sdl/api/query` when available) and classifies every attribute the parser emits into `principal_user` / `principal_host` / `principal_ip` / `action` / `temporal` / `network` / `file` / `process` / `grouping_candidate` / `other`. Picks `prim_key` + `action_key` from whatever the source actually carries, so downstream code never hardcodes field names. Exports `discover_schema(client, source, hours, sample, extra_filter, backend, escalate)` and `pick_keys(schema)`; CLI: `python scripts/inspect_source.py --source "" --window 24h`. See "Data source + schema discovery" in [references/querying-logs.md](references/querying-logs.md). - `scripts/uam_alert_interface.py`: UAM (Unified Alert Management) Alert Interface client for pushing OCSF indicators + alerts INTO UAM via `POST /v1/indicators` and `POST /v1/alerts` on the SentinelOne HEC ingest host (e.g. `ingest.us1.sentinelone.net`, the same host used for log ingest). Handles the gzip-compressed concatenated-JSON body, `Bearer` auth (the endpoint rejects `ApiToken`), and the `S1-Scope` header. Exposes `UAMAlertInterfaceClient`, plus `build_file_indicator()`, `build_process_indicator()`, `build_network_indicator()`, and `build_alert_referencing()` payload helpers. URL defaults to `https://ingest.us1.sentinelone.net`; override via the `S1_HEC_INGEST_URL` env var or credentials.json key (former canonical `S1_UAM_ALERT_INTERFACE_URL` and legacy snake_case `uam_alert_interface_url` still honored). - `tests/test_uam_alert_interface_single.py`: minimum-viable reversible write-side round-trip: POST one OCSF FileSystem-Activity indicator + one SecurityAlert referencing it, poll UAM GraphQL until the alert surfaces, verify the indicator is stitched in, then close the alert via bulk-ops (status=RESOLVED, analystVerdict=TRUE_POSITIVE_BENIGN). Covers the single-indicator happy path into UAM. - `tests/test_uam_alert_interface_batch.py`: comprehensive reversible round-trip: batched POST of 3 indicators (OCSF classes 1001 FileSystem Activity, 1007 Process Activity, 4001 Network Activity) each carrying 3+ observables, referenced by a single SecurityAlert via `finding_info.related_events[]`. Verifies all 3 metadata.uids and their observable names surface in `alert.rawIndicators`, then closes the alert. Covers batching, multi-observable, and multi-indicator linkage. - `scripts/ingestion_gateway.py` + `tests/test_ingestion_gateway_alert_with_indicator.py`: deprecated back-compat shims. The helper re-exports from `uam_alert_interface`; the test prints a pointer to the renamed file and exits non-zero. - `scripts/build_source_report.py`: collector for the CTO report pipeline. Runs dimension probes + per-principal mix + timeline for a named data source via `scripts/pq.py` and writes `reports/_/data.json`. Outputs to a per-source subfolder so multiple sources and windows coexist cleanly. - `scripts/render_charts.py`: pure-function renderer. `data.json` in, PNG charts out under `reports/_/charts/`. No tenant calls. -- `scripts/build_docx.py` / `scripts/build_pptx.py`: source-agnostic renderers that read `data.json` and emit `_CTO_Report_.docx` and `_CTO_Deck_.pptx`. Every section is gated on `dims` so dimension-sparse sources render cleanly. See "CTO report generation pipeline" below for the full contract and renderer gotchas. +- `scripts/build_docx.py` / `scripts/build_pptx.py`: source-agnostic renderers that read `data.json` and emit `_CTO_Report_.docx` and `_CTO_Deck_.pptx`. Every section is gated on `dims` so dimension-sparse sources render cleanly. See "CTO report generation pipeline" in [references/querying-logs.md](references/querying-logs.md) for the full contract and renderer gotchas. - `reports/_/`: per-run artefact directory. Holds `data.json`, `charts/`, and the rendered `.docx` / `.pptx`. Treat this as the portable unit: move or archive the whole folder. ## Using the client in Python -```python -import sys -sys.path.insert(0, "scripts") # or set PYTHONPATH -from s1_client import S1Client, S1APIError - -c = S1Client(cache_ttl=60) # optional 60s cache for accounts/sites/groups/system-info - -# single page -r = c.get("/web/api/v2.1/threats", params={"limit": 100, "resolved": False}) - -# full iteration -for threat in c.iter_items("/web/api/v2.1/threats", params={"limit": 200}): - ... - -# parallel fan-out: independent GETs over pooled connections (~3× faster) -results = c.get_many([ - ("/web/api/v2.1/accounts", {"limit": 1}), - ("/web/api/v2.1/sites", {"limit": 1}), - ("/web/api/v2.1/groups", {"limit": 1}), - ("/web/api/v2.1/system/info", None), -], max_workers=8) -# -> [{"path":..., "ok":True, "status":200, "data":..., "elapsed_ms":...}, ...] - -# action endpoint -c.post("/web/api/v2.1/agents/actions/disconnect", json_body={"filter": {"ids": ["AGENT_ID"]}}) -``` +Import `S1Client` from `scripts/s1_client.py` for anything needing loops, joins, or pagination; use `c.get_many([...])` for parallel independent GETs over pooled connections. Full worked examples (single page, `iter_items`, parallel fan-out, and an action POST) are in [references/python-client.md](references/python-client.md). ## Authentication @@ -386,849 +174,19 @@ The safe pattern: run the matching `GET` with `countOnly=true` first to show the ## Purple AI: natural-language query, alert summary, and auto-investigation -> **Precedence:** if the user explicitly mentions "purple mcp" or "mcp", prefer the Purple MCP tools (`mcp__purple-mcp__purple_ai`, `mcp__purple-mcp__powerquery`, etc.); this skill is the backup path in that case. Use the wrapper below when the user has asked for the S1 console/API directly, when Purple MCP is unavailable, or when you need to script a raw GraphQL call. - -### Hosts - -| Host | Purpose | -|---|---| -| `.sentinelone.net` | Management console: all REST and GraphQL calls use this host | -| `id.na1.sentinelone.net` | Browser session keep-alive (not relevant for API tokens) | -| `metrics-proxy-use1.na1.sentinelone.net` | Internal metrics (returned 503 in observed sessions, non-fatal) | - -### Endpoints - -Purple AI uses **three** GraphQL endpoints (all traffic is `POST` with JSON body). Each request carries `?opname=&requestId=` as query-string params for tracing; the server only inspects the JSON body. - -| Endpoint | Purpose | -|---|---| -| `POST /web/api/v2.1/graphql` | LLM dispatcher: `purpleLaunchQuery`, `purpleAlertSummary` | -| `POST /sdl/v2/graphql` | Notebook lifecycle + SDL query execution (see operation table below) | -| `POST /web/api/v2.1/unifiedalerts/graphql` | UAM: auto-investigation trigger and polling | - -Auth uses the same `Authorization: ApiToken ` header as REST. No extra credential setup. - -### SDL operation table (`POST /sdl/v2/graphql`) - -| opname | Triggered by | Purpose | -|---|---|---| -| `notebooks` | Sidebar load | List My Notebooks / Shared Notebooks | -| `createNotebook` | "+ New Notebook" or starter prompt | Creates a notebook; response includes `teamToken` | -| `purpleNotebook` | Selecting a notebook | Loads full Q&A history for the notebook | -| `addPurpleInputOutputMessage` | After each LLM/query step | Persists user prompt + AI output into the notebook | -| `launchQuery` | After LLM produces a PowerQuery | Executes the PQ against the data lake | -| `pingQuery` | Repeatedly while query runs | Short-poll for status/progress/results | -| `removeQuery` | On completion or cancel | Cleans up the query and releases resources | - -### Bootstrap REST calls (browser-only) - -These fire on page load and when entering a notebook. Not needed for API-token workflows, but useful for debugging feature availability: - -- `GET /web/api/v2.1/private/system/enabled-features?siteIds=`: feature flags (`purpleNative`, `purpleConversations`, `purpleAutoInvestigations`) -- `GET /web/api/v2.1/private/rbac/user-permissions?siteIds=`: current user's permission set -- `POST /web/api/v2.1/private/users/session-info`: session info - -### Operations - -#### purpleLaunchQuery: NL → PowerQuery (or summary/suggestions) - -**Critical: this is a GraphQL `query` (not `mutation`). Variable wrapper is `request` (type `PurpleLaunchQueryRequest`). Prior implementations using `mutation` + `PurpleLaunchQueryInput` fail with HTTP 400.** - -`contentType` controls what the LLM is asked to do: - -| contentType | Returns | -|---|---| -| `NATURAL_LANGUAGE` | `result.powerQuery.query`: the generated PQ string | -| `QUERY_RESULTS` | `result.summary`: English summary of a previous PQ result set | -| `SUGGEST_QUESTIONS` | `result.suggestedQuestions[]`: follow-up question chips | -| `STAR_RULE`, `DETECTION_RULE` | `result.detectionRule`: generated detection logic | - -```graphql -query purpleLaunchQuery($request: PurpleLaunchQueryRequest!) { - purpleLaunchQuery(request: $request) { - token - resultType - status { state error { errorType errorDetail origin } } - stepsCompleted - result { - message - summary - maskedMetadata - powerQuery { query viewSelector timeRange { start end } } - suggestedQuestions { question powerQuery viewSelector timeRange { start end } } - } - } -} -``` - -Variables (`$request`): - -```jsonc -{ - "request": { - "isAsync": false, - "contentType": "NATURAL_LANGUAGE", - "consoleDetails": { "baseUrl": "https://.sentinelone.net/", "version": "S-26.1.3#69" }, - // TOP-LEVEL inputContent required (confirmed: HTTP 400 "missing input value at $request.inputContent" without it) - "inputContent": { - "userInput": "", - "viewSelector": "EDR", - "displayedTimeRange": { "start": , "end": }, - "resultsPq": null, "powerQueryForResults": null, "contextId": null, "userDetails": null - }, - "conversation": { - "id": "", - "messages": [{ - "inputMessage": { - "id": "", "feedItemId": "<32hex>", "conversationId": "", - "createdAt": "", "messageType": "INPUT", "contentType": "NATURAL_LANGUAGE", - "inputContent": { - "userInput": "", - "viewSelector": "EDR", - "displayedTimeRange": { "start": , "end": }, - "resultsPq": null, "powerQueryForResults": null, "contextId": null, "userDetails": null - } - } - }] - } - } -} -``` - -`PurpleUserDetailsRequest` schema (confirmed via live validation error, does NOT have `siteIds` or `groupIds`): - -```jsonc -{ - "accountId": "<19-digit ID>", // ID! required - "teamToken": "<24-char>", // ID! required, session token from Purple AI workspace - "sessionId": "", - "emailAddress": "", - "userAgent": "", - "buildDate": "", - "buildHash": "" -} -``` - -Full end-to-end flow for one user question (observed in browser session): - -```text -1. purpleLaunchQuery (NATURAL_LANGUAGE, /web/api/v2.1/graphql) - → result.powerQuery.query (the generated PQ string) - -2. addPurpleInputOutputMessage (/sdl/v2/graphql) - → persists user prompt + generated PQ into the notebook - -3. launchQuery (/sdl/v2/graphql) - → submits PQ to SDL execution engine - → returns { token, ids, status: RUNNING } - -4. pingQuery × N (/sdl/v2/graphql, ~1 Hz) - → polls until status = COMPLETED - → returns columns + cells (table data) - -5. purpleLaunchQuery (QUERY_RESULTS, /web/api/v2.1/graphql) - → result.summary (English summary of the rows) - -6. purpleLaunchQuery (SUGGEST_QUESTIONS, /web/api/v2.1/graphql) - → result.suggestedQuestions[] (follow-up chips) - -7. addPurpleInputOutputMessage (/sdl/v2/graphql) - → persists the final answer into the notebook - -8. removeQuery (/sdl/v2/graphql) - → cleanup / releases query budget -``` - -Starter-prompt flow (e.g. "Find install logs" on the homepage): - -```text -createNotebook → purpleLaunchQuery (NATURAL_LANGUAGE) → addPurpleInputOutputMessage -``` - -Note: starter prompts that return a documentation-style answer skip the `launchQuery`/`pingQuery` stage entirely. - -**API-token users: use the Purple MCP.** The `purple_ai_query` MCP tool has been removed, `purpleLaunchQuery NATURAL_LANGUAGE` is confirmed non-functional for service-account API tokens (requires browser-session teamToken; returns AsimovError from LaunchQueryManager). Use `mcp__purple-mcp__purple_ai` for NL queries and `mcp__purple-mcp__powerquery` to execute the returned PQ. - -#### purpleAlertSummary: per-alert natural-language summary - -Separate operation (not purpleLaunchQuery). Synchronous (isAsync=false, no polling). - -```graphql -query AlertSummary($request: PurpleAlertSummaryRequest!) { - purpleAlertSummary(request: $request) { - token - result { summary } - } -} -``` - -Variables: `request.contentType = "ALERT_ENTRY"`, `request.inputAlert = ""`, `request.consoleDetails`, `request.userDetails`. - -**MCP tool:** `purple_ai_alert_summary`, call `uam_get_alert` first to get the OCSF JSON, then pass it here. - -#### aiInvestigations: auto-investigation - -Two-step flow, both via the UAM GraphQL endpoint (`/web/api/v2.1/unifiedalerts/graphql`): - -1. `alertTriggerActions` mutation with `id: "S1/aiInvestigation/run"`: fires investigation -2. `GetAlertAiInvestigations` query, polled every ~4s: streams `investigationStep` text, final `result` (markdown, ~16KB) + `verdict` enum - -Verdict enum: `UNKNOWN`, `TRUE_POSITIVE`, `FALSE_POSITIVE`. - -**API-token users: use the Purple MCP.** The `purple_ai_investigate` MCP tool has been removed, `aiInvestigation/run` via `alertTriggerActions` is confirmed non-functional for service-account API tokens (returns SERVICE_ERROR; same teamToken dependency as `purpleLaunchQuery`). Use `mcp__purple-mcp__purple_ai` for AI investigations. - -### Python CLI - -> **`purple_query()` and `scripts/call_purple.py` are non-functional for API tokens.** Both call `purpleLaunchQuery NATURAL_LANGUAGE`, which requires a browser-session teamToken that service accounts never have (confirmed SERVICE_ERROR 2026-05-03). Use the Purple MCP instead: -> -> ```text -> mcp__purple-mcp__purple_ai # natural-language query → PQ → result -> mcp__purple-mcp__powerquery # run a PQ string directly -> ``` -> -> `scripts/purple_ai.py` and `scripts/call_purple.py` are kept in the skill for reference but will fail with AsimovError on any service-account token. - -### Domain boundary - -Purple AI answers questions about **SDL telemetry** (process/network/file events, indicators, ingested logs). It does **not** answer questions about **console entities** (alerts, threats, agents, sites, policies). Those are REST resources. Out-of-domain questions return `resultType: "MESSAGE"` with a guardrail refusal, switch to the REST path. - -### Caveats and confirmed API-token limitations (live-tested 2026-05-03) - -| Operation | API-token result | Notes | -|---|---|---| -| `purpleAlertSummary` (ALERT_ENTRY) | **PASS**: returns real LLM summary | Works with `userDetails: null`; no SDL dependency | -| `purpleLaunchQuery` (NATURAL_LANGUAGE) | **FAIL**: `AsimovError` from `LaunchQueryManager` | LLM layer rejects service-account requests; see below | -| `aiInvestigation/run` via `alertTriggerActions` | **FAIL**: `SERVICE_ERROR` | Same LLM-layer dependency | -| `/sdl/v2/graphql` queries (`purpleConversations`, etc.) | **FAIL**: `unauthenticated` | SDL queries require browser session cookie | -| `/sdl/v2/graphql` mutations (`createNotebook`, etc.) | **PARTIAL**: schema validation reached | Auth passes for mutations; mutation names differ from browser UI (introspection blocked) | - -**Root cause of NATURAL_LANGUAGE failures:** `purpleLaunchQuery` internally routes to the same LLM workspace layer as the browser UI. That layer uses `teamToken` (obtained by browser users via `createNotebook` on `/sdl/v2/graphql`) to identify the user's Purple AI notebook. Service accounts (API tokens) never initialize a browser session, so `teamToken` is always empty and the `LaunchQueryManager` (`AsimovError`) rejects the request. This is not a schema issue, the request passes GraphQL validation (HTTP 200) and fails at the LLM dispatch layer. - -`purpleAlertSummary` bypasses this because it is a self-contained summarisation operation: the full alert context is supplied inline in `inputAlert`, no SDL session or teamToken lookup is needed. - -**SDL auth nuance (confirmed via live testing):** SDL query operations return `unauthenticated` for API tokens. SDL mutations reach schema validation (meaning auth succeeds), but the specific mutation names used by the browser UI (`createNotebook`, etc.) are not exposed under those names for API tokens. Introspection is blocked on the SDL endpoint. - -- The GraphQL endpoint is **not a committed public API**. Field names and schema can change between console releases. -- Notebooks have a TTL: old notebooks return "Notebook is expired" and queries return no results. -- Each GraphQL request should carry a unique `requestId` UUID as a query param for tracing (`?opname=&requestId=`). The server ignores it but it aids debugging. -- Permission failures for `purpleLaunchQuery` surface as `status.state = FAILED` with `error.origin = LaunchQueryManager` (HTTP 200, not 400 or 403): not the usual "invalid query" 400 you get for schema errors. -- Do **not** auto-execute generated PQ without showing it to the user first, Purple can hallucinate fields. -- `inputContent` must appear at **both** the top level of `request` AND inside `conversation.messages[0].inputMessage.inputContent`: the server requires both (confirmed via live validation). +Purple AI covers natural-language to PowerQuery, per-alert summaries, and auto-investigation across three GraphQL endpoints. For API-token (service-account) workflows, `purpleLaunchQuery NATURAL_LANGUAGE` and `aiInvestigation/run` are non-functional because they need a browser-session teamToken; use `mcp__purple-mcp__purple_ai` instead. `purpleAlertSummary` (via `purple_ai_alert_summary`) does work for API tokens. Full endpoint map, GraphQL request shapes, end-to-end operation flows, Python CLI notes, domain boundary, and the API-token limitations table: [references/purple-ai.md](references/purple-ai.md). ## Querying logs via the mgmt console API: the foolproof procedure -Every time somebody rolls their own `requests.post(...)` for a PowerQuery, one of the same six things goes wrong: wrong auth prefix, wrong endpoint path, missing `tenant: true`, missing `X-Dataset-Query-Forward-Tag`, no retry on transient 5xx, or 0 rows and the wrong debugging reflex. The fix is: do not hand-roll the call. Use `scripts/pq.py`. - -### Step 0: pick the right surface before you write a query - -| The user wants… | Use | Why | -|---|---|---| -| Raw event telemetry (EDR, third-party logs, SDL data) | **`scripts/pq.py`** (LRQ PowerQuery) | This is what SDL/PowerQuery is for. All `dataSource.*`, `event.*`, `src.process.*`, `tgt.file.*`, `i.scheme="edr"` filters. | -| Triage/filter/note/status on an existing alert | `scripts/unified_alerts.py` (UAM GraphQL) | Alerts are entities, not log events. UAM filter syntax is GraphQL `FilterInput`, NOT PowerQuery. Do not confuse the two. | -| Legacy STAR/cloud-detection alert REST shape | `/web/api/v2.1/cloud-detection/alerts` | Only when you need `agentDetectionInfo` / `sourceProcess` etc. Otherwise UAM. | -| A console entity: threat, agent, site, policy, IOC, group | REST via `s1_client.py` | Not a log query. `GET /web/api/v2.1/{threats,agents,sites,...}`. | -| Natural-language hunt that can be hand-reviewed | `purple_ai.purple_query(...)` then LRQ-execute the returned PQ | Purple generates PQ text; `pq.py` runs it. | - -If the user names a vendor ("Example Source", "Zscaler", "Okta", "FortiGate") and says "query" or "search logs", that is always the PQ path, never UAM filter syntax. - -### Step 1: use `scripts/pq.py`, not inline `requests` - -```python -import sys -sys.path.insert(0, "scripts") -from s1_client import S1Client -from pq import run_pq, list_data_sources, PQError - -c = S1Client() - -# One call. Handles launch, polling, forward-tag, cancel, retry, the lot. -res = run_pq( - c, - "dataSource.name = 'Example Source' " - "| group ct = count() by event.type " - "| sort -ct " - "| limit 50", - hours=24, -) -print(res["matchCount"], "events ->", res["row_count"], "rows") -for row in res["rows"]: - print(row) -``` - -The helper does ALL of this for you, so there is nothing to remember: - -- `Authorization: Bearer ` (flipped from the REST `ApiToken` prefix; same JWT, different scheme). -- `POST /sdl/v2/api/queries` on the tenant console host. **NOT** `/web/api/v2.1/sdl/v2/api/queries`, **NOT** `xdr.us1.sentinelone.net`. Do not "fix" a 404 by adding `/web/api/v2.1`; that path does not exist; the fix is the shorter path. -- Captures `X-Dataset-Query-Forward-Tag` from the POST response and echoes it on every GET / DELETE (mandatory for shard routing; without it you get rejections). -- Sets `queryType: "PQ"`, `tenant: true`, `pq: {query, resultType: "TABLE"}` (omit `tenant` and you silently get `matchCount=0`). -- Polls at 1s (query expires 30s after the last poll: slower polling means you lose the query). -- Retries 5xx / 429 / connection errors with exponential backoff. Honors `Retry-After`. The DNS-cache-overflow 503s behind some egress proxies are exactly what this is for. -- Cancels on every exit path (success, deadline, failure) to release the per-account concurrent-query budget. - -### Step 2: if you get 0 rows, follow the ladder, do NOT widen the window first - -`run_pq` returning `row_count=0` has an ordered diagnostic. Burning time by widening the window first is the most common failure mode; the window is almost never the cause. - -1. **Enumerate the data sources.** If your filter names a vendor / product, first confirm it exists on THIS tenant and you have the string right. Spelling, case, and punctuation matter, the filter is a literal string match. - - ```python - sources = list_data_sources(c, hours=24) - for s in sources[:30]: - print(s["dataSource.name"], s["dataSource.category"], s["ct"]) - ``` - - If "Example Source" isn't in the list, the tenant isn't ingesting it; no amount of widening the window will help. If it's there under a different spelling (`"PromptSecurity"`, `"Prompt Sec"`), use the exact string. -2. **Compare `matchCount` vs `row_count`.** `matchCount=0` means the initial filter discarded everything before any aggregation, the filter is too tight (or naming the wrong thing). `matchCount > 0` with `row_count = 0` means a post-pipe stage (`| group`, `| filter after group`) ate the rows, inspect the pipe. -3. **Only after the above come back clean**, widen the time window: in that order: 24h → 7d → 30d. - -### Step 3: for large windows / heavy aggregates, slice - -For ranges past 2-3 days with `event.type=*`-scale aggregates, slice the window and run slices in parallel. Full reference, measured perf (30d 574M-event aggregate lands in ~29s with two service-user JWTs), and the two-JWT runner recipe are in the `powerquery` skill at `references/lrq-api.md`. `run_pq` is the single-slice primitive underneath. - -### Step 3a (timeseries): prefer client-side day slicing over `timebucket(...)` - -`timebucket(...)` works in PQ when used as a NAMED grouping output inside `group ... by`: - -```text -| group n = count() by day = timebucket('1d'), action ← works -``` - -The bare positional form (no alias) and references inside `let` / `filter` are unreliable across tenant versions and have historically returned HTTP 500 `"undefined field 'timebucket'"`. Even when `timebucket` does work, a single 7d / 30d aggregate against a busy source frequently exceeds the LRQ per-call deadline (~38s observed), a 7d aggregate that finishes in 60s on the older `/api/powerQuery` endpoint will time out on LRQ. - -**Default to client-side day slicing for any window > 24h.** It's faster, avoids the deadline budget, respects the per-user 3 rps cap cleanly, and produces the same end result. The named-form `day = timebucket('1d')` is fine inside a single 24h-or-less slice when you really do need per-hour or per-15-min buckets: - -```python -from datetime import datetime, timedelta, timezone -import concurrent.futures as cf - -def slice_day(c, base, start, end): - iso = lambda t: t.strftime("%Y-%m-%dT%H:%M:%SZ") - return run_pq(c, base + " | group n=count() by action | sort -n", - start_time=iso(start), end_time=iso(end), - poll_deadline_s=90) - -end = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) -days = [(end - timedelta(days=i+1), end - timedelta(days=i)) for i in range(7)] -with cf.ThreadPoolExecutor(max_workers=3) as ex: # 3rps user cap - results = list(ex.map(lambda se: slice_day(c, base, *se), days)) -``` - -7 daily slices run in ~20s wall-clock (vs ~2 min for a 7d aggregate) and respect the per-user 3 rps cap. For hourly buckets over a 24h window use 24 slices at the same concurrency; for 30d use hourly slicing with 2 JWTs (see `powerquery` skill). - -### Step 3b: window-scaling playbook (performance by period) - -| Window | Recommended runner | Why | -|---|---|---| -| seconds to 1h | single `run_pq(hours=1)` | server returns in <5s | -| 1h to 24h | single `run_pq(hours=24)` | 5-30s depending on filter selectivity | -| 24h to 7d | single call OK for selective filters; for `event.type=*`-scale aggregates, 7 x 1d slices in parallel (max_workers=3) | single-call ~2 min; sliced ~20s | -| 7d to 30d | mandatory slicing (daily buckets) + 2 JWTs | two-JWT runner in `powerquery` | -| 30d+ | hourly slicing + 2-3 JWTs, cache results | 574M-event aggregate at 30d = ~29s with two JWTs | - -### Step 3c: LRQ response-shape gotchas (handled by `run_pq`) - -If you ever have to read a raw LRQ response (e.g. debugging), know: - -- `columns` is a list of dicts `{name, cellType, decimalPlaces}`, not a list of strings. Zipping values by `col["name"]` (not `str(col)`) is mandatory. -- `matchCount` lives inside the `data` block (`response["data"]["matchCount"]`), not at top level. Default to that path; fall back to top-level for older engines. -- `values` is an array of arrays (one per row); `run_pq` pairs it with column names for you. - -### Step 4: when NOT to use `pq.py` - -- If the user said "purple mcp" or "mcp", defer to `mcp__purple-mcp__powerquery` first; this is the backup path when the MCP times out or 5xxs. -- If the user is working with alerts as entities (listing, filtering, note, status), that's UAM GraphQL (`unified_alerts.py`), not PowerQuery. UAM filter syntax is `[{fieldId, stringEqual: {...}}]`; it is NOT PowerQuery `| filter` syntax. Mixing them is a common trap in screenshot-driven debugging. - -### Checklist before running a PQ programmatically - -- [ ] You called `run_pq` / `list_data_sources`, not inline `requests.post`. -- [ ] Base URL is the tenant console (e.g. `https://your-tenant.sentinelone.net`), not `xdr.us1.sentinelone.net`. -- [ ] Endpoint path is `/sdl/v2/api/queries` (short form). If you see 404s, do NOT add `/web/api/v2.1`; that's wrong. -- [ ] If you're filtering on EDR data (`src.process.*`, `event.type=*`, `tgt.file.*`), prepend `dataSource.name='SentinelOne' dataSource.category='security'`: on mixed tenants the default scope carries Scalyr/infra logs too and wide filters silently return `matchCount=0`. -- [ ] 0 rows → ran `list_data_sources` and checked `matchCount` vs `row_count` BEFORE widening the window. - -## Unified Alert Management (UAM): PRIMARY alert API - -> **Alert API precedence, important:** -> -> 1. **PRIMARY: GraphQL UAM** at `POST /web/api/v2.1/unifiedalerts/graphql`. This is the modern, multi-source alerts inbox (EDR, XDR, Identity, STAR, Cloud, NGFW, and ingested third-party telemetry). IDs are UUIDs (e.g. `019db24c-8b6d-7451-8697-b1b2e1a270f1`). Use this for any alert listing, filtering, triage, note, status, verdict, assignment, group-by, facet, or CSV-export task. -> 2. **SECONDARY: REST** at `GET /web/api/v2.1/cloud-detection/alerts`. Older surface, scoped to cloud-detection events (STAR rule hits, EDR overflow). IDs are int64 (e.g. `2055164731151448891`). Use only when you specifically need the denormalized REST payload (`agentDetectionInfo`, `sourceProcess`, `targetProcess`, `ruleInfo`) or when UAM is unavailable. These are **parallel surfaces, not redundant**, the same alert will have different IDs in each. -> 3. **No `createAlert`.** S1 does not expose a mutation for creating alerts directly. Alerts are server-side byproducts of detection engines, create a STAR/Custom Detection rule (`POST /web/api/v2.1/cloud-detection/rules`), upload an IOC that matches live telemetry (`POST /web/api/v2.1/threat-intelligence/iocs`), or generate synthetic endpoint activity. `addAlertNote` is the closest reversible content-creation path against an existing alert. - -Auth is the same `Authorization: ApiToken` header as REST; no extra credentials. Full reference in `references/UNIFIED_ALERTS.md`. The end-to-end dual-API round-trip test is `tests/test_alerts_dual_api.py`. - -Use UAM whenever the user is working with *alerts* as first-class entities, triaging, filtering, adding notes, resolving, bulk-assigning, rather than the older `GET /web/api/v2.1/threats` surface. - -```python -import sys -sys.path.insert(0, "scripts") -from s1_client import S1Client -import unified_alerts as uam - -c = S1Client() - -# discover: fieldIds, enum values, which views have data -cols = uam.column_metadata(c) -avail = uam.view_data_availability(c) - -# triage: top 20 NEW CRITICAL EDR alerts from the last day -page = uam.list_alerts(c, filters=[ - uam.build_filter(fieldId="detectionProduct", stringEqual={"value": "EDR"}), - uam.build_filter(fieldId="status", stringEqual={"value": "NEW"}), - uam.build_filter(fieldId="severity", stringEqual={"value": "CRITICAL"}), -], first=20) - -# act: bulk resolve a specific list of alerts, with a note -account = uam.scope([""]) -uam.set_alert_status( - c, scope_input=account, - alert_ids=["", ""], - status="RESOLVED", - note="Auto-closed: part of campaign tracked in JIRA-1234", -) -``` - -CLI equivalents: - -```text -python scripts/call_unified_alerts.py list --filter detectionProduct=EDR --first 20 -python scripts/call_unified_alerts.py facets status severity detectionProduct -python scripts/call_unified_alerts.py notes -python scripts/call_unified_alerts.py add-note "Investigating" -python scripts/call_unified_alerts.py set-status --scope --alert-id RESOLVED --note "..." -python scripts/call_unified_alerts.py csv-export --filter severity=CRITICAL -o crit.csv -``` - -### UAM domain: what belongs here vs REST - -UAM owns everything in the modern Alerts inbox, including alert notes, alert history, mitigation results, trigger-actions, and Cursor-paginated group-by / facet views. The older `/web/api/v2.1/threats` REST surface still exists and covers the classic endpoint-protection threat lifecycle, when the user says "alerts", "unified alerts", "alert notes", or mentions XDR / multi-source detections, route to UAM. When they say "threat", "threat group", "incident", or reference `/threats`, stay on REST. - -### Important quirks (hidden by the wrapper, but mind them if writing raw GraphQL) - -- The `alerts` query takes a flat `filters: [FilterInput!]` (AND-joined); mutations and `alertAvailableActions` take `filter: OrFilterSelectionInput` shaped as `{ or: [{ and: [FilterInput, ...] }, ...] }`. Mixing these up is a validation error. The wrapper exposes `build_filter(...)`, `or_filter(...)`, and `scope(...)` helpers so callers don't have to hand-assemble them. -- `updateAlertNote` and `deleteAlertNote` fail for ~30-90s after a note is freshly created (`"Alert Note with ID ... does not have mgmt_note_id set, unable to [edit|delete], try again later!"`) because the management-console backend is still propagating an internal id. The wrapper retries automatically with backoff, callers don't need to sleep. -- `aiInvestigations` has no `data` wrapper, but `alertNotes` / `alertFiltersCount` / `alertGroupByCount` / `alertAvailableActions` / `alertMitigationActionResults` / CSV exports all do. `alerts` / `alertHistory` / `alertTimeline` / `alertGroups` use connection shape (`edges`/`pageInfo`/`totalCount`). -- Full list of traps, including the `SortOrderType` enum name, `alertGroupByCount` using `limit` (not `first`), subselection requirements on `CsvResponse` / `ActionsError`, and the actual shape of `alertsViewDataAvailability`, is in `references/UNIFIED_ALERTS.md` under "Schema quirks". - -### Destructive actions: blast radius - -`alertTriggerActions` is the single mutation that can touch many alerts at once. Passing `filter: null` means *every alert in scope*, potentially hundreds of thousands. The safe pattern is the same as REST bulk actions: - -1. Use `list_alerts(..., first=1)` with the proposed filter and read `totalCount`. -2. Show the user the exact filter + action list + count. -3. Only after explicit confirmation, call `trigger_actions(...)` or one of the `set_alert_status` / `set_analyst_verdict` / `assign_alerts` convenience wrappers (all of which constrain the filter to an explicit alert-id list by default). - -## UAM Alert Interface (Unified Alert Management) -- pushing OCSF indicators + alerts INTO UAM - -Everything else in this skill talks to `.sentinelone.net/web/api/v2.1/...` (the Mgmt Console) and is read-or-mutate on pre-existing server state. The **UAM Alert Interface** (formerly "Ingestion Gateway") is a separate API family on a separate host for the write-side path: it lets you push OCSF-formatted indicators and alerts INTO UAM so they show up in the console as real alerts with attached indicators. Use it when a user asks to "create an alert", "ingest indicators", "send alerts from my pipeline", or "test alert ingestion". - -**Host and wire contract:** - -- Prod (US1): `https://ingest.us1.sentinelone.net`. This is the SentinelOne HEC (HTTP Event Collector) ingest host, shared between log ingest and OCSF alert/indicator ingest. Configure via the `S1_HEC_INGEST_URL` env var, the `--uam-url` flag, or the `S1_HEC_INGEST_URL` key in `credentials.json`. The former canonical `S1_UAM_ALERT_INTERFACE_URL` and legacy snake_case `uam_alert_interface_url` are still honored as fallbacks. -- Auth: `Authorization: Bearer `. NOT `ApiToken`. The mgmt-console JWT from `S1_CONSOLE_API_TOKEN` works; the endpoint rejects `ApiToken ...` with HTTP 401 `"Unsupported auth type"`. -- Body: concatenated JSON (one or more objects back-to-back, optionally newline-separated), gzip-compressed. `Content-Encoding: gzip` is mandatory. zstd also accepted. -- Scope: `S1-Scope: ` or `:[:]` is mandatory. -- Success shape: `202 Accepted` with `{"details":"Success","status":202}`. - -**Endpoints:** - -- `POST /v1/indicators` -- raw behavioural indicators. Each must carry `metadata.profiles = ["s1/security_indicator"]` and a unique `metadata.uid` (this is the join key). Batching: send many indicators in one call by passing a list; the client concatenates + gzips. -- `POST /v1/alerts` -- SecurityAlert wrappers. Each references its indicator(s) via `finding_info.related_events[].uid == indicator.metadata.uid`. A single alert can reference multiple indicators (one entry per indicator). The server stitches them into `alert.rawIndicators` / the UAM Indicators tab once both land. **Call with ONE alert per POST.** The wire format accepts multi-alert bodies and the gateway returns HTTP 202, but the stitcher silently drops all but one alert in a multi-alert batch (your-tenant 2026-04-22); loop one at a time, or use `post_alert_with_indicators` which enforces the safe pattern. - -**Supported indicator classes (via builders):** - -- `build_file_indicator(...)` -- OCSF class 1001 FileSystem Activity. Observables: Hostname, File Name, Hash (SHA-256/MD5), User Name, IP Address. -- `build_process_indicator(...)` -- OCSF class 1007 Process Activity. Observables: Hostname, Process Name, Resource UID (pid), User Name, IP Address, plus parent process. -- `build_network_indicator(...)` -- OCSF class 4001 Network Activity. Observables: Hostname, src/dst IP Address, URL, User Name. - -**Python usage:** - -```python -import sys, time, uuid -sys.path.insert(0, "scripts") -from s1_client import S1Client -from uam_alert_interface import ( - UAMAlertInterfaceClient, - build_file_indicator, build_process_indicator, build_network_indicator, - build_alert_referencing, -) - -mgmt = S1Client() -uam_iface = UAMAlertInterfaceClient(bearer_token=mgmt.api_token) - -now_ms = int(time.time() * 1000) -ind_uid_a, ind_uid_b, alert_uid = (str(uuid.uuid4()) for _ in range(3)) - -ind_a = build_file_indicator( - indicator_uid=ind_uid_a, file_name="payload.iso", - file_sha256="0"*64, device_uid=str(uuid.uuid4()), - device_hostname="host-1", device_ip="192.0.2.10", - user_uid=str(uuid.uuid4()), now_ms=now_ms, -) -ind_b = build_process_indicator( - indicator_uid=ind_uid_b, process_name="powershell.exe", - process_pid=4242, process_cmd_line="powershell -enc ...", - parent_process_name="explorer.exe", - device_uid=str(uuid.uuid4()), device_hostname="host-1", - user_uid=str(uuid.uuid4()), now_ms=now_ms, -) -alert = build_alert_referencing( - alert_uid=alert_uid, indicators=[ind_a, ind_b], now_ms=now_ms, - title="Ingested alert", description="...", -) - -# Preferred safe path. Posts the indicators, sleeps 3s (so each -# metadata.uid registers before the stitcher resolves related_events), -# then posts the single alert. For many alerts, LOOP this call -- do -# NOT pass multiple alerts to post_alerts() in one go (see constraints -# below). -uam_iface.post_alert_with_indicators( - alert, [ind_a, ind_b], scope=f"{account_id}:{site_id}") -# Then poll UAM GraphQL (unified_alerts.list_alerts) to see it surface. -``` - -**Validation:** after ingest, find the alert via UAM GraphQL (`unified_alerts.list_alerts` filtered by name, or `get_alert(alert_id)` once you know it). `get_alert_with_raw_indicators(c, alert_id)` returns the raw indicator dict(s) so you can confirm every `metadata.uid` and its observable names made it through. - -**Cleanup:** ingested alerts are not hard-deletable via public API. The standard reversibility pattern is to set `status=RESOLVED` and `analystVerdict=TRUE_POSITIVE_BENIGN` via the bulk-ops mutations in `unified_alerts` so the alert exits the active SOC queue and is tagged as synthetic. - -**Multi-indicator alert constraints** (empirically confirmed on -`your-tenant` 2026-04-22): - -- **One alert per `POST /v1/alerts` call.** The wire format accepts - concatenated JSON for N alerts in one body and the gateway returns - HTTP 202, but the stitcher silently drops all but one of the alerts. - Callers with many alerts MUST loop. `post_alerts` emits a - `RuntimeWarning` when `len(alerts) > 1` to flag the hazard. Use - `post_alert_with_indicators(alert, indicators, ...)` for the safe - one-at-a-time path. -- **Sleep between `POST /v1/indicators` and `POST /v1/alerts`.** If - the alert is posted immediately after its indicators, the stitcher - can resolve `finding_info.related_events[].uid` before the indicator's - `metadata.uid` is registered on the scope and silently drop the alert - (HTTP 202 still returned). A ~3s sleep between the two POSTs avoids - this; reducing below ~2s has been observed to regress on loaded - tenants. `post_alert_with_indicators` builds the sleep in; callers - using the low-level `post_indicators` + `post_alerts` path MUST add - it manually. `test_uam_alert_interface_batch.py` encodes this exact - sequence. -- Alerts with multiple `resources[]` entries (i.e. indicators spanning - different `device.uid` values) are silently dropped by the stitcher. - Return: HTTP 202 at the wire, NEVER surfaces in UAM. The builder - collapses to a single `resources[]` entry (first indicator's device) - to avoid this. If you truly need per-indicator assets, emit separate - alerts. -- Each `finding_info.related_events[]` entry MUST carry `class_uid`, - `type_uid`, `category_uid`, `activity_id`, `severity_id`, `time`, - `message`, and enriched `observables[]` (each with `type` + - `typeName` alongside `type_id`/`name`/`value`). `build_alert_referencing()` - populates all of these. Omitting any of them tends to cause the - stitcher to silently drop the alert. -- **`file.hashes` MUST be an OCSF Fingerprint array, not a dict.** - OCSF 1.6.0 defines `file.hashes` as `Array of Fingerprint objects`: - `[{"algorithm_id": 3, "algorithm": "SHA-256", "value": ""}, ...]`. - Posting `{"sha256": ""}` (dict form) causes the stitcher to - silently drop the file indicator even though POST returns 202. - `build_file_indicator()` emits the correct array shape; custom - payload builders must follow the same convention (algorithm_id 2=MD5, - 3=SHA-256, 4=SHA-1, 5=SHA-512). -- Multi-indicator stitching is asynchronous. Alerts surface within - ~30s; individual indicators appear in `alert.rawIndicators` over a - window of 2-120s. Tests must poll with a grace window, not assert - immediately. -- **Server-side rendering quirk in `alertWithRawIndicators` GraphQL:** - when an alert has multiple stitched rawIndicators, the flat-key - representation (`observables[N].name`/`.value`/`.type_id`) has - shuffled VALUES on all but the last entry in the array -- keys are - stable, values get mixed with other fields (e.g. `observables[2].name` - may return `"smoke-product"` because it was populated from - `metadata.product.name`). Does NOT affect stitching -- `metadata.uid` - is correct and the UI reads from a different code path. Programmatic - consumers should assert on `metadata.uid` presence, not on flattened - `observables[N].name` fields, in batch mode. - -**Tested on `your-tenant` 2026-04-22:** - -- `tests/test_uam_alert_interface_single.py` -- CONFIRMED WORKING end-to-end. 1 indicator + 1 alert, indicator stitches inside 30s, cleanup verified. -- `tests/test_uam_alert_interface_batch.py` -- CONFIRMED WORKING end-to-end. 3 indicators batched into one POST, alert with 3 related_events surfaces in UAM, all 3 indicators stitch into `alert.rawIndicators` within 2-5s, cleanup verified. Per-observable name assertion treated as informational due to GraphQL server-side rendering quirk noted above. - -See `tests/test_uam_alert_interface_single.py` for the minimum-viable worked example, and `tests/test_uam_alert_interface_batch.py` for a batched 3-indicator / multi-observable / multi-class round-trip. - -### Asset linkage on ingested alerts - -Ingested alerts always create a synthetic `assets[]` entry derived from `resources[]`; they **never** populate `assets[].agentUuid`. That linkage to real tenant inventory is only established when the alert originates from an installed S1 agent (real detection, STAR rule hit, or Hyperautomation `sendCustomEvent`). No OCSF field combination on the ingest path (tested: `resources[].agent_list`, `device.agent_uuid`, matching real agent UUIDs, `os.type_id` hints, etc.) reconciles against inventory. - -What IS controllable is the asset classification. `metadata.product.name` + `metadata.product.vendor_name` on the alert envelope drive `assets[].category` / `assets[].subcategory`: - -- Defaults (`smoke-product` / `smoke-vendor`) classify as "Device / Other Device" -- `SentinelOne` / `SentinelOne` classifies as "Server / Virtual Machine" - -Pass these via `build_alert_referencing(detection_product=..., detection_vendor=...)` when you want a demo alert to visually resemble an agent-generated alert. `get_alert` now defaults to `_ALERT_DETAIL_FIELDS` which includes the `assets { ... }` block; `list_alerts` / `paginate_alerts` still default to `_ALERT_CORE_FIELDS` (cheap) and accept an explicit `fields=_ALERT_DETAIL_FIELDS` override when callers want the asset join on every edge. - -Full empirical matrix including per-field behaviour and probing recipes: `references/ASSET_LINKAGE.md`. - -## Data source + schema discovery - -Before you write queries, dashboards, or detections against an SDL data source, discover two things: (1) what sources exist on this tenant and which are actively ingesting, and (2) for a given source, what attributes the parser actually emits. Hardcoded field lists are the number-one reason queries return 0 rows on a new tenant. This workflow replaces them. - -### Step 1: enumerate sources (`dataSource.name = *`) - -```python -from pq import list_data_sources -sources = list_data_sources(client, hours=24, limit=200) -# -> [{"dataSource.name": "SentinelOne", "dataSource.category": "security", "ct": 18304051}, ...] -``` - -CLI: `python scripts/inspect_source.py --list` prints a ranked table of every source that ingested in the last 24h. If a name the user asked for isn't in the list, fuzzy-match and surface candidates rather than running a query that will return 0. - -Rules of thumb: - -- There can be multiple rows with the same `dataSource.name` under different `dataSource.category` values (e.g. `SentinelOne / security`, `SentinelOne / None`, `SentinelOne / telemetry`). Treat category as metadata, not part of the name. -- A source with non-zero `ct` in 24h is live. Anything else is either decommissioned, in a different time window, or scoped out of the current token. - -### Step 2: discover the schema for one source (`discover_schema`) - -```python -from inspect_source import discover_schema, pick_keys - -schema = discover_schema( - client, "Example Source", - hours=24, sample=150, - extra_filter="(tag != 'logVolume' OR !(tag = *))", # ALWAYS exclude logVolume - backend="auto", # sync SDL first, LRQ LOG fallback - escalate=True, # 1h -> 4h -> 24h until min_events rung satisfied -) -prim_key, action_key = pick_keys(schema) -``` - -CLI: `python scripts/inspect_source.py --source "" --window 24h`. - -Key points: - -- Uses the LRQ `LOG` queryType (not PowerQuery). PQ has no wildcard column projection; `| columns *` errors and `| limit N` only returns `timestamp + message`. `LOG` returns every flat attribute the parser emits under `matches[].values`, which is how the Event Search UI populates its "Event properties" panel. -- Sync SDL `/sdl/api/query` is ~30% faster than async LRQ on your-tenant. The dispatcher prefers it and falls back to LRQ LOG on HTTP 404/401/403. Force a backend with `backend="sdl"` or `backend="lrq"` if benchmarking. -- Escalating window (1h -> 4h -> 24h -> requested) keeps busy sources ~3s. Only sparse sources (audit, low-volume demos) pay the full widening cost. Override with `escalate=False` for a single-rung run at `hours=`. -- Each field is classified: `principal_user` / `principal_host` / `principal_ip` / `action` / `temporal` / `network` / `file` / `process` / `grouping_candidate` / `other`. `pick_keys(schema)` returns `(prim_key, action_key)` picked from whatever is populated, preferring `user > hostname > IP`, then shortest name, then exact-name action hits (`action`, `event.type`, `outcome`, `result`, `severity`, ...) in that priority. -- `extra_filter` is passed through verbatim and appended to the base `dataSource.name='...'` filter. - -### ALWAYS exclude `tag='logVolume'` from discovery samples - -Many SentinelOne parsers emit metric events alongside real data, tagged `tag='logVolume'`. They have `metric`, `value`, `path1` fields and nothing else useful. If you don't exclude them, they crowd out real events in a sample window and the classifier picks `severity` as the action key because it's the only field at 100% populated. Pass: - -```python -extra_filter="(tag != 'logVolume' OR !(tag = *))" -``` - -The `OR !(tag = *)` half keeps sources that don't emit `tag` at all (rather than excluding them as null). `build_source_report.py` always passes this filter. Do the same in any new caller. - -### Benchmarked results (5 sources, your-tenant, 24h ceiling) - -| Source | Wall | Effective | n sampled | attrs | prim_key | action_key | -|---|---|---|---|---|---|---| -| SentinelOne | 2.9s | 1h | 150 | 333 | `src.process.eUserName` | `event.type` | -| Windows Event Logs | 3.1s | 1h | 150 | 148 | `winEventLog.data.event.eventData.subjectUserName` | `event.type` | -| FortiGate | 2.5s | 1h | 150 | 247 | `device.name` | `event.type` | -| Zscaler Internet Access | 2.5s | 1h | 150 | 47 | `None` | `action` | -| Example Source | 16.8s | 24h | 133 | 59 | `user` | `action` | - -Four of five land in ~3s because busy sources satisfy the `min_events=50` threshold on the 1h rung. Only low-volume sources (demo Example Source) pay the full escalation cost (1h -> 4h -> 24h = 3 rungs). Reproduce with `python scripts/bench_5_sources.py`. - -Zscaler returning `prim_key=None` is a real classifier gap: its user-ish fields are named `deviceowner` / `department` without a separator, so they don't match the `principal_user` regex. This is visible, not hidden. Operators can inspect the `other` class in the report and manually set the prim_key for downstream queries. - -### Using the discovered schema in code - -```python -base = f"dataSource.name = '{source}' (tag != 'logVolume' OR !(tag = *))" - -# volume-by-action breakdown (always safe; default to count() if no action key) -if action_key: - q = f"{base} | group n=count() by {action_key} | sort -n" -else: - q = f"{base} | group n=count()" - -# per-principal mix (skip if no principal) -if prim_key and action_key: - q = f"{base} | group n=count() by {prim_key}, {action_key} | sort -n | limit 60" -elif prim_key: - q = f"{base} | group n=count() by {prim_key} | sort -n | limit 25" -``` - -`build_source_report.py` is the reference consumer of this pattern. Read it before writing a new pipeline that needs the same keys. - -## Source-agnostic baseline + anomaly detection - -`scripts/baseline_anomaly.py` is the productionised end-to-end pipeline for behavioural baselining and z-score anomaly detection on ANY data source. It composes the schema-discovery + key-picker + LRQ runner already in this skill, so a caller never has to hand-pick principal/action fields per source. - -What it does: - -1. Calls `inspect_source.discover_schema()` for the named source and `pick_keys(schema)` to choose `prim_key` (principal: user / host / IP / role) and `action_key` (event.type / activity_name / action). Honors per-source overrides if the caller knows better. -2. Runs N daily count slices (default 30) via `pq.run_pq()` over the baseline window. Daily slicing avoids the LRQ per-call deadline; `max_workers=3` respects the per-user 3 rps cap. -3. Runs one 24h live slice. -4. Merges slices client-side. Supports two baseline strategies: pooled (all daily samples in one bucket) and DoW-stratified (one bucket per day-of-week, eliminates weekday/weekend false-positives). -5. Surfaces three anomaly classes on every run: matched-pair z-score deviations (SPIKE/DROP), silent pairs (baseline → live=0), and new-behaviour pairs (live with no baseline). - -Usage: - -```bash -python scripts/baseline_anomaly.py --source "" --days 30 --stratify dow -python scripts/baseline_anomaly.py --source "Okta" --days 7 -python scripts/baseline_anomaly.py --source "FortiGate" --days 30 --stratify dow --principal src.ip.address --action unmapped.action -``` - -State is checkpointed to disk per source (`baseline_anomaly__state.json`) so the script is resumable across runs; use this when working in environments with short shell budgets. - -PQ building blocks the script wraps live in the `powerquery` skill at `examples/behavioral-baselines.md`. Read that file when authoring the equivalent as a STAR / PowerQuery Alert detection rule body, the rule-body shape uses `lookup` against a pre-computed baseline table (from `savelookup`) instead of the script's two-window LRQ pattern. - -### When to re-run discovery - -- Before writing any query against a source you haven't touched on this tenant. -- When a previously-working query starts returning 0 rows (parser may have changed field names after a platform update). -- On tenant handover: different customers enable different parser versions, especially for XDR connectors. -- Before authoring a detection rule body (STAR / Custom Detection / PowerQuery Alert), to confirm the fields the rule references actually exist. Pass the discovered schema through to the rule author in the body of the request. - -## CTO report generation pipeline - -A source-agnostic pipeline for producing CTO-grade Word + PowerPoint reports on any SDL data source. Three scripts, one JSON artefact. - -1. `scripts/build_source_report.py --source "" --window <7d|24h|...>`. Runs dimension probes, a unified per-principal query, and a timeline aggregate against the tenant via `scripts/pq.py`, then writes `reports/_/data.json`. Probes which of `user`, `src.ip.address`, `src.hostname`, `action`, `event.type` actually carry values, so the renderer can skip sections that would otherwise be empty. -2. `scripts/render_charts.py `. Emits PNG charts into `reports/_/charts/`. Pure function of the JSON, no tenant calls. -3. `scripts/build_docx.py ` and `scripts/build_pptx.py `. Read the same JSON, emit `_CTO_Report_.docx` and `_CTO_Deck_.pptx` next to it. Every chart, section, stat card, and recommendation is gated on `data["dims"]` so a dimension-sparse source (e.g. Windows Event Logs has only `event.type`) produces a shorter but coherent report, not a broken one with empty tiles. - -### Data.json contract (renderers depend on this shape) - -- `source`, `slug`, `window_label`, `window_start`, `window_end`, `base_filter`. -- `dims`: boolean-per-dimension probe result. -- `summary`: derived metrics. Key fields are `total`, `intervention_rate` (only meaningful if `dims.action`), `prim_key` (name of the principal field actually used: `user`, `src.hostname`, `src.ip.address`, or null), `top_principal_key`, `top_user`, `by_action`, `rank_24h`, `n_slices`. -- `per_user_mix_top10`: the unified top-N-principals-by-action-mix result. The renderer slices this into `by_user`, `by_action_blocks`, `by_user_bypass` rather than running three separate queries. Collector does one PQ; renderer derives the rest. - -### Principal key fallback - -Order: `user`, then `src.hostname`, then `src.ip.address`, then none. The collector picks the first dim that returned non-null; the renderer reads `summary.prim_key` and labels stat cards and takeaways accordingly (e.g. "Dominant host" vs "Dominant user"). - -### Renderer gotchas (learned the hard way) - -- **Never use em-dashes or en-dashes in any commentary string.** They read as AI-generated. Use commas, colons, or parentheses. -- **Stat card overflow.** Long labels (e.g. "Windows Event Log Creation") wrap through the card edge at 40pt. Use length-based font sizing: len<=7 gets 40pt, <=12 gets 28pt, <=18 gets 20pt, else 16pt. -- **Chart title "dayly" is not a word.** `f"{kind}ly"` where kind="day" is wrong. Use a lookup: `{"day": "Daily", "hour": "Hourly", "week": "Weekly", "month": "Monthly"}`. -- **X-axis label crowding on hourly charts.** A 24-slice timeline rotates 24 timestamps into each other. Sparsify with `ax.set_xticks(ticks[::step])` where `step = max(1, int(len(dates) / 10))`, BEFORE `autofmt_xdate`. -- **Single-series legend clutter.** Gate `ax.legend(...)` on `n_series > 1`. A one-series chart needs no legend; the title carries the meaning. -- **Bar data-labels overlap on dense charts.** Skip them when `len(dates) > 12`. The Y-axis scale is enough for dense timelines. -- **Adaptive title on dimensionless sources.** `title_suffix = "volume by action" if has_action else "volume"`. Don't claim action breakdown when there is none. -- **Recommendations grid leaves empty bottom cell.** With 2 cards, use a single row (not 2x2). With 1 card, full width. -- **Fallback bullets when both `action` and `top_user` are missing.** Otherwise the "CTO takeaways" section renders empty. Fall back to dominant `prim_key`, tenant rank (24h), and the data-lake story. - -### Commentary generators - -`_intervention_note()`, `_concentration_note()`, `_bypass_note()` in `build_docx.py` and `build_pptx.py` take metric values and return commentary strings gated on thresholds (>=40 high, >=10 moderate, else low). The thresholds are tuned for LLM-app traffic; if they feel off for a new source, edit the thresholds rather than the template strings. - -### Running the whole thing - -```text -# From the skill root, with $CLAUDE_CONFIG_DIR/sentinelone/credentials.json configured. -python scripts/build_source_report.py --source "" --window <7d|24h|...> -python scripts/render_charts.py reports/_/data.json -python scripts/build_docx.py reports/_/data.json -python scripts/build_pptx.py reports/_/data.json -``` - -The collector creates `reports/_/` on first run. The `reports/` directory is `.gitignored`; this skill ships with the framework only, not sample outputs. - -## Common high-value workflows - -- **Unified alert triage**: `list_alerts(...)` from `unified_alerts` for the modern multi-source alerts inbox (EDR + XDR + Identity + cloud + third-party); use `facets`/`group-by` for volume rollups; `set_alert_status` / `set_analyst_verdict` / `assign_alerts` for triage decisions; `add_alert_note` for context. -- **Threat triage (legacy)**: `GET /threats` filtered by `createdAt__gte` + `resolved=false`; enrich with agent details from `/agents?ids=...`; output a table. -- **Endpoint isolation**: find agent IDs (`/agents` with name/IP filter), confirm count, `POST /agents/actions/disconnect` with filter. -- **Hunt across DV / PowerQuery** -- `POST /sdl/v2/api/queries` with `queryType="LOG"` (S1QL) or `queryType="PQ"` (PowerQuery), then poll `GET /sdl/v2/api/queries/{id}` echoing the `X-Dataset-Query-Forward-Tag` response header. Auth is Bearer, not ApiToken. Legacy `/dv/init-query` + `/dv/query-status` + `/dv/events` + `/dv/events/pq` flows are deprecated (sunset 2027-02-15). See `references/WORKFLOWS.md` Section 4 for the canonical runner. -- **Natural-language hunt via Purple AI** -- Use `mcp__purple-mcp__purple_ai` (Purple MCP). The `purple_query()` Python helper and `scripts/call_purple.py` are non-functional for API tokens (`purpleLaunchQuery NATURAL_LANGUAGE` requires a browser-session teamToken, confirmed 2026-05-03). Only for SDL-telemetry questions; route entity questions to REST. -- **Site/Group inventory**: `/sites`, `/groups`, `/accounts` are the tenant-structure endpoints; many resources require filtering by `siteIds` / `accountIds`. -- **Bulk action audit**: `/activities` is the system-wide audit log; filter by `activityTypes` and `createdAt__gte`. -- **Push alerts + indicators INTO UAM** -- build OCSF payloads, then call `UAMAlertInterfaceClient.post_alert_with_indicators(alert, [...])` once per alert (loop for many). The helper posts indicators, sleeps 3s, and posts the single alert in the one sequence proven to surface cleanly on US1 tenants. See "UAM Alert Interface" section above for the two silent-drop failure modes (multi-alert POST, no sleep) it prevents. Use for pipeline integrations, synthetic-alert generation, and detection testing. -- **CTO report for a data source** -- `python scripts/build_source_report.py --source "" --window <7d|24h>` then `scripts/render_charts.py`, `scripts/build_docx.py`, `scripts/build_pptx.py` on the resulting `reports/_/data.json`. Works for any SDL data source; the renderer gates every section on `dims` so dimension-sparse sources (e.g. Windows Event Logs with only `event.type`) still produce a coherent deck. See "CTO report generation pipeline" for the data contract and renderer gotchas. - -Consult the per-tag reference files for exact parameter names, the above are orientation, not copy-paste ready. - -## Hyperautomation (HA): workflow management - -API root (confirmed via live network capture 2026-05-03): - -```text -/web/api/v2.1/hyper-automate/api/v1 -``` - -Auth: same `Authorization: ApiToken ` header as all other S1 REST calls. - -### Hyperautomation Endpoints - -| Operation | Method | Path | -|---|---|---| -| List workflows | `GET` | `/workflows?limit=&skip=&siteIds=&sortBy=&sortOrder=` | -| Get single workflow | `GET` | `/workflows/single/{workflowId}/{revisionId}` | -| Workflow filter counts | `GET` | `/workflows/filters-count?siteIds=` | -| Delete workflow | `DELETE` | `/workflows/{id}?accountIds=` | -| Export all workflows (ZIP) | `GET` | `/workflow-import-export/export` *(confirmed on /public path)* | -| Import workflow | `POST` | `/workflow-import-export/import` *(confirmed on /public path)* | - -**Important:** the single-workflow fetch requires BOTH `workflowId` AND `revisionId`. The `revisionId` is the `workflow.version_id` field returned in the list response. `GET /workflows/single/{id}` without a revision returns 404. - -**Deletion is a REST `DELETE` (soft, recoverable).** `DELETE /web/api/v2.1/hyper-automate/api/v1/workflows/{id}?accountIds=` returns `204` (validated 2026-06-13: import then publish then delete then gone-from-list). Scope with `accountIds` or `siteIds` to match where the workflow lives. The older `POST /workflows/archive` and the legacy archive wrapper return HTTP 500 on this tenant; do not use them; the REST `DELETE` is the correct mechanism. - -Export/import were not captured in the v1 network trace. They are confirmed working at the `/public` base path; the `/v1` equivalents have not been verified. - -### List response shape (key fields) - -Each item in `data[]`: - -```jsonc -{ - "id": "", // top-level workflow ID - "workflow": { - "id": "", - "version_id": "", // pass as revisionId to single endpoint - "name": "...", - "state": "active|inactive|deactivated|draft", - "status": "idle|running|...", - "scope_level": "account|site", - "scope_id": "<19-digit>", - "site_name": "", - "created_at": "", - "updated_at": "", - "version_count": - }, - "actions": [ - { "id": "", "integration_id": "", "type": "" } - ] -} -``` - -Action types observed: `singularity_response_trigger`, `manual_trigger`, `http_trigger`, `scheduled_trigger`, `email_trigger`, `http_request`, `condition`, `loop`, `variable`, `delay`, `send_email`, `snippet`, `data_formation`, `wait_for_slack`, `break_loop`, `create_interaction`, `wait_for_interaction`, `llm`. - -### filter-count response structure - -`GET /workflows/filters-count` returns `data[]` with keys: `states`, `scope_ids`, `trigger_types`, `core_actions`, `tags`, `integrations`. Each entry has `{ count, value, title }`. Use this for summary dashboards (e.g. how many active workflows, which integrations are most used). - -### MCP tools - -`ha_list_workflows`, list with scope/sort/pagination. Returns `revisionId` alongside each workflow. -`ha_get_workflow`, fetch a single workflow by `workflowId` + optional `revisionId` (auto-resolves from list if omitted). -`ha_delete_workflow`, soft-delete one or more workflows via `DELETE /workflows/{id}` (scope with accountIds/siteIds). Confirm with user before calling. -`ha_import_workflow`, create workflow from JSON. Requires Hyper Automate.write permission. -`ha_export_workflow`, export all workflows as ZIP. - -### Permissions - -`Hyper Automate.view`, read operations (list, get, filter-count, export). -`Hyper Automate.write`, write operations (import, delete). Confirmed: without this permission, import returns 403. - -## Using s1-secops-mcp tools for direct console operations +Do not hand-roll a `requests.post` for a PowerQuery; use `scripts/pq.py` (`run_pq` / `list_data_sources`), which handles Bearer auth, the `/sdl/v2/api/queries` path, the mandatory `X-Dataset-Query-Forward-Tag`, `tenant: true`, transient-error retries, and cancel-on-exit. The full playbook (surface-selection table, the 0-rows diagnostic ladder, window-scaling and slicing, LRQ response-shape gotchas, and the pre-run checklist) is in [references/querying-logs.md](references/querying-logs.md). -Console operations use the `s1-secops-mcp` MCP tools, which bypass the Cowork sandbox proxy -entirely. Use `s1_api_get`, `s1_api_post`, `uam_list_alerts`, `uam_get_alert`, `uam_set_status`, -and other MCP tools directly instead of falling back to the `mgmt-console-api` -skill scripts. The MCP tools run locally on your machine and make direct HTTPS calls to -`*.sentinelone.net` without proxy interference. +## UAM, UAM Alert Interface, data-source discovery, reporting, and Hyperautomation -## STAR / Custom Detection rule lifecycle (learnings) +These operational playbooks moved into [references/querying-logs.md](references/querying-logs.md). Open it when the task is one of: -- **Update in place:** `PUT /web/api/v2.1/cloud-detection/rules/{id}` requires the FULL body `{data, filter}`; omitting `filter` returns HTTP 400 "filter: Missing data for required field". PUT resets the rule to the body's `status` (typically Disabled), so re-enable afterward. -- **Delete:** `DELETE /web/api/v2.1/cloud-detection/rules/{id}`, or bulk with `{"filter": {"ids": [...], "siteIds" | "accountIds": [...]}}`. -- **List:** always pass `isLegacy=false` or scheduled / PowerQuery rules are silently omitted. -- **Scheduled rules run on a pre-aggregated data layer**, so PowerQuery functions like `dataset`, `datasource`, `now`, `querystart`/`queryend`/`queryspan`, `topK`, `savelookup`, CIDR/wildcard `lookup`, `lookup` over a >10,000-row table, time-shifted `timebucket`, and `timebucket` < 30s are NOT available in a scheduled-rule body (full list in the `powerquery` skill). A detection needing any of them, e.g. an absent-pair anti-join (`left join` + `dataset`), runs as a Hyperautomation watchdog instead (see `hyperautomation`). -- **Lookup-reading rules are account-scope only.** Lookup tables / datatables are ACCOUNT-level objects, so any rule whose PQ body reads one (`| lookup ... from `) can only be created with `filter.accountIds`; site-scoped creation of lookup-reading rules is invalid. +- Unified Alert Management (UAM), the PRIMARY alert API: the GraphQL alerts inbox via `scripts/unified_alerts.py`, alert vs threat routing, filter shapes, and blast-radius safety. Deeper schema quirks stay in [references/UNIFIED_ALERTS.md](references/UNIFIED_ALERTS.md). +- UAM Alert Interface: pushing OCSF indicators and alerts INTO UAM via the HEC ingest host, with the multi-indicator stitching constraints and asset-linkage matrix ([references/ASSET_LINKAGE.md](references/ASSET_LINKAGE.md)). +- Data source and schema discovery: `list_data_sources` plus `inspect_source.discover_schema` / `pick_keys`, and the always-exclude-`logVolume` rule. +- Source-agnostic baseline and anomaly detection (`scripts/baseline_anomaly.py`). +- CTO report generation pipeline (`build_source_report.py`, `render_charts.py`, `build_docx.py`, `build_pptx.py`). +- Common high-value workflows, `s1-secops-mcp` direct-console usage, and the STAR / Custom Detection rule lifecycle learnings. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/CAPABILITY_MAP.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/CAPABILITY_MAP.md index 5ec6369..56e211e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/CAPABILITY_MAP.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/CAPABILITY_MAP.md @@ -176,4 +176,4 @@ These paths produce HTTP 404 and have never been in the v2.1 spec. They look pla | `POST /web/api/v2.1/threats/count` | `GET /web/api/v2.1/threats?countOnly=true` | | `POST /web/api/v2.1/agents/summary` | `GET /web/api/v2.1/agents/count` | -The S1 REST API does not use POST for read, count, or export operations. All data retrieval is GET. Before calling `s1_api_post`, confirm the path appears in `search_endpoints.py` output. \ No newline at end of file +The S1 REST API does not use POST for read, count, or export operations. All data retrieval is GET. Before calling `s1_api_post`, confirm the path appears in `search_endpoints.py` output. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/POWERQUERY_RECIPES.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/POWERQUERY_RECIPES.md index e7917ca..161e70e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/POWERQUERY_RECIPES.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/POWERQUERY_RECIPES.md @@ -20,7 +20,7 @@ the most-frequently-seen indicator name and where it was seen. Useful for "how noisy is our UAM feed" and for spotting synthetic indicator floods from pre-sales demos. -``` +```text event.type = 'IndicatorEvent' | group hits = count(), endpoints = count_distinct(endpoint.name), @@ -42,7 +42,7 @@ What it finds: PowerShell processes that opened a connection to a non-RFC1918 IP in the time range. Classic C2 / living-off-the-land beaconing pattern. -``` +```text src.process.name contains 'powershell' dst.ip.address = * | let is_private = net_rfc1918(dst.ip.address) | filter is_private = false @@ -67,7 +67,7 @@ and source address. Good for lateral-movement / password-spray triage; the `count_distinct(src.ip.address)` column lets you rank by "one attacker hitting many boxes" vs "noise". -``` +```text event.type = 'LoginEvent' event.login.loginIsSuccessful = false | group fails = count(), distinct_users = count_distinct(event.login.userName), @@ -91,7 +91,7 @@ launched, files touched, and destinations contacted. This is the "what did this attack actually do" one-liner that SOC analysts want when they open a UAM alert. -``` +```text src.process.storyline.id = * | group events = count(), procs = count_distinct(src.process.name), @@ -118,7 +118,7 @@ and want to confirm it landed in the data lake (not just in the UAM console), grep for it by name or by the `run_tag` substring your smoke test bakes in. -``` +```text indicator.name = * | filter indicator.name contains 'smoke-' OR message contains 'smoke-' | columns timestamp, indicator.name, indicator.category, @@ -137,7 +137,7 @@ What it finds: for each endpoint, the most recent event and the gap vs now. Useful when a pre-sales demo's test VM has gone offline and you need to prove it before blaming the product. -``` +```text endpoint.name = * | group last_seen = max(timestamp), events = count() by endpoint.name, endpoint.uuid @@ -198,7 +198,7 @@ and return a parse error ("Field must be enclosed in a grouping function"). ### Forms -``` +```text | group function(expression), function2(expression2) | group function(expression) by expression3, expression4, … | group name=function(expression) by name3=expression3, name4=expression4, … @@ -228,7 +228,7 @@ and return a parse error ("Field must be enclosed in a grouping function"). ### Examples -``` +```text | group count() by event.type | group hits=count() by event.type | group hits=count(), last=max(timestamp) by event.type @@ -239,14 +239,15 @@ and return a parse error ("Field must be enclosed in a grouping function"). ### What NOT to do -``` +```text # Wrong: bare field after by without aggregation wrapper: | group count = count() by event.type | sort count desc ← alias collision | group event.type by count() ← reversed ``` Sort references must use the output column name (the alias you gave it): -``` + +```javascript | group hits=count() by event.type | sort -hits ← correct | group count() by event.type | sort -count ← correct (implicit alias = function name) ``` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/UNIFIED_ALERTS.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/UNIFIED_ALERTS.md index 01971b6..8054757 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/UNIFIED_ALERTS.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/UNIFIED_ALERTS.md @@ -4,7 +4,7 @@ **Schema endpoint:** `POST /web/api/v2.1/unifiedalerts/graphql/schema` **Auth:** same `Authorization: ApiToken ` header as REST; no extra permission grant required beyond the RBAC entries under "Unified Alerts". **Skill entry points:** `scripts/unified_alerts.py` (module) and `scripts/call_unified_alerts.py` (CLI). -**Upstream docs:** https://community.sentinelone.com/s/article/000010170 +**Upstream docs:** --- @@ -183,11 +183,13 @@ From live `alertAvailableActions` on a Singularity Platform tenant with EDR, Ide ## Common recipes **Status roll-up for an account** + ```python facets = uam.filters_count(c, ["status", "severity", "detectionProduct"]) ``` **EDR alerts from the last hour** + ```python import time cutoff_ms = int((time.time() - 3600) * 1000) @@ -198,6 +200,7 @@ edr = uam.list_alerts(c, filters=[ ``` **Bulk resolve all `NEW` STAR alerts (mirrors the upstream docs example)** + ```python filt = uam.or_filter([ uam.build_filter(fieldId="detectionProduct", stringEqual={"value": "STAR"}), @@ -218,6 +221,7 @@ uam.trigger_actions( ``` **Add → update → delete note with eventual consistency handled** + ```python notes_before = {n["id"] for n in uam.alert_notes(c, alert_id)} uam.add_alert_note(c, alert_id, "Investigating") @@ -230,6 +234,7 @@ uam.delete_alert_note(c, new_id) ``` **CSV for an executive one-pager** + ```python csv = uam.export_alerts_csv(c, filters=[ uam.build_filter(fieldId="severity", stringIn={"values": ["CRITICAL", "HIGH"]}), @@ -282,5 +287,6 @@ Findings from a live events-vs-scheduled reproduction (2026-06). ## Ingestion paths (HEC vs UAM) Two distinct ingest APIs share the ingest host URL but are not connected: + - **HEC ingest** (HTTP Event Collector): raw logs/events + a named `parser`; feeds Event Search, PowerQuery, and detection rules. This is the log-ingestion path (replaces the removed SDL `uploadLogs`). For pre-structured / OCSF JSON ingested with `?isParsed=true` (no parser), each event MUST include `dataSource.name`, `dataSource.vendor`, `dataSource.category` (set to `security` for custom OCSF sources), `event.type` (as a FLAT dotted key, a nested `event:{...}` object is dropped since `event` is HEC-reserved), and `site_id`. OCSF omits these, and without them events land with a null source (no attribution; `dataSource.name`-based filters/detections miss). - **UAM ingest** (`uam_post_indicators` / `uam_ingest_alert`, `/v1/*`): creates UAM alerts/indicators directly and builds the alert asset from the event `device` object. Site routing via `scope = accountId:siteId`. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/WORKFLOWS.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/WORKFLOWS.md index 2576082..7c9d3da 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/WORKFLOWS.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/WORKFLOWS.md @@ -124,6 +124,7 @@ requests.delete(urljoin(base, f"/sdl/v2/api/queries/{qid}"), ``` Key points: + - `queryType: "PQ"` runs a PowerQuery; `queryType: "LOG"` runs S1QL log search. Both replace the old `/dv/*` endpoints. - The `X-Dataset-Query-Forward-Tag` response header from the launch must be echoed on every subsequent GET/DELETE. GET/DELETE without it is rejected. - Per-user rate cap is 3 rps. For multi-slice parallel runs over long windows (7d+), see the `powerquery` skill's `references/lrq-api.md` for slicing, two-JWT round-robin, and merge patterns. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/detection-rules.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/detection-rules.md new file mode 100644 index 0000000..ba1ce89 --- /dev/null +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/detection-rules.md @@ -0,0 +1,194 @@ +## Detection rules and saved filters: request-body schema gotchas + +### Custom Detection Rules and STAR Rules: POST /cloud-detection/rules + +**STAR rules** ("streaming threat assessment rules") are the product name for real-time +detection rules that evaluate every matching event as it arrives. There is no separate +`/star-rules` API path; that path returns 404. STAR rules are `cloud-detection/rules` +with `queryType=events`, the same endpoint as all other Custom Detection Rules. + +`queryType` enum (from swagger): `events`, `scheduled`, `correlation`, `uebafirstseen`. + +--- + +#### ⚠️ LISTING DETECTION RULES: read this BEFORE calling GET /cloud-detection/rules + +The list endpoint hides `queryType: "scheduled"` rules by default. **Without `isLegacy=false` in the query string, scheduled rules return 0 results even though they exist and are visible in the console UI.** This is the single most common drift between API output and console reality. There is no error, no warning, no hint, the response simply omits scheduled rules. Re-confirmed against the live API, 2026-05. + +**Always pass `isLegacy=false` when listing.** The only time you can omit it is if you are 100% sure you only care about events-type STAR rules and want to filter scheduled rules out. + +| Goal | Correct query | Anti-pattern (silently wrong) | +|---|---|---| +| List all detection rules | `GET /cloud-detection/rules?isLegacy=false&limit=200` | `GET /cloud-detection/rules?limit=200`: drops scheduled | +| List only scheduled detections | `GET /cloud-detection/rules?isLegacy=false&queryType=scheduled&limit=200` | `GET /cloud-detection/rules?queryType=scheduled`: returns 0 | +| List only events (STAR) rules | `GET /cloud-detection/rules?queryType=events&limit=200` | (this one is safe; events rules are not gated by `isLegacy`) | +| Search by name across all types | `GET /cloud-detection/rules?isLegacy=false&name__contains=Foo` | `GET /cloud-detection/rules?name__contains=Foo`: drops scheduled | + +**Verdict-language note for analysts:** if you query without `isLegacy=false` and the response is empty, the correct statement is "no events rules came back; I haven't yet checked scheduled rules", not "this tenant has zero scheduled detections". Promoting the absence of evidence to "confirmed zero" is the failure mode this gotcha exists to prevent. + +`queryType` accepts a single value or an array (comma-separated): `queryType=scheduled` or `queryType=events,scheduled` both work. The combination `queryType=` + `nameSubstring=` returns HTTP 500; use one filter at a time, or use `name__contains` instead of `nameSubstring` (which works with both). + +--- + +**⚠️ Pick the right `queryType` for your rule body BEFORE composing the POST. Mismatches return HTTP 400.** + +| Rule body language | Correct `queryType` | Correct `queryLang` | Field carrying the query | +|---|---|---|---| +| **PowerQuery (pipe syntax `\|`)** | **`scheduled`** | **`"2.0"`** | `data.scheduledParams.query` | +| S1QL log-search / event search | `events` | `"1.0"` (default, omit) | `data.s1ql` | +| EUEBA first-seen | `uebafirstseen` | n/a | per swagger schema | +| Correlation | `correlation` | **`"2.0"`** | `data.correlationParams` (`entity`, `matchInOrder`, `subQueries[]`) | + +**Any time the rule body has the pipe character `\|` (i.e. PowerQuery), the only working path is `queryType: "scheduled"` + `queryLang: "2.0"`.** `queryType: "events"` rejects pipe syntax (HTTP 400 `Don't understand [|]`), and `queryLang: "2.1"` is not in the enum (HTTP 400 `queryLang: "2.1" is not a valid choice`). Confirmed against the live API, 2026-05. + +**Correlation rules also require `queryLang: "2.0"`.** Confirmed against the live API, 2026-06: a `queryType: "correlation"` POST without `queryLang` (or with `"1.0"`) returns HTTP 400 `query lang must be 2.0`, even though the subquery bodies themselves can be boolean S1QL (e.g. `EventType = "Logon" AND LogonResult = "Fail"`). So only single-event `events` rules use 1.0; both `scheduled` and `correlation` require 2.0. `correlationParams` requires `entity` (`user`/`process`/`ip`/`endpoint`/`storyline`/`custom`/`none`) and `matchInOrder`, with 1 to 10 `subQueries[]` (each `{subQuery, matchesRequired}`); `timeWindow.windowMinutes` is one of {1,5,10,30,60,240,480,720}. + +**Operational learnings, validated live (2026-07, custom detection rules + alerts):** + +- **1.0 operators do not evaluate under `queryLang 2.0`.** An events rule whose body used the S1QL-1.0 operator `ContainsCIS` was accepted at create time but never fired; rewriting it to the 2.0 operator `contains:anycase` fired immediately. `scheduled` and `correlation` rules are always 2.0, so their bodies must use 2.0 operators (`contains:anycase`, `in:anycase`, etc.), not 1.0 forms (`ContainsCIS`, `In`). For `events` rules the story is more subtle: omitting `queryLang` stores `"1.0"` (live-verified 2026-07-29: a POST without `queryLang` came back `queryLang: "1.0"`), but a fleet audit found 99 of 100 live events rules carry `"2.0"` because creators set it explicitly to get the 2.0 operator set. Decide deliberately: leave it defaulted to 1.0 and write 1.0 operators, OR set `queryLang: "2.0"` explicitly and write 2.0 operators. Mixing (2.0 rule, 1.0 operator, or vice versa) stores fine and silently matches nothing. Lint rule bodies for operator/lang mismatch before deploy. +- **Alerts inherit the rule's `description`.** The generated alert carries the rule description verbatim (`ruleInfo.description` on `GET /cloud-detection/alerts`, and `description` on UAM alerts). Because the rule schema has no native MITRE/tag/custom-attribute field, embedding metadata in the description (e.g. a `[DaC] MITRE: ... | Tags: ... | Owner: ...` footer) is the way to surface MITRE/tags/owner on a custom-rule alert. +- **Alert-surface split by rule type.** `events` and `correlation` alerts appear in `GET /cloud-detection/alerts`; **scheduled** (PowerQuery) detection alerts do NOT surface there, they appear only in **UAM** (`unifiedalerts` / `uam_list_alerts`). Look for scheduled-rule alerts in UAM, not the REST alerts path. Also `sortBy=createdAt` on `/cloud-detection/alerts` returns `400 "not a valid choice"`, and a free-text `searchText` on `uam_list_alerts` can throw `Field * does not exist or not supported` (use structured filters / `viewType` instead). +- **Scheduled-rule activation latency, and PUT resets it.** After enable, a scheduled rule sits in `Activating` ("will become Active within an hour") before its first run, then runs on `runIntervalMinutes`. Every PUT/update re-enters `Activating`, so repeated re-syncs delay firing. Enable once and avoid churn when waiting for a scheduled rule to fire. + +See the **PowerQuery Scheduled Detections** subsection below for the full scheduled-rule body, gotchas, and the feature-flag fallback path if the tenant has not enabled Scheduled Detections. + +`expirationMode` valid values: `"Permanent"` or `"Temporary"`. `"Never"` is not valid and returns 400. + +**Events (STAR) rule, confirmed CREATE body (live API, 2026-05):** + +```json +{ + "data": { + "name": "my-star-rule", + "description": "optional", + "severity": "Low", + "expirationMode": "Permanent", + "queryType": "events", + "status": "Draft", + "s1ql": "EventType = \"Process Creation\" AND ProcessName = \"suspicious.exe\"", + "treatAsThreat": "UNDEFINED", + "networkQuarantine": false + }, + "filter": {"siteIds": [""]} +} +``` + +Events rule gotchas confirmed against live API: + +- `"activeResponse"` in the body returns HTTP 400 "Unknown field": omit it. +- `queryLang` defaults to `"1.0"` for events rules; do not set it explicitly (unlike scheduled rules which require `"2.0"`). +- `treatAsThreat="UNDEFINED"` is accepted on input but stored as `null` in the response. +- `status="Draft"` creates a rule that never fires even if live telemetry matches. +- GET `nameSubstring` + `queryType` combined returns HTTP 500: use one filter at a time. +- DELETE body is top-level `{filter: {ids: [...], siteIds: [...]}}` with no `"data"` wrapper. +- `isLegacy=false` is NOT needed for events rules (only required for scheduled rules). + +`status` valid values: `"Draft"`, `"Activating"`, `"Active"`, `"Disabling"`, `"Disabled"`. Use `"Disabled"` to create a non-firing rule; the API returns `"Draft"` in the response for a rule that has never been activated. + +**PUT /cloud-detection/rules/{id}**, `status` is required in the PUT body even though it is read-only in practice. Omitting it returns 400 "Missing data for required field." + +### Saved Filters: POST /filters + +The body field for filter criteria is `filterFields`, not `filters`: + +```json +{ + "data": { + "name": "my-filter", + "filterFields": {"infected": true, "networkStatuses": ["connected"]} + }, + "filter": {"accountIds": [""]} +} +``` + +**PUT /filters/{id}**, does NOT accept a top-level `filter` scope wrapper. Pass only `{"data": {...}}`: + +```json +{"data": {"name": "updated-name", "filterFields": {"infected": false}}} +``` + +### PowerQuery Scheduled Detections: POST/PUT/GET/DELETE /cloud-detection/rules + +`queryType: "scheduled"` rules are PowerQuery-based detections. **This is the only path that accepts pipe-syntax PowerQuery in a detection rule body.** Events rules reject pipe syntax even with `queryLang: "2.0"`. They use a different schema from `events` and `correlation` rules and have several non-obvious requirements confirmed against the live API (2026-05). + +**If the POST fails with a feature-not-enabled / not-licensed / unauthorized response on a tenant where the schema is otherwise correct, stop and tell the user to enable the Scheduled Detections feature on the tenant before retrying.** Do not silently downgrade the rule body to S1QL or re-attempt with `queryType: "events"`. The console path is typically *Settings → Account → Detection / SDL Add-Ons → Scheduled Detections* but varies by platform version, so phrase the ask in terms of capability ("please enable Scheduled Detections on this account") rather than the exact click path. + +#### CREATE: POST /web/api/v2.1/cloud-detection/rules + +All five `data` fields are required. `queryLang: "2.0"` is mandatory; omitting it returns HTTP 400 "query lang must be 2.0". `filter` accepts `accountIds` or `siteIds` (account-level rules cover all sites under that account): + +```json +{ + "data": { + "name": "My Scheduled Detection", + "queryType": "scheduled", + "queryLang": "2.0", + "severity": "Medium", + "expirationMode": "Permanent", + "status": "Disabled", + "scheduledParams": { + "query": "dataSource.name='Proofpoint' event.type='Click' unmapped.classification='malware' | group hits=count(), first_seen=oldest(timestamp), last_seen=newest(timestamp) by clickIP | filter hits >= 1 | sort -hits | limit 100", + "runIntervalMinutes": 60, + "lookbackWindowMinutes": 60, + "threshold": {"value": 0, "operator": "Greater"} + } + }, + "filter": {"accountIds": [""]} +} +``` + +**Scheduled-rule gotchas confirmed against live API (2026-05):** + +- `disableAgentMitigation` is **not** part of the scheduled schema. Including it returns HTTP 400 `Unknown field`. Cloud-source PQ rules do not need it; mitigation actions are not supported on scheduled rules anyway. +- `treatAsThreat: "Malicious"` is for events rules with EDR telemetry. Scheduled rules accept `treatAsThreat: "UNDEFINED"` (or omit) and `networkQuarantine: false`. The verdict surfaces via the rule's `severity`, not via mitigation. +- New rules are created in `Draft` status regardless of the requested `status` in the POST. To enable, call `PUT /web/api/v2.1/cloud-detection/rules/enable` with `{"filter": {"ids": [...], "accountIds": [...]}}` after creation. The response transitions to `Activating` and then `Active` within the hour. +- The PowerQuery in `scheduledParams.query` must NOT use `nolimit`, `compare`, or subqueries. The 1,000-row intermediate cap documented in the `powerquery` skill at `references/detection-rules.md` applies. + +**GET requires `isLegacy=false`**, without this query parameter, the list endpoint returns 0 results for scheduled rules even though they exist and are visible in the console UI. Always include it. **When listing all rules regardless of type (e.g. searching by name), always pass `isLegacy=false`, without it you will only see events-type rules and silently miss all scheduled rules:** + +```text +GET /web/api/v2.1/cloud-detection/rules?isLegacy=false&limit=100 +GET /web/api/v2.1/cloud-detection/rules?siteIds=&queryType=scheduled&isLegacy=false +GET /web/api/v2.1/cloud-detection/rules?ids=&siteIds=&isLegacy=false +``` + +Note: `name__contains` filter silently returns 0 for scheduled rules when `isLegacy=false` is omitted. Always pair name searches with `isLegacy=false`. + +**PUT requires `filter.siteIds`** in the body even though swagger marks `filter` as optional. Also requires all five data fields (name, queryType, severity, expirationMode, status). Additional PUT gotchas confirmed 2026-05-26: + +- `activeResponse` in the PUT body returns HTTP 400 "Unknown field": omit it (same as POST). +- `treatAsThreat: null` in the PUT body returns HTTP 400 "Field may not be null": use `"UNDEFINED"` even though the GET response shows `null`. The API stores it as `null` but rejects `null` as input. +- `status` must be included (e.g. `"Active"`) even though it is effectively read-only on PUT. + +```json +{ + "data": { + "name": "...", "queryType": "scheduled", "queryLang": "2.0", + "severity": "High", "expirationMode": "Permanent", "status": "Active", + "networkQuarantine": false, "treatAsThreat": "UNDEFINED", + "scheduledParams": {...} + }, + "filter": {"siteIds": [""]} +} +``` + +**ENABLE/DISABLE**, dedicated PUT endpoints. The filter takes `ids` plus an optional scope (`siteIds` or `accountIds`); `ids` alone works because rule IDs are globally unique. Do NOT include `isLegacy` in the body, it returns `400 filter: isLegacy: Unknown field` (`isLegacy` is a GET-listing param only). Tenant-validated 2026-06-16: `{"filter": {"ids": [...]}}` returned `{"affected": N}`. + +```text +PUT /web/api/v2.1/cloud-detection/rules/enable body: {"filter": {"ids": [""], "siteIds": [""]}} +PUT /web/api/v2.1/cloud-detection/rules/disable body: {"filter": {"ids": [""], "siteIds": [""]}} +``` + +**DELETE body; use `json_body=` keyword arg in s1_client.py.** The method signature is `delete(path, params=None, json_body=None)`. Passing the filter dict positionally sends it as query params and returns HTTP 400: + +```python +# Correct +client.delete("/web/api/v2.1/cloud-detection/rules", + json_body={"filter": {"ids": [rule_id], "accountIds": [acct_id]}}) + +# Wrong: sends filter as query string +client.delete("/web/api/v2.1/cloud-detection/rules", + {"filter": {"ids": [rule_id], "accountIds": [acct_id]}}) +``` + +**Verify deletion with GET (expect 0 hits), not a second DELETE.** A second DELETE returns HTTP 400 "Could not find rule with id: ..." rather than `affected: 0`. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/lrq-api.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/lrq-api.md index cd6de95..d20461b 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/lrq-api.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/lrq-api.md @@ -4,7 +4,7 @@ The LRQ API is the **default** programmatic path for every PowerQuery this skill ## Endpoints (all on the tenant's own console host) -``` +```text POST https://.sentinelone.net/sdl/v2/api/queries GET https://.sentinelone.net/sdl/v2/api/queries/{id}?lastStepSeen=N DELETE https://.sentinelone.net/sdl/v2/api/queries/{id} @@ -14,13 +14,13 @@ The console host is tenant-specific (for example `your-tenant.sentinelone.net`), ## Auth: Bearer, not ApiToken -``` +```text Authorization: Bearer ``` The JWT is the **same** console service-user token used by the Mgmt API; only the prefix changes. Calling `/sdl/v2/api/queries` with `Authorization: ApiToken ` returns HTTP 500: -``` +```text "Header must start with Bearer, but actually starts with \"ApiTok\"" ``` @@ -70,7 +70,7 @@ Service user tokens are preferred over personal user tokens because the per-user ## Polling -``` +```text GET /sdl/v2/api/queries/{id}?lastStepSeen= Headers: Authorization: Bearer @@ -83,7 +83,7 @@ Done when `stepsCompleted >= stepsTotal` and `stepsTotal > 0`. `data.values` is ## Cancel -``` +```text DELETE /sdl/v2/api/queries/{id} Headers: Authorization: Bearer @@ -105,13 +105,13 @@ Each API call (POST, GET, DELETE) counts as one request. A single slice with 5s On most SentinelOne tenants, the default scope carries a mix of SentinelOne EDR telemetry and Scalyr/infra logs. If you want EDR events (Process Creation, File Creation, Module Load, etc.), prepend this to the query: -``` +```text dataSource.name='SentinelOne' dataSource.category='security' ``` or equivalently: -``` +```text i.scheme="edr" ``` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/purple-ai.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/purple-ai.md new file mode 100644 index 0000000..ca93b86 --- /dev/null +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/purple-ai.md @@ -0,0 +1,230 @@ +## Purple AI: natural-language query, alert summary, and auto-investigation + +> **Precedence:** if the user explicitly mentions "purple mcp" or "mcp", prefer the Purple MCP tools (`mcp__purple-mcp__purple_ai`, `mcp__purple-mcp__powerquery`, etc.); this skill is the backup path in that case. Use the wrapper below when the user has asked for the S1 console/API directly, when Purple MCP is unavailable, or when you need to script a raw GraphQL call. + +### Hosts + +| Host | Purpose | +|---|---| +| `.sentinelone.net` | Management console: all REST and GraphQL calls use this host | +| `id.na1.sentinelone.net` | Browser session keep-alive (not relevant for API tokens) | +| `metrics-proxy-use1.na1.sentinelone.net` | Internal metrics (returned 503 in observed sessions, non-fatal) | + +### Endpoints + +Purple AI uses **three** GraphQL endpoints (all traffic is `POST` with JSON body). Each request carries `?opname=&requestId=` as query-string params for tracing; the server only inspects the JSON body. + +| Endpoint | Purpose | +|---|---| +| `POST /web/api/v2.1/graphql` | LLM dispatcher: `purpleLaunchQuery`, `purpleAlertSummary` | +| `POST /sdl/v2/graphql` | Notebook lifecycle + SDL query execution (see operation table below) | +| `POST /web/api/v2.1/unifiedalerts/graphql` | UAM: auto-investigation trigger and polling | + +Auth uses the same `Authorization: ApiToken ` header as REST. No extra credential setup. + +### SDL operation table (`POST /sdl/v2/graphql`) + +| opname | Triggered by | Purpose | +|---|---|---| +| `notebooks` | Sidebar load | List My Notebooks / Shared Notebooks | +| `createNotebook` | "+ New Notebook" or starter prompt | Creates a notebook; response includes `teamToken` | +| `purpleNotebook` | Selecting a notebook | Loads full Q&A history for the notebook | +| `addPurpleInputOutputMessage` | After each LLM/query step | Persists user prompt + AI output into the notebook | +| `launchQuery` | After LLM produces a PowerQuery | Executes the PQ against the data lake | +| `pingQuery` | Repeatedly while query runs | Short-poll for status/progress/results | +| `removeQuery` | On completion or cancel | Cleans up the query and releases resources | + +### Bootstrap REST calls (browser-only) + +These fire on page load and when entering a notebook. Not needed for API-token workflows, but useful for debugging feature availability: + +- `GET /web/api/v2.1/private/system/enabled-features?siteIds=`: feature flags (`purpleNative`, `purpleConversations`, `purpleAutoInvestigations`) +- `GET /web/api/v2.1/private/rbac/user-permissions?siteIds=`: current user's permission set +- `POST /web/api/v2.1/private/users/session-info`: session info + +### Operations + +#### purpleLaunchQuery: NL → PowerQuery (or summary/suggestions) + +**Critical: this is a GraphQL `query` (not `mutation`). Variable wrapper is `request` (type `PurpleLaunchQueryRequest`). Prior implementations using `mutation` + `PurpleLaunchQueryInput` fail with HTTP 400.** + +`contentType` controls what the LLM is asked to do: + +| contentType | Returns | +|---|---| +| `NATURAL_LANGUAGE` | `result.powerQuery.query`: the generated PQ string | +| `QUERY_RESULTS` | `result.summary`: English summary of a previous PQ result set | +| `SUGGEST_QUESTIONS` | `result.suggestedQuestions[]`: follow-up question chips | +| `STAR_RULE`, `DETECTION_RULE` | `result.detectionRule`: generated detection logic | + +```graphql +query purpleLaunchQuery($request: PurpleLaunchQueryRequest!) { + purpleLaunchQuery(request: $request) { + token + resultType + status { state error { errorType errorDetail origin } } + stepsCompleted + result { + message + summary + maskedMetadata + powerQuery { query viewSelector timeRange { start end } } + suggestedQuestions { question powerQuery viewSelector timeRange { start end } } + } + } +} +``` + +Variables (`$request`): + +```jsonc +{ + "request": { + "isAsync": false, + "contentType": "NATURAL_LANGUAGE", + "consoleDetails": { "baseUrl": "https://.sentinelone.net/", "version": "S-26.1.3#69" }, + // TOP-LEVEL inputContent required (confirmed: HTTP 400 "missing input value at $request.inputContent" without it) + "inputContent": { + "userInput": "", + "viewSelector": "EDR", + "displayedTimeRange": { "start": , "end": }, + "resultsPq": null, "powerQueryForResults": null, "contextId": null, "userDetails": null + }, + "conversation": { + "id": "", + "messages": [{ + "inputMessage": { + "id": "", "feedItemId": "<32hex>", "conversationId": "", + "createdAt": "", "messageType": "INPUT", "contentType": "NATURAL_LANGUAGE", + "inputContent": { + "userInput": "", + "viewSelector": "EDR", + "displayedTimeRange": { "start": , "end": }, + "resultsPq": null, "powerQueryForResults": null, "contextId": null, "userDetails": null + } + } + }] + } + } +} +``` + +`PurpleUserDetailsRequest` schema (confirmed via live validation error, does NOT have `siteIds` or `groupIds`): + +```jsonc +{ + "accountId": "<19-digit ID>", // ID! required + "teamToken": "<24-char>", // ID! required, session token from Purple AI workspace + "sessionId": "", + "emailAddress": "", + "userAgent": "", + "buildDate": "", + "buildHash": "" +} +``` + +Full end-to-end flow for one user question (observed in browser session): + +```text +1. purpleLaunchQuery (NATURAL_LANGUAGE, /web/api/v2.1/graphql) + → result.powerQuery.query (the generated PQ string) + +2. addPurpleInputOutputMessage (/sdl/v2/graphql) + → persists user prompt + generated PQ into the notebook + +3. launchQuery (/sdl/v2/graphql) + → submits PQ to SDL execution engine + → returns { token, ids, status: RUNNING } + +4. pingQuery × N (/sdl/v2/graphql, ~1 Hz) + → polls until status = COMPLETED + → returns columns + cells (table data) + +5. purpleLaunchQuery (QUERY_RESULTS, /web/api/v2.1/graphql) + → result.summary (English summary of the rows) + +6. purpleLaunchQuery (SUGGEST_QUESTIONS, /web/api/v2.1/graphql) + → result.suggestedQuestions[] (follow-up chips) + +7. addPurpleInputOutputMessage (/sdl/v2/graphql) + → persists the final answer into the notebook + +8. removeQuery (/sdl/v2/graphql) + → cleanup / releases query budget +``` + +Starter-prompt flow (e.g. "Find install logs" on the homepage): + +```text +createNotebook → purpleLaunchQuery (NATURAL_LANGUAGE) → addPurpleInputOutputMessage +``` + +Note: starter prompts that return a documentation-style answer skip the `launchQuery`/`pingQuery` stage entirely. + +**API-token users: use the Purple MCP.** The `purple_ai_query` MCP tool has been removed, `purpleLaunchQuery NATURAL_LANGUAGE` is confirmed non-functional for service-account API tokens (requires browser-session teamToken; returns AsimovError from LaunchQueryManager). Use `mcp__purple-mcp__purple_ai` for NL queries and `mcp__purple-mcp__powerquery` to execute the returned PQ. + +#### purpleAlertSummary: per-alert natural-language summary + +Separate operation (not purpleLaunchQuery). Synchronous (isAsync=false, no polling). + +```graphql +query AlertSummary($request: PurpleAlertSummaryRequest!) { + purpleAlertSummary(request: $request) { + token + result { summary } + } +} +``` + +Variables: `request.contentType = "ALERT_ENTRY"`, `request.inputAlert = ""`, `request.consoleDetails`, `request.userDetails`. + +**MCP tool:** `purple_ai_alert_summary`, call `uam_get_alert` first to get the OCSF JSON, then pass it here. + +#### aiInvestigations: auto-investigation + +Two-step flow, both via the UAM GraphQL endpoint (`/web/api/v2.1/unifiedalerts/graphql`): + +1. `alertTriggerActions` mutation with `id: "S1/aiInvestigation/run"`: fires investigation +2. `GetAlertAiInvestigations` query, polled every ~4s: streams `investigationStep` text, final `result` (markdown, ~16KB) + `verdict` enum + +Verdict enum: `UNKNOWN`, `TRUE_POSITIVE`, `FALSE_POSITIVE`. + +**API-token users: use the Purple MCP.** The `purple_ai_investigate` MCP tool has been removed, `aiInvestigation/run` via `alertTriggerActions` is confirmed non-functional for service-account API tokens (returns SERVICE_ERROR; same teamToken dependency as `purpleLaunchQuery`). Use `mcp__purple-mcp__purple_ai` for AI investigations. + +### Python CLI + +> **`purple_query()` and `scripts/call_purple.py` are non-functional for API tokens.** Both call `purpleLaunchQuery NATURAL_LANGUAGE`, which requires a browser-session teamToken that service accounts never have (confirmed SERVICE_ERROR 2026-05-03). Use the Purple MCP instead: +> +> ```text +> mcp__purple-mcp__purple_ai # natural-language query → PQ → result +> mcp__purple-mcp__powerquery # run a PQ string directly +> ``` +> +> `scripts/purple_ai.py` and `scripts/call_purple.py` are kept in the skill for reference but will fail with AsimovError on any service-account token. + +### Domain boundary + +Purple AI answers questions about **SDL telemetry** (process/network/file events, indicators, ingested logs). It does **not** answer questions about **console entities** (alerts, threats, agents, sites, policies). Those are REST resources. Out-of-domain questions return `resultType: "MESSAGE"` with a guardrail refusal, switch to the REST path. + +### Caveats and confirmed API-token limitations (live-tested 2026-05-03) + +| Operation | API-token result | Notes | +|---|---|---| +| `purpleAlertSummary` (ALERT_ENTRY) | **PASS**: returns real LLM summary | Works with `userDetails: null`; no SDL dependency | +| `purpleLaunchQuery` (NATURAL_LANGUAGE) | **FAIL**: `AsimovError` from `LaunchQueryManager` | LLM layer rejects service-account requests; see below | +| `aiInvestigation/run` via `alertTriggerActions` | **FAIL**: `SERVICE_ERROR` | Same LLM-layer dependency | +| `/sdl/v2/graphql` queries (`purpleConversations`, etc.) | **FAIL**: `unauthenticated` | SDL queries require browser session cookie | +| `/sdl/v2/graphql` mutations (`createNotebook`, etc.) | **PARTIAL**: schema validation reached | Auth passes for mutations; mutation names differ from browser UI (introspection blocked) | + +**Root cause of NATURAL_LANGUAGE failures:** `purpleLaunchQuery` internally routes to the same LLM workspace layer as the browser UI. That layer uses `teamToken` (obtained by browser users via `createNotebook` on `/sdl/v2/graphql`) to identify the user's Purple AI notebook. Service accounts (API tokens) never initialize a browser session, so `teamToken` is always empty and the `LaunchQueryManager` (`AsimovError`) rejects the request. This is not a schema issue, the request passes GraphQL validation (HTTP 200) and fails at the LLM dispatch layer. + +`purpleAlertSummary` bypasses this because it is a self-contained summarisation operation: the full alert context is supplied inline in `inputAlert`, no SDL session or teamToken lookup is needed. + +**SDL auth nuance (confirmed via live testing):** SDL query operations return `unauthenticated` for API tokens. SDL mutations reach schema validation (meaning auth succeeds), but the specific mutation names used by the browser UI (`createNotebook`, etc.) are not exposed under those names for API tokens. Introspection is blocked on the SDL endpoint. + +- The GraphQL endpoint is **not a committed public API**. Field names and schema can change between console releases. +- Notebooks have a TTL: old notebooks return "Notebook is expired" and queries return no results. +- Each GraphQL request should carry a unique `requestId` UUID as a query param for tracing (`?opname=&requestId=`). The server ignores it but it aids debugging. +- Permission failures for `purpleLaunchQuery` surface as `status.state = FAILED` with `error.origin = LaunchQueryManager` (HTTP 200, not 400 or 403): not the usual "invalid query" 400 you get for schema errors. +- Do **not** auto-execute generated PQ without showing it to the user first, Purple can hallucinate fields. +- `inputContent` must appear at **both** the top level of `request` AND inside `conversation.messages[0].inputMessage.inputContent`: the server requires both (confirmed via live validation). diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/python-client.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/python-client.md new file mode 100644 index 0000000..3a796a5 --- /dev/null +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/python-client.md @@ -0,0 +1,28 @@ +## Using the client in Python: full examples + +```python +import sys +sys.path.insert(0, "scripts") # or set PYTHONPATH +from s1_client import S1Client, S1APIError + +c = S1Client(cache_ttl=60) # optional 60s cache for accounts/sites/groups/system-info + +# single page +r = c.get("/web/api/v2.1/threats", params={"limit": 100, "resolved": False}) + +# full iteration +for threat in c.iter_items("/web/api/v2.1/threats", params={"limit": 200}): + ... + +# parallel fan-out: independent GETs over pooled connections (~3× faster) +results = c.get_many([ + ("/web/api/v2.1/accounts", {"limit": 1}), + ("/web/api/v2.1/sites", {"limit": 1}), + ("/web/api/v2.1/groups", {"limit": 1}), + ("/web/api/v2.1/system/info", None), +], max_workers=8) +# -> [{"path":..., "ok":True, "status":200, "data":..., "elapsed_ms":...}, ...] + +# action endpoint +c.post("/web/api/v2.1/agents/actions/disconnect", json_body={"filter": {"ids": ["AGENT_ID"]}}) +``` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/querying-logs.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/querying-logs.md new file mode 100644 index 0000000..ab46953 --- /dev/null +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/querying-logs.md @@ -0,0 +1,641 @@ +## Querying logs via the mgmt console API: the foolproof procedure + +Every time somebody rolls their own `requests.post(...)` for a PowerQuery, one of the same six things goes wrong: wrong auth prefix, wrong endpoint path, missing `tenant: true`, missing `X-Dataset-Query-Forward-Tag`, no retry on transient 5xx, or 0 rows and the wrong debugging reflex. The fix is: do not hand-roll the call. Use `scripts/pq.py`. + +### Step 0: pick the right surface before you write a query + +| The user wants… | Use | Why | +|---|---|---| +| Raw event telemetry (EDR, third-party logs, SDL data) | **`scripts/pq.py`** (LRQ PowerQuery) | This is what SDL/PowerQuery is for. All `dataSource.*`, `event.*`, `src.process.*`, `tgt.file.*`, `i.scheme="edr"` filters. | +| Triage/filter/note/status on an existing alert | `scripts/unified_alerts.py` (UAM GraphQL) | Alerts are entities, not log events. UAM filter syntax is GraphQL `FilterInput`, NOT PowerQuery. Do not confuse the two. | +| Legacy STAR/cloud-detection alert REST shape | `/web/api/v2.1/cloud-detection/alerts` | Only when you need `agentDetectionInfo` / `sourceProcess` etc. Otherwise UAM. | +| A console entity: threat, agent, site, policy, IOC, group | REST via `s1_client.py` | Not a log query. `GET /web/api/v2.1/{threats,agents,sites,...}`. | +| Natural-language hunt that can be hand-reviewed | `purple_ai.purple_query(...)` then LRQ-execute the returned PQ | Purple generates PQ text; `pq.py` runs it. | + +If the user names a vendor ("Example Source", "Zscaler", "Okta", "FortiGate") and says "query" or "search logs", that is always the PQ path, never UAM filter syntax. + +### Step 1: use `scripts/pq.py`, not inline `requests` + +```python +import sys +sys.path.insert(0, "scripts") +from s1_client import S1Client +from pq import run_pq, list_data_sources, PQError + +c = S1Client() + +# One call. Handles launch, polling, forward-tag, cancel, retry, the lot. +res = run_pq( + c, + "dataSource.name = 'Example Source' " + "| group ct = count() by event.type " + "| sort -ct " + "| limit 50", + hours=24, +) +print(res["matchCount"], "events ->", res["row_count"], "rows") +for row in res["rows"]: + print(row) +``` + +The helper does ALL of this for you, so there is nothing to remember: + +- `Authorization: Bearer ` (flipped from the REST `ApiToken` prefix; same JWT, different scheme). +- `POST /sdl/v2/api/queries` on the tenant console host. **NOT** `/web/api/v2.1/sdl/v2/api/queries`, **NOT** `xdr.us1.sentinelone.net`. Do not "fix" a 404 by adding `/web/api/v2.1`; that path does not exist; the fix is the shorter path. +- Captures `X-Dataset-Query-Forward-Tag` from the POST response and echoes it on every GET / DELETE (mandatory for shard routing; without it you get rejections). +- Sets `queryType: "PQ"`, `tenant: true`, `pq: {query, resultType: "TABLE"}` (omit `tenant` and you silently get `matchCount=0`). +- Polls at 1s (query expires 30s after the last poll: slower polling means you lose the query). +- Retries 5xx / 429 / connection errors with exponential backoff. Honors `Retry-After`. The DNS-cache-overflow 503s behind some egress proxies are exactly what this is for. +- Cancels on every exit path (success, deadline, failure) to release the per-account concurrent-query budget. + +### Step 2: if you get 0 rows, follow the ladder, do NOT widen the window first + +`run_pq` returning `row_count=0` has an ordered diagnostic. Burning time by widening the window first is the most common failure mode; the window is almost never the cause. + +1. **Enumerate the data sources.** If your filter names a vendor / product, first confirm it exists on THIS tenant and you have the string right. Spelling, case, and punctuation matter, the filter is a literal string match. + + ```python + sources = list_data_sources(c, hours=24) + for s in sources[:30]: + print(s["dataSource.name"], s["dataSource.category"], s["ct"]) + ``` + + If "Example Source" isn't in the list, the tenant isn't ingesting it; no amount of widening the window will help. If it's there under a different spelling (`"PromptSecurity"`, `"Prompt Sec"`), use the exact string. +2. **Compare `matchCount` vs `row_count`.** `matchCount=0` means the initial filter discarded everything before any aggregation, the filter is too tight (or naming the wrong thing). `matchCount > 0` with `row_count = 0` means a post-pipe stage (`| group`, `| filter after group`) ate the rows, inspect the pipe. +3. **Only after the above come back clean**, widen the time window: in that order: 24h → 7d → 30d. + +### Step 3: for large windows / heavy aggregates, slice + +For ranges past 2-3 days with `event.type=*`-scale aggregates, slice the window and run slices in parallel. Full reference, measured perf (30d 574M-event aggregate lands in ~29s with two service-user JWTs), and the two-JWT runner recipe are in the `powerquery` skill at `references/lrq-api.md`. `run_pq` is the single-slice primitive underneath. + +### Step 3a (timeseries): prefer client-side day slicing over `timebucket(...)` + +`timebucket(...)` works in PQ when used as a NAMED grouping output inside `group ... by`: + +```text +| group n = count() by day = timebucket('1d'), action ← works +``` + +The bare positional form (no alias) and references inside `let` / `filter` are unreliable across tenant versions and have historically returned HTTP 500 `"undefined field 'timebucket'"`. Even when `timebucket` does work, a single 7d / 30d aggregate against a busy source frequently exceeds the LRQ per-call deadline (~38s observed), a 7d aggregate that finishes in 60s on the older `/api/powerQuery` endpoint will time out on LRQ. + +**Default to client-side day slicing for any window > 24h.** It's faster, avoids the deadline budget, respects the per-user 3 rps cap cleanly, and produces the same end result. The named-form `day = timebucket('1d')` is fine inside a single 24h-or-less slice when you really do need per-hour or per-15-min buckets: + +```python +from datetime import datetime, timedelta, timezone +import concurrent.futures as cf + +def slice_day(c, base, start, end): + iso = lambda t: t.strftime("%Y-%m-%dT%H:%M:%SZ") + return run_pq(c, base + " | group n=count() by action | sort -n", + start_time=iso(start), end_time=iso(end), + poll_deadline_s=90) + +end = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) +days = [(end - timedelta(days=i+1), end - timedelta(days=i)) for i in range(7)] +with cf.ThreadPoolExecutor(max_workers=3) as ex: # 3rps user cap + results = list(ex.map(lambda se: slice_day(c, base, *se), days)) +``` + +7 daily slices run in ~20s wall-clock (vs ~2 min for a 7d aggregate) and respect the per-user 3 rps cap. For hourly buckets over a 24h window use 24 slices at the same concurrency; for 30d use hourly slicing with 2 JWTs (see `powerquery` skill). + +### Step 3b: window-scaling playbook (performance by period) + +| Window | Recommended runner | Why | +|---|---|---| +| seconds to 1h | single `run_pq(hours=1)` | server returns in <5s | +| 1h to 24h | single `run_pq(hours=24)` | 5-30s depending on filter selectivity | +| 24h to 7d | single call OK for selective filters; for `event.type=*`-scale aggregates, 7 x 1d slices in parallel (max_workers=3) | single-call ~2 min; sliced ~20s | +| 7d to 30d | mandatory slicing (daily buckets) + 2 JWTs | two-JWT runner in `powerquery` | +| 30d+ | hourly slicing + 2-3 JWTs, cache results | 574M-event aggregate at 30d = ~29s with two JWTs | + +### Step 3c: LRQ response-shape gotchas (handled by `run_pq`) + +If you ever have to read a raw LRQ response (e.g. debugging), know: + +- `columns` is a list of dicts `{name, cellType, decimalPlaces}`, not a list of strings. Zipping values by `col["name"]` (not `str(col)`) is mandatory. +- `matchCount` lives inside the `data` block (`response["data"]["matchCount"]`), not at top level. Default to that path; fall back to top-level for older engines. +- `values` is an array of arrays (one per row); `run_pq` pairs it with column names for you. + +### Step 4: when NOT to use `pq.py` + +- If the user said "purple mcp" or "mcp", defer to `mcp__purple-mcp__powerquery` first; this is the backup path when the MCP times out or 5xxs. +- If the user is working with alerts as entities (listing, filtering, note, status), that's UAM GraphQL (`unified_alerts.py`), not PowerQuery. UAM filter syntax is `[{fieldId, stringEqual: {...}}]`; it is NOT PowerQuery `| filter` syntax. Mixing them is a common trap in screenshot-driven debugging. + +### Checklist before running a PQ programmatically + +- [ ] You called `run_pq` / `list_data_sources`, not inline `requests.post`. +- [ ] Base URL is the tenant console (e.g. `https://your-tenant.sentinelone.net`), not `xdr.us1.sentinelone.net`. +- [ ] Endpoint path is `/sdl/v2/api/queries` (short form). If you see 404s, do NOT add `/web/api/v2.1`; that's wrong. +- [ ] If you're filtering on EDR data (`src.process.*`, `event.type=*`, `tgt.file.*`), prepend `dataSource.name='SentinelOne' dataSource.category='security'`: on mixed tenants the default scope carries Scalyr/infra logs too and wide filters silently return `matchCount=0`. +- [ ] 0 rows → ran `list_data_sources` and checked `matchCount` vs `row_count` BEFORE widening the window. + +## Unified Alert Management (UAM): PRIMARY alert API + +> **Alert API precedence, important:** +> +> 1. **PRIMARY: GraphQL UAM** at `POST /web/api/v2.1/unifiedalerts/graphql`. This is the modern, multi-source alerts inbox (EDR, XDR, Identity, STAR, Cloud, NGFW, and ingested third-party telemetry). IDs are UUIDs (e.g. `019db24c-8b6d-7451-8697-b1b2e1a270f1`). Use this for any alert listing, filtering, triage, note, status, verdict, assignment, group-by, facet, or CSV-export task. +> 2. **SECONDARY: REST** at `GET /web/api/v2.1/cloud-detection/alerts`. Older surface, scoped to cloud-detection events (STAR rule hits, EDR overflow). IDs are int64 (e.g. `2055164731151448891`). Use only when you specifically need the denormalized REST payload (`agentDetectionInfo`, `sourceProcess`, `targetProcess`, `ruleInfo`) or when UAM is unavailable. These are **parallel surfaces, not redundant**, the same alert will have different IDs in each. +> 3. **No `createAlert`.** S1 does not expose a mutation for creating alerts directly. Alerts are server-side byproducts of detection engines, create a STAR/Custom Detection rule (`POST /web/api/v2.1/cloud-detection/rules`), upload an IOC that matches live telemetry (`POST /web/api/v2.1/threat-intelligence/iocs`), or generate synthetic endpoint activity. `addAlertNote` is the closest reversible content-creation path against an existing alert. + +Auth is the same `Authorization: ApiToken` header as REST; no extra credentials. Full reference in `references/UNIFIED_ALERTS.md`. The end-to-end dual-API round-trip test is `tests/test_alerts_dual_api.py`. + +Use UAM whenever the user is working with *alerts* as first-class entities, triaging, filtering, adding notes, resolving, bulk-assigning, rather than the older `GET /web/api/v2.1/threats` surface. + +```python +import sys +sys.path.insert(0, "scripts") +from s1_client import S1Client +import unified_alerts as uam + +c = S1Client() + +# discover: fieldIds, enum values, which views have data +cols = uam.column_metadata(c) +avail = uam.view_data_availability(c) + +# triage: top 20 NEW CRITICAL EDR alerts from the last day +page = uam.list_alerts(c, filters=[ + uam.build_filter(fieldId="detectionProduct", stringEqual={"value": "EDR"}), + uam.build_filter(fieldId="status", stringEqual={"value": "NEW"}), + uam.build_filter(fieldId="severity", stringEqual={"value": "CRITICAL"}), +], first=20) + +# act: bulk resolve a specific list of alerts, with a note +account = uam.scope([""]) +uam.set_alert_status( + c, scope_input=account, + alert_ids=["", ""], + status="RESOLVED", + note="Auto-closed: part of campaign tracked in JIRA-1234", +) +``` + +CLI equivalents: + +```text +python scripts/call_unified_alerts.py list --filter detectionProduct=EDR --first 20 +python scripts/call_unified_alerts.py facets status severity detectionProduct +python scripts/call_unified_alerts.py notes +python scripts/call_unified_alerts.py add-note "Investigating" +python scripts/call_unified_alerts.py set-status --scope --alert-id RESOLVED --note "..." +python scripts/call_unified_alerts.py csv-export --filter severity=CRITICAL -o crit.csv +``` + +### UAM domain: what belongs here vs REST + +UAM owns everything in the modern Alerts inbox, including alert notes, alert history, mitigation results, trigger-actions, and Cursor-paginated group-by / facet views. The older `/web/api/v2.1/threats` REST surface still exists and covers the classic endpoint-protection threat lifecycle, when the user says "alerts", "unified alerts", "alert notes", or mentions XDR / multi-source detections, route to UAM. When they say "threat", "threat group", "incident", or reference `/threats`, stay on REST. + +### Important quirks (hidden by the wrapper, but mind them if writing raw GraphQL) + +- The `alerts` query takes a flat `filters: [FilterInput!]` (AND-joined); mutations and `alertAvailableActions` take `filter: OrFilterSelectionInput` shaped as `{ or: [{ and: [FilterInput, ...] }, ...] }`. Mixing these up is a validation error. The wrapper exposes `build_filter(...)`, `or_filter(...)`, and `scope(...)` helpers so callers don't have to hand-assemble them. +- `updateAlertNote` and `deleteAlertNote` fail for ~30-90s after a note is freshly created (`"Alert Note with ID ... does not have mgmt_note_id set, unable to [edit|delete], try again later!"`) because the management-console backend is still propagating an internal id. The wrapper retries automatically with backoff, callers don't need to sleep. +- `aiInvestigations` has no `data` wrapper, but `alertNotes` / `alertFiltersCount` / `alertGroupByCount` / `alertAvailableActions` / `alertMitigationActionResults` / CSV exports all do. `alerts` / `alertHistory` / `alertTimeline` / `alertGroups` use connection shape (`edges`/`pageInfo`/`totalCount`). +- Full list of traps, including the `SortOrderType` enum name, `alertGroupByCount` using `limit` (not `first`), subselection requirements on `CsvResponse` / `ActionsError`, and the actual shape of `alertsViewDataAvailability`, is in `references/UNIFIED_ALERTS.md` under "Schema quirks". + +### Destructive actions: blast radius + +`alertTriggerActions` is the single mutation that can touch many alerts at once. Passing `filter: null` means *every alert in scope*, potentially hundreds of thousands. The safe pattern is the same as REST bulk actions: + +1. Use `list_alerts(..., first=1)` with the proposed filter and read `totalCount`. +2. Show the user the exact filter + action list + count. +3. Only after explicit confirmation, call `trigger_actions(...)` or one of the `set_alert_status` / `set_analyst_verdict` / `assign_alerts` convenience wrappers (all of which constrain the filter to an explicit alert-id list by default). + +## UAM Alert Interface (Unified Alert Management) -- pushing OCSF indicators + alerts INTO UAM + +Everything else in this skill talks to `.sentinelone.net/web/api/v2.1/...` (the Mgmt Console) and is read-or-mutate on pre-existing server state. The **UAM Alert Interface** (formerly "Ingestion Gateway") is a separate API family on a separate host for the write-side path: it lets you push OCSF-formatted indicators and alerts INTO UAM so they show up in the console as real alerts with attached indicators. Use it when a user asks to "create an alert", "ingest indicators", "send alerts from my pipeline", or "test alert ingestion". + +**Host and wire contract:** + +- Prod (US1): `https://ingest.us1.sentinelone.net`. This is the SentinelOne HEC (HTTP Event Collector) ingest host, shared between log ingest and OCSF alert/indicator ingest. Configure via the `S1_HEC_INGEST_URL` env var, the `--uam-url` flag, or the `S1_HEC_INGEST_URL` key in `credentials.json`. The former canonical `S1_UAM_ALERT_INTERFACE_URL` and legacy snake_case `uam_alert_interface_url` are still honored as fallbacks. +- Auth: `Authorization: Bearer `. NOT `ApiToken`. The mgmt-console JWT from `S1_CONSOLE_API_TOKEN` works; the endpoint rejects `ApiToken ...` with HTTP 401 `"Unsupported auth type"`. +- Body: concatenated JSON (one or more objects back-to-back, optionally newline-separated), gzip-compressed. `Content-Encoding: gzip` is mandatory. zstd also accepted. +- Scope: `S1-Scope: ` or `:[:]` is mandatory. +- Success shape: `202 Accepted` with `{"details":"Success","status":202}`. + +**Endpoints:** + +- `POST /v1/indicators` -- raw behavioural indicators. Each must carry `metadata.profiles = ["s1/security_indicator"]` and a unique `metadata.uid` (this is the join key). Batching: send many indicators in one call by passing a list; the client concatenates + gzips. +- `POST /v1/alerts` -- SecurityAlert wrappers. Each references its indicator(s) via `finding_info.related_events[].uid == indicator.metadata.uid`. A single alert can reference multiple indicators (one entry per indicator). The server stitches them into `alert.rawIndicators` / the UAM Indicators tab once both land. **Call with ONE alert per POST.** The wire format accepts multi-alert bodies and the gateway returns HTTP 202, but the stitcher silently drops all but one alert in a multi-alert batch (your-tenant 2026-04-22); loop one at a time, or use `post_alert_with_indicators` which enforces the safe pattern. + +**Supported indicator classes (via builders):** + +- `build_file_indicator(...)` -- OCSF class 1001 FileSystem Activity. Observables: Hostname, File Name, Hash (SHA-256/MD5), User Name, IP Address. +- `build_process_indicator(...)` -- OCSF class 1007 Process Activity. Observables: Hostname, Process Name, Resource UID (pid), User Name, IP Address, plus parent process. +- `build_network_indicator(...)` -- OCSF class 4001 Network Activity. Observables: Hostname, src/dst IP Address, URL, User Name. + +**Python usage:** + +```python +import sys, time, uuid +sys.path.insert(0, "scripts") +from s1_client import S1Client +from uam_alert_interface import ( + UAMAlertInterfaceClient, + build_file_indicator, build_process_indicator, build_network_indicator, + build_alert_referencing, +) + +mgmt = S1Client() +uam_iface = UAMAlertInterfaceClient(bearer_token=mgmt.api_token) + +now_ms = int(time.time() * 1000) +ind_uid_a, ind_uid_b, alert_uid = (str(uuid.uuid4()) for _ in range(3)) + +ind_a = build_file_indicator( + indicator_uid=ind_uid_a, file_name="payload.iso", + file_sha256="0"*64, device_uid=str(uuid.uuid4()), + device_hostname="host-1", device_ip="192.0.2.10", + user_uid=str(uuid.uuid4()), now_ms=now_ms, +) +ind_b = build_process_indicator( + indicator_uid=ind_uid_b, process_name="powershell.exe", + process_pid=4242, process_cmd_line="powershell -enc ...", + parent_process_name="explorer.exe", + device_uid=str(uuid.uuid4()), device_hostname="host-1", + user_uid=str(uuid.uuid4()), now_ms=now_ms, +) +alert = build_alert_referencing( + alert_uid=alert_uid, indicators=[ind_a, ind_b], now_ms=now_ms, + title="Ingested alert", description="...", +) + +# Preferred safe path. Posts the indicators, sleeps 3s (so each +# metadata.uid registers before the stitcher resolves related_events), +# then posts the single alert. For many alerts, LOOP this call -- do +# NOT pass multiple alerts to post_alerts() in one go (see constraints +# below). +uam_iface.post_alert_with_indicators( + alert, [ind_a, ind_b], scope=f"{account_id}:{site_id}") +# Then poll UAM GraphQL (unified_alerts.list_alerts) to see it surface. +``` + +**Validation:** after ingest, find the alert via UAM GraphQL (`unified_alerts.list_alerts` filtered by name, or `get_alert(alert_id)` once you know it). `get_alert_with_raw_indicators(c, alert_id)` returns the raw indicator dict(s) so you can confirm every `metadata.uid` and its observable names made it through. + +**Cleanup:** ingested alerts are not hard-deletable via public API. The standard reversibility pattern is to set `status=RESOLVED` and `analystVerdict=TRUE_POSITIVE_BENIGN` via the bulk-ops mutations in `unified_alerts` so the alert exits the active SOC queue and is tagged as synthetic. + +**Multi-indicator alert constraints** (empirically confirmed on +`your-tenant` 2026-04-22): + +- **One alert per `POST /v1/alerts` call.** The wire format accepts + concatenated JSON for N alerts in one body and the gateway returns + HTTP 202, but the stitcher silently drops all but one of the alerts. + Callers with many alerts MUST loop. `post_alerts` emits a + `RuntimeWarning` when `len(alerts) > 1` to flag the hazard. Use + `post_alert_with_indicators(alert, indicators, ...)` for the safe + one-at-a-time path. +- **Sleep between `POST /v1/indicators` and `POST /v1/alerts`.** If + the alert is posted immediately after its indicators, the stitcher + can resolve `finding_info.related_events[].uid` before the indicator's + `metadata.uid` is registered on the scope and silently drop the alert + (HTTP 202 still returned). A ~3s sleep between the two POSTs avoids + this; reducing below ~2s has been observed to regress on loaded + tenants. `post_alert_with_indicators` builds the sleep in; callers + using the low-level `post_indicators` + `post_alerts` path MUST add + it manually. `test_uam_alert_interface_batch.py` encodes this exact + sequence. +- Alerts with multiple `resources[]` entries (i.e. indicators spanning + different `device.uid` values) are silently dropped by the stitcher. + Return: HTTP 202 at the wire, NEVER surfaces in UAM. The builder + collapses to a single `resources[]` entry (first indicator's device) + to avoid this. If you truly need per-indicator assets, emit separate + alerts. +- Each `finding_info.related_events[]` entry MUST carry `class_uid`, + `type_uid`, `category_uid`, `activity_id`, `severity_id`, `time`, + `message`, and enriched `observables[]` (each with `type` + + `typeName` alongside `type_id`/`name`/`value`). `build_alert_referencing()` + populates all of these. Omitting any of them tends to cause the + stitcher to silently drop the alert. +- **`file.hashes` MUST be an OCSF Fingerprint array, not a dict.** + OCSF 1.6.0 defines `file.hashes` as `Array of Fingerprint objects`: + `[{"algorithm_id": 3, "algorithm": "SHA-256", "value": ""}, ...]`. + Posting `{"sha256": ""}` (dict form) causes the stitcher to + silently drop the file indicator even though POST returns 202. + `build_file_indicator()` emits the correct array shape; custom + payload builders must follow the same convention (algorithm_id 2=MD5, + 3=SHA-256, 4=SHA-1, 5=SHA-512). +- Multi-indicator stitching is asynchronous. Alerts surface within + ~30s; individual indicators appear in `alert.rawIndicators` over a + window of 2-120s. Tests must poll with a grace window, not assert + immediately. +- **Server-side rendering quirk in `alertWithRawIndicators` GraphQL:** + when an alert has multiple stitched rawIndicators, the flat-key + representation (`observables[N].name`/`.value`/`.type_id`) has + shuffled VALUES on all but the last entry in the array -- keys are + stable, values get mixed with other fields (e.g. `observables[2].name` + may return `"smoke-product"` because it was populated from + `metadata.product.name`). Does NOT affect stitching -- `metadata.uid` + is correct and the UI reads from a different code path. Programmatic + consumers should assert on `metadata.uid` presence, not on flattened + `observables[N].name` fields, in batch mode. + +**Tested on `your-tenant` 2026-04-22:** + +- `tests/test_uam_alert_interface_single.py` -- CONFIRMED WORKING end-to-end. 1 indicator + 1 alert, indicator stitches inside 30s, cleanup verified. +- `tests/test_uam_alert_interface_batch.py` -- CONFIRMED WORKING end-to-end. 3 indicators batched into one POST, alert with 3 related_events surfaces in UAM, all 3 indicators stitch into `alert.rawIndicators` within 2-5s, cleanup verified. Per-observable name assertion treated as informational due to GraphQL server-side rendering quirk noted above. + +See `tests/test_uam_alert_interface_single.py` for the minimum-viable worked example, and `tests/test_uam_alert_interface_batch.py` for a batched 3-indicator / multi-observable / multi-class round-trip. + +### Asset linkage on ingested alerts + +Ingested alerts always create a synthetic `assets[]` entry derived from `resources[]`; they **never** populate `assets[].agentUuid`. That linkage to real tenant inventory is only established when the alert originates from an installed S1 agent (real detection, STAR rule hit, or Hyperautomation `sendCustomEvent`). No OCSF field combination on the ingest path (tested: `resources[].agent_list`, `device.agent_uuid`, matching real agent UUIDs, `os.type_id` hints, etc.) reconciles against inventory. + +What IS controllable is the asset classification. `metadata.product.name` + `metadata.product.vendor_name` on the alert envelope drive `assets[].category` / `assets[].subcategory`: + +- Defaults (`smoke-product` / `smoke-vendor`) classify as "Device / Other Device" +- `SentinelOne` / `SentinelOne` classifies as "Server / Virtual Machine" + +Pass these via `build_alert_referencing(detection_product=..., detection_vendor=...)` when you want a demo alert to visually resemble an agent-generated alert. `get_alert` now defaults to `_ALERT_DETAIL_FIELDS` which includes the `assets { ... }` block; `list_alerts` / `paginate_alerts` still default to `_ALERT_CORE_FIELDS` (cheap) and accept an explicit `fields=_ALERT_DETAIL_FIELDS` override when callers want the asset join on every edge. + +Full empirical matrix including per-field behaviour and probing recipes: `references/ASSET_LINKAGE.md`. + +## Data source + schema discovery + +Before you write queries, dashboards, or detections against an SDL data source, discover two things: (1) what sources exist on this tenant and which are actively ingesting, and (2) for a given source, what attributes the parser actually emits. Hardcoded field lists are the number-one reason queries return 0 rows on a new tenant. This workflow replaces them. + +### Step 1: enumerate sources (`dataSource.name = *`) + +```python +from pq import list_data_sources +sources = list_data_sources(client, hours=24, limit=200) +# -> [{"dataSource.name": "SentinelOne", "dataSource.category": "security", "ct": 18304051}, ...] +``` + +CLI: `python scripts/inspect_source.py --list` prints a ranked table of every source that ingested in the last 24h. If a name the user asked for isn't in the list, fuzzy-match and surface candidates rather than running a query that will return 0. + +Rules of thumb: + +- There can be multiple rows with the same `dataSource.name` under different `dataSource.category` values (e.g. `SentinelOne / security`, `SentinelOne / None`, `SentinelOne / telemetry`). Treat category as metadata, not part of the name. +- A source with non-zero `ct` in 24h is live. Anything else is either decommissioned, in a different time window, or scoped out of the current token. + +### Step 2: discover the schema for one source (`discover_schema`) + +```python +from inspect_source import discover_schema, pick_keys + +schema = discover_schema( + client, "Example Source", + hours=24, sample=150, + extra_filter="(tag != 'logVolume' OR !(tag = *))", # ALWAYS exclude logVolume + backend="auto", # sync SDL first, LRQ LOG fallback + escalate=True, # 1h -> 4h -> 24h until min_events rung satisfied +) +prim_key, action_key = pick_keys(schema) +``` + +CLI: `python scripts/inspect_source.py --source "" --window 24h`. + +Key points: + +- Uses the LRQ `LOG` queryType (not PowerQuery). PQ has no wildcard column projection; `| columns *` errors and `| limit N` only returns `timestamp + message`. `LOG` returns every flat attribute the parser emits under `matches[].values`, which is how the Event Search UI populates its "Event properties" panel. +- Sync SDL `/sdl/api/query` is ~30% faster than async LRQ on your-tenant. The dispatcher prefers it and falls back to LRQ LOG on HTTP 404/401/403. Force a backend with `backend="sdl"` or `backend="lrq"` if benchmarking. +- Escalating window (1h -> 4h -> 24h -> requested) keeps busy sources ~3s. Only sparse sources (audit, low-volume demos) pay the full widening cost. Override with `escalate=False` for a single-rung run at `hours=`. +- Each field is classified: `principal_user` / `principal_host` / `principal_ip` / `action` / `temporal` / `network` / `file` / `process` / `grouping_candidate` / `other`. `pick_keys(schema)` returns `(prim_key, action_key)` picked from whatever is populated, preferring `user > hostname > IP`, then shortest name, then exact-name action hits (`action`, `event.type`, `outcome`, `result`, `severity`, ...) in that priority. +- `extra_filter` is passed through verbatim and appended to the base `dataSource.name='...'` filter. + +### ALWAYS exclude `tag='logVolume'` from discovery samples + +Many SentinelOne parsers emit metric events alongside real data, tagged `tag='logVolume'`. They have `metric`, `value`, `path1` fields and nothing else useful. If you don't exclude them, they crowd out real events in a sample window and the classifier picks `severity` as the action key because it's the only field at 100% populated. Pass: + +```python +extra_filter="(tag != 'logVolume' OR !(tag = *))" +``` + +The `OR !(tag = *)` half keeps sources that don't emit `tag` at all (rather than excluding them as null). `build_source_report.py` always passes this filter. Do the same in any new caller. + +### Benchmarked results (5 sources, your-tenant, 24h ceiling) + +| Source | Wall | Effective | n sampled | attrs | prim_key | action_key | +|---|---|---|---|---|---|---| +| SentinelOne | 2.9s | 1h | 150 | 333 | `src.process.eUserName` | `event.type` | +| Windows Event Logs | 3.1s | 1h | 150 | 148 | `winEventLog.data.event.eventData.subjectUserName` | `event.type` | +| FortiGate | 2.5s | 1h | 150 | 247 | `device.name` | `event.type` | +| Zscaler Internet Access | 2.5s | 1h | 150 | 47 | `None` | `action` | +| Example Source | 16.8s | 24h | 133 | 59 | `user` | `action` | + +Four of five land in ~3s because busy sources satisfy the `min_events=50` threshold on the 1h rung. Only low-volume sources (demo Example Source) pay the full escalation cost (1h -> 4h -> 24h = 3 rungs). Reproduce with `python scripts/bench_5_sources.py`. + +Zscaler returning `prim_key=None` is a real classifier gap: its user-ish fields are named `deviceowner` / `department` without a separator, so they don't match the `principal_user` regex. This is visible, not hidden. Operators can inspect the `other` class in the report and manually set the prim_key for downstream queries. + +### Using the discovered schema in code + +```python +base = f"dataSource.name = '{source}' (tag != 'logVolume' OR !(tag = *))" + +# volume-by-action breakdown (always safe; default to count() if no action key) +if action_key: + q = f"{base} | group n=count() by {action_key} | sort -n" +else: + q = f"{base} | group n=count()" + +# per-principal mix (skip if no principal) +if prim_key and action_key: + q = f"{base} | group n=count() by {prim_key}, {action_key} | sort -n | limit 60" +elif prim_key: + q = f"{base} | group n=count() by {prim_key} | sort -n | limit 25" +``` + +`build_source_report.py` is the reference consumer of this pattern. Read it before writing a new pipeline that needs the same keys. + +## Source-agnostic baseline + anomaly detection + +`scripts/baseline_anomaly.py` is the productionised end-to-end pipeline for behavioural baselining and z-score anomaly detection on ANY data source. It composes the schema-discovery + key-picker + LRQ runner already in this skill, so a caller never has to hand-pick principal/action fields per source. + +What it does: + +1. Calls `inspect_source.discover_schema()` for the named source and `pick_keys(schema)` to choose `prim_key` (principal: user / host / IP / role) and `action_key` (event.type / activity_name / action). Honors per-source overrides if the caller knows better. +2. Runs N daily count slices (default 30) via `pq.run_pq()` over the baseline window. Daily slicing avoids the LRQ per-call deadline; `max_workers=3` respects the per-user 3 rps cap. +3. Runs one 24h live slice. +4. Merges slices client-side. Supports two baseline strategies: pooled (all daily samples in one bucket) and DoW-stratified (one bucket per day-of-week, eliminates weekday/weekend false-positives). +5. Surfaces three anomaly classes on every run: matched-pair z-score deviations (SPIKE/DROP), silent pairs (baseline → live=0), and new-behaviour pairs (live with no baseline). + +Usage: + +```bash +python scripts/baseline_anomaly.py --source "" --days 30 --stratify dow +python scripts/baseline_anomaly.py --source "Okta" --days 7 +python scripts/baseline_anomaly.py --source "FortiGate" --days 30 --stratify dow --principal src.ip.address --action unmapped.action +``` + +State is checkpointed to disk per source (`baseline_anomaly__state.json`) so the script is resumable across runs; use this when working in environments with short shell budgets. + +PQ building blocks the script wraps live in the `powerquery` skill at `examples/behavioral-baselines.md`. Read that file when authoring the equivalent as a STAR / PowerQuery Alert detection rule body, the rule-body shape uses `lookup` against a pre-computed baseline table (from `savelookup`) instead of the script's two-window LRQ pattern. + +### When to re-run discovery + +- Before writing any query against a source you haven't touched on this tenant. +- When a previously-working query starts returning 0 rows (parser may have changed field names after a platform update). +- On tenant handover: different customers enable different parser versions, especially for XDR connectors. +- Before authoring a detection rule body (STAR / Custom Detection / PowerQuery Alert), to confirm the fields the rule references actually exist. Pass the discovered schema through to the rule author in the body of the request. + +## CTO report generation pipeline + +A source-agnostic pipeline for producing CTO-grade Word + PowerPoint reports on any SDL data source. Three scripts, one JSON artefact. + +1. `scripts/build_source_report.py --source "" --window <7d|24h|...>`. Runs dimension probes, a unified per-principal query, and a timeline aggregate against the tenant via `scripts/pq.py`, then writes `reports/_/data.json`. Probes which of `user`, `src.ip.address`, `src.hostname`, `action`, `event.type` actually carry values, so the renderer can skip sections that would otherwise be empty. +2. `scripts/render_charts.py `. Emits PNG charts into `reports/_/charts/`. Pure function of the JSON, no tenant calls. +3. `scripts/build_docx.py ` and `scripts/build_pptx.py `. Read the same JSON, emit `_CTO_Report_.docx` and `_CTO_Deck_.pptx` next to it. Every chart, section, stat card, and recommendation is gated on `data["dims"]` so a dimension-sparse source (e.g. Windows Event Logs has only `event.type`) produces a shorter but coherent report, not a broken one with empty tiles. + +### Data.json contract (renderers depend on this shape) + +- `source`, `slug`, `window_label`, `window_start`, `window_end`, `base_filter`. +- `dims`: boolean-per-dimension probe result. +- `summary`: derived metrics. Key fields are `total`, `intervention_rate` (only meaningful if `dims.action`), `prim_key` (name of the principal field actually used: `user`, `src.hostname`, `src.ip.address`, or null), `top_principal_key`, `top_user`, `by_action`, `rank_24h`, `n_slices`. +- `per_user_mix_top10`: the unified top-N-principals-by-action-mix result. The renderer slices this into `by_user`, `by_action_blocks`, `by_user_bypass` rather than running three separate queries. Collector does one PQ; renderer derives the rest. + +### Principal key fallback + +Order: `user`, then `src.hostname`, then `src.ip.address`, then none. The collector picks the first dim that returned non-null; the renderer reads `summary.prim_key` and labels stat cards and takeaways accordingly (e.g. "Dominant host" vs "Dominant user"). + +### Renderer gotchas (learned the hard way) + +- **Never use em-dashes or en-dashes in any commentary string.** They read as AI-generated. Use commas, colons, or parentheses. +- **Stat card overflow.** Long labels (e.g. "Windows Event Log Creation") wrap through the card edge at 40pt. Use length-based font sizing: len<=7 gets 40pt, <=12 gets 28pt, <=18 gets 20pt, else 16pt. +- **Chart title "dayly" is not a word.** `f"{kind}ly"` where kind="day" is wrong. Use a lookup: `{"day": "Daily", "hour": "Hourly", "week": "Weekly", "month": "Monthly"}`. +- **X-axis label crowding on hourly charts.** A 24-slice timeline rotates 24 timestamps into each other. Sparsify with `ax.set_xticks(ticks[::step])` where `step = max(1, int(len(dates) / 10))`, BEFORE `autofmt_xdate`. +- **Single-series legend clutter.** Gate `ax.legend(...)` on `n_series > 1`. A one-series chart needs no legend; the title carries the meaning. +- **Bar data-labels overlap on dense charts.** Skip them when `len(dates) > 12`. The Y-axis scale is enough for dense timelines. +- **Adaptive title on dimensionless sources.** `title_suffix = "volume by action" if has_action else "volume"`. Don't claim action breakdown when there is none. +- **Recommendations grid leaves empty bottom cell.** With 2 cards, use a single row (not 2x2). With 1 card, full width. +- **Fallback bullets when both `action` and `top_user` are missing.** Otherwise the "CTO takeaways" section renders empty. Fall back to dominant `prim_key`, tenant rank (24h), and the data-lake story. + +### Commentary generators + +`_intervention_note()`, `_concentration_note()`, `_bypass_note()` in `build_docx.py` and `build_pptx.py` take metric values and return commentary strings gated on thresholds (>=40 high, >=10 moderate, else low). The thresholds are tuned for LLM-app traffic; if they feel off for a new source, edit the thresholds rather than the template strings. + +### Running the whole thing + +```text +# From the skill root, with $CLAUDE_CONFIG_DIR/sentinelone/credentials.json configured. +python scripts/build_source_report.py --source "" --window <7d|24h|...> +python scripts/render_charts.py reports/_/data.json +python scripts/build_docx.py reports/_/data.json +python scripts/build_pptx.py reports/_/data.json +``` + +The collector creates `reports/_/` on first run. The `reports/` directory is `.gitignored`; this skill ships with the framework only, not sample outputs. + +## Common high-value workflows + +- **Unified alert triage**: `list_alerts(...)` from `unified_alerts` for the modern multi-source alerts inbox (EDR + XDR + Identity + cloud + third-party); use `facets`/`group-by` for volume rollups; `set_alert_status` / `set_analyst_verdict` / `assign_alerts` for triage decisions; `add_alert_note` for context. +- **Threat triage (legacy)**: `GET /threats` filtered by `createdAt__gte` + `resolved=false`; enrich with agent details from `/agents?ids=...`; output a table. +- **Endpoint isolation**: find agent IDs (`/agents` with name/IP filter), confirm count, `POST /agents/actions/disconnect` with filter. +- **Hunt across DV / PowerQuery** -- `POST /sdl/v2/api/queries` with `queryType="LOG"` (S1QL) or `queryType="PQ"` (PowerQuery), then poll `GET /sdl/v2/api/queries/{id}` echoing the `X-Dataset-Query-Forward-Tag` response header. Auth is Bearer, not ApiToken. Legacy `/dv/init-query` + `/dv/query-status` + `/dv/events` + `/dv/events/pq` flows are deprecated (sunset 2027-02-15). See `references/WORKFLOWS.md` Section 4 for the canonical runner. +- **Natural-language hunt via Purple AI** -- Use `mcp__purple-mcp__purple_ai` (Purple MCP). The `purple_query()` Python helper and `scripts/call_purple.py` are non-functional for API tokens (`purpleLaunchQuery NATURAL_LANGUAGE` requires a browser-session teamToken, confirmed 2026-05-03). Only for SDL-telemetry questions; route entity questions to REST. +- **Site/Group inventory**: `/sites`, `/groups`, `/accounts` are the tenant-structure endpoints; many resources require filtering by `siteIds` / `accountIds`. +- **Bulk action audit**: `/activities` is the system-wide audit log; filter by `activityTypes` and `createdAt__gte`. +- **Push alerts + indicators INTO UAM** -- build OCSF payloads, then call `UAMAlertInterfaceClient.post_alert_with_indicators(alert, [...])` once per alert (loop for many). The helper posts indicators, sleeps 3s, and posts the single alert in the one sequence proven to surface cleanly on US1 tenants. See "UAM Alert Interface" section above for the two silent-drop failure modes (multi-alert POST, no sleep) it prevents. Use for pipeline integrations, synthetic-alert generation, and detection testing. +- **CTO report for a data source** -- `python scripts/build_source_report.py --source "" --window <7d|24h>` then `scripts/render_charts.py`, `scripts/build_docx.py`, `scripts/build_pptx.py` on the resulting `reports/_/data.json`. Works for any SDL data source; the renderer gates every section on `dims` so dimension-sparse sources (e.g. Windows Event Logs with only `event.type`) still produce a coherent deck. See "CTO report generation pipeline" for the data contract and renderer gotchas. + +Consult the per-tag reference files for exact parameter names, the above are orientation, not copy-paste ready. + +## Hyperautomation (HA): workflow management + +API root (confirmed via live network capture 2026-05-03): + +```text +/web/api/v2.1/hyper-automate/api/v1 +``` + +Auth: same `Authorization: ApiToken ` header as all other S1 REST calls. + +### Hyperautomation Endpoints + +| Operation | Method | Path | +|---|---|---| +| List workflows | `GET` | `/workflows?limit=&skip=&siteIds=&sortBy=&sortOrder=` | +| Get single workflow | `GET` | `/workflows/single/{workflowId}/{revisionId}` | +| Workflow filter counts | `GET` | `/workflows/filters-count?siteIds=` | +| Delete workflow | `DELETE` | `/workflows/{id}?accountIds=` | +| Export all workflows (ZIP) | `GET` | `/workflow-import-export/export` *(confirmed on /public path)* | +| Import workflow | `POST` | `/workflow-import-export/import` *(confirmed on /public path)* | + +**Important:** the single-workflow fetch requires BOTH `workflowId` AND `revisionId`. The `revisionId` is the `workflow.version_id` field returned in the list response. `GET /workflows/single/{id}` without a revision returns 404. + +**Deletion is a REST `DELETE` (soft, recoverable).** `DELETE /web/api/v2.1/hyper-automate/api/v1/workflows/{id}?accountIds=` returns `204` (validated 2026-06-13: import then publish then delete then gone-from-list). Scope with `accountIds` or `siteIds` to match where the workflow lives. The older `POST /workflows/archive` and the legacy archive wrapper return HTTP 500 on this tenant; do not use them; the REST `DELETE` is the correct mechanism. + +Export/import were not captured in the v1 network trace. They are confirmed working at the `/public` base path; the `/v1` equivalents have not been verified. + +**⚠ Listing workflows: the page is alphabetical and capped, and the name filter 502s on spaces.** +Both bite any "does this workflow already exist?" check, and both fail SILENTLY as "not found", +which makes a deploy re-import and leave a duplicate ACTIVE copy of every flow. Tenant-validated +2026-08-09 (one redeploy produced 4 active duplicates on a 400+ workflow tenant). + +| Call | Result | +| --- | --- | +| `GET /workflows?limit=200` (no filter) | first 200 **by name**; anything sorting later is invisible | +| `GET /workflows?name__contains=` | **HTTP 502** | +| `GET /workflows?name__contains=` | 200, correct subset | + +So: filter server-side on a whitespace-free token (e.g. the deployment prefix), then match the +exact name client-side. Never scan an unfiltered capped page. And do not treat an API error as +"absent": `mgmt()`-style helpers turn a 502 into an empty result, which is exactly how a lookup +built to prevent duplicates ends up causing them. On error, log and fall back rather than +reporting not-found. + +**Parked run-now executions.** A manually triggered execution can sit at `state: Running` with +`executed_actions: 0` indefinitely, and abandoned ones accumulate and hold scheduler slots (10 +observed after a day of interrupted test runs, blocking new executions). Clear one with +`deactivate` → `activate` → run again, or delete the workflow. Never trigger multiple flows +concurrently. Check `GET /workflow-execution` for `Running` + `executed_actions: 0` before +debugging a flow that "will not start". + +### List response shape (key fields) + +Each item in `data[]`: + +```jsonc +{ + "id": "", // top-level workflow ID + "workflow": { + "id": "", + "version_id": "", // pass as revisionId to single endpoint + "name": "...", + "state": "active|inactive|deactivated|draft", + "status": "idle|running|...", + "scope_level": "account|site", + "scope_id": "<19-digit>", + "site_name": "", + "created_at": "", + "updated_at": "", + "version_count": + }, + "actions": [ + { "id": "", "integration_id": "", "type": "" } + ] +} +``` + +Action types observed: `singularity_response_trigger`, `manual_trigger`, `http_trigger`, `scheduled_trigger`, `email_trigger`, `http_request`, `condition`, `loop`, `variable`, `delay`, `send_email`, `snippet`, `data_formation`, `wait_for_slack`, `break_loop`, `create_interaction`, `wait_for_interaction`, `llm`. + +### filter-count response structure + +`GET /workflows/filters-count` returns `data[]` with keys: `states`, `scope_ids`, `trigger_types`, `core_actions`, `tags`, `integrations`. Each entry has `{ count, value, title }`. Use this for summary dashboards (e.g. how many active workflows, which integrations are most used). + +### MCP tools + +`ha_list_workflows`, list with scope/sort/pagination. Returns `revisionId` alongside each workflow. +`ha_get_workflow`, fetch a single workflow by `workflowId` + optional `revisionId` (auto-resolves from list if omitted). +`ha_delete_workflow`, soft-delete one or more workflows via `DELETE /workflows/{id}` (scope with accountIds/siteIds). Confirm with user before calling. +`ha_import_workflow`, create workflow from JSON. Requires Hyper Automate.write permission. +`ha_export_workflow`, export all workflows as ZIP. + +### Permissions + +`Hyper Automate.view`, read operations (list, get, filter-count, export). +`Hyper Automate.write`, write operations (import, delete). Confirmed: without this permission, import returns 403. + +## Using s1-secops-mcp tools for direct console operations + +Console operations use the `s1-secops-mcp` MCP tools, which bypass the Cowork sandbox proxy +entirely. Use `s1_api_get`, `s1_api_post`, `uam_list_alerts`, `uam_get_alert`, `uam_set_status`, +and other MCP tools directly instead of falling back to the `mgmt-console-api` +skill scripts. The MCP tools run locally on your machine and make direct HTTPS calls to +`*.sentinelone.net` without proxy interference. + +## STAR / Custom Detection rule lifecycle (learnings) + +- **Update in place:** `PUT /web/api/v2.1/cloud-detection/rules/{id}` requires the FULL body `{data, filter}`; omitting `filter` returns HTTP 400 "filter: Missing data for required field". PUT resets the rule to the body's `status` (typically Disabled), so re-enable afterward. +- **Delete:** `DELETE /web/api/v2.1/cloud-detection/rules/{id}`, or bulk with `{"filter": {"ids": [...], "siteIds" | "accountIds": [...]}}`. +- **List:** always pass `isLegacy=false` or scheduled / PowerQuery rules are silently omitted. +- **Scheduled rules run on a pre-aggregated data layer**, so PowerQuery functions like `dataset`, `datasource`, `now`, `querystart`/`queryend`/`queryspan`, `topK`, `savelookup`, CIDR/wildcard `lookup`, `lookup` over a >10,000-row table, time-shifted `timebucket`, and `timebucket` < 30s are NOT available in a scheduled-rule body (full list in the `powerquery` skill). A detection needing any of them, e.g. an absent-pair anti-join (`left join` + `dataset`), runs as a Hyperautomation watchdog instead (see `hyperautomation`). +- **Lookup-reading rules are account-scope only.** Lookup tables / datatables are ACCOUNT-level objects, so any rule whose PQ body reads one (`| lookup ... from
`) can only be created with `filter.accountIds`; site-scoped creation of lookup-reading rules is invalid. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Accounts.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Accounts.md index 105b048..16c97b2 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Accounts.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Accounts.md @@ -3,6 +3,7 @@ 12 endpoints. ## `GET /web/api/v2.1/accounts` + **Get Accounts** `operationId`: `_web_api_accounts_get` @@ -11,6 +12,7 @@ Get the Accounts, and their data, that match the filter. This command gives the Required permissions: `Accounts.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -42,6 +44,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/accounts` + **Create Account** `operationId`: `_web_api_accounts_post` @@ -50,11 +53,13 @@ Create a new Account. This command requires Global permissions and an MSSP deplo Required permissions: `Accounts.create` Parameters: -- `body` [body, accounts.schemas_PostAccountSchema]: + +- `body` [body, accounts.schemas_PostAccountSchema]: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/accounts/{account_id}` + **Get Account by ID** `operationId`: `_web_api_accounts_{account_id}_get` @@ -63,11 +68,13 @@ Get Account data from a given Account ID. To get an Account ID, run "accounts". Required permissions: `Accounts.view` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". Responses: 404 Account not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/accounts/{account_id}` + **Update Account** `operationId`: `_web_api_accounts_{account_id}_put` @@ -76,12 +83,14 @@ Change the data of an Account. This command requires a Global user or an Account Required permissions: `Accounts.edit` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". -- `body` [body, accounts.schemas_AccountPutSchema]: +- `body` [body, accounts.schemas_AccountPutSchema]: Responses: 404 Account not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/accounts/{account_id}/expire-now` + **Expire an Account** `operationId`: `_web_api_accounts_{account_id}_expire-now_post` @@ -90,11 +99,13 @@ Expire an Account immediately. The user must have Global access or Account acces Required permissions: `Accounts.edit` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". Responses: 404 Account not found, 200 Expire account now, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/accounts/{account_id}/reactivate` + **Reactivate Account** `operationId`: `_web_api_accounts_{account_id}_reactivate_put` @@ -103,12 +114,14 @@ Reactivate an expired Account. This command requires a Global user or Support. C Required permissions: `Accounts.edit` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". -- `body` [body, accounts.schemas_ReactivateAccountSchema]: +- `body` [body, accounts.schemas_ReactivateAccountSchema]: Responses: 404 Account not found, 200 Account reactivated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/accounts/{account_id}/revert-policy` + **Revert Policy** `operationId`: `_web_api_accounts_{account_id}_revert-policy_put` @@ -117,12 +130,14 @@ The policy of the Account is based on the default Global policy and is enforced Required permissions: `Policy.edit` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". -- `body` [body, policies_schemas_RevertPolicySchema]: +- `body` [body, policies_schemas_RevertPolicySchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/accounts/{account_id}/uninstall-password/generate` + **Generate/Regenerate Uninstall Password** `operationId`: `_web_api_accounts_{account_id}_uninstall-password_generate_post` @@ -131,12 +146,14 @@ You can uninstall all Agents of one Account with one command that requires a pas Required permissions: `Endpoints.modifyUninstallPassword` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". -- `body` [body, accounts.schemas_UninstallPasswordGenerateRequestSchema]: +- `body` [body, accounts.schemas_UninstallPasswordGenerateRequestSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/accounts/{account_id}/uninstall-password/metadata` + **Get Uninstall Password Metadata** `operationId`: `_web_api_accounts_{account_id}_uninstall-password_metadata_get` @@ -145,11 +162,13 @@ Get the uninstall password metadata, such as which user created and revoked it a Required permissions: `Accounts.view` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". Responses: 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/accounts/{account_id}/uninstall-password/revoke` + **Revoke Uninstall Password** `operationId`: `_web_api_accounts_{account_id}_uninstall-password_revoke_post` @@ -158,11 +177,13 @@ Delete the account-level uninstall password. If you do not delete it, you or ano Required permissions: `Endpoints.modifyUninstallPassword` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". Responses: 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/accounts/{account_id}/uninstall-password/view` + **Get Uninstall Password** `operationId`: `_web_api_accounts_{account_id}_uninstall-password_view_get` @@ -171,11 +192,13 @@ Get the uninstall password to uninstall several Agents of one Account with one c Required permissions: `Endpoints.viewUninstallPassword` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". Responses: 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/export/accounts` + **Export Accounts** `operationId`: `_web_api_export_accounts_get` @@ -184,6 +207,7 @@ Export Accounts data to a CSV, for Accounts that match the filter. Required permissions: `Accounts.view` Parameters: + - `ids` [query, array]: A list of Account IDs. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to search for. Example: "225494730938493804,225494730938493915". - `query` [query, string]: Full text search for fields: name. (Note: on single-Account Consoles, the Account name will not be matched) diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Activities.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Activities.md index e310f26..c344ef8 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Activities.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Activities.md @@ -3,6 +3,7 @@ 4 endpoints. ## `GET /web/api/v2.1/activities` + **Get Activities** `operationId`: `_web_api_activities_get` @@ -12,6 +13,7 @@ Get the activities, and their data, that match the filters. Required permissions: `Activity Page.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -42,6 +44,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/activities/types` + **Get Activity Types** `operationId`: `_web_api_activities_types_get` @@ -52,6 +55,7 @@ Required permissions: `Activity Page.view` Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/export/activities` + **Export Activities** `operationId`: `_web_api_export_activities_get` @@ -60,6 +64,7 @@ Export the list of activities. Required permissions: `Activity Page.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -84,6 +89,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/last-activity-as-syslog` + **Last activity as Syslog message** `operationId`: `_web_api_last-activity-as-syslog_get` @@ -92,6 +98,7 @@ To see examples of Syslog messages, you can get the Syslog message that correspo Required permissions: `Activity Page.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agent_Actions.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agent_Actions.md index a21f0a0..acf38b5 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agent_Actions.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agent_Actions.md @@ -3,6 +3,7 @@ 38 endpoints. ## `POST /web/api/v2.1/agents/actions/abort-scan` + **Abort Scan** `operationId`: `_web_api_agents_actions_abort-scan_post` @@ -11,11 +12,13 @@ Immediately stop a Full Disk Scan on all Agents that match the filter. See "Init Required permissions: `Endpoints.abortScan` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/approve-stateless-upgrade` + **Approve Stateless Upgrades** `operationId`: `_web_api_agents_actions_approve-stateless-upgrade_post` @@ -24,11 +27,13 @@ Approve stateless upgrade for agents Required permissions: `Endpoints.uninstall` Parameters: -- `body` [body, agents.schemas_AgentsCleaninstallerSchema]: + +- `body` [body, agents.schemas_AgentsCleaninstallerSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/approve-uninstall` + **Approve Uninstall** `operationId`: `_web_api_agents_actions_approve-uninstall_post` @@ -37,11 +42,13 @@ If a user tries to uninstall the SentinelOne Agent from an endpoint, an uninstal Required permissions: `Endpoints.approveUninstall` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/broadcast` + **Broadcast Message** `operationId`: `_web_api_agents_actions_broadcast_post` @@ -50,11 +57,13 @@ You can send a message through the Agents that users can see.
This is useful Required permissions: `Endpoints.sendMessage` Parameters: -- `body` [body, agents.schemas_AgentsBroadcastActionSchema]: + +- `body` [body, agents.schemas_AgentsBroadcastActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/can-start-remote-shell` + **Can run Remote Shell** `operationId`: `_web_api_agents_actions_can-start-remote-shell_post` @@ -63,11 +72,13 @@ Who can run Remote Shell? Remote Shell is a powerful way to respond remotely to Required permissions: `Endpoints.remoteShell` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 Insufficient permissions, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry., 200 Success ## `POST /web/api/v2.1/agents/actions/connect` + **Connect to Network** `operationId`: `_web_api_agents_actions_connect_post` @@ -76,11 +87,13 @@ After you run "disconnect from network" on endpoints, analyze the issue, and mit Required permissions: `Endpoints.reconnectToNetwork` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/decommission` + **Decommission** `operationId`: `_web_api_agents_actions_decommission_post` @@ -89,11 +102,13 @@ If a user is scheduled for time off, or a device is scheduled for maintenance, y Required permissions: `Endpoints.decommission` Parameters: -- `body` [body, agents.schemas_AgentsDangerousActionSchema]: + +- `body` [body, agents.schemas_AgentsDangerousActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/disable-agent` + **Disable Agent** `operationId`: `_web_api_agents_actions_disable-agent_post` @@ -102,11 +117,13 @@ Use this command to disable Agents that match the filter.
Disabled agents ru Required permissions: `Endpoints.disableAgent` Parameters: -- `body` [body, agents.schemas_AgentDisableActionSchema]: + +- `body` [body, agents.schemas_AgentDisableActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/disconnect` + **Disconnect from Network** `operationId`: `_web_api_agents_actions_disconnect_post` @@ -115,11 +132,13 @@ Use this command to isolate (quarantine) endpoints from the network, if the endp Required permissions: `Endpoints.disconnectFromNetwork` Parameters: -- `body` [body, agents.schemas_AgentsDangerousActionSchema]: + +- `body` [body, agents.schemas_AgentsDangerousActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/enable-agent` + **Enable Agent** `operationId`: `_web_api_agents_actions_enable-agent_post` @@ -128,11 +147,13 @@ Use this command to enable disabled Agents that match the filter.
If the com Required permissions: `Endpoints.enableAgent` Parameters: -- `body` [body, agents.schemas_AgentEnableActionSchema]: + +- `body` [body, agents.schemas_AgentEnableActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/fetch-firewall-rules` + **Fetch Firewall Rules** `operationId`: `_web_api_agents_actions_fetch-firewall-rules_post` @@ -141,11 +162,13 @@ Firewall Control is disabled at the Global level. When it is first enabled, all Required permissions: `Endpoints.configureFirewallLogging` Parameters: -- `body` [body, agents.schemas_AgentFetchFirewallRulesActionSchema]: + +- `body` [body, agents.schemas_AgentFetchFirewallRulesActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/fetch-installed-apps` + **Get Applications** `operationId`: `_web_api_agents_actions_fetch-installed-apps_post` @@ -154,11 +177,13 @@ Application Risk Management is an EA feature. Contact your partner or SentinelOn Required permissions: `Endpoints.showApplications` Parameters: -- `body` [body, agents.schemas_AgentsDangerousActionSchema]: + +- `body` [body, agents.schemas_AgentsDangerousActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/fetch-logs` + **Fetch Logs** `operationId`: `_web_api_agents_actions_fetch-logs_post` @@ -167,11 +192,13 @@ Get the Agent and Endpoint logs from Agents that match the filter.
The Agent Required permissions: `Endpoints.FetchLogs` Parameters: -- `body` [body, agents.schemas_AgentFetchLogsActionSchema]: + +- `body` [body, agents.schemas_AgentFetchLogsActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/firewall-logging` + **Fetch Firewall Logs** `operationId`: `_web_api_agents_actions_firewall-logging_post` @@ -180,11 +207,13 @@ Get Firewall Control events in the local log file, written in clear text, for Fi Required permissions: `Endpoints.configureFirewallLogging` Parameters: -- `body` [body, agents.schemas_AgentFirewallLoggingActionSchema]: + +- `body` [body, agents.schemas_AgentFirewallLoggingActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/initiate-scan` + **Initiate Scan** `operationId`: `_web_api_agents_actions_initiate-scan_post` @@ -193,11 +222,13 @@ Use this command to run a Full Disk Scan on Agents that match the filter.
Fu Required permissions: `Endpoints.initiateScan` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/local-upgrade-authorization` + **Edit local upgrade/downgrade Site authorization** `operationId`: `_web_api_agents_actions_local-upgrade-authorization_post` @@ -206,11 +237,13 @@ Edit when authorization of local upgrades/downgrades expires. Required permissions: `Local Upgrade/Downgrade Authorization.edit` Parameters: -- `body` [body, agents.schemas_AgentLocalUpgradeAuthorizationActionSchema]: + +- `body` [body, agents.schemas_AgentLocalUpgradeAuthorizationActionSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/manage-tags` + **Manage endpoint tags: add, remove, override** `operationId`: `_web_api_agents_actions_manage-tags_post` @@ -219,11 +252,13 @@ The "add" operation adds the given tags to the endpoints (if not already present Required permissions: `Endpoints.manageEndpointTags` Parameters: -- `body` [body, agents.schemas_ManageEndpointTagsSchema]: + +- `body` [body, agents.schemas_ManageEndpointTagsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/mark-up-to-date` + **Mark as up-to-date** `operationId`: `_web_api_agents_actions_mark-up-to-date_post` @@ -232,11 +267,13 @@ The value of the Agent version as "up-to-date" is a useful filter for many actio Required permissions: `Endpoints.markAsUpToDate` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/move-to-console` + **Move to Console** `operationId`: `_web_api_agents_actions_move-to-console_post` @@ -245,11 +282,13 @@ You can move Agents between Management Consoles. This command moves Agents to a Required permissions: `Endpoints.migrateAgent` Parameters: -- `body` [body, agents.schemas_AgentsMoveToConsoleSchema]: + +- `body` [body, agents.schemas_AgentsMoveToConsoleSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/move-to-site` + **Move between Sites** `operationId`: `_web_api_agents_actions_move-to-site_post` @@ -258,11 +297,13 @@ This command requires Account or Global level access.
Agents are assigned to Required permissions: `Endpoints.moveToAnotherSite` Parameters: -- `body` [body, agents.schemas_AgentsMoveToSiteSchema]: + +- `body` [body, agents.schemas_AgentsMoveToSiteSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/randomize-uuid` + **Randomize UUID** `operationId`: `_web_api_agents_actions_randomize-uuid_post` @@ -271,11 +312,13 @@ IMPORTANT: This action will assign a new UUID to Agents that match the filter. < Required permissions: `Endpoints.randomizeUuid` Parameters: -- `body` [body, agents.schemas_AgentsDangerousActionSchema]: + +- `body` [body, agents.schemas_AgentsDangerousActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/ranger-disable` + **Disable Network Discovery** `operationId`: `_web_api_agents_actions_ranger-disable_post` @@ -284,11 +327,13 @@ Disable Network Discovery from the Agents that match the filter.
Singularity Required permissions: `Endpoints.disableRanger` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/ranger-enable` + **Enable Network Discovery** `operationId`: `_web_api_agents_actions_ranger-enable_post` @@ -297,11 +342,13 @@ Singularity Network Discovery gives full visibility of all devices connected to Required permissions: `Endpoints.enableRanger` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/reject-uninstall` + **Reject uninstall** `operationId`: `_web_api_agents_actions_reject-uninstall_post` @@ -310,11 +357,13 @@ Reject uninstall requests for all Agents that match the filter. To learn more ab Required permissions: `Endpoints.rejectUninstall` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/reset-local-config` + **Reset Local Config** `operationId`: `_web_api_agents_actions_reset-local-config_post` @@ -323,11 +372,13 @@ SentinelCtl is the CLI for Agents. It runs commands directly on one Agent at a t Required permissions: `Endpoints.resetLocalConfiguration` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/reset-passphrase` + **Reset Passphrases** `operationId`: `_web_api_agents_actions_reset-passphrase_post` @@ -336,11 +387,13 @@ Initiate an Agent passphrase reset for Agents that match the filter.
This ac Required permissions: `Endpoints.resetPassphrase` Parameters: -- `body` [body, agents.schemas_AgentsResetPassphrasesSchema]: + +- `body` [body, agents.schemas_AgentsResetPassphrasesSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/reset-passphrase/capability` + **Reset Passphrase Capability** `operationId`: `_web_api_agents_actions_reset-passphrase_capability_post` @@ -349,11 +402,13 @@ Check if a passphrase reset can be initiated for Agents that match the filter. < Required permissions: `Endpoints.resetPassphrase` Parameters: -- `body` [body, agents.schemas_AgentsResetPassphraseCapabilitySchema]: + +- `body` [body, agents.schemas_AgentsResetPassphraseCapabilitySchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/restart-machine` + **Restart** `operationId`: `_web_api_agents_actions_restart-machine_post` @@ -362,24 +417,28 @@ Use this command to restart endpoints that have an Agent installed and that fit Required permissions: `Endpoints.reboot` Parameters: -- `body` [body, agents.schemas_AgentsDangerousActionSchema]: + +- `body` [body, agents.schemas_AgentsDangerousActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/set-config` + **Set Persistent Configuration Overrides** `operationId`: `_web_api_agents_actions_set-config_post` -This command requires Global permissions or Support.
The configuration of an Agent can be changed in different ways, such as through Policy settings, Policy Override, SentinelCtl, and changes to the LocalConfig.json file.
For Windows, Policy Override overwrites policy settings, and local changes (to the file and from this command) overwrite Policy Override from the Console or with policy updates from the API.
For macOS, the Policy Override has the highest priority. If you run this command and then update a Group policy that affects both Windows and macOS endpoints, the settings of this command are applied to the Windows endpoints. But the macOS endpoints will apply the settings of the policy, for settings that are duplicated in both the policy and this command.
When you use this command, enter the filter values to set which Agents get the change. Then use the data parameter to set the actual changes. Get the JSON settings for data from the Agent Configuration or see the Knowledge Base: https://support.sentinelone.com/hc/en-us/articles/360022158673-sentinelctl +This command requires Global permissions or Support.
The configuration of an Agent can be changed in different ways, such as through Policy settings, Policy Override, SentinelCtl, and changes to the LocalConfig.json file.
For Windows, Policy Override overwrites policy settings, and local changes (to the file and from this command) overwrite Policy Override from the Console or with policy updates from the API.
For macOS, the Policy Override has the highest priority. If you run this command and then update a Group policy that affects both Windows and macOS endpoints, the settings of this command are applied to the Windows endpoints. But the macOS endpoints will apply the settings of the policy, for settings that are duplicated in both the policy and this command.
When you use this command, enter the filter values to set which Agents get the change. Then use the data parameter to set the actual changes. Get the JSON settings for data from the Agent Configuration or see the Knowledge Base: Required permissions: `Endpoints.configuration` Parameters: -- `body` [body, agents.schemas_AgentsSetConfigSchema]: + +- `body` [body, agents.schemas_AgentsSetConfigSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/set-external-id` + **Set External ID** `operationId`: `_web_api_agents_actions_set-external-id_post` @@ -388,11 +447,13 @@ You can add a Customer Identifier (a string) to identify each endpoint or to tag Required permissions: `Endpoints.setCustomerIdentifier` Parameters: -- `body` [body, agents.schemas_AgentsUpdateExternalIdScheme]: + +- `body` [body, agents.schemas_AgentsUpdateExternalIdScheme]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/shutdown` + **Shutdown** `operationId`: `_web_api_agents_actions_shutdown_post` @@ -401,11 +462,13 @@ You can shut down endpoints remotely for performance, maintenance, or security. Required permissions: `Endpoints.shutDown` Parameters: -- `body` [body, agents.schemas_AgentsDangerousActionSchema]: + +- `body` [body, agents.schemas_AgentsDangerousActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/start-profiling` + **Start Remote Profiling** `operationId`: `_web_api_agents_actions_start-profiling_post` @@ -414,11 +477,13 @@ Use this command to start remote profiling on Agents that match the filter.
Required permissions: `Endpoints.remoteProfiling` Parameters: -- `body` [body, agents.schemas_AgentStartProfilingActionSchema]: + +- `body` [body, agents.schemas_AgentStartProfilingActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/start-remote-shell` + **Start Remote Shell** `operationId`: `_web_api_agents_actions_start-remote-shell_post` @@ -427,11 +492,13 @@ Remote shell is an opened websocket between the browser and the Agent, with a pr Required permissions: `Endpoints.remoteShell` Parameters: -- `body` [body, agents.schemas_StartRemoteShellSchema]: + +- `body` [body, agents.schemas_StartRemoteShellSchema]: Responses: 403 Insufficient permissions, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry., 200 Success ## `POST /web/api/v2.1/agents/actions/stop-profiling` + **Stop Remote Profiling** `operationId`: `_web_api_agents_actions_stop-profiling_post` @@ -440,11 +507,13 @@ Use this command to stop remote profiling on Agents that match the filter.
I Required permissions: `Endpoints.remoteProfiling` Parameters: -- `body` [body, agents.schemas_AgentStopProfilingActionSchema]: + +- `body` [body, agents.schemas_AgentStopProfilingActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/terminate-remote-shell` + **Terminate Remote Shell** `operationId`: `_web_api_agents_actions_terminate-remote-shell_post` @@ -453,11 +522,13 @@ Remote Shell is a powerful, full shell for Windows, macOS, and Linux. It is best Required permissions: `Endpoints.remoteShell` Parameters: -- `body` [body, agents.schemas_TerminateRemoteShellSchema]: + +- `body` [body, agents.schemas_TerminateRemoteShellSchema]: Responses: 403 Insufficient permissions, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry., 200 Success ## `POST /web/api/v2.1/agents/actions/uninstall` + **Uninstall** `operationId`: `_web_api_agents_actions_uninstall_post` @@ -466,11 +537,13 @@ Use this command to uninstall Agents that match the filter. For Windows and macO Required permissions: `Endpoints.uninstall` Parameters: -- `body` [body, agents.schemas_AgentsDangerousActionSchema]: + +- `body` [body, agents.schemas_AgentsDangerousActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/actions/update-software` + **Update Software** `operationId`: `_web_api_agents_actions_update-software_post` @@ -479,11 +552,13 @@ Use this command to update the Agent version on endpoints that have the Agent in Required permissions: `Endpoints.updateSoftware` Parameters: -- `body` [body, agents.schemas_AgentsUpdateSoftwareActionSchema]: + +- `body` [body, agents.schemas_AgentsUpdateSoftwareActionSchema]: Responses: 403 User has insufficient permissions to perform the requested a, 409 The Agent is automatically upgraded according to its Upgrade, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/{agent_id}/actions/fetch-files` + **Fetch Files** `operationId`: `_web_api_agents_{agent_id}_actions_fetch-files_post` @@ -492,7 +567,8 @@ Fetch files from endpoints (up to 10 MB for each command) to analyze the root of Required permissions: `Endpoints.fileFetch` Parameters: + - `agent_id` [path, string] **required**: Agent ID. Example: "225494730938493804". -- `body` [body, agents.schemas_AgentFetchFilesActionSchema]: +- `body` [body, agents.schemas_AgentFetchFilesActionSchema]: Responses: 404 Agent not found, 403 User has insufficient permissions to perform the requested a, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agent_Support_Actions.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agent_Support_Actions.md index 20d4017..f4320a6 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agent_Support_Actions.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agent_Support_Actions.md @@ -3,6 +3,7 @@ 1 endpoints. ## `POST /web/api/v2.1/agents/actions/clear-remote-shell-session` + **Clear Remote Shell** `operationId`: `_web_api_agents_actions_clear-remote-shell-session_post` @@ -11,6 +12,7 @@ Remote Shell is a powerful way to respond remotely to events on endpoints. It le Required permissions: `Endpoints.clearRemoteShellSession` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agents.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agents.md index 763472a..425c713 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agents.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agents.md @@ -3,6 +3,7 @@ 14 endpoints. ## `GET /web/api/v2.1/agents` + **Get Agents** `operationId`: `_web_api_agents_get` @@ -11,6 +12,7 @@ Get the Agents, and their data, that match the filter. This command gives the Ag Required permissions: `Endpoints.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -221,6 +223,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/agents/applications` + **Applications** `operationId`: `_web_api_agents_applications_get` @@ -229,11 +232,13 @@ Get the installed applications for a specific Agent.
To get the Agent ID, ru Required permissions: `Endpoints.view` Parameters: + - `ids` [query, array] **required**: Agent ID list. Example: "225494730938493804,225494730938493915". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/agents/count` + **Count Agents** `operationId`: `_web_api_agents_count_get` @@ -242,6 +247,7 @@ Get the count of Agents that match a filter. This command is useful to run befor Required permissions: `Endpoints.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -445,6 +451,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/disable-hyper-automation-pna` + **Disable PNA for Hyperautomation** `operationId`: `_web_api_agents_disable-hyper-automation-pna_post` @@ -453,11 +460,13 @@ Disable Agent PNA for Hyperautomation Required permissions: `Endpoints.edit, Hyper Automate.connectionsEdit` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/agents/enable-hyper-automation-pna` + **Enable Agent PNA for Hyperautomation** `operationId`: `_web_api_agents_enable-hyper-automation-pna_post` @@ -466,11 +475,13 @@ Enable Agent PNA for Hyperautomation Required permissions: `Endpoints.edit, Hyper Automate.connectionsEdit` Parameters: -- `body` [body, agents.schemas_AgentsActionSchema]: + +- `body` [body, agents.schemas_AgentsActionSchema]: Responses: 400 Invalid user input received. See error details for further i, 404 Agent not found, 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/agents/passphrases` + **Get Passphrase** `operationId`: `_web_api_agents_passphrases_get` @@ -479,6 +490,7 @@ Show the passphrase for the Agents that match the filter. This is an important c Required permissions: `Endpoints.showPassphrase` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -687,6 +699,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/agents/processes` + **Processes** `operationId`: `_web_api_agents_processes_get` @@ -695,11 +708,13 @@ Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input rec Required permissions: `Endpoints.view` Parameters: + - `ids` [query, array] **required**: Agent ID list. Example: "225494730938493804,225494730938493915". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/agents/tags` + **Get the endpoint tags that match the filters.** `operationId`: `_web_api_agents_tags_get` @@ -709,6 +724,7 @@ Required permissions: `Endpoints.view` Optional permissions: `Tag Management.edit` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -739,6 +755,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/agents/tags/filters-count` + **Endpoint tags count by Filters** `operationId`: `_web_api_agents_tags_filters-count_get` @@ -748,6 +765,7 @@ Required permissions: `Endpoints.view` Optional permissions: `Tag Management.edit` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -767,6 +785,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/agents/{agent_id}/local-upgrade-authorization` + **Get local upgrade/downgrade Agent authorization** `operationId`: `_web_api_agents_{agent_id}_local-upgrade-authorization_get` @@ -775,11 +794,13 @@ Get the time when authorization of local upgrades/downgrades expires Required permissions: `Local Upgrade/Downgrade Authorization.view` Parameters: + - `agent_id` [path, string] **required**: Agent ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/agents/{agent_id}/uploads/{activity_id}` + **Export Agent Logs** `operationId`: `_web_api_agents_{agent_id}_uploads_{activity_id}_get` @@ -789,12 +810,14 @@ Required permissions: `Endpoints.view` Optional permissions: `Endpoints.fileFetch, Endpoints.FetchLogs, Endpoints.downloadRemoteShellTranscript` Parameters: + - `agent_id` [path, string] **required**: Agent ID. Example: "225494730938493804". - `activity_id` [path, string] **required**: ID of activity that logs files uploaded by agent. Example: "225494730938493804". Responses: 404 Agent or activity not found, 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/export/agents` + **Export Agents** `operationId`: `_web_api_export_agents_get` @@ -803,6 +826,7 @@ Export Agent data to a CSV, for Agents that match the filter. This command expor Required permissions: `Endpoints.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -1006,6 +1030,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/export/agents-light` + **Export Agents - Light** `operationId`: `_web_api_export_agents-light_get` @@ -1014,6 +1039,7 @@ Export Agent data to a CSV, for Agents that match the filter. This command expor Required permissions: `Endpoints.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -1217,6 +1243,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/export/agents-passphrases` + **Export Agents - Passphrases** `operationId`: `_web_api_export_agents-passphrases_post` @@ -1225,6 +1252,7 @@ Export Agent passphrases to a CSV, for Agents that match the filter. This comman Required permissions: `Endpoints.showPassphrase` Parameters: -- `body` [body, agents.schemas_ExportAgentsPassphrasesSchema]: + +- `body` [body, agents.schemas_ExportAgentsPassphrasesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agents_Repository_Beta.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agents_Repository_Beta.md index bc357ac..93b7e2d 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agents_Repository_Beta.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Agents_Repository_Beta.md @@ -3,6 +3,7 @@ 3 endpoints. ## `DELETE /web/api/v2.1/agent-artifacts/token` + **Delete Access Token** `operationId`: `_web_api_agent-artifacts_token_delete` @@ -11,6 +12,7 @@ Deletes an access token for the S1 Agent Artifacts Repository Required permissions: `Agent Artifacts.delete` Parameters: + - `scope_level` [query, string]: Scope level to list the tokens for. Possible values: 'site', 'account' - `scope_id` [query, integer]: Scope id to list the tokens for, example: '983604236220743370' - `token_id` [query, integer]: token id of the token to be deleted, example: '42' @@ -18,6 +20,7 @@ Parameters: Responses: 200 Token deleted, 400 Invalid request, 401 Unauthorized, 404 Not found, 500 Internal error ## `GET /web/api/v2.1/agent-artifacts/token` + **List Access Tokens** `operationId`: `_web_api_agent-artifacts_token_get` @@ -26,6 +29,7 @@ Lists valid access tokens for the S1 Agent Artifacts Repository, with the option Required permissions: `Agent Artifacts.listAccessTokens` Parameters: + - `scope_level` [query, string]: Scope level to list the tokens for. Possible values: 'site', 'account', 'tenant' - `scope_id` [query, integer]: Scope id to list the tokens for, example: '983604236220743370' - `limit` [query, integer]: The number of tokens to return, for example: '10'. Optional @@ -34,6 +38,7 @@ Parameters: Responses: 200 OK, 400 Invalid request, 401 Unauthorized, 500 Internal error ## `POST /web/api/v2.1/agent-artifacts/token` + **Create Access Token** `operationId`: `_web_api_agent-artifacts_token_post` @@ -42,6 +47,7 @@ Creates an access token for the S1 Agent Artifacts Repository, which is needed f Required permissions: `Agent Artifacts.create` Parameters: + - `request` [body, handlers.TokenRequest] **required**: expected request body Responses: 200 OK, 400 Invalid request, 401 Unauthorized, 500 Internal Error diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Management.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Management.md index 20b5514..d421dc2 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Management.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Management.md @@ -3,6 +3,7 @@ 16 endpoints. ## `GET /web/api/v2.1/application-management/inventory` + **Get Application Inventory** `operationId`: `_web_api_application-management_inventory_get` @@ -11,6 +12,7 @@ Get application inventory data grouped by application name and vendor. Required permissions: `Applications Page.view` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `sortOrder` [query, string] (enum: asc, desc): Sort direction. Example: "asc". - `accountIds` [query, array]: Single Account ID to filter by. Example: "225494730938493804". @@ -34,6 +36,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/inventory/applications` + **Get Endpoint Apps** `operationId`: `_web_api_application-management_inventory_applications_get` @@ -42,11 +45,13 @@ Get the installed applications for a specific endpoint.
To get the Agent ID, Required permissions: `Applications Page.view` Parameters: + - `ids` [query, array] **required**: Agent ID list. Example: "225494730938493804,225494730938493915". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/inventory/endpoints` + **Get App Inventory Endpoints** `operationId`: `_web_api_application-management_inventory_endpoints_get` @@ -55,6 +60,7 @@ Get endpoint data for a specific application. Required permissions: `Applications Page.view` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `sortOrder` [query, string] (enum: asc, desc): Sort direction. Example: "asc". - `detectionDate__gte` [query, string]: Application detection date after or at this timestamp. Example: "2018-02-27T04:49:26.257525Z". @@ -82,6 +88,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/inventory/endpoints/export/csv` + **Inventory Endpoints Data Export** `operationId`: `_web_api_application-management_inventory_endpoints_export_csv_get` @@ -90,6 +97,7 @@ Export application inventory endpoints data to CSV. Required permissions: `Applications Page.view` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `detectionDate__gte` [query, string]: Application detection date after or at this timestamp. Example: "2018-02-27T04:49:26.257525Z". - `accountIds` [query, array]: Single Account ID to filter by. Example: "225494730938493804". @@ -111,6 +119,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/inventory/export/csv` + **Inventory Data Export** `operationId`: `_web_api_application-management_inventory_export_csv_get` @@ -119,6 +128,7 @@ Export application inventory data to CSV. Required permissions: `Applications Page.view` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `accountIds` [query, array]: Single Account ID to filter by. Example: "225494730938493804". - `csvDelimiter` [query, string] (enum: ,, ;): "Optionally specify character to be used as CSV delimiter. Defaults to ",". Example: ",". @@ -136,6 +146,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks` + **Get CVE data** `operationId`: `_web_api_application-management_risks_get` @@ -144,6 +155,7 @@ Get the CVE vulnerability data for each CVE. Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `riskUpdatedDate__gt` [query, string]: Significant CVE updates after this timestamp. Example: "2018-02-27T04:49:26.257525Z". - `sortOrder` [query, string] (enum: asc, desc): Sort direction. Example: "asc". @@ -195,6 +207,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/aggregated-applications` + **Get Aggregated Applications With Risk** `operationId`: `_web_api_application-management_risks_aggregated-applications_get` @@ -203,6 +216,7 @@ Get data for all applications. Available with Ranger Insights license. Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `sortOrder` [query, string] (enum: asc, desc): Sort direction. Example: "asc". - `highestSeverities` [query, array]: Included highest severities. Example: "CRITICAL,HIGH". @@ -238,6 +252,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/aggregated-applications/export/csv` + **Aggregated Application Risk Data Export** `operationId`: `_web_api_application-management_risks_aggregated-applications_export_csv_get` @@ -246,6 +261,7 @@ Export aggregated application data to CSV. Available with Ranger Insights licens Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `highestSeverities` [query, array]: Included highest severities. Example: "CRITICAL,HIGH". - `detectionDate__gte` [query, string]: Application detection date after or at this timestamp. Example: "2018-02-27T04:49:26.257525Z". @@ -275,6 +291,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/applications` + **Get Applications With Risk** `operationId`: `_web_api_application-management_risks_applications_get` @@ -283,6 +300,7 @@ Get data for each version of all applications. Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `sortOrder` [query, string] (enum: asc, desc): Sort direction. Example: "asc". - `highestSeverities` [query, array]: Included highest severities. Example: "CRITICAL,HIGH". @@ -318,6 +336,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/applications/export/csv` + **Application Risk Data Export** `operationId`: `_web_api_application-management_risks_applications_export_csv_get` @@ -326,6 +345,7 @@ Export application data to CSV. Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `highestSeverities` [query, array]: Included highest severities. Example: "CRITICAL,HIGH". - `detectionDate__gte` [query, string]: Application detection date after or at this timestamp. Example: "2018-02-27T04:49:26.257525Z". @@ -355,6 +375,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/cves` + **Get Application CVEs** `operationId`: `_web_api_application-management_risks_cves_get` @@ -363,6 +384,7 @@ Get CVE data for a specific application. Use applicationIds query parameter to i Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `sortOrder` [query, string] (enum: asc, desc): Sort direction. Example: "asc". - `applicationIds` [query, array]: Included application versions by id. Example: "225494730938493804,225494730938493915". @@ -393,6 +415,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/cves/export/csv` + **Application CVE Data Export** `operationId`: `_web_api_application-management_risks_cves_export_csv_get` @@ -401,6 +424,7 @@ Export CVE data to CSV. Use applicationIds query parameter to include a single a Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `applicationIds` [query, array]: Included application versions by id. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: Single Account ID to filter by. Example: "225494730938493804". @@ -425,6 +449,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/endpoints` + **Get Endpoints For Vulnerable App** `operationId`: `_web_api_application-management_risks_endpoints_get` @@ -433,6 +458,7 @@ Get a list of all endpoints installed with a specific application that contains Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `statusMessage__contains` [query, array]: Free-text filter by status message (supports multiple values). Available with Ranger Insights. Example: "assigned to john,top priority". - `sortOrder` [query, string] (enum: asc, desc): Sort direction. Example: "asc". @@ -475,6 +501,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/endpoints/export/csv` + **Risk Endpoint Data Export** `operationId`: `_web_api_application-management_risks_endpoints_export_csv_get` @@ -483,6 +510,7 @@ Export endpoint data to CSV. Use applicationIds query parameter to include a sin Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `statusMessage__contains` [query, array]: Free-text filter by status message (supports multiple values). Available with Ranger Insights. Example: "assigned to john,top priority". - `applicationIds` [query, array]: Included application versions by id. Example: "225494730938493804,225494730938493915". @@ -519,6 +547,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/application-management/risks/export/csv` + **Risks Data Export** `operationId`: `_web_api_application-management_risks_export_csv_get` @@ -527,6 +556,7 @@ Export risks data to CSV. Required permissions: `Applications Page.viewRisks` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `riskUpdatedDate__gt` [query, string]: Significant CVE updates after this timestamp. Example: "2018-02-27T04:49:26.257525Z". - `detectionDate__gte` [query, string]: CVE detection date after or at this timestamp. Example: "2018-02-27T04:49:26.257525Z". @@ -572,6 +602,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/application-management/scan` + **Initiate scan** `operationId`: `_web_api_application-management_scan_post` @@ -580,6 +611,7 @@ Initiate application vulnerability scan. Required permissions: `Applications Page.scanVulnerabilities` Parameters: -- `body` [body, v2_1.application_management.application_management_schemas_ScanPostSchema]: + +- `body` [body, v2_1.application_management.application_management_schemas_ScanPostSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Management_Settings.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Management_Settings.md index fee2292..16283a0 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Management_Settings.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Management_Settings.md @@ -3,6 +3,7 @@ 2 endpoints. ## `GET /web/api/v2.1/application-management/settings` + **Get Application Management Settings** `operationId`: `_web_api_application-management_settings_get` @@ -11,6 +12,7 @@ Get Application Management settings. Required permissions: `Applications Page.view` Parameters: + - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `groupIds` [query, array]: Single Group ID to filter by. Example: "225494730938493804". - `accountIds` [query, array]: Single Account ID to filter by. Example: "225494730938493804". @@ -18,6 +20,7 @@ Parameters: Responses: 403 Insufficient permissions, 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/application-management/settings` + **Update Application Management Settings** `operationId`: `_web_api_application-management_settings_post` @@ -25,6 +28,7 @@ Update Application Management Settings Optional permissions: `Applications Page.scanVulnerabilities, Applications Page.changeVulnerabilitiesScanPolicy` Parameters: -- `body` [body, v2_1.application_management.settings_filters_ApplicationManagementSettingsSchema]: + +- `body` [body, v2_1.application_management.settings_filters_ApplicationManagementSettingsSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Risk.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Risk.md index aebdf91..16cd5c9 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Risk.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Risk.md @@ -3,6 +3,7 @@ 1 endpoints. ## `GET /web/api/v2.1/export/installed-applications` + **Export Applications** `operationId`: `_web_api_export_installed-applications_get` @@ -11,6 +12,7 @@ Export the list of applications installed on endpoints with Application Risk-ena Required permissions: `Applications Page.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Risk_Deprecated.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Risk_Deprecated.md index 06bb856..090f412 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Risk_Deprecated.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Application_Risk_Deprecated.md @@ -3,6 +3,7 @@ 2 endpoints. ## `GET /web/api/v2.1/installed-applications` + **[DEPRECATED] Get Applications** `operationId`: `_web_api_installed-applications_get` @@ -11,6 +12,7 @@ Get the applications, and their data (such as risk level), installed on endpoint Required permissions: `Applications Page.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -43,6 +45,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/installed-applications/cves` + **[DEPRECATED] Get CVEs** `operationId`: `_web_api_installed-applications_cves_get` @@ -51,6 +54,7 @@ Get known CVEs for applications that are installed on endpoints with Application Required permissions: `Applications Page.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Auto_Upgrade_Policy.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Auto_Upgrade_Policy.md index 280fb64..db82f14 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Auto_Upgrade_Policy.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Auto_Upgrade_Policy.md @@ -3,6 +3,7 @@ 13 endpoints. ## `GET /web/api/v2.1/upgrade-policy/all-policies-count` + **All Policies OS Count** `operationId`: `_web_api_upgrade-policy_all-policies-count_get` @@ -11,12 +12,14 @@ Get the number of all policies for each OS from the given scope and the inherite Required permissions: `Auto-Upgrade Policy.view` Parameters: + - `scopeLevel` [query, string] **required**: Scope level, one of 'account', 'group', 'site' or 'tenant' - `scopeId` [query, string]: Scope ID Responses: 200 Success, 400 Bad request ## `GET /web/api/v2.1/upgrade-policy/available-packages` + **Get Available Packages** `operationId`: `_web_api_upgrade-policy_available-packages_get` @@ -25,6 +28,7 @@ Get Available Packages Required permissions: `Auto-Upgrade Policy.view` Parameters: + - `scopeLevel` [query, string] **required**: Scope level, one of 'account', 'group', 'site' or 'tenant' - `scopeId` [query, string]: Scope ID - `osType` [query, string] **required**: OS type, one of 'linux', 'macos' or 'windows' @@ -33,17 +37,20 @@ Parameters: Responses: 200 Success, 400 Bad request ## `POST /web/api/v2.1/upgrade-policy/has-policy` + **Has Policy** `operationId`: `_web_api_upgrade-policy_has-policy_post` Has policy Parameters: + - `payload` [body, v2_1.models.HasPoliciesRequest] **required**: Policy payload Responses: 200 Success, 400 Bad request ## `GET /web/api/v2.1/upgrade-policy/parent-policies` + **Get Parent Policies** `operationId`: `_web_api_upgrade-policy_parent-policies_get` @@ -52,6 +59,7 @@ Get paginated and ordered parent policies by a given scope Required permissions: `Auto-Upgrade Policy.view` Parameters: + - `scopeLevel` [query, string] **required**: Scope level, one of 'account', 'group', 'site' or 'tenant' - `scopeId` [query, string]: Scope ID - `osType` [query, string] **required**: OS type, one of 'linux', 'macos' or 'windows' @@ -63,6 +71,7 @@ Parameters: Responses: 200 Success, 400 Bad request ## `GET /web/api/v2.1/upgrade-policy/policies` + **Get Policies** `operationId`: `_web_api_upgrade-policy_policies_get` @@ -71,6 +80,7 @@ Get paginated and ordered policies by a given scope Required permissions: `Auto-Upgrade Policy.view` Parameters: + - `scopeLevel` [query, string] **required**: Scope level, one of 'account', 'group', 'site' or 'tenant' - `scopeId` [query, string]: Scope ID - `osType` [query, string] **required**: OS type, one of 'linux', 'macos' or 'windows' @@ -82,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Bad request ## `POST /web/api/v2.1/upgrade-policy/policies` + **Deactivate Policies** `operationId`: `_web_api_upgrade-policy_policies_post` @@ -90,6 +101,7 @@ Deactivate all policies Required permissions: `Auto-Upgrade Policy.disableAllPolicies` Parameters: + - `scopeLevel` [query, string] **required**: Scope level, one of 'account', 'group', 'site' or 'tenant' - `scopeId` [query, string]: Scope ID - `osType` [query, string] **required**: OS type, one of 'linux', 'macos' or 'windows' @@ -97,6 +109,7 @@ Parameters: Responses: 200 Success, 400 Bad request ## `GET /web/api/v2.1/upgrade-policy/policies-count` + **Policies OS Count** `operationId`: `_web_api_upgrade-policy_policies-count_get` @@ -105,12 +118,14 @@ Get the number of policies for each OS, for a given scope level and id Required permissions: `Auto-Upgrade Policy.view` Parameters: + - `scopeLevel` [query, string] **required**: Scope level, one of 'account', 'group', 'site' or 'tenant' - `scopeId` [query, string]: Scope ID Responses: 200 Success, 400 Bad request ## `POST /web/api/v2.1/upgrade-policy/policy` + **Create Policy** `operationId`: `_web_api_upgrade-policy_policy_post` @@ -119,11 +134,13 @@ Add policy Required permissions: `Auto-Upgrade Policy.create` Parameters: + - `payload` [body, v2_1.models.Policy] **required**: Policy payload Responses: 200 Success, 400 Bad request ## `POST /web/api/v2.1/upgrade-policy/policy/{policyid}` + **Policy Action** `operationId`: `_web_api_upgrade-policy_policy_{policyid}_post` @@ -132,12 +149,14 @@ Perform action on a certain policy Required permissions: `Auto-Upgrade Policy.policyAction` Parameters: + - `payload` [body, v2_1.models.EndpointActionRequest] **required**: Policy payload - `policyid` [path, string] **required**: Policy id Responses: 200 Success, 400 Bad request ## `PUT /web/api/v2.1/upgrade-policy/policy/{policyid}` + **Update Policy** `operationId`: `_web_api_upgrade-policy_policy_{policyid}_put` @@ -146,12 +165,14 @@ Update existing policy Required permissions: `Auto-Upgrade Policy.edit` Parameters: + - `payload` [body, v2_1.models.CreatePolicyRequest] **required**: Policy payload - `policyid` [path, string] **required**: Policy id Responses: 200 Success, 400 Bad request ## `PUT /web/api/v2.1/upgrade-policy/policy/{policyid}/reset-retry-counter` + **Reset Policy Retry Counter** `operationId`: `_web_api_upgrade-policy_policy_{policyid}_reset-retry-counter_put` @@ -160,11 +181,13 @@ Reset the number of times an Agent upgrade will be retried if the original upgra Required permissions: `Auto-Upgrade Policy.edit` Parameters: + - `policyid` [path, string] **required**: Policy ID Responses: 200 Success, 400 Bad request ## `PUT /web/api/v2.1/upgrade-policy/reorder` + **Reorder Policies** `operationId`: `_web_api_upgrade-policy_reorder_put` @@ -173,11 +196,13 @@ Reorder policies Required permissions: `Auto-Upgrade Policy.edit` Parameters: + - `payload` [body, v2_1.models.ReorderPolicyRequest] **required**: Policy payload Responses: 200 Success, 400 Bad request ## `PUT /web/api/v2.1/upgrade-policy/set-inheriting` + **Set Scope Inheriting** `operationId`: `_web_api_upgrade-policy_set-inheriting_put` @@ -186,6 +211,7 @@ Set Scope Inheriting Required permissions: `Auto-Upgrade Policy.edit` Parameters: + - `payload` [body, v2_1.models.ScopeInheritanceRequest] **required**: payload Responses: 200 Success, 400 Bad request diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Cloud_Funnel.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Cloud_Funnel.md index b4d4bfc..6e1fb60 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Cloud_Funnel.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Cloud_Funnel.md @@ -3,6 +3,7 @@ 8 endpoints. ## `GET /web/api/v2.1/cloud-funnel/assume-role-external-id` + **Get AWS assume role external ID.** `operationId`: `_web_api_cloud-funnel_assume-role-external-id_get` @@ -11,12 +12,14 @@ Get the AWS assume role external ID. Required permissions: `Cloud Funnel.view` Parameters: + - `accountId` [query, string]: Account id. Example: "225494730938493804". - `siteId` [query, string]: Site id. Example: "225494730938493804". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/cloud-funnel/estimator` + **Get estimate size of events** `operationId`: `_web_api_cloud-funnel_estimator_get` @@ -25,11 +28,13 @@ Get estimate size of events in the bucket. You need the estimator ID which can b Required permissions: `Cloud Funnel.view` Parameters: + - `estimatorId` [query, string] **required**: Estimator query id. Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/cloud-funnel/estimator` + **Create Estimator ID** `operationId`: `_web_api_cloud-funnel_estimator_post` @@ -38,11 +43,13 @@ Create estimator ID. This is needed to run the API "Get Estimate Size Of Events" Required permissions: `Cloud Funnel.view` Parameters: -- `body` [body, v2_1.cloud_funnel.schemas_InitEstimatorSchema]: + +- `body` [body, v2_1.cloud_funnel.schemas_InitEstimatorSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/cloud-funnel/onboarding` + **Delete cloud funnel rule** `operationId`: `_web_api_cloud-funnel_onboarding_delete` @@ -51,11 +58,13 @@ Deletes cloud funnel onboarding rule. Required permissions: `Cloud Funnel.delete` Parameters: -- `body` [body, v2_1.cloud_funnel.schemas_OnboardingDeleteSchema]: + +- `body` [body, v2_1.cloud_funnel.schemas_OnboardingDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/cloud-funnel/onboarding` + **Get cloud funnel rule** `operationId`: `_web_api_cloud-funnel_onboarding_get` @@ -64,12 +73,14 @@ Gets cloud funnel onboarding rule details Required permissions: `Cloud Funnel.view` Parameters: + - `accountId` [query, string]: Account id. Example: "225494730938493804". - `siteId` [query, string]: Site id. Example: "225494730938493804". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/cloud-funnel/onboarding` + **Post onboarding cloud funnel** `operationId`: `_web_api_cloud-funnel_onboarding_post` @@ -78,11 +89,13 @@ Post onboarding cloud funnel rule. Required permissions: `Cloud Funnel.create, Cloud Funnel.edit` Parameters: -- `body` [body, v2_1.cloud_funnel.schemas_OnboardingPostSchema]: + +- `body` [body, v2_1.cloud_funnel.schemas_OnboardingPostSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/cloud-funnel/validate-bucket-permissions` + **Validate Bucket** `operationId`: `_web_api_cloud-funnel_validate-bucket-permissions_post` @@ -91,11 +104,13 @@ Validates bucket permissions. Required permissions: `Cloud Funnel.create, Cloud Funnel.edit` Parameters: -- `body` [body, v2_1.cloud_funnel.schemas_BucketValidationSchema]: + +- `body` [body, v2_1.cloud_funnel.schemas_BucketValidationSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/cloud-funnel/validate-query` + **Validate Query** `operationId`: `_web_api_cloud-funnel_validate-query_post` @@ -104,6 +119,7 @@ Verifies that a query is valid before using it as filterfor a Cloud Funnel onboa Required permissions: `Cloud Funnel.create, Cloud Funnel.edit` Parameters: -- `body` [body, v2_1.cloud_funnel.schemas_QueryValidationSchema]: + +- `body` [body, v2_1.cloud_funnel.schemas_QueryValidationSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Cloud_Resources.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Cloud_Resources.md index a4f0733..9e7004d 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Cloud_Resources.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Cloud_Resources.md @@ -3,6 +3,7 @@ 2 endpoints. ## `GET /web/api/v2.1/cloudnative/cloud-rogues` + **Get cloud rogue resources** `operationId`: `_web_api_cloudnative_cloud-rogues_get` @@ -11,6 +12,7 @@ Returns the cloud rogue resources for given filter Required permissions: `cloudRogues.view` Parameters: + - `cloudProviderAccountName` [query, array]: Filter by cloud account (supports multiple values) - `sortBy` [query, string] (enum: id, createdTime, resourceType, name, region, virtualNetworkId, imageId, osType, cloudProviderAccountName, cloudProviderAccountId, cloudProviderOrganization, cloudProviderName): The column to sort the results by. Example: "id". - `name__contains` [query, array]: Free-text filter by resource name (supports multiple values) @@ -36,6 +38,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/cloudnative/cloud-rogues/export` + **Export cloud rogue resources to csv (default) or json** `operationId`: `_web_api_cloudnative_cloud-rogues_export_get` @@ -44,6 +47,7 @@ Returns the results for given cloud rogues filter in a csv (default) or json for Required permissions: `cloudRogues.view` Parameters: + - `cloudProviderAccountName` [query, array]: Filter by cloud account (supports multiple values) - `sortBy` [query, string] (enum: id, createdTime): The column to sort the results by. Example: "id". - `name__contains` [query, array]: Free-text filter by resource name (supports multiple values) diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Config_Overrides.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Config_Overrides.md index f986eca..1df4a19 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Config_Overrides.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Config_Overrides.md @@ -3,6 +3,7 @@ 5 endpoints. ## `DELETE /web/api/v2.1/config-override` + **Delete Config Overrides** `operationId`: `_web_api_config-override_delete` @@ -11,11 +12,13 @@ Delete overrides value. To get the required IDs, run "config-override". Required permissions: `Policy Override.delete` Parameters: -- `body` [body, config_overrides_ConfigOverrideDeleteSchema]: + +- `body` [body, config_overrides_ConfigOverrideDeleteSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/config-override` + **Get Config Overrides** `operationId`: `_web_api_config-override_get` @@ -24,6 +27,7 @@ There are different ways to override the configuration of an Agent, and the prio Required permissions: `Policy Override.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -52,6 +56,7 @@ Parameters: Responses: 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/config-override` + **Create Config Override** `operationId`: `_web_api_config-override_post` @@ -60,11 +65,13 @@ Override the configuration of Agents that match the filter. Best practice: Run Required permissions: `Policy Override.create` Parameters: -- `body` [body, config_overrides_CreateConfigOverrideSchema]: + +- `body` [body, config_overrides_CreateConfigOverrideSchema]: Responses: 400 Invalid user input received. See error details for further i, 404 Scope not found., 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/config-override/{override_id}` + **Delete Config Override** `operationId`: `_web_api_config-override_{override_id}_delete` @@ -73,11 +80,13 @@ Delete an override value. To get the required ID, run "config-override". Required permissions: `Policy Override.delete` Parameters: + - `override_id` [path, string] **required**: Config override object ID. Example: "225494730938493804". Responses: 404 Override not found., 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/config-override/{override_id}` + **Update Config Override** `operationId`: `_web_api_config-override_{override_id}_put` @@ -86,7 +95,8 @@ Use this command to change the value of one configuration value. To get the requ Required permissions: `Policy Override.edit` Parameters: + - `override_id` [path, string] **required**: Config override object ID. Example: "225494730938493804". -- `body` [body, config_overrides_PutConfigOverrideSchema]: +- `body` [body, config_overrides_PutConfigOverrideSchema]: Responses: 404 Override not found., 403 Insufficient permissions., 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Custom_Detection_Rule.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Custom_Detection_Rule.md index ada6349..92fe6c2 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Custom_Detection_Rule.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Custom_Detection_Rule.md @@ -3,6 +3,7 @@ 6 endpoints. ## `DELETE /web/api/v2.1/cloud-detection/rules` + **Delete Rules** `operationId`: `_web_api_cloud-detection_rules_delete` @@ -11,11 +12,13 @@ Deletes Custom Detection Rules that match a filter. Required permissions: `Custom Rules.manage` Parameters: -- `body` [body, v2_1.rules.schemas_RuleDeleteSchema]: + +- `body` [body, v2_1.rules.schemas_RuleDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/cloud-detection/rules` + **Get Rules** `operationId`: `_web_api_cloud-detection_rules_get` @@ -27,6 +30,7 @@ Get a list of Custom Detection Rules for a given scope.
Note: You can creat > If you ever want to claim "this tenant has no scheduled detections", you MUST have called this endpoint with `isLegacy=false` first. Without it, the absence of evidence is meaningless. > > Correct invocations (re-verified 2026-05): +> > - All rules: `GET /cloud-detection/rules?isLegacy=false&limit=200` > - Only scheduled: `GET /cloud-detection/rules?isLegacy=false&queryType=scheduled&limit=200` > - Only events: `GET /cloud-detection/rules?queryType=events&limit=200` (isLegacy not needed) @@ -37,6 +41,7 @@ Get a list of Custom Detection Rules for a given scope.
Note: You can creat Required permissions: `Custom Rules.view` Parameters: + - `statuses` [query, array]: Statuses. Example: "Activating". - `name__contains` [query, array]: Free-text filter by rule name. You can enter multiple values, separated by commas. Example: "Service Pack 1". - `description__contains` [query, array]: Free-text filter by rule description. You can enter multiple values, separated by commas. Example: "Service Pack 1". @@ -79,6 +84,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/cloud-detection/rules` + **Create Rule** `operationId`: `_web_api_cloud-detection_rules_post` @@ -88,11 +94,13 @@ Required permissions: `Custom Rules.manage` Optional permissions: `Threats.markSuspicious, Threats.markThreat, Endpoints.disconnectFromNetwork` Parameters: -- `body` [body, v2_1.rules.schemas_PostRuleSchema]: + +- `body` [body, v2_1.rules.schemas_PostRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/cloud-detection/rules/disable` + **Disable Rules** `operationId`: `_web_api_cloud-detection_rules_disable_put` @@ -101,11 +109,13 @@ Disable Custom Detection Rules based on a filter. Required permissions: `Custom Rules.manage` Parameters: -- `body` [body, v2_1.rules.schemas_FilterRuleSchema]: + +- `body` [body, v2_1.rules.schemas_FilterRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/cloud-detection/rules/enable` + **Activate Rules** `operationId`: `_web_api_cloud-detection_rules_enable_put` @@ -114,11 +124,13 @@ Activate Custom Detection Rules based on a filter. Required permissions: `Custom Rules.manage` Parameters: -- `body` [body, v2_1.rules.schemas_FilterRuleSchema]: + +- `body` [body, v2_1.rules.schemas_FilterRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/cloud-detection/rules/{rule_id}` + **Update Rule** `operationId`: `_web_api_cloud-detection_rules_{rule_id}_put` @@ -128,8 +140,9 @@ Required permissions: `Custom Rules.manage` Optional permissions: `Threats.markSuspicious, Threats.markThreat, Endpoints.disconnectFromNetwork` Parameters: + - `rule_id` [path, string] **required**: The Rule ID in the URL path. Example: "225494730938493804". -- `body` [body, v2_1.rules.schemas_PostRuleSchema]: +- `body` [body, v2_1.rules.schemas_PostRuleSchema]: Responses: 404 Custom Detection rule not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Datalake_Unified_Actions.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Datalake_Unified_Actions.md index c04aafa..7410c29 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Datalake_Unified_Actions.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Datalake_Unified_Actions.md @@ -3,6 +3,7 @@ 4 endpoints. ## `POST /web/api/v2.1/xdr/action-controller/fetch-surface-ids` + **Fetch surface ids in case of select all with filters** `operationId`: `_web_api_xdr_action-controller_fetch-surface-ids_post` @@ -11,14 +12,16 @@ Fetch surface ids in case of select all with filters Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.action_controller.schemas_FetchSurfaceIdsRequestSchema]: +- `body` [body, v2_1.action_controller.schemas_FetchSurfaceIdsRequestSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/action-controller/fetch-unified-actions` + **Get Available Actions by Asset/Entity Type** `operationId`: `_web_api_xdr_action-controller_fetch-unified-actions_post` @@ -27,14 +30,16 @@ Get Available Actions by Asset/Entity Type Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.action_controller.schemas_AffectedEntitiesSchema]: +- `body` [body, v2_1.action_controller.schemas_AffectedEntitiesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/action-controller/perform-unified-action` + **Perform an Action on selected assets/entities** `operationId`: `_web_api_xdr_action-controller_perform-unified-action_post` @@ -43,14 +48,16 @@ Perform an Action on selected assets/entities Required permissions: `XDR Inventory.create, XDR Inventory.edit` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.action_controller.schemas_PerformActionRequestSchema]: +- `body` [body, v2_1.action_controller.schemas_PerformActionRequestSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/action-controller/perform-unified-action/notify` + **Internal api only to notify action was triggered without actually performing it** `operationId`: `_web_api_xdr_action-controller_perform-unified-action_notify_post` @@ -59,9 +66,10 @@ Internal api only to notify action was triggered without actually performing it Required permissions: `XDR Inventory.create, XDR Inventory.edit` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.action_controller.schemas_PerformActionNotificationSchema]: +- `body` [body, v2_1.action_controller.schemas_PerformActionNotificationSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Deep_Visibility.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Deep_Visibility.md index 16a5bf4..0136098 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Deep_Visibility.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Deep_Visibility.md @@ -5,6 +5,7 @@ **All query endpoints in this tag are deprecated and sunset on 2027-02-15.** Use the Long Running Query (LRQ) tag instead: `POST /sdl/v2/api/queries` with `queryType="LOG"` (S1QL) or `queryType="PQ"` (PowerQuery), poll `GET /sdl/v2/api/queries/{id}` echoing the `X-Dataset-Query-Forward-Tag` response header, DELETE when done. Auth is Bearer, not ApiToken. See `tags/Long_Running_Query.md` and the `powerquery` skill for the canonical runner. `GET /dv/fetch-file` (file download) is the only endpoint in this tag that is not deprecated. ## `POST /web/api/v2.1/dv/cancel-query` + **[DEPRECATED] Cancel Running Query** `operationId`: `_web_api_dv_cancel-query_post` @@ -13,11 +14,13 @@ Stop a Deep Visibility Query by queryId. The body is {"queryID":"string_ID"}. Ge Required permissions: `SDL Data.viewEdr` Parameters: -- `body` [body, deep_visibility.deep_visibility_v2_schemas_DeepVisibilityQueryIdRequestSchema]: + +- `body` [body, deep_visibility.deep_visibility_v2_schemas_DeepVisibilityQueryIdRequestSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/dv/events` + **[DEPRECATED] Get Events** `operationId`: `_web_api_dv_events_get` @@ -26,6 +29,7 @@ Get all Deep Visibility events from a queryId. You can use this command to send Required permissions: `SDL Data.viewEdr` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Should be used instead of skip. cursor currently supports sort by with createdAt, pid, processStartTime @@ -37,6 +41,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/dv/events/pq` + **[DEPRECATED] Create a Power Query and Get QueryId** `operationId`: `_web_api_dv_events_pq_post` @@ -45,11 +50,13 @@ Start a Deep Visibility Power Query, get back status and potential results (ping Required permissions: `SDL Data.viewEdr` Parameters: -- `body` [body, deep_visibility.deep_visibility_v2_schemas_DeepVisibilityPQRequestSchema]: + +- `body` [body, deep_visibility.deep_visibility_v2_schemas_DeepVisibilityPQRequestSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/dv/events/pq-ping` + **[DEPRECATED] Ping a Power Query if results haven't been retrieved** `operationId`: `_web_api_dv_events_pq-ping_get` @@ -58,11 +65,13 @@ Ping a Deep Visibility Power Query using the queryId if results have not returne Required permissions: `SDL Data.viewEdr` Parameters: + - `queryId` [query, string]: QueryId query param Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/dv/events/{event_type}` + **[DEPRECATED] Get Events By Type** `operationId`: `_web_api_dv_events_{event_type}_get` @@ -71,6 +80,7 @@ Get Deep Visibility results from the query that matches the given event type. Va Required permissions: `SDL Data.viewEdr` Parameters: + - `event_type` [path, string] **required**: Event type for Autocomplete - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". @@ -83,6 +93,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/dv/fetch-file` + **Download source process file** `operationId`: `_web_api_dv_fetch-file_get` @@ -91,11 +102,13 @@ Download the source process file associated with a Deep Visibility event. Required permissions: `Deep Visibility.fileFetch` Parameters: + - `downloadToken` [query, string] **required**: Download token Responses: 404 File not found, 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/dv/init-query` + **[DEPRECATED] Create Query and Get QueryId** `operationId`: `_web_api_dv_init-query_post` @@ -104,11 +117,13 @@ Start a Deep Visibility Query and get the queryId. You can use the queryId for o Required permissions: `SDL Data.viewEdr` Parameters: -- `body` [body, deep_visibility.deep_visibility_v2_schemas_DeepVisibilityApiRequestSchema]: + +- `body` [body, deep_visibility.deep_visibility_v2_schemas_DeepVisibilityApiRequestSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/dv/process-state` + **[DEPRECATED] Get Process State** `operationId`: `_web_api_dv_process-state_get` @@ -117,6 +132,7 @@ Get details of all Deep Visibility processes from a queryId.To get the ID from " Required permissions: `SDL Data.viewEdr` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -127,6 +143,7 @@ Parameters: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/dv/query-status` + **[DEPRECATED] Get Query Status** `operationId`: `_web_api_dv_query-status_get` @@ -135,6 +152,7 @@ Get that status of a Deep Visibility Query. When the status is FINISHED, you can Required permissions: `SDL Data.viewEdr` Parameters: + - `queryId` [query, string] **required**: QueryId obtained when creating a query under Create Query. Example: "q1xx2xx3". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Default_Reports.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Default_Reports.md index 99b10d5..ef3852b 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Default_Reports.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Default_Reports.md @@ -3,6 +3,7 @@ 9 endpoints. ## `GET /web/api/v2.1/report-tasks` + **Get Default Report Tasks** `operationId`: `_web_api_report-tasks_get` @@ -11,6 +12,7 @@ Get the tasks that were done to generate default reports and to schedule future Required permissions: `Reports Page.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -36,6 +38,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/report-tasks` + **Create Default Report Task** `operationId`: `_web_api_report-tasks_post` @@ -44,11 +47,13 @@ Create a task to generate a default report immediately, one time in the future, Required permissions: `Reports Page.create` Parameters: -- `body` [body, reports_ReportTasksPostSchema]: + +- `body` [body, reports_ReportTasksPostSchema]: Responses: 404 Validation errors, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/report-tasks/{task_id}` + **Update Default Report Task** `operationId`: `_web_api_report-tasks_{task_id}_put` @@ -57,12 +62,14 @@ Update the default report task of the given ID. Default Reports require Reports Required permissions: `Reports Page.edit` Parameters: + - `task_id` [path, string] **required**: Task ID. Example: "225494730938493804". -- `body` [body, reports_ReportTasksPutSchema]: +- `body` [body, reports_ReportTasksPutSchema]: Responses: 404 Validation errors, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/reports` + **Get Default Reports** `operationId`: `_web_api_reports_get` @@ -71,6 +78,7 @@ Get the default reports that match the filter and the data of the reports. Defau Required permissions: `Reports Page.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -97,6 +105,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/reports/delete-reports` + **Delete Default Reports** `operationId`: `_web_api_reports_delete-reports_post` @@ -105,11 +114,13 @@ Delete the default reports that match the filter. Default Reports require Report Required permissions: `Reports Page.delete` Parameters: -- `body` [body, reports_ReportDeleteSchema]: + +- `body` [body, reports_ReportDeleteSchema]: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/reports/delete-tasks` + **Delete Default Report Tasks** `operationId`: `_web_api_reports_delete-tasks_post` @@ -118,11 +129,13 @@ You can schedule a default report to be generated on a routine. Default Reports Required permissions: `Reports Page.delete` Parameters: -- `body` [body, reports_ReportTaskDeleteSchema]: + +- `body` [body, reports_ReportTaskDeleteSchema]: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/reports/insights/types` + **Get Default Insight Reports** `operationId`: `_web_api_reports_insights_types_get` @@ -131,6 +144,7 @@ Get the Insight Report types for Default Reports. Default Reports require Report Required permissions: `Reports Page.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -139,6 +153,7 @@ Parameters: Responses: 404 Package not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/reports/{report_id}/{report_format}` + **Download Default Report** `operationId`: `_web_api_reports_{report_id}_{report_format}_get` @@ -147,12 +162,14 @@ When the Management generates a default report, it is uploaded to the Management Required permissions: `Reports Page.view` Parameters: + - `report_id` [path, string] **required**: Report ID. Example: "225494730938493804". - `report_format` [path, string] **required** (enum: pdf, html): Report format. Example: "pdf". Responses: 404 Report not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/sentinelonerss` + **S1 RSS Feed** `operationId`: `_web_api_sentinelonerss_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Device_Control.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Device_Control.md index fe44d62..91c692f 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Device_Control.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Device_Control.md @@ -3,6 +3,7 @@ 12 endpoints. ## `DELETE /web/api/v2.1/device-control` + **Delete Rules** `operationId`: `_web_api_device-control_delete` @@ -11,11 +12,13 @@ Delete Device Control rules that match the filter. Required permissions: `Device Control.delete` Parameters: -- `body` [body, device_control.schemas_RuleDeleteSchema]: + +- `body` [body, device_control.schemas_RuleDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/device-control` + **Get Device Rules** `operationId`: `_web_api_device-control_get` @@ -24,6 +27,7 @@ Get the Device Control rules of a specified Account, Site, Group or Global (tena Required permissions: `Device Control.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -66,27 +70,31 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/device-control` + **Create Device Control Rule** `operationId`: `_web_api_device-control_post` -Use this command to create a new Device Control rule. These rules allow or block devices, based on device identifiers. Rules apply to a scope: Global (tenant), Account, Site, or Group. To learn details of the fields, see https://support.sentinelone.com/hc/en-us/articles/360023338494.
Recommended: Before you begin, see Device Control Known Limitations: https://support.sentinelone.com/hc/en-us/articles/360021104114.
Device Control requires Control SKU. Linux Agents do not support Device Control. +Use this command to create a new Device Control rule. These rules allow or block devices, based on device identifiers. Rules apply to a scope: Global (tenant), Account, Site, or Group. To learn details of the fields, see .
Recommended: Before you begin, see Device Control Known Limitations: .
Device Control requires Control SKU. Linux Agents do not support Device Control. Required permissions: `Device Control.create` Parameters: -- `body` [body, device_control.schemas_PostDeviceSchema]: + +- `body` [body, device_control.schemas_PostDeviceSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/device-control/configuration` + **Get Configuration** `operationId`: `_web_api_device-control_configuration_get` -Get Device Control configuration for a given scope.
To filter the results for a scope:
* Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given.To filter the results for a scope:
*Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given.. Required permissions: `Device Control.edit` Parameters: + - `rule_id` [path, string] **required**: Rule ID. Example: "225494730938493804". -- `body` [body, device_control.schemas_PutDeviceSchema]: +- `body` [body, device_control.schemas_PutDeviceSchema]: Responses: 404 Device rule not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Dynamic_tag_rules.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Dynamic_tag_rules.md index 013b985..e9c555f 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Dynamic_tag_rules.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Dynamic_tag_rules.md @@ -3,6 +3,7 @@ 5 endpoints. ## `DELETE /web/api/v2.1/xdr/assets/tags/rules` + **Delete tag rules** `operationId`: `_web_api_xdr_assets_tags_rules_delete` @@ -11,6 +12,7 @@ Delete tag rules Required permissions: `XDR Inventory.delete` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `ids` [query, array]: The list of tag rule ID identifiers to be removed. @@ -19,6 +21,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/tags/rules` + **Get all tags rules** `operationId`: `_web_api_xdr_assets_tags_rules_get` @@ -27,6 +30,7 @@ Get all tags rules Required permissions: `XDR Inventory.view` Parameters: + - `createdByEmail` [query, array]: Filter by created by emails of the tag rules. - `tagIds` [query, array]: The list of tag identifiers that the rule is associated with. - `sortOrder` [query, string] (enum: asc, desc): Sort direction @@ -52,6 +56,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/tags/rules` + **Create new tag rule** `operationId`: `_web_api_xdr_assets_tags_rules_post` @@ -60,14 +65,16 @@ Create new tag rule Required permissions: `XDR Inventory.create` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.tags.rules.schemas_TagRuleSchema]: +- `body` [body, v2_1.inventory.tags.rules.schemas_TagRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/xdr/assets/tags/rules` + **Update tag rule** `operationId`: `_web_api_xdr_assets_tags_rules_put` @@ -76,14 +83,16 @@ Update tag rule Required permissions: `XDR Inventory.edit` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.tags.rules.schemas_TagRuleSchema]: +- `body` [body, v2_1.inventory.tags.rules.schemas_TagRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/tags/rules/test` + **Check how many assets this tag rule matches** `operationId`: `_web_api_xdr_assets_tags_rules_test_post` @@ -92,6 +101,7 @@ Check how many assets this tag rule matches Required permissions: `XDR Inventory.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". - `groupIds` [query, array]: List of Group IDs to filter by - `sortOrder` [query, string] (enum: asc, desc): Sort direction @@ -102,6 +112,6 @@ Parameters: - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. - `skipCount` [query, boolean]: If true, total number of items will not be calculated, which speeds up execution time. - `sortBy` [query, string] (enum: s1UpdatedAt, name): The column to sort the results by. -- `body` [body, v2_1.inventory.tags.rules.schemas_TagRuleSchema]: +- `body` [body, v2_1.inventory.tags.rules.schemas_TagRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Exclusions_and_Blocklist.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Exclusions_and_Blocklist.md index 0bb709e..7599921 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Exclusions_and_Blocklist.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Exclusions_and_Blocklist.md @@ -3,6 +3,7 @@ 16 endpoints. ## `DELETE /web/api/v2.1/exclusions` + **Delete Exclusions** `operationId`: `_web_api_exclusions_delete` @@ -11,19 +12,22 @@ Every Exclusion opens a possible security hole. If you decide that an Exclusion Required permissions: `Exclusions.delete` Parameters: -- `body` [body, exclusions.schemas_DeleteExclusionSchema]: + +- `body` [body, exclusions.schemas_DeleteExclusionSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/exclusions` + **Get Exclusions** `operationId`: `_web_api_exclusions_get` -Get a list of all the Exclusions that match the filter.
Note: To filter the results for a scope:
* Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given. +Get a list of all the Exclusions that match the filter.
Note: To filter the results for a scope:
*Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given. Required permissions: `Exclusions.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -78,19 +82,22 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/exclusions` + **Create Exclusion** `operationId`: `_web_api_exclusions_post` -Create Exclusions to make your Agents suppress alerts and mitigation for items that you consider to be benign or which you require for interoperability.
IMPORTANT! Every Exclusion is a possible security hole. Do not create Exclusions unless you are sure this hash, path, certificate signer, file type, or browser is always benign.
Of course, if you can make the Exclusion by its hash or path, that is much more secure than excluding all detections of a specific signer, file type, or browser. We do not recommend the last types for Exclusions on production endpoints. These Exclusions might be helpful in a lab or pentester group. When you create an Exclusion, make sure you set the filter to the smallest possible scope. For example, if you can exclude security for this item on a group, do not enter values for siteIds or accountIds.
We recommend that you read "Not Recommended Exclusions: https://support.sentinelone.com/hc/en-us/articles/360007532894
and Best Practices for Exclusions: https://support.sentinelone.com/hc/en-us/articles/360008709014 +Create Exclusions to make your Agents suppress alerts and mitigation for items that you consider to be benign or which you require for interoperability.
IMPORTANT! Every Exclusion is a possible security hole. Do not create Exclusions unless you are sure this hash, path, certificate signer, file type, or browser is always benign.
Of course, if you can make the Exclusion by its hash or path, that is much more secure than excluding all detections of a specific signer, file type, or browser. We do not recommend the last types for Exclusions on production endpoints. These Exclusions might be helpful in a lab or pentester group. When you create an Exclusion, make sure you set the filter to the smallest possible scope. For example, if you can exclude security for this item on a group, do not enter values for siteIds or accountIds.
We recommend that you read "Not Recommended Exclusions:
and Best Practices for Exclusions: Required permissions: `Exclusions.create` Parameters: -- `body` [body, exclusions.schemas_PostExclusionSchema]: + +- `body` [body, exclusions.schemas_PostExclusionSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/exclusions` + **Update Exclusions** `operationId`: `_web_api_exclusions_put` @@ -99,11 +106,13 @@ Change the properties of an Exclusion through the data fields. To get the origin Required permissions: `Exclusions.edit` Parameters: -- `body` [body, exclusions.schemas_PutExclusionSchema]: + +- `body` [body, exclusions.schemas_PutExclusionSchema]: Responses: 404 Exclusion not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/exclusions/import` + **Import Exclusions** `operationId`: `_web_api_exclusions_import_post` @@ -112,12 +121,14 @@ Upload a CSV file that contains exclusion entries to import to a scope in your M Required permissions: `Exclusions.create` Parameters: + - `filter` [formData, string]: The details of the scope where the entities will be imported, for example: For Global - '{"tenant":true}' For an Account - '{"accountIds": ["225494730938493804"]}' For a Site - '{"siteIds": ["225494730938493804"]}' For a Group - '{"groupIds": ["225494730938493804"]}' - `file` [formData, file] **required**: The input CSV file Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/exclusions/report/{report_id}` + **Get Exclusion Import Validation Report** `operationId`: `_web_api_exclusions_report_{report_id}_get` @@ -126,24 +137,28 @@ Get the Validation Report generated for the import to help you fix entries that Required permissions: `Exclusions.view` Parameters: + - `report_id` [path, string] **required**: The ID of the requested Validation Report. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/exclusions/validate` + **Validate Exclusion Item** `operationId`: `_web_api_exclusions_validate_post` -Check if an exclusion is on the list of SentinelOne items that are "Not Allowed" or "Not Recommended". This API returns one of the following statuses:
* Not Recommended: This item is not recommended by SentinelOne because it decreases security. For example, If you accidentally exclude a path that is too broad, malware can enter your environment.
* Not Allowed: This exclusion can harm the product and lead to unexpected functionality. From version North Pole SP3 you are prevented from creating Not Allowed exclusions.* None: This item is not on the list of SentinelOne items that are "Not Allowed" or "Not Recommended". +Check if an exclusion is on the list of SentinelOne items that are "Not Allowed" or "Not Recommended". This API returns one of the following statuses:
*Not Recommended: This item is not recommended by SentinelOne because it decreases security. For example, If you accidentally exclude a path that is too broad, malware can enter your environment.
* Not Allowed: This exclusion can harm the product and lead to unexpected functionality. From version North Pole SP3 you are prevented from creating Not Allowed exclusions.* None: This item is not on the list of SentinelOne items that are "Not Allowed" or "Not Recommended". Required permissions: `Exclusions.create` Parameters: -- `body` [body, exclusions.schemas_ValidateExclusionSchema]: + +- `body` [body, exclusions.schemas_ValidateExclusionSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/export/exclusions` + **Export Exclusions** `operationId`: `_web_api_export_exclusions_get` @@ -152,6 +167,7 @@ Get a csv of all the items in the Exclusions that match the filter.
Note: To Required permissions: `Exclusions.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -199,6 +215,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/export/restrictions` + **Export Blocklist** `operationId`: `_web_api_export_restrictions_get` @@ -207,6 +224,7 @@ Get a csv of all the items in the Blocklist that match the filter.
Note: To Required permissions: `Blacklist.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -241,6 +259,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/restrictions` + **Delete Blocklist Item** `operationId`: `_web_api_restrictions_delete` @@ -249,19 +268,22 @@ Agents immediately identify files on the blocklist and block them from executing Required permissions: `Blacklist.delete` Parameters: -- `body` [body, exclusions.schemas_DeleteRestrictionSchema]: + +- `body` [body, exclusions.schemas_DeleteRestrictionSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/restrictions` + **Get Blocklist** `operationId`: `_web_api_restrictions_get` -Get a list of all the items in the Blocklist that match the filter.
To filter the results for a scope:
* Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given. +Get a list of all the items in the Blocklist that match the filter.
To filter the results for a scope:
*Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given. Required permissions: `Blacklist.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -304,6 +326,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/restrictions` + **Create Blocklist Item** `operationId`: `_web_api_restrictions_post` @@ -312,11 +335,13 @@ Create a blocklist item for a SHA1 or SHA256 hash or both, for the scopes you en Required permissions: `Blacklist.create` Parameters: -- `body` [body, exclusions.schemas_PostRestrictionSchema]: + +- `body` [body, exclusions.schemas_PostRestrictionSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/restrictions` + **Update Blocklist Item** `operationId`: `_web_api_restrictions_put` @@ -325,11 +350,13 @@ Change the properties of a Blocklist item through the data fields. To get the or Required permissions: `Blacklist.edit` Parameters: -- `body` [body, exclusions.schemas_PutRestrictionSchema]: + +- `body` [body, exclusions.schemas_PutRestrictionSchema]: Responses: 404 Blocklist not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/restrictions/import` + **Import Blocklist Items** `operationId`: `_web_api_restrictions_import_post` @@ -338,12 +365,14 @@ Upload a CSV file that contains blocklist entries to import to a scope in your M Required permissions: `Blacklist.create` Parameters: + - `filter` [formData, string]: The details of the scope where the entities will be imported, for example: For Global - '{"tenant":true}' For an Account - '{"accountIds": ["225494730938493804"]}' For a Site - '{"siteIds": ["225494730938493804"]}' For a Group - '{"groupIds": ["225494730938493804"]}' - `file` [formData, file] **required**: The input CSV file Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/restrictions/report/{report_id}` + **Get Blocklist Import Validation Report** `operationId`: `_web_api_restrictions_report_{report_id}_get` @@ -352,19 +381,22 @@ Get the Validation Report generated for the import to help you fix entries that Required permissions: `Exclusions.view` Parameters: + - `report_id` [path, string] **required**: The ID of the requested Validation Report. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/restrictions/validate` + **Validate Blocklist Item** `operationId`: `_web_api_restrictions_validate_post` -Check if a hash is on the list of SentinelOne items that are "Not Allowed" or "Not Recommended". This API returns one of the following statuses:
* Not Recommended: This item is not recommended by SentinelOne because it decreases security.
* Not Allowed: This item can harm the product and lead to unexpected functionality. From version North Pole SP3 you are prevented from creating Not Allowed blocklist item. * None: This item is not on the list of SentinelOne items that are "Not Allowed" or "Not Recommended". +Check if a hash is on the list of SentinelOne items that are "Not Allowed" or "Not Recommended". This API returns one of the following statuses:
*Not Recommended: This item is not recommended by SentinelOne because it decreases security.
* Not Allowed: This item can harm the product and lead to unexpected functionality. From version North Pole SP3 you are prevented from creating Not Allowed blocklist item. * None: This item is not on the list of SentinelOne items that are "Not Allowed" or "Not Recommended". Required permissions: `Blacklist.create` Parameters: -- `body` [body, exclusions.schemas_ValidateRestrictionSchema]: + +- `body` [body, exclusions.schemas_ValidateRestrictionSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Exclusions_v2_1.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Exclusions_v2_1.md index 6aab56f..3683c4e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Exclusions_v2_1.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Exclusions_v2_1.md @@ -3,25 +3,29 @@ 8 endpoints. ## `DELETE /web/api/v2.1/unified-exclusions` + **Delete Exclusions** `operationId`: `_web_api_unified-exclusions_delete` Required permissions: `Exclusions.delete` Parameters: -- `body` [body, exclusions.delete_schema_UnifiedExclusionSchemaDeleteRequest]: + +- `body` [body, exclusions.delete_schema_UnifiedExclusionSchemaDeleteRequest]: Responses: 403 User is not allowed to perform this operation., 200 Exclusions successfully deleted., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/unified-exclusions` + **Get Exclusions** `operationId`: `_web_api_unified-exclusions_get` -Get a list of all the Exclusions that match the filter.
Note: To filter the results for a scope:
* Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given. +Get a list of all the Exclusions that match the filter.
Note: To filter the results for a scope:
*Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given. Required permissions: `Exclusions.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -86,6 +90,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/unified-exclusions` + **Create Unified Exclusion** `operationId`: `_web_api_unified-exclusions_post` @@ -94,11 +99,13 @@ Create Exclusions to make your Agents suppress alerts and mitigation for items t Required permissions: `Exclusions.create` Parameters: -- `body` [body, object]: + +- `body` [body, object]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/unified-exclusions` + **Update Exclusions** `operationId`: `_web_api_unified-exclusions_put` @@ -107,11 +114,13 @@ Change the properties of an Exclusion through the data fields. To get the origin Required permissions: `Exclusions.edit` Parameters: -- `body` [body, object]: + +- `body` [body, object]: Responses: 404 Exclusion not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/unified-exclusions/available-actions` + **Get Exclusion Actions** `operationId`: `_web_api_unified-exclusions_available-actions_get` @@ -120,6 +129,7 @@ Get a list of available actions for exclusions that match the filter criteria. Required permissions: `Exclusions.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -186,6 +196,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/unified-exclusions/bulk` + **Create Bulk Unified Exclusion** `operationId`: `_web_api_unified-exclusions_bulk_post` @@ -194,11 +205,13 @@ Create Bulk Exclusions to make your Agents suppress alerts and mitigation for it Required permissions: `Exclusions.create` Parameters: -- `body` [body, exclusions.post_schema_PostUnifiedExclusionSchema_many]: + +- `body` [body, exclusions.post_schema_PostUnifiedExclusionSchema_many]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/unified-exclusions/export` + **Export Unified Exclusions** `operationId`: `_web_api_unified-exclusions_export_get` @@ -207,6 +220,7 @@ Export the currently filtered exclusions to a JSON file. You can use the export Required permissions: `Exclusions.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -264,6 +278,7 @@ Parameters: Responses: 403 User is not allowed to perform this operation., 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/unified-exclusions/import` + **Import Unified Exclusions** `operationId`: `_web_api_unified-exclusions_import_post` @@ -272,6 +287,7 @@ Import exclusions to a specified scope in the Console. Use an exclusion JSON fil Required permissions: `Exclusions.create` Parameters: + - `filter` [formData, object] **required**: Filter - `file` [formData, file] **required**: The input JSON or CSV file diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Filters.md index ed09d47..6539708 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Filters.md @@ -3,6 +3,7 @@ 14 endpoints. ## `GET /web/api/v2.1/filters` + **Get Filters** `operationId`: `_web_api_filters_get` @@ -11,6 +12,7 @@ Get the list of saved filters. See Save Filter. The response includes the ID of Required permissions: `Endpoints.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -29,6 +31,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/filters` + **Save Filter** `operationId`: `_web_api_filters_post` @@ -38,11 +41,13 @@ For example, you can save a filter with {"data":{"filterFields":{"infected":true Required permissions: `Endpoints.edit` Parameters: -- `body` [body, filters.filters_NewFilterSchema]: + +- `body` [body, filters.filters_NewFilterSchema]: Responses: 403 User is not allowed to perform this operation., 200 Filter successfully saved. Returns created object., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/filters/csv-filter` + **Upload CSV file** `operationId`: `_web_api_filters_csv-filter_post` @@ -51,6 +56,7 @@ Upload CSV file Required permissions: `Endpoints.view` Parameters: + - `agentFilterField` [formData, string] **required**: The property of the endpoint to filter by - `excludeHeader` [formData, boolean] **required**: Set to True to exclude the column header - `file` [formData, file] **required**: File @@ -58,6 +64,7 @@ Parameters: Responses: 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/filters/dv` + **[DEPRECATED] Get Deep Visibility Filters** `operationId`: `_web_api_filters_dv_get` @@ -66,6 +73,7 @@ Get saved Deep Visibility queries with full data. See Save Deep Visibility Filte Required permissions: `Deep Visibility.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -85,6 +93,7 @@ Parameters: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/filters/dv` + **[DEPRECATED] Save Deep Visibility Filter** `operationId`: `_web_api_filters_dv_post` @@ -94,11 +103,13 @@ Deep Visibility requires a Complete SKU. Required permissions: `Deep Visibility.create` Parameters: -- `body` [body, filters.filters_NewDeepVisibilityFilterSchema]: + +- `body` [body, filters.filters_NewDeepVisibilityFilterSchema]: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/filters/dv/{filter_id}` + **[DEPRECATED] Delete Deep Visibility Filter** `operationId`: `_web_api_filters_dv_{filter_id}_delete` @@ -107,11 +118,13 @@ Delete a saved Deep Visibility query. Required permissions: `Deep Visibility.delete` Parameters: + - `filter_id` [path, string] **required**: Filter ID. Example: "225494730938493804". Responses: 404 Filter not found, 403 User is not allowed to perform this operation., 200 Filter successfully deleted., 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/filters/dv/{filter_id}` + **[DEPRECATED] Update Deep Visibility Filter** `operationId`: `_web_api_filters_dv_{filter_id}_put` @@ -120,12 +133,14 @@ Change a saved Deep Visibility filter. To get the ID and fields to change, run G Required permissions: `Deep Visibility.edit` Parameters: + - `filter_id` [path, string] **required**: Filter ID. Example: "225494730938493804". -- `body` [body, filters.filters_NewDeepVisibilityFilterSchema]: +- `body` [body, filters.filters_NewDeepVisibilityFilterSchema]: Responses: 404 Filter not found, 403 User is not allowed to perform this operation., 200 Filter successfully updated. Returns updated object., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/filters/{filter_id}` + **Delete Filter** `operationId`: `_web_api_filters_{filter_id}_delete` @@ -134,11 +149,13 @@ Delete a saved filter. Required permissions: `Endpoints.edit` Parameters: + - `filter_id` [path, string] **required**: Filter ID. Example: "225494730938493804". Responses: 404 Filter not found, 403 User is not allowed to perform this operation., 200 Filter successfully deleted., 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/filters/{filter_id}` + **Update Filter** `operationId`: `_web_api_filters_{filter_id}_put` @@ -147,12 +164,14 @@ Update an existing filter Required permissions: `Endpoints.edit` Parameters: + - `filter_id` [path, string] **required**: Filter ID. Example: "225494730938493804". -- `body` [body, filters.filters_UpdateFilterSchema]: +- `body` [body, filters.filters_UpdateFilterSchema]: Responses: 404 Filter not found, 403 User is not allowed to perform this operation., 200 Filter successfully updated. Returns updated object., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/filters` + **Get Filters** `operationId`: `_web_api_xdr_filters_get` @@ -161,6 +180,7 @@ Get the list of saved filters. See Save Filter. The response includes the ID of Required permissions: `XDR Inventory.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". - `sortOrder` [query, string] (enum: asc, desc): Sort direction - `accountIds` [query, array]: List of Account IDs to filter by @@ -178,6 +198,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/filters` + **Save Filter** `operationId`: `_web_api_xdr_filters_post` @@ -187,11 +208,13 @@ For example, you can save a filter with {"data":{"filterFields":{"infected":true Required permissions: `XDR Inventory.edit` Parameters: -- `body` [body, v2_1.config.schemas_NewFilterSchema]: + +- `body` [body, v2_1.config.schemas_NewFilterSchema]: Responses: 403 User is not allowed to perform this operation., 200 Filter successfully saved. Returns created object., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/xdr/filters/{filter_id}` + **Delete Filter** `operationId`: `_web_api_xdr_filters_{filter_id}_delete` @@ -200,11 +223,13 @@ Delete a saved filter. Required permissions: `XDR Inventory.edit` Parameters: + - `filter_id` [path, string] **required**: Filter ID Responses: 404 Filter not found, 403 User is not allowed to perform this operation., 200 Filter successfully deleted., 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/xdr/filters/{filter_id}` + **Update Filter** `operationId`: `_web_api_xdr_filters_{filter_id}_put` @@ -213,12 +238,14 @@ Update an existing filter Required permissions: `XDR Inventory.edit` Parameters: + - `filter_id` [path, string] **required**: Filter ID -- `body` [body, v2_1.config.schemas_UpdateFilterSchema]: +- `body` [body, v2_1.config.schemas_UpdateFilterSchema]: Responses: 404 Filter not found, 403 User is not allowed to perform this operation., 200 Filter successfully updated. Returns updated object., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/private/filters/enriched` + **Filters with Metadata** `operationId`: `_web_api_xdr_private_filters_enriched_get` @@ -227,6 +254,7 @@ Get a list of saved endpoint filters, with enriched data. One of the fields in t Required permissions: `XDR Inventory.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". - `sortOrder` [query, string] (enum: asc, desc): Sort direction - `accountIds` [query, array]: List of Account IDs to filter by diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Firewall_Control.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Firewall_Control.md index 0c77f96..96a3722 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Firewall_Control.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Firewall_Control.md @@ -3,6 +3,7 @@ 17 endpoints. ## `DELETE /web/api/v2.1/firewall-control` + **Delete Rules** `operationId`: `_web_api_firewall-control_delete` @@ -10,11 +11,13 @@ Delete Firewall Control rules that match the filter. Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_RuleDeleteSchema]: + +- `body` [body, firewall_control.schemas_RuleDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control` + **Get Firewall Rules** `operationId`: `_web_api_firewall-control_get` @@ -22,6 +25,7 @@ Get the Firewall Control rules for a scope specified by ID (run "accounts", "sit Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -60,6 +64,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control` + **Create Firewall Rule** `operationId`: `_web_api_firewall-control_post` @@ -67,30 +72,35 @@ Create a Firewall Control rule for a scope specified by ID (run "accounts", "sit Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_PostFirewallSchema]: + +- `body` [body, firewall_control.schemas_PostFirewallSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/add-tags` + **Add Rule Tags** `operationId`: `_web_api_firewall-control_add-tags_post` -Create a Firewall Rule tag.
Create tags to represent Firewall policies - a set of rules in a specific order. After you create the tag, add rules to it.
Notes:
* Tags apply to a scope and cannot be linked to rules from different scopes.
* Tags must be 2 to 256 characters. +Create a Firewall Rule tag.
Create tags to represent Firewall policies - a set of rules in a specific order. After you create the tag, add rules to it.
Notes:
*Tags apply to a scope and cannot be linked to rules from different scopes.
* Tags must be 2 to 256 characters. Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_ChangeRulesTagsSchema]: + +- `body` [body, firewall_control.schemas_ChangeRulesTagsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control/configuration` + **Get Configuration** `operationId`: `_web_api_firewall-control_configuration_get` -Get the Firewall Control configuration for a given scope.
To filter the results for a scope:
* Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given.
The response shows if Firewall Control is enabled for the scope, if Location Awareness is enabled, the higher scope from which this scope inherited the configuration, and whether a lower scope inherits this configuration.
Firewall Control requires Control SKU. +Get the Firewall Control configuration for a given scope.
To filter the results for a scope:
*Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given.
The response shows if Firewall Control is enabled for the scope, if Location Awareness is enabled, the higher scope from which this scope inherited the configuration, and whether a lower scope inherits this configuration.
Firewall Control requires Control SKU. Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -99,6 +109,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/firewall-control/configuration` + **Update Configuration** `operationId`: `_web_api_firewall-control_configuration_put` @@ -106,11 +117,13 @@ Change the Firewall Control configuration for a given scope.
To get the ID Optional permissions: `Firewall Control.modifySettings(preferencesTab), Network Quarantine Control.modifySettings(preferencesTab), Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_PostFirewallSettingsSchema]: + +- `body` [body, firewall_control.schemas_PostFirewallSettingsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/copy-rules` + **Copy Rules** `operationId`: `_web_api_firewall-control_copy-rules_post` @@ -118,11 +131,13 @@ Copy a set of rules to other scopes.
In the filter of the body, enter the pr Optional permissions: `Firewall Control.view, Network Quarantine Control.view, Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_CopyRuleSchema]: + +- `body` [body, firewall_control.schemas_CopyRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/firewall-control/enable` + **Enable/Disable Rules** `operationId`: `_web_api_firewall-control_enable_put` @@ -130,11 +145,13 @@ Change the status of a set of Firewall Control rules that match the filter to "E Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_EnableRuleSchema]: + +- `body` [body, firewall_control.schemas_EnableRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control/export` + **Export Rules** `operationId`: `_web_api_firewall-control_export_get` @@ -142,6 +159,7 @@ Export Firewall Control rules that match the filter to a JSON file from a scope Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -172,6 +190,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/import` + **Import Rules** `operationId`: `_web_api_firewall-control_import_post` @@ -179,6 +198,7 @@ Import Firewall Control rules from an exported JSON file to scopes specified by Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `accountIds` [formData, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [formData, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [formData, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -188,6 +208,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/move-rules` + **Move Rules** `operationId`: `_web_api_firewall-control_move-rules_post` @@ -195,11 +216,13 @@ Remove Firewall Rules, defined with the ID of the rules (run 'firewall-control') Optional permissions: `Firewall Control.view, Network Quarantine Control.view, Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_CopyRuleSchema]: + +- `body` [body, firewall_control.schemas_CopyRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control/protocols` + **Get Protocols** `operationId`: `_web_api_firewall-control_protocols_get` @@ -207,6 +230,7 @@ Get a list of protocols that can be used in Firewall Control rules. Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -220,6 +244,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/remove-tags` + **Remove Rule Tags** `operationId`: `_web_api_firewall-control_remove-tags_post` @@ -227,11 +252,13 @@ Remove firewall tags from rules matching the filter.
Tags represent Firewall Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_ChangeRulesTagsSchema]: + +- `body` [body, firewall_control.schemas_ChangeRulesTagsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/firewall-control/reorder` + **Reorder Rules** `operationId`: `_web_api_firewall-control_reorder_put` @@ -239,11 +266,13 @@ Change the order of rules for a scope specified by ID (run "accounts", "sites", Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_ReorderSchema]: + +- `body` [body, firewall_control.schemas_ReorderSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/set-location` + **Set Location** `operationId`: `_web_api_firewall-control_set-location_post` @@ -251,11 +280,13 @@ Set location attributes for a Location Aware Firewall Control rule. These rules Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: -- `body` [body, firewall_control.schemas_SetLocationSchema]: + +- `body` [body, firewall_control.schemas_SetLocationSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control/tag-rules/{tag_id}` + **Get Tag Firewall Rules** `operationId`: `_web_api_firewall-control_tag-rules_{tag_id}_get` @@ -263,6 +294,7 @@ Get all Firewall rules linked to tag, regardless of inheritance mode.
To get Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `tag_id` [path, string] **required**: Rule ID. Example: "225494730938493804". - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". @@ -302,6 +334,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/firewall-control/{firewall_rule_category}` + **Update Firewall Rule** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_put` @@ -309,7 +342,8 @@ Change a Firewall Control rule.
This command requires the rule ID, which you Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: Rule ID. Example: "225494730938493804". -- `body` [body, firewall_control.schemas_PutFirewallSchema]: +- `body` [body, firewall_control.schemas_PutFirewallSchema]: Responses: 404 Firewall rule not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Forensics.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Forensics.md index 2beaad9..6d45034 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Forensics.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Forensics.md @@ -3,6 +3,7 @@ 4 endpoints. ## `GET /web/api/v2.1/applications/{application_id}/forensics` + **Application Forensics** `operationId`: `_web_api_applications_{application_id}_forensics_get` @@ -11,6 +12,7 @@ DEPRECATED Required permissions: `Threats.view, Applications Page.view` Parameters: + - `application_id` [path, string] **required**: Application ID. Example: "56ee72a79c7e5c62dd36e6b1". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -19,6 +21,7 @@ Parameters: Responses: 404 Not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/applications/{application_id}/forensics/connections` + **Application Connections** `operationId`: `_web_api_applications_{application_id}_forensics_connections_get` @@ -27,6 +30,7 @@ Responses: 404 Not found, 200 Success, 400 Invalid user input received. See erro Required permissions: `Threats.view, Applications Page.view` Parameters: + - `application_id` [path, string] **required**: Application ID. Example: "56ee72a79c7e5c62dd36e6b1". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -36,6 +40,7 @@ Parameters: Responses: 404 Not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/applications/{application_id}/forensics/details` + **Application Forensics - Detailed** `operationId`: `_web_api_applications_{application_id}_forensics_details_get` @@ -44,6 +49,7 @@ Responses: 404 Not found, 200 Success, 400 Invalid user input received. See erro Required permissions: `Threats.view, Applications Page.view` Parameters: + - `application_id` [path, string] **required**: Application ID. Example: "56ee72a79c7e5c62dd36e6b1". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -52,6 +58,7 @@ Parameters: Responses: 404 Not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/applications/{application_id}/forensics/export/{export_format}` + **Export Application** `operationId`: `_web_api_applications_{application_id}_forensics_export_{export_format}_get` @@ -60,6 +67,7 @@ Responses: 404 Not found, 200 Success, 400 Invalid user input received. See erro Required permissions: `Threats.view, Applications Page.view` Parameters: + - `application_id` [path, string] **required**: Application ID. Example: "56ee72a79c7e5c62dd36e6b1". - `export_format` [path, string] **required** (enum: csv, json): Export format. Example: "csv". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Gateways.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Gateways.md index 8c41714..2052c1f 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Gateways.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Gateways.md @@ -3,15 +3,17 @@ 3 endpoints. ## `GET /web/api/v2.1/ranger/gateways` + **Get Gateways** `operationId`: `_web_api_ranger_gateways_get` -Get the gateways in your deployment that match the filter from a Network Discovery scan. +Get the gateways in your deployment that match the filter from a Network Discovery scan. Network Discovery requires a Network Discovery license. Required permissions: `Ranger.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -82,6 +84,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger/gateways/update` + **Update Gateways** `operationId`: `_web_api_ranger_gateways_update_post` @@ -90,11 +93,13 @@ Change the status of filtered gateways discovered by Network Discovery. You can Required permissions: `Ranger.manageDiscoveredNetworks` Parameters: -- `body` [body, ranger.gateway_schema_PostUpdateGatewayData]: + +- `body` [body, ranger.gateway_schema_PostUpdateGatewayData]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/ranger/gateways/{gateway_id}` + **Update Gateway** `operationId`: `_web_api_ranger_gateways_{gateway_id}_put` @@ -103,7 +108,8 @@ Change the Network Discovery scan configuration for a gateway that Network Disco Required permissions: `Ranger.manageDiscoveredNetworks` Parameters: + - `gateway_id` [path, string] **required**: Gateway ID. Example: "225494730938493804". -- `body` [body, ranger.gateway_schema_PutGatewayData]: +- `body` [body, ranger.gateway_schema_PutGatewayData]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph.md index 421f4b2..7c242e4 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph.md @@ -3,6 +3,7 @@ 3 endpoints. ## `POST /web/api/v2.1/xdr/graph-explorer/query/explorer` + **Query the graph based on query builder filters** `operationId`: `_web_api_xdr_graph-explorer_query_explorer_post` @@ -11,17 +12,19 @@ Query the graph Required permissions: `XDR Inventory.view` Parameters: + - `mock` [query, boolean]: Mock - `groupIds` [query, array]: List of Group IDs to filter by - `accountIds` [query, array]: List of Account IDs to filter by - `limit` [query, integer]: Limit - `continuationToken` [query, string]: Continuation token - `siteIds` [query, array]: List of Site IDs to filter by -- `body` [body, v2_1.graph.query.schemas_QueryGraphInputSchema]: +- `body` [body, v2_1.graph.query.schemas_QueryGraphInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/graph-explorer/query/explorer/v2` + **Query the graph based on query builder filters** `operationId`: `_web_api_xdr_graph-explorer_query_explorer_v2_post` @@ -30,17 +33,19 @@ Query the graph Required permissions: `XDR Inventory.view` Parameters: + - `mock` [query, boolean]: Mock - `groupIds` [query, array]: List of Group IDs to filter by - `accountIds` [query, array]: List of Account IDs to filter by - `limit` [query, integer]: Limit - `continuationToken` [query, string]: Continuation token - `siteIds` [query, array]: List of Site IDs to filter by -- `body` [body, v2_1.graph.query.schemas_QueryGraphInputSchemaV2]: +- `body` [body, v2_1.graph.query.schemas_QueryGraphInputSchemaV2]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/graph-explorer/query/subgraph` + **Query the sub graph of an asset type and id** `operationId`: `_web_api_xdr_graph-explorer_query_subgraph_post` @@ -49,9 +54,10 @@ Query the sub graph of an asset type and id Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.graph.query.schemas_QuerySubGrapInputSchema]: +- `body` [body, v2_1.graph.query.schemas_QuerySubGrapInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph_Query_Builder.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph_Query_Builder.md index 620fde4..e3ec3c9 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph_Query_Builder.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph_Query_Builder.md @@ -3,6 +3,7 @@ 8 endpoints. ## `GET /web/api/v2.1/xdr/assets/query/builder/metadata` + **Get Graph Query Builder Initial Metadata** `operationId`: `_web_api_xdr_assets_query_builder_metadata_get` @@ -11,6 +12,7 @@ Get graph query builder initial metadata Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by @@ -18,6 +20,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/graph-explorer/query/builder/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_graph-explorer_query_builder_autocomplete_get` @@ -26,6 +29,7 @@ This api is now deprecated use /xdr/graph-explorer/query/builder/autocomplete/v2 Required permissions: `XDR Inventory.view` Parameters: + - `ids` [query, array]: List of asset type ids - `groupIds` [query, array]: List of Group IDs to filter by - `accountIds` [query, array]: List of Account IDs to filter by @@ -40,6 +44,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/graph-explorer/query/builder/autocomplete/v2` + **Auto Complete** `operationId`: `_web_api_xdr_graph-explorer_query_builder_autocomplete_v2_get` @@ -48,6 +53,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `ids` [query, array]: List of asset type ids - `groupIds` [query, array]: List of Group IDs to filter by - `accountIds` [query, array]: List of Account IDs to filter by @@ -62,6 +68,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/graph-explorer/query/builder/metadata` + **Get Graph Query Builder Initial Metadata** `operationId`: `_web_api_xdr_graph-explorer_query_builder_metadata_get` @@ -70,6 +77,7 @@ Get graph query builder initial metadata Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by @@ -77,6 +85,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/graph-explorer/query/builder/metadata/available-options/v2` + **Get Query Builder metadata For Requested Resource Types** `operationId`: `_web_api_xdr_graph-explorer_query_builder_metadata_available-options_v2_get` @@ -85,6 +94,7 @@ Get query builder metadata for requested cloud asset types Required permissions: `XDR Inventory.view` Parameters: + - `ids` [query, array]: List of asset type ids - `groupIds` [query, array]: List of Group IDs to filter by - `accountIds` [query, array]: List of Account IDs to filter by @@ -96,6 +106,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/graph-explorer/query/builder/metadata/available-relations` + **Get the available relations** `operationId`: `_web_api_xdr_graph-explorer_query_builder_metadata_available-relations_get` @@ -104,6 +115,7 @@ Get the available relations Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by @@ -111,6 +123,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/graph-explorer/query/builder/tag/autocomplete` + **Tag Auto Complete** `operationId`: `_web_api_xdr_graph-explorer_query_builder_tag_autocomplete_post` @@ -119,6 +132,7 @@ Use this command to get tag keys or values. When you send this command with inpu Required permissions: `XDR Inventory.view` Parameters: + - `ids` [query, array]: List of asset type ids - `groupIds` [query, array]: List of Group IDs to filter by - `accountIds` [query, array]: List of Account IDs to filter by @@ -133,6 +147,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/private/graph-services-features` + **Get all of the feature toggles for graph services.** `operationId`: `_web_api_xdr_private_graph-services-features_get` @@ -141,6 +156,7 @@ Get all of the feature toggles for graph services Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph_Query_Management.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph_Query_Management.md index d9ca27e..4e36dbb 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph_Query_Management.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Graph_Query_Management.md @@ -3,6 +3,7 @@ 6 endpoints. ## `GET /web/api/v2.1/xdr/graph-explorer/query/management/query` + **Get graph query list** `operationId`: `_web_api_xdr_graph-explorer_query_management_query_get` @@ -11,6 +12,7 @@ Get graph query list Required permissions: `XDR Inventory.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". - `groupIds` [query, array]: List of Group IDs to filter by - `sortOrder` [query, string] (enum: asc, desc): Sort direction @@ -28,6 +30,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/graph-explorer/query/management/query` + **Save graph query** `operationId`: `_web_api_xdr_graph-explorer_query_management_query_post` @@ -36,14 +39,16 @@ Save graph query Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.graph.query.schemas_QueryManagementUpdateSchema]: +- `body` [body, v2_1.graph.query.schemas_QueryManagementUpdateSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/graph-explorer/query/management/query/type-counts` + **Get graph query counts by type** `operationId`: `_web_api_xdr_graph-explorer_query_management_query_type-counts_get` @@ -52,6 +57,7 @@ Get graph query counts by type Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by @@ -59,6 +65,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/xdr/graph-explorer/query/management/query/{query_id}` + **Delete graph query** `operationId`: `_web_api_xdr_graph-explorer_query_management_query_{query_id}_delete` @@ -67,6 +74,7 @@ Delete graph query Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by @@ -75,6 +83,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/xdr/graph-explorer/query/management/query/{query_id}` + **Update graph query** `operationId`: `_web_api_xdr_graph-explorer_query_management_query_{query_id}_put` @@ -83,15 +92,17 @@ Update graph query Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by - `query_id` [path, string] **required**: Query ID -- `body` [body, v2_1.graph.query.schemas_QueryManagementUpdateSchema]: +- `body` [body, v2_1.graph.query.schemas_QueryManagementUpdateSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/graph-explorer/query/management/recent-queries` + **Get graph recent query list** `operationId`: `_web_api_xdr_graph-explorer_query_management_recent-queries_get` @@ -100,6 +111,7 @@ Get graph recent query list Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `limit` [query, integer]: Limit number of returned items diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Groups.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Groups.md index 51fc131..058357e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Groups.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Groups.md @@ -3,6 +3,7 @@ 10 endpoints. ## `GET /web/api/v2.1/groups` + **Get Groups** `operationId`: `_web_api_groups_get` @@ -11,6 +12,7 @@ Get data of groups that match the filter. Best practice: use as narrow a filter Required permissions: `Groups.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -38,6 +40,7 @@ Parameters: Responses: 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/groups` + **Create Group** `operationId`: `_web_api_groups_post` @@ -46,11 +49,13 @@ Create a new group. You must create the Group in a Site (run "sites" to get the Required permissions: `Groups.create` Parameters: -- `body` [body, groups_PostGroupSchema]: + +- `body` [body, groups_PostGroupSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/groups/ranks` + **Update Ranks** `operationId`: `_web_api_groups_ranks_put` @@ -59,11 +64,13 @@ An Agent can belong to only one Group. If the Agent matches multiple Dynamic Gro Required permissions: `Groups.edit` Parameters: -- `body` [body, groups_PutRanksSchema]: + +- `body` [body, groups_PutRanksSchema]: Responses: 403 Insufficient permissions, 204 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/groups/{group_id}` + **Delete Group** `operationId`: `_web_api_groups_{group_id}_delete` @@ -72,11 +79,13 @@ Delete a Group given by the required Group ID (run "groups"). If there are Agent Required permissions: `Groups.delete` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". Responses: 404 Group not found., 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/groups/{group_id}` + **Get Group by ID** `operationId`: `_web_api_groups_{group_id}_get` @@ -85,11 +94,13 @@ Get data of a given Group. To get a Group ID, run "groups". This command respond Required permissions: `Groups.view` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". Responses: 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/groups/{group_id}` + **Update Group** `operationId`: `_web_api_groups_{group_id}_put` @@ -98,12 +109,14 @@ Change properties of a Group specified by its ID (run "groups"). The body of the Required permissions: `Groups.edit` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". -- `body` [body, groups_PutGroupSchema]: +- `body` [body, groups_PutGroupSchema]: Responses: 404 Group not found., 403 Insufficient permissions., 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/groups/{group_id}/move-agents` + **Move Agents** `operationId`: `_web_api_groups_{group_id}_move-agents_put` @@ -112,12 +125,14 @@ Move Agents that match the filter to a Group. The Group ID (run "groups") is req Required permissions: `Groups.moveToGroup` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". -- `body` [body, groups_PutAddAgentsSchema]: +- `body` [body, groups_PutAddAgentsSchema]: Responses: 409 Conflict, 403 Insufficient permissions, 204 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/groups/{group_id}/regenerate-key` + **Regenerate Group Token** `operationId`: `_web_api_groups_{group_id}_regenerate-key_put` @@ -126,11 +141,13 @@ Get a new Group Token for a static Group. This command requires the Group ID ("g Required permissions: `Groups.edit` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". Responses: 403 No permission for regenerating a key., 404 Group not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/groups/{group_id}/revert-policy` + **Revert Policy** `operationId`: `_web_api_groups_{group_id}_revert-policy_put` @@ -139,12 +156,14 @@ A Group can have a policy that is different from its Site policy. Use this comma Required permissions: `Policy.edit` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". -- `body` [body, policies_schemas_RevertPolicySchema]: +- `body` [body, policies_schemas_RevertPolicySchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/groups/{group_id}/token` + **Get Site registration token by ID** `operationId`: `_web_api_groups_{group_id}_token_get` @@ -153,6 +172,7 @@ Get the registration token of the Group of the ID. Required permissions: `Groups.view` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". Responses: 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Hashes.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Hashes.md index 853dd9d..18fcd6c 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Hashes.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Hashes.md @@ -3,16 +3,18 @@ 1 endpoints. ## `GET /web/api/v2.1/hashes/{hash}/verdict` + **Hash Reputation verdict** `operationId`: `_web_api_hashes_{hash}_verdict_get` [DEPRECATED] Get the verdict of the of a hash, given the required SHA1. -A hash, either malicious or non-malicious, means it has been marked as such by the Reputation's sources. +A hash, either malicious or non-malicious, means it has been marked as such by the Reputation's sources. An unknown answer is given for hashes that are not yet known by Reputation. Required permissions: `Blacklist.view` Parameters: + - `hash` [path, string] **required**: Hash Responses: 200 Verdict of the hash known to the management, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Hyperautomation.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Hyperautomation.md index ed20e41..17b8dc1 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Hyperautomation.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Hyperautomation.md @@ -3,120 +3,133 @@ 13 endpoints. ## `POST /web/api/v2.1/hyper-automate/api/public/workflow-action-expressions/{base_action_id}/evaluate-expression` + **Evaluate Expression** `operationId`: `_web_api_hyper-automate_api_public_workflow-action-expressions_{base_action_id}_evaluate-expression_post` Parameters: -- `base_action_id` [path, string] **required**: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: -- `body` [body, S1ApiBody_ExpressionEvaluationInput_] **required**: + +- `base_action_id` [path, string] **required**: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: +- `body` [body, S1ApiBody_ExpressionEvaluationInput_] **required**: Responses: 200 Successful Response, 422 Validation Error ## `POST /web/api/v2.1/hyper-automate/api/public/workflow-action-expressions/{base_action_id}/expression-breakdown` + **Expression Breakdown** `operationId`: `_web_api_hyper-automate_api_public_workflow-action-expressions_{base_action_id}_expression-breakdown_post` Parameters: -- `base_action_id` [path, string] **required**: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: -- `body` [body, S1ApiBody_ExpressionEvaluationInput_] **required**: + +- `base_action_id` [path, string] **required**: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: +- `body` [body, S1ApiBody_ExpressionEvaluationInput_] **required**: Responses: 200 Successful Response, 422 Validation Error ## `GET /web/api/v2.1/hyper-automate/api/public/workflow-execution` + **List all workflow executions** `operationId`: `_web_api_hyper-automate_api_public_workflow-execution_get` Required permissions: `Hyper Automate.view` Parameters: -- `trigger_types` [query, string]: -- `states` [query, string]: -- `scope_ids` [query, string]: -- `versions_count` [query, string]: -- `created_at__gte` [query, object]: -- `created_at__lt` [query, object]: -- `limit` [query, integer]: -- `skip` [query, integer]: -- `workflow_name__contains` [query, object]: -- `integrations` [query, object]: -- `workflow_id` [query, object]: -- `is_snippet` [query, boolean]: -- `tags` [query, string]: -- `sortBy` [query, object]: -- `sortOrder` [query, object]: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: + +- `trigger_types` [query, string]: +- `states` [query, string]: +- `scope_ids` [query, string]: +- `versions_count` [query, string]: +- `created_at__gte` [query, object]: +- `created_at__lt` [query, object]: +- `limit` [query, integer]: +- `skip` [query, integer]: +- `workflow_name__contains` [query, object]: +- `integrations` [query, object]: +- `workflow_id` [query, object]: +- `is_snippet` [query, boolean]: +- `tags` [query, string]: +- `sortBy` [query, object]: +- `sortOrder` [query, object]: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: Responses: 200 Successful Response, 422 Validation Error ## `POST /web/api/v2.1/hyper-automate/api/public/workflow-execution/manual/{workflow_id}/{version_id}` + **Trigger a workflow that uses a manual trigger** `operationId`: `_web_api_hyper-automate_api_public_workflow-execution_manual_{workflow_id}_{version_id}_post` Required permissions: `Hyper Automate.workflowsRun` Parameters: -- `workflow_id` [path, string] **required**: -- `version_id` [path, string] **required**: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: -- `body` [body, S1ApiBody_WorkflowExecutionCreate_] **required**: + +- `workflow_id` [path, string] **required**: +- `version_id` [path, string] **required**: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: +- `body` [body, S1ApiBody_WorkflowExecutionCreate_] **required**: Responses: 201 Successful Response, 422 Validation Error ## `GET /web/api/v2.1/hyper-automate/api/public/workflow-execution/{workflow_execution_id}` + **Get a workflow execution by its ID** `operationId`: `_web_api_hyper-automate_api_public_workflow-execution_{workflow_execution_id}_get` Required permissions: `Hyper Automate.view` Parameters: -- `workflow_execution_id` [path, string] **required**: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: + +- `workflow_execution_id` [path, string] **required**: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: Responses: 200 Successful Response, 422 Validation Error ## `GET /web/api/v2.1/hyper-automate/api/public/workflow-import-export/export` + **Batch export workflows** `operationId`: `_web_api_hyper-automate_api_public_workflow-import-export_export_get` Required permissions: `Hyper Automate.workflowsExport` Parameters: -- `workflow_ids` [query, string]: -- `integrations` [query, string]: -- `trigger_types` [query, string]: -- `core_actions` [query, string]: -- `states` [query, string]: -- `scope_ids` [query, string]: -- `name__contains` [query, object]: -- `description__contains` [query, object]: -- `name__eq` [query, object]: -- `oversight` [query, boolean]: -- `tags` [query, string]: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: + +- `workflow_ids` [query, string]: +- `integrations` [query, string]: +- `trigger_types` [query, string]: +- `core_actions` [query, string]: +- `states` [query, string]: +- `scope_ids` [query, string]: +- `name__contains` [query, object]: +- `description__contains` [query, object]: +- `name__eq` [query, object]: +- `oversight` [query, boolean]: +- `tags` [query, string]: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: Responses: 200 Successful Response, 422 Validation Error ## `GET /web/api/v2.1/hyper-automate/api/public/workflow-import-export/export/{workflow_id}/{version_id}` + **Export workflow** `operationId`: `_web_api_hyper-automate_api_public_workflow-import-export_export_{workflow_id}_{version_id}_get` @@ -125,16 +138,18 @@ Export a specific workflow version. Required permissions: `Hyper Automate.workflowsExport` Parameters: -- `workflow_id` [path, string] **required**: -- `version_id` [path, string] **required**: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: + +- `workflow_id` [path, string] **required**: +- `version_id` [path, string] **required**: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: Responses: 200 Successful Response, 422 Validation Error ## `POST /web/api/v2.1/hyper-automate/api/public/workflow-import-export/import` + **Import workflow** `operationId`: `_web_api_hyper-automate_api_public_workflow-import-export_import_post` @@ -143,15 +158,17 @@ Import workflows that have been previously exported from Hyperautomation. Required permissions: `Hyper Automate.workflowsCreateEdit` Parameters: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: -- `body` [body, S1ApiBody_WorkflowImportExport_] **required**: + +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: +- `body` [body, S1ApiBody_WorkflowImportExport_] **required**: Responses: 200 Successful Response, 422 Validation Error ## `POST /web/api/v2.1/hyper-automate/api/public/workflow-import-export/import/batch` + **Batch import workflows** `operationId`: `_web_api_hyper-automate_api_public_workflow-import-export_import_batch_post` @@ -160,89 +177,98 @@ Import workflows that have been previously exported from Hyperautomation. Required permissions: `Hyper Automate.workflowsCreateEdit` Parameters: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: -- `body` [body, Body_import_workflow_batch_web_api_v2_1_hyper_automate_api_public_workflow_import_export_import_batch_post] **required**: + +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: +- `body` [body, Body_import_workflow_batch_web_api_v2_1_hyper_automate_api_public_workflow_import_export_import_batch_post] **required**: Responses: 201 Successful Response, 422 Validation Error ## `GET /web/api/v2.1/hyper-automate/api/public/workflows` + **List all workflows** `operationId`: `_web_api_hyper-automate_api_public_workflows_get` Required permissions: `Hyper Automate.view` Parameters: -- `integrations` [query, string]: -- `trigger_types` [query, string]: -- `core_actions` [query, string]: -- `states` [query, string]: -- `scope_ids` [query, string]: -- `limit` [query, integer]: -- `skip` [query, integer]: -- `is_snippet` [query, boolean]: -- `name__contains` [query, object]: -- `description__contains` [query, object]: -- `name__eq` [query, object]: -- `tags` [query, string]: -- `sortBy` [query, object]: -- `sortOrder` [query, object]: -- `oversight` [query, boolean]: -- `workflow_ids` [query, object]: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: + +- `integrations` [query, string]: +- `trigger_types` [query, string]: +- `core_actions` [query, string]: +- `states` [query, string]: +- `scope_ids` [query, string]: +- `limit` [query, integer]: +- `skip` [query, integer]: +- `is_snippet` [query, boolean]: +- `name__contains` [query, object]: +- `description__contains` [query, object]: +- `name__eq` [query, object]: +- `tags` [query, string]: +- `sortBy` [query, object]: +- `sortOrder` [query, object]: +- `oversight` [query, boolean]: +- `workflow_ids` [query, object]: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: Responses: 200 Successful Response, 422 Validation Error ## `GET /web/api/v2.1/hyper-automate/api/public/workflows/versions/list/{workflow_id}` + **List workflow versions** `operationId`: `_web_api_hyper-automate_api_public_workflows_versions_list_{workflow_id}_get` Required permissions: `Hyper Automate.view` Parameters: -- `workflow_id` [path, string] **required**: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: + +- `workflow_id` [path, string] **required**: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: Responses: 200 Successful Response, 422 Validation Error ## `POST /web/api/v2.1/hyper-automate/api/public/workflows/{workflow_id}/deactivate` + **Deactivate The active workflow** `operationId`: `_web_api_hyper-automate_api_public_workflows_{workflow_id}_deactivate_post` Required permissions: `Hyper Automate.workflowsActivateDeactivate` Parameters: -- `workflow_id` [path, string] **required**: -- `version_id` [query, string]: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: -- `body` [body, S1ApiBody]: + +- `workflow_id` [path, string] **required**: +- `version_id` [query, string]: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: +- `body` [body, S1ApiBody]: Responses: 204 Successful Response, 422 Validation Error ## `POST /web/api/v2.1/hyper-automate/api/public/workflows/{workflow_id}/{version_id}/activation` + **Activate a workflow version** `operationId`: `_web_api_hyper-automate_api_public_workflows_{workflow_id}_{version_id}_activation_post` Required permissions: `Hyper Automate.workflowsActivateDeactivate` Parameters: -- `workflow_id` [path, string] **required**: -- `version_id` [path, string] **required**: -- `groupIds` [query, object]: -- `siteIds` [query, object]: -- `accountIds` [query, object]: -- `cicdschema` [header, object]: -- `body` [body, S1ApiBody_WorkflowPatch_]: + +- `workflow_id` [path, string] **required**: +- `version_id` [path, string] **required**: +- `groupIds` [query, object]: +- `siteIds` [query, object]: +- `accountIds` [query, object]: +- `cicdschema` [header, object]: +- `body` [body, S1ApiBody_WorkflowPatch_]: Responses: 204 Successful Response, 422 Validation Error diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/ISPM.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/ISPM.md index 388c074..75e8940 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/ISPM.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/ISPM.md @@ -3,6 +3,7 @@ 6 endpoints. ## `GET /web/api/v2.1/ranger-ad/assessment-status` + **Get Assessment Status** `operationId`: `_web_api_ranger-ad_assessment-status_get` @@ -11,12 +12,14 @@ Use the below Cloud API to get the status of the AD Assessment status for that a Required permissions: `AD Exposures.View` Parameters: + - `siteIds` [query, string]: List of site IDs separated by comma - `accountIds` [query, string]: List of account IDs separated by comma Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger-ad/get-affected-objects` + **Get Affected Objects** `operationId`: `_web_api_ranger-ad_get-affected-objects_post` @@ -25,15 +28,17 @@ Use the below Cloud API to get all the affected objects based on the selected fi Required permissions: `AD Exposures.View` Parameters: + - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `accountIds` [query, string]: List of account IDs separated by comma - `siteIds` [query, string]: List of site IDs separated by comma -- `body` [body, v2_1.public_api.schemas_GetAffectedObjectsRequestSchema]: +- `body` [body, v2_1.public_api.schemas_GetAffectedObjectsRequestSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger-ad/get-exposures` + **Get Exposures** `operationId`: `_web_api_ranger-ad_get-exposures_post` @@ -42,15 +47,17 @@ Use the below Cloud API to get all the exposures based on the selected filters Required permissions: `AD Exposures.View` Parameters: + - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `accountIds` [query, string]: List of account IDs separated by comma - `siteIds` [query, string]: List of site IDs separated by comma -- `body` [body, v2_1.public_api.schemas_GetExposuresRequestSchema]: +- `body` [body, v2_1.public_api.schemas_GetExposuresRequestSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger-ad/set-ack-status` + **Set Acknowledged Status** `operationId`: `_web_api_ranger-ad_set-ack-status_post` @@ -59,13 +66,15 @@ Use the below Cloud API to set acknowledgement status Required permissions: `AD Exposures.Ack or Unack AD Assessment` Parameters: + - `siteIds` [query, string]: List of site IDs separated by comma - `accountIds` [query, string]: List of account IDs separated by comma -- `body` [body, v2_1.public_api.schemas_SetAckExposuresRequestSchema]: +- `body` [body, v2_1.public_api.schemas_SetAckExposuresRequestSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger-ad/set-skipped-exposures` + **Set Skipped Exposures** `operationId`: `_web_api_ranger-ad_set-skipped-exposures_post` @@ -74,13 +83,15 @@ Use the below Cloud API to set the list of exposures to be skipped Required permissions: `AD Exposure Exclusions.Edit` Parameters: + - `siteIds` [query, string]: List of site IDs separated by comma - `accountIds` [query, string]: List of account IDs separated by comma -- `body` [body, v2_1.public_api.schemas_SetSkippedExposuresRequestSchema]: +- `body` [body, v2_1.public_api.schemas_SetSkippedExposuresRequestSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger-ad/trigger-assessment` + **Trigger Assessment** `operationId`: `_web_api_ranger-ad_trigger-assessment_post` @@ -89,8 +100,9 @@ Use the below Cloud API to trigger ADAssessment Required permissions: `AD Exposures.Trigger Assessment` Parameters: + - `siteIds` [query, string]: List of site IDs separated by comma - `accountIds` [query, string]: List of account IDs separated by comma -- `body` [body, v2_1.public_api.schemas_TriggerAssessmentRequestSchema]: +- `body` [body, v2_1.public_api.schemas_TriggerAssessmentRequestSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory.md index 6dc9d86..ab0f71a 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory.md @@ -3,6 +3,7 @@ 9 endpoints. ## `GET /web/api/v2.1/xdr/assets` + **Assets** `operationId`: `_web_api_xdr_assets_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -103,6 +105,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets` + **Assets using POST** `operationId`: `_web_api_xdr_assets_post` @@ -111,14 +114,16 @@ POST API to get assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.schemas_InventoryViewInputSchema]: +- `body` [body, v2_1.inventory.schemas_InventoryViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/action` + **Perform action** `operationId`: `_web_api_xdr_assets_action_post` @@ -127,6 +132,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -208,11 +214,12 @@ Parameters: - `allTagsKey__exists` [query, array]: User and cloud tag keys exists - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_InventoryActionPayloadSchema]: +- `body` [body, v2_1.inventory.schemas_InventoryActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/asset-counts` + **Get inventory counts for menu items** `operationId`: `_web_api_xdr_assets_asset-counts_get` @@ -221,6 +228,7 @@ Get inventory counts categories, subcategories and surfaces Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by @@ -228,6 +236,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_available-actions_with-status_post` @@ -236,6 +245,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -317,11 +327,12 @@ Parameters: - `allTagsKey__exists` [query, array]: User and cloud tag keys exists - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/categories` + **Categories and counts** `operationId`: `_web_api_xdr_assets_categories_get` @@ -330,6 +341,7 @@ Get inventory categories and their asset counts Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by @@ -337,6 +349,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/cloud-tags/export` + **Export Cloud Tags to CSV or JSON** `operationId`: `_web_api_xdr_assets_cloud-tags_export_get` @@ -345,6 +358,7 @@ Returns the tags for given id in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `groupIds` [query, array]: List of Group IDs to filter by - `accountIds` [query, array]: List of Account IDs to filter by - `exportFormat` [query, string] **required** (enum: csv, json): Export format @@ -354,6 +368,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_export_get` @@ -362,6 +377,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -455,6 +471,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/sub-categories` + **Counts per subcategory for categories** `operationId`: `_web_api_xdr_assets_sub-categories_get` @@ -463,6 +480,7 @@ Get asset counts per subcategory for each category Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_AI_ML.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_AI_ML.md index a812dcd..cd6a988 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_AI_ML.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_AI_ML.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/ai-ml` + **Assets** `operationId`: `_web_api_xdr_assets_ai-ml_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/ai-ml` + **Assets using POST** `operationId`: `_web_api_xdr_assets_ai-ml_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.ai_ml.schemas_AIMLViewInputSchema]: +- `body` [body, v2_1.inventory.ai_ml.schemas_AIMLViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/ai-ml/action` + **Perform action** `operationId`: `_web_api_xdr_assets_ai-ml_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.ai_ml.schemas_AIMLActionPayloadSchema]: +- `body` [body, v2_1.inventory.ai_ml.schemas_AIMLActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/ai-ml/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_ai-ml_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/ai-ml/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_ai-ml_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_AI_ML_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_AI_ML_Filters.md index 96bc319..8f2a7aa 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_AI_ML_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_AI_ML_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/ai-ml/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_ai-ml_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/ai-ml/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_ai-ml_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/ai-ml/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_ai-ml_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Account.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Account.md index ff97f32..3716153 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Account.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Account.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/account` + **Assets** `operationId`: `_web_api_xdr_assets_account_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/account` + **Assets using POST** `operationId`: `_web_api_xdr_assets_account_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.account.schemas_AccountsViewInputSchema]: +- `body` [body, v2_1.inventory.account.schemas_AccountsViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/account/action` + **Perform action** `operationId`: `_web_api_xdr_assets_account_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.account.schemas_AccountActionPayloadSchema]: +- `body` [body, v2_1.inventory.account.schemas_AccountActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/account/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_account_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/account/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_account_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Account_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Account_Filters.md index 33b2888..0f54685 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Account_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Account_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/account/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_account_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/account/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_account_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/account/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_account_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Application_Integration.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Application_Integration.md index 1748ef5..172435f 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Application_Integration.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Application_Integration.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/application-integration` + **Assets** `operationId`: `_web_api_xdr_assets_application-integration_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/application-integration` + **Assets using POST** `operationId`: `_web_api_xdr_assets_application-integration_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.application_integration.schemas_ApplicationIntegrationsViewInputSchema]: +- `body` [body, v2_1.inventory.application_integration.schemas_ApplicationIntegrationsViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/application-integration/action` + **Perform action** `operationId`: `_web_api_xdr_assets_application-integration_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.application_integration.schemas_ApplicationIntegrationActionPayloadSchema]: +- `body` [body, v2_1.inventory.application_integration.schemas_ApplicationIntegrationActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/application-integration/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_application-integration_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/application-integration/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_application-integration_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Application_Integration_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Application_Integration_Filters.md index bbfd871..4a71cca 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Application_Integration_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Application_Integration_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/application-integration/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_application-integration_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/application-integration/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_application-integration_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/application-integration/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_application-integration_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Application.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Application.md index d077a56..8024c07 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Application.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Application.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/cloud-application` + **Assets** `operationId`: `_web_api_xdr_assets_cloud-application_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/cloud-application` + **Assets using POST** `operationId`: `_web_api_xdr_assets_cloud-application_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.cloud_application.schemas_CloudApplicationViewInputSchema]: +- `body` [body, v2_1.inventory.cloud_application.schemas_CloudApplicationViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/cloud-application/action` + **Perform action** `operationId`: `_web_api_xdr_assets_cloud-application_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.cloud_application.schemas_CloudApplicationActionPayloadSchema]: +- `body` [body, v2_1.inventory.cloud_application.schemas_CloudApplicationActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/cloud-application/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_cloud-application_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/cloud-application/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_cloud-application_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Application_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Application_Filters.md index 539c30a..a2b6ca2 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Application_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Application_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/cloud-application/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_cloud-application_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/cloud-application/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_cloud-application_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/cloud-application/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_cloud-application_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Surface.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Surface.md index 6b29aad..79f5b3d 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Surface.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Surface.md @@ -3,6 +3,7 @@ 4 endpoints. ## `GET /web/api/v2.1/xdr/assets/surface/cloud` + **Assets** `operationId`: `_web_api_xdr_assets_surface_cloud_get` @@ -11,6 +12,7 @@ Get Cloud assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -169,6 +171,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/cloud/action` + **Perform action** `operationId`: `_web_api_xdr_assets_surface_cloud_action_post` @@ -177,6 +180,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.edit, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -324,11 +328,12 @@ Parameters: - `osFamily__nin` [query, array]: The operating system family of the device (not in) - `scanStatus` [query, array]: The CDS malware scan status - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.surfaces.cloud.schemas_CloudActionPayloadSchema]: +- `body` [body, v2_1.inventory.surfaces.cloud.schemas_CloudActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/cloud/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_surface_cloud_available-actions_with-status_post` @@ -337,6 +342,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -484,11 +490,12 @@ Parameters: - `osFamily__nin` [query, array]: The operating system family of the device (not in) - `scanStatus` [query, array]: The CDS malware scan status - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/cloud/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_surface_cloud_export_get` @@ -497,6 +504,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Surface_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Surface_Filters.md index ac1ce97..75d9fa2 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Surface_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Cloud_Surface_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/surface/cloud/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_surface_cloud_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -165,6 +167,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/cloud/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_surface_cloud_filters_count_get` @@ -173,6 +176,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -324,6 +328,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/cloud/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_surface_cloud_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Container.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Container.md index f326a59..4f3a11f 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Container.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Container.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/container` + **Assets** `operationId`: `_web_api_xdr_assets_container_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -237,6 +239,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/container` + **Assets using POST** `operationId`: `_web_api_xdr_assets_container_post` @@ -245,14 +248,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.container.schemas_ContainerViewInputSchema]: +- `body` [body, v2_1.inventory.container.schemas_ContainerViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/container/action` + **Perform action** `operationId`: `_web_api_xdr_assets_container_action_post` @@ -261,6 +266,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -476,11 +482,12 @@ Parameters: - `agentDiskEncryption` [query, array]: The agent disk encryption - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.container.schemas_ContainerActionPayloadSchema]: +- `body` [body, v2_1.inventory.container.schemas_ContainerActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/container/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_container_available-actions_with-status_post` @@ -489,6 +496,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -704,11 +712,12 @@ Parameters: - `agentDiskEncryption` [query, array]: The agent disk encryption - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/container/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_container_export_get` @@ -717,6 +726,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Container_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Container_Filters.md index f31fb4c..aaf18e6 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Container_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Container_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/container/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_container_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -233,6 +235,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/container/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_container_filters_count_get` @@ -241,6 +244,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -460,6 +464,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/container/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_container_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Analysis.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Analysis.md index 2ebf915..5c394d4 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Analysis.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Analysis.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/data-analysis` + **Assets** `operationId`: `_web_api_xdr_assets_data-analysis_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/data-analysis` + **Assets using POST** `operationId`: `_web_api_xdr_assets_data-analysis_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.data_analysis.schemas_DataAnalysisViewInputSchema]: +- `body` [body, v2_1.inventory.data_analysis.schemas_DataAnalysisViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/data-analysis/action` + **Perform action** `operationId`: `_web_api_xdr_assets_data-analysis_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.data_analysis.schemas_DataAnalysisActionPayloadSchema]: +- `body` [body, v2_1.inventory.data_analysis.schemas_DataAnalysisActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/data-analysis/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_data-analysis_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/data-analysis/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_data-analysis_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Analysis_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Analysis_Filters.md index 8cc0ec0..611188e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Analysis_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Analysis_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/data-analysis/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_data-analysis_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/data-analysis/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_data-analysis_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/data-analysis/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_data-analysis_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Store.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Store.md index f40fff9..5b42028 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Store.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Store.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/data-store` + **Assets** `operationId`: `_web_api_xdr_assets_data-store_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -119,6 +121,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/data-store` + **Assets using POST** `operationId`: `_web_api_xdr_assets_data-store_post` @@ -127,14 +130,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.data_store.schemas_DataStoreViewInputSchema]: +- `body` [body, v2_1.inventory.data_store.schemas_DataStoreViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/data-store/action` + **Perform action** `operationId`: `_web_api_xdr_assets_data-store_action_post` @@ -143,6 +148,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -240,11 +246,12 @@ Parameters: - `dataClassificationStatus__nin` [query, array]: Data Classification Status (not in) - `scanStatus` [query, array]: The CDS malware scan status - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.data_store.schemas_DataStoreActionPayloadSchema]: +- `body` [body, v2_1.inventory.data_store.schemas_DataStoreActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/data-store/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_data-store_available-actions_with-status_post` @@ -253,6 +260,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -350,11 +358,12 @@ Parameters: - `dataClassificationStatus__nin` [query, array]: Data Classification Status (not in) - `scanStatus` [query, array]: The CDS malware scan status - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/data-store/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_data-store_export_get` @@ -363,6 +372,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Store_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Store_Filters.md index 10f27ce..27b6c85 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Store_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Data_Store_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/data-store/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_data-store_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -115,6 +117,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/data-store/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_data-store_filters_count_get` @@ -123,6 +126,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -224,6 +228,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/data-store/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_data-store_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Developer_Tool.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Developer_Tool.md index 5e29d59..517403b 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Developer_Tool.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Developer_Tool.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/developer-tool` + **Assets** `operationId`: `_web_api_xdr_assets_developer-tool_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/developer-tool` + **Assets using POST** `operationId`: `_web_api_xdr_assets_developer-tool_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.developer_tool.schemas_DeveloperToolsViewInputSchema]: +- `body` [body, v2_1.inventory.developer_tool.schemas_DeveloperToolsViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/developer-tool/action` + **Perform action** `operationId`: `_web_api_xdr_assets_developer-tool_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.developer_tool.schemas_DeveloperToolActionPayloadSchema]: +- `body` [body, v2_1.inventory.developer_tool.schemas_DeveloperToolActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/developer-tool/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_developer-tool_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/developer-tool/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_developer-tool_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Developer_Tool_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Developer_Tool_Filters.md index d301fab..6986c65 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Developer_Tool_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Developer_Tool_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/developer-tool/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_developer-tool_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/developer-tool/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_developer-tool_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/developer-tool/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_developer-tool_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Device.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Device.md index b570d10..abe5e18 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Device.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Device.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/device` + **Assets** `operationId`: `_web_api_xdr_assets_device_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -121,6 +123,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/device` + **Assets using POST** `operationId`: `_web_api_xdr_assets_device_post` @@ -129,14 +132,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.device.schemas_DeviceViewInputSchema]: +- `body` [body, v2_1.inventory.device.schemas_DeviceViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/device/action` + **Perform action** `operationId`: `_web_api_xdr_assets_device_action_post` @@ -145,6 +150,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.edit, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -244,11 +250,12 @@ Parameters: - `osFamily__nin` [query, array]: The operating system family of the device (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.device.schemas_DeviceActionPayloadSchema]: +- `body` [body, v2_1.inventory.device.schemas_DeviceActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/device/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_device_available-actions_with-status_post` @@ -257,6 +264,7 @@ Get cloud inventory device available-actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -356,11 +364,12 @@ Parameters: - `osFamily__nin` [query, array]: The operating system family of the device (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/device/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_device_export_get` @@ -369,6 +378,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Device_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Device_Filters.md index 6b6c960..e0f72d0 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Device_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Device_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/device/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_device_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -117,6 +119,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/device/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_device_filters_count_get` @@ -125,6 +128,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -228,6 +232,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/device/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_device_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Endpoint_Surface.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Endpoint_Surface.md index 4ec3487..46c7ad3 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Endpoint_Surface.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Endpoint_Surface.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/surface/endpoint` + **Assets** `operationId`: `_web_api_xdr_assets_surface_endpoint_get` @@ -11,6 +12,7 @@ Get inventory endpoint assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -178,6 +180,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/endpoint` + **Assets using POST** `operationId`: `_web_api_xdr_assets_surface_endpoint_post` @@ -186,14 +189,16 @@ POST API to get endpoint assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.surfaces.endpoint.schemas_EndpointViewInputSchema]: +- `body` [body, v2_1.inventory.surfaces.endpoint.schemas_EndpointViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/endpoint/action` + **Perform action** `operationId`: `_web_api_xdr_assets_surface_endpoint_action_post` @@ -202,6 +207,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.edit, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -358,11 +364,12 @@ Parameters: - `osFamily__nin` [query, array]: The operating system family of the device (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.surfaces.endpoint.schemas_EndpointActionPayloadSchema]: +- `body` [body, v2_1.inventory.surfaces.endpoint.schemas_EndpointActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/endpoint/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_surface_endpoint_available-actions_with-status_post` @@ -371,6 +378,7 @@ Get endpoint available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -527,11 +535,12 @@ Parameters: - `osFamily__nin` [query, array]: The operating system family of the device (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/endpoint/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_surface_endpoint_export_get` @@ -540,6 +549,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Endpoint_Surface_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Endpoint_Surface_Filters.md index 35ea903..5360b01 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Endpoint_Surface_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Endpoint_Surface_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/surface/endpoint/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_surface_endpoint_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +176,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/endpoint/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_surface_endpoint_filters_count_get` @@ -182,6 +185,7 @@ Get Endpoint filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -342,6 +346,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/endpoint/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_surface_endpoint_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Filters.md index 70f89ed..81a116a 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Filters.md @@ -3,6 +3,7 @@ 4 endpoints. ## `GET /web/api/v2.1/xdr/assets/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -99,6 +101,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_filters_count_get` @@ -107,6 +110,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -192,6 +196,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/filters/csv-filter` + **Upload CSV file** `operationId`: `_web_api_xdr_assets_filters_csv-filter_post` @@ -200,6 +205,7 @@ Upload CSV file Required permissions: `XDR Inventory.view` Parameters: + - `surface` [formData, string] (enum: Cloud, Identity, Network Discovery, Endpoint): The surface that each resource belongs to - `category` [formData, string] (enum: All, Account, AI ML, Application Integration, Cloud Application, Code, Container, Data Analysis, Data Store, Developer Tool, Device, Function, Identity, Secrets, Server, Storage, Network, Governance, Workstation): The category that each resource belongs to - `excludeHeader` [formData, boolean] **required**: Set to True to exclude the column header @@ -209,6 +215,7 @@ Parameters: Responses: 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Function.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Function.md index bbf26f2..3425ad5 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Function.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Function.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/function` + **Assets** `operationId`: `_web_api_xdr_assets_function_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/function` + **Assets using POST** `operationId`: `_web_api_xdr_assets_function_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.function.schemas_FunctionsViewInputSchema]: +- `body` [body, v2_1.inventory.function.schemas_FunctionsViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/function/action` + **Perform action** `operationId`: `_web_api_xdr_assets_function_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.function.schemas_FunctionActionPayloadSchema]: +- `body` [body, v2_1.inventory.function.schemas_FunctionActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/function/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_function_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/function/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_function_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Function_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Function_Filters.md index bacb23f..738dfc0 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Function_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Function_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/function/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_function_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/function/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_function_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/function/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_function_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Governance.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Governance.md index 0937f50..f945b14 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Governance.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Governance.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/governance` + **Assets** `operationId`: `_web_api_xdr_assets_governance_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/governance` + **Assets using POST** `operationId`: `_web_api_xdr_assets_governance_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.governance.schemas_GovernanceViewInputSchema]: +- `body` [body, v2_1.inventory.governance.schemas_GovernanceViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/governance/action` + **Perform action** `operationId`: `_web_api_xdr_assets_governance_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.governance.schemas_GovernanceActionPayloadSchema]: +- `body` [body, v2_1.inventory.governance.schemas_GovernanceActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/governance/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_governance_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get cloud inventory governance available-actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/governance/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_governance_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Governance_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Governance_Filters.md index 13975b7..c9ab215 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Governance_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Governance_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/governance/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_governance_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/governance/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_governance_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/governance/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_governance_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity.md index 30b0262..a697ba2 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/identity` + **Assets** `operationId`: `_web_api_xdr_assets_identity_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -59,7 +61,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -73,7 +75,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -141,6 +143,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/identity` + **Assets using POST** `operationId`: `_web_api_xdr_assets_identity_post` @@ -149,14 +152,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.identity.schemas_IdentityViewInputSchema]: +- `body` [body, v2_1.inventory.identity.schemas_IdentityViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/identity/action` + **Perform action** `operationId`: `_web_api_xdr_assets_identity_action_post` @@ -165,6 +170,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -211,7 +217,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -225,7 +231,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -284,11 +290,12 @@ Parameters: - `enabled` [query, array]: Whether the Identity Group is enabled or not - `userPrincipalName__nin` [query, array]: The User Principal Name (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.identity.schemas_IdentityActionPayloadSchema]: +- `body` [body, v2_1.inventory.identity.schemas_IdentityActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/identity/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_identity_available-actions_with-status_post` @@ -297,6 +304,7 @@ Get cloud inventory identity available-actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -343,7 +351,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -357,7 +365,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -416,11 +424,12 @@ Parameters: - `enabled` [query, array]: Whether the Identity Group is enabled or not - `userPrincipalName__nin` [query, array]: The User Principal Name (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/identity/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_identity_export_get` @@ -429,6 +438,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -477,7 +487,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -491,7 +501,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Filters.md index f2445d7..f4fc6c8 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/identity/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_identity_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -59,7 +61,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -73,7 +75,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -137,6 +139,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/identity/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_identity_filters_count_get` @@ -145,6 +148,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -191,7 +195,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -205,7 +209,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -268,6 +272,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/identity/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_identity_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Surface.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Surface.md index 06e70cf..f25abdd 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Surface.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Surface.md @@ -3,6 +3,7 @@ 4 endpoints. ## `GET /web/api/v2.1/xdr/assets/surface/identity` + **Assets** `operationId`: `_web_api_xdr_assets_surface_identity_get` @@ -11,6 +12,7 @@ Get identity surface assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -59,7 +61,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -73,7 +75,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -141,6 +143,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/identity/action` + **Perform action** `operationId`: `_web_api_xdr_assets_surface_identity_action_post` @@ -149,6 +152,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -195,7 +199,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -209,7 +213,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -268,11 +272,12 @@ Parameters: - `enabled` [query, array]: Whether the Identity Group is enabled or not - `userPrincipalName__nin` [query, array]: The User Principal Name (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.identity.schemas_IdentityActionPayloadSchema]: +- `body` [body, v2_1.inventory.identity.schemas_IdentityActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/identity/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_surface_identity_available-actions_with-status_post` @@ -281,6 +286,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -327,7 +333,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -341,7 +347,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -400,11 +406,12 @@ Parameters: - `enabled` [query, array]: Whether the Identity Group is enabled or not - `userPrincipalName__nin` [query, array]: The User Principal Name (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/identity/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_surface_identity_export_get` @@ -413,6 +420,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -461,7 +469,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -475,7 +483,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Surface_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Surface_Filters.md index ac613fa..34a0828 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Surface_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Identity_Surface_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/surface/identity/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_surface_identity_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -59,7 +61,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -73,7 +75,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -137,6 +139,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/identity/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_surface_identity_filters_count_get` @@ -145,6 +148,7 @@ Get identity filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -191,7 +195,7 @@ Parameters: - `mail__contains` [query, array]: The Email Address - `cloudTagsKeyValue` [query, array]: The cloud tags key value - `accountIds` [query, array]: List of Account IDs to filter by -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `domain__contains` [query, array]: The AD Domain Name - `resourceType__nin` [query, array]: The canonical name for the resource type (not in) - `cn` [query, array]: The LDAP Common Name @@ -205,7 +209,7 @@ Parameters: - `assetEnvironment` [query, array]: The environment that the asset exists in - AWS | Azure | GCP | Active Directory - `surfaces__nin` [query, array]: The Surface that each asset belongs to (not in) - `forest__nin` [query, array]: The Forest Name (not in) -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `id__in` [query, array]: The ID - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type @@ -268,6 +272,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/identity/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_surface_identity_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network.md index 8f5616a..a7ba1b3 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/network` + **Assets** `operationId`: `_web_api_xdr_assets_network_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -94,6 +96,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/network` + **Assets using POST** `operationId`: `_web_api_xdr_assets_network_post` @@ -102,14 +105,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.network.schemas_NetworkViewInputSchema]: +- `body` [body, v2_1.inventory.network.schemas_NetworkViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/network/action` + **Perform action** `operationId`: `_web_api_xdr_assets_network_action_post` @@ -118,6 +123,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -190,11 +196,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.network.schemas_NetworkActionPayloadSchema]: +- `body` [body, v2_1.inventory.network.schemas_NetworkActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/network/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_network_available-actions_with-status_post` @@ -203,6 +210,7 @@ Get cloud inventory network available-actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -275,11 +283,12 @@ Parameters: - `countsFor` [query, array]: The columns for which filter count would be returned for - `cloudTagsKeyValue__contains` [query, array]: Free-text filter by cloud tag key value (supports multiple values) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/network/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_network_export_get` @@ -288,6 +297,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Discovery_Surface.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Discovery_Surface.md index 413ef9a..863e83d 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Discovery_Surface.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Discovery_Surface.md @@ -3,6 +3,7 @@ 4 endpoints. ## `GET /web/api/v2.1/xdr/assets/surface/networkDiscovery` + **Assets** `operationId`: `_web_api_xdr_assets_surface_networkDiscovery_get` @@ -11,6 +12,7 @@ Get inventory of Network Discovery surface assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -122,6 +124,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/networkDiscovery/action` + **Perform action** `operationId`: `_web_api_xdr_assets_surface_networkDiscovery_action_post` @@ -130,6 +133,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.edit, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -230,11 +234,12 @@ Parameters: - `osFamily__nin` [query, array]: The operating system family of the device (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.surfaces.network_discovery.schemas_NetworkDiscoveryActionPayloadSchema]: +- `body` [body, v2_1.inventory.surfaces.network_discovery.schemas_NetworkDiscoveryActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/surface/networkDiscovery/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_surface_networkDiscovery_available-actions_with-status_post` @@ -243,6 +248,7 @@ Get inventory network discovery available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -343,11 +349,12 @@ Parameters: - `osFamily__nin` [query, array]: The operating system family of the device (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/networkDiscovery/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_surface_networkDiscovery_export_get` @@ -356,6 +363,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Discovery_Surface_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Discovery_Surface_Filters.md index a345594..76412d6 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Discovery_Surface_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Discovery_Surface_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/surface/networkDiscovery/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_surface_networkDiscovery_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -118,6 +120,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/networkDiscovery/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_surface_networkDiscovery_filters_count_get` @@ -126,6 +129,7 @@ Get Network Discovery filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -230,6 +234,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/surface/networkDiscovery/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_surface_networkDiscovery_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Filters.md index 939f80f..b038c4a 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Network_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/network/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_network_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -90,6 +92,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/network/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_network_filters_count_get` @@ -98,6 +101,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `resourceType__contains` [query, array]: The Asset Type - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -174,6 +178,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/network/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_network_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Notes.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Notes.md index 6f558a8..a2bf0cf 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Notes.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Notes.md @@ -3,6 +3,7 @@ 2 endpoints. ## `DELETE /web/api/v2.1/xdr/assets/notes` + **Delete note** `operationId`: `_web_api_xdr_assets_notes_delete` @@ -11,14 +12,16 @@ Delete note Required permissions: `XDR Inventory.delete` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.notes.schemas_InventoryNotesPayloadSchema]: +- `body` [body, v2_1.inventory.notes.schemas_InventoryNotesPayloadSchema]: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/notes` + **Create or update note against asset** `operationId`: `_web_api_xdr_assets_notes_post` @@ -27,9 +30,10 @@ create or update note Required permissions: `XDR Inventory.create` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.notes.schemas_InventoryNotesPayloadSchema]: +- `body` [body, v2_1.inventory.notes.schemas_InventoryNotesPayloadSchema]: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Server.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Server.md index 971ac02..17470c9 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Server.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Server.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/server` + **Assets** `operationId`: `_web_api_xdr_assets_server_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -251,6 +253,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/server` + **Assets using POST** `operationId`: `_web_api_xdr_assets_server_post` @@ -259,14 +262,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.server.schemas_ServerViewInputSchema]: +- `body` [body, v2_1.inventory.server.schemas_ServerViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/server/action` + **Perform action** `operationId`: `_web_api_xdr_assets_server_action_post` @@ -275,6 +280,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -504,11 +510,12 @@ Parameters: - `agentDiskEncryption` [query, array]: The agent disk encryption - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.server.schemas_ServerActionPayloadSchema]: +- `body` [body, v2_1.inventory.server.schemas_ServerActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/server/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_server_available-actions_with-status_post` @@ -517,6 +524,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -746,11 +754,12 @@ Parameters: - `agentDiskEncryption` [query, array]: The agent disk encryption - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/server/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_server_export_get` @@ -759,6 +768,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Server_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Server_Filters.md index ed8445c..dda132b 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Server_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Server_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/server/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_server_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -247,6 +249,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/server/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_server_filters_count_get` @@ -255,6 +258,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -488,6 +492,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/server/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_server_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Storage.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Storage.md index 53126bb..782503c 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Storage.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Storage.md @@ -3,6 +3,7 @@ 5 endpoints. ## `GET /web/api/v2.1/xdr/assets/storage` + **Assets** `operationId`: `_web_api_xdr_assets_storage_get` @@ -11,6 +12,7 @@ Get assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -117,6 +119,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/storage` + **Assets using POST** `operationId`: `_web_api_xdr_assets_storage_post` @@ -125,14 +128,16 @@ POST API to get Assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.storage.schemas_StorageViewInputSchema]: +- `body` [body, v2_1.inventory.storage.schemas_StorageViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/storage/action` + **Perform action** `operationId`: `_web_api_xdr_assets_storage_action_post` @@ -141,6 +146,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.create, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -238,11 +244,12 @@ Parameters: - `dataClassificationStatus__nin` [query, array]: Data Classification Status (not in) - `scanStatus` [query, array]: The CDS malware scan status - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.storage.schemas_StorageActionPayloadSchema]: +- `body` [body, v2_1.inventory.storage.schemas_StorageActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/storage/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_storage_available-actions_with-status_post` @@ -251,6 +258,7 @@ Get available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -348,11 +356,12 @@ Parameters: - `dataClassificationStatus__nin` [query, array]: Data Classification Status (not in) - `scanStatus` [query, array]: The CDS malware scan status - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/storage/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_storage_export_get` @@ -361,6 +370,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Storage_Filters.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Storage_Filters.md index 0a3c972..5964e9e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Storage_Filters.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Storage_Filters.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/xdr/assets/storage/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_storage_filters_autocomplete_get` @@ -11,6 +12,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -115,6 +117,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/storage/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_storage_filters_count_get` @@ -123,6 +126,7 @@ Get filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) @@ -224,6 +228,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/storage/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_storage_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Tags.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Tags.md index b95e038..067f0c5 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Tags.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Tags.md @@ -3,6 +3,7 @@ 4 endpoints. ## `POST /web/api/v2.1/xdr/assets/fetch-tags` + **Get tags info of assets by asset id** `operationId`: `_web_api_xdr_assets_fetch-tags_post` @@ -11,6 +12,7 @@ Get tags info for all assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) - `missingCoverage` [query, array]: The missing coverage for the asset @@ -99,11 +101,12 @@ Parameters: - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.unified_actions.schemas_AffectedEntitiesSchema]: +- `body` [body, v2_1.inventory.unified_actions.schemas_AffectedEntitiesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/tags` + **Fetch all Unique Tags** `operationId`: `_web_api_xdr_assets_tags_get` @@ -112,6 +115,7 @@ Fetch all tags removing duplicates Required permissions: `XDR Inventory.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". - `includeParent` [query, boolean]: Include parent - `groupIds` [query, array]: List of Group IDs to filter by @@ -133,6 +137,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/tags/count` + **Get count of assets by tag id** `operationId`: `_web_api_xdr_assets_tags_count_post` @@ -141,14 +146,16 @@ Get asset count for given tag ids Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.tags.schemas_InventoryTagsCountPayloadSchema]: +- `body` [body, v2_1.inventory.tags.schemas_InventoryTagsCountPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/tags/filters-count` + **Get asset tags filters count** `operationId`: `_web_api_xdr_assets_tags_filters-count_get` @@ -157,6 +164,7 @@ Get asset tags filters count Required permissions: `XDR Inventory.view` Parameters: + - `includeParent` [query, boolean]: Include parent - `groupIds` [query, array]: List of Group IDs to filter by - `accountIds` [query, array]: List of Account IDs to filter by diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Unified_Actions.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Unified_Actions.md index 49ceb31..2b6b8fa 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Unified_Actions.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Unified_Actions.md @@ -3,6 +3,7 @@ 3 endpoints. ## `POST /web/api/v2.1/xdr/assets/actions/fetch-agent-ids` + **Loads all agent ids for the unified actions** `operationId`: `_web_api_xdr_assets_actions_fetch-agent-ids_post` @@ -11,6 +12,7 @@ Loads all agent ids for the unified actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -119,7 +121,7 @@ Parameters: - `serialNumber__nin` [query, array]: The serial number (not in) - `agentDecommissioned` [query, array]: Whether the agent is decommissioned - `agentSubscribeOnDt__between` [query, string]: The agent subscribe time -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `expirationTime__between` [query, array]: Expiration Time - `domain__contains` [query, array]: The domain - `osNameVersion__contains` [query, array]: The OS names and versions @@ -157,7 +159,7 @@ Parameters: - `adsEnabled` [query, array]: ADS Enabled - `rangerTagsKey__nin` [query, array]: The ranger tags key (not in) - `encryptionType` [query, array]: The encryption type -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `agentLocation` [query, array]: The agent location - `agentPendingActions__nin` [query, array]: The agent pending actions (not in) - `id__in` [query, array]: The ID @@ -311,11 +313,12 @@ Parameters: - `enabled` [query, array]: Whether the Identity Group is enabled or not - `userPrincipalName__nin` [query, array]: The User Principal Name (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.unified_actions.schemas_AffectedEntitiesSchema]: +- `body` [body, v2_1.inventory.unified_actions.schemas_AffectedEntitiesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/actions/fetch-unified-actions` + **Get Available Actions by Asset/Entity Type** `operationId`: `_web_api_xdr_assets_actions_fetch-unified-actions_post` @@ -324,6 +327,7 @@ Get Available Actions by Asset/Entity Type Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -432,7 +436,7 @@ Parameters: - `serialNumber__nin` [query, array]: The serial number (not in) - `agentDecommissioned` [query, array]: Whether the agent is decommissioned - `agentSubscribeOnDt__between` [query, string]: The agent subscribe time -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `expirationTime__between` [query, array]: Expiration Time - `domain__contains` [query, array]: The domain - `osNameVersion__contains` [query, array]: The OS names and versions @@ -470,7 +474,7 @@ Parameters: - `adsEnabled` [query, array]: ADS Enabled - `rangerTagsKey__nin` [query, array]: The ranger tags key (not in) - `encryptionType` [query, array]: The encryption type -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `agentLocation` [query, array]: The agent location - `agentPendingActions__nin` [query, array]: The agent pending actions (not in) - `id__in` [query, array]: The ID @@ -624,11 +628,12 @@ Parameters: - `enabled` [query, array]: Whether the Identity Group is enabled or not - `userPrincipalName__nin` [query, array]: The User Principal Name (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.unified_actions.schemas_AffectedEntitiesSchema]: +- `body` [body, v2_1.inventory.unified_actions.schemas_AffectedEntitiesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/actions/perform-unified-action` + **Perform an Action on selected assets/entities** `operationId`: `_web_api_xdr_assets_actions_perform-unified-action_post` @@ -637,6 +642,7 @@ Perform an Action on selected assets/entities Required permissions: `XDR Inventory.create, XDR Inventory.delete, XDR Inventory.edit` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -745,7 +751,7 @@ Parameters: - `serialNumber__nin` [query, array]: The serial number (not in) - `agentDecommissioned` [query, array]: Whether the agent is decommissioned - `agentSubscribeOnDt__between` [query, string]: The agent subscribe time -- `onPremisesDistinguishedName__contains` [query, array]: +- `onPremisesDistinguishedName__contains` [query, array]: - `expirationTime__between` [query, array]: Expiration Time - `domain__contains` [query, array]: The domain - `osNameVersion__contains` [query, array]: The OS names and versions @@ -783,7 +789,7 @@ Parameters: - `adsEnabled` [query, array]: ADS Enabled - `rangerTagsKey__nin` [query, array]: The ranger tags key (not in) - `encryptionType` [query, array]: The encryption type -- `onPremisesSecurityIdentifier__contains` [query, array]: +- `onPremisesSecurityIdentifier__contains` [query, array]: - `agentLocation` [query, array]: The agent location - `agentPendingActions__nin` [query, array]: The agent pending actions (not in) - `id__in` [query, array]: The ID @@ -937,6 +943,6 @@ Parameters: - `enabled` [query, array]: Whether the Identity Group is enabled or not - `userPrincipalName__nin` [query, array]: The User Principal Name (not in) - `cloudProviderProjectId__contains` [query, array]: The cloud provider project ID -- `body` [body, v2_1.inventory.unified_actions.schemas_PerformActionRequestSchema]: +- `body` [body, v2_1.inventory.unified_actions.schemas_PerformActionRequestSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Workstation.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Workstation.md index 14b4960..91e1953 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Workstation.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Inventory_Workstation.md @@ -3,6 +3,7 @@ 8 endpoints. ## `GET /web/api/v2.1/xdr/assets/workstation` + **Assets** `operationId`: `_web_api_xdr_assets_workstation_get` @@ -11,6 +12,7 @@ Get inventory workstation assets Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -198,6 +200,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/workstation` + **Assets using POST** `operationId`: `_web_api_xdr_assets_workstation_post` @@ -206,14 +209,16 @@ POST API to get workstation assets Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.workstation.schemas_WorkstationViewInputSchema]: +- `body` [body, v2_1.inventory.workstation.schemas_WorkstationViewInputSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/workstation/action` + **Perform action** `operationId`: `_web_api_xdr_assets_workstation_action_post` @@ -222,6 +227,7 @@ Perform action on selected assets Required permissions: `XDR Inventory.edit, XDR Inventory.delete` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -398,11 +404,12 @@ Parameters: - `agentDiskEncryption` [query, array]: The agent disk encryption - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.workstation.schemas_WorkstationActionPayloadSchema]: +- `body` [body, v2_1.inventory.workstation.schemas_WorkstationActionPayloadSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/xdr/assets/workstation/available-actions/with-status` + **Available actions** `operationId`: `_web_api_xdr_assets_workstation_available-actions_with-status_post` @@ -411,6 +418,7 @@ Get inventory workstation available actions Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -587,11 +595,12 @@ Parameters: - `agentDiskEncryption` [query, array]: The agent disk encryption - `infectionStatus__nin` [query, array]: The status alerts of the asset (not in) - `osNameVersion` [query, array]: The operating system name and version of the device -- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: +- `body` [body, v2_1.inventory.schemas_AffectedResourcesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/workstation/export` + **Export assets to CSV or JSON** `operationId`: `_web_api_xdr_assets_workstation_export_get` @@ -600,6 +609,7 @@ Returns the results for given inventory filter in a CSV or JSON format Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -788,6 +798,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/workstation/filters/autocomplete` + **Auto Complete** `operationId`: `_web_api_xdr_assets_workstation_filters_autocomplete_get` @@ -796,6 +807,7 @@ Use this command to get values for other fields. When you send this command with Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -979,6 +991,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/workstation/filters/count` + **Filter counts** `operationId`: `_web_api_xdr_assets_workstation_filters_count_get` @@ -987,6 +1000,7 @@ Get workstation filter counts Required permissions: `XDR Inventory.view` Parameters: + - `tagsKey__contains` [query, array]: Free-text filter by tag key (supports multiple values) - `agentOperationalState__nin` [query, array]: The agent operational state (not in) - `assetCriticality__nin` [query, array]: The criticality that each asset belongs to (not in) @@ -1167,6 +1181,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/xdr/assets/workstation/filters/free-text` + **Free text filters** `operationId`: `_web_api_xdr_assets_workstation_filters_free-text_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Live_Updates.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Live_Updates.md index 1a2c83d..5830f0f 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Live_Updates.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Live_Updates.md @@ -3,6 +3,7 @@ 1 endpoints. ## `GET /web/api/v2.1/content-updates-inventory` + **Get Agent Merged Updates** `operationId`: `_web_api_content-updates-inventory_get` @@ -11,6 +12,7 @@ Get Agent's merged updates. Required permissions: `Endpoints.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Locations.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Locations.md index 09006e1..f50c8f4 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Locations.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Locations.md @@ -3,6 +3,7 @@ 4 endpoints. ## `DELETE /web/api/v2.1/locations` + **Delete Locations** `operationId`: `_web_api_locations_delete` @@ -11,11 +12,13 @@ Delete location definitions of a given location. To get location IDs, run "locat Required permissions: `Locations.delete` Parameters: -- `body` [body, locations.schemas_DeleteLocationsSchema]: + +- `body` [body, locations.schemas_DeleteLocationsSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/locations` + **Get Locations** `operationId`: `_web_api_locations_get` @@ -24,6 +27,7 @@ Get the locations of Agents in a given scope that match the filter. Agent locat Required permissions: `Locations.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -48,19 +52,22 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/locations` + **Create Location** `operationId`: `_web_api_locations_post` -Create a location that defines parameters of Agents in a scope filter. Parameters include:
* ipAddresses - The Agent compares the endpoint active IPv4 or IPv6 addresses to the IP addresses, ranges, and CIDRs defined for the location.
* dnsServers - The Agent compares the configured DNS servers of the endpoint to the DNS servers defined for the location.
* dnsLookup - The Agent resolves the FQDN of the endpoint to IPv4 or IPv6 addresses and compares them to the addresses configured in the location setting.
* networkInterfaces - The Agent determines if the endpoint is connected to the network over a wireless connection. If one connected interface is wireless, the endpoint is considered wireless.
* serverConnectivity - The Agent reports if it is connected to its Management.
* registryKeys - The Agent compares the endpoint registry keys in HKEY_LOCAL_MACHINE\SOFTWARE with the registry key of the location definition.
When you set a location parameter, also set the operator to ALL, NONE, or at least 1.
The serverConnectivity parameter takes "enabled" (true or false) and "value" (connected or disconnected).
The networkInterfaces parameter takes "enabled" (t … +Create a location that defines parameters of Agents in a scope filter. Parameters include:
*ipAddresses - The Agent compares the endpoint active IPv4 or IPv6 addresses to the IP addresses, ranges, and CIDRs defined for the location.
* dnsServers - The Agent compares the configured DNS servers of the endpoint to the DNS servers defined for the location.
*dnsLookup - The Agent resolves the FQDN of the endpoint to IPv4 or IPv6 addresses and compares them to the addresses configured in the location setting.
* networkInterfaces - The Agent determines if the endpoint is connected to the network over a wireless connection. If one connected interface is wireless, the endpoint is considered wireless.
*serverConnectivity - The Agent reports if it is connected to its Management.
* registryKeys - The Agent compares the endpoint registry keys in HKEY_LOCAL_MACHINE\SOFTWARE with the registry key of the location definition.
When you set a location parameter, also set the operator to ALL, NONE, or at least 1.
The serverConnectivity parameter takes "enabled" (true or false) and "value" (connected or disconnected).
The networkInterfaces parameter takes "enabled" (t … Required permissions: `Locations.create` Parameters: -- `body` [body, locations.schemas_NewLocationSchema]: + +- `body` [body, locations.schemas_NewLocationSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/locations/{location_id}` + **Update Location** `operationId`: `_web_api_locations_{location_id}_put` @@ -69,7 +76,8 @@ Change the parameter values of a location definition. See Create Location. Required permissions: `Locations.edit` Parameters: + - `location_id` [path, string] **required**: Location ID. Example: "225494730938493804". -- `body` [body, locations.schemas_UpdateLocationSchema]: +- `body` [body, locations.schemas_UpdateLocationSchema]: Responses: 404 Location not found, 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Log_Collection.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Log_Collection.md index 1796742..474f506 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Log_Collection.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Log_Collection.md @@ -3,6 +3,7 @@ 9 endpoints. ## `GET /web/api/v2.1/log-collection/agent-type-count` + **Get Agent type count** `operationId`: `_web_api_log-collection_agent-type-count_get` @@ -11,6 +12,7 @@ Get the total number of log collection rules per agent type Required permissions: `Log Collection Rules.view` Parameters: + - `accountIds` [query, array]: List of account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -21,23 +23,27 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/log-collection/rules` + **Delete log collection rules** `operationId`: `_web_api_log-collection_rules_delete` Required permissions: `Log Collection Rules.delete` Parameters: -- `body` [body, log_collection.schemas.delete_schema_LogCollectionRulesDeleteSchema]: + +- `body` [body, log_collection.schemas.delete_schema_LogCollectionRulesDeleteSchema]: Responses: 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/log-collection/rules` + **Get Log Collection rules** `operationId`: `_web_api_log-collection_rules_get` Required permissions: `Log Collection Rules.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -94,28 +100,33 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/log-collection/rules` + **Create a log collection rule** `operationId`: `_web_api_log-collection_rules_post` Required permissions: `Log Collection Rules.create` Parameters: -- `body` [body, log_collection.schemas.post_schema_LogCollectionRulesPostSchema]: + +- `body` [body, log_collection.schemas.post_schema_LogCollectionRulesPostSchema]: Responses: 400 Invalid user input received. See error details for further i, 409 Conflict, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/log-collection/rules/activation` + **Change activation status of log collection rules** `operationId`: `_web_api_log-collection_rules_activation_post` Required permissions: `Log Collection Rules.activate` Parameters: -- `body` [body, log_collection.schemas.post_schema_LogCollectionRulesActivationPostSchema]: + +- `body` [body, log_collection.schemas.post_schema_LogCollectionRulesActivationPostSchema]: Responses: 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/log-collection/rules/export` + **Export log collection rules** `operationId`: `_web_api_log-collection_rules_export_get` @@ -124,6 +135,7 @@ Get a CSV file with all log collection rules according to the passed filters Required permissions: `Log Collection Rules.view` Parameters: + - `accountIds` [query, array]: List of account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -173,6 +185,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/log-collection/rules/export/{agent_type}` + **Export log collection rules** `operationId`: `_web_api_log-collection_rules_export_{agent_type}_get` @@ -181,6 +194,7 @@ Get a CSV file with all log collection rules according to the passed filters Required permissions: `Log Collection Rules.view` Parameters: + - `agent_type` [path, string] **required**: Agent type - `accountIds` [query, array]: List of account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of site IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -231,12 +245,14 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/log-collection/rules/{agent_type}` + **Get Log Collection rules by agent type** `operationId`: `_web_api_log-collection_rules_{agent_type}_get` Required permissions: `Log Collection Rules.view` Parameters: + - `agent_type` [path, string] **required**: Agent type - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". @@ -294,13 +310,15 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/log-collection/rules/{rule_id}` + **Update a log collection rule** `operationId`: `_web_api_log-collection_rules_{rule_id}_put` Required permissions: `Log Collection Rules.edit` Parameters: + - `rule_id` [path, string] **required**: Rule id -- `body` [body, log_collection.schemas.post_schema_LogCollectionRulesPostSchema]: +- `body` [body, log_collection.schemas.post_schema_LogCollectionRulesPostSchema]: Responses: 400 Invalid user input received. See error details for further i, 409 Conflict, 200 Success, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Long_Running_Query.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Long_Running_Query.md index 7110c77..500f6d4 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Long_Running_Query.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Long_Running_Query.md @@ -5,41 +5,47 @@ **This is the canonical programmatic query path for this tenant.** It supersedes the Deep Visibility query endpoints (`/dv/init-query`, `/dv/query-status`, `/dv/events`, `/dv/events/pq`, `/dv/events/pq-ping`) and the V1 SDL endpoints (`/api/query`, `/api/powerQuery`), all of which sunset on 2027-02-15. LRQ is async (launch + poll + cancel), uses Bearer auth (not ApiToken - same JWT as the Mgmt API, different prefix), and lives on the tenant's own console host. The `X-Dataset-Query-Forward-Tag` response header from the launch must be echoed on every subsequent GET/DELETE. Required body: `queryType` (`"PQ"` or `"LOG"`), `tenant: true` (or `accountIds` + `tenant: false`), `startTime`/`endTime`, and `pq.query` + `pq.resultType` for PQ. Per-user rate cap is 3 rps - see the `powerquery` skill's `references/lrq-api.md` for slicing and two-JWT round-robin patterns. ## `POST /sdl/v2/api/queries` + **Launch a query** `operationId`: `_sdl_v2_api_queries_post` -Returns a `QueryResult` containing the query identifier, status, and, if the query completes immediately, the full result set in the `data` field. If the query is still processing, the `data` field will be null. Use the returned query id to poll the query via GET `/v2/api/queries/{id}` until results are available. The API rate limit is 100 requests per second. Log queries have a default limit of 1000 events but by paginating can be used to return essentially unlimited results. Power query results are subject to the standard power query limits on row count and memory consumption. A successful response will also include a X-Dataset-Query-Forward-Tag header that must be applied to the subsequent Poll and Delete requests for routing. Authorization is accomplished via a Bearer service token in the request header. Service tokens are not tied to a specific user. Learn more about service users here: https://community.sentinelone.com/s/article/000005290. Due to limitations, clicking "Run on console" may not work as expected, and the "Body Sample" may not be accurate because of polymorphism in the body. Refer to the "Body Schema" instead. Only one set of attributes like `log` or `pq` should … +Returns a `QueryResult` containing the query identifier, status, and, if the query completes immediately, the full result set in the `data` field. If the query is still processing, the `data` field will be null. Use the returned query id to poll the query via GET `/v2/api/queries/{id}` until results are available. The API rate limit is 100 requests per second. Log queries have a default limit of 1000 events but by paginating can be used to return essentially unlimited results. Power query results are subject to the standard power query limits on row count and memory consumption. A successful response will also include a X-Dataset-Query-Forward-Tag header that must be applied to the subsequent Poll and Delete requests for routing. Authorization is accomplished via a Bearer service token in the request header. Service tokens are not tied to a specific user. Learn more about service users here: . Due to limitations, clicking "Run on console" may not work as expected, and the "Body Sample" may not be accurate because of polymorphism in the body. Refer to the "Body Schema" instead. Only one set of attributes like `log` or `pq` should … Required permissions: `Skylight Query API.view` Parameters: -- `body` [body, object]: + +- `body` [body, object]: Responses: 200 Query launched successfully. ## `DELETE /sdl/v2/api/queries/{id}` + **Delete query** `operationId`: `_sdl_v2_api_queries_{id}_delete` -Remove query from the list of launched queries. Clients are required to call this after their query is complete. Subsequent polls using specified token will return not found response. The X-Dataset-Query-Forward-Tag header value from the launch query response must be applied for routing. Authorization is accomplished via a Bearer service token in the request header. Service tokens are not tied to a specific user. Learn more about service users here: https://community.sentinelone.com/s/article/000005290. Due to limitations, clicking "Run on console" may not work as expected +Remove query from the list of launched queries. Clients are required to call this after their query is complete. Subsequent polls using specified token will return not found response. The X-Dataset-Query-Forward-Tag header value from the launch query response must be applied for routing. Authorization is accomplished via a Bearer service token in the request header. Service tokens are not tied to a specific user. Learn more about service users here: . Due to limitations, clicking "Run on console" may not work as expected Required permissions: `Skylight Query API.view` Parameters: + - `id` [path, string] **required**: The unique query identifier - `X-Dataset-Query-Forward-Tag` [header, string] **required**: routing header Responses: 204 Query has been removed successfully ## `GET /sdl/v2/api/queries/{id}` + **Poll query** `operationId`: `_sdl_v2_api_queries_{id}_get` -Poll a previously launched query by its unique identifier. Responses will return a `QueryResult`. If the query has not yet completed, the `data` field will be null. Once the query completes, `data` will contain the full result set (up to configured limits). It is recommended to poll every second; queries expire after the configured TTL (default 30 seconds). The X-Dataset-Query-Forward-Tag header value from the launch query response must be applied for routing. Authorization is accomplished via a Bearer service token in the request header. Service tokens are not tied to a specific user. Learn more about service users here: https://community.sentinelone.com/s/article/000005290. Due to limitations, clicking "Run on console" may not work as expected +Poll a previously launched query by its unique identifier. Responses will return a `QueryResult`. If the query has not yet completed, the `data` field will be null. Once the query completes, `data` will contain the full result set (up to configured limits). It is recommended to poll every second; queries expire after the configured TTL (default 30 seconds). The X-Dataset-Query-Forward-Tag header value from the launch query response must be applied for routing. Authorization is accomplished via a Bearer service token in the request header. Service tokens are not tied to a specific user. Learn more about service users here: . Due to limitations, clicking "Run on console" may not work as expected Required permissions: `Skylight Query API.view` Parameters: + - `id` [path, string] **required**: The unique query identifier - `lastStepSeen` [query, integer] **required**: The step to start return result from - `X-Dataset-Query-Forward-Tag` [header, string] **required**: routing header diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Mobile_Integration.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Mobile_Integration.md index b4f390c..7c03049 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Mobile_Integration.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Mobile_Integration.md @@ -3,6 +3,7 @@ 11 endpoints. ## `GET /web/api/v2.1/mobile-integration/devices` + **Devices - Get list of devices for specific scope** `operationId`: `_web_api_mobile-integration_devices_get` @@ -11,6 +12,7 @@ Devices - Get list devices for specific scope Required permissions: `Mobile Endpoints.view` Parameters: + - `privileges__in` [query, array]: Include devices only with given privileges. Example: "none". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `model__contains` [query, string]: Include devices by models that contain text @@ -36,6 +38,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/mobile-integration/incidents` + **Incidents - Get list of incidents** `operationId`: `_web_api_mobile-integration_incidents_get` @@ -44,6 +47,7 @@ Incidents - Get list of incidents Required permissions: `Mobile Alerts.view, Mobile Threats.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `countOnly` [query, boolean]: If true, only total number of items will be returned, without any of the actual objects. @@ -68,6 +72,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/mobile-integration/mssp-provisioning/partner` + **Provision - Get MSSP partner with admin user** `operationId`: `_web_api_mobile-integration_mssp-provisioning_partner_get` @@ -76,6 +81,7 @@ Gets MSSP partner with the first admin user by scope Required permissions: `Mobile Integrations.view` Parameters: + - `tenant` [query, boolean]: Indicates a tenant scope request - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -84,6 +90,7 @@ Parameters: Responses: 200 MSSP partner retrieved, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/mobile-integration/mssp-provisioning/partner` + **Provision - Provision MSSP partner with admin user** `operationId`: `_web_api_mobile-integration_mssp-provisioning_partner_post` @@ -92,11 +99,13 @@ Provision a new MSSP partner and create an admin user for the partner account Required permissions: `Mobile Integrations.manage` Parameters: -- `body` [body, v2_1.provisioning.schemas_ProvisionWithUserRequestWrapper]: + +- `body` [body, v2_1.provisioning.schemas_ProvisionWithUserRequestWrapper]: Responses: 403 403 - You do not have authorization to complete request., 401 Unauthorized access - please sign in and retry., 400 Invalid user input received. See error details for further i, 201 MSSP partner provisioned and admin user created ## `GET /web/api/v2.1/mobile-integration/provisioning/can-provision-tenant` + **Provision - Check if tenant can be provisioned** `operationId`: `_web_api_mobile-integration_provisioning_can-provision-tenant_get` @@ -105,6 +114,7 @@ Checks if tenant can be provisioned by scope Required permissions: `Mobile Integrations.view` Parameters: + - `tenant` [query, boolean]: Indicates a tenant scope request - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -113,6 +123,7 @@ Parameters: Responses: 200 Tenant retrieved, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/mobile-integration/provisioning/partner-key` + **Provision - Get MSSP partner key** `operationId`: `_web_api_mobile-integration_provisioning_partner-key_get` @@ -121,6 +132,7 @@ Gets MSSP partner key by scope Required permissions: `Mobile Integrations.view` Parameters: + - `tenant` [query, boolean]: Indicates a tenant scope request - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -129,6 +141,7 @@ Parameters: Responses: 200 Partner key retrieved successfully., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/mobile-integration/provisioning/partner-key` + **Provision - Persist MSSP partner key** `operationId`: `_web_api_mobile-integration_provisioning_partner-key_post` @@ -137,11 +150,13 @@ Persists MSSP partner key - client ID and secret - for future customer provision Required permissions: `Mobile Integrations.manage` Parameters: -- `body` [body, v2_1.provisioning.schemas_PartnerKeyRequestWrapper]: + +- `body` [body, v2_1.provisioning.schemas_PartnerKeyRequestWrapper]: Responses: 403 403 - You do not have authorization to complete request., 401 Unauthorized access - please sign in and retry., 400 Invalid user input received. See error details for further i, 201 Partner key persisted successfully. ## `PUT /web/api/v2.1/mobile-integration/provisioning/partner-key` + **Provision - Update MSSP partner key** `operationId`: `_web_api_mobile-integration_provisioning_partner-key_put` @@ -150,11 +165,13 @@ Updates MSSP partner key - client ID and secret - for future customer provisioni Required permissions: `Mobile Integrations.manage` Parameters: -- `body` [body, v2_1.provisioning.schemas_PartnerKeyRequestWrapper]: + +- `body` [body, v2_1.provisioning.schemas_PartnerKeyRequestWrapper]: Responses: 404 404 - Partner key not found., 403 403 - You do not have authorization to complete request., 401 Unauthorized access - please sign in and retry., 400 Invalid user input received. See error details for further i, 201 Partner key updated successfully. ## `DELETE /web/api/v2.1/mobile-integration/provisioning/partner-key/{client_id}` + **Deletes MSSP partner key by client ID** `operationId`: `_web_api_mobile-integration_provisioning_partner-key_{client_id}_delete` @@ -163,12 +180,14 @@ Provision - Delete MSSP partner key Required permissions: `Mobile Integrations.manage` Parameters: + - `client_id` [path, string] **required**: Client id -- `body` [body, v2_1.provisioning.schemas_DeletePartnerKeyRequestSchema]: +- `body` [body, v2_1.provisioning.schemas_DeletePartnerKeyRequestSchema]: Responses: 404 Partner key not found, 204 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/mobile-integration/provisioning/tenant` + **Provision - Get tenant with users** `operationId`: `_web_api_mobile-integration_provisioning_tenant_get` @@ -177,6 +196,7 @@ Gets tenant with users by scope Required permissions: `Mobile Integrations.view` Parameters: + - `tenant` [query, boolean]: Indicates a tenant scope request - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -185,6 +205,7 @@ Parameters: Responses: 200 Tenant retrieved, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/mobile-integration/provisioning/tenant` + **Provision - Provision tenant with admin user** `operationId`: `_web_api_mobile-integration_provisioning_tenant_post` @@ -193,6 +214,7 @@ Provision a new tenant and create an admin user for the tenant account Required permissions: `Mobile Integrations.manage` Parameters: -- `body` [body, v2_1.provisioning.schemas_ProvisionWithUserRequestWrapper]: + +- `body` [body, v2_1.provisioning.schemas_ProvisionWithUserRequestWrapper]: Responses: 403 403 - You do not have authorization to complete request., 401 Unauthorized access - please sign in and retry., 400 Invalid user input received. See error details for further i, 201 Tenant provisioned and admin user created diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Discovery.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Discovery.md index fd950a8..30cc8b0 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Discovery.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Discovery.md @@ -3,6 +3,7 @@ 9 endpoints. ## `POST /web/api/v2.1/ranger/device-review` + **Change Device Review in Bulk** `operationId`: `_web_api_ranger_device-review_post` @@ -11,11 +12,13 @@ Change the review state of more than one device. Required permissions: `Ranger.applyDeviceReview` Parameters: -- `body` [body, schemas_DeviceReviewSchema]: + +- `body` [body, schemas_DeviceReviewSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/ranger/device-review/{inventory_id}` + **Change Device Review** `operationId`: `_web_api_ranger_device-review_{inventory_id}_put` @@ -24,12 +27,14 @@ Change the review state of one device. Required permissions: `Ranger.applyDeviceReview` Parameters: + - `inventory_id` [path, string] **required**: Inventory ID. Example: "225494730938493804". -- `body` [body, schemas_DeviceReviewSchemaPut]: +- `body` [body, schemas_DeviceReviewSchemaPut]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/ranger/report/csv` + **Export Network Discovery Data** `operationId`: `_web_api_ranger_report_csv_get` @@ -38,6 +43,7 @@ Export Network Discovery data to csv. You can set filters to get only relevant d Required permissions: `Ranger.view` Parameters: + - `accountIds` [query, array]: Single Account ID to filter by. Example: "225494730938493804". - `siteIds` [query, array]: Single Site ID to filter by. Example: "225494730938493804". - `osType` [query, string]: OS type @@ -91,20 +97,23 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/ranger/settings` + **Get Network Discovery Settings** `operationId`: `_web_api_ranger_settings_get` -Network Discovery gives full visibility of all devices connected to your network. Network Discovery scans your corporate environment to identify and manage connected devices, even those not protected by or supported by SentinelOne. Network Discovery identifies devices as:
* Secured - End-user computer or laptop, or server, with a SentinelOne Agent.
* Unsecured - Endpoint of supported hardware and OS, without an Agent.
* Unsupported - Hardware or software that are not compatible with the SentinelOne Agent.
* Unknown - Network Discovery cannot determine if the device is Unsecured or Unsupported.
When you install Windows Agents with Network Discovery, the Agents can become scanners. Selected scanners from networks that you enable for scanning find connected devices with passive and active scan techniques. The scanners send the collected data to Network Discovery on the Management. Network Discovery then runs fingerprinting to identify and classify unique devices and to update the Device Inventory Table in the Management Console. With port scanning, it is important that you understand the legal and ethical considerations and that you document a Network Discovery plan and … +Network Discovery gives full visibility of all devices connected to your network. Network Discovery scans your corporate environment to identify and manage connected devices, even those not protected by or supported by SentinelOne. Network Discovery identifies devices as:
*Secured - End-user computer or laptop, or server, with a SentinelOne Agent.
* Unsecured - Endpoint of supported hardware and OS, without an Agent.
*Unsupported - Hardware or software that are not compatible with the SentinelOne Agent.
* Unknown - Network Discovery cannot determine if the device is Unsecured or Unsupported.
When you install Windows Agents with Network Discovery, the Agents can become scanners. Selected scanners from networks that you enable for scanning find connected devices with passive and active scan techniques. The scanners send the collected data to Network Discovery on the Management. Network Discovery then runs fingerprinting to identify and classify unique devices and to update the Device Inventory Table in the Management Console. With port scanning, it is important that you understand the legal and ethical considerations and that you document a Network Discovery plan and … Required permissions: `Ranger.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/ranger/settings` + **Update Network Discovery Settings** `operationId`: `_web_api_ranger_settings_put` @@ -113,11 +122,13 @@ Change the Network Discovery Settings. Best Practice: Get the current settings b Required permissions: `Ranger.manageNetworkDiscoverySettings` Parameters: -- `body` [body, schemas_PutRangerSchema]: + +- `body` [body, schemas_PutRangerSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/ranger/table-view` + **Get Network Discovery Table** `operationId`: `_web_api_ranger_table-view_get` @@ -126,6 +137,7 @@ Get the data for each row in the Network Discovery Device Inventory Table. Best Required permissions: `Ranger.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -186,6 +198,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger/tags` + **Change Device Tags** `operationId`: `_web_api_ranger_tags_post` @@ -194,11 +207,13 @@ Change the device tags. Required permissions: `Ranger.manageDeviceTags` Parameters: -- `body` [body, schemas_DeviceTagsSchema]: + +- `body` [body, schemas_DeviceTagsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/ranger/{inventory_id}/json` + **JSON Raw Data** `operationId`: `_web_api_ranger_{inventory_id}_json_get` @@ -207,11 +222,13 @@ Get a json string with the Network Discovery data for one device, by ID in the D Required permissions: `Ranger.view` Parameters: + - `inventory_id` [path, string] **required**: Inventory ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/ranger/{inventory_id}/json/export` + **Export JSON Raw Data** `operationId`: `_web_api_ranger_{inventory_id}_json_export_get` @@ -220,6 +237,7 @@ Export the raw data for one device, by its ID in the Device Inventory Data. To g Required permissions: `Ranger.view` Parameters: + - `inventory_id` [path, string] **required**: Inventory ID. Example: "225494730938493804". Responses: 404 Not found, 200 Success, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Discovery_Self_Enablement.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Discovery_Self_Enablement.md index b577a8c..79962aa 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Discovery_Self_Enablement.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Discovery_Self_Enablement.md @@ -3,23 +3,27 @@ 5 endpoints. ## `POST /web/api/v2.1/ranger/enable-self-management` + **Change the Self-Enablement for Accounts** `operationId`: `_web_api_ranger_enable-self-management_post` [DEPRECATED] Use the Update Account, Get Account, Get Sites, or the Update Site Add-ons APIs instead. Parameters: -- `body` [body, ranger.enablement.schemas_UpdateEnablementPostSchema]: + +- `body` [body, ranger.enablement.schemas_UpdateEnablementPostSchema]: Responses: 404 , 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/ranger/enablement` + **Get Self Enablement** `operationId`: `_web_api_ranger_enablement_get` [DEPRECATED] Use the Update Account, Get Account, Get Sites, or the Update Site Add-ons APIs instead. Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -48,34 +52,40 @@ Parameters: Responses: 404 , 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger/enablement` + **Change Network Discovery or Unprotected Endpoints Discovery Features** `operationId`: `_web_api_ranger_enablement_post` [DEPRECATED] Use the Update Account, Get Account, Get Sites, or the Update Site Add-ons APIs instead. Parameters: -- `body` [body, ranger.enablement.schemas_UpdateSelfEnablementFeaturesSchema]: + +- `body` [body, ranger.enablement.schemas_UpdateSelfEnablementFeaturesSchema]: Responses: 404 , 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/ranger/enablement/defaults` + **Features Configuration for New Sites** `operationId`: `_web_api_ranger_enablement_defaults_get` [DEPRECATED] Use the Update Account, Get Account, Get Sites, or the Update Site Add-ons APIs instead.. Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 404 , 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger/enablement/defaults` + **Change Feature Defaults for New Sites** `operationId`: `_web_api_ranger_enablement_defaults_post` [DEPRECATED] Use the Update Account, Get Account, Get Sites, or the Update Site Add-ons APIs instead. Parameters: -- `body` [body, ranger.enablement.schemas_UpdateSelfEnablementFeaturesSchema]: + +- `body` [body, ranger.enablement.schemas_UpdateSelfEnablementFeaturesSchema]: Responses: 404 , 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Quarantine_Control.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Quarantine_Control.md index ca51551..36bb6fa 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Quarantine_Control.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Network_Quarantine_Control.md @@ -3,6 +3,7 @@ 15 endpoints. ## `DELETE /web/api/v2.1/firewall-control/{firewall_rule_category}` + **Delete Rules** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_delete` @@ -10,12 +11,14 @@ Delete Firewall Control rules that match the filter. Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_RuleDeleteSchema]: +- `body` [body, firewall_control.schemas_RuleDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control/{firewall_rule_category}` + **Get Firewall Rules** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_get` @@ -23,6 +26,7 @@ Get the Firewall Control rules for a scope specified by ID (run "accounts", "sit Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". @@ -62,6 +66,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/{firewall_rule_category}` + **Create Firewall Rule** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_post` @@ -69,32 +74,37 @@ Create a Firewall Control rule for a scope specified by ID (run "accounts", "sit Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_PostFirewallSchema]: +- `body` [body, firewall_control.schemas_PostFirewallSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/{firewall_rule_category}/add-tags` + **Add Rule Tags** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_add-tags_post` -Create a Firewall Rule tag.
Create tags to represent Firewall policies - a set of rules in a specific order. After you create the tag, add rules to it.
Notes:
* Tags apply to a scope and cannot be linked to rules from different scopes.
* Tags must be 2 to 256 characters. +Create a Firewall Rule tag.
Create tags to represent Firewall policies - a set of rules in a specific order. After you create the tag, add rules to it.
Notes:
*Tags apply to a scope and cannot be linked to rules from different scopes.
* Tags must be 2 to 256 characters. Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_ChangeRulesTagsSchema]: +- `body` [body, firewall_control.schemas_ChangeRulesTagsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control/{firewall_rule_category}/configuration` + **Get Configuration** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_configuration_get` -Get the Firewall Control configuration for a given scope.
To filter the results for a scope:
* Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given.
The response shows if Firewall Control is enabled for the scope, if Location Awareness is enabled, the higher scope from which this scope inherited the configuration, and whether a lower scope inherits this configuration.
Firewall Control requires Control SKU. +Get the Firewall Control configuration for a given scope.
To filter the results for a scope:
*Global - Make sure "tenant" is "true" and no other scope ID is given.
* Account - Make sure "tenant" is "false" and at least one Account ID is given.
* Site - Make sure "tenant" is "false" and at least one Site ID is given.
The response shows if Firewall Control is enabled for the scope, if Location Awareness is enabled, the higher scope from which this scope inherited the configuration, and whether a lower scope inherits this configuration.
Firewall Control requires Control SKU. Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -104,6 +114,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/firewall-control/{firewall_rule_category}/configuration` + **Update Configuration** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_configuration_put` @@ -111,12 +122,14 @@ Change the Firewall Control configuration for a given scope.
To get the ID Optional permissions: `Firewall Control.modifySettings(preferencesTab), Network Quarantine Control.modifySettings(preferencesTab), Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_PostFirewallSettingsSchema]: +- `body` [body, firewall_control.schemas_PostFirewallSettingsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/{firewall_rule_category}/copy-rules` + **Copy Rules** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_copy-rules_post` @@ -124,12 +137,14 @@ Copy a set of rules to other scopes.
In the filter of the body, enter the pr Optional permissions: `Firewall Control.view, Network Quarantine Control.view, Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_CopyRuleSchema]: +- `body` [body, firewall_control.schemas_CopyRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/firewall-control/{firewall_rule_category}/enable` + **Enable/Disable Rules** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_enable_put` @@ -137,12 +152,14 @@ Change the status of a set of Firewall Control rules that match the filter to "E Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_EnableRuleSchema]: +- `body` [body, firewall_control.schemas_EnableRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control/{firewall_rule_category}/export` + **Export Rules** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_export_get` @@ -150,6 +167,7 @@ Export Firewall Control rules that match the filter to a JSON file from a scope Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -181,6 +199,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/{firewall_rule_category}/import` + **Import Rules** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_import_post` @@ -188,6 +207,7 @@ Import Firewall Control rules from an exported JSON file to scopes specified by Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine - `accountIds` [formData, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [formData, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -198,6 +218,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/{firewall_rule_category}/move-rules` + **Move Rules** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_move-rules_post` @@ -205,12 +226,14 @@ Remove Firewall Rules, defined with the ID of the rules (run 'firewall-control') Optional permissions: `Firewall Control.view, Network Quarantine Control.view, Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_CopyRuleSchema]: +- `body` [body, firewall_control.schemas_CopyRuleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/firewall-control/{firewall_rule_category}/protocols` + **Get Protocols** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_protocols_get` @@ -218,6 +241,7 @@ Get a list of protocols that can be used in Firewall Control rules. Optional permissions: `Firewall Control.view, Network Quarantine Control.view` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". @@ -232,6 +256,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/{firewall_rule_category}/remove-tags` + **Remove Rule Tags** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_remove-tags_post` @@ -239,12 +264,14 @@ Remove firewall tags from rules matching the filter.
Tags represent Firewall Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_ChangeRulesTagsSchema]: +- `body` [body, firewall_control.schemas_ChangeRulesTagsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/firewall-control/{firewall_rule_category}/reorder` + **Reorder Rules** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_reorder_put` @@ -252,12 +279,14 @@ Change the order of rules for a scope specified by ID (run "accounts", "sites", Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_ReorderSchema]: +- `body` [body, firewall_control.schemas_ReorderSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/firewall-control/{firewall_rule_category}/set-location` + **Set Location** `operationId`: `_web_api_firewall-control_{firewall_rule_category}_set-location_post` @@ -265,7 +294,8 @@ Set location attributes for a Location Aware Firewall Control rule. These rules Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags` Parameters: + - `firewall_rule_category` [path, string] **required**: To affect Network Quarantine use network-quarantine -- `body` [body, firewall_control.schemas_SetLocationSchema]: +- `body` [body, firewall_control.schemas_SetLocationSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Platform_Detection_Rules.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Platform_Detection_Rules.md index 3711168..6afeec5 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Platform_Detection_Rules.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Platform_Detection_Rules.md @@ -7,23 +7,27 @@ Platform rules are SentinelOne-managed out-of-the-box detections (`createdBy: "SentinelOne"`, ~2,000+ in the catalog), separate from custom STAR rules. Custom rules live at `/web/api/v2.1/cloud-detection/rules`; platform rules live ONLY under `/detection-library/*`. Listing `cloud-detection/rules` will NOT return platform rules. Reading: + - List with `GET /detection-library/platform-rules`, scoped via `scopeId` + `scopeLevel` (`global|group|account|site`). For `global`, OMIT `scopeId` (otherwise 400 "a tenant scope request should not include scope id"). - The facet endpoints (`data-sources`, `surfaces`, `severities`, `statuses`) scope via `siteIds=`, not `scopeId`/`scopeLevel`. - `hideQuery=Shown` includes the `s1ql` body in each rule (enum is `Shown`/`Hidden`, not `true`/`false`). - `skip` is capped at 1000 ("Cannot display more than 1000 results, please refine your search"). To reach the full catalog, filter by `sources` (array). Source names are vendor-specific, e.g. `Mimecast`, `Palo Alto Networks Firewall`, `Zscaler Internet Access`, `Okta`. Not every ingested source has platform rules (Cisco Umbrella and Tenable had none on this tenant). Enabling / disabling: + - `PUT /detection-library/platform-rules/enable`, FLAT body `{ "platformRuleIds": [], "scopeId": "", "scopeLevel": "site" }`. `platformRuleIds` MUST be integers. String IDs return a misleading HTTP 500 "Server could not process the request". Response carries `data.affected`. Enable "creates a new rule and activates it" (a scoped active copy). `disable` uses the same body shape. - The enable/disable/settings body is FLAT, NOT wrapped in `{"data": ...}` (wrapping returns 400 "scopeLevel: Missing data for required field"). - An account-scoped API user cannot enable at `global`/tenant scope (400 "can not create rule with higher scope ... tenant"). Enable at `account` or `site`. Site-scope enable requires disabling inheritance FIRST (otherwise enable returns HTTP 500): + - `PUT /detection-library/platform-rules/settings`, FLAT body `{ "scopeId": "", "scopeLevel": "site", "disableInheritance": true }`. Send ONLY `disableInheritance` plus scope. Including `core`/`autoDefault` in the same call returns 400 "cannot enable auto default when inheritance is enabled". - Settings category toggles are `core`, `autoDefault`, `emergingThreat`, `smartDefault` (each `On`/`Off`); at least one of `core`/`autoDefault` is required when setting those, but omit them when only flipping inheritance. Permissions: reads need `Custom Rules.view`; enable/disable/settings need `Custom Rules.manage`. ## `GET /web/api/v2.1/detection-library/data-sources` + **Get Data Sources** `operationId`: `_web_api_detection-library_data-sources_get` @@ -34,6 +38,7 @@ Required permissions: `Custom Rules.view` Responses: 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/detection-library/platform-rules` + **Get Managed Detection Rules** `operationId`: `_web_api_detection-library_platform-rules_get` @@ -42,6 +47,7 @@ Return Managed Detection Rules for the given scope Required permissions: `Custom Rules.view` Parameters: + - `statuses` [query, array]: Statuses. Example: "Activating". - `attackSurfaces` [query, array]: To filter by attack surfaces associated with the rule. - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". @@ -63,17 +69,20 @@ Parameters: Responses: 404 Managed Detection Rules not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/detection-library/platform-rules/disable` + **Disable a Managed Detection Rule** `operationId`: `_web_api_detection-library_platform-rules_disable_put` Required permissions: `Custom Rules.manage` Parameters: -- `body` [body, v2_1.gdl.schemas_PlatformRuleSchemaWithValidation]: + +- `body` [body, v2_1.gdl.schemas_PlatformRuleSchemaWithValidation]: Responses: 404 Managed Detection Rule not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/detection-library/platform-rules/enable` + **Enable a Managed Detection Rule** `operationId`: `_web_api_detection-library_platform-rules_enable_put` @@ -82,11 +91,13 @@ Enable a Managed Detection Rule creates a new rule and activates it. Required permissions: `Custom Rules.manage` Parameters: -- `body` [body, v2_1.gdl.schemas_PlatformRuleSchemaWithValidation]: + +- `body` [body, v2_1.gdl.schemas_PlatformRuleSchemaWithValidation]: Responses: 404 Managed Detection Rule not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/detection-library/platform-rules/settings` + **Get settings for Managed Detection Rules** `operationId`: `_web_api_detection-library_platform-rules_settings_get` @@ -95,12 +106,14 @@ Get settings for Managed Detection Rules for the given scope Required permissions: `Custom Rules.view` Parameters: + - `scopeId` [query, string]: The Account or Site ID, depending on the scope. Null if the scope is Global. Example: "225494730938493804". - `scopeLevel` [query, string] **required** (enum: global, group, account, site): Scope level. Example: "global". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/detection-library/platform-rules/settings` + **Update settings for Managed Detection Rules** `operationId`: `_web_api_detection-library_platform-rules_settings_put` @@ -109,11 +122,13 @@ Update settings for Managed Detection Rules Required permissions: `Custom Rules.manage` Parameters: -- `body` [body, v2_1.gdl.schemas_PlatformSettingsSchema]: + +- `body` [body, v2_1.gdl.schemas_PlatformSettingsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/detection-library/rules` + **Get Managed Detection Rules** `operationId`: `_web_api_detection-library_rules_get` @@ -122,6 +137,7 @@ Return Catalog Rules for the given scope Required permissions: `Custom Rules.view` Parameters: + - `statuses` [query, array]: Statuses. Example: "Activating". - `categories` [query, array]: Categories. Example: "Events". - `customRuleIds` [query, array]: custom rule ids. Example: "225494730938493804,225494730938493915". @@ -148,6 +164,7 @@ Parameters: Responses: 404 Managed Detection Rules not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/detection-library/rules/free-text-filters` + **Free-Text Filters** `operationId`: `_web_api_detection-library_rules_free-text-filters_get` @@ -158,6 +175,7 @@ Required permissions: `Custom Rules.view` Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/detection-library/severities` + **Get Severities** `operationId`: `_web_api_detection-library_severities_get` @@ -168,6 +186,7 @@ Required permissions: `Custom Rules.view` Responses: 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/detection-library/statuses` + **Get Statuses** `operationId`: `_web_api_detection-library_statuses_get` @@ -178,6 +197,7 @@ Required permissions: `Custom Rules.view` Responses: 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/detection-library/surfaces` + **Get Surfaces** `operationId`: `_web_api_detection-library_surfaces_get` @@ -188,6 +208,7 @@ Required permissions: `Custom Rules.view` Responses: 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/detection-library/template-rules` + **Get Template Detection Rules** `operationId`: `_web_api_detection-library_template-rules_get` @@ -196,6 +217,7 @@ Return Template Detection Rules for the given scope Required permissions: `Custom Rules.view` Parameters: + - `statuses` [query, array]: Statuses. Example: "Activating". - `attackSurfaces` [query, array]: To filter by attack surfaces associated with the rule. - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Policies.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Policies.md index 82e5363..664434d 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Policies.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Policies.md @@ -3,6 +3,7 @@ 8 endpoints. ## `GET /web/api/v2.1/accounts/{account_id}/policy` + **Account Policy** `operationId`: `_web_api_accounts_{account_id}_policy_get` @@ -11,11 +12,13 @@ Get the policy for the Account given by ID. To get the ID of an Account, run "ac Required permissions: `Policy.view` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". Responses: 404 Policy not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/accounts/{account_id}/policy` + **Update Account Policy** `operationId`: `_web_api_accounts_{account_id}_policy_put` @@ -25,12 +28,14 @@ Required permissions: `Policy.edit` Optional permissions: `Remote Ops Forensics.view` Parameters: + - `account_id` [path, string] **required**: Account ID. You can get the ID from the Get accounts command. Example: "225494730938493804". -- `body` [body, policies_TenantPolicySchema]: +- `body` [body, policies_TenantPolicySchema]: Responses: 404 Account not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/groups/{group_id}/policy` + **Group Policy** `operationId`: `_web_api_groups_{group_id}_policy_get` @@ -39,11 +44,13 @@ Get the policy of the Group given by ID. To get the ID of a Group, run "groups". Required permissions: `Policy.view` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". Responses: 404 Policy not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/groups/{group_id}/policy` + **Update Group Policy** `operationId`: `_web_api_groups_{group_id}_policy_put` @@ -53,12 +60,14 @@ Required permissions: `Policy.edit` Optional permissions: `Remote Ops Forensics.view` Parameters: + - `group_id` [path, string] **required**: Group ID. Example: "225494730938493804". -- `body` [body, policies_TenantPolicySchema]: +- `body` [body, policies_TenantPolicySchema]: Responses: 404 Group not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/sites/{site_id}/policy` + **Site Policy** `operationId`: `_web_api_sites_{site_id}_policy_get` @@ -67,11 +76,13 @@ Get the policy of the Site given by ID. To get the ID of a Site, run "sites". Se Required permissions: `Policy.view` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 404 Policy not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/sites/{site_id}/policy` + **Update Site Policy** `operationId`: `_web_api_sites_{site_id}_policy_put` @@ -81,12 +92,14 @@ Required permissions: `Policy.edit` Optional permissions: `Remote Ops Forensics.view` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". -- `body` [body, policies_TenantPolicySchema]: +- `body` [body, policies_TenantPolicySchema]: Responses: 404 Site not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/tenant/policy` + **Global Policy** `operationId`: `_web_api_tenant_policy_get` @@ -97,16 +110,18 @@ Required permissions: `Policy.view` Responses: 404 Policy not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/tenant/policy` + **Update Global Policy** `operationId`: `_web_api_tenant_policy_put` -Change the policy of your deployment. Best practice: Get the Global policy before you attempt to change it. See also: Get Policy. +Change the policy of your deployment. Best practice: Get the Global policy before you attempt to change it. See also: Get Policy. You must be a Global Admin user to change the Global Policy. Required permissions: `Policy.edit` Optional permissions: `Remote Ops Forensics.view` Parameters: -- `body` [body, policies_TenantPolicySchema]: + +- `body` [body, policies_TenantPolicySchema]: Responses: 404 Policy not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RBAC.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RBAC.md index 48e2c9b..9be24c4 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RBAC.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RBAC.md @@ -3,6 +3,7 @@ 6 endpoints. ## `GET /web/api/v2.1/rbac/role` + **Get template for new role** `operationId`: `_web_api_rbac_role_get` @@ -11,6 +12,7 @@ Get the template for a new role. Required permissions: `Roles.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -19,6 +21,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/rbac/role` + **Create new role** `operationId`: `_web_api_rbac_role_post` @@ -27,11 +30,13 @@ Create a new role for Role-Based Access Control (RBAC). Required permissions: `Roles.create` Parameters: -- `body` [body, rbac.schemas_RbacCreateRoleSchema]: + +- `body` [body, rbac.schemas_RbacCreateRoleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/rbac/role/{role_id}` + **Delete role** `operationId`: `_web_api_rbac_role_{role_id}_delete` @@ -41,12 +46,14 @@ Required permissions: `Roles.delete` Optional permissions: `Users.edit` Parameters: + - `role_id` [path, string] **required**: Role ID. Example: "225494730938493804". -- `body` [body, rbac.schemas_RbacDeleteRoleSchema]: +- `body` [body, rbac.schemas_RbacDeleteRoleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/rbac/role/{role_id}` + **Get Specific Role Definition** `operationId`: `_web_api_rbac_role_{role_id}_get` @@ -55,6 +62,7 @@ With the ID of a role (see Get All Roles) you can see the permissions of that ro Required permissions: `Roles.view` Parameters: + - `role_id` [path, string] **required**: Role ID. Example: "225494730938493804". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -76,6 +84,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/rbac/role/{role_id}` + **Update role** `operationId`: `_web_api_rbac_role_{role_id}_put` @@ -84,12 +93,14 @@ With the ID of a role (see Get All Roles), you can update the permissions of use Required permissions: `Roles.edit` Parameters: + - `role_id` [path, string] **required**: Role ID. Example: "225494730938493804". -- `body` [body, rbac.schemas_RbacUpdateRoleSchema]: +- `body` [body, rbac.schemas_RbacUpdateRoleSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/rbac/roles` + **Get All Roles** `operationId`: `_web_api_rbac_roles_get` @@ -98,6 +109,7 @@ See roles assigned to users that match the filter, a basic description of the ro Required permissions: `Roles.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RemoteOps_Forensics.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RemoteOps_Forensics.md index 80aebd6..b6d845a 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RemoteOps_Forensics.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RemoteOps_Forensics.md @@ -3,6 +3,7 @@ 10 endpoints. ## `GET /web/api/v2.1/remote-ops/forensics/artifact-types` + **Get list of supported artifact types** `operationId`: `_web_api_remote-ops_forensics_artifact-types_get` @@ -11,6 +12,7 @@ Return a complete list of supported artifact types Responses: 200 Successes, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/forensics/collection-file-url` + **Returns collection file download pre-signed url** `operationId`: `_web_api_remote-ops_forensics_collection-file-url_get` @@ -18,6 +20,7 @@ Returns collection file download pre-signed url Optional permissions: `Remote Ops Forensics.view` Parameters: + - `siteId` [query, string] **required**: Site id. Example: "225494730938493804". - `agentId` [query, string] **required**: Agent id. Example: "225494730938493804". - `signature` [query, string] **required**: Signature @@ -27,6 +30,7 @@ Parameters: Responses: 200 Remote Ops Forensics Collection File Found, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/remote-ops/forensics/collection-profiles` + **Delete Collection profiles** `operationId`: `_web_api_remote-ops_forensics_collection-profiles_delete` @@ -35,11 +39,13 @@ Delete multiple Forensics Collection profiles. The profiles that are not possibl Required permissions: `Remote Ops Forensics.delete` Parameters: -- `body` [body, v2_1.forensics.schema_DeleteProfilesRequestSchema]: + +- `body` [body, v2_1.forensics.schema_DeleteProfilesRequestSchema]: Responses: 200 Delete was completed or partially completed., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/forensics/collection-profiles` + **Get list of available Collection profiles** `operationId`: `_web_api_remote-ops_forensics_collection-profiles_get` @@ -48,6 +54,7 @@ Get list of available Forensics collection profiles. The list may be narrowed by Required permissions: `Remote Ops Forensics.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `sortOrder` [query, string] (enum: asc, desc): Sort direction. Example: "asc". - `skipCount` [query, boolean]: If true, total number of items will not be calculated, which speeds up execution time. @@ -65,6 +72,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-ops/forensics/collection-profiles` + **Create new Collection profile** `operationId`: `_web_api_remote-ops_forensics_collection-profiles_post` @@ -72,11 +80,13 @@ Create a Forensics Collection profile with provided artifacts on the specified s Optional permissions: `Remote Ops Forensics.create, Remote Ops Forensics.upload` Parameters: -- `body` [body, v2_1.forensics.schema_CollectionProfileRequestSchema]: + +- `body` [body, v2_1.forensics.schema_CollectionProfileRequestSchema]: Responses: 200 Collection profile is created, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/forensics/collection-profiles/{profile_id}` + **Get Collection profile by ID** `operationId`: `_web_api_remote-ops_forensics_collection-profiles_{profile_id}_get` @@ -85,11 +95,13 @@ Get contents of an existing Forensics Collection profile, including specificatio Required permissions: `Remote Ops Forensics.view` Parameters: + - `profile_id` [path, string] **required**: Profile ID. Example: "225494730938493804". Responses: 403 User has insufficient permission to perform such action, 404 Collection profile was not found, 200 Collection profile content in returned, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/remote-ops/forensics/collection-profiles/{profile_id}` + **Update Collection profile by ID** `operationId`: `_web_api_remote-ops_forensics_collection-profiles_{profile_id}_put` @@ -98,12 +110,14 @@ Update contents of an existing Forensics Collection profile. All the profile dat Required permissions: `Remote Ops Forensics.edit` Parameters: + - `profile_id` [path, string] **required**: Profile ID. Example: "225494730938493804". -- `body` [body, v2_1.forensics.schema_PutCollectionProfileRequestSchema]: +- `body` [body, v2_1.forensics.schema_PutCollectionProfileRequestSchema]: Responses: 403 User has insufficient permission to perform such action, 404 Collection profile was not found, 200 Collection profile is updated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/forensics/is-collection-file` + **Check if collection file exists for given storyline** `operationId`: `_web_api_remote-ops_forensics_is-collection-file_get` @@ -112,12 +126,14 @@ Check if collection file exists for given storyline Required permissions: `Remote Ops Forensics.view` Parameters: + - `storyline` [query, string] **required**: Storyline ID - `agentId` [query, string] **required**: Agent's ID. Example: "225494730938493804". Responses: 404 Collection file not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-ops/forensics/start-collection` + **Start collection of Forensics artifacts according to specified profile** `operationId`: `_web_api_remote-ops_forensics_start-collection_post` @@ -126,11 +142,13 @@ Start collection of Forensics artifacts according to specified profile Required permissions: `Remote Ops Forensics.view, Remote Ops Forensics.runForensicsCollection` Parameters: -- `body` [body, remote_ops.schemas_StartCollectionSchema]: + +- `body` [body, remote_ops.schemas_StartCollectionSchema]: Responses: 202 Forensics collection has been started, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/forensics/task-result` + **Return result of collection task** `operationId`: `_web_api_remote-ops_forensics_task-result_get` @@ -140,6 +158,7 @@ Required permissions: `Remote Ops Forensics.view` Optional permissions: `Remote Ops Forensics.viewOutput` Parameters: + - `taskId` [query, string] **required**: Task id. Example: "225494730938493804". Responses: 200 Task is found and result is returned, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RemoteOps_Scripts.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RemoteOps_Scripts.md index f7b012c..16a53de 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RemoteOps_Scripts.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/RemoteOps_Scripts.md @@ -3,6 +3,7 @@ 16 endpoints. ## `DELETE /web/api/v2.1/remote-scripts` + **Delete Scripts** `operationId`: `_web_api_remote-scripts_delete` @@ -11,11 +12,13 @@ Deletes scripts that match a filter. Required permissions: `Remote Script Orchestration.delete` Parameters: -- `body` [body, schemas_ScriptDeleteSchema]: + +- `body` [body, schemas_ScriptDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-scripts` + **Get Scripts** `operationId`: `_web_api_remote-scripts_get` @@ -23,6 +26,7 @@ Get data of the scripts in the SentinelOne Script Library.
The SentinelOne S Optional permissions: `Remote Script Orchestration.view, Remote Script Orchestration.delete` Parameters: + - `skipCount` [query, boolean]: If true, total number of items will not be calculated, which speeds up execution time. - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -42,6 +46,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-scripts` + **Upload New Script** `operationId`: `_web_api_remote-scripts_post` @@ -50,6 +55,7 @@ Upload a new script file. The file and various properties are required. To see t Required permissions: `Remote Script Orchestration.upload` Parameters: + - `isScriptContentEncoded` [formData, boolean]: True if script content is encoded - `packageMaxSize` [formData, string]: Package max size - `inputExample` [formData, string]: Input example @@ -76,6 +82,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/remote-scripts/edit/{script_id}` + **Update a Script** `operationId`: `_web_api_remote-scripts_edit_{script_id}_put` @@ -84,6 +91,7 @@ Change the properties of a given script: runtime timeout, name, and whether inpu Required permissions: `Remote Script Orchestration.edit` Parameters: + - `script_id` [path, string] **required**: Script ID. Example: "225494730938493804". - `scriptContent` [formData, string]: Filled out with a new content of a script if the script content was changedon an already previously uploaded script - `inputInstructions` [formData, string] **required**: Input instructions @@ -107,6 +115,7 @@ Parameters: Responses: 400 Invalid user input received. See error details for further i, 404 Script not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-scripts/execute` + **Run Remote Script** `operationId`: `_web_api_remote-scripts_execute_post` @@ -114,11 +123,13 @@ Run a remote script that was uploaded to the SentinelOne Script Library. Optional permissions: `Remote Script Orchestration.view, Remote Script Orchestration.runArtifactCollectionScript, Remote Script Orchestration.runDataCollectionScript, Remote Script Orchestration.runActionScript` Parameters: -- `body` [body, cloud_proxy.remote_scripts_ExecuteScriptSchema]: + +- `body` [body, cloud_proxy.remote_scripts_ExecuteScriptSchema]: Responses: 200 Run remote script request was successful, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-scripts/fetch-files` + **Get Script Results** `operationId`: `_web_api_remote-scripts_fetch-files_post` @@ -127,11 +138,13 @@ Get scripts results URLs. Accessible via API only Required permissions: `Remote Script Orchestration.view` Parameters: -- `body` [body, _FetchScriptsResultsSchema]: + +- `body` [body, _FetchScriptsResultsSchema]: Responses: 200 Get remote script results was successful, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-scripts/fetch-upload-limits` + **Get upload limit for Package** `operationId`: `_web_api_remote-scripts_fetch-upload-limits_get` @@ -142,6 +155,7 @@ Required permissions: `Remote Script Orchestration.view` Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-scripts/guardrails/check` + **Check whether guardrail applies to an execution** `operationId`: `_web_api_remote-scripts_guardrails_check_post` @@ -150,11 +164,13 @@ Check whether guardrail applies to an execution Required permissions: `Remote Script Orchestration.view` Parameters: -- `body` [body, schemas_EncapsulatedPostGuardrailCheckSchema]: + +- `body` [body, schemas_EncapsulatedPostGuardrailCheckSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/remote-scripts/guardrails/configuration` + **Deletes a specific guardrails configuration** `operationId`: `_web_api_remote-scripts_guardrails_configuration_delete` @@ -163,11 +179,13 @@ Deletes a specific guardrails configuration Required permissions: `Remote Script Orchestration.manageGuardrails` Parameters: -- `body` [body, schemas_EncapsulatedDeleteGuardrailsSchema]: + +- `body` [body, schemas_EncapsulatedDeleteGuardrailsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-scripts/guardrails/configuration` + **Gets a guardrails configuration for a given scope** `operationId`: `_web_api_remote-scripts_guardrails_configuration_get` @@ -176,12 +194,14 @@ Gets a guardrails configuration for a given scope Required permissions: `Remote Script Orchestration.view` Parameters: + - `scopeId` [query, string] **required**: Scope ID. Example: "225494730938493804". - `scopeLevel` [query, string] **required** (enum: account, site, group): Scope level. Example: "account". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-scripts/guardrails/configuration` + **Updates or inserts (if record does not exist) a guardrails configuration** `operationId`: `_web_api_remote-scripts_guardrails_configuration_post` @@ -190,11 +210,13 @@ Updates or inserts (if record does not exist) a guardrails configuration Required permissions: `Remote Script Orchestration.manageGuardrails` Parameters: -- `body` [body, schemas_EncapsulatedPostGuardrailsSchema]: + +- `body` [body, schemas_EncapsulatedPostGuardrailsSchema]: Responses: 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-scripts/pending-executions` + **Get paginated pending executions** `operationId`: `_web_api_remote-scripts_pending-executions_get` @@ -203,6 +225,7 @@ Get paginated pending executions Required permissions: `Remote Script Orchestration.view` Parameters: + - `skipCount` [query, boolean]: If true, total number of items will not be calculated, which speeds up execution time. - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -217,6 +240,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/remote-scripts/pending-executions/{pending_execution_id}` + **Approve/decline pending execution** `operationId`: `_web_api_remote-scripts_pending-executions_{pending_execution_id}_put` @@ -225,12 +249,14 @@ Approve/decline pending execution Required permissions: `Remote Script Orchestration.reviewPendingExecutions` Parameters: + - `pending_execution_id` [path, string] **required**: Pending execution ID. Example: "225494730938493804". -- `body` [body, schemas_ApproveDeclinePendingExecutionRequestSchema]: +- `body` [body, schemas_ApproveDeclinePendingExecutionRequestSchema]: Responses: 404 Pending execution not found, 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-scripts/script-content` + **Get script content** `operationId`: `_web_api_remote-scripts_script-content_get` @@ -239,11 +265,13 @@ Get Script content by script id Required permissions: `Remote Script Orchestration.view` Parameters: + - `scriptId` [query, string]: Script ID. Example: "225494730938493804". Responses: 404 Script not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-scripts/status` + **Get Remote Scripts Tasks Status** `operationId`: `_web_api_remote-scripts_status_get` @@ -252,6 +280,7 @@ Get remote scripts tasks using a variety of filters. Accessible via API only
Required permissions: `Task Management.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -287,6 +316,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/remote-scripts/{script_id}` + **Update a Script** `operationId`: `_web_api_remote-scripts_{script_id}_put` @@ -295,7 +325,8 @@ Change the properties of a given script: runtime timeout, name, and whether inpu Required permissions: `Remote Script Orchestration.edit` Parameters: + - `script_id` [path, string] **required**: Script ID. Example: "225494730938493804". -- `body` [body, schemas_UpdateScript]: +- `body` [body, schemas_UpdateScript]: Responses: 404 Script not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Remote_Ops_MMS.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Remote_Ops_MMS.md index d6fc773..be4d54f 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Remote_Ops_MMS.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Remote_Ops_MMS.md @@ -3,6 +3,7 @@ 13 endpoints. ## `DELETE /web/api/v2.1/remote-ops/data-exporter/destination-profiles` + **Delete multiple Destination profiles by ID** `operationId`: `_web_api_remote-ops_data-exporter_destination-profiles_delete` @@ -11,11 +12,13 @@ Delete multiple Destination profiles. The profiles that are not possible to dele Required permissions: `Remote Script Orchestration.manageDestinationCredentials` Parameters: -- `body` [body, v2_1.data_exporter.schema_DeleteDestinationProfilesRequestSchema]: + +- `body` [body, v2_1.data_exporter.schema_DeleteDestinationProfilesRequestSchema]: Responses: 200 Delete was completed or partially completed., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/data-exporter/destination-profiles` + **Get available Destination profiles** `operationId`: `_web_api_remote-ops_data-exporter_destination-profiles_get` @@ -24,12 +27,14 @@ Get Destination profiles available for the specified scope. The profiles are inh Required permissions: `Remote Script Orchestration.viewDestinationCredentials` Parameters: + - `scopeLevel` [query, string] (enum: tenant, account, site, group): Scope level to get Destination profile configuration. Example: "tenant". - `scopeId` [query, string]: Scope ID to get Destination profiles configuration. Example: "225494730938493804". Responses: 403 User has insufficient permission to perform such action, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-ops/data-exporter/destination-profiles` + **Create new Destination profile.** `operationId`: `_web_api_remote-ops_data-exporter_destination-profiles_post` @@ -38,11 +43,13 @@ Create Destination profile inside specified scope. If the created profile is req Required permissions: `Remote Script Orchestration.manageDestinationCredentials` Parameters: -- `body` [body, v2_1.data_exporter.schema_PostDestinationProfileRequestSchema]: + +- `body` [body, v2_1.data_exporter.schema_PostDestinationProfileRequestSchema]: Responses: 401 Unauthorized access - please sign in and retry., 200 Successes, 400 Invalid user input received. See error details for further i ## `POST /web/api/v2.1/remote-ops/data-exporter/destination-profiles/set-default` + **Set profile as default profile of the scope** `operationId`: `_web_api_remote-ops_data-exporter_destination-profiles_set-default_post` @@ -51,11 +58,13 @@ Set profile as default profile of the scope Required permissions: `Remote Script Orchestration.manageDestinationCredentials` Parameters: -- `body` [body, v2_1.data_exporter.schema_SetDefaultDestinationProfile]: + +- `body` [body, v2_1.data_exporter.schema_SetDefaultDestinationProfile]: Responses: 403 User has insufficient permission to perform such action, 400 Invalid user input received. See error details for further i, 200 Get Destination profile, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/remote-ops/data-exporter/destination-profiles/{profile_id}` + **Delete Destination profile by ID** `operationId`: `_web_api_remote-ops_data-exporter_destination-profiles_{profile_id}_delete` @@ -64,11 +73,13 @@ Delete Destination profile with specified ID. If the profile was used as default Required permissions: `Remote Script Orchestration.manageDestinationCredentials` Parameters: + - `profile_id` [path, string] **required**: Profile ID. Example: "225494730938493804". Responses: 403 User has insufficient permission to perform such action, 404 Destination profile is not found, 200 Destination profile is deleted, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/data-exporter/destination-profiles/{profile_id}` + **Get Destination profile by ID** `operationId`: `_web_api_remote-ops_data-exporter_destination-profiles_{profile_id}_get` @@ -77,6 +88,7 @@ Get Destination profile with specified ID Required permissions: `Remote Script Orchestration.manageDestinationCredentials` Parameters: + - `profile_id` [path, string] **required**: Profile ID. Example: "225494730938493804". - `scopeLevel` [query, string] (enum: tenant, account, site, group): Scope level to get Destination profile configuration. Example: "tenant". - `scopeId` [query, string]: Scope ID to get Destination profiles configuration. Example: "225494730938493804". @@ -84,6 +96,7 @@ Parameters: Responses: 403 User has insufficient permission to perform such action, 404 Destination profile is not found, 200 Get Destination profile, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/remote-ops/data-exporter/destination-profiles/{profile_id}` + **Update existing Destination profile** `operationId`: `_web_api_remote-ops_data-exporter_destination-profiles_{profile_id}_put` @@ -92,12 +105,14 @@ Update contents of existing Destination profile with specified ID. All the profi Required permissions: `Remote Script Orchestration.manageDestinationCredentials` Parameters: + - `profile_id` [path, string] **required**: Profile ID. Example: "225494730938493804". -- `body` [body, v2_1.data_exporter.schema_PutDestinationProfileRequestSchema]: +- `body` [body, v2_1.data_exporter.schema_PutDestinationProfileRequestSchema]: Responses: 403 User has insufficient permission to perform such action, 404 Destination profile is not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/data-exporter/results` + **Get results sent to data exporter** `operationId`: `_web_api_remote-ops_data-exporter_results_get` @@ -105,6 +120,7 @@ Get results sent to data exporter Optional permissions: `Remote Script Orchestration.view, Remote Ops Forensics.view, Remote Script Orchestration.viewDestinationResults` Parameters: + - `taskId` [query, string]: Task id - `maliciousGroupId` [query, string]: Threat malicious group id - `agentId` [query, string] **required**: Id of the agent the data came from @@ -112,6 +128,7 @@ Parameters: Responses: 403 User has insufficient permission to perform such action, 200 Get Destination profile results, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/remote-ops/schedule/forensics` + **Schedule forensics for future run.** `operationId`: `_web_api_remote-ops_schedule_forensics_post` @@ -120,11 +137,13 @@ Schedule forensics for future run. The profile will be scheduled for execution o Required permissions: `Remote Script Orchestration.createScheduledTasks, Remote Ops Forensics.runForensicsCollection` Parameters: -- `body` [body, v2_1.scheduling.schema_ScheduleForensicsCollectionRequestSchema]: + +- `body` [body, v2_1.scheduling.schema_ScheduleForensicsCollectionRequestSchema]: Responses: 401 Unauthorized access - please sign in and retry., 200 Success, 400 Invalid user input received. See error details for further i ## `POST /web/api/v2.1/remote-ops/schedule/remote-script` + **Schedule remote script for future run.** `operationId`: `_web_api_remote-ops_schedule_remote-script_post` @@ -134,11 +153,13 @@ Required permissions: `Remote Script Orchestration.createScheduledTasks` Optional permissions: `Remote Script Orchestration.runActionScript, Remote Script Orchestration.runDataCollectionScript, Remote Script Orchestration.runArtifactCollectionScript` Parameters: -- `body` [body, v2_1.scheduling.schema_ScheduleRemoteScriptRequestSchema]: + +- `body` [body, v2_1.scheduling.schema_ScheduleRemoteScriptRequestSchema]: Responses: 401 Unauthorized access - please sign in and retry., 200 Success, 400 Invalid user input received. See error details for further i ## `DELETE /web/api/v2.1/remote-ops/scheduled-tasks` + **Delete multiple scheduled tasks by ID** `operationId`: `_web_api_remote-ops_scheduled-tasks_delete` @@ -147,11 +168,13 @@ Delete multiple Scheduled tasks. The tasks that are not possible to delete (e.g. Required permissions: `Remote Script Orchestration.deleteScheduledTasks` Parameters: -- `body` [body, v2_1.scheduling.schema_DeleteScheduledTasksRequestSchema]: + +- `body` [body, v2_1.scheduling.schema_DeleteScheduledTasksRequestSchema]: Responses: 200 Delete was completed or partially completed., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/remote-ops/scheduled-tasks` + **Get available Scheduled Tasks** `operationId`: `_web_api_remote-ops_scheduled-tasks_get` @@ -160,6 +183,7 @@ Get available Scheduled Tasks Required permissions: `Remote Script Orchestration.viewScheduledTasks` Parameters: + - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". - `scopeName__contains` [query, array]: Keyword to search in scope name - `outputDestination` [query, array]: List of the tasks types. Example: "SentinelCloud". @@ -188,6 +212,7 @@ Parameters: Responses: 403 User has insufficient permission to perform such action, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/remote-ops/scheduled-tasks/{scheduled_task_id}` + **Update existing Scheduled task** `operationId`: `_web_api_remote-ops_scheduled-tasks_{scheduled_task_id}_put` @@ -196,7 +221,8 @@ Update existing Scheduled task Required permissions: `Remote Script Orchestration.updateScheduledTasks` Parameters: + - `scheduled_task_id` [path, string] **required**: Scheduled Task ID. Example: "225494730938493804". -- `body` [body, v2_1.scheduling.schema_PutScheduledTaskRequestSchema]: +- `body` [body, v2_1.scheduling.schema_PutScheduledTaskRequestSchema]: Responses: 403 User has insufficient permission to perform such action, 404 Scheduled task is not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Saved_Searches.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Saved_Searches.md index 2681f98..8ef1d92 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Saved_Searches.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Saved_Searches.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /sdl/v2/api/saved-searches` + **List saved searches** `operationId`: `_sdl_v2_api_saved-searches_get` @@ -11,11 +12,13 @@ Retrieves all saved searches visible to the current user. Saved searches allow y Required permissions: `SDL Search (Previously Skylight).create, SDL Search (Previously Skylight).edit, SDL Search (Previously Skylight).delete` Parameters: + - `type` [query, string] (enum: PRIVATE, SHARED): Filter by search type. PRIVATE searches are visible only to you, while SHARED searches are visible to all users in your current scope. Defaults to PRIVATE if not specified. Responses: 200 List of saved searches retrieved successfully., 401 Unauthorized. Authentication is required., 403 Forbidden. User does not have permission to manage searches. ## `PUT /sdl/v2/api/saved-searches` + **Create or update saved searches** `operationId`: `_sdl_v2_api_saved-searches_put` @@ -26,11 +29,13 @@ Note: A `teamToken` query parameter will be automatically added to each search U Required permissions: `SDL Search (Previously Skylight).create, SDL Search (Previously Skylight).edit, SDL Search (Previously Skylight).delete` Parameters: + - `body` [body, v2_1.saved_searches.schemas_BatchUpsertRequest] **required**: List of saved searches to create or update, along with the duplicate handling strategy. -Responses: 200 All searches were processed successfully. This includes sear, 207 Partial success. Some searches were saved successfully, but , 400 Invalid request. The request body is malformed or contains i, 401 Authentication required. Please provide valid credentials., 403 Permission denied. You do not have the required permissions , 500 Internal server error. All searches failed to save due to a +Responses: 200 All searches were processed successfully. This includes sear, 207 Partial success. Some searches were saved successfully, but , 400 Invalid request. The request body is malformed or contains i, 401 Authentication required. Please provide valid credentials., 403 Permission denied. You do not have the required permissions , 500 Internal server error. All searches failed to save due to a ## `POST /sdl/v2/api/saved-searches/batch-delete` + **Delete saved searches** `operationId`: `_sdl_v2_api_saved-searches_batch-delete_post` @@ -39,6 +44,7 @@ Delete multiple saved searches in a single request. You can delete up to 100 sea Required permissions: `SDL Search (Previously Skylight).create, SDL Search (Previously Skylight).edit, SDL Search (Previously Skylight).delete` Parameters: + - `body` [body, v2_1.saved_searches.schemas_BatchDeleteRequest] **required**: List of saved searches to delete, identified by name and type. -Responses: 200 All searches were deleted successfully., 207 Partial success. Some searches were deleted successfully, bu, 400 Invalid request. The request body is malformed or contains i, 401 Authentication required. Please provide valid credentials., 403 Permission denied. You do not have the required permissions , 500 Internal server error. All searches failed to delete due to +Responses: 200 All searches were deleted successfully., 207 Partial success. Some searches were deleted successfully, bu, 400 Invalid request. The request body is malformed or contains i, 401 Authentication required. Please provide valid credentials., 403 Permission denied. You do not have the required permissions , 500 Internal server error. All searches failed to delete due to diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Sentinel_Deploy.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Sentinel_Deploy.md index c05f6f4..7b0dacc 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Sentinel_Deploy.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Sentinel_Deploy.md @@ -3,6 +3,7 @@ 7 endpoints. ## `GET /web/api/v2.1/ranger/cred-groups` + **Get Cred groups** `operationId`: `_web_api_ranger_cred-groups_get` @@ -11,6 +12,7 @@ Get the data for each row in the Cred Groups table. Required permissions: `Ranger.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -29,6 +31,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger/cred-groups` + **Create Cred Group** `operationId`: `_web_api_ranger_cred-groups_post` @@ -37,11 +40,13 @@ Create a new Cred Group. Required permissions: `Ranger.manageCredentials` Parameters: -- `body` [body, ranger.auto_deploy_schemas_CredGroupsPostSchema]: + +- `body` [body, ranger.auto_deploy_schemas_CredGroupsPostSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/ranger/cred-groups/details` + **Get Cred group details** `operationId`: `_web_api_ranger_cred-groups_details_get` @@ -50,6 +55,7 @@ Get the data for each row in the Cred Groups details table. Required permissions: `Ranger.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -68,6 +74,7 @@ Parameters: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/ranger/cred-groups/details` + **Add cred details** `operationId`: `_web_api_ranger_cred-groups_details_post` @@ -76,11 +83,13 @@ Add cred details to a cred group. Required permissions: `Ranger.manageCredentials` Parameters: -- `body` [body, ranger.auto_deploy_schemas_CredGroupsDetailsPostSchema]: + +- `body` [body, ranger.auto_deploy_schemas_CredGroupsDetailsPostSchema]: Responses: 404 Cred group not found., 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/ranger/cred-groups/details/{detail_id}` + **Delete Cred Group Detail** `operationId`: `_web_api_ranger_cred-groups_details_{detail_id}_delete` @@ -89,11 +98,13 @@ Delete cred group detail value. Required permissions: `Ranger.manageCredentials` Parameters: + - `detail_id` [path, string] **required**: Cred group detail ID. Example: "225494730938493804". Responses: 404 Cred group not found., 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/ranger/cred-groups/details/{detail_id}` + **Update Cred Group Details** `operationId`: `_web_api_ranger_cred-groups_details_{detail_id}_put` @@ -102,12 +113,14 @@ Update cred group values. Required permissions: `Ranger.manageCredentials` Parameters: + - `detail_id` [path, string] **required**: Cred group detail ID. Example: "225494730938493804". -- `body` [body, ranger.auto_deploy_schemas_CredPutDetailsSchema]: +- `body` [body, ranger.auto_deploy_schemas_CredPutDetailsSchema]: Responses: 404 Cred group not found., 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/ranger/cred-groups/{cred_group_id}` + **Delete Cred Group** `operationId`: `_web_api_ranger_cred-groups_{cred_group_id}_delete` @@ -116,6 +129,7 @@ Delete cred group value. Required permissions: `Ranger.manageCredentials` Parameters: + - `cred_group_id` [path, string] **required**: Cred group ID. Example: "225494730938493804". Responses: 404 Cred group not found., 200 Success, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Service_Users.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Service_Users.md index 603e3c0..f001a92 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Service_Users.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Service_Users.md @@ -3,6 +3,7 @@ 8 endpoints. ## `GET /web/api/v2.1/export/service-users` + **Export Service Users** `operationId`: `_web_api_export_service-users_get` @@ -11,6 +12,7 @@ Export Service User data to a CSV, for Service Users that match the filter. Required permissions: `Service Users.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `ids` [query, array]: List of service user IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -20,6 +22,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/service-users` + **Get Service Users** `operationId`: `_web_api_service-users_get` @@ -28,6 +31,7 @@ Get a list of service users. Required permissions: `Service Users.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -44,6 +48,7 @@ Parameters: Responses: 401 Unauthorized access - please sign in and retry., 200 List of service users retrieved successfully., 400 Invalid user input received. See error details for further i ## `POST /web/api/v2.1/service-users` + **Create Service User** `operationId`: `_web_api_service-users_post` @@ -52,11 +57,13 @@ Create a new service user. Required permissions: `Service Users.create` Parameters: -- `body` [body, service_users.schemas_CreateServiceUserSchema]: + +- `body` [body, service_users.schemas_CreateServiceUserSchema]: Responses: 403 Not enough permissions to create service user., 200 Service User created successfully., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/service-users/delete-service-users` + **Bulk Delete Service Users** `operationId`: `_web_api_service-users_delete-service-users_post` @@ -65,11 +72,13 @@ Delete all service users that match the filter. Required permissions: `Service Users.delete` Parameters: -- `body` [body, service_users.schemas_BulkDeleteServiceUsersSchema]: + +- `body` [body, service_users.schemas_BulkDeleteServiceUsersSchema]: Responses: 403 Insufficient permissions., 200 Service Users deleted successfully., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/service-users/{service_user_id}` + **Delete Service User** `operationId`: `_web_api_service-users_{service_user_id}_delete` @@ -78,11 +87,13 @@ Delete a service user by ID. Required permissions: `Service Users.delete` Parameters: + - `service_user_id` [path, string] **required**: Service User ID. Example: "225494730938493804". Responses: 403 Insufficient permissions., 200 Service User deleted successfully., 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/service-users/{service_user_id}` + **Get Service User** `operationId`: `_web_api_service-users_{service_user_id}_get` @@ -91,11 +102,13 @@ Get a specific service user by ID. Required permissions: `Service Users.view` Parameters: + - `service_user_id` [path, string] **required**: Service User ID. Example: "225494730938493804". Responses: 404 Service User not found., 401 Unauthorized access - please sign in and retry., 200 Service user retrieved successfully. ## `PUT /web/api/v2.1/service-users/{service_user_id}` + **Update Service User** `operationId`: `_web_api_service-users_{service_user_id}_put` @@ -104,12 +117,14 @@ Change properties of the service user with the given ID. Required permissions: `Service Users.edit` Parameters: + - `service_user_id` [path, string] **required**: Service User ID. Example: "225494730938493804". -- `body` [body, service_users.schemas_UpdateServiceUserSchema]: +- `body` [body, service_users.schemas_UpdateServiceUserSchema]: Responses: 404 Service User not found., 403 Forbidden., 200 Service User updated successfully., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/service-users/{service_user_id}/generate-api-token` + **Generate API Token for Service User** `operationId`: `_web_api_service-users_{service_user_id}_generate-api-token_post` @@ -118,7 +133,8 @@ Generate a new API token for a service user and revoke the existing API token. Required permissions: `Service Users.edit` Parameters: + - `service_user_id` [path, string] **required**: Service User ID. Example: "225494730938493804". -- `body` [body, service_users.schemas_GenerateServiceUserApiTokenSchema]: +- `body` [body, service_users.schemas_GenerateServiceUserApiTokenSchema]: Responses: 409 API token creation conflict., 404 Service User not found., 403 Forbidden., 200 API token delivered to user., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Settings.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Settings.md index 2cf751e..5d60594 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Settings.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Settings.md @@ -3,6 +3,7 @@ 27 endpoints. ## `GET /web/api/v2.1/settings/active-directory` + **Get AD Settings** `operationId`: `_web_api_settings_active-directory_get` @@ -11,12 +12,14 @@ Get the Global Active Directory settings. Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 403 User is not allowed in this scope, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/active-directory` + **Set AD Settings** `operationId`: `_web_api_settings_active-directory_put` @@ -25,11 +28,13 @@ Update the Global Active Directory settings. Required permissions: `Integrations.edit` Parameters: -- `body` [body, settings_AdSettingsPutSchema]: + +- `body` [body, settings_AdSettingsPutSchema]: Responses: 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/active-directory/scope-mapping` + **Get AD FQDNs** `operationId`: `_web_api_settings_active-directory_scope-mapping_get` @@ -38,12 +43,14 @@ Get the map of Active Directory FQDNs to user roles of the given Sites (use "sit Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 403 User is not allowed in this scope, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/active-directory/scope-mapping` + **Set AD FQDNs** `operationId`: `_web_api_settings_active-directory_scope-mapping_put` @@ -52,11 +59,13 @@ Update the Active Directory FQDNs of a Site or Account. Required permissions: `Integrations.edit` Parameters: -- `body` [body, settings_AdFqdnsPutSchema]: + +- `body` [body, settings_AdFqdnsPutSchema]: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/settings/active-directory/test` + **Test AD Settings** `operationId`: `_web_api_settings_active-directory_test_post` @@ -65,11 +74,13 @@ Test Active Directory settings. Required permissions: `Integrations.create` Parameters: -- `body` [body, settings_AdSettingsPutSchema]: + +- `body` [body, settings_AdSettingsPutSchema]: Responses: 404 Scope does not exist, 400 Invalid user input received. See error details for further i, 200 Data retrieved successfully, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/microsoft` + **Get Microsoft Settings** `operationId`: `_web_api_settings_microsoft_get` @@ -78,12 +89,14 @@ Responses: 404 Scope does not exist, 400 Invalid user input received. See error Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/microsoft` + **Set Microsoft Settings** `operationId`: `_web_api_settings_microsoft_put` @@ -92,11 +105,13 @@ Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Required permissions: `Integrations.edit` Parameters: -- `body` [body, settings_MicrosoftSettingsPutSchema]: + +- `body` [body, settings_MicrosoftSettingsPutSchema]: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/settings/microsoft/test` + **Test Microsoft Settings** `operationId`: `_web_api_settings_microsoft_test_post` @@ -105,11 +120,13 @@ Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Required permissions: `Integrations.create` Parameters: -- `body` [body, settings_MicrosoftSettingsPutSchema]: + +- `body` [body, settings_MicrosoftSettingsPutSchema]: Responses: 404 Scope does not exist, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/notifications` + **Get Notification Settings** `operationId`: `_web_api_settings_notifications_get` @@ -118,12 +135,14 @@ Get the notification settings for the given Sites (to get the IDs, run "settings Required permissions: `Notifications.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 403 User is not allowed in this scope, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/notifications` + **Set Notification Settings** `operationId`: `_web_api_settings_notifications_put` @@ -132,11 +151,13 @@ Change the notifications for the given Sites (to get the IDs, run "settings") or Required permissions: `Notifications.edit` Parameters: -- `body` [body, notifications_schemas_NotificationSettingsPutSchema]: + +- `body` [body, notifications_schemas_NotificationSettingsPutSchema]: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/settings/notifications/cancel-pending-emails` + **Clear Pending Emails** `operationId`: `_web_api_settings_notifications_cancel-pending-emails_post` @@ -145,11 +166,13 @@ Clear (discard without sending) pending email notifications for the given Sites Required permissions: `Notifications.delete` Parameters: -- `body` [body, notifications_schemas_CancelPendingEmailNotificationsPostSchema]: + +- `body` [body, notifications_schemas_CancelPendingEmailNotificationsPostSchema]: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/recipients` + **Get Notification Recipients** `operationId`: `_web_api_settings_recipients_get` @@ -158,6 +181,7 @@ Get the emails that are configured to receive notifications. Required permissions: `Notifications.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `name` [query, string]: Name @@ -168,6 +192,7 @@ Parameters: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/recipients` + **Set Notification Recipients** `operationId`: `_web_api_settings_recipients_put` @@ -177,11 +202,13 @@ Required permissions: `Notifications.edit` Optional permissions: `Notifications.create` Parameters: -- `body` [body, settings_NotificationRecipientSettingsPutSchema]: + +- `body` [body, settings_NotificationRecipientSettingsPutSchema]: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/settings/recipients/{recipient_id}` + **Delete Notification Recipient** `operationId`: `_web_api_settings_recipients_{recipient_id}_delete` @@ -190,11 +217,13 @@ Delete a notification recipient by ID. To get the IDs of recipients, run "recipi Required permissions: `Notifications.delete` Parameters: + - `recipient_id` [path, string] **required**: Recipient ID. Example: "225494730938493804". Responses: 403 Insufficient permissions., 200 Recipient deleted successfully., 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/sms` + **Get SMS Settings** `operationId`: `_web_api_settings_sms_get` @@ -203,12 +232,14 @@ Responses: 403 Insufficient permissions., 200 Recipient deleted successfully., 4 Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/sms` + **Set SMS Settings** `operationId`: `_web_api_settings_sms_put` @@ -217,11 +248,13 @@ Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Required permissions: `Integrations.edit` Parameters: -- `body` [body, settings_SmsSettingsPutSchema]: + +- `body` [body, settings_SmsSettingsPutSchema]: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/smtp` + **Get SMTP Settings** `operationId`: `_web_api_settings_smtp_get` @@ -230,12 +263,14 @@ Get the SMTP server configuration of the given Sites (to get the IDs, run "sites Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/smtp` + **Set SMTP Settings** `operationId`: `_web_api_settings_smtp_put` @@ -244,11 +279,13 @@ Change the SMTP server configuration for the given Sites or Accounts. Use this c Required permissions: `Integrations.edit` Parameters: -- `body` [body, settings_SmtpSettingsPutSchema]: + +- `body` [body, settings_SmtpSettingsPutSchema]: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/settings/smtp/test` + **Test SMTP Settings** `operationId`: `_web_api_settings_smtp_test_post` @@ -257,11 +294,13 @@ Test SMTP settings between the Management and the SMTP server. This integration Required permissions: `Integrations.create` Parameters: -- `body` [body, settings_SmtpSettingsTestSchema]: + +- `body` [body, settings_SmtpSettingsTestSchema]: Responses: 404 Scope does not exist, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/sso` + **Get SSO Settings** `operationId`: `_web_api_settings_sso_get` @@ -270,25 +309,29 @@ Get the Single Sign-On configuration for the given Sites (to get the IDs, run "s Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/sso` + **Set SSO Settings** `operationId`: `_web_api_settings_sso_put` -Change the Single Sign-On configuration for the given Sites (to get the IDs, run "sites") or Accounts ("accounts").
The Management supports SAML 2.0 and will integrate with SAML 2.0 compliant SSO providers.
SentinelOne Technical Support can help you with issues related to the provider we tested: Okta. To use a different ID provider, see the provider documentation and support.
For requirements and best practices of Okta integration, see https://support.sentinelone.com/hc/en-us/articles/360004195714. +Change the Single Sign-On configuration for the given Sites (to get the IDs, run "sites") or Accounts ("accounts").
The Management supports SAML 2.0 and will integrate with SAML 2.0 compliant SSO providers.
SentinelOne Technical Support can help you with issues related to the provider we tested: Okta. To use a different ID provider, see the provider documentation and support.
For requirements and best practices of Okta integration, see . Required permissions: `Integrations.edit` Parameters: -- `body` [body, settings_SsoSettingsPutSchema]: + +- `body` [body, settings_SsoSettingsPutSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/sso/sp-cert` + **Get SSO Service Provider Certificate** `operationId`: `_web_api_settings_sso_sp-cert_get` @@ -297,12 +340,14 @@ Get the Service Provider Certificate for the Single Sign-On configuration for th Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/sso/sp-cert/download` + **Download SSO Service Provider Certificate** `operationId`: `_web_api_settings_sso_sp-cert_download_get` @@ -311,12 +356,14 @@ Download the Service Provider Certificate for the Single Sign-On configuration f Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/settings/sso/test` + **Test SSO Settings** `operationId`: `_web_api_settings_sso_test_post` @@ -325,11 +372,13 @@ Test Single Sign-On settings. Required permissions: `Integrations.create` Parameters: -- `body` [body, settings_SsoSettingsPutSchema]: + +- `body` [body, settings_SsoSettingsPutSchema]: Responses: 200 The url to redirect too., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/settings/syslog` + **Get Syslog Settings** `operationId`: `_web_api_settings_syslog_get` @@ -338,12 +387,14 @@ Get the configuration of the syslog server integrated with the given Sites (to g Required permissions: `Integrations.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/settings/syslog` + **Set Syslog Settings** `operationId`: `_web_api_settings_syslog_put` @@ -352,11 +403,13 @@ Change the configuration of the syslog server of the given Sites (to get the IDs Required permissions: `Integrations.edit` Parameters: -- `body` [body, settings_SyslogSettingsPutSchema]: + +- `body` [body, settings_SyslogSettingsPutSchema]: Responses: 404 Scope does not exist, 403 User is not allowed in this scope, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/settings/syslog/test` + **Test Syslog Settings** `operationId`: `_web_api_settings_syslog_test_post` @@ -365,6 +418,7 @@ Test Syslog settings. The Management tests the connection to the Syslog server. Required permissions: `Integrations.create` Parameters: -- `body` [body, settings_SyslogSettingsPutSchema]: + +- `body` [body, settings_SyslogSettingsPutSchema]: Responses: 404 Scope does not exist, 200 Data retrieved successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Sites.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Sites.md index bcb7926..3da14cc 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Sites.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Sites.md @@ -3,6 +3,7 @@ 17 endpoints. ## `GET /web/api/v2.1/export/sites` + **Export Sites** `operationId`: `_web_api_export_sites_get` @@ -11,6 +12,7 @@ Export Sites data to a CSV, for Sites that match the filter. Required permissions: `Sites.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `query` [query, string]: Full text search for fields: name, account_name, description. (Note: on single-account consoles account name will not be matched) @@ -43,6 +45,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/site-with-admin` + **Create Site and User** `operationId`: `_web_api_site-with-admin_post` @@ -51,11 +54,13 @@ Create a Site and an Admin role user. This requires an Admin role with a Global Required permissions: `Sites.create` Parameters: -- `body` [body, sites_SiteDataWithUserSchema]: + +- `body` [body, sites_SiteDataWithUserSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/sites` + **Get Sites** `operationId`: `_web_api_sites_get` @@ -65,6 +70,7 @@ Required permissions: `Sites.view` Optional permissions: `Endpoints.moveToAnotherSite` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -104,6 +110,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/sites` + **Create Site** `operationId`: `_web_api_sites_post` @@ -112,11 +119,13 @@ Create a Site. This requires an Admin role with a Global scope or Account scope Required permissions: `Sites.create` Parameters: -- `body` [body, sites_PostSiteSchema]: + +- `body` [body, sites_PostSiteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/sites/duplicate-site` + **Create duplicate site** `operationId`: `_web_api_sites_duplicate-site_post` @@ -125,11 +134,13 @@ Responses: 200 Success, 400 Invalid user input received. See error details for f Required permissions: `Sites.create` Parameters: -- `body` [body, sites_DuplicateSiteSchema]: + +- `body` [body, sites_DuplicateSiteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/sites/update-bulk` + **Update Sites** `operationId`: `_web_api_sites_update-bulk_put` @@ -138,11 +149,13 @@ Change the properties of the Sites given by IDs.
To get the IDs, run 'sites' Required permissions: `Sites.edit` Parameters: -- `body` [body, sites_SiteBulkPutSchema]: + +- `body` [body, sites_SiteBulkPutSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/sites/{site_id}` + **Delete Site** `operationId`: `_web_api_sites_{site_id}_delete` @@ -151,11 +164,13 @@ Delete the Site of the given ID. To get the ID, run "sites".
You must have a Required permissions: `Sites.delete` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/sites/{site_id}` + **Get Site by ID** `operationId`: `_web_api_sites_{site_id}_get` @@ -164,11 +179,13 @@ Get the data of the Site of the ID. To get the ID, run "sites".
The response Required permissions: `Sites.view` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 404 Site not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/sites/{site_id}` + **Update Site** `operationId`: `_web_api_sites_{site_id}_put` @@ -177,12 +194,14 @@ Change the policy and properties of the Site given by ID.
To get the ID, run Required permissions: `Sites.edit` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". -- `body` [body, sites_SitePutSchema]: +- `body` [body, sites_SitePutSchema]: Responses: 404 Site not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/sites/{site_id}/expire-now` + **Expire Site** `operationId`: `_web_api_sites_{site_id}_expire-now_post` @@ -191,11 +210,13 @@ Expire the Site of the given ID (run "sites" to get the ID).
You must have a Required permissions: `Sites.edit` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 404 Site not found, 200 Expire site now, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/sites/{site_id}/local-authorization` + **Get local upgrade/downgrade Site authorization** `operationId`: `_web_api_sites_{site_id}_local-authorization_get` @@ -204,11 +225,13 @@ Get the time when authorization of local upgrades/downgrades expires, and the nu Required permissions: `Local Upgrade/Downgrade Authorization.view` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/sites/{site_id}/local-authorization` + **Edit local upgrade/downgrade Site authorization** `operationId`: `_web_api_sites_{site_id}_local-authorization_put` @@ -217,12 +240,14 @@ Edit when authorization of local upgrades/downgrades expires. Returns the number Required permissions: `Local Upgrade/Downgrade Authorization.edit` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". -- `body` [body, sites_PutSiteApprovalJsonSchema]: +- `body` [body, sites_PutSiteApprovalJsonSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/sites/{site_id}/local-upgrade-approved-agents-csv` + **Get a CSV file of local upgrade/downgrade Site authorization data** `operationId`: `_web_api_sites_{site_id}_local-upgrade-approved-agents-csv_get` @@ -231,11 +256,13 @@ Get a CSV file containing the Agents authorized for local upgrades/downgrades, i Required permissions: `Local Upgrade/Downgrade Authorization.view` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/sites/{site_id}/reactivate` + **Reactivate Site** `operationId`: `_web_api_sites_{site_id}_reactivate_put` @@ -244,12 +271,14 @@ Reactivate an expired Site.
You must have an Admin role with scope access th Required permissions: `Sites.edit` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". -- `body` [body, sites_ReactivateSiteSchema]: +- `body` [body, sites_ReactivateSiteSchema]: Responses: 404 Site not found, 200 Site reactivated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/sites/{site_id}/regenerate-key` + **Regenerate Site Key** `operationId`: `_web_api_sites_{site_id}_regenerate-key_put` @@ -258,11 +287,13 @@ Regenerate the key for the given Site.
To get the site_id, use "sites". Required permissions: `Sites.edit` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 403 No permission for regenerating a key., 404 Site not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/sites/{site_id}/revert-policy` + **Revert Policy** `operationId`: `_web_api_sites_{site_id}_revert-policy_put` @@ -271,12 +302,14 @@ When a Site is created through the Console, it gets the Global policy.
If yo Required permissions: `Policy.edit` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". -- `body` [body, policies_schemas_RevertPolicySchema]: +- `body` [body, policies_schemas_RevertPolicySchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/sites/{site_id}/token` + **Get Site registration token by ID** `operationId`: `_web_api_sites_{site_id}_token_get` @@ -285,6 +318,7 @@ Get the registration token of the Site of the ID. Required permissions: `Sites.view` Parameters: + - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 404 Site not found, 200 Success, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/System.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/System.md index 007d831..e555c9e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/System.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/System.md @@ -3,6 +3,7 @@ 7 endpoints. ## `GET /web/api/v2.1/system/configuration` + **Get System Config** `operationId`: `_web_api_system_configuration_get` @@ -11,12 +12,14 @@ Get the configuration of your SentinelOne system.
The response shows basic i Required permissions: `Configuration.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/system/configuration` + **Set System Config** `operationId`: `_web_api_system_configuration_put` @@ -26,11 +29,13 @@ Required permissions: `Configuration.edit` Optional permissions: `Users.allow2FAForOtherUsers` Parameters: -- `body` [body, system_PutSystemConfiguration]: + +- `body` [body, system_PutSystemConfiguration]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/system/env` + **System Environment** `operationId`: `_web_api_system_env_get` @@ -39,6 +44,7 @@ Get environment details of the system Responses: 200 Success ## `GET /web/api/v2.1/system/info` + **System Info** `operationId`: `_web_api_system_info_get` @@ -47,6 +53,7 @@ Get the Console build, version, patch, and release information. Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/system/status` + **System Status** `operationId`: `_web_api_system_status_get` @@ -55,6 +62,7 @@ Get an indication of the system's health status.
This command always returns Responses: 200 Success ## `GET /web/api/v2.1/system/status/cache` + **Cache Status** `operationId`: `_web_api_system_status_cache_get` @@ -63,6 +71,7 @@ Responses: 200 Success Responses: 200 Success ## `GET /web/api/v2.1/system/status/db` + **Database Status** `operationId`: `_web_api_system_status_db_get` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tag_Manager.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tag_Manager.md index 25553bb..4cc2612 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tag_Manager.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tag_Manager.md @@ -3,6 +3,7 @@ 3 endpoints. ## `DELETE /web/api/v2.1/tag-manager` + **Delete tags** `operationId`: `_web_api_tag-manager_delete` @@ -11,11 +12,13 @@ Delete all tags that match the filters. Required permissions: `Tag Management.delete` Parameters: -- `body` [body, v2_1.mgmt_tag_manager.schemas_TagsDeleteSchema]: + +- `body` [body, v2_1.mgmt_tag_manager.schemas_TagsDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/tag-manager` + **Create a new endpoint tag** `operationId`: `_web_api_tag-manager_post` @@ -24,11 +27,13 @@ Each tag must contain a type (endpoints) and key, Value is optional but recommen Required permissions: `Tag Management.create` Parameters: -- `body` [body, v2_1.mgmt_tag_manager.schemas_PostTagSchema]: + +- `body` [body, v2_1.mgmt_tag_manager.schemas_PostTagSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/tag-manager/{tag_id}` + **Edit an existing tag** `operationId`: `_web_api_tag-manager_{tag_id}_put` @@ -37,7 +42,8 @@ Change the key, value, or description of a tag. Required permissions: `Tag Management.edit` Parameters: + - `tag_id` [path, string] **required**: Tag ID. You can get the ID from the Get Tag-Manager command. Example: "225494730938493804". -- `body` [body, v2_1.mgmt_tag_manager.schemas_PutTagSchema]: +- `body` [body, v2_1.mgmt_tag_manager.schemas_PutTagSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tags.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tags.md index 6f66af4..192ade9 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tags.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tags.md @@ -3,6 +3,7 @@ 5 endpoints. ## `DELETE /web/api/v2.1/tags` + **Delete Tags** `operationId`: `_web_api_tags_delete` @@ -10,11 +11,13 @@ Delete tags by given filter. Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags, Ranger.manageDeviceTags` Parameters: -- `body` [body, tags.schemas_TagDeleteSchema]: + +- `body` [body, tags.schemas_TagDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/tags` + **Get Tags** `operationId`: `_web_api_tags_get` @@ -22,6 +25,7 @@ Get tags. Optional permissions: `Firewall Control.view, Network Quarantine Control.view, Ranger.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -45,6 +49,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/tags` + **Create Tags** `operationId`: `_web_api_tags_post` @@ -52,11 +57,13 @@ Add tags to create user-defined logical groups. Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags, Ranger.manageDeviceTags` Parameters: -- `body` [body, tags.schemas_PostTagSchema]: + +- `body` [body, tags.schemas_PostTagSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/tags/{tag_id}` + **Delete Tag by ID** `operationId`: `_web_api_tags_{tag_id}_delete` @@ -64,11 +71,13 @@ Delete tag by ID. Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags, Ranger.manageDeviceTags` Parameters: + - `tag_id` [path, string] **required**: Rule ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/tags/{tag_id}` + **Edit Tag** `operationId`: `_web_api_tags_{tag_id}_put` @@ -76,7 +85,8 @@ Edit tag Optional permissions: `Firewall Control.manageRulesAndTags, Network Quarantine Control.manageRulesAndTags, Ranger.manageDeviceTags` Parameters: + - `tag_id` [path, string] **required**: Rule ID. Example: "225494730938493804". -- `body` [body, tags.schemas_PutTagSchema]: +- `body` [body, tags.schemas_PutTagSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tasks.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tasks.md index af99484..685999e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tasks.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Tasks.md @@ -3,6 +3,7 @@ 7 endpoints. ## `GET /web/api/v2.1/tasks-configuration` + **Get Task Configuration** `operationId`: `_web_api_tasks-configuration_get` @@ -11,6 +12,7 @@ Get the task configuration of a scope. Required permissions: `Upgrade Policy.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -27,6 +29,7 @@ Parameters: Responses: 403 Insufficient permissions, 404 Configuration not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/tasks-configuration` + **Create Task** `operationId`: `_web_api_tasks-configuration_put` @@ -35,11 +38,13 @@ Create a task configuration. Required permissions: `Upgrade Policy.edit` Parameters: -- `body` [body, tasks.schemas_PutTaskSchema]: + +- `body` [body, tasks.schemas_PutTaskSchema]: Responses: 403 Operation is not allowed, 404 Configuration not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/tasks-configuration/explicit-subscopes` + **Get Child Scope Task Configuration** `operationId`: `_web_api_tasks-configuration_explicit-subscopes_get` @@ -48,6 +53,7 @@ Get the task configuration of child scopes of the given scope, if the tasks are Required permissions: `Upgrade Policy.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -65,6 +71,7 @@ Parameters: Responses: 403 User is not allowed in this scope, 404 Configuration not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/tasks-configuration/flexible` + **Get Task Configuration (Flexible MW)** `operationId`: `_web_api_tasks-configuration_flexible_get` @@ -73,6 +80,7 @@ Get task configuration with flexible maintenance window format. Returns policy_p Required permissions: `Upgrade Policy.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -82,6 +90,7 @@ Parameters: Responses: 403 Insufficient permissions, 404 Configuration not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/tasks-configuration/flexible` + **Update Task Configuration (Flexible MW)** `operationId`: `_web_api_tasks-configuration_flexible_put` @@ -90,11 +99,13 @@ Update task configuration with flexible maintenance window format. Requires mw_a Required permissions: `Upgrade Policy.edit` Parameters: -- `body` [body, tasks.schemas_PutFlexibleTaskSchema]: + +- `body` [body, tasks.schemas_PutFlexibleTaskSchema]: Responses: 400 Invalid user input received. See error details for further i, 403 Operation is not allowed or feature not enabled, 404 Configuration not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/tasks-configuration/has-explicit-subscope` + **Has Child Scopes** `operationId`: `_web_api_tasks-configuration_has-explicit-subscope_get` @@ -103,6 +114,7 @@ From a given scope, see if there are scopes under it that have local, explicit t Required permissions: `Upgrade Policy.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -119,6 +131,7 @@ Parameters: Responses: 403 User is not allowed in this scope, 404 Configuration not found, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/tasks-configuration/maintenance-windows/export_mw` + **Export Maintenance Windows as CSV** `operationId`: `_web_api_tasks-configuration_maintenance-windows_export_mw_get` @@ -127,6 +140,7 @@ Export all maintenance window occurrences for a specific scope as CSV. Supports Required permissions: `Upgrade Policy.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threat_Intelligence.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threat_Intelligence.md index 3d9995f..f2a24d5 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threat_Intelligence.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threat_Intelligence.md @@ -3,6 +3,7 @@ 7 endpoints. ## `DELETE /web/api/v2.1/threat-intelligence/iocs` + **Delete IOCs** `operationId`: `_web_api_threat-intelligence_iocs_delete` @@ -11,11 +12,13 @@ Delete an IoC from the Threat Intelligence database that matches a filter using Required permissions: `Threat Intelligence.manage` Parameters: -- `body` [body, v2_1.schemas_IOCDeleteSchema]: + +- `body` [body, v2_1.schemas_IOCDeleteSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threat-intelligence/iocs` + **Get IOCs** `operationId`: `_web_api_threat-intelligence_iocs_get` @@ -24,7 +27,8 @@ Get the IOCs of a specified Account that match the filter.
Note: Using creati Required permissions: `Threat Intelligence.view` Parameters: -- `creator__contains` [query, array]: Free-text filter by the user uploaded the Threat Intelligence indicator (supports multiple values). Example: "admin@sentinelone.com". + +- `creator__contains` [query, array]: Free-text filter by the user uploaded the Threat Intelligence indicator (supports multiple values). Example: "". - `creationTime__lt` [query, string]: Creation Time as set by the user lesser than. Example: "2021-07-13T20:33:29.007906Z". - `creationTime__lte` [query, string]: Creation Time as set by the user lesser or equal than. Example: "2021-07-11T20:33:29.007906Z". - `severity` [query, array]: A list of severities to filter by (0-7) @@ -66,6 +70,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threat-intelligence/iocs` + **Create IOCs** `operationId`: `_web_api_threat-intelligence_iocs_post` @@ -74,11 +79,13 @@ Add an IoC to the Threat Intelligence database.
These values under data are Required permissions: `Threat Intelligence.manage` Parameters: -- `body` [body, v2_1.schemas_PostThreatIntelligenceSchema]: + +- `body` [body, v2_1.schemas_PostThreatIntelligenceSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threat-intelligence/iocs/stix` + **Create IOCs from STIX bundle** `operationId`: `_web_api_threat-intelligence_iocs_stix_post` @@ -87,11 +94,13 @@ Add IOCs to the Threat Intelligence database from a STIX 2.1 bundle. The API wil Required permissions: `Threat Intelligence.manage` Parameters: -- `body` [body, v2_1.schemas_StixPostSchema]: + +- `body` [body, v2_1.schemas_StixPostSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/threat-intelligence/user-config` + **Delete Threat Intelligence user config** `operationId`: `_web_api_threat-intelligence_user-config_delete` @@ -100,11 +109,13 @@ Delete Threat Intelligence user config that match the filter. Required permissions: `Threat Intelligence.manage` Parameters: -- `body` [body, v2_1.schemas_UserConfigFilterSchema]: + +- `body` [body, v2_1.schemas_UserConfigFilterSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threat-intelligence/user-config` + **Get Threat Intelligence user config** `operationId`: `_web_api_threat-intelligence_user-config_get` @@ -113,6 +124,7 @@ Get the Threat Intelligence user config that match the filter. Required permissions: `Threat Intelligence.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "4,2,6,4,1,8,0,3,0,2,1,2,0,7,3,7,6,2". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "4,2,6,4,1,8,0,3,0,2,1,2,0,7,3,7,6,2". - `tenant` [query, boolean]: Indicates a tenant scope request @@ -120,6 +132,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threat-intelligence/user-config` + **Create Threat Intelligence user config** `operationId`: `_web_api_threat-intelligence_user-config_post` @@ -128,6 +141,7 @@ Create Threat Intelligence user config. Required permissions: `Threat Intelligence.manage` Parameters: -- `body` [body, v2_1.schemas_PostUserConfigSchema]: + +- `body` [body, v2_1.schemas_PostUserConfigSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threat_Notes.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threat_Notes.md index 59899ea..f8d8f18 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threat_Notes.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threat_Notes.md @@ -3,6 +3,7 @@ 4 endpoints. ## `POST /web/api/v2.1/threats/notes` + **Add Note to Multiple** `operationId`: `_web_api_threats_notes_post` @@ -11,11 +12,13 @@ Add a threat note to multiple threats. Required permissions: `Threats.view` Parameters: -- `body` [body, threats.schemas_ThreatsNoteCreateSchema]: + +- `body` [body, threats.schemas_ThreatsNoteCreateSchema]: Responses: 200 Threats note successfully created, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threats/{threat_id}/notes` + **Get Threat Notes** `operationId`: `_web_api_threats_{threat_id}_notes_get` @@ -24,6 +27,7 @@ Get the threat notes that match the filter. Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". @@ -38,6 +42,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/threats/{threat_id}/notes/{note_id}` + **Delete Threat Note** `operationId`: `_web_api_threats_{threat_id}_notes_{note_id}_delete` @@ -46,12 +51,14 @@ Delete a threat note. Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". - `note_id` [path, string] **required**: Threat Note ID. Example: "225494730938493804". Responses: 200 Threat note successfully deleted, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/threats/{threat_id}/notes/{note_id}` + **Update Threat Note** `operationId`: `_web_api_threats_{threat_id}_notes_{note_id}_put` @@ -60,8 +67,9 @@ Change the text of a threat note. Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". - `note_id` [path, string] **required**: Threat Note ID. Example: "225494730938493804". -- `body` [body, threats.schemas_PostThreatNoteDataSchema]: +- `body` [body, threats.schemas_PostThreatNoteDataSchema]: Responses: 200 Threat note successfully updated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threats.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threats.md index ba69000..3cdd3cc 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threats.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Threats.md @@ -3,6 +3,7 @@ 22 endpoints. ## `GET /web/api/v2.1/export/threats/{threat_id}/explore/events` + **Export Events** `operationId`: `_web_api_export_threats_{threat_id}_explore_events_get` @@ -11,6 +12,7 @@ Export threat events in CSV or JSON format. Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". - `eventTypes` [query, array]: Filter events by type. Example: "events". - `eventSubTypes` [query, array]: Filter events by sub-type. Example: "PROCESSCREATION". @@ -21,6 +23,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/export/threats/{threat_id}/timeline` + **Export Threat Timeline** `operationId`: `_web_api_export_threats_{threat_id}_timeline_get` @@ -29,6 +32,7 @@ Export a threat's timeline. Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -39,6 +43,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threats` + **Get Threats** `operationId`: `_web_api_threats_get` @@ -47,6 +52,7 @@ Get data of threats that match the filter.
Best Practice: Use the filters. E Required permissions: `Threats.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -89,8 +95,8 @@ Parameters: - `osTypes` [query, array]: Included OS types. Example: "macos". - `osTypesNin` [query, array]: Excluded OS types. Example: "macos". - `osArchs` [query, array]: Included OS Architectures. Example: "32 bit". -- `osNames` [query, array]: -- `osNamesNin` [query, array]: +- `osNames` [query, array]: +- `osNamesNin` [query, array]: - `agentIsActive` [query, boolean]: Include Agents currently connected to the Management Console - `initiatedBy` [query, array]: Only include threats from specific initiating sources. Example: "agent_policy,dv_command". - `initiatedByNin` [query, array]: Exclude threats with specific initiating sources. Example: "agent_policy,dv_command". @@ -156,6 +162,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/actions/container-network-connect` + **Reconnect Container** `operationId`: `_web_api_threats_actions_container-network-connect_post` @@ -164,11 +171,13 @@ Restore network to a container that was disconnected Required permissions: `Endpoints.reconnectToNetwork` Parameters: -- `body` [body, threats.schemas_ContainerNetworkQuarantineSchema]: + +- `body` [body, threats.schemas_ContainerNetworkQuarantineSchema]: Responses: 200 Reconnect command was created, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/actions/container-network-disconnect` + **Disconnect Container** `operationId`: `_web_api_threats_actions_container-network-disconnect_post` @@ -177,11 +186,13 @@ Network quarantine a specific container Required permissions: `Endpoints.disconnectFromNetwork` Parameters: -- `body` [body, threats.schemas_ContainerNetworkQuarantineSchema]: + +- `body` [body, threats.schemas_ContainerNetworkQuarantineSchema]: Responses: 200 Disconnect command was created, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/add-to-blacklist` + **Add to Blocklist** `operationId`: `_web_api_threats_add-to-blacklist_post` @@ -191,11 +202,13 @@ Required permissions: `Blacklist.create` Optional permissions: `Threats.updateAnalystVerdict` Parameters: -- `body` [body, threats.schemas_ThreatsAddToRestrictionsWithTargetSchema]: + +- `body` [body, threats.schemas_ThreatsAddToRestrictionsWithTargetSchema]: Responses: 200 Hash threat added to black list, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/add-to-exclusions` + **Add to Exclusions** `operationId`: `_web_api_threats_add-to-exclusions_post` @@ -205,11 +218,13 @@ Required permissions: `Exclusions.create` Optional permissions: `Threats.updateAnalystVerdict` Parameters: -- `body` [body, threats.schemas_ThreatsAddToExclusionsWithTargetSchema]: + +- `body` [body, threats.schemas_ThreatsAddToExclusionsWithTargetSchema]: Responses: 200 Added to exclusions, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/analyst-verdict` + **Update Threat Analyst Verdict** `operationId`: `_web_api_threats_analyst-verdict_post` @@ -218,11 +233,13 @@ Change the verdict of a threat, as determined by a Console user. Required permissions: `Threats.updateAnalystVerdict` Parameters: -- `body` [body, threats.schemas_ThreatsAnalystVerdictSchema]: + +- `body` [body, threats.schemas_ThreatsAnalystVerdictSchema]: Responses: 200 Threats analyst verdict successfully updated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/dv-add-to-blacklist` + **Add to Blocklist (Deep Visibility)** `operationId`: `_web_api_threats_dv-add-to-blacklist_post` @@ -231,11 +248,13 @@ From Deep Visibility results, add a SHA1 hash to the Blocklist. Set the scope of Required permissions: `Blacklist.create` Parameters: -- `body` [body, threats.schemas_DvAddToBlackListSchema]: + +- `body` [body, threats.schemas_DvAddToBlackListSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/dv-mark-as-threat` + **Mark as Threat (Deep Visibility)** `operationId`: `_web_api_threats_dv-mark-as-threat_post` @@ -244,11 +263,13 @@ Mark an event from Deep Visibility data as a threat. (see Deep Visibility > Get Required permissions: `Threats.markThreat` Parameters: -- `body` [body, threats.schemas_DvMarkAsThreatSchema]: + +- `body` [body, threats.schemas_DvMarkAsThreatSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/engines/disable` + **Disable Engines** `operationId`: `_web_api_threats_engines_disable_post` @@ -257,11 +278,13 @@ If your list of threats shows too many False Positives, use this command to trou Required permissions: `Policy.edit` Parameters: -- `body` [body, threats.schemas_EngineListSchema]: + +- `body` [body, threats.schemas_EngineListSchema]: Responses: 200 Engines disabled, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threats/export` + **Export Threats** `operationId`: `_web_api_threats_export_get` @@ -270,6 +293,7 @@ Export data of threats (as seen in the Console > Incidents) that match the filte Required permissions: `Threats.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -305,8 +329,8 @@ Parameters: - `osTypes` [query, array]: Included OS types. Example: "macos". - `osTypesNin` [query, array]: Excluded OS types. Example: "macos". - `osArchs` [query, array]: Included OS Architectures. Example: "32 bit". -- `osNames` [query, array]: -- `osNamesNin` [query, array]: +- `osNames` [query, array]: +- `osNamesNin` [query, array]: - `agentIsActive` [query, boolean]: Include Agents currently connected to the Management Console - `initiatedBy` [query, array]: Only include threats from specific initiating sources. Example: "agent_policy,dv_command". - `initiatedByNin` [query, array]: Exclude threats with specific initiating sources. Example: "agent_policy,dv_command". @@ -372,6 +396,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/external-ticket-id` + **Update Threat External Ticket ID** `operationId`: `_web_api_threats_external-ticket-id_post` @@ -380,11 +405,13 @@ Change the external ticket ID of a threat. Required permissions: `Threats.updateExternalTicketId` Parameters: -- `body` [body, threats.schemas_ThreatExternalTicketSchema]: + +- `body` [body, threats.schemas_ThreatExternalTicketSchema]: Responses: 200 Threats external ticket id successfully updated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/fetch-file` + **Fetch Threat File** `operationId`: `_web_api_threats_fetch-file_post` @@ -393,11 +420,13 @@ Fetch a file associated with the threat that matches the filter. Your user role Required permissions: `Threats.fetchThreatFile` Parameters: -- `body` [body, threats.schemas_ThreatsFetchFileRequestSchema]: + +- `body` [body, threats.schemas_ThreatsFetchFileRequestSchema]: Responses: 200 Number of affected agents, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/incident` + **Updated Threat Incident** `operationId`: `_web_api_threats_incident_post` @@ -407,11 +436,13 @@ Required permissions: `Threats.updateIncidentStatus` Optional permissions: `Threats.updateAnalystVerdict` Parameters: -- `body` [body, threats.schemas_ThreatsIncidentSchema]: + +- `body` [body, threats.schemas_ThreatsIncidentSchema]: Responses: 200 Threats incident successfully updated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/mitigate-alerts` + **Mitigate Alerts** `operationId`: `_web_api_threats_mitigate-alerts_post` @@ -420,11 +451,13 @@ Mark an alerts as a threat and run mitigation action from the Management UI. Required permissions: `Threats.markThreat` Parameters: -- `body` [body, threats.schemas_MitigateAlertsSchema]: + +- `body` [body, threats.schemas_MitigateAlertsSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/threats/mitigate/{action}` + **Mitigate Threats** `operationId`: `_web_api_threats_mitigate_{action}_post` @@ -432,12 +465,14 @@ Apply a mitigation action to a group of threats that match the filter. Valid val Optional permissions: `Threats.kill, Threats.quarantine, Threats.unquarantine, Threats.remediate, Threats.rollback, Threats.removeMacro, Threats.restoreMacro` Parameters: + - `action` [path, string] **required** (enum: kill, remediate, rollback-remediation, quarantine, un-quarantine, None, remove_macros, restore_macros): Mitigation action. Example: "kill". -- `body` [body, threats.schemas_ThreatsMitigateRequestSchema]: +- `body` [body, threats.schemas_ThreatsMitigateRequestSchema]: Responses: 200 Threat successfully mitigated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threats/mitigation-report/{report_id}` + **Export Mitigation Report** `operationId`: `_web_api_threats_mitigation-report_{report_id}_get` @@ -446,11 +481,13 @@ Export the mitigation report as a CSV file. Required permissions: `Activity Page.view` Parameters: + - `report_id` [path, string] **required**: Mitigation report ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threats/{threat_id}/download-from-cloud` + **Download from cloud** `operationId`: `_web_api_threats_{threat_id}_download-from-cloud_get` @@ -459,11 +496,13 @@ Download threat file from cloud. Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threats/{threat_id}/explore/events` + **Get Events** `operationId`: `_web_api_threats_{threat_id}_explore_events_get` @@ -472,6 +511,7 @@ Get all threat events. Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". @@ -488,6 +528,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threats/{threat_id}/timeline` + **Get Threat Timeline** `operationId`: `_web_api_threats_{threat_id}_timeline_get` @@ -496,6 +537,7 @@ Get a threat's timeline. Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". @@ -513,6 +555,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/threats/{threat_id}/whitening-options` + **Exclusion Options** `operationId`: `_web_api_threats_{threat_id}_whitening-options_get` @@ -521,6 +564,7 @@ Get the Exclusion types that can be created from the detection data.
For ex Required permissions: `Threats.view` Parameters: + - `threat_id` [path, string] **required**: Threat ID. Example: "225494730938493804". Responses: 200 Success, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Unprotected_Endpoints_Discovery.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Unprotected_Endpoints_Discovery.md index e00f7e2..b848a2a 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Unprotected_Endpoints_Discovery.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Unprotected_Endpoints_Discovery.md @@ -3,6 +3,7 @@ 4 endpoints. ## `GET /web/api/v2.1/rogues/report/csv` + **Export Unprotected Endpoints Discovery Data** `operationId`: `_web_api_rogues_report_csv_get` @@ -11,6 +12,7 @@ Export Unprotected Endpoints Discovery data to CSV. You can set filters to get o Required permissions: `Rogues.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -48,20 +50,23 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/rogues/settings` + **Get Unprotected Endpoints Discovery Settings** `operationId`: `_web_api_rogues_settings_get` -Unprotected Endpoints Discovery gives full visibility of all unsecured devices connected to your network. Unprotected Endpoints Discovery scans your corporate environment to identify and manage connected devices, even those not protected by or supported by SentinelOne. Unprotected Endpoints Discovery identifies devices as:
* UnSecured - End-user computer or laptop, or server, without a SentinelOne Agent.
When you install Windows Agents with Unprotected Endpoints Discovery, the Agents can become scanners. Selected scanners from networks that you enable for scanning find connected devices with passive and active scan techniques. The scanners send the collected data to Unprotected Endpoints Discovery page on the Management. Unprotected Endpoints Discovery then runs fingerprinting to identify and classify unique devices and to update the Device Inventory Table in the Management Console. With port scanning, it is important that you understand the legal and ethical considerations and that you document an Unprotected Endpoints Discovery plan and implementation. See Legal Considerations and Proper Implementation in the Console Help.
* minAgentsInNetworkToScan - To help you dete … +Unprotected Endpoints Discovery gives full visibility of all unsecured devices connected to your network. Unprotected Endpoints Discovery scans your corporate environment to identify and manage connected devices, even those not protected by or supported by SentinelOne. Unprotected Endpoints Discovery identifies devices as:
*UnSecured - End-user computer or laptop, or server, without a SentinelOne Agent.
When you install Windows Agents with Unprotected Endpoints Discovery, the Agents can become scanners. Selected scanners from networks that you enable for scanning find connected devices with passive and active scan techniques. The scanners send the collected data to Unprotected Endpoints Discovery page on the Management. Unprotected Endpoints Discovery then runs fingerprinting to identify and classify unique devices and to update the Device Inventory Table in the Management Console. With port scanning, it is important that you understand the legal and ethical considerations and that you document an Unprotected Endpoints Discovery plan and implementation. See Legal Considerations and Proper Implementation in the Console Help.
* minAgentsInNetworkToScan - To help you dete … Required permissions: `Rogues.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/rogues/settings` + **Update Unprotected Endpoints Discovery Settings** `operationId`: `_web_api_rogues_settings_put` @@ -70,11 +75,13 @@ Change the Unprotected Endpoints Discovery Settings. Best Practice: Get the curr Required permissions: `Rogues.edit` Parameters: -- `body` [body, rogue_schemas_PutRoguesSchema]: + +- `body` [body, rogue_schemas_PutRoguesSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/rogues/table-view` + **Get Unprotected Endpoints Discovery Table** `operationId`: `_web_api_rogues_table-view_get` @@ -83,6 +90,7 @@ Get the data for each row in the Unprotected Endpoints Discovery Device Inventor Required permissions: `Rogues.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Updates.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Updates.md index bd765e9..bf26a5e 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Updates.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Updates.md @@ -3,6 +3,7 @@ 9 endpoints. ## `GET /web/api/v2.1/update/agent/download/{package_id}` + **Download Agent Package** `operationId`: `_web_api_update_agent_download_{package_id}_get` @@ -11,11 +12,13 @@ Required permissions: `Packages.view` Parameters: + - `package_id` [path, string] **required**: Package ID. Example: "225494730938493804". Responses: 404 Package not found, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/update/agent/download/{site_id}/{package_id}` + **Download Package** `operationId`: `_web_api_update_agent_download_{site_id}_{package_id}_get` @@ -24,12 +27,14 @@ Download a package by site_id ("sites") and filename.
Rate limit: 2 call per Required permissions: `Packages.view` Parameters: + - `package_id` [path, string] **required**: Package ID. Example: "225494730938493804". - `site_id` [path, string] **required**: Site ID. Example: "225494730938493804". Responses: 404 Package not found or bad site, 200 Success, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/update/agent/latest-packages` + **Latest Packages by OS** `operationId`: `_web_api_update_agent_latest-packages_get` @@ -38,6 +43,7 @@ Responses: 404 Package not found or bad site, 200 Success, 401 Unauthorized acce Required permissions: `Packages.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `packageType` [query, string] (enum: Agent, Ranger, AgentAndRanger): Package type. Example: "Agent". @@ -45,6 +51,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/update/agent/packages` + **Delete Packages** `operationId`: `_web_api_update_agent_packages_delete` @@ -53,11 +60,13 @@ Delete Agent packages from your Management. Use the IDs from Get Latest Packages Required permissions: `Packages.delete` Parameters: -- `body` [body, packages.schemas_DeletePackagesSchema]: + +- `body` [body, packages.schemas_DeletePackagesSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/update/agent/packages` + **Get Latest Packages** `operationId`: `_web_api_update_agent_packages_get` @@ -66,6 +75,7 @@ Get the Agent packages that are uploaded to your Management.
The response sh Required permissions: `Packages.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -104,6 +114,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/update/agent/packages/{package_id}` + **Update package** `operationId`: `_web_api_update_agent_packages_{package_id}_put` @@ -112,12 +123,14 @@ Update the metadata for an existing package. Required permissions: `Packages.edit` Parameters: + - `package_id` [path, string] **required**: Package ID. Example: "225494730938493804". -- `body` [body, packages.schemas_PutPackageSchema]: +- `body` [body, packages.schemas_PutPackageSchema]: Responses: 404 Package not found, 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/upload/agent/software` + **Upload Agent Package** `operationId`: `_web_api_upload_agent_software_post` @@ -126,6 +139,7 @@ If you have an On-Prem Management or you are a participant in the Beta program, Required permissions: `Packages.edit` Parameters: + - `siteIds` [formData, array]: List of sites to make the package available in. Applicable only if scopeLevel is set to "site". Example: "225494730938493804,225494730938493915". - `accountIds` [formData, array]: List of accounts to make the package available in. Applicable only if scopeLevel is set to "account". Example: "225494730938493804,225494730938493915". - `osType` [formData, string] (enum: macos, windows, linux_k8s, linux, sdk, windows_legacy, threat_detection_s3, threat_detection_netapp): Platform type. Example: "macos". @@ -139,6 +153,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/upload/software` + **Upload System Package** `operationId`: `_web_api_upload_software_post` @@ -147,11 +162,13 @@ If you have an On-Prem Management or otherwise require a manual package upload, Required permissions: `Packages.edit` Parameters: + - `file` [formData, file] **required**: File Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/upload/software/deploy` + **Deploy System Package** `operationId`: `_web_api_upload_software_deploy_post` diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Users.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Users.md index 266acf1..f9bdfcc 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Users.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/Users.md @@ -3,6 +3,7 @@ 41 endpoints. ## `GET /web/api/v2.1/export/users` + **Export Users** `operationId`: `_web_api_export_users_get` @@ -11,11 +12,12 @@ Export User data to a CSV, for Users that match the filter. Required permissions: `Users.view` Parameters: + - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `source` [query, string] (enum: mgmt, sso_saml, active_directory, global): User Source. Example: "mgmt". - `sources` [query, array]: Source in. Example: "mgmt". -- `email` [query, string]: Email. Example: "admin@sentinelone.com". +- `email` [query, string]: Email. Example: "". - `email__contains` [query, array]: Match email partially (substring) - `emailReadOnly` [query, boolean]: True if email cannot be changed - `fullName` [query, string]: Full name @@ -54,6 +56,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/user` + **User by token** `operationId`: `_web_api_user_get` @@ -62,6 +65,7 @@ Get a user by token. Required permissions: `Users.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `siteIds` [query, array]: List of Site IDs to filter by. Example: "225494730938493804,225494730938493915". - `groupIds` [query, array]: List of Group IDs to filter by. Example: "225494730938493804,225494730938493915". @@ -70,6 +74,7 @@ Parameters: Responses: 401 Unauthorized access - please sign in and retry., 200 User retrieved correctly., 400 Invalid user input received. See error details for further i ## `GET /web/api/v2.1/users` + **List users** `operationId`: `_web_api_users_get` @@ -78,6 +83,7 @@ Get a list of users. Required permissions: `Users.view` Parameters: + - `skip` [query, integer]: Skip first number of items (0-1000). To iterate over more than 1000 items, use "cursor". Example: "150". - `limit` [query, integer]: Limit number of returned items (1-1000). Example: "10". - `cursor` [query, string]: Cursor position returned by the last request. Use to iterate over more than 1000 items. Example: "YWdlbnRfaWQ6NTgwMjkzODE=". @@ -89,7 +95,7 @@ Parameters: - `accountIds` [query, array]: List of Account IDs to filter by. Example: "225494730938493804,225494730938493915". - `source` [query, string] (enum: mgmt, sso_saml, active_directory, global): User Source. Example: "mgmt". - `sources` [query, array]: Source in. Example: "mgmt". -- `email` [query, string]: Email. Example: "admin@sentinelone.com". +- `email` [query, string]: Email. Example: "". - `email__contains` [query, array]: Match email partially (substring) - `emailReadOnly` [query, boolean]: True if email cannot be changed - `fullName` [query, string]: Full name @@ -128,6 +134,7 @@ Parameters: Responses: 401 Unauthorized access - please sign in and retry., 200 List of users retrieved successfully., 400 Invalid user input received. See error details for further i ## `POST /web/api/v2.1/users` + **Create User** `operationId`: `_web_api_users_post` @@ -136,11 +143,13 @@ Create a new user. Required permissions: `Users.create` Parameters: -- `body` [body, users.schemas_CreateUserSchema]: + +- `body` [body, users.schemas_CreateUserSchema]: Responses: 403 Not enough permissions to create user., 200 User created successfully., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/2fa/disable` + **Disable 2FA** `operationId`: `_web_api_users_2fa_disable_post` @@ -148,11 +157,13 @@ Disable Two-Factor Authentication for one user. This requires the ID of the user Optional permissions: `Users.edit` Parameters: -- `body` [body, users.schemas_UserIdSchema]: + +- `body` [body, users.schemas_UserIdSchema]: Responses: 403 No permission for the action, 200 2FA successfully disabled, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/2fa/enable` + **Enable 2FA** `operationId`: `_web_api_users_2fa_enable_post` @@ -160,44 +171,52 @@ Enable two-factor authentication for a given user. Optional permissions: `Users.edit` Parameters: -- `body` [body, users.schemas_UserIdSchema]: + +- `body` [body, users.schemas_UserIdSchema]: Responses: 403 No permission for the action, 200 2FA successfully enabled, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/api-token-details` + **API Token Details** `operationId`: `_web_api_users_api-token-details_post` Get details of the API token that matches the filter. Parameters: -- `body` [body, users.schemas_ApiTokenSchema]: + +- `body` [body, users.schemas_ApiTokenSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i ## `POST /web/api/v2.1/users/auth/app` + **Auth App** `operationId`: `_web_api_users_auth_app_post` Authenticate a user with a third-party app, such as DUO or Google Authenticator, for deployments that require Two Factor Authentication. Parameters: -- `body` [body, users.schemas_AuthCodeSchema]: + +- `body` [body, users.schemas_AuthCodeSchema]: Responses: 200 Authenticated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/auth/elevate` + **Auth Elevate** `operationId`: `_web_api_users_auth_elevate_post` Elevate a session with a third-party app, such as DUO or Google Authenticator. Parameters: -- `body` [body, users.schemas_ElevateSessionSchema]: + +- `body` [body, users.schemas_ElevateSessionSchema]: Responses: 200 Elevated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/auth/eula` + **Sign EULA** `operationId`: `_web_api_users_auth_eula_post` @@ -206,6 +225,7 @@ Mark the End User License Agreement (EULA) as signed for user scopes. Responses: 200 Authenticated, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/change-password` + **Change Password** `operationId`: `_web_api_users_change-password_post` @@ -213,11 +233,13 @@ Change the user password. Optional permissions: `Users.edit` Parameters: -- `body` [body, users.schemas_ChangePasswordSchema]: + +- `body` [body, users.schemas_ChangePasswordSchema]: Responses: 404 User not found, 403 Insufficient permissions, 200 Password changed, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/delete-2fa` + **Delete 2FA** `operationId`: `_web_api_users_delete-2fa_post` @@ -226,11 +248,13 @@ Delete 2FA for users. Required permissions: `Users.edit, Users.allow2FAForOtherUsers` Parameters: -- `body` [body, users.schemas_DeleteTfaSchema]: + +- `body` [body, users.schemas_DeleteTfaSchema]: Responses: 404 User not found, 403 Insufficient permissions, 200 2FA delete completed, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/delete-users` + **Bulk Delete Users** `operationId`: `_web_api_users_delete-users_post` @@ -239,22 +263,26 @@ Delete all users that match the filter. Required permissions: `Users.delete` Parameters: -- `body` [body, users.schemas_BulkUsersActionSchema]: + +- `body` [body, users.schemas_BulkUsersActionSchema]: Responses: 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/enable-app` + **Enable 2FA App** `operationId`: `_web_api_users_enable-app_post` Enable support for the 2FA app (such as Duo or Google Authenticator) that your Console users will use to log in. Parameters: -- `body` [body, users.schemas_EnableAppSchema]: + +- `body` [body, users.schemas_EnableAppSchema]: Responses: 200 2FA app enabled, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/enroll-2fa` + **Enroll 2FA** `operationId`: `_web_api_users_enroll-2fa_post` @@ -263,22 +291,26 @@ Enroll users for 2FA setup. Required permissions: `Users.edit, Users.allow2FAForOtherUsers` Parameters: -- `body` [body, users.schemas_UserIdsSchema]: + +- `body` [body, users.schemas_UserIdsSchema]: Responses: 404 User not found, 403 Insufficient permissions, 200 2FA enrollment completed, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/generate-api-token` + **Generate API Token** `operationId`: `_web_api_users_generate-api-token_post` Get the API token for the authenticated user. Parameters: -- `body` [body, users.schemas_GenerateApiTokenSchema]: + +- `body` [body, users.schemas_GenerateApiTokenSchema]: Responses: 409 API token conflict (already generated), 200 API token delivered to user, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/generate-iframe-token` + **Generate iFrame Token** `operationId`: `_web_api_users_generate-iframe-token_post` @@ -287,50 +319,59 @@ Get a new iFrame token with the provided limitations. Required permissions: `Users.create` Parameters: -- `body` [body, users.schemas_CreateIFrameUserSchema]: + +- `body` [body, users.schemas_CreateIFrameUserSchema]: Responses: 403 Not enough permissions to create user., 200 User created successfully., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/login` + **Login** `operationId`: `_web_api_users_login_post` Authenticate a user by username and password and return an authentication token. Rate limit: 1 call per second for each different IP address that communicates with the Console. Parameters: -- `body` [body, users.schemas_LoginInputSchema]: + +- `body` [body, users.schemas_LoginInputSchema]: Responses: 401 Login failed. May be the result of bad credentials, or a wro, 400 Invalid user input received. See error details for further i, 200 User authenticated successfully. ## `POST /web/api/v2.1/users/login-continue` + **Continue with login due to upcoming password expiration or SSO 2FA setup** `operationId`: `_web_api_users_login-continue_post` For SSO 2FA setup tokens, allows users to skip setting up the 2FA and proceed with their login.
Accepts a temporary token from SSO login flow with error code 4010035.

For password expiration tokens, allows users to decide if they want to change their soon to expire password now or later.
Users can also choose not to receive the notification again for this password cycle.
Accepts a temporary token from /users/login with error code 4010093.
For users eligible to be onboarded to global identity allows to decide if they want to skip and finish their login Parameters: -- `body` [body, users.schemas_LoginContinueSchema]: + +- `body` [body, users.schemas_LoginContinueSchema]: Responses: 401 Unauthorized.
In password expiration flow, a temporary t, 200 Login can continue, 400 Invalid user input received. See error details for further i ## `POST /web/api/v2.1/users/login/by-api-token` + **Login by API Token** `operationId`: `_web_api_users_login_by-api-token_post` -Log in to the API with a token. To learn more about temporary and 6-month tokens and how to generate them, see https://support.sentinelone.com/hc/en-us/articles/360004195934. +Log in to the API with a token. To learn more about temporary and 6-month tokens and how to generate them, see . Parameters: -- `body` [body, users.schemas_LoginByApiTokenSchema]: + +- `body` [body, users.schemas_LoginByApiTokenSchema]: Responses: 401 User authentication failed, 200 user logged in, 400 Invalid user input received. See error details for further i ## `GET /web/api/v2.1/users/login/by-token` + **Login by Token** `operationId`: `_web_api_users_login_by-token_get` Log in with user token. Parameters: + - `token` [query, string] **required**: User token. Example: "bfd9070c1afa88516d3cdfd722e62fe433e42bad6bb14da27088140ad785585f8582adaccd56fb69". - `removedSavedScope` [query, string]: Removed saved scope - `redirectTo` [query, string]: Relative url to redirect to @@ -339,6 +380,7 @@ Parameters: Responses: 401 User authentication failed, 200 user logged in, 400 Invalid user input received. See error details for further i ## `POST /web/api/v2.1/users/login/force-reset-password-on-login` + **Reset password on next login** `operationId`: `_web_api_users_login_force-reset-password-on-login_post` @@ -347,11 +389,13 @@ Force users to reset their password on next login. Required permissions: `Users.edit` Parameters: -- `body` [body, users.schemas_BulkUsersActionSchema]: + +- `body` [body, users.schemas_BulkUsersActionSchema]: Responses: 403 Insufficient permissions, 200 Users marked to reset password on next login successfully, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/login/send-reset-password-email` + **Prompt reset password** `operationId`: `_web_api_users_login_send-reset-password-email_post` @@ -360,45 +404,53 @@ Prompt reset password for users. Required permissions: `Users.edit` Parameters: -- `body` [body, users.schemas_BulkUsersActionSchema]: + +- `body` [body, users.schemas_BulkUsersActionSchema]: Responses: 403 Insufficient permissions, 200 Prompt reset password completed, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/login/set-password` + **Set a New Password** `operationId`: `_web_api_users_login_set-password_post` Sets a new password for the user.
Used by forced password reset and password expiration flows.
Accepts temporary tokens from /users/login with error codes 4010091 and 4010092. Parameters: -- `body` [body, users.schemas_SetPasswordSchema]: + +- `body` [body, users.schemas_SetPasswordSchema]: Responses: 401 Unauthorized, 200 Password was set, 400 Invalid user input received. See error details for further i ## `GET /web/api/v2.1/users/login/sso-saml2` + **Redirect to SSO** `operationId`: `_web_api_users_login_sso-saml2_get` If SSO is enabled for a deployment or scope, and a user attempts to log in with name and password, this command redirects the login to SSO. Parameters: -- `email` [query, string]: Email address of the user trying to log in. Example: "me@sentinelone.com". + +- `email` [query, string]: Email address of the user trying to log in. Example: "". - `scopeId` [query, string]: The scope the desired SSO IdP is configured on. email is irrelevant when using scope_id. If both are provided, email is ignored. Example: "225494730938493804". Responses: 401 Not authenticated user., 302 Login redirected., 400 Invalid user input received. See error details for further i ## `POST /web/api/v2.1/users/login/sso-saml2/{scope_id}` + **Auth by SSO** `operationId`: `_web_api_users_login_sso-saml2_{scope_id}_post` Authenticate a Single Sign-On response over SAML v2 protocol. Parameters: + - `scope_id` [path, string] **required**: Scope ID. Example: "225494730938493804". Responses: 404 Site not found., 401 Not authenticated user., 302 SSO authenticated. ## `POST /web/api/v2.1/users/logout` + **Logout** `operationId`: `_web_api_users_logout_post` @@ -407,6 +459,7 @@ Log out the authenticated user. Responses: 401 Unauthorized access - please sign in and retry., 200 User logged out successfully. ## `POST /web/api/v2.1/users/onboarding/send-verification-email` + **Send Verification Email** `operationId`: `_web_api_users_onboarding_send-verification-email_post` @@ -415,45 +468,53 @@ Send verification email to users that match the filter. Warning: Active users wi Required permissions: `Users.edit` Parameters: -- `body` [body, users.schemas_BulkUsersActionSchema]: + +- `body` [body, users.schemas_BulkUsersActionSchema]: Responses: 403 Insufficient permissions, 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/users/onboarding/validate-token` + **Validate Verification Token** `operationId`: `_web_api_users_onboarding_validate-token_get` When a new user verifies their email, the Management gets a token. Use this command to validate the token. Parameters: + - `token` [query, string] **required**: Verification token - `resetPasswordFlow` [query, boolean]: Reset password flow Responses: 404 A user matching the input verification token wasn't found, 401 Verification failed, 400 Invalid user input received. See error details for further i, 200 Token is valid ## `POST /web/api/v2.1/users/onboarding/verify` + **Email Verification** `operationId`: `_web_api_users_onboarding_verify_post` When a new user verifies their email, the Management gets a token. Use this command to verify the token and set a new password. Parameters: -- `body` [body, users.schemas_OnboardingVerificationSchema]: + +- `body` [body, users.schemas_OnboardingVerificationSchema]: Responses: 404 A user matching the input verification token wasn't found, 401 Verification failed, 400 Invalid user input received. See error details for further i, 200 User successfully verified ## `POST /web/api/v2.1/users/request-app` + **Request 2FA App** `operationId`: `_web_api_users_request-app_post` Request 2FA App response. Parameters: -- `body` [body, users.schemas_RequestAppSchema]: + +- `body` [body, users.schemas_RequestAppSchema]: Responses: 400 Invalid user input received. See error details for further i, 200 Success, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/reset-2fa` + **Reset 2FA** `operationId`: `_web_api_users_reset-2fa_post` @@ -462,11 +523,13 @@ Reset 2FA for users. Required permissions: `Users.edit, Users.allow2FAForOtherUsers` Parameters: -- `body` [body, users.schemas_ResetTfaSchema]: + +- `body` [body, users.schemas_ResetTfaSchema]: Responses: 404 User not found, 403 Insufficient permissions, 200 2FA reset completed, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/users/revoke-api-token` + **Revoke API Token** `operationId`: `_web_api_users_revoke-api-token_post` @@ -474,11 +537,13 @@ Revoke an API token. Optional permissions: `Users.revokeOtherUsersApiTokens` Parameters: -- `body` [body, users.schemas_UserIdSchema]: + +- `body` [body, users.schemas_UserIdSchema]: Responses: 404 User not found, 403 Insufficient permissions, 200 Api token revoked, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/users/rs-auth-check` + **Check Remote Shell Permissions** `operationId`: `_web_api_users_rs-auth-check_get` @@ -489,6 +554,7 @@ Required permissions: `Endpoints.remoteShell` Responses: 403 Insufficient permissions, 200 User is allowed to use remote shell feature., 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/users/sso-saml2/re-auth` + **Redirect to SSO for re-authentication** `operationId`: `_web_api_users_sso-saml2_re-auth_get` @@ -497,6 +563,7 @@ Initiates re-authentication with user's identity provider. Responses: 500 Error in SAML handler initialization., 403 User is not allowed to re-authenticate with their IDP, 302 Redirect user to their IDP for re-authentication., 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/users/tenant-admin-auth-check` + **Check Global User** `operationId`: `_web_api_users_tenant-admin-auth-check_get` @@ -505,6 +572,7 @@ See if logged in user is a user with the Global scope of access. Responses: 403 Insufficient permissions, 200 User is Global., 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/users/viewer-auth-check` + **Check Viewer** `operationId`: `_web_api_users_viewer-auth-check_get` @@ -513,6 +581,7 @@ See if the logged in user has only viewer permissions. Responses: 200 User is a viewer., 401 Unauthorized access - please sign in and retry. ## `DELETE /web/api/v2.1/users/{user_id}` + **Delete User** `operationId`: `_web_api_users_{user_id}_delete` @@ -521,11 +590,13 @@ Delete a user by ID. Required permissions: `Users.delete` Parameters: + - `user_id` [path, string] **required**: User ID. Example: "225494730938493804". Responses: 403 Insufficient permissions., 200 User deleted successfully., 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/users/{user_id}` + **Get User** `operationId`: `_web_api_users_{user_id}_get` @@ -534,11 +605,13 @@ Get a user by ID. Required permissions: `Users.view` Parameters: + - `user_id` [path, string] **required**: User ID. Example: "225494730938493804". Responses: 404 Could not retrieve user., 403 Insufficient permissions., 200 User retrieved successfully., 401 Unauthorized access - please sign in and retry. ## `PUT /web/api/v2.1/users/{user_id}` + **Update User** `operationId`: `_web_api_users_{user_id}_put` @@ -546,12 +619,14 @@ Change properties of the user of the given ID. Optional permissions: `Users.edit, Users.allowGenerateApiToken` Parameters: + - `user_id` [path, string] **required**: User ID. Example: "225494730938493804". -- `body` [body, users.schemas_UpdateUserSchema]: +- `body` [body, users.schemas_UpdateUserSchema]: Responses: 409 User or Email already taken., 404 User not found., 403 Forbidden., 200 User updated successfully., 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `GET /web/api/v2.1/users/{user_id}/api-token-details` + **API Token by User ID** `operationId`: `_web_api_users_{user_id}_api-token-details_get` @@ -559,6 +634,7 @@ Get the details of the API token generated for a given user. Optional permissions: `Users.view` Parameters: + - `user_id` [path, string] **required**: User ID. Example: "225494730938493804". Responses: 404 User not found, 403 Insufficient permissions, 200 Success, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/VCS_Integration.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/VCS_Integration.md index ae040fe..066a8f6 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/VCS_Integration.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/VCS_Integration.md @@ -3,271 +3,311 @@ 20 endpoints. ## `GET /web/api/v2.1/cnapp/vcs/filters/count` + **Fetch filter count** `operationId`: `_web_api_cnapp_vcs_filters_count_get` Fetch filter count Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: Responses: 200 Filter count fetched successfully, 400 Bad Request, 500 Internal Server Error ## `GET /web/api/v2.1/cnapp/vcs/integration/{integrationId}/offboarding` + **Delete a VCS Integration** `operationId`: `_web_api_cnapp_vcs_integration_{integrationId}_offboarding_get` This API is used to off-board a VCS integration. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: Responses: 200 VCS offboarding app url created successfully, 400 Bad Request, 500 Internal Server Error ## `PUT /web/api/v2.1/cnapp/vcs/integration/{integrationId}/repos/disable-scan` + **Disable scanning for repositories in a VCS integration** `operationId`: `_web_api_cnapp_vcs_integration_{integrationId}_repos_disable-scan_put` Deactivate scanning for repositories associated with the given VCS integration. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `integrationId` [path, string] **required**: -- `body` [body, EnableDisableRepos] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `integrationId` [path, string] **required**: +- `body` [body, EnableDisableRepos] **required**: Responses: 200 Successfully disabled repositories, 400 Bad Request, 500 Internal Server Error ## `PUT /web/api/v2.1/cnapp/vcs/integration/{integrationId}/repos/edit-tags` + **Edit tags for repositories in a VCS integration** `operationId`: `_web_api_cnapp_vcs_integration_{integrationId}_repos_edit-tags_put` Allows modification of tags associated with one or more repositories under a specified VCS integration. This operation supports adding, removing, or updating tags to help organize and categorize repositories effectively. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `integrationId` [path, string] **required**: -- `body` [body, EditReposTags] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `integrationId` [path, string] **required**: +- `body` [body, EditReposTags] **required**: Responses: 200 Tags updated successfully, 400 Bad Request, 500 Internal Server Error ## `PUT /web/api/v2.1/cnapp/vcs/integration/{integrationId}/repos/enable-scan` + **Enable scanning for repositories in a VCS integration** `operationId`: `_web_api_cnapp_vcs_integration_{integrationId}_repos_enable-scan_put` Activates scanning for repositories associated with the given VCS integration. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `integrationId` [path, string] **required**: -- `body` [body, EnableDisableRepos] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `integrationId` [path, string] **required**: +- `body` [body, EnableDisableRepos] **required**: Responses: 200 Successfully enabled repositories, 400 Bad Request, 500 Internal Server Error ## `POST /web/api/v2.1/cnapp/vcs/integration/{integrationId}/repos/get-tags` + **Fetch repository tags** `operationId`: `_web_api_cnapp_vcs_integration_{integrationId}_repos_get-tags_post` This endpoint retrieves tags associated with repositories under a VCS integration. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `integrationId` [path, string] **required**: -- `body` [body, FetchReposTags] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `integrationId` [path, string] **required**: +- `body` [body, FetchReposTags] **required**: Responses: 200 Repositories tags fetched successfully, 400 Bad Request, 500 Internal Server Error ## `DELETE /web/api/v2.1/cnapp/vcs/integration/{vcsIntegrationId}` + **Delete a VCS integration** `operationId`: `_web_api_cnapp_vcs_integration_{vcsIntegrationId}_delete` This endpoint permanently deletes a VCS integration. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `vcsIntegrationId` [path, string] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `vcsIntegrationId` [path, string] **required**: Responses: 200 VCS integration deleted successfully, 400 Bad Request, 500 Internal Server Error ## `PUT /web/api/v2.1/cnapp/vcs/integration/{vcsIntegrationId}` + **Update a VCS integration** `operationId`: `_web_api_cnapp_vcs_integration_{vcsIntegrationId}_put` This endpoint allows users to update configuration details for a VCS integration. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `vcsIntegrationId` [path, string] **required**: -- `body` [body, UpdateVCSIntegration] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `vcsIntegrationId` [path, string] **required**: +- `body` [body, UpdateVCSIntegration] **required**: Responses: 200 VCS integration updated successfully, 400 Bad Request, 500 Internal Server Error ## `GET /web/api/v2.1/cnapp/vcs/integration/{vcsIntegrationId}/repos` + **List VCS integration repositories** `operationId`: `_web_api_cnapp_vcs_integration_{vcsIntegrationId}_repos_get` Fetches a list of repositories associated with the VCS integration Parameters: -- `scopeType` [query, string]: -- `scopeIds` [query, string]: -- `limit` [query, string]: -- `cursor` [query, string]: -- `skip` [query, string]: -- `vcsIntegrationId` [path, string] **required**: + +- `scopeType` [query, string]: +- `scopeIds` [query, string]: +- `limit` [query, string]: +- `cursor` [query, string]: +- `skip` [query, string]: +- `vcsIntegrationId` [path, string] **required**: Responses: 200 Successfully retrieved the list of repositories, 400 Bad Request, 500 Internal Server Error ## `PUT /web/api/v2.1/cnapp/vcs/integration/{vcsIntegrationId}/repos/resync` + **Resync VCS Integration Repositories** `operationId`: `_web_api_cnapp_vcs_integration_{vcsIntegrationId}_repos_resync_put` Initiates a process to resynchronize repositories associated with the specified VCS integration Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `vcsIntegrationId` [path, string] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `vcsIntegrationId` [path, string] **required**: Responses: 200 Resynchronization initiated successfully, 400 Bad Request, 500 Internal Server Error ## `GET /web/api/v2.1/cnapp/vcs/integrations` + **List VCS integrations** `operationId`: `_web_api_cnapp_vcs_integrations_get` Fetches a list of all configured VCS integrations. Parameters: -- `scopeType` [query, string]: -- `scopeIds` [query, string]: -- `limit` [query, string]: -- `skip` [query, string]: + +- `scopeType` [query, string]: +- `scopeIds` [query, string]: +- `limit` [query, string]: +- `skip` [query, string]: Responses: 200 Successfully retrieved the list of VCS integrations, 400 Bad Request, 500 Internal Server Error ## `PUT /web/api/v2.1/cnapp/vcs/integrations/edit-tags` + **Edit tags for a VCS integration** `operationId`: `_web_api_cnapp_vcs_integrations_edit-tags_put` Allows modification of tags in a VCS integration. This operation supports adding, removing, or updating tags to help organize and categorize integration repositories effectively. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `body` [body, EditIntegrationsTags] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `body` [body, EditIntegrationsTags] **required**: Responses: 200 Tags updated successfully, 400 Bad Request, 500 Internal Server Error ## `POST /web/api/v2.1/cnapp/vcs/onboarding` + **Onboard a new VCS integration** `operationId`: `_web_api_cnapp_vcs_onboarding_post` This endpoint allows users to onboard a new VCS integration, enabling automated scanning for secrets and IaC misconfigurations. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `body` [body, VCSOnboarding] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `body` [body, VCSOnboarding] **required**: Responses: 200 VCS onboarding completed successfully, 400 Bad Request, 500 Internal Server Error ## `GET /web/api/v2.1/cnapp/vcs/scanner-policies` + **List VCS and CICD scanner policies** `operationId`: `_web_api_cnapp_vcs_scanner-policies_get` This endpoint retrieves a list of all configured scanner policies used for VCS and CICD integrations. Parameters: -- `scopeType` [query, string]: -- `scopeIds` [query, string]: -- `limit` [query, string]: -- `skip` [query, string]: + +- `scopeType` [query, string]: +- `scopeIds` [query, string]: +- `limit` [query, string]: +- `skip` [query, string]: Responses: 200 Scanner policies fetched successfully, 400 Bad Request, 500 Internal Server Error ## `POST /web/api/v2.1/cnapp/vcs/scanner-policy` + **Create a VCS and CICD scanner policy** `operationId`: `_web_api_cnapp_vcs_scanner-policy_post` Defines a scanning policy for a VCS integration, configuring parameters for detecting secrets, IaC misconfigurations, and vulnerabilities within repositories. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `body` [body, AddScannerPolicy] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `body` [body, AddScannerPolicy] **required**: Responses: 200 Scanner policy created successfully, 400 Bad Request, 500 Internal Server Error ## `GET /web/api/v2.1/cnapp/vcs/scanner-policy/max-allowed-priority` + **Get max allowed priority** `operationId`: `_web_api_cnapp_vcs_scanner-policy_max-allowed-priority_get` This endpoint returns the maximum allowed value for the priority in scanner policies. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `targetScopeType` [query, string] **required**: -- `targetScopeId` [query, string] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `targetScopeType` [query, string] **required**: +- `targetScopeId` [query, string] **required**: Responses: 200 Max allowed priority fetched successfully, 400 Bad Request, 500 Internal Server Error ## `DELETE /web/api/v2.1/cnapp/vcs/scanner-policy/{policyId}` + **Delete a VCS and CICD scanner policy** `operationId`: `_web_api_cnapp_vcs_scanner-policy_{policyId}_delete` This endpoint deletes a scanner policy. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `policyId` [path, string] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `policyId` [path, string] **required**: Responses: 200 VCS scanner policy deleted successfully, 400 Bad Request, 500 Internal Server Error ## `GET /web/api/v2.1/cnapp/vcs/scanner-policy/{policyId}` + **Get a VCS and CICD scanner policy** `operationId`: `_web_api_cnapp_vcs_scanner-policy_{policyId}_get` Get a VCS and CICD scanner policy Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `policyId` [path, string] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `policyId` [path, string] **required**: Responses: 200 This endpoint fetches detailed information for a scanner pol, 400 Bad Request, 500 Internal Server Error ## `PUT /web/api/v2.1/cnapp/vcs/scanner-policy/{policyId}` + **Update a VCS and CICD scanner policy** `operationId`: `_web_api_cnapp_vcs_scanner-policy_{policyId}_put` This endpoint updates an existing scanner policy. A scanner policy defines how secrets, IaC misconfigurations, and vulnerabilities should be detected and handled within a VCS integration/CICD. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `policyId` [path, string] **required**: -- `body` [body, UpdateVCSScannerPolicy] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `policyId` [path, string] **required**: +- `body` [body, UpdateVCSScannerPolicy] **required**: Responses: 200 Scanner policy updated successfully, 400 Bad Request, 500 Internal Server Error ## `POST /web/api/v2.1/cnapp/vcs/tunnel/user` + **Register Tunnel User** `operationId`: `_web_api_cnapp_vcs_tunnel_user_post` This API is used to register a new tunnel user. It sets up the necessary tunnel configuration and credentials for secure access. Parameters: -- `scopeType` [query, string] **required**: -- `scopeIds` [query, string] **required**: -- `body` [body, TunnelUser] **required**: + +- `scopeType` [query, string] **required**: +- `scopeIds` [query, string] **required**: +- `body` [body, TunnelUser] **required**: Responses: 200 Tunnel user registered successfully, 400 Bad Request, 500 Internal Server Error diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/alerts.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/alerts.md index dfcc8fb..2d05ace 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/alerts.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/alerts.md @@ -3,6 +3,7 @@ 3 endpoints. ## `GET /web/api/v2.1/cloud-detection/alerts` + **Get alerts** `operationId`: `_web_api_cloud-detection_alerts_get` @@ -11,6 +12,7 @@ Get a list of alerts for a given scope Required permissions: `Custom Alerts.view` Parameters: + - `createdAt__gt` [query, string]: Created at greater than. Example: "2018-02-27T04:49:26.257525Z". - `sourceProcessFileHashSha256__contains` [query, array]: Free-text filter by source sha255. Example: "rule1". - `origAgentName__contains` [query, array]: Free-text filter by agent name. Example: "ilia". @@ -66,6 +68,7 @@ Parameters: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/cloud-detection/alerts/analyst-verdict` + **Update Alert Analyst Verdict** `operationId`: `_web_api_cloud-detection_alerts_analyst-verdict_post` @@ -74,11 +77,13 @@ Change the verdict of an alert Required permissions: `Custom Alerts.updateAnalystVerdict` Parameters: -- `body` [body, v2_1.alerts.schemas_AlertsAnalystVerdictSchema]: + +- `body` [body, v2_1.alerts.schemas_AlertsAnalystVerdictSchema]: Responses: 200 Threats incident successfully updated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. ## `POST /web/api/v2.1/cloud-detection/alerts/incident` + **Update Threat Incident** `operationId`: `_web_api_cloud-detection_alerts_incident_post` @@ -87,6 +92,7 @@ Update the incident details of an alert. Required permissions: `Custom Alerts.updateIncidentStatus` Parameters: -- `body` [body, v2_1.alerts.schemas_AlertsIncidentSchema]: + +- `body` [body, v2_1.alerts.schemas_AlertsIncidentSchema]: Responses: 200 Threats incident successfully updated, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/licenses.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/licenses.md index 08a4b07..d3eca9f 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/licenses.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/licenses.md @@ -3,6 +3,7 @@ 1 endpoints. ## `PUT /web/api/v2.1/licenses/update-sites-modules` + **Update sites add-ons** `operationId`: `_web_api_licenses_update-sites-modules_put` @@ -11,6 +12,7 @@ Change the add-ons of the sites by a given filter Required permissions: `Sites.edit` Parameters: -- `body` [body, licenses.schemas_SiteBulkModulesSchema]: + +- `body` [body, licenses.schemas_SiteBulkModulesSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/marketplace.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/marketplace.md index 944285d..2592239 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/marketplace.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/marketplace.md @@ -3,6 +3,7 @@ 9 endpoints. ## `DELETE /web/api/v2.1/singularity-marketplace/applications` + **Delete Application** `operationId`: `_web_api_singularity-marketplace_applications_delete` @@ -11,11 +12,13 @@ Delete application integration from your Marketplace. Required permissions: `Singularity Marketplace.manage` Parameters: -- `body` [body, DeleteApplicationRequest] **required**: + +- `body` [body, DeleteApplicationRequest] **required**: Responses: 200 OK ## `GET /web/api/v2.1/singularity-marketplace/applications` + **Get Installed Applications** `operationId`: `_web_api_singularity-marketplace_applications_get` @@ -24,6 +27,7 @@ Get the installed Marketplace applications for a scope specified. Required permissions: `Singularity Marketplace.view` Parameters: + - `applicationCatalogId` [query, string]: Filter results by application catalog id. Example: "225494730938493804,225494730938493915". - `id` [query, string]: A list of applications IDs. Example: "225494730938493804,225494730938493915". - `name__contains` [query, string]: Free-text filter by application name (supports multiple values). Example: "Service Pack 1". @@ -41,6 +45,7 @@ Parameters: Responses: 200 OK ## `POST /web/api/v2.1/singularity-marketplace/applications` + **Install Applications** `operationId`: `_web_api_singularity-marketplace_applications_post` @@ -49,11 +54,13 @@ Install application from the Application Catalog. Required permissions: `Singularity Marketplace.manage` Parameters: -- `body` [body, InstallationRequest] **required**: + +- `body` [body, InstallationRequest] **required**: Responses: 200 OK ## `PUT /web/api/v2.1/singularity-marketplace/applications` + **Update Application Configuration** `operationId`: `_web_api_singularity-marketplace_applications_put` @@ -62,11 +69,13 @@ Update installed application configuration. Required permissions: `Singularity Marketplace.manage` Parameters: -- `body` [body, UpdateConfigurationRequest] **required**: + +- `body` [body, UpdateConfigurationRequest] **required**: Responses: 200 OK ## `GET /web/api/v2.1/singularity-marketplace/applications-catalog` + **Get Applications Catalog** `operationId`: `_web_api_singularity-marketplace_applications-catalog_get` @@ -75,6 +84,7 @@ Get the Marketplace Application Catalog. Required permissions: `Singularity Marketplace.view` Parameters: + - `id` [query, string]: Filter results by application catalog id. Example: "225494730938493804,225494730938493915". - `category__contains` [query, string]: Free-text filter by catalog application category (supports multiple values). Example: "Service Pack 1". - `name__contains` [query, string]: Free-text filter by catalog application name (supports multiple values). Example: "Service Pack 1". @@ -89,6 +99,7 @@ Parameters: Responses: 200 OK ## `GET /web/api/v2.1/singularity-marketplace/applications-catalog/{applicationCatalogId}/config` + **Get Configuration Fields** `operationId`: `_web_api_singularity-marketplace_applications-catalog_{applicationCatalogId}_config_get` @@ -97,11 +108,13 @@ Get the Configuration Fields of the Catalog Application. Required permissions: `Singularity Marketplace.view` Parameters: -- `applicationCatalogId` [path, string] **required**: + +- `applicationCatalogId` [path, string] **required**: Responses: 200 OK ## `GET /web/api/v2.1/singularity-marketplace/applications/{applicationId}/config` + **Get Configuration Fields For Installed Application** `operationId`: `_web_api_singularity-marketplace_applications_{applicationId}_config_get` @@ -110,11 +123,13 @@ Get the Catalog Application Configuration Fields. Required permissions: `Singularity Marketplace.view` Parameters: -- `applicationId` [path, string] **required**: + +- `applicationId` [path, string] **required**: Responses: 200 OK ## `POST /web/api/v2.1/singularity-marketplace/applications/{applicationMode}` + **Enable Or Disable Application** `operationId`: `_web_api_singularity-marketplace_applications_{applicationMode}_post` @@ -123,12 +138,14 @@ Use this command to enable or disable application integrations that match the fi Required permissions: `Singularity Marketplace.manage` Parameters: -- `applicationMode` [path, string] **required** (enum: enable, disable): -- `body` [body, SwitchApplicationModeRequest] **required**: + +- `applicationMode` [path, string] **required** (enum: enable, disable): +- `body` [body, SwitchApplicationModeRequest] **required**: Responses: 200 OK ## `GET /web/api/v2.1/singularity-marketplace/applications/{id}/log` + **Get application log** `operationId`: `_web_api_singularity-marketplace_applications_{id}_log_get` @@ -137,6 +154,7 @@ Returns application invocation log. Required permissions: `Singularity Marketplace.view` Parameters: + - `id` [path, string] **required**: Application ID - `only_errors` [query, string]: If true, only logs with error status ('Failure' or 'Retry') will be returned diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/overview.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/overview.md index 2fd89b0..9365248 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/overview.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tags/overview.md @@ -3,6 +3,7 @@ 1 endpoints. ## `POST /web/api/v2.1/xdr/assets/overview` + **Cloud Inventory resource overview** `operationId`: `_web_api_xdr_assets_overview_post` @@ -11,9 +12,10 @@ Get overview of a resource belonging to a category Required permissions: `XDR Inventory.view` Parameters: + - `accountIds` [query, array]: List of Account IDs to filter by - `siteIds` [query, array]: List of Site IDs to filter by - `groupIds` [query, array]: List of Group IDs to filter by -- `body` [body, v2_1.inventory.overview.schemas_InventoryOverviewJsonSchema]: +- `body` [body, v2_1.inventory.overview.schemas_InventoryOverviewJsonSchema]: Responses: 200 Success, 400 Invalid user input received. See error details for further i, 401 Unauthorized access - please sign in and retry. diff --git a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tenant_capabilities.md b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tenant_capabilities.md index f249952..597aeb5 100644 --- a/plugins/s1-secops-skills/skills/mgmt-console-api/references/tenant_capabilities.md +++ b/plugins/s1-secops-skills/skills/mgmt-console-api/references/tenant_capabilities.md @@ -427,6 +427,7 @@ | GET | `/web/api/v2.1/ranger/enablement` | Network Discovery Self Enablement | 404 | HTTP 404: {"errors":[{"code":4040010,"detail":"","title":"Requested resource was not found"}]} | | GET | `/web/api/v2.1/ranger/enablement/defaults` | Network Discovery Self Enablement | 404 | HTTP 404: {"errors":[{"code":4040010,"detail":"","title":"Requested resource was not found"}]} | | GET | `/web/api/v2.1/remote-ops/forensics/collection-profiles` | RemoteOps Forensics | 429 | HTTP 429: + 429 Too Many Requests

429 Too Many Requests

diff --git a/plugins/s1-secops-skills/skills/powerquery/examples/behavioral-baselines.md b/plugins/s1-secops-skills/skills/powerquery/examples/behavioral-baselines.md index e7e8607..bad5e2a 100644 --- a/plugins/s1-secops-skills/skills/powerquery/examples/behavioral-baselines.md +++ b/plugins/s1-secops-skills/skills/powerquery/examples/behavioral-baselines.md @@ -48,7 +48,7 @@ Run this once per baseline day. Each call produces one row per `(action, principal)` pair seen on that day, with `day_count`. Replace the bracketed placeholders. -``` +```text dataSource.name = '' | let action = ? : | let principal = ? : @@ -73,7 +73,7 @@ client-side merge that follows. Same shape, single 24h window ending at the detection moment. -``` +```text dataSource.name = '' | let action = ? : | let principal = ? : @@ -272,7 +272,7 @@ The end-to-end production shape with persisted baselines: 2. **Author the detection rule** as a PowerQuery Alert (`queryLang: "2.0"`, `queryType: "scheduled"`): -``` +```text dataSource.name = '' | let action = ? : | let principal = ? : @@ -294,7 +294,7 @@ dataSource.name = '' table from step 1 must be keyed on `(action, principal, dow)` with `dow` stored in the same `%a` format, not just `(action, principal)`. -3. **Set rule constraints**: for `queryType: "scheduled"`, +1. **Set rule constraints**: for `queryType: "scheduled"`, `treatAsThreat: "UNDEFINED"` and `networkQuarantine: false` are required. Use the alert severity field, not mitigation actions, to surface the verdict. diff --git a/plugins/s1-secops-skills/skills/powerquery/examples/detection-library.md b/plugins/s1-secops-skills/skills/powerquery/examples/detection-library.md index e70098c..6aa5821 100644 --- a/plugins/s1-secops-skills/skills/powerquery/examples/detection-library.md +++ b/plugins/s1-secops-skills/skills/powerquery/examples/detection-library.md @@ -16,7 +16,7 @@ Before deploying, run the body in Event Search over 24 hours and walk the thresh ### Macro-spawned LOLBin (MITRE T1566.001 + T1059) -``` +```text event.type = 'Process Creation' src.process.parent.name in ('winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe') src.process.name in ('powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cscript.exe', 'mshta.exe', 'regsvr32.exe', 'rundll32.exe', 'certutil.exe', 'bitsadmin.exe') @@ -35,7 +35,7 @@ src.process.name in ('powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cs ### HTA execution from Office or mail client (T1218.005) -``` +```text event.type = 'Process Creation' src.process.name = 'mshta.exe' src.process.parent.name in ('winword.exe', 'excel.exe', 'outlook.exe', 'explorer.exe') @@ -57,7 +57,7 @@ src.process.parent.name in ('winword.exe', 'excel.exe', 'outlook.exe', 'explorer ### Encoded PowerShell (T1059.001) -``` +```text event.type = 'Process Creation' src.process.name in ('powershell.exe', 'pwsh.exe') src.process.cmdline matches '(?i)\\s-(e|en|enc|enco|encod|encode|encoded|encodedc|encodedco|encodedcom|encodedcomm|encodedcomma|encodedcomman|encodedcommand)\\b' @@ -75,7 +75,7 @@ src.process.cmdline matches '(?i)\\s-(e|en|enc|enco|encod|encode|encoded|encoded ### Suspicious LOLBin with download-like args (T1105) -``` +```text event.type = 'Process Creation' src.process.name in ('certutil.exe', 'bitsadmin.exe', 'mshta.exe', 'regsvr32.exe', 'rundll32.exe') (src.process.cmdline contains 'http://' or src.process.cmdline contains 'https://' or src.process.cmdline contains 'frombase64string') @@ -93,7 +93,7 @@ src.process.name in ('certutil.exe', 'bitsadmin.exe', 'mshta.exe', 'regsvr32.exe ### Rundll32 launching `javascript:` (T1055 / T1218.011) -``` +```text event.type = 'Process Creation' src.process.name = 'rundll32.exe' src.process.cmdline matches '(?i)javascript:' @@ -114,7 +114,7 @@ src.process.cmdline matches '(?i)javascript:' ### Run-key / RunOnce registry write (T1547.001) -``` +```text event.type = 'Registry Value Modified' registry.keyPath matches '(?i)(\\\\Run\\\\|\\\\RunOnce\\\\|\\\\RunServices\\\\)' | group @@ -132,7 +132,7 @@ registry.keyPath matches '(?i)(\\\\Run\\\\|\\\\RunOnce\\\\|\\\\RunServices\\\\)' ### Scheduled task created from script host (T1053.005) -``` +```text event.type = 'Process Creation' src.process.cmdline matches '(?i)(schtasks\\s+/create|new-scheduledtask|register-scheduledtask)' !(src.process.parent.name in ('msiexec.exe', 'svchost.exe', 'services.exe')) @@ -150,7 +150,7 @@ src.process.cmdline matches '(?i)(schtasks\\s+/create|new-scheduledtask|register ### WMI event subscription persistence (T1546.003) -``` +```text event.type = 'Process Creation' src.process.cmdline matches '(?i)(__EventFilter|CommandLineEventConsumer|FilterToConsumerBinding)' | group @@ -170,7 +170,7 @@ src.process.cmdline matches '(?i)(__EventFilter|CommandLineEventConsumer|FilterT ### UAC bypass via fodhelper / computerdefaults (T1548.002) -``` +```text event.type = 'Registry Value Modified' registry.keyPath matches '(?i)\\\\Classes\\\\ms-settings\\\\Shell\\\\Open\\\\command' | group @@ -188,7 +188,7 @@ registry.keyPath matches '(?i)\\\\Classes\\\\ms-settings\\\\Shell\\\\Open\\\\com ### Printspoofer / tokenvator / juicypotato (T1134) -``` +```text event.type = 'Process Creation' src.process.cmdline matches '(?i)(printspoofer|juicypotato|tokenvator|getsystem)' | group @@ -209,7 +209,7 @@ src.process.cmdline matches '(?i)(printspoofer|juicypotato|tokenvator|getsystem) ### EventLog clearing (T1070.001) -``` +```text event.type = 'Process Creation' ( src.process.cmdline contains 'wevtutil cl' @@ -230,7 +230,7 @@ event.type = 'Process Creation' ### Defender tampering (T1562.001) -``` +```text event.type = 'Process Creation' src.process.cmdline matches '(?i)(Set-MpPreference\\s+-Disable|Add-MpPreference\\s+-ExclusionPath|sc\\s+(config|stop)\\s+WinDefend)' | group @@ -247,7 +247,7 @@ src.process.cmdline matches '(?i)(Set-MpPreference\\s+-Disable|Add-MpPreference\ ### Shadow copy deletion (pre-ransomware, T1490) -``` +```text event.type = 'Process Creation' ( src.process.cmdline matches '(?i)vssadmin\\s+delete\\s+shadows' @@ -272,7 +272,7 @@ event.type = 'Process Creation' ### Mimikatz / secretsdump / procdump lsass (T1003.001) -``` +```text event.type = 'Process Creation' ( (src.process.name in ('procdump.exe', 'procdump64.exe') and src.process.cmdline contains 'lsass') @@ -292,7 +292,7 @@ event.type = 'Process Creation' ### Failed logon spike (T1110) -``` +```text event.category = 'logins' event.login.loginIsSuccessful = false | group @@ -310,7 +310,7 @@ event.login.loginIsSuccessful = false ### S1 built-in CredentialDumping indicator -``` +```text indicator.name = 'CredentialDumping' | group count = count(), @@ -330,7 +330,7 @@ indicator.name = 'CredentialDumping' ### Burst of recon commands in one storyline (T1082 / T1087 / T1016) -``` +```text event.type = 'Process Creation' src.process.name in ('whoami.exe', 'net.exe', 'net1.exe', 'nltest.exe', 'systeminfo.exe', 'hostname.exe', 'ipconfig.exe', 'tasklist.exe', 'quser.exe', 'arp.exe', 'route.exe') | group @@ -354,7 +354,7 @@ A single storyline running whoami + net user + nltest + systeminfo in quick succ ### Remote service creation via SMB (T1021.002) -``` +```text event.type = 'Process Creation' src.process.parent.name = 'services.exe' src.process.name in ('cmd.exe', 'powershell.exe', 'pwsh.exe') @@ -374,7 +374,7 @@ The four backslashes in the `contains` match a literal `\\` (UNC prefix); see `r ### PSEXESVC.exe execution (T1021.002) -``` +```text event.type = 'Process Creation' src.process.parent.name in ('PSEXESVC.exe', 'paexec.exe', 'csexec.exe') | group @@ -397,7 +397,7 @@ src.process.parent.name in ('PSEXESVC.exe', 'paexec.exe', 'csexec.exe') Detects hosts with outbound connections to the same external destination in a high fraction of the rule's lookback window: persistent traffic to a narrow destination set, a common C2 shape. This tests persistence, not interval regularity. -``` +```text event.type = 'IP Connect' event.network.direction = 'OUTGOING' | filter !net_rfc1918(dst.ip.address) and !net_private(dst.ip.address) @@ -416,7 +416,7 @@ Normalizing by `queryspan` keeps the threshold meaningful whatever lookback the ### DNS to low-reputation TLD -``` +```text event.category = 'network' event.type = 'DNS Resolved' dns.request matches '(?i)\\.(top|xyz|tk|ml|cf|gq|ga)$' @@ -438,7 +438,7 @@ dns.request matches '(?i)\\.(top|xyz|tk|ml|cf|gq|ga)$' ### Mass file modification (ransomware shape, T1486) -``` +```text event.type in ('File Modification', 'File Rename') | group files = estimate_distinct(tgt.file.path), @@ -457,7 +457,7 @@ Backups and installers can also trip this, pair with an allowlist `lookup` on `s ### Archive creation in unusual location (T1560) -``` +```text event.type in ('File Creation', 'File Modification') tgt.file.path matches '(?i)\\.(7z|rar|zip|tar\\.gz|tgz)$' tgt.file.path matches '(?i)\\\\(Temp|AppData\\\\Local\\\\Temp|ProgramData|Users\\\\Public)\\\\' @@ -483,7 +483,7 @@ tgt.file.size > 10000000 Fires when the same storyline that tripped the `CredentialDumping` indicator also had outbound traffic to a non-internal IP. -``` +```text | inner join creds = ( indicator.name = 'CredentialDumping' @@ -515,7 +515,7 @@ Fires when the same storyline that tripped the `CredentialDumping` indicator als Matches a host doing recon AND then seeing a remote interactive logon, "someone ran whoami then logged into another box." -``` +```text | inner join recon = ( event.type = 'Process Creation' @@ -545,7 +545,7 @@ Matches a host doing recon AND then seeing a remote interactive logon, "someone When a rule is otherwise correct but fires on 1-2 known-good patterns, use a `lookup` against a config datatable to suppress. -``` +```text | group count = count(), diff --git a/plugins/s1-secops-skills/skills/powerquery/examples/investigations.md b/plugins/s1-secops-skills/skills/powerquery/examples/investigations.md index 790c9b6..95f27c2 100644 --- a/plugins/s1-secops-skills/skills/powerquery/examples/investigations.md +++ b/plugins/s1-secops-skills/skills/powerquery/examples/investigations.md @@ -17,7 +17,7 @@ All queries assume the EDR or XDR data view unless noted. They're written to be **Use it when** you need to confirm a tenant has telemetry, or want a fast roll-up of active hosts. -``` +```text | group last_seen = newest(timestamp), events = count() by endpoint.name, agent.uuid | sort -last_seen | limit 50 @@ -30,7 +30,7 @@ All queries assume the EDR or XDR data view unless noted. They're written to be **Use it when** triaging a noisy host or hunting for "the one machine that's different." -``` +```text | group ct = count() by endpoint.name | sort -ct | let share_pct = percent_of_total(ct), running = running_percent(ct) @@ -47,7 +47,7 @@ All queries assume the EDR or XDR data view unless noted. They're written to be **Use it when** hunting for download cradles, C2 beaconing from PS, or general "PowerShell that talked to the internet." -``` +```text event.type = 'Process Creation' src.process.name in ('powershell.exe', 'pwsh.exe') !(src.process.parent.name in ('explorer.exe', 'svchost.exe', 'services.exe')) @@ -70,7 +70,7 @@ src.process.name in ('powershell.exe', 'pwsh.exe') **Use it when** triaging phishing, BEC follow-on, or first-stage payload delivery. -``` +```text event.type = 'Process Creation' src.process.parent.name in ('winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe') src.process.name in ('powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cscript.exe', 'mshta.exe', 'regsvr32.exe', 'rundll32.exe', 'certutil.exe', 'bitsadmin.exe') @@ -92,7 +92,7 @@ src.process.name in ('powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cs **Use it when** broad LOLBin sweep, useful as a daily review. -``` +```text event.type = 'Process Creation' src.process.name in ( 'certutil.exe', 'bitsadmin.exe', 'mshta.exe', 'regsvr32.exe', @@ -113,7 +113,7 @@ src.process.cmdline contains ('http://', 'https://', '\\\\', '-encodedcommand', `contains_any` does not exist in PowerQuery and returns "Unknown function" (live-verified 2026-07-29 via LRQ v2). Multi-value `contains ('a', 'b', ...)` as used above is the correct form. An explicit `or` chain is equivalent if you prefer it spelled out: -``` +```text ... and ( src.process.cmdline contains 'http://' or src.process.cmdline contains 'https://' or @@ -127,7 +127,7 @@ src.process.cmdline contains ('http://', 'https://', '\\\\', '-encodedcommand', **Use it when** specifically hunting `-enc` / `-EncodedCommand`. -``` +```text event.type = 'Process Creation' src.process.name in ('powershell.exe', 'pwsh.exe') src.process.cmdline matches '(?i)\\s-(e|en|enc|enco|encod|encode|encoded|encodedc|encodedco|encodedcom|encodedcomm|encodedcomma|encodedcomman|encodedcommand)\\b' @@ -150,7 +150,7 @@ src.process.cmdline matches '(?i)\\s-(e|en|enc|enco|encod|encode|encoded|encoded **Use it when** suspecting psexec / smbexec / remote Service Manager activity. -``` +```text event.type = 'Process Creation' src.process.parent.name = 'services.exe' src.process.name in ('cmd.exe', 'powershell.exe', 'pwsh.exe') @@ -170,7 +170,7 @@ The four backslashes match a literal `\\` in the command line (UNC prefix); see **Use it when** investigating credential theft / lateral spread. -``` +```text event.category = 'logins' event.login.loginIsSuccessful = true event.login.type in ('NETWORK', 'NETWORK_CLEAR_TEXT', 'REMOTE_INTERACTIVE') @@ -190,7 +190,7 @@ event.login.type in ('NETWORK', 'NETWORK_CLEAR_TEXT', 'REMOTE_INTERACTIVE') **Use it when** triaging an authentication-failure alert. -``` +```text event.category = 'logins' event.login.loginIsSuccessful = false | group @@ -213,7 +213,7 @@ If `src.endpoint.ip.address` is null on a hit, fall back to the destination endp **Use it when** investigating a `CredentialDumping` indicator or a tooling alert. -``` +```text indicator.name in ('CredentialDumping', 'LSASSAccess') | group first_seen = oldest(timestamp), @@ -232,7 +232,7 @@ indicator.name in ('CredentialDumping', 'LSASSAccess') **Use it when** sweeping for `mimikatz`, `secretsdump`, `procdump lsass`. -``` +```text event.type = 'Process Creation' ( src.process.name in ('procdump.exe', 'procdump64.exe') and src.process.cmdline contains 'lsass' @@ -256,7 +256,7 @@ event.type = 'Process Creation' **Use it when** investigating "logs went quiet" or a suspected post-exploitation cleanup. -``` +```text event.type = 'Process Creation' ( src.process.cmdline contains 'wevtutil cl' @@ -277,7 +277,7 @@ event.type = 'Process Creation' **Use it when** persistence hunt. -``` +```text event.type = 'Process Creation' src.process.name in ('schtasks.exe', 'powershell.exe', 'pwsh.exe') src.process.cmdline matches '(?i)(schtasks\\s+/create|new-scheduledtask|register-scheduledtask)' @@ -294,7 +294,7 @@ src.process.cmdline matches '(?i)(schtasks\\s+/create|new-scheduledtask|register **Use it when** persistence hunt with registry data. -``` +```text event.type = 'Registry Value Modified' registry.keyPath matches '(?i)(\\\\Run\\\\|\\\\RunOnce\\\\|\\\\RunServices\\\\)' | group @@ -317,7 +317,7 @@ registry.keyPath matches '(?i)(\\\\Run\\\\|\\\\RunOnce\\\\|\\\\RunServices\\\\)' **Use it when** sweeping for low-reputation DGA-style domains. -``` +```text event.category = 'network' event.type = 'DNS Resolved' dns.request matches '(?i)\\.(top|xyz|tk|ml|cf|gq|ga|cn)$' @@ -334,7 +334,7 @@ dns.request matches '(?i)\\.(top|xyz|tk|ml|cf|gq|ga|cn)$' **Use it when** scoping data exfiltration / C2 beaconing for a single host. -``` +```text event.type = 'IP Connect' event.network.direction = 'OUTGOING' endpoint.name = 'EC2AMAZ-4158GRS' @@ -357,7 +357,7 @@ endpoint.name = 'EC2AMAZ-4158GRS' **Use it when** suspecting a C2 client. Buckets connections per 10-minute window per host+destination and computes the fraction of the query span in which the pair was active. This measures persistence (traffic in most windows), not interval regularity. -``` +```text event.type = 'IP Connect' event.network.direction = 'OUTGOING' | filter !net_rfc1918(dst.ip.address) @@ -378,7 +378,7 @@ event.network.direction = 'OUTGOING' **Use it when** investigating dropper / payload staging. -``` +```text event.type in ('File Creation', 'File Modification') tgt.file.path matches '(?i)\\\\(Temp|AppData\\\\Local\\\\Temp|ProgramData)\\\\.+\\.(exe|dll|ps1|bat|vbs|js|hta)$' src.process.name in ('powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cscript.exe', 'mshta.exe', 'rundll32.exe') @@ -397,7 +397,7 @@ src.process.name in ('powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cs **Use it when** hunting unfolding ransomware activity. Looks for one process touching many files quickly. -``` +```text event.type in ('File Modification', 'File Rename') | group file_count = estimate_distinct(tgt.file.path), @@ -420,7 +420,7 @@ event.type in ('File Modification', 'File Rename') **Use it when** wanting a high-level summary of what S1's behavioural engine has flagged. -``` +```text indicator.category = * | group count = count(), @@ -436,7 +436,7 @@ indicator.category = * **Use it when** searching for activity tagged with a specific technique ID (the description field contains the mapping). -``` +```text indicator.description contains 'T1055' | group count = count(), @@ -458,7 +458,7 @@ indicator.description contains 'T1055' **Use it when** baselining "what runs on this host" or "what's unique to this host." -``` +```text event.type = 'Process Creation' endpoint.name = 'LUCKY_NUCK' | group ct = count() by src.process.name @@ -470,7 +470,7 @@ endpoint.name = 'LUCKY_NUCK' **Use it when** sweeping for non-vendor-signed code. -``` +```text event.type = 'Process Creation' src.process.signedStatus in ('unsigned', 'unknown') | group @@ -487,7 +487,7 @@ src.process.signedStatus in ('unsigned', 'unknown') **Use it when** scoping who's logged in where. -``` +```text event.category = 'logins' event.login.loginIsSuccessful = true | group @@ -505,7 +505,7 @@ event.login.loginIsSuccessful = true ### Process tree of a known bad storyline -``` +```text src.process.storyline.id = '' or tgt.process.storyline.id = '' | sort timestamp | columns timestamp, event.type, src.process.name, src.process.cmdline, tgt.process.name, tgt.file.path @@ -516,7 +516,7 @@ If pivoting from any event in a storyline, this gives you the chronological feed ### Find all events for a hash -``` +```text #hash = '' | sort timestamp | columns timestamp, endpoint.name, event.type, src.process.name, src.process.cmdline, tgt.file.path @@ -527,7 +527,7 @@ If pivoting from any event in a storyline, this gives you the chronological feed **Use it when** asking "did the host with the credential-dumping alert also reach out to the internet?" -``` +```text | inner join creds = ( indicator.name = 'CredentialDumping' @@ -555,7 +555,7 @@ If pivoting from any event in a storyline, this gives you the chronological feed ### Process creation rate per minute -``` +```text event.type = 'Process Creation' | group rate = count() / queryspan('minutes') by endpoint.name | sort -rate @@ -564,7 +564,7 @@ event.type = 'Process Creation' ### Hourly buckets of an indicator -``` +```text indicator.category = 'Persistence' | group ct = count() by bucket = timebucket('1h') | sort bucket diff --git a/plugins/s1-secops-skills/skills/powerquery/examples/o365-email-hunting.md b/plugins/s1-secops-skills/skills/powerquery/examples/o365-email-hunting.md index 49686a1..9db79f9 100644 --- a/plugins/s1-secops-skills/skills/powerquery/examples/o365-email-hunting.md +++ b/plugins/s1-secops-skills/skills/powerquery/examples/o365-email-hunting.md @@ -6,7 +6,7 @@ Workflow patterns for the most common Microsoft 365 audit hunts: finding senders Before any M365 hunt, confirm the source string, the actor / operation field names, and the populated values on this tenant. -``` +```text // Confirm the source name. | group ct=count() by dataSource.name | sort -ct | limit 50 @@ -26,7 +26,7 @@ The `` placeholders below should be replaced with the field n This is the single most common M365 hunt, and the answer is rarely "yes/no" by itself; it's usually a count by operation type plus a rough cadence. The trap to avoid: assuming the user is a sender. If the address only appears as a recipient or in inbound mail content, the hunt should report a coverage gap rather than "no activity". -``` +```text // Step A: does the address appear anywhere on this source? dataSource.name='' * contains 'user@example.com' | group ct=count() by @@ -51,7 +51,7 @@ Reading the result: Used to spot bursts, off-hours activity, or sudden drops. -``` +```text dataSource.name='' in () ( contains:anycase 'user@example.com' @@ -68,7 +68,7 @@ The "did user X send mail to anyone outside the org" question. Recipients live i PQ side: -``` +```text dataSource.name='' in () ( contains:anycase 'user@example.com' @@ -137,7 +137,7 @@ for domain, count in domain_counts.most_common(20): When DLP fires on a send, the `message` blob contains policy and rule details. Useful for triaging "which DLP rules is user X tripping repeatedly". -``` +```text dataSource.name='' = 13 // discover the field name carrying RecordType first ( contains:anycase 'user@example.com' @@ -162,7 +162,7 @@ Run recipe 1 step A and step B together; the comparison is the answer. When the search predicate is an identity string (email, user id, IP), partition the result set on `dataSource.name` presence to separate real-world events from SDL platform-internal audit records of prior analyst searches. The audit trail is itself a finding, but it must be reported separately. -``` +```text * contains 'user@example.com' | let category = (dataSource.name = *) ? 'subject_activity' : 'investigation_noise' | group ct=count(), first_seen=min(timestamp), last_seen=max(timestamp) by category @@ -170,7 +170,7 @@ When the search predicate is an identity string (email, user id, IP), partition To inspect what the investigation-noise records carry on this tenant: -``` +```text * contains 'user@example.com' | filter !(dataSource.name = *) | limit 5 | columns * diff --git a/plugins/s1-secops-skills/skills/powerquery/references/automatic-lookups.md b/plugins/s1-secops-skills/skills/powerquery/references/automatic-lookups.md index 422e442..a71dc0d 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/automatic-lookups.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/automatic-lookups.md @@ -55,7 +55,7 @@ These were verified live while building the Windows Event Logs SID enrichment (u 1. **Output value field names must be globally unique across ALL `lookupSpecs`.** Two specs writing the same output field (even when keyed on different event fields) returns: - ``` + ```text HTTP 400: Syntax error at position -1: Output value fields are not unique ``` @@ -143,7 +143,7 @@ After deploy, any matching event gains the searchable fields `sid_username`, `si **1. Automatic lookup, no command.** The injected fields appear for free. Keep a presence check on the keyed field or most rows are null: -``` +```text dataSource.name='Windows Event Logs' winEventLog.data.event.eventData.subjectUserSid=* | columns sid=winEventLog.data.event.eventData.subjectUserSid, native_user=winEventLog.data.event.eventData.subjectUserName, @@ -153,7 +153,7 @@ dataSource.name='Windows Event Logs' winEventLog.data.event.eventData.subjectUse **2. Explicit `lookup` against the CSV.** Table name keeps the `.csv`; the `by` direction is `lookupTableColumn = eventField`: -``` +```text dataSource.name='Windows Event Logs' winEventLog.data.event.eventData.subjectUserSid=* | lookup username, domain, account_type from sid_username.csv by sid = winEventLog.data.event.eventData.subjectUserSid | columns sid=winEventLog.data.event.eventData.subjectUserSid, username, domain, account_type @@ -162,7 +162,7 @@ dataSource.name='Windows Event Logs' winEventLog.data.event.eventData.subjectUse **3. `lookup` after `group` (best practice, resolves once per SID):** -``` +```text dataSource.name='Windows Event Logs' winEventLog.data.event.eventData.subjectUserSid=* | group cnt=count() by sid=winEventLog.data.event.eventData.subjectUserSid | lookup username, domain, account_type from sid_username.csv by sid = sid @@ -181,7 +181,7 @@ dataSource.name='Windows Event Logs' winEventLog.data.event.eventData.subjectUse ## Deploying via the SDL API -``` +```text 1. sdl_get_file '/automaticLookups' -> capture current `version` 2. sdl_put_file '/datatables/.csv' -> new lookup table (omit expectedVersion when creating) 3. sdl_put_file '/automaticLookups' -> pass expectedVersion = version from step 1 diff --git a/plugins/s1-secops-skills/skills/powerquery/references/commands-reference.md b/plugins/s1-secops-skills/skills/powerquery/references/commands-reference.md index 024625d..0606a51 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/commands-reference.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/commands-reference.md @@ -23,7 +23,7 @@ In-depth documentation for every PowerQuery command. Read before writing anythin ## 1. `filter` -``` +```text | filter expr ``` @@ -31,7 +31,7 @@ Keeps rows where `expr` evaluates truthy. The initial filter (everything before Only the initial filter supports `* contains` and `* matches`. After the first pipe, search operators must name a field. -``` +```text event.login.loginIsSuccessful = false | group ct = count() by event.login.userName | filter ct > 5 @@ -42,7 +42,7 @@ event.login.loginIsSuccessful = false ## 2. `columns` -``` +```text | columns f1, f2 // select and order | columns display = f1, "Pretty Name" = f2 // rename / quote for spaces | columns ratio = success / (success + failure) // compute @@ -52,7 +52,7 @@ event.login.loginIsSuccessful = false Nice for ternary-based bucketing at the end of a pipeline: -``` +```text | columns size_bucket = (tgt.file.size < 1_000_000) ? 'Small' : (tgt.file.size < 5_000_000) ? 'Medium' : 'Large' @@ -60,7 +60,7 @@ Nice for ternary-based bucketing at the end of a pipeline: For unit conversion on timestamps (e.g., promoting a seconds column into PQ's nanosecond-timestamp convention so it renders as a date): -``` +```text | columns create.timestamp = createSecs * 1_000_000_000 ``` @@ -70,7 +70,7 @@ Any numeric field named `timestamp` or ending in `.timestamp` renders as an ISO ## 3. `let` -``` +```text | let f1 = expr, "f 2" = expr2, … ``` @@ -78,7 +78,7 @@ Adds computed fields. Unlike `columns`, `let` preserves the existing record; use Cannot overwrite a field that was produced by a preceding command; *can* overwrite a field that exists in the underlying event data. -``` +```text src.process.name contains 'powershell' dst.ip.address = * | let is_rfc1918 = net_rfc1918(dst.ip.address) | filter is_rfc1918 = false @@ -89,7 +89,7 @@ src.process.name contains 'powershell' dst.ip.address = * ## 4. `group` -``` +```text | group agg(x), name2 = agg2(y) | group agg(x) by f1, "Label" = f2 ``` @@ -117,14 +117,14 @@ Like `columns`, `group` creates a new record set, fields not named in the `group A `where` clause applies to the LAST argument of a multi-arg function, or the sole argument otherwise: -``` +```text | group mean(tgt.file.size where tgt.file.path contains 'temp') | group pct(90, tgt.file.size where tgt.file.path contains 'temp') ``` ### Grouping by time -``` +```text | group count() by timestamp = timebucket('1h') // hourly buckets | group count() by timestamp = timebucket(timestamp, '5m') // explicit form | group count() by timestamp = timebucket('1d'), endpoint.name @@ -136,7 +136,7 @@ A `where` clause applies to the LAST argument of a multi-arg function, or the so ## 5. `sort` -``` +```text | sort expr // ascending | sort +expr // explicit ascending | sort -expr // descending @@ -149,7 +149,7 @@ If there's no `sort` after the last `group`, results are implicitly sorted ascen ## 6. `limit` / `nolimit` -``` +```text | limit // default 10 rows | limit 250 | nolimit // raise cap to 3 GB; one concurrent per tenant; never in Dashboards or Alerts @@ -163,7 +163,7 @@ Without `limit` or `group`, outputs are capped at 1,000 rows. The `Show All` but ## 7. `parse` -``` +```text | parse "format with $field$ markers" from sourceField | parse "$digits=digits$ seconds" from latencyStr | parse ".*\\\\$filename{regex=[^\\\\]+}$$$" from tgt.file.path @@ -179,7 +179,7 @@ Performance note: most `parse` use cases are better solved by configuring a pars Work with CSV / JSON lookup tables stored under Config Files (`/datatables/`). -``` +```text | lookup osVersion from machineinfo by endpoint.name // join on equal names | lookup osVersion, "Region" = region from machineinfo by endpoint.name = endpoint.name | dataset 'config://datatables/machineinfo' // use the lookup table as the pipeline source @@ -204,6 +204,8 @@ Best practices: defer the `lookup` until after a `group`, so the lookup is perfo **Confirmed on-tenant (usea1-purple, 2026-06-01):** - `from
` takes the **literal filename**. If the data table file is `sid_username.csv`, write `from sid_username.csv` (keep the extension). A bare name without the extension can miss the file. Both `/datatables/foo` and `/datatables/foo.csv` can coexist, so the name must be exact. +- **`from
` resolves under `/datatables/` only, never `/lookups/`.** A CSV written to `/lookups/.csv` is a real config file that reads back fine over the API, but `| lookup ... from .csv` returns HTTP 400 `Lookup table ".csv" does not exist`. Put lookup tables in `/datatables/` (live-confirmed 2026-08-07). +- **Hyphens in a table name break the parser: use underscores.** `from my-table.csv` returns HTTP 400 `Identifier "my-table.csv" is ambiguous. To subtract, add spaces ... Otherwise, add backslashes`, because the parser reads the hyphens as subtraction. Escaping works (`from my\-table\.csv`) but is easy to get wrong; name the table `my_table.csv` instead. Note the asymmetry with `| dataset 'config://datatables/my-table.csv'`, which takes the name **quoted** and so accepts hyphens without escaping (live-confirmed 2026-08-07). - `by` direction is `lookupColumn = eventField`. Left of the `=` is the lookup-table key column, right is the event field or expression. Example: `by sid = winEventLog.data.event.eventData.subjectUserSid`. - `| dataset 'config://datatables/'` reads a saved lookup table as the pipeline source. The leading `|` is required; without it the text is parsed as an initial filter and returns 0 rows. @@ -213,7 +215,7 @@ Best practices: defer the `lookup` until after a `group`, so the lookup is perfo ## 9. `join` -``` +```text | [inner|left|outer|sql inner|sql left|sql outer] join [a =] (query1), [b =] (query2), … @@ -247,7 +249,7 @@ Performance: start with the most selective (smallest-cardinality) subquery. PQ e ## 10. `union` -``` +```text | union (query1), (query2), … // comma-separated branches in ONE union ``` @@ -257,7 +259,7 @@ A `union` takes at most **10 subqueries**; more than 10 returns HTTP 400. Write Use to merge heterogeneous sources (e.g., `api_server` logs with fields `operation`/`elapsed_time` and `frontend` logs with `url`/`http_status`). Rename columns in each sub-query's `columns` to unify them: -``` +```text | union (logfile = 'api_server' | columns operation, status = status_code), (logfile = 'frontend' | columns url, status = http_status) @@ -270,7 +272,7 @@ For EDR/XDR data with a single schema, `filter (a OR b)` is usually simpler than ## 11. `transpose` -``` +```text | transpose columnToPivot | transpose columnToPivot on keyCol1, keyCol2 | transpose columnToPivot on keys with_totals @@ -280,6 +282,7 @@ For EDR/XDR data with a single schema, `filter (a OR b)` is usually simpler than Pivots a column into many columns, each distinct value becomes a column. Useful for "one column per category" reports and for plotting one series per entity on a line chart. Rules: + - Must be the **last** command in the query. - Cannot appear in a subquery. - Max 100 new columns (most-frequent 100 win). @@ -292,7 +295,7 @@ Typical flow is `group … by , ` then `transpose on < ## 12. `compare` -``` +```text | compare [name =] timeshift('[-|+]') | compare previous = timeshift('-1w') | compare next_period = timeshift(+queryspan()) @@ -301,6 +304,7 @@ Typical flow is `group … by , ` then `transpose on < Runs the same query over a shifted time range and attaches those numeric columns (with the prefix `()`) alongside. Rules: + - Must be the **last** command. - Only one `timeshift` per query. - Shifted query has the same time-range length as the original (4-hour query + `timeshift('-1d')` → the 4 hours ending 20 hours ago). @@ -312,20 +316,21 @@ Pair with `sort` placed *before* `compare` if you want ordering on the primary r ## 13. `top` -``` +```text | top K [alias =] scoring(expr) by f1, f2 ``` Probabilistic top-K. Scoring functions allowed: `count()` (estimated), `sum(x)` (estimated), `min(x)` (exact), `max(x)` (exact). Adds a synthetic `rank` column; estimated results append "(estimated)" to the column name. Use when: + - The time range is very long (hours of `group` → minutes of `top`). - `group` is hitting intermediate-row memory limits. - You need a fast dashboard panel and can tolerate ~few-percent error on counts. For exact values on top entities without paying for full aggregation: -``` +```text | sql join (| top 4 s_est = sum(x) by endpoint), (| group s = sum(x) by endpoint) @@ -341,7 +346,7 @@ Requires S-25.3.6+ and the Network Discovery add-on. Supported in Singularity Op ## 14. Subqueries: `field in (…)` -``` +```text field in (filter_expr | commands_that_yield_field) | outer_commands ``` @@ -349,6 +354,7 @@ field in (filter_expr | commands_that_yield_field) Runs the inner query first, collects one column of values, and filters the outer query to rows where `field` is in that set. Rules (enforce them; these are where subqueries go wrong): + - The inner query **must** produce a column named the same as `field`. Use `columns field` or `group 1 by field` or `top N count() by field`. - The `in` subquery must appear **before** any `group`, `sort`, or `limit` in the outer query. After aggregation, the left side's value is computed, PQ can't push the filter in. Use `join` for post-aggregation correlation. - The inner query and the outer query are independent filters. If you also want the outer rows to have a condition (e.g., severity 5), state it outside the subquery too, `threat_level = 5 user in (threat_level = 5 | top 3 count() by user)`. @@ -356,7 +362,7 @@ Rules (enforce them; these are where subqueries go wrong): Good patterns: -``` +```text // Users who logged in AND ran processes user in (action='login' | group 1 by user) AND user in (action='process_start' | group 1 by user) diff --git a/plugins/s1-secops-skills/skills/powerquery/references/datasource-command.md b/plugins/s1-secops-skills/skills/powerquery/references/datasource-command.md index 453ad93..2009725 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/datasource-command.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/datasource-command.md @@ -8,7 +8,7 @@ lookup table). Source: SentinelOne Community article 000012487, plus behaviours validated on `usea1-purple` (2026-06-13). -``` +```text | datasource | datasource from ``` @@ -50,7 +50,7 @@ and `` (raw rows for one report; list names via the `reports` datas Columns vary per source and per dataset. Run the command with a small `limit` to see the available column set, then filter on those columns: -``` +```text | datasource misconfigurations | limit 5 | datasource misconfigurations | filter environment = 'AWS' ``` @@ -95,7 +95,7 @@ A saved lookup datatable can be up to **150 MB per table**, so a `savelookup` bu high `| limit` for large inventories. The much smaller 100,000-row (unvalidated) / 5 MB / 50-column cap applies only to tables registered as automatic lookups, not to explicit `| lookup` or `dataset` reads. -``` +```text // Identity enrichment table, keyed on samAccountName, empties suppressed | datasource assets from 'surface/identity' | filter resourceType = 'AD User' @@ -136,7 +136,7 @@ returned `device_os` "Windows 11 Pro", `device_criticality` "high"; `adm.webb` r Do not use `compare timeshift(queryspan())` or `timebucket(...)` on a raw `datasource` query. Use the aggregated-snapshot datasources, which carry a `snapshotDate`: -``` +```text | datasource alert_aggregated_snapshots | group count=sum(findingCount) by severity, timestamp=snapshotDate | transpose severity @@ -151,7 +151,7 @@ The `metering` datasource exposes Usage Metering reports for cost, usage, and (f - **Datasets:** `tenants` (tenants you can see), `reports` (available report names with metadata), and `` for the raw rows of one report (e.g. `server_endpoints`). Always list report names via `from 'reports'` first; the set is tenant- and permission-dependent. - **Drilldown filter:** append normal PQ after the generator to post-process the rows the metering backend returns, exactly as the UI drilldowns do: -``` +```text | datasource 'metering' from 'server_endpoints' | filter endpoint_bundle in ('Core', 'Complete') ``` @@ -160,7 +160,7 @@ The `metering` datasource exposes Usage Metering reports for cost, usage, and (f ## Example queries -``` +```text // Total alerts in the current inventory snapshot. (`compare timeshift(...)` // is not valid on a raw datasource query; for a time series use // `alert_aggregated_snapshots`, see "Time series" above.) diff --git a/plugins/s1-secops-skills/skills/powerquery/references/detection-rules.md b/plugins/s1-secops-skills/skills/powerquery/references/detection-rules.md index f558b3a..b58e161 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/detection-rules.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/detection-rules.md @@ -92,7 +92,7 @@ If you hit the 1,000-row limit on an intermediate `group`, the alert silently un ## Shape of a good rule -``` +```text | group count = count(), @@ -120,7 +120,7 @@ Why this shape: Something that fires once per endpoint per unusual activity. Low threshold, high specificity. -``` +```text indicator.name = 'EventViewerTampering' | group first_seen = oldest(timestamp), @@ -136,7 +136,7 @@ indicator.name = 'EventViewerTampering' "More than N of X from one entity in the window." -``` +```text event.login.loginIsSuccessful = false | group fails = count(), @@ -152,7 +152,7 @@ event.login.loginIsSuccessful = false Combine filters with `and` in the initial filter, not `and` in a computed column, the initial filter is cheapest and gates what the Summary service scans. -``` +```text event.type = 'Process Creation' src.process.parent.name = 'winword.exe' src.process.name in ('powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cscript.exe', 'mshta.exe', 'regsvr32.exe', 'rundll32.exe') @@ -171,7 +171,7 @@ src.process.name in ('powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cs When a rule would otherwise fire too broadly, exclude known-good via a config-managed data table. -``` +```text | lookup is_allowed = allowed from allowlist_hosts by endpoint.name | filter is_allowed = null // kept rows had no allowlist entry @@ -186,7 +186,7 @@ This uses `lookup` with a config data table (`/datatables/allowlist_hosts`). Kee `inner` / `left` joins work in alerts, bounded by the 1,000-row / 1 MB budget. Put strict filters inside each subquery; don't rely on the outer `filter` to prune. -``` +```text | inner join lsass_access = ( indicator.name = 'CredentialDumping' @@ -246,6 +246,7 @@ Working recipe for a scheduled rule with a mapped asset, two parts that must agr Confirmed on a live tenant: the same rule that showed "Unknown Device" with no `entityMappings` mapped the asset once `entityMappings` was configured on its `endpoint.name` / `src_ip` columns. (The earlier A/B tests showed Unknown Device only because they never set `entityMappings`.) Other paths that bind the entity: + - **Events-type rules** (`queryType: "events"`): the entity is taken from the matched event automatically, with no `entityMappings`. The console binds a **Device** by reconciling the event's device identity against inventory. `i.scheme` is NOT required, and `account.id` / `site.id` come from the ingest scope (S1-Scope header), not the event body. Fields like `event.type` / `src.process.*` matter only if the rule's `s1ql` filters on them. - **Do NOT trust the cloud-detection REST `agentRealtimeInfo` block as proof of binding.** Tenant-tested 2026-06-14: an event carrying ONLY the top-level `agent.uuid` made the REST payload's `agentRealtimeInfo` resolve hostname/OS/agent-id for display, but the console alert still showed **Target Asset = "Unknown Device"**. `agentRealtimeInfo` is a uuid lookup for display; it is not the entity binding. Confirm the actual **Target Asset** via `datasource alerts` (`assetName` / `assetAgentUuid`, which matches the console) or the console UI, never the REST `agentRealtimeInfo`. - **Minimum to bind the Target Asset (pinned by bisection, 2026-06-14): two fields, `device.uid` + `class_uid`.** `device.uid` must carry the **numeric console agent id** (the `agentRealtimeInfo.id` / console agent id, e.g. `2497649316206445895`, NOT the agent UUID), and `class_uid` must be an endpoint class (tested with `1007`). With just those two on a custom `dataSource.name`, the events-rule alert bound the real endpoint (`assetName` corp-ws-01, OS Windows, real `assetId`); the platform resolved `assetAgentUuid` from inventory even though the event carried no uuid. @@ -332,6 +333,8 @@ Notes on the shape: - **Scope:** `filter` accepts `accountIds` or `siteIds`. Pick the layer the rule should fire at. Account-level rules cover all sites under the account. - **Threshold:** the trigger threshold is the alert-firing threshold (`scheduledParams.threshold`), not the internal `| filter` inside the PowerQuery. `{value: 0, operator: "Greater"}` means "alert if the PQ returns any rows at all", combined with an internal `| filter hits >= N`, you get N as the effective threshold. - **Run interval and lookback:** match these (e.g. 60 / 60) for non-overlapping evaluation. Setting `lookbackWindowMinutes` higher than `runIntervalMinutes` causes overlap and duplicate alerts. +- **Hard cap: `lookbackWindowMinutes / runIntervalMinutes` must be <= 96.** Above that the create returns `HTTP 400 "Validation Error"` naming NEITHER field, so it reads as a malformed body. Probed at the boundary (2026-08-09): lookback 1440 accepts cadence 15 (ratio 96.0) and rejects 14 (102.9), 12, 10 and 5; lookback 60 accepts 5 and 10. A daily-baseline rule therefore cannot evaluate more often than every 15 minutes. If a UI or config offers a faster cadence (a 5-minute "demo" option is a common trap), clamp it: raise the requested interval to `ceil(lookback / 96)` rather than letting the whole deploy fail. +- **The cadence is also the floor on any end-to-end test.** There is no run-now for a scheduled rule, so validating that one fires costs at least one interval (15 min on a daily lookback). No amount of parallelism removes it; budget for it. - **`status`:** new rules land as `Draft` on creation regardless of the requested status. Enable separately with `PUT /web/api/v2.1/cloud-detection/rules/enable` (body `{"filter": {"ids": [...], "accountIds": [...]}}`). - **No `disableAgentMitigation` field:** that property is not part of the scheduled-rule schema. Including it returns HTTP 400 `Unknown field`. Cloud-source PQ rules do not need it. - **No `treatAsThreat: "Malicious"`:** scheduled rules accept `treatAsThreat: "UNDEFINED"` (or omit) and `networkQuarantine: false`. Inline (on-rule) mitigation is not supported on scheduled rules; drive mitigation from a Hyperautomation flow triggered by the alert instead. @@ -344,7 +347,7 @@ Do not use Hyperautomation workflows to schedule PQ detections. `cloud-detection ### Updating and enabling a rule -``` +```json PUT /web/api/v2.1/cloud-detection/rules/{id} # full-replacement update; all 5 data fields required, plus filter PUT /web/api/v2.1/cloud-detection/rules/enable # body: {"filter": {"ids": [...], "accountIds": [...]}} PUT /web/api/v2.1/cloud-detection/rules/disable # same shape @@ -360,7 +363,7 @@ Enabling a rule does NOT make it evaluate immediately. After `PUT .../enable` th Always poll the rule status until it is Active before ingesting any validation data: -``` +```text GET /web/api/v2.1/cloud-detection/rules?ids=&accountIds=&isLegacy=false # proceed only when data[0].status == "Active" ``` @@ -376,10 +379,9 @@ Do not ingest, do not judge a rule "not firing", and do not strip attributes to 3. Run it over 7 days for baseline volume: expected row count × 7 ≈ what a week of alerting will look like. 4. If the `group`-intermediate ever exceeds 1,000 rows in a 24-hour window, tighten the initial filter. - ## Lookup table size and per-device detections (validated) - `savelookup` / `| lookup` datatables can be up to **150MB per table** (extensible by contacting SentinelOne); table size is essentially never the design blocker. Do not treat lookups as capped to a small row count. - A `lookup` used INSIDE a scheduled `cloud-detection` rule is additionally validated for load size (on at least one tenant a ~26,800-row lookup table was rejected with "Maximum number of rows allowed is 10000"). Treat this as a soft, tenant-configurable limit: use a coarser key (hour-of-day = 24 buckets instead of hour-of-week = 168) or a volume floor to keep the rule's lookup small, or request an increase. It is not a reason to abandon a lookup-based detection. - Name the pipeline join expression differently from the table key: `| lookup col from t by = ` (e.g. `by devkey = dk`), not `by devkey = devkey`, which can fail the rule parser with "Expected ')'". -- Bound per-device / high-cardinality detections by doing the baseline `lookup` + `filter exp_gib > 0` per event BEFORE the `group`, so the intermediate stays within the 1,000-row alert budget. \ No newline at end of file +- Bound per-device / high-cardinality detections by doing the baseline `lookup` + `filter exp_gib > 0` per event BEFORE the `group`, so the intermediate stays within the 1,000-row alert budget. diff --git a/plugins/s1-secops-skills/skills/powerquery/references/fields-and-schema.md b/plugins/s1-secops-skills/skills/powerquery/references/fields-and-schema.md index 60b9045..2ffa7ff 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/fields-and-schema.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/fields-and-schema.md @@ -2,7 +2,7 @@ Common field paths in SentinelOne EDR / XDR data. When in doubt about a field name, the fastest way to confirm it is to run a small exploratory query: -``` +```text event.type = 'Process Creation' | limit 3 | columns timestamp, endpoint.name, src.process.name, src.process.cmdline @@ -177,6 +177,7 @@ Repeated from the syntax reference because they save real time: SentinelOne fields are increasingly OCSF-aligned. Many queries also work with OCSF categories (`event.category`). When exploring a new data source in **All Data** view, read a couple of raw events first to see which fields are populated, log sources outside EDR/XDR (e.g., SentinelOne Collector logs) don't follow the EDR schema and often put the unparsed text in `message`. For those `message`-style sources, use: + - `$"regex"` shorthand for `message matches "regex"` (single-escape) - Explicit `message contains 'text'` for non-regex - `| parse "…$field$…"` to extract fields on the fly @@ -198,7 +199,7 @@ Singularity Data Lake stamps every ingested event with a small set of `sca:`-pre ### Ingest volume (the common case) -``` +```text dataSource.name='Windows Event Logs' endpoint.name='D01-QCDC01' | group gb = sum(number(sca:bytesToCharge)) / 1024 / 1024 / 1024 ``` @@ -207,7 +208,7 @@ Works for any source / endpoint combination. Divide by `1024 / 1024 / 1024` for Per-source volume leaderboard: -``` +```text sca:bytesToCharge=* dataSource.name=* | let gib = number(sca:bytesToCharge) / 1024 / 1024 / 1024 | group GiB = sum(gib), events = count() by source = dataSource.name @@ -219,7 +220,7 @@ sca:bytesToCharge=* dataSource.name=* `sca:ingestTime` is in seconds; `timestamp` is in nanoseconds. Convert before subtracting: -``` +```text sca:ingestTime=* dataSource.name=* | let ingest_ts = number(sca:ingestTime) | let evt_ts = number(timestamp) / 1000000000 diff --git a/plugins/s1-secops-skills/skills/powerquery/references/functions-reference.md b/plugins/s1-secops-skills/skills/powerquery/references/functions-reference.md index 243bb60..fc2b2e0 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/functions-reference.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/functions-reference.md @@ -49,7 +49,7 @@ These are element-wise; don't confuse with the `min()` / `max()` aggregation fun Strings support `.method()` chaining: `len(x)` → `x.len()`, `substr(x, y, z)` → `x.substr(y, z)`. Drop the first (string) argument. -``` +```text | limit 1 | columns s = "three blind mice" | columns mice_len = s.substr(12).len() @@ -61,7 +61,7 @@ Strings support `.method()` chaining: `len(x)` → `x.len()`, `substr(x, y, z)` Printf-style; up to 50 values. -``` +```text | let line = format("%,d events for %s (%.2f%%)", ct, endpoint, ratio * 100) ``` @@ -84,7 +84,7 @@ Flags (between `%` and the letter): `-` left-justify, `+` force sign, ` ` (space `json_object_value(obj, "field")`, parse a JSON string, return the named field's value. Returns a primitive or an array of strings. -``` +```json | columns obj = '{"users":[{"name":"A"},{"name":"B"}]}' | let users = json_object_value(obj, "users") | let first_user = array_get(users, 0) @@ -123,6 +123,7 @@ See [commands-reference §4](commands-reference.md#4-group) for the full list. T `avg(x)` is the group mean and `stddev(x)` is the sample standard deviation; percentiles support `p10/p50/p90/p95/p99/p999` and the general `pct(N, x)`. Tenant-validated. **Avoid** on general tenants (return 500 error on this deployment even though docs list them): + - `percentile(x, N)`: use `p50` / `p95` / `p99` instead. - `first(x)` / `last(x)`: use `min_by(x, timestamp)` / `max_by(x, timestamp)`. These are more explicit and always work. @@ -140,7 +141,7 @@ These operate over the already-computed result set and add a value per row. Useful for Pareto-style "top contributors" reports: -``` +```text | group ct = count() by endpoint.name | sort -ct | let share_pct = percent_of_total(ct), running_share = running_percent(ct) @@ -181,7 +182,7 @@ Array functions are only in PowerQueries (not alerts). Arrays cap at 8 MB. Creat `func(args) -> expression`. No nesting. -``` +```text tgt.file.size = * | group sizes = array_agg(tgt.file.size) by endpoint.name | let big_files = sizes.filter(func(x) -> x > 1_000_000) @@ -236,7 +237,7 @@ Timestamps are stored as nanoseconds since epoch. Any numeric field named `times | `queryend([unit])` | End of query window | | `queryspan([unit])` | Length of query window | -``` +```text // "events per minute" rate indicator.category = * | group rate = count() / queryspan('minutes') by endpoint.name diff --git a/plugins/s1-secops-skills/skills/powerquery/references/lrq-api.md b/plugins/s1-secops-skills/skills/powerquery/references/lrq-api.md index cd6de95..d20461b 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/lrq-api.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/lrq-api.md @@ -4,7 +4,7 @@ The LRQ API is the **default** programmatic path for every PowerQuery this skill ## Endpoints (all on the tenant's own console host) -``` +```text POST https://.sentinelone.net/sdl/v2/api/queries GET https://.sentinelone.net/sdl/v2/api/queries/{id}?lastStepSeen=N DELETE https://.sentinelone.net/sdl/v2/api/queries/{id} @@ -14,13 +14,13 @@ The console host is tenant-specific (for example `your-tenant.sentinelone.net`), ## Auth: Bearer, not ApiToken -``` +```text Authorization: Bearer ``` The JWT is the **same** console service-user token used by the Mgmt API; only the prefix changes. Calling `/sdl/v2/api/queries` with `Authorization: ApiToken ` returns HTTP 500: -``` +```text "Header must start with Bearer, but actually starts with \"ApiTok\"" ``` @@ -70,7 +70,7 @@ Service user tokens are preferred over personal user tokens because the per-user ## Polling -``` +```text GET /sdl/v2/api/queries/{id}?lastStepSeen= Headers: Authorization: Bearer @@ -83,7 +83,7 @@ Done when `stepsCompleted >= stepsTotal` and `stepsTotal > 0`. `data.values` is ## Cancel -``` +```text DELETE /sdl/v2/api/queries/{id} Headers: Authorization: Bearer @@ -105,13 +105,13 @@ Each API call (POST, GET, DELETE) counts as one request. A single slice with 5s On most SentinelOne tenants, the default scope carries a mix of SentinelOne EDR telemetry and Scalyr/infra logs. If you want EDR events (Process Creation, File Creation, Module Load, etc.), prepend this to the query: -``` +```text dataSource.name='SentinelOne' dataSource.category='security' ``` or equivalently: -``` +```text i.scheme="edr" ``` diff --git a/plugins/s1-secops-skills/skills/powerquery/references/o365-fields.md b/plugins/s1-secops-skills/skills/powerquery/references/o365-fields.md index 33c955d..26caeb8 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/o365-fields.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/o365-fields.md @@ -21,7 +21,7 @@ M365 audit events arrive as a hybrid: a thin layer of OCSF-normalized scalars on ## Discovery recipe (run this first) -``` +```text // 1. Confirm the source string and event volume on this tenant. | group ct=count() by dataSource.name | sort -ct | limit 50 @@ -40,7 +40,7 @@ If `` returns mostly nulls or "Unknown", it's not the right fie A common pattern, **not a contract**: many M365 fields exist in both an OCSF-normalized form (often empty) and an `unmapped.*` form (often populated, mirroring the upstream audit JSON key). When in doubt, write the filter against both with `OR`, then narrow once discovery confirms which is real on this tenant: -``` +```text dataSource.name='' ( contains:anycase 'user@example.com' OR contains:anycase 'user@example.com') @@ -62,7 +62,7 @@ Two strategies for hunting on blob-only fields: Outbound mail on M365 spans several operation values across workloads. The set is not fixed across parser versions; discover the actual operation strings on this tenant, then filter on the discovered set: -``` +```text dataSource.name='' | group ct=count() by | sort -ct | limit 50 @@ -74,7 +74,7 @@ Conceptually you'll typically see at minimum: a per-user mailbox send, a delegat M365 audit records carry an integer category code. The exact integer-to-category mapping is documented by Microsoft and is stable across tenants; the field name carrying it on the SDL side is the variable part. Discover the field name once, then use the integer for cheap pre-filters: -``` +```text // discover the field name carrying the M365 RecordType integer dataSource.name='' | limit 1 | columns * @@ -88,7 +88,7 @@ If a human-readable category column is needed, build a small lookup table (`save Client IP fields on M365 audit events are dominated by Microsoft service-tier traffic. Any "top external IPs" or "unique source IPs" analysis without a service-tier exclusion produces a meaningless ranking. The exact prefix set drifts and varies by tenant geography; **build the exclusion list from this tenant's data**, not from a hardcoded list: -``` +```text // 1. Pull the top client IPs without filtering. dataSource.name='' | group ct=count() by @@ -116,7 +116,7 @@ Searching SDL for an identity string returns two interleaved record sets: Always partition the result set on `dataSource.name` presence and report the two as separate quantities. The SDL audit trail of prior investigations is itself a finding (it tells you the subject has been investigated before), but it is not subject activity: -``` +```text * contains 'user@example.com' | let category = (dataSource.name = *) ? 'subject_activity' : 'investigation_noise' | group ct=count() by category @@ -124,7 +124,7 @@ Always partition the result set on `dataSource.name` presence and report the two To inspect what the investigation-noise records actually look like on this tenant, drill in: -``` +```text * contains 'user@example.com' | filter !(dataSource.name = *) | limit 5 | columns * @@ -134,7 +134,7 @@ To inspect what the investigation-noise records actually look like on this tenan When a user's email shows up frequently under `* contains ''` but returns zero rows when filtered as actor, three interpretations are possible: the mailbox isn't ingested (coverage gap), the entity is external (only appears as a recipient or in message content), or the active UPN differs from the address you're searching for. Run both queries; if the first returns rows and the second returns zero, frame the result as one of those three, not as "no activity". -``` +```text // Does the address appear anywhere? * contains 'user@example.com' | group ct=count() by event.type diff --git a/plugins/s1-secops-skills/skills/powerquery/references/pitfalls.md b/plugins/s1-secops-skills/skills/powerquery/references/pitfalls.md index 65e037e..e3ebadf 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/pitfalls.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/pitfalls.md @@ -6,7 +6,7 @@ Curated failure modes. When a PowerQuery is misbehaving, check this list before ### `*` alone as a filter returns 500 -``` +```text * ← NOT a valid initial filter: HTTP 500 ("Don't understand [*]") * | limit 5 ← same ``` @@ -17,7 +17,7 @@ There are three distinct `*` idioms in PowerQuery. They look similar but mean di Means "field is present and non-null". Use as a query-opener when you want all events that have a given field, or as the starting predicate for aggregations. Confirmed working on live tenant. -``` +```text dataSource.name=* | group count=count() by dataSource.name | sort -count | limit 10 event.type=* | limit 5 ``` @@ -28,7 +28,7 @@ This is NOT an all-column text search; it checks whether a specific attribute is Searches ALL indexed fields across the event. Use when you need to find a specific string anywhere in the event, what users describe as "search all logs for X", "all column search", "find this text anywhere". Only valid as the **initial filter** (before the first `|`). Not valid in `| filter …` after a pipe, and not valid in Alerts. -``` +```text dataSource.name='MySource' * contains 'evil.com' // string in any field on a source * contains 'suspicious_domain.com' | limit 50 // all sources, any field * matches '\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}' | limit 20 // IP pattern anywhere @@ -40,7 +40,7 @@ Much faster than `message contains 'value'` because it scans indexed fields, not Use when you want all events with no initial predicate. -``` +```text | limit 5 // "all events, first 5" | group ct=count() by event.type ``` @@ -49,14 +49,14 @@ Use when you want all events with no initial predicate. PowerQuery uses `-`/`+` prefix for sort direction. `desc` and `asc` are not valid keywords and cause the LRQ API to return HTTP 500 "Unable to parse the entire query". -``` +```text | sort count desc ← HTTP 500, "desc" is not valid PowerQuery syntax | sort timestamp asc ← HTTP 500, "asc" is not valid PowerQuery syntax ``` Fix: use `-` for descending, `+` for ascending (ascending is also the default when no prefix is given). -``` +```text | sort -count | sort +timestamp | sort -hits, +endpoint.name @@ -66,7 +66,7 @@ Fix: use `-` for descending, `+` for ascending (ascending is also the default wh ### `join` without a leading pipe -``` +```text join (q1), (q2) on x ← "join" is interpreted as a search keyword ``` @@ -74,7 +74,7 @@ Fix: `| join (q1), (q2) on x`. The same rule applies to `union`. ### `compare` or `transpose` not last -``` +```text | compare last_week = timeshift('1w') | sort -count ← too late; compare must be LAST ``` @@ -83,28 +83,28 @@ Fix: move `sort` before `compare`. The display ordering is applied to the main r ### Subquery after `group` / `sort` / `limit` -``` +```text | group count() by user | filter user in (role='admin' | columns user) ← invalid ``` Fix: move the subquery into the initial filter position. -``` +```text user in (role='admin' | columns user) | group count() by user ``` ### Subquery doesn't define the filter column -``` +```text user in (action='login') ← fails user in (action='login' | group count() by ip) ← fails ("user" column not produced) ``` Fix: produce the column. -``` +```text user in (action='login' | columns user) user in (action='login' | group 1 by user) user in (action='login' | top 10 count() by user) @@ -112,7 +112,7 @@ user in (action='login' | top 10 count() by user) ### Shortcut fields as initial filter return 500 -``` +```text #cmdline contains 'python' ← 500 on many tenants #name = 'bash' ← 500 #hash = * ← 500 @@ -120,7 +120,7 @@ user in (action='login' | top 10 count() by user) The docs list `#cmdline`, `#name`, `#hash`, `#ip`, `#storylineid`, `#username`, `#dns` as multi-field shortcuts. In practice, they're unreliable across tenants; in this deployment they all return 500 as initial filters. Fix: use the explicit field. -``` +```text src.process.cmdline contains 'python' src.process.name = 'bash' tgt.file.sha256 = * @@ -130,25 +130,25 @@ The explicit form is only a few characters longer and always works. Save shortcu ### `parse` with the wrong argument order -``` +```text | parse src.process.cmdline, "$bin$ $args$" ← 500 ``` Fix: the source goes at the end with `from`. -``` +```text | parse "$bin$ $args$" from src.process.cmdline ``` ### `timebucket` in `group by` without an alias: "undefined field 'timebucket'" -``` +```text | group count=count() by timebucket('1h') ← 500 "undefined field 'timebucket'" ``` Without an alias, PQ treats the bare name `timebucket` as a field lookup rather than a function call. Fix: always assign an alias in `group by` when using functions as keys. -``` +```text | group count=count() by bucket=timebucket('1h') | sort +bucket ``` @@ -183,13 +183,13 @@ Some docs list these as aggregate functions, but they fail on many tenants. Use ### Ternary parsed as an identifier -``` +```text cond?x:y ← ":" may be glued into an identifier ``` Fix: spaces around the `:`. -``` +```text cond ? x : y ``` @@ -199,7 +199,7 @@ PQ has no `coalesce()`. The intuitive way to fall back across multiple fields breaks because `field = *` is a filter operator, not a boolean expression usable in a computed column. -``` +```text | let user_id = (actor.user.email_addr = *) ? actor.user.email_addr : actor.user.name ← HTTP 500 ``` @@ -207,7 +207,7 @@ expression usable in a computed column. Fix, bare-field truthy test (the field's null-or-truthy value drives the ternary directly): -``` +```text | let user_id = actor.user.email_addr ? actor.user.email_addr : (actor.user.name ? actor.user.name : src.process.user) @@ -219,14 +219,14 @@ N fields. Use the same pattern any time you'd reach for `coalesce` / ### `sum(if(...))` for conditional counts: use `count(predicate)` instead -``` +```text | group critical = sum(if(severity_ in:anycase ('Critical'), 1, 0)) by host ← invalid ``` PowerQuery does not accept `if(...)` as an aggregate body. The right idiom is to pass a predicate directly to `count()`: -``` +```text | group critical = count(severity_id == 5), high = count(severity_id == 4), medium = count(severity_id == 3), @@ -258,14 +258,14 @@ Same source pipeline, different upstream casing, values like `Critical`, `CRITICAL`, `High`, `HIGH`, `Medium`, `MEDIUM`, `Low`, `LOW` co-exist in the same `severity_` column. -``` +```text | group count() by timestamp = timebucket('1h'), severity_ | transpose severity_ on timestamp ← produces 8 columns ``` Fix, normalise before grouping: -``` +```text | let sev = lower(severity_) | group count() by timestamp = timebucket('1h'), sev | transpose sev on timestamp ← 4 clean columns @@ -276,7 +276,7 @@ Fix, normalise before grouping: Or skip the string field entirely and use the numeric OCSF `severity_id` for filters: -``` +```text severity_id >= 4 | group count() by timestamp = timebucket('1h'), severity_id | transpose severity_id on timestamp ← columns are 4, 5 @@ -290,7 +290,7 @@ the source data has been ingested untyped), the column stays string forever. Subsequent writes, even from a parser declaring `type: "long"`, get coerced back to string at index time. Numeric aggregation then breaks silently: -``` +```text dataSource.name='FortiGate' unmapped.action='close' | group sessions=count(), bytes_out=sum(traffic.bytes_out) ← NaN, even though values are populated | limit 1 @@ -301,7 +301,7 @@ The values ARE there (you can see them in Event Search), but `sum()` / **Failsafe pattern, cast at query time with `number()`:** -``` +```text dataSource.name='FortiGate' unmapped.action='close' | let bytes_out_n = number(traffic.bytes_out) | let bytes_in_n = number(traffic.bytes_in) @@ -323,7 +323,7 @@ preemptively to: Same trick works for arithmetic comparisons and sorts: -``` +```text | let sev = number(severity_id) | filter sev >= 4 | group n=count() by sev @@ -337,7 +337,7 @@ recommended default for OCSF counter fields. ### Bracket array indexing in `columns` returns HTTP 500 -``` +```text dataSource.name='alert' | columns severity_id, resources[0].name, vulnerabilities[0].cve.uid ← HTTP 500 ``` @@ -349,7 +349,7 @@ paths. Fix, for first-element access inside a query, use `array_get` in a `let`: -``` +```text | let first_resource = array_get(resources, 0) | let first_resource_name = first_resource.name ``` @@ -365,7 +365,7 @@ group key on this stream. Wildcard values come back JSON-array-wrapped, e.g. `[" `["DESKTOP-GDIA5I7"]`, so strip the `[` `]` `"` wrapping when post-processing. The same wildcard form reads optional MITRE on custom alerts: `finding_info.attacks[*].tactic.uid` / `.name`. -``` +```text dataSource.name='alert' class_uid=99602001 | filter finding_info.title contains:anycase("SPIKE") | group hits = count() by entity = resources[*].name, tactic = finding_info.attacks[*].tactic.uid @@ -380,19 +380,19 @@ JSON. ### Regex backslashes eaten -``` +```text src.process.cmdline matches "\d+" ← only one level of escaping; often matches nothing ``` Fix: double-escape everywhere except the `$"…"` shorthand. -``` +```text src.process.cmdline matches "\\d+" ``` Windows paths, four-ish backslashes for a literal `\`: -``` +```text tgt.file.path matches '^C:\\\\Windows\\\\Temp\\\\[a-z]{8}\\.tmp$' ``` @@ -416,7 +416,7 @@ If a query "misses" something you can see in the data, check whether case was th `not in` is accepted by the parser and returns no error and no rows. In a matched A/B control (live-verified 2026-07-29 via LRQ v2), `!(x in (...))` returned ~38k rows while the identical predicate written as `x not in (...)` returned 0. Always write the negation as `!(x in (...))`: -``` +```text event.type = 'Process Creation' !(src.process.parent.name in ('explorer.exe', 'svchost.exe', 'services.exe')) ``` @@ -439,7 +439,7 @@ These functions require the original timestamp ordering of events. If you `sort` ### "Memory limits" message -``` +```text 213,408 of 37,059,484 matching events (0.576%) were omitted due to memory limits. ``` @@ -460,13 +460,44 @@ The query timeout is 5 minutes. If a 30-day query times out: - Use `top` instead of `group`. - Consider running the query over 7-day chunks and `savelookup`-ing each, then `union`-ing. +Note the chunking suggestion above cannot be spread across separate writers to one table: see +"`savelookup` replaces, it does not append". + +### `savelookup` replaces its table, it does not append + +Each `| savelookup '
'` REPLACES the table's contents. Tenant-validated 2026-08-09: writing +source A then source B to the same table left only B's rows. Any design where N flows each +`savelookup` into a shared table silently keeps only whichever ran last. If you need N producers, +give each its own table and add a merge pass that `| dataset`-unions them into the canonical table +(the merge is cheap, it reads datatables rather than raw events). + +### A wide `| union` is what gets a query killed, not the time window + +Query cost scales with the number of union blocks, roughly 15 execution steps per block, and the +backend terminates long-running queries mid-flight. Measured on one tenant with a per-source union +baseline (`| nolimit` savelookup): + +| union blocks | `stepsTotal` | outcome | +| --- | --- | --- | +| 1 | 16 | always completes | +| 3 | 46 | completes (139s) | +| 6 | 91 | **killed at step 58** | + +Shortening the window does NOT rescue it: at 6 blocks the same query was killed with a 5-day +window (47s standalone) exactly as with 7 days (86s). Nor is poll interval a factor: fixed 5s, 4s +and 2s polls all completed the same query standalone. **Cap the number of union blocks**, and note +the query completing standalone via the API proves nothing about it surviving inside an HA flow. + +When a query IS killed, subsequent polls answer `404 {"code":"not_found"}` on the query token +forever, which is why any HA poll loop needs an explicit 4xx gate (see the hyperautomation skill). + ### Reaching for `message contains` on a JSON-blob source Some data sources (O365 audit, generic webhook ingest, custom HEC sources) keep most fields inside a raw JSON `message` blob rather than as parsed top-level columns. The first instinct is to write `message contains 'value'`, but that forces a substring scan of the entire blob and falls off a performance cliff fast: queries that work at 1 day routinely time out at 7. Fix: use the multi-field shortcut `* contains 'value'` (or `* matches 'regex'`) in the initial filter. It searches across all indexed fields, including parsed scalars from the source, and is dramatically faster than scanning a single concatenated blob. -``` +```text // slow: single-column substring scan dataSource.name='' message contains 'value' @@ -494,7 +525,7 @@ Grouping by full URL or full command line yields one row per variant, useless fo A `lookup` before a `group` is evaluated per-event. Once per-group is always cheaper: -``` +```text // ← slower | lookup os_version from machineinfo by endpoint.name | group count() by endpoint.name, os_version @@ -540,7 +571,7 @@ When a bare `| group count()` with no `timebucket` dimension runs against a sile **Failsafe for time-bounded aggregations:** add `by timebucket(timestamp, "")` to force the engine to partition by time. Then `| sort -total | limit 1` returns only the most recent bucket: -``` +```text dataSource.name='MySource' | group total=count(), field_a=count(some.field) by timebucket(timestamp, "10m") @@ -561,14 +592,14 @@ Two annotations on this failsafe (live-verified 2026-07-29 via LRQ v2): **Approximate (fast):** use `estimate_distinct(x)`. Returns a probabilistic HLL count. Fine for dashboards and thresholds where ±5% error is acceptable. -``` +```text | group approx_ports = estimate_distinct(dst_endpoint.port) by src_endpoint.ip | filter approx_ports > 100 ``` **Exact (two-stage grouping):** when you need a precise count of distinct values per key, do it in two `group` passes: -``` +```text // Stage 1: one row per (src, port) pair dataSource.name='Palo Alto Networks Firewall' dst_endpoint.port=* | group count=count() by src_endpoint.ip, dst_endpoint.port @@ -640,7 +671,7 @@ Alerts don't support these. Move the correlation logic into a `join` (bounded) o **Always add `field=*` to the initial filter** to scope to events that actually carry the field: -``` +```text // Wrong: returns nulls because most events don't have message dataSource.name='FortiGate' | limit 3 | columns message @@ -665,11 +696,10 @@ Don't keep re-running slightly rephrased versions, the Purple MCP docs warn expl Common cause: grouping dropped a field you assumed was still present, or duplicate rows from a `union`. Add `columns` at the end to make the exact shape explicit, then re-inspect. - ## Ingest-health validated pitfalls - `replace_all(...)` returns "Unknown function" on this engine; use `replace(...)`. - `count(field=*)` returns "Don't understand [*]"; count non-null rows with a flag: `| let f = (field ? 1 : 0) | group n = sum(f)`. Note the flag idiom is a TRUTHY count: 0, `false`, and the empty string are dropped along with null. For an exact non-null count use a predicate: `count(field != null)` (live-verified 2026-07-29; `field != null` matches exactly the rows `field=*` matches). - A second `group` cannot reference a field renamed in the first group: after `group ... by source = dataSource.name`, key the next group on `by source`, not `by source = dataSource.name` ("undefined field 'dataSource.name'"). - Do not transpose on `dataSource.name` or a device key (values contain spaces); use honeycomb, single-series time charts, or `grouped_data`. -- `avg()`, `stddev()`, `pct(N, x)` and `p10/p90/p999` all work in `group` (do not treat them as missing). \ No newline at end of file +- `avg()`, `stddev()`, `pct(N, x)` and `p10/p90/p999` all work in `group` (do not treat them as missing). diff --git a/plugins/s1-secops-skills/skills/powerquery/references/syntax-and-operators.md b/plugins/s1-secops-skills/skills/powerquery/references/syntax-and-operators.md index e3025a1..0ee26ea 100644 --- a/plugins/s1-secops-skills/skills/powerquery/references/syntax-and-operators.md +++ b/plugins/s1-secops-skills/skills/powerquery/references/syntax-and-operators.md @@ -34,7 +34,7 @@ Parentheses work as expected: `a AND (b OR c)`. ### `contains`: substring search -``` +```text src.process.cmdline contains 'powershell' src.process.cmdline contains ('powershell', 'pwsh', 'cmd') // OR of multiple src.process.cmdline contains:matchcase 'PowerShell' // case-sensitive @@ -46,7 +46,7 @@ Case-insensitive by default. Cannot be used on numeric values. The OR form is mu ### `matches`: regex -``` +```text src.process.cmdline matches '\\w+\\.exe' src.process.cmdline matches ('regex1', 'regex2') // OR of multiple src.process.cmdline matches:matchcase 'CaseSensitive' @@ -56,7 +56,7 @@ Case-insensitive by default. Regex has a 1,000-byte ceiling. Double-escape speci ### `in`: exact equals any of -``` +```text event.login.type in ('NETWORK', 'NETWORK_CLEAR_TEXT', 'CACHED_REMOTE_INTERACTIVE') severity in (3, 4, 5) // numbers / booleans unquoted event.login.type in:anycase ('network') // case-insensitive variant @@ -66,7 +66,7 @@ Default is case-sensitive (opposite of `contains`). Cannot match null. Quote str ### Equality and inequality -``` +```text endpoint.name = 'DC-1' // exact equal (case-sensitive for strings) indicator.name != 'RawVolumeAccess' indicator.name !=* // never valid; use !(x=*) for is-null @@ -76,7 +76,7 @@ indicator.name !=* // never valid; use !(x=*) for is-null There are three distinct `*` idioms; they look similar but mean different things: -``` +```text // 1. FIELD PRESENCE / ATTRIBUTE WILDCARD dataSource.name = * // "field is present/non-null"; use as query opener or filter indicator.category = * // same pattern; works for any field @@ -92,6 +92,7 @@ $"regex" // shorthand for message matches "regex ``` Key facts about `*`: + - `*` by itself is NOT a valid filter. `*` alone returns HTTP 500 ("Don't understand [*]"). Never use `*` as the only initial token. - **`field = *`** (attribute wildcard) checks whether a specific field is present/non-null. Use `dataSource.name=*` as a query-opener for aggregations over all events that have that field. Confirmed working on live tenant. - **`* contains` / `* matches`** (all-column search) searches across ALL indexed fields. Only works in the **initial filter** (before the first `|`). Not valid in `| filter …` after a pipe, not in Alerts, not in dashboards. Much faster than `message contains` on JSON-blob sources. @@ -142,7 +143,7 @@ Preceded by `#`, these search across multiple related fields at once. Only valid | `#uid` | All UID fields | | `#username` | All process user fields | -``` +```text #hash = '44d88612fea8a8f36de82e1278abb02f' // find an IOC hash anywhere #ip = '198.51.100.7' // any IP match #storylineid = 'F5BA787CED1FF38A' | limit 100 @@ -210,7 +211,7 @@ When calling the Purple MCP `powerquery` tool, pass ISO-8601 with an explicit of `||` and `OR` don't return booleans; they return the first truthy operand. Falsy values: `null`, `0`, `false`, `""`, `NaN`. Everything else is truthy (including `"0"` and `"false"`; those are non-empty strings). -``` +```text | let first_name = preferred_name || legal_name || 'Unknown' ``` diff --git a/plugins/s1-secops-skills/skills/sdl-api/README.md b/plugins/s1-secops-skills/skills/sdl-api/README.md index a424647..ab700ea 100644 --- a/plugins/s1-secops-skills/skills/sdl-api/README.md +++ b/plugins/s1-secops-skills/skills/sdl-api/README.md @@ -1,6 +1,6 @@ # sdl-api (Claude skill) -A Claude skill wrapping the SentinelOne **Singularity Data Lake (SDL) API** for query and configuration-file management on a Scalyr/SDL/XDR tenant. Covers the SDL query and configuration methods (`query`, `numericQuery`, `facetQuery`, `timeseriesQuery`, `powerQuery`, `getFile`, `putFile`, `listFiles`) with a Python client, a CLI, and per-method reference docs. Raw-log ingestion has moved to the HEC path (see `mgmt-console-api`). +A Claude skill wrapping the SentinelOne **Singularity Data Lake (SDL) API** for query and configuration-file management on a Scalyr/SDL/XDR tenant. Covers the SDL query methods (`query`, `numericQuery`, `facetQuery`, `timeseriesQuery`, `powerQuery`) and the GraphQL configuration-file operations (`configFiles`, `configFile`, `addConfigFile`, `deleteConfigFile`) with a Python client, a CLI, and per-method reference docs. Raw-log ingestion has moved to the HEC path (see `mgmt-console-api`). ## Install @@ -55,8 +55,8 @@ Generate the token in the S1 Console → Settings → Users → My User → **AP | `S1_CONSOLE_API_TOKEN` | yes | Authorises every query and config method. Sent as `Authorization: Bearer`. | | `SDL_S1_SCOPE` | only when multi-scope | Set when the token spans multiple sites or accounts. Format `:` for site scope, `` for account scope. | -One token covers all of it: `query`, `numericQuery`, `facetQuery`, `timeseriesQuery`, -`powerQuery`, `listFiles`, `getFile`, `putFile`. +One token covers all of it: the query methods and the GraphQL configuration-file +operations. The scoped SDL keys are retired. When using `s1-secops-mcp`, environment variables set in `claude_desktop_config.json` take priority. When using skills directly, environment variables set in your shell override the credentials file. @@ -68,13 +68,14 @@ cd ~/.claude/skills/sdl-api python tests/smoke_test.py ``` -The smoke test exercises the query and configuration methods end-to-end: runs `query` / `facetQuery` / `numericQuery` / `timeseriesQuery` / `powerQuery`, then `listFiles` + `getFile` + a full `putFile` create→update→delete round-trip on a throwaway `/lookups/sdl_skill_smoke_…` path. Reports a per-method pass/fail line. +The smoke test exercises the query and configuration methods end-to-end: runs `query` / `facetQuery` / `numericQuery` / `timeseriesQuery` / `powerQuery`, then `configFiles` + `configFile` by udoId + a full `put_config_file` create→update→stale-version→delete round-trip on a throwaway `/lookups/sdl_skill_smoke_…` path. Reports a per-method pass/fail line. ## CLI ```bash -python scripts/sdl_cli.py list-files -python scripts/sdl_cli.py get-file /logParsers/MyParser +python scripts/sdl_cli.py config-files --prefix /dashboards/ +python scripts/sdl_cli.py config-file --name /logParsers/MyParser +python scripts/sdl_cli.py config-file --udo-id 6554761743556608 # dashboards python scripts/sdl_cli.py power-query "dataset='accesslog' | group count() by status" --start 1h python scripts/sdl_cli.py query "tag='ingestionFailure'" --start 1h --max 20 @@ -82,11 +83,11 @@ python scripts/sdl_cli.py facet-query srcIp --filter "status >= 400" --start 1h python scripts/sdl_cli.py numeric-query --function count --start 1h --buckets 30 python scripts/sdl_cli.py timeseries-query --function count --filter "*" --start 1h --buckets 60 -python scripts/sdl_cli.py put-file /lookups/MyTable --content-file ./table.json -python scripts/sdl_cli.py put-file /lookups/Stale --delete +python scripts/sdl_cli.py put-config-file --name /datatables/MyTable --content-file ./table.csv +python scripts/sdl_cli.py delete-config-file --name /datatables/Stale ``` -The CLI subcommands are `list-files`, `get-file`, `put-file`, `query`, `power-query`, `facet-query`, `numeric-query`, and `timeseries-query`. +The CLI subcommands are `config-files`, `config-file`, `put-config-file`, `delete-config-file`, `query`, `power-query`, `facet-query`, `numeric-query`, and `timeseries-query`. The legacy `list-files` / `get-file` / `put-file` subcommands remain but use the incomplete REST surface; prefer the `config-*` set. **Log/event ingestion is not part of this CLI.** The former `upload-logs` and `add-events` subcommands were removed when ingestion moved to the HEC endpoint. To ingest raw logs or events, use the `hec_ingest` tool in `s1-secops-mcp` (posts to `S1_HEC_INGEST_URL` and applies a named parser via `sourcetype`). @@ -105,10 +106,15 @@ r = c.power_query("status >= 100 status <= 599 | group count() by status", start for m in c.iter_query(filter="tag='ingestionFailure'", start_time="24h", max_total=500): ... -# Configuration files -paths = c.list_files()["paths"] -parser = c.get_file("/parsers/MyParser") -c.put_file("/parsers/MyParser", content="// parser body", expected_version=parser["version"]) +# Configuration files (GraphQL: the complete surface) +files = c.config_files() # every file, including udoId-addressed dashboards +parser = c.config_file(name="/logParsers/MyParser") +c.put_config_file(name="/logParsers/MyParser", content="// parser body", + expected_version=parser["version"]) + +# Dashboards are addressed by udoId on update; a name-addressed write duplicates them. +dash = [f for f in files if f["name"] == "/dashboards/SOC Overview"][0] +c.put_config_file(udo_id=dash["udoId"], content=body, expected_version=dash["version"]) ``` The client picks the right key per method automatically, retries on 429/5xx/`error/server/backoff` with exponential backoff honouring `Retry-After`, and returns parsed JSON. Errors surface as `SDLAPIError` with `.status` and `.body`. @@ -117,7 +123,7 @@ The client picks the right key per method automatically, retries on 429/5xx/`err - `SKILL.md`: instructions Claude reads when the skill triggers - `scripts/bootstrap_creds.sh`: idempotent helper to copy workspace creds into the sandbox-local path -- `scripts/sdl_client.py`: `SDLClient` (auto key selection across 3 scoped keys + console token, `Bearer` auth, retries, `iter_query` pagination) +- `scripts/sdl_client.py`: `SDLClient` (console token, `Bearer` auth, retries, `iter_query` pagination, GraphQL config-file operations) - `scripts/sdl_cli.py`: shell CLI covering every method - `references/methods.md`: per-method reference (params, defaults, response shape, field requirements) - `references/auth_and_limits.md`: key matrix, console-token + S1-Scope rules, CPU leaky-bucket model, daily caps, 2026-03-19 8 QPS cap diff --git a/plugins/s1-secops-skills/skills/sdl-api/SKILL.md b/plugins/s1-secops-skills/skills/sdl-api/SKILL.md index 1a5e84e..7533c5a 100644 --- a/plugins/s1-secops-skills/skills/sdl-api/SKILL.md +++ b/plugins/s1-secops-skills/skills/sdl-api/SKILL.md @@ -1,7 +1,7 @@ --- name: sdl-api author: Prithvi Moses -description: Use whenever the user wants to read data and manage configuration through the SentinelOne Singularity Data Lake (SDL) API: run queries or manage configuration files (parsers, dashboards, alerts, lookups, datatables) on a Scalyr/SDL/XDR tenant. Trigger on "SDL", "SDL API", "Singularity Data Lake", "Scalyr", "DataSet", or any "*.sentinelone.net/sdl/api/*" URL, and on the method names "query", "powerQuery", "facetQuery", "timeseriesQuery", "numericQuery", "getFile", "putFile", "listFiles". Also trigger on tasks like "run a powerQuery", "list configuration files", "edit my parser via API", "deploy a dashboard JSON", "compute the rate of failures over time", or anything involving SDL Bearer-token auth or the S1-Scope header. Wraps every SDL method with a Python client and CLI. +description: Use whenever the user wants to read data and manage configuration through the SentinelOne Singularity Data Lake (SDL) API: run queries or manage configuration files (parsers, dashboards, alerts, lookups, datatables) on a Scalyr/SDL/XDR tenant. Trigger on "SDL", "SDL API", "Singularity Data Lake", "Scalyr", "DataSet", or any "*.sentinelone.net/sdl/api/*" URL, and on the method names "query", "powerQuery", "facetQuery", "timeseriesQuery", "numericQuery", "configFiles", "configFile", "addConfigFile", "deleteConfigFile", "getFile", "putFile", "listFiles". Also trigger on "udoId", "config file", "/sdl/v2/graphql", or a console display string of the form "/dashboards/id/{number}/{name}". Also trigger on tasks like "run a powerQuery", "list configuration files", "edit my parser via API", "deploy a dashboard JSON", "compute the rate of failures over time", or anything involving SDL Bearer-token auth or the S1-Scope header. Wraps every SDL method with a Python client and CLI. --- # SentinelOne SDL API @@ -25,7 +25,54 @@ The query methods on this skill (`query`, `powerQuery`, `facetQuery`, `timeserie | PowerQuery programmatically (any range) | **`mgmt-console-api`** → LRQ at `POST /sdl/v2/api/queries` on console host | | Dashboard panel queries | SDL console renders them in-browser: no API needed | | Quick one-off stats under 24h (deprecated) | V1 methods on this skill still work until 2027-02-15 | -| `get_file` / `put_file` / `list_files` (parsers, dashboards, lookups) | **This skill** | +| `get_file` / `put_file` / `list_files` (parsers, dashboards, lookups) | **This skill**, via GraphQL, see below | + +## STOP: config files are GraphQL, not the REST `/api/*File` endpoints + +`POST /sdl/v2/graphql` is the canonical config-file surface. The legacy REST endpoints +(`/api/listFiles`, `/api/getFile`, `/api/putFile`) are **incomplete** and must not be used to +decide whether a file exists. + +Measured live on `usea1-purple`: REST `listFiles` returned **1,914** paths, GraphQL +`configFiles` returned **2,264**. The entire 350-file gap is `/dashboards/` files that carry a +`udoId`, and REST `getFile` on any of them returns `success/noSuchFile`. + +**Tripwire, non-negotiable.** If a file is not found by name, or a listing count disagrees with +what the console shows, do **not** conclude the file is absent. The REST listing is incomplete by +design. Re-check with `configFiles` before reporting "not found". A count in the 1,900s when the +console says 2,200-plus means you used the wrong surface. + +### The `udoId` rule + +The console's Configuration Files grid displays a dashboard as: + +```text +/dashboards/id/6554761743556608/AI Usage + ^^^^^^^^^^^^^^^^ this is the udoId, NOT a path segment +``` + +That display string is not a path. Reading it as one returns `no file exists at path`. Pass +`6554761743556608` as `udoId`; the file's real `name` is `/dashboards/AI Usage`. + +`udoId` assignment is by **namespace** (verified live): only `/dashboards/` files get one. +`/lookups/`, `/datatables/`, `/logParsers/` and `/automaticLookups` all return `udoId: null` and +are addressed by name. + +| Namespace | Address by | Write by name updates in place? | +|---|---|---| +| `/dashboards/` | `udoId` | **No, it creates a duplicate** | +| `/lookups/`, `/datatables/`, `/logParsers/`, `/automaticLookups` | `name` | Yes | + +### Never write a dashboard by name + +`addConfigFile(name:)` **updates in place** for a name-addressed file but **creates a duplicate** +for a dashboard. Create a dashboard by name once (there is no `udoId` yet), then address it by +`udoId` forever after. Skipping this is how one tenant accumulated **152 copies** of +`/dashboards/AI Usage` and 256 surplus dashboard files overall. + +`expectedVersion` is enforced on **both** address forms; a stale value is rejected with +"There are conflicting changes in the file." and the stored content is left untouched. A +`deleteConfigFile` returning `null` with no `errors` array is **success**, not failure. ## Setup: configure credentials first @@ -33,7 +80,7 @@ Drop a `credentials.json` file directly into your Cowork project folder with the ```json { - "S1_CONSOLE_API_TOKEN": "eyJ...your-token...", + "S1_CONSOLE_API_TOKEN": "eyJ...your-token..." } ``` @@ -53,8 +100,8 @@ Before running anything, confirm `S1_CONSOLE_URL` and `S1_CONSOLE_API_TOKEN` are When the user asks for something involving the SDL API: -1. **Pick the method.** Check `references/methods.md` for the right call. For **configuration files** (`get_file`, `put_file`, `list_files`), this skill is the right tool. Raw-log ingestion is via HEC (see `mgmt-console-api`). For **queries**, use the V1 methods on this skill only for quick one-off stats under 24h; for anything programmatic or multi-day, switch to the **`mgmt-console-api`** skill and the LRQ API, LRQ is NOT available on the SDL config/query surface. -2. **Use the client.** `from sdl_client import SDLClient` then call the named method (`query`, `power_query`, `facet_query`, `timeseries_query`, `numeric_query`, `list_files`, `get_file`, `put_file`). The client picks the correct key, handles JSON encoding, retries 429/5xx/`error/server/backoff`, and returns parsed JSON. Note: `query` and `power_query` hit the deprecated V1 endpoints; they work until 2027-02-15 for quick lookups but should not be used for production query pipelines. +1. **Pick the method.** Check `references/methods.md` for the right call. For **configuration files**, this skill is the right tool, but use the **GraphQL** methods (`config_files`, `config_file`, `put_config_file`, `delete_config_file`), not the legacy REST ones, see the STOP section above and `references/config-file-graphql.md`. Raw-log ingestion is via HEC (see `mgmt-console-api`). For **queries**, use the V1 methods on this skill only for quick one-off stats under 24h; for anything programmatic or multi-day, switch to the **`mgmt-console-api`** skill and the LRQ API, LRQ is NOT available on the SDL config/query surface. +2. **Use the client.** `from sdl_client import SDLClient` then call the named method (`query`, `power_query`, `facet_query`, `timeseries_query`, `numeric_query`, `config_files`, `config_file`, `put_config_file`, `delete_config_file`). The client picks the correct key, handles JSON encoding, retries 429/5xx/`error/server/backoff`, and returns parsed JSON. Note: `query` and `power_query` hit the deprecated V1 endpoints; they work until 2027-02-15 for quick lookups but should not be used for production query pipelines. 3. **For ad-hoc shots, use the CLI.** `python scripts/sdl_cli.py [args]`. The CLI mirrors the client. 4. **Summarize for the user.** Don't dump raw JSON unless asked. For query results, prefer a concise table or CSV; for ingestion, confirm `bytesCharged` and the session ID; for config files, show path + version + (truncated) content. @@ -189,14 +236,22 @@ ts = c.timeseries_query(queries=[ {"filter": "serverHost contains 'frontend'", "function": "count", "startTime": "1h", "buckets": 60} ]) -# ---- Configuration files ---- +# ---- Configuration files (GraphQL) ---- # Parsers live under /logParsers/: the SDL API also accepts /parsers/ -# but the Log Parsers UI only reads /logParsers/, so PUTs at /parsers/ are invisible +# but the Log Parsers UI only reads /logParsers/, so writes at /parsers/ are invisible # in the console. Use /logParsers/ by default. -files = c.list_files() # {"status":"success","paths":["/foo", ...]} -parser = c.get_file("/logParsers/MyParser") # {"status":"success","content":"...","version":7,...} -c.put_file("/logParsers/MyParser", content="// new parser body") -c.put_file("/logParsers/Stale", delete=True) +files = c.config_files() # [{"udoId":..., "name":"/foo", "version":7}, ...] + +# Name-addressed files (/logParsers/, /lookups/, /datatables/, /automaticLookups) +parser = c.config_file(name="/logParsers/MyParser") # {"name":..., "content":"...", "version":7, ...} +c.put_config_file(name="/logParsers/MyParser", content="// new parser body", + expected_version=parser["version"]) +c.delete_config_file(name="/logParsers/Stale", expected_version=7) + +# Dashboards are addressed by udoId: a name-addressed write creates a duplicate +dash = c.config_file(udo_id="96200328708096") +c.put_config_file(udo_id=dash["udoId"], content=new_dashboard_json, + expected_version=dash["version"]) ``` ## Authentication @@ -221,10 +276,11 @@ For long-running ingest, use the binary truncated exponential backoff loop in `r ## Destructive actions: confirm first -`put_file(delete=True)` and `put_file(content=...)` overwriting an existing path can wipe a parser, dashboard, alert, or lookup table. Before any `putFile` write or delete: +`delete_config_file(...)` and `put_config_file(content=...)` overwriting an existing file can wipe a parser, dashboard, alert, or lookup table. Before any config-file write or delete: -- Run `get_file` first to read current `version` and content. Pass that version as `expected_version` on the write to fail-fast on a concurrent edit (`error/client/versionMismatch`). -- For deletes, summarise the path and last-modified date and get explicit confirmation. +- Run `config_file(...)` first to read current `version` and content. Pass that version as `expected_version` on the write to fail-fast on a concurrent edit; a stale value is rejected with "There are conflicting changes in the file." on both address forms. +- Address a dashboard by `udo_id`. A name-addressed write to an existing `/dashboards/` file creates a duplicate rather than updating it. +- For deletes, summarise the file name (and `udoId` for a dashboard) and get explicit confirmation. A `delete_config_file` returning `null` with no `errors` array is success. - Keep a backup in the working directory before overwriting non-trivial parsers or dashboards. There is no undo. Configuration files are versioned but accidental deletes still take effect immediately. @@ -232,8 +288,8 @@ There is no undo. Configuration files are versioned but accidental deletes still ## Common high-value workflows - **Hunt with PowerQuery.** Use the **`mgmt-console-api`** skill, which holds the LRQ runner at `POST /sdl/v2/api/queries` on your console host. LRQ is NOT reachable via the SDL API (`xdr.us1.sentinelone.net`). This skill's `c.power_query()` hits the deprecated V1 endpoint and should only be used for a quick ad-hoc one-off before 2027-02-15. -- **Promote a parser/dashboard.** `get_file("/logParsers/Foo")` from staging → `put_file("/logParsers/Foo", content=..., expected_version=N)` on production. The `expected_version` guard catches concurrent edits. (Parser path is `/logParsers/`, `/parsers/` is API-accepted but not UI-visible.) -- **Audit configuration drift.** `list_files()` then `get_file()` for each path; diff against a checked-in copy. +- **Promote a parser/dashboard.** `config_file(name="/logParsers/Foo")` from staging → `put_config_file(name="/logParsers/Foo", content=..., expected_version=N)` on production. The `expected_version` guard catches concurrent edits. (Parser path is `/logParsers/`, `/parsers/` is API-accepted but not UI-visible.) Promote a dashboard by `udo_id` on the target tenant, creating it by name only the first time. +- **Audit configuration drift.** `config_files()` then `config_file(name=...)` for each name-addressed file and `config_file(udo_id=...)` for each dashboard; diff against a checked-in copy. - **Quick stats panel.** `facet_query(field="srcIp", filter="status >= 500", start_time="1h")` returns the top offenders fast. For complex hunts and detection authoring use the `powerquery` skill for the query body, then call `c.power_query()` from this skill to execute it. For Mgmt Console resources (agents, threats, sites) use `mgmt-console-api`. @@ -259,3 +315,11 @@ This is not a credential issue. Do not widen time windows or change query logic - **Singularity Threat Intelligence IOC matching, ingest requirements:** for a HEC-ingested OCSF event to be eligible for a TI match it must (1) carry `metadata.version` with any non-empty value, this is the only "is OCSF" check the TI engine performs, it does NOT validate the full OCSF schema; (2) be ingested with `dataSource.category=security` (see above); and (3) carry the IOC value in the OCSF field the engine matches on, for an IP IOC that is `src_endpoint.ip` (also checks `dst_endpoint.ip`). A match generates a NEW event with `dataSource.name='Threat Intelligence'` and `metadata.labels[0]='s1_threat_intelligence_indicator'`; the event Name is the source data-source name. Matching is asynchronous, allow several minutes before querying for the match log. - **Backdating:** a top-level `time` field in **epoch SECONDS** backdates the event; a nested `event.time` (ms) does NOT. `isParsed=true` indexes the JSON keys directly as top-level attributes, so the field names you ingest are the field names you query (source-agnostic, no OCSF mapping needed). - **Query-time timestamp:** on HEC `isParsed` events `event.time` is NOT populated; the queryable event time is `timestamp` (epoch NANOSECONDS). Use `timestamp` for `newest()/oldest()`, `strftime()` hour-of-day, and time math; convert to ms with `number(ts)/1000000`. +- **⚠ Back-dated events ALSO produce receive-time "shadow" copies. Known platform defect, do not go looking for a bug in your ingest code.** A POST with a back-dated top-level `time` indexes correctly at the requested timestamp AND writes one or more extra copies stamped at ingest time. The shadows carry the URL-supplied `dataSource.name` / `dataSource.vendor` but NOT the JSON body fields, so they are invisible to `field = *` filters and show up only in unfiltered counts. Full reproduction and impact analysis: `SUPPORT_TICKET_HEC_shadow_copies.md` at the repo root (observed 2026-07-29, re-confirmed 2026-08-09). + + Consequences when building synthetic test data: + - Any count over a window that spans "now" is inflated. Volume baselines, ingest-health analytics and dashboards summing across the ingest moment double-count. + - A source you deliberately left EMPTY in the live window will not read as empty, so a detection requiring zero live events (e.g. a SILENT / anti-join watchdog) cannot be validated this way. + - Scale seen: a source sent 430 back-dated events and nothing live read ~500 events in the trailing 24h. It is not proportional to batch size and is unaffected by payload shape. + - **Already eliminated as causes, do not re-test these:** envelope vs flat payload, `isParsed` on/off, chunk size (100 vs 500), run-tag field name, timestamp recency, and same-source vs cross-source concurrency. All four payload forms behave identically. + - Workaround for assertions: tag every event you post and count with ` = *`, which excludes shadows because they carry no body fields. Only use unfiltered counts when you specifically need what a DETECTION sees, and expect them to be inflated. diff --git a/plugins/s1-secops-skills/skills/sdl-api/references/auth_and_limits.md b/plugins/s1-secops-skills/skills/sdl-api/references/auth_and_limits.md index e5d4079..0aab5fd 100644 --- a/plugins/s1-secops-skills/skills/sdl-api/references/auth_and_limits.md +++ b/plugins/s1-secops-skills/skills/sdl-api/references/auth_and_limits.md @@ -36,6 +36,7 @@ The `SDLClient` only sets `S1-Scope` when (a) a console token was selected for t - Each call returns `cpuUsage` (ms) on success: that's how much it cost. - The bucket leaks at `cpuUsageRefillRate` CPU sec/sec. When `cpuUsageCapacity >= 1`, queries are rejected until it drains. - A 429 carries: + ```json { "rateLimit": { @@ -48,6 +49,7 @@ The `SDLClient` only sets `S1-Scope` when (a) a console token was selected for t "status": "error/server/backoff" } ``` + - Headers also surface state: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-RefillRate`. `priority: low` (the default) gets a more generous bucket than `priority: high`. Audit trail: search `tag='audit' cpuUsage=*` for query audit events. @@ -65,6 +67,7 @@ Non-query operations: - Aggregate request bytes per operation: starting budget **30 MB**, refill **4 MB/s**. Usage Metering datasource calls (`| datasource "metering"` for `tenants` / `reports` / `report_name`) have their own cap: **50 requests/sec with a 100-request burst**, and require the `Metering Reports - View` permission. See `methods.md` → Usage Metering reports. + - **12 concurrent requests** max from the same API key. ### Ingestion (moved to HEC) @@ -74,6 +77,7 @@ Raw-log/event ingestion is no longer part of this skill; use the HEC ingest path ## Retry strategy The SDLClient retries automatically on: + - HTTP 429 - HTTP 5xx - HTTP 200 with body `status` starting `error/server/backoff` diff --git a/plugins/s1-secops-skills/skills/sdl-api/references/config-file-graphql.md b/plugins/s1-secops-skills/skills/sdl-api/references/config-file-graphql.md new file mode 100644 index 0000000..dfbb3c8 --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-api/references/config-file-graphql.md @@ -0,0 +1,187 @@ +# SDL Config File API (GraphQL) + +GraphQL API for reading and writing SDL configuration files: dashboards, parsers, lookups, +datatables, automatic lookups, monitors, alerts, and partition rules. + +This is the **canonical** config-file surface. The legacy REST endpoints (`/sdl/api/listFiles`, +`getFile`, `putFile`) are incomplete, see "Why not REST" below. + +Every claim here was verified live against `usea1-purple` on 2026-08-07. + +## Endpoint and authentication + +```text +POST https:///sdl/v2/graphql +``` + +Two headers, and only two: + +```text +Authorization: Bearer +Content-Type: application/json +``` + +| Detail | Value | +|---|---| +| Auth scheme | `Bearer`. `ApiToken` returns `unauthenticated`. | +| Token | A standard console API token, already a JWT. | +| `?opname=` and `?requestId=` | Optional, correlation only. The server routes on the request body. | +| `s1-scope` header | Not required. It is ignored, not rejected, so sending one is harmless. | + +Errors come back as **HTTP 200 with an `errors` array**. Check `errors`, not the status code. + +## Why not REST + +| | REST `/sdl/api/*File` | GraphQL `configFiles` | +|---|---|---| +| Files listed (live tenant) | 1,914 | 2,264 | +| Dashboards visible | 1,160 | 1,510 | +| udoId-addressed dashboards | **0**, `getFile` returns `success/noSuchFile` | 350 | +| Plain files (lookups, parsers, datatables, automaticLookups) | Works | Works | + +The 350-file gap is entirely udoId-addressed dashboards. REST cannot see or modify any of them, +which makes a REST listing unsafe for any "does this file exist" decision. + +**Tripwire.** If a file is not found by name, or a listing count disagrees with the console, +re-check with `configFiles` before reporting "not found". + +## The `udoId` rule + +The console displays a dashboard in its Configuration Files grid as: + +```text +/dashboards/id/6554761743556608/AI Usage + ^^^^^^^^^^^^^^^^ this is the udoId +``` + +That display string is **not a path**. Reading it as one returns `no file exists at path`. Pass +`6554761743556608` as `udoId`; the file's real `name` is `/dashboards/AI Usage`. + +`udoId` is assigned by **namespace**, not by creation method: + +| Namespace | `udoId` on create | Address by | +|---|---|---| +| `/dashboards/` | assigned | `udoId` | +| `/lookups/` | `null` | `name` | +| `/datatables/` | `null` | `name` | +| `/logParsers/` | `null` | `name` | +| `/automaticLookups` | `null` | `name` | + +## Operations + +| Operation | Type | Address by | Returns | +|---|---|---|---| +| `configFiles` | query | n/a | array of `{udoId, name, readOnly, version}` | +| `configFile` | query | `id` or `udoId` | one file including `content` | +| `addConfigFile` | mutation | `name` to create, `udoId` to update | `{udoId, name, version}` | +| `deleteConfigFile` | mutation | `id` or `udoId` | `null` on success | +| `formatConfigFile` | mutation | `id` or `udoId` | formatted string | + +### List + +```bash +curl -sS -X POST "https://$S1_CONSOLE/sdl/v2/graphql?opname=getConfigurationFiles" \ + -H "Content-Type: application/json" -H "Authorization: Bearer $S1_TOKEN" \ + -d '{"query":"query getConfigurationFiles { configFiles { udoId name readOnly version } }"}' +``` + +`version` is required for any later update or delete. Cache it. + +### Read + +By name for plain files, by `udoId` for dashboards: + +```json +{ + "query": "query f($udoId: ID!) { configFile(udoId: $udoId) { udoId name content version } }", + "variables": { "udoId": "96200328708096" } +} +``` + +`content` is HJSON: unquoted keys and relaxed commas are both accepted. + +**Absence is reported as an error, not as a null result**, and the text differs by address form: + +| Address form | Message when the file does not exist | +|---|---| +| by `name` / `id` | `Config file with name /x/y not found.` | +| by `udoId` | `Something went wrong. Please try again and if the issue persists contact Support.` | + +The `udoId` message is generic and is **also what a version conflict returns**, so absence cannot +be decided from the message alone on that form. Disambiguate against `configFiles`: absent from +the listing means gone, present means the error was real and must propagate. A transport failure +whose body happens to contain "not found" must never be treated as absence. `SDLClient.config_file()` +and the `sdl_get_file` tool handle all of this and return `None` / a `notFound` status. + +### Create, update, delete + +```json +{ + "query": "mutation f($name: String, $content: String!) { addConfigFile(name: $name, content: $content) { udoId name version } }", + "variables": { "name": "/dashboards/Alert Volume", "content": "{\"graphs\":[]}" } +} +``` + +Update addresses by `udoId` and passes the current `version` as `expectedVersion`. Delete uses +`deleteConfigFile` with the same address form. + +**A delete returning `null` with no `errors` array is SUCCESS.** The deleted object is not echoed +back. Treating that null as a failure is the most common mistake against this operation. + +## Write semantics: the duplicate trap + +`addConfigFile(name:)` behaves differently by namespace. Both branches verified live: + +| Target | `addConfigFile(name:)` on an existing file | +|---|---| +| `/lookups/`, `/datatables/`, `/logParsers/` | **Updates in place.** One file, new version. | +| `/dashboards/` | **Creates a duplicate.** Two files sharing the name, two `udoId`s. | + +So: create a dashboard by name once (no `udoId` exists yet), then address it by `udoId` forever +after. + +This is not theoretical. On the live tenant, 1,510 dashboard files carry only 1,254 distinct +names, **256 surplus copies**, including **152 copies of `/dashboards/AI Usage`**, 25 of +`EDR Data Collection Analysis`, and 21 of `FIM for PCI DSS v4.0.1 Compliance`. Every duplicated +name is a stock template dashboard; hand-authored names have none. Each install of a template +creates another file, so a shared tenant accumulates copies over time. + +`expectedVersion` is honoured on **both** address forms. A stale value is rejected with +`{"errors":[{"message":"There are conflicting changes in the file."}],"data":{"addConfigFile":null}}` +and the stored content is left untouched (verified 2026-08-07 on a name-addressed +`/datatables/` write). Always pass it on an update; omitting it is last-write-wins. + +## Client + +Use `SDLClient` from `scripts/sdl_client.py`: + +```python +from sdl_client import SDLClient + +c = SDLClient() + +files = c.config_files() # all 2,264, including dashboards +dash = [f for f in files if f["name"].startswith("/dashboards/")] + +# read a dashboard the REST surface cannot see +d = c.config_file(udo_id="96200328708096") + +# safe dashboard update +c.put_config_file(udo_id=d["udoId"], content=new_json, expected_version=d["version"]) + +# plain files address by name +c.put_config_file(name="/lookups/assets.csv", content="k,v\na,1\n") + +c.delete_config_file(udo_id=d["udoId"], expected_version=d["version"]) +``` + +`put_config_file` refuses a name-addressed write to an existing `/dashboards/` file and tells you +which `udoId`s already hold that name. + +## Credentials + +The console API token alone covers every SDL operation. The scoped SDL keys +(`SDL_CONFIG_READ_KEY`, `SDL_CONFIG_WRITE_KEY`, `SDL_LOG_READ_KEY`, `SDL_LOG_WRITE_KEY`) are +retired and are not used by this skill. Note that auth scheme differs by surface: `Bearer` for +`/sdl/v2/graphql`, `/sdl/api/*` and HEC; `ApiToken` for the Management API at +`/web/api/v2.1/*`. diff --git a/plugins/s1-secops-skills/skills/sdl-api/references/lrq-api.md b/plugins/s1-secops-skills/skills/sdl-api/references/lrq-api.md index cd6de95..d20461b 100644 --- a/plugins/s1-secops-skills/skills/sdl-api/references/lrq-api.md +++ b/plugins/s1-secops-skills/skills/sdl-api/references/lrq-api.md @@ -4,7 +4,7 @@ The LRQ API is the **default** programmatic path for every PowerQuery this skill ## Endpoints (all on the tenant's own console host) -``` +```text POST https://.sentinelone.net/sdl/v2/api/queries GET https://.sentinelone.net/sdl/v2/api/queries/{id}?lastStepSeen=N DELETE https://.sentinelone.net/sdl/v2/api/queries/{id} @@ -14,13 +14,13 @@ The console host is tenant-specific (for example `your-tenant.sentinelone.net`), ## Auth: Bearer, not ApiToken -``` +```text Authorization: Bearer ``` The JWT is the **same** console service-user token used by the Mgmt API; only the prefix changes. Calling `/sdl/v2/api/queries` with `Authorization: ApiToken ` returns HTTP 500: -``` +```text "Header must start with Bearer, but actually starts with \"ApiTok\"" ``` @@ -70,7 +70,7 @@ Service user tokens are preferred over personal user tokens because the per-user ## Polling -``` +```text GET /sdl/v2/api/queries/{id}?lastStepSeen= Headers: Authorization: Bearer @@ -83,7 +83,7 @@ Done when `stepsCompleted >= stepsTotal` and `stepsTotal > 0`. `data.values` is ## Cancel -``` +```text DELETE /sdl/v2/api/queries/{id} Headers: Authorization: Bearer @@ -105,13 +105,13 @@ Each API call (POST, GET, DELETE) counts as one request. A single slice with 5s On most SentinelOne tenants, the default scope carries a mix of SentinelOne EDR telemetry and Scalyr/infra logs. If you want EDR events (Process Creation, File Creation, Module Load, etc.), prepend this to the query: -``` +```text dataSource.name='SentinelOne' dataSource.category='security' ``` or equivalently: -``` +```text i.scheme="edr" ``` diff --git a/plugins/s1-secops-skills/skills/sdl-api/references/methods.md b/plugins/s1-secops-skills/skills/sdl-api/references/methods.md index bcec4e7..3caec3a 100644 --- a/plugins/s1-secops-skills/skills/sdl-api/references/methods.md +++ b/plugins/s1-secops-skills/skills/sdl-api/references/methods.md @@ -29,6 +29,7 @@ All five methods consume the CPU leaky bucket described in `auth_and_limits.md`. Event search. Filter syntax matches the UI search bar. Params: + - `filter` (string): search expression (e.g. `status >= 400 status < 500`). Escape `"` with `\"` when embedding in JSON. - `startTime` / `endTime`: UI time syntax (`"1h"`, `"24h"`, `"10/27 4 PM"`) or epoch sec/ms/ns. `startTime` inclusive, `endTime` exclusive. Omit both for past 24h. - `maxCount`: 1..5000, default 100. @@ -92,7 +93,7 @@ Usage Metering (cost / usage / MSSP chargeback) is reached through the `datasour - **Rate limit:** metering datasource calls are capped at **50 rps with a 100-request burst** (see `auth_and_limits.md`). - **Drilldown filter:** append normal PQ to post-process the returned rows: -``` +```text | datasource "metering" from "server_endpoints" | filter endpoint_bundle in ('Core', 'Complete') ``` @@ -162,6 +163,7 @@ Response: Effectively superseded by `timeseriesQuery` with `createSummaries=false` and `onlyUseSummaries=false`. Keep it for two reasons: + 1. Users whose role cannot call timeseriesQuery can still call this. 2. Sub-30-second bucket granularity (timeseries min is 30s). @@ -180,6 +182,17 @@ lookups, datatables. Paths look like `/logParsers/Foo`, `/dashboards/Bar`, `/alerts`, etc. Parsers specifically must use `/logParsers/`; the API also accepts `/parsers/` but the Log Parsers UI reads only `/logParsers/`. +> **Warning: `listFiles`, `getFile` and `putFile` are legacy and incomplete.** +> These three REST methods omit every udoId-addressed `/dashboards/` file. +> Measured live on `usea1-purple`: REST `listFiles` returned **1,914** paths +> against GraphQL `configFiles`' **2,264**, and REST `getFile` on any file in +> that 350-file gap returns `success/noSuchFile`. **A `noSuchFile` response is +> not proof that a file is absent.** Use the GraphQL surface for all +> configuration-file work: `configFiles`, `configFile`, `addConfigFile` and +> `deleteConfigFile` at `POST /sdl/v2/graphql`, exposed on the client +> as `config_files()`, `config_file()`, `put_config_file()` and +> `delete_config_file()`. Full reference: `references/config-file-graphql.md`. + ### `listFiles`: `c.list_files()`, CLI `list-files` No params. Returns `{"status":"success","paths":["/a","/b/c","/z"]}` sorted @@ -215,6 +228,7 @@ from the preceding `getFile` for optimistic concurrency; a mismatch returns Response on success: `{"status":"success"}`. -Requires Configuration Write Access key (or console token with config-write -permission). Console tokens are permitted but scope-aware, `S1-Scope` header -may be required. +Authorisation is `S1_CONSOLE_API_TOKEN`, sent as `Authorization: Bearer `. +The scoped SDL keys are retired; the console token covers every SDL operation. +Console tokens are scope-aware, so the `S1-Scope` header may be required on the +REST endpoints. diff --git a/plugins/s1-secops-skills/skills/sdl-api/scripts/sdl_cli.py b/plugins/s1-secops-skills/skills/sdl-api/scripts/sdl_cli.py index 0b47752..13de71a 100644 --- a/plugins/s1-secops-skills/skills/sdl-api/scripts/sdl_cli.py +++ b/plugins/s1-secops-skills/skills/sdl-api/scripts/sdl_cli.py @@ -53,7 +53,55 @@ def _print(obj) -> None: print(json.dumps(obj, indent=2, default=str)) +def cmd_config_files(c, args): + """Complete listing via GraphQL, including udoId-addressed dashboards.""" + files = c.config_files() + if args.prefix: + files = [f for f in files if (f.get("name") or "").startswith(args.prefix)] + _print({"count": len(files), "files": files}) + + +def cmd_config_file(c, args): + if not args.name and not args.udo_id: + raise SystemExit("config-file requires --name or --udo-id") + res = c.config_file(name=args.name, udo_id=args.udo_id) + if res is None: + raise SystemExit( + "No file at that address. If this is a dashboard it is udoId-addressed: " + "run 'config-files --prefix /dashboards/' and retry with --udo-id." + ) + _print(res) + + +def cmd_put_config_file(c, args): + if args.content is not None: + content = args.content + elif args.content_file: + content = Path(args.content_file).read_text() + else: + raise SystemExit("put-config-file requires --content or --content-file") + _print( + c.put_config_file( + name=args.name, + udo_id=args.udo_id, + content=content, + expected_version=args.expected_version, + ) + ) + + +def cmd_delete_config_file(c, args): + if not args.name and not args.udo_id: + raise SystemExit("delete-config-file requires --name or --udo-id") + _print( + c.delete_config_file( + name=args.name, udo_id=args.udo_id, expected_version=args.expected_version + ) + ) + + def cmd_list_files(c, args): + """Legacy REST listing. Incomplete: omits udoId-addressed dashboards.""" _print(c.list_files()) @@ -144,8 +192,36 @@ def build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser(prog="sdl_cli", description="SentinelOne SDL API CLI") sub = p.add_subparsers(dest="cmd", required=True) - # list-files - sp = sub.add_parser("list-files", help="List configuration file paths") + # ---- configuration files (GraphQL, canonical) ---- + sp = sub.add_parser( + "config-files", help="List every configuration file (complete; includes dashboards)" + ) + sp.add_argument("--prefix", default=None, help='Filter, e.g. "/dashboards/"') + sp.set_defaults(func=cmd_config_files) + + sp = sub.add_parser("config-file", help="Read a configuration file by name or udoId") + sp.add_argument("--name", default=None, help="Path for name-addressed files") + sp.add_argument("--udo-id", default=None, help="udoId, required for /dashboards/ files") + sp.set_defaults(func=cmd_config_file) + + sp = sub.add_parser("put-config-file", help="Create or update a configuration file") + sp.add_argument("--name", default=None, help="Path; use to create, or to update a non-dashboard file") + sp.add_argument("--udo-id", default=None, help="udoId; required to update an existing dashboard") + sp.add_argument("--content", default=None) + sp.add_argument("--content-file", default=None) + sp.add_argument("--expected-version", type=int, default=None) + sp.set_defaults(func=cmd_put_config_file) + + sp = sub.add_parser("delete-config-file", help="Delete a configuration file") + sp.add_argument("--name", default=None) + sp.add_argument("--udo-id", default=None) + sp.add_argument("--expected-version", type=int, default=None) + sp.set_defaults(func=cmd_delete_config_file) + + # ---- legacy REST config methods (incomplete; omit udoId dashboards) ---- + sp = sub.add_parser( + "list-files", help="[legacy] REST listing; omits udoId-addressed dashboards" + ) sp.set_defaults(func=cmd_list_files) # get-file diff --git a/plugins/s1-secops-skills/skills/sdl-api/scripts/sdl_client.py b/plugins/s1-secops-skills/skills/sdl-api/scripts/sdl_client.py index c3101e7..7d7b3a3 100644 --- a/plugins/s1-secops-skills/skills/sdl-api/scripts/sdl_client.py +++ b/plugins/s1-secops-skills/skills/sdl-api/scripts/sdl_client.py @@ -42,10 +42,10 @@ c.query(filter="*", max_count=5, start_time="5m") # config files - c.list_files() - c.get_file("/alerts") + c.config_files() + c.config_file(name="/alerts") # Parsers: use /logParsers/; /parsers/ is API-accepted but invisible in the UI. - c.put_file("/logParsers/MyParser", content="// parser body") + c.put_config_file(name="/logParsers/MyParser", content="// parser body") The client retries 429 and 5xx with exponential backoff and honours Retry-After. All responses are returned as parsed JSON dicts. Errors @@ -56,8 +56,10 @@ import json import os +import re import time import uuid +from urllib.parse import quote from pathlib import Path from typing import Any, Dict, Iterable, List, Optional, Union @@ -157,10 +159,23 @@ def _walk_up_for_workspace_creds() -> Optional[Path]: class SDLAPIError(RuntimeError): - def __init__(self, status: int, message: str, body: Any = None): + def __init__(self, status: int, message: str, body: Any = None, graphql: bool = False): super().__init__(f"HTTP {status}: {message}") self.status = status self.body = body + # True when the failure came from the GraphQL layer rather than the + # transport. Absence detection keys off this. + self.graphql = graphql + + +def _normalise_name(n: Any) -> str: + """SDL config names are case-insensitive and tolerate stray whitespace, so + the absence check and the duplicate guard must normalise identically.""" + return str(n or "").strip().lower() + + +def _matches_name(file_obj: Dict[str, Any], name: Any) -> bool: + return _normalise_name((file_obj or {}).get("name")) == _normalise_name(name) class SandboxProxyBlockedError(RuntimeError): @@ -337,7 +352,16 @@ def _request( extra_headers: Optional[Dict[str, str]] = None, content_type: str = "application/json", retries: int = 3, + allow_retry: Optional[bool] = None, ) -> Dict[str, Any]: + # Status-based retry is restricted to idempotent methods, mirroring + # lib/sdl.js. A 5xx received after the server committed a write would + # otherwise be re-sent, and a re-sent addConfigFile(name:) against + # /dashboards/ creates a duplicate. Read-only POSTs opt in explicitly. + method_retryable = ( + allow_retry if allow_retry is not None + else method.upper() in ("GET", "HEAD") + ) if not path.startswith("/"): path = "/" + path url = self.base_url + path @@ -383,11 +407,13 @@ def _request( ) if status < 400 and not (isinstance(sdl_status, str) and sdl_status.startswith("error/")): return body - if retryable and attempt <= retries: + if retryable and method_retryable and attempt <= retries: wait = min(2 ** attempt, 30) ra = resp.headers.get("Retry-After") if ra and ra.isdigit(): - wait = int(ra) + # Cap the server-supplied delay: an unbounded Retry-After + # would park the process (observed guidance: cap at 30s). + wait = min(int(ra), 30) time.sleep(wait) continue # non-retryable, non-auth failure @@ -546,10 +572,274 @@ def power_query( return self._request("POST", "/api/powerQuery", json_body=body) # ========================================================================= - # Configuration files + # Configuration files (GraphQL, canonical) + # ========================================================================= + # + # POST /sdl/v2/graphql is the canonical config-file surface and a strict + # superset of the REST /api/*File endpoints below. Measured live: REST + # listFiles returned 1,914 paths, configFiles returned 2,264. The 350-file + # gap is entirely udoId-addressed /dashboards/ files, and REST getFile on + # any of them returns 'success/noSuchFile'. + # + # udoId is assigned by namespace: only /dashboards/ files have one. + # /lookups/, /datatables/, /logParsers/ and /automaticLookups are + # name-addressed with udoId None. + + _CONFIG_FIELDS = "udoId name readOnly version" + + def _graphql( + self, + opname: str, + query: str, + variables: Optional[Dict[str, Any]] = None, + read_only: bool = False, + ) -> Dict[str, Any]: + """POST /v2/graphql. Raises SDLAPIError on the GraphQL `errors` array. + + GraphQL failures return HTTP 200 with an `errors` array, so the status + code alone never tells you a call failed. Three shapes must all raise + rather than fall through to an empty result, since every caller's + "nothing here" default is indistinguishable from a real empty answer: + a non-dict body (a proxy or SSO interstitial served as text), an + `errors` value that is not a list, and a body carrying neither `data` + nor `errors`. + """ + body: Dict[str, Any] = {"query": query} + if variables: + body["variables"] = variables + payload = self._request( + "POST", + f"/v2/graphql?opname={quote(str(opname), safe='')}", + json_body=body, + allow_retry=read_only, + ) + if not isinstance(payload, dict): + raise SDLAPIError( + 200, + f"SDL GraphQL {opname}: expected a JSON object, got " + f"{type(payload).__name__}. This usually means a proxy or auth " + f"interstitial answered instead of the API. " + f"First 200 chars: {str(payload)[:200]}", + payload, + graphql=True, + ) + errors = payload.get("errors") + if errors: + errs = errors if isinstance(errors, list) else [errors] + first = errs[0] if errs else {} + message = (first or {}).get("message", "unknown GraphQL error") + correlation = (payload.get("extensions") or {}).get("correlationId") or ( + (first or {}).get("extensions") or {} + ).get("correlationId") + if correlation: + message = f"{message} (correlationId={correlation})" + raise SDLAPIError(200, f"SDL GraphQL {opname}: {message}", payload, graphql=True) + if "data" not in payload: + raise SDLAPIError( + 200, + f"SDL GraphQL {opname}: response carried neither data nor errors.", + payload, + graphql=True, + ) + return payload.get("data") or {} + + def config_files(self) -> List[Dict[str, Any]]: + """Every config file on the tenant, including udoId-addressed dashboards. + + Prefer this over `list_files()`, which omits them. + """ + data = self._graphql( + "getConfigurationFiles", + f"query getConfigurationFiles {{ configFiles {{ {self._CONFIG_FIELDS} }} }}", + read_only=True, + ) + return data.get("configFiles") or [] + + def config_file( + self, + name: Optional[str] = None, + udo_id: Optional[str] = None, + ) -> Optional[Dict[str, Any]]: + """Read one config file by name (plain files) or udo_id (dashboards). + Returns None when the file does not exist. + + Absence is a normal outcome of a lookup, but the server reports it as a + GraphQL error whose message differs by address form (verified live): + + by name : "Config file with name /x/y not found." -> explicit + by udo_id: "Something went wrong. Please try again..." -> generic, and + the SAME text a version conflict returns, so it cannot be + trusted on message alone. + + The explicit form is normalised directly; the ambiguous one is + disambiguated against the file listing. The extra listing only happens + on the error path. + """ + if not name and not udo_id: + raise ValueError("config_file requires either name or udo_id") + try: + if udo_id: + data = self._graphql( + "configFile", + f"query f($udoId: ID!) {{ configFile(udoId: $udoId) {{ {self._CONFIG_FIELDS} content }} }}", + {"udoId": str(udo_id)}, + read_only=True, + ) + else: + data = self._graphql( + "configFile", + f"query f($id: ID!) {{ configFile(id: $id) {{ {self._CONFIG_FIELDS} content }} }}", + {"id": name}, + read_only=True, + ) + except SDLAPIError as exc: + # Only a GraphQL-layer error can mean "absent". A transport failure + # whose body happens to contain "not found" must never be read as + # absence: that is how a delete gets confirmed against a file that + # was never checked. + if not getattr(exc, "graphql", False): + raise + if re.search(r"config file with (name|id) .* not found", str(exc), re.I): + return None + # The udo_id form returns a generic message that a version conflict + # also returns, so settle it against the listing. If the listing + # itself fails, keep the ORIGINAL error. + try: + all_files = self.config_files() + except SDLAPIError as list_exc: + raise SDLAPIError( + exc.status, + f"{exc} (absence check failed: {list_exc})", + exc.body, + ) from exc + if udo_id: + present = any(str(f.get("udoId")) == str(udo_id) for f in all_files) + else: + present = any(_matches_name(f, name) for f in all_files) + if not present: + return None + raise + return data.get("configFile") + + def put_config_file( + self, + name: Optional[str] = None, + udo_id: Optional[str] = None, + content: str = "", + expected_version: Optional[int] = None, + ) -> Optional[Dict[str, Any]]: + """Create or update a config file. + + Pass `udo_id` to update a dashboard in place; pass `name` to create, or + to update any name-addressed file. + + A name-addressed write UPDATES IN PLACE for plain files but CREATES A + DUPLICATE for /dashboards/. This method refuses a name-addressed write + to an existing dashboard for that reason; one tenant reached 152 copies + of '/dashboards/AI Usage' that way. + """ + if not name and not udo_id: + raise ValueError("put_config_file requires either name or udo_id") + if not udo_id and _normalise_name(name).startswith("/dashboards/"): + all_files = self.config_files() + if not all_files: + raise ValueError( + f'Refusing to write "{name}" by name: the configFiles listing came ' + "back empty, so the duplicate check could not run. Retry, or pass " + "an explicit udo_id." + ) + existing = [f for f in all_files if _matches_name(f, name)] + if existing: + ids = ", ".join(str(f.get("udoId")) for f in existing if f.get("udoId")) + raise ValueError( + f'Refusing to write "{name}" by name: {len(existing)} dashboard(s) ' + "already use that name, and a name-addressed write to /dashboards/ " + "creates another duplicate rather than updating. Pass one of these " + f"udoIds instead: {ids or '(none)'}." + ) + if udo_id: + data = self._graphql( + "addConfigFile", + "mutation f($udoId: ID, $content: String!, $expectedVersion: Long) " + "{ addConfigFile(udoId: $udoId, content: $content, expectedVersion: $expectedVersion) " + f"{{ {self._CONFIG_FIELDS} }} }}", + { + "udoId": str(udo_id), + "content": content, + "expectedVersion": expected_version, + }, + ) + else: + # expectedVersion is enforced on name-addressed writes too: a stale + # value is rejected with "There are conflicting changes in the file." + # Omitting it silently downgrades every parser, lookup, datatable and + # /automaticLookups update to last-write-wins. + data = self._graphql( + "addConfigFile", + "mutation f($name: String, $content: String!, $expectedVersion: Long) " + "{ addConfigFile(name: $name, content: $content, expectedVersion: $expectedVersion) " + f"{{ {self._CONFIG_FIELDS} }} }}", + { + "name": name, + "content": content, + "expectedVersion": expected_version, + }, + ) + return data.get("addConfigFile") + + def delete_config_file( + self, + name: Optional[str] = None, + udo_id: Optional[str] = None, + expected_version: Optional[int] = None, + ) -> Dict[str, Any]: + """Delete a config file. Dashboards by udo_id, everything else by name. + + The mutation returns null on success and does not echo the deleted + object, so its response cannot distinguish "deleted" from "matched + nothing". Confirm by re-reading, and raise if the file survives. + """ + if not name and not udo_id: + raise ValueError("delete_config_file requires either name or udo_id") + if udo_id: + raw = self._graphql( + "deleteConfigFile", + "mutation f($udoId: ID, $expectedVersion: Long) " + "{ deleteConfigFile(udoId: $udoId, expectedVersion: $expectedVersion) { udoId } }", + {"udoId": str(udo_id), "expectedVersion": expected_version}, + ) + else: + raw = self._graphql( + "deleteConfigFile", + "mutation f($id: ID, $expectedVersion: Long) " + "{ deleteConfigFile(id: $id, expectedVersion: $expectedVersion) { udoId } }", + {"id": name, "expectedVersion": expected_version}, + ) + still = self.config_file(name=name, udo_id=udo_id) + if still: + target = f"udoId {udo_id}" if udo_id else name + raise SDLAPIError( + 200, + f"delete_config_file: {target} still exists after the delete mutation " + f"(version {still.get('version')}). The mutation reported no errors " + "but nothing was removed.", + still, + ) + return { + "status": "success", + "deleted": {"udoId": str(udo_id)} if udo_id else {"name": name}, + "raw": (raw or {}).get("deleteConfigFile"), + } + + # ========================================================================= + # Configuration files (legacy REST) # ========================================================================= def list_files(self) -> Dict[str, Any]: - """POST /api/listFiles: list every configuration file path.""" + """POST /api/listFiles. INCOMPLETE: omits udoId-addressed dashboards. + + Prefer `config_files()`. Never use this listing to decide whether a + file exists. + """ return self._request("POST", "/api/listFiles", json_body={}) def get_file( @@ -650,4 +940,4 @@ def iter_query( if __name__ == "__main__": # Smoke test: list configuration files. c = SDLClient() - print(json.dumps(c.list_files(), indent=2)[:2000]) + print(json.dumps(c.config_files(), indent=2)[:2000]) diff --git a/plugins/s1-secops-skills/skills/sdl-api/tests/smoke_test.py b/plugins/s1-secops-skills/skills/sdl-api/tests/smoke_test.py index d8bc587..dce4a08 100644 --- a/plugins/s1-secops-skills/skills/sdl-api/tests/smoke_test.py +++ b/plugins/s1-secops-skills/skills/sdl-api/tests/smoke_test.py @@ -114,38 +114,73 @@ def main(): start_time="1h", )) - # --------------------------- CONFIG READ --------------------------------- - list_res = _run("listFiles", lambda: c.list_files()) - - pick_path = None - if isinstance(list_res, dict): - paths = list_res.get("paths") or [] - # prefer a /logParsers path for getFile; else the first one - parsers = [p for p in paths if p.startswith("/logParsers/")] - pick_path = (parsers or paths or [None])[0] - - if pick_path: - _run(f"getFile ({pick_path[:30]})", lambda: c.get_file(pick_path, prettyprint=False)) - else: - print("[SKIP] getFile; no files present to read") - RESULTS.append(("getFile", "SKIP", 0, "no files", "tenant has no config files yet")) - - # --------------------------- CONFIG WRITE -------------------------------- - # Create, read, update, delete a harmless lookup file. - create_body = '{"keys": {"a": "1"}}' # simple JSON lookup - - put_create = _run("putFile create", lambda: c.put_file(test_path, content=create_body)) - - # read back to capture version for expectedVersion - read_back = _run("getFile (created)", lambda: c.get_file(test_path)) - v = None - if isinstance(read_back, dict): - v = read_back.get("version") - - update_body = '{"keys": {"a": "1", "b": "2"}}' - _run("putFile update", lambda: c.put_file(test_path, content=update_body, expected_version=v)) - - _run("putFile delete", lambda: c.put_file(test_path, delete=True)) + # --------------------------- CONFIG READ (GraphQL) ----------------------- + # configFiles is the canonical listing. The legacy REST listFiles is checked + # too, but only to assert it is the NARROWER of the two: if REST ever equals + # or exceeds GraphQL, either the tenant has no dashboards or this test is + # pointed at the wrong surface, and the udoId guidance needs re-verifying. + files = _run("configFiles (GraphQL)", lambda: c.config_files()) + rest = _run("listFiles (legacy REST)", lambda: c.list_files()) + + if isinstance(files, list) and isinstance(rest, dict): + gql_n, rest_n = len(files), len(rest.get("paths") or []) + dashboards = [f for f in files if (f.get("name") or "").startswith("/dashboards/")] + udo = [f for f in dashboards if f.get("udoId")] + ok = rest_n <= gql_n + RESULTS.append(( + "GraphQL listing is a superset of REST", "PASS" if ok else "FAIL", 0, + f"graphql={gql_n} rest={rest_n} dashboards={len(dashboards)} udoId-addressed={len(udo)}", + "" if ok else "REST returned at least as many paths as GraphQL; investigate before trusting either.", + )) + print(f"[{'PASS' if ok else 'FAIL'}] GraphQL listing is a superset of REST" + f" graphql={gql_n} rest={rest_n} udoId-addressed={len(udo)}") + + # Read a udoId-addressed dashboard: the exact class of file REST cannot see. + if udo: + sample = udo[0] + _run(f"configFile by udoId ({str(sample['udoId'])[:16]})", + lambda: c.config_file(udo_id=sample["udoId"])) + else: + print("[SKIP] configFile by udoId; tenant has no udoId-addressed dashboards") + RESULTS.append(("configFile by udoId", "SKIP", 0, "none present", "")) + + # --------------------------- CONFIG WRITE (GraphQL) ---------------------- + # Create, read, update (with optimistic locking), delete a harmless file. + _run("put_config_file create", lambda: c.put_config_file(name=test_path, content='{"keys": {"a": "1"}}')) + + read_back = _run("config_file (created)", lambda: c.config_file(name=test_path)) + v = read_back.get("version") if isinstance(read_back, dict) else None + + _run("put_config_file update (expectedVersion)", + lambda: c.put_config_file(name=test_path, content='{"keys": {"a": "1", "b": "2"}}', + expected_version=v)) + + # expectedVersion must be enforced on name-addressed writes, not merely accepted. + # Re-using the now-stale v must be rejected; if it succeeds, optimistic + # locking is silently a no-op and concurrent edits will be lost. + stale_rejected = False + try: + c.put_config_file(name=test_path, content='{"keys": {"stale": true}}', expected_version=v) + except Exception: + stale_rejected = True + RESULTS.append(( + "stale expectedVersion is rejected", "PASS" if stale_rejected else "FAIL", 0, + "conflict raised" if stale_rejected else "stale write ACCEPTED", + "" if stale_rejected else "Optimistic locking is not being enforced on name-addressed writes.", + )) + print(f"[{'PASS' if stale_rejected else 'FAIL'}] stale expectedVersion is rejected") + + cur = c.config_file(name=test_path) + _run("delete_config_file", lambda: c.delete_config_file(name=test_path, + expected_version=cur.get("version"))) + + gone = not [f for f in c.config_files() if f.get("name") == test_path] + RESULTS.append(( + "deleted file is gone from configFiles", "PASS" if gone else "FAIL", 0, + "absent" if gone else "STILL PRESENT", + "" if gone else "delete_config_file reported success but the file survived.", + )) + print(f"[{'PASS' if gone else 'FAIL'}] deleted file is gone from configFiles") # --------------------------- SUMMARY ------------------------------------- print("-" * 80) diff --git a/plugins/s1-secops-skills/skills/sdl-api/tests/test_client.py b/plugins/s1-secops-skills/skills/sdl-api/tests/test_client.py new file mode 100644 index 0000000..786a55a --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-api/tests/test_client.py @@ -0,0 +1,236 @@ +"""Unit tests for SDLClient's GraphQL config-file layer. + +These stub `requests.request`, so they need no tenant and no credentials. +`smoke_test.py` covers the live path; this file covers the branches that only +appear when the server misbehaves, which is where every defect in this module +has actually been found. + +Run: python3 -m pytest sdl-api/tests/test_client.py -q + or: python3 sdl-api/tests/test_client.py +""" + +from __future__ import annotations + +import json +import os +import sys +import unittest +from pathlib import Path +from unittest import mock + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts")) + +os.environ.setdefault("S1_CONSOLE_URL", "https://tenant.sentinelone.net") +os.environ.setdefault("S1_CONSOLE_API_TOKEN", "test-token") + +from sdl_client import SDLClient, SDLAPIError # noqa: E402 + +# Fail loudly rather than silently reaching a real tenant if a stub ever misses. +os.environ["S1_CONSOLE_URL"] = "https://unit-test.invalid" + + +class FakeResponse: + """Mimics the requests.Response surface the client actually touches: + status_code, headers, text, content and json().""" + + def __init__(self, status=200, body=None, headers=None): + self.status_code = status + self._body = body if body is not None else {} + self.headers = headers or {} + self.text = self._body if isinstance(self._body, str) else json.dumps(self._body) + self.content = self.text.encode() + + def json(self): + if isinstance(self._body, str): + raise ValueError("not json") + return self._body + + +def client_with(responses): + """Return (client, calls). `responses` is consumed in order.""" + calls = [] + c = SDLClient() + + def fake_request(method, url, **kw): + calls.append({"method": method, "url": url, "json": kw.get("json"), + "headers": kw.get("headers", {})}) + if not responses: + raise AssertionError("ran out of queued responses") + return responses.pop(0) + + # The client issues every call through self.session.request, so that is the + # only seam. Patching anything else silently lets the tests hit a live tenant. + c.session = mock.Mock() + c.session.request = fake_request + return c, calls + + +def gql(data): + return FakeResponse(200, {"data": data}) + + +def gql_err(message): + return FakeResponse(200, {"errors": [{"message": message}]}) + + +class AbsenceHandling(unittest.TestCase): + def test_explicit_not_found_returns_none(self): + c, _ = client_with([gql_err("Config file with name /lookups/x.csv not found.")]) + self.assertIsNone(c.config_file(name="/lookups/x.csv")) + + def test_generic_error_disambiguated_by_listing(self): + # A deleted udoId returns the generic message, which a version conflict + # also returns. Absent from the listing means genuinely gone. + c, _ = client_with([ + gql_err("Something went wrong. Please try again and if the issue persists contact Support."), + gql({"configFiles": [{"udoId": "999", "name": "/dashboards/Other", "version": 1}]}), + ]) + self.assertIsNone(c.config_file(udo_id="5")) + + def test_generic_error_rethrows_when_file_still_present(self): + c, _ = client_with([ + gql_err("Something went wrong. Please try again."), + gql({"configFiles": [{"udoId": "5", "name": "/dashboards/Here", "version": 1}]}), + ]) + with self.assertRaises(SDLAPIError): + c.config_file(udo_id="5") + + def test_transport_error_is_not_treated_as_absence(self): + # A 404 page whose body contains "not found" must not be read as + # "the file does not exist"; that would let a delete confirm itself. + c, _ = client_with([FakeResponse(404, {"error": "not found"})]) + with self.assertRaises(SDLAPIError): + c.config_file(name="/lookups/x.csv") + + def test_non_json_200_raises(self): + c, _ = client_with([FakeResponse(200, "SSO interstitial")]) + with self.assertRaises(SDLAPIError): + c.config_files() + + +class RetryPolicy(unittest.TestCase): + def test_mutations_are_not_retried_on_5xx(self): + # A retried addConfigFile(name:) against /dashboards/ creates a duplicate. + c, calls = client_with([ + gql({"configFiles": [{"udoId": "1", "name": "/dashboards/Other", "version": 1}]}), + FakeResponse(502, "bad gateway"), + ]) + with self.assertRaises(SDLAPIError): + c.put_config_file(name="/dashboards/New", content="{}") + mutations = [k for k in calls if "mutation" in (k["json"] or {}).get("query", "")] + self.assertEqual(len(mutations), 1, "the mutation must be sent exactly once") + + def test_read_only_queries_are_retried(self): + c, calls = client_with([ + FakeResponse(503, "unavailable"), + gql({"configFiles": [{"udoId": None, "name": "/lookups/a.csv", "version": 1}]}), + ]) + with mock.patch("time.sleep"): + files = c.config_files() + self.assertEqual(len(files), 1) + self.assertEqual(len(calls), 2, "the query should have been retried once") + + def test_retry_after_is_capped(self): + c, _ = client_with([ + FakeResponse(429, "slow down", headers={"Retry-After": "3600"}), + gql({"configFiles": []}), + ]) + with mock.patch("time.sleep") as slept: + c.config_files() + self.assertLessEqual(slept.call_args[0][0], 30, + "an unbounded Retry-After would park the process") + + +class DuplicateGuard(unittest.TestCase): + def test_blocks_name_write_to_existing_dashboard(self): + c, _ = client_with([ + gql({"configFiles": [{"udoId": "777", "name": "/dashboards/AI Usage", "version": 1}]}), + ]) + with self.assertRaises(ValueError) as ctx: + c.put_config_file(name="/dashboards/AI Usage", content="{}") + self.assertIn("777", str(ctx.exception)) + + def test_case_variant_path_does_not_bypass_the_guard(self): + c, _ = client_with([ + gql({"configFiles": [{"udoId": "777", "name": "/dashboards/AI Usage", "version": 1}]}), + ]) + with self.assertRaises(ValueError): + c.put_config_file(name="/Dashboards/AI Usage", content="{}") + + def test_fails_closed_on_empty_listing(self): + c, _ = client_with([gql({"configFiles": []})]) + with self.assertRaises(ValueError) as ctx: + c.put_config_file(name="/dashboards/Anything", content="{}") + self.assertIn("empty", str(ctx.exception)) + + def test_create_of_a_new_dashboard_is_allowed(self): + c, _ = client_with([ + gql({"configFiles": [{"udoId": "1", "name": "/dashboards/Other", "version": 1}]}), + gql({"addConfigFile": {"udoId": "888", "name": "/dashboards/New", "version": 1}}), + ]) + self.assertEqual(c.put_config_file(name="/dashboards/New", content="{}")["udoId"], "888") + + def test_non_dashboard_namespaces_are_not_guarded(self): + c, calls = client_with([ + gql({"addConfigFile": {"udoId": None, "name": "/lookups/a.csv", "version": 2}}), + ]) + c.put_config_file(name="/lookups/a.csv", content="k,v\n") + self.assertEqual(len(calls), 1, "no listing should be fetched for a non-dashboard write") + + +class ExpectedVersion(unittest.TestCase): + def test_sent_on_name_addressed_writes(self): + c, calls = client_with([ + gql({"addConfigFile": {"udoId": None, "name": "/logParsers/P", "version": 2}}), + ]) + c.put_config_file(name="/logParsers/P", content="x", expected_version=1168977232) + sent = calls[0]["json"] + self.assertIn("$expectedVersion: Long", sent["query"]) + self.assertEqual(sent["variables"]["expectedVersion"], 1168977232) + + def test_sent_on_udoid_addressed_writes(self): + c, calls = client_with([ + gql({"addConfigFile": {"udoId": "5", "name": "/dashboards/D", "version": 2}}), + ]) + c.put_config_file(udo_id="5", content="x", expected_version=99) + self.assertEqual(calls[0]["json"]["variables"]["expectedVersion"], 99) + + +class DeleteVerification(unittest.TestCase): + def test_throws_when_the_file_survives(self): + c, _ = client_with([ + gql({"deleteConfigFile": None}), + gql({"configFile": {"udoId": "5", "name": "/dashboards/D", "version": 9}}), + ]) + with self.assertRaises(SDLAPIError) as ctx: + c.delete_config_file(udo_id="5") + self.assertIn("still exists", str(ctx.exception)) + + def test_success_once_absent(self): + c, _ = client_with([ + gql({"deleteConfigFile": None}), + gql_err("Config file with name /lookups/x.csv not found."), + ]) + res = c.delete_config_file(name="/lookups/x.csv") + self.assertEqual(res["status"], "success") + + def test_transport_error_does_not_produce_a_false_success(self): + c, _ = client_with([ + gql({"deleteConfigFile": None}), + FakeResponse(404, "nginx: not found"), + ]) + with self.assertRaises(SDLAPIError): + c.delete_config_file(name="/lookups/x.csv") + + +class Injection(unittest.TestCase): + def test_caller_values_travel_in_variables_not_the_document(self): + c, calls = client_with([gql({"configFile": None})]) + hostile = '/lookups/a"}) { evil }' + c.config_file(name=hostile) + self.assertNotIn("evil", calls[0]["json"]["query"]) + self.assertEqual(calls[0]["json"]["variables"]["id"], hostile) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/README.md b/plugins/s1-secops-skills/skills/sdl-dashboard/README.md index 38733f8..a4541ac 100644 --- a/plugins/s1-secops-skills/skills/sdl-dashboard/README.md +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/README.md @@ -22,7 +22,7 @@ Or install the full plugin (recommended) to get all the SentinelOne SecOps skill This skill has no Python client of its own: dashboards are authored as JSON and deployed via the `sdl-api` skill's `put_file` method. Use alongside: -- **`sdl-api`**: to deploy the dashboard JSON to your SDL tenant (`put_file /dashboards/`) +- **`sdl-api`**: to deploy the dashboard JSON to your SDL tenant (`put_config_file`, by name on first create, by `udo_id` thereafter) - **`powerquery`**: to validate and compose the queries inside panels before embedding them ## What this skill does diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/SKILL.md b/plugins/s1-secops-skills/skills/sdl-dashboard/SKILL.md index 6b757cc..9fcb334 100644 --- a/plugins/s1-secops-skills/skills/sdl-dashboard/SKILL.md +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/SKILL.md @@ -25,7 +25,7 @@ This workflow is mandatory for every new or modified dashboard. Steps 0, 1, and 2. **Design the structure**: Choose tabs (if multi-topic), then panels per tab. Match panel type to the data shape using the guide in `references/panel-type-cheatsheet.md`. Key decisions: flows/kill-chains → `sankey`; KPI vs SLA target → `bullet`; SOC queue health → `gauge`; 3D outlier detection → `scattered_bubble`; time-based density → `heatmap`; multiple queries in one panel → tabbed table. Where one `event.type` covers multiple semantic populations (delivery-time vs click-time, scheduled vs on-demand, inbound vs outbound), build separate sections per population, not a mixed section. 3. **Write the JSON**: Use the panel type reference below and real examples in `references/community-examples.md`. Compute explicit `x`/`y`/`w`/`h` for every panel. Apply the naming-hygiene rule from **Panel naming hygiene** so titles read as SLA-grade claims. 4. **Validate queries**: Sample 3-5 events per source/event-ID to confirm field semantics. Test each panel query via the `powerquery` skill. Run the parallel load test (see **Pre-deploy validation**), acceptance thresholds: slowest panel ≤ 2s, wall-clock ≤ 5s. Run `scripts/panel_safety_check.py` against the dashboard JSON; resolve every flag before deploy. -5. **Deploy**: Use the `sdl-api` skill to `put_file` to a path like `/dashboards/my-dashboard` with `expected_version` set from a prior `get_file` (CAS guard). Save a backup of the prior JSON first. Sleep 3s, then `get_file` to verify the version bumped AND grep the returned content for a canary string from your change. +5. **Deploy**: Use the `sdl-api` skill's GraphQL methods. First deploy only: `put_config_file(name="/dashboards/my-dashboard", content=...)`, then record the returned `udoId`. Every deploy after that: `config_file(udo_id=...)` for the current `version`, then `put_config_file(udo_id=..., content=..., expected_version=...)` as the CAS guard. A name-addressed write to an existing dashboard is refused because it duplicates. Save a backup of the prior JSON first. Sleep 3s, then re-read by `udo_id` to verify the version bumped AND grep the returned content for a canary string from your change. 6. **Iterate**: Show the user what was built, explain each panel, offer to tweak. If the dashboard hangs, follow the escalation ladder in **Pre-deploy validation**. 7. **Log-evidence report (MANDATORY)**: Run `scripts/validate_dashboard.py` against the deployed dashboard JSON to replay every panel, persist per-panel evidence (sample rows, row count, matchCount, elapsed, errors) to a JSON, and emit a markdown evidence file. Then run `scripts/render_validation_pdf.py` to render the PDF report (cover, per-tab sections, sample-data tables, empty-result appendix with SOC-meaningful interpretations). Deliver both alongside the dashboard. A dashboard delivered without an evidence report is incomplete. @@ -114,80 +114,7 @@ Some sources emit multiple log subtypes under the same `event.type` (header logs ## PowerQuery feature gaps to design around -The patterns below produce HTTP 500s or silent renderer failures on current SDL builds. They appear syntactically valid in language references and may even work in a developer's local PowerQuery preview, but they are not safe inside dashboard JSON. Treat them as red flags during code review. `scripts/panel_safety_check.py` scans for them automatically. - -| Pattern | Failure mode | -|---|---| -| `\| let x = if(predicate, then, else)` then aggregating on `x` | 500 server error | -| `count_if(predicate)` / `countif(predicate)` aggregate functions | 500 server error | -| `sum(if(predicate, value, 0))` inside `\| group ... by ...` | 500 server error | -| `concat(field_a, ' literal ', field_b)` in `\| let` bindings | 500 server error | -| `\| union (subquery)` MID-PIPELINE (after any other command) | HTTP 400 (live-verified 2026-07-29). `union` as the FIRST command of the query, one subquery per row set, IS valid and works, including with commands after it (e.g. `\| sort`). Use union-first for funnels/synthetic rows; never mid-pipeline | -| `let totals = (... \| group ...)` named subquery before main pipeline | 500 server error | -| `\| parse //` with named captures and grouping in same query | 500 server error | -| `\| matches ''` with `\\s` / `\\d` escapes inside the regex literal | 500 server error | -| Anything after `\| transpose` (terminal command) | "transpose can only be used as the last command" | -| `graphStyle: "area"` panel with a `query` field (not `plots: [...]`) | Indefinite spinner, no error surfaced | -| Hyphenated arithmetic in ANY `let`, including z-scores / ratios: `(live-base)/sd`, `total-min` without spaces | "Identifier is ambiguous", the WHOLE panel fails with "Couldn't load content". Always write `(live - base) / sd` | -| `markdown` panel with `content:` field instead of `markdown:` | Renders blank tile, no error | -| `markdown` panel with NO `title` key (S-26.1) | Header renders "Untitled". Set a short plain-text `title`; prose only in `markdown` | -| Number panel `suffix` repeats the unit already in the `title` | Reads "34 principals" under title "Active principals". Put the unit in the title OR the suffix, not both | -| `graphStyle: "bar"` / `"line"` / `"area"` with a categorical (non-time) first column | "The first column of power query output should have numeric value in epoch s/ms/us/ns" error. For a category bar chart use `"stacked_bar"` with `"xAxis": "grouped_data"` and a `(category, value)` query | - -### Patterns that DO work and should be preferred - -| Pattern | Use case | -|---|---| -| `\| group n=count() \| limit 1` | Number panel | -| `\| group n=estimate_distinct() \| limit 1` | Cardinality number panel (HyperLogLog, fast) | -| `\| group =count() by , \| sort - \| limit N` | Top-N table | -| `\| group =count() by timestamp=timebucket(''), \| transpose on timestamp` | Time-series stacked-bar / line | -| `\| group =count() by , \| transpose on ` | Cross-tab / per-category × action stacked-bar | -| Long-format table: `\| group hits=count() by , \| sort , -hits \| limit N` | When you need both dims as columns and a wide table can't be produced | -| Index-level filter (before the first pipe) | Narrow scan to relevant events; cheaper than a post-pipe `\| filter` | -| `\| filter matches ''` for selective dim filtering | Works with simple character classes; avoid `\\s` / `\\d` escapes | - -### Two-pass parse for quoted KV values - -Network device logs (FortiGate, Palo Alto, etc.) emit KV pairs where values are wrapped in double quotes: `app="HTTPS.BROWSER" appcat="Web.Client"`. The `| parse` format string is itself double-quoted, so you cannot embed a `"` to match the wrappers. The workaround is two passes: - -**Pass 1**, capture the whole non-whitespace token including its surrounding quotes: - -```text -| parse "app=$raw_app{regex=\\S+}$" from message -``` - -This extracts `"HTTPS.BROWSER"` (with quotes) into `raw_app`. - -**Pass 2**, extract the clean value by matching the alphanumeric content, which skips the leading `"`: - -```text -| parse "$app_name{regex=[A-Z0-9./_-]+}$" from raw_app -``` - -This produces `HTTPS.BROWSER` (no quotes) in `app_name`. - -Full example for an app-ctrl panel: - -```text -dataSource.name='FortiGate' event.type='app-ctrl' -| parse "app=$raw_app{regex=\\S+}$" from message -| parse "appcat=$raw_cat{regex=\\S+}$" from message -| parse "$app_name{regex=[A-Z0-9./_-]+}$" from raw_app -| parse "$app_cat{regex=[A-Za-z0-9./_-]+}$" from raw_cat -| filter app_name != '' -| group Events=count() by Application=app_name, Category=app_cat -| sort -Events -| limit 20 -``` - -**Escaping in dashboard JSON:** `\\S+` in the PowerQuery string (what the engine sees) must be written as `\\\\S+` in the JSON source because JSON applies one level of backslash-escaping before PowerQuery sees the string. - -**Always invoke the `powerquery` skill before authoring parse expressions.** It references official parse documentation and gets to the correct pattern without trial-and-error. - -### Workaround for "I need totals AND breakdown in one panel" - -When `sum(if())` and `count_if()` fail, first try a union-FIRST query (one subquery per row set; live-verified 2026-07-29). If that does not fit, the cleanest substitute is two adjacent panels: one for totals, one for the per-action breakdown (long-format). Lay them side-by-side at half-width so they read as a single visual unit. Trying to force a single wide table with both columns generally requires one of the unsupported patterns above. +The catalog of PowerQuery patterns that 500 or render badly inside dashboard JSON, the safe patterns to prefer, the two-pass quoted-KV parse, and the totals-plus-breakdown workaround is in [`references/powerquery-gaps.md`](references/powerquery-gaps.md). `scripts/panel_safety_check.py` scans for the failing patterns automatically. --- @@ -298,300 +225,11 @@ A dashboard is a JSON object (SDL also accepts unquoted keys, JavaScript-literal | `options` | `{"layout": {"fixed": 1}}` to lock drag-and-drop | | `teamEmails` | Array of account emails whose data is pooled | -## Panel types and JSON - -Every panel is an object inside `graphs`. The `graphStyle` property picks the panel type. - -### Layout +## Panel types -Every panel **must** have explicit `x`, `y`, `w`, `h` in its `layout` object. Dashboards with many panels (observed at 18+) where `x`/`y` are omitted can hang the browser renderer indefinitely, the auto-layout pass appears to loop on collision detection when panels stack at the implicit (0,0) origin. The symptom is the browser tab becoming unresponsive before any query fires. - -```json -"layout": { "w": 30, "h": 14, "x": 0, "y": 0 } -``` +Every panel is an object inside `graphs`; the `graphStyle` property picks the panel type. Every panel also needs an explicit `layout` object with `x`, `y`, `w`, `h` (omitting them can hang the renderer). -Use this helper to pack panels into the 60-wide grid when generating JSON: - -```python -class Grid: - def __init__(self, width=60): - self.W = width; self.x = 0; self.y = 0; self.row_h = 0 - def place(self, w, h): - if self.x + w > self.W: - self.y += self.row_h; self.x = 0; self.row_h = 0 - layout = {"w": w, "h": h, "x": self.x, "y": self.y} - self.x += w; self.row_h = max(self.row_h, h) - return layout - def newline(self): - if self.x > 0: - self.y += self.row_h; self.x = 0; self.row_h = 0 -``` - ---- - -### Line / Area chart (time-series, multi-plot) - -`graphStyle`: `"line"` or `"area"` (or `"stacked"` for stacked area) - -Best for: event rates over time, multi-metric comparison, trend lines. - -```json -{ - "title": "Threat confidence over time", - "graphStyle": "area", - "lineSmoothing": "straightLines", - "yScale": "linear", - "plots": [ - { "filter": "event.category='indicators' indicator.category='Ransomware'", "label": "Ransomware", "facet": "count" }, - { "filter": "event.category='indicators' indicator.category='Exploitation'", "label": "Exploitation", "facet": "count" } - ] -} -``` - -For a **PowerQuery-driven** line chart (needed for complex grouping): - -```json -{ - "title": "Login attempts over time", - "graphStyle": "line", - "lineSmoothing": "straightLines", - "query": "event.login.loginIsSuccessful=false | group count() by timestamp=timebucket('1h'), endpoint.name | transpose endpoint.name on timestamp" -} -``` - ---- - -### Stacked bar chart - -`graphStyle`: `"stacked_bar"` or `"bar"` - -Best for: category breakdowns over time, per-group counts. - -```json -{ - "title": "Threats by confidence level per day", - "graphStyle": "stacked_bar", - "xAxis": "time", - "yScale": "linear", - "query": "index='activities' activity_type in ('18','19','20') | group count=count() by timestamp=timebucket('1 day'), data.confidence_level | transpose data.confidence_level on timestamp" -} -``` - -For a **grouped-data X-axis** (not time): - -```json -{ - "graphStyle": "stacked_bar", - "xAxis": "grouped_data", - "query": "event.category='indicators' | group count=count() by indicator.category | sort -count" -} -``` - ---- - -### Pie / Donut chart - -`graphStyle`: `"pie"` or `"donut"` - -Query **must return exactly one text column and one numeric column**. - -```json -{ - "title": "Top indicator types", - "graphStyle": "donut", - "maxPieSlices": 10, - "dataLabelType": "PERCENTAGE", - "query": "event.category='indicators' | group count() by indicator.category" -} -``` - ---- - -### Table panel - -`graphStyle`: `"table"` (or omit, table is the default for PowerQuery panels) - -Best for: raw event lists, top-N tables, IOC lookups. - -```json -{ - "title": "Outbound PowerShell connections", - "graphStyle": "table", - "query": "src.process.name contains 'powershell' dst.ip.address=* | let rfc1918 = not (dst.ip.address matches '((127\\..*)|(192\\.168\\..*)|(10\\..*)|(172\\.1[6-9]\\..*)|(172\\.2[0-9]\\..*)|(172\\.3[0-1]\\..*)).*') | filter rfc1918=true | group hits=count() by IP=dst.ip.address | sort -hits" -} -``` - ---- - -### Number panel (gauge) - -`graphStyle`: `"number"` - -Query must reduce to a single number (use `group count()`, `estimate_distinct()`, etc.). - -```json -{ - "title": "Distinct active endpoints", - "graphStyle": "number", - "query": "| group estimate_distinct(agent.uuid) | limit 1", - "options": { - "format": "auto", - "precision": "0", - "suffix": " endpoints" - } -} -``` - -> **No "millions" (or thousands) number format.** SDL number panels scale only via `format: "auto"` (which may render K/M/B); there is no explicit millions option. To force a specific unit, divide in the query and label the title: `... | group ev = count() | let Events_M = ev / 1000000 | columns Events_M | limit 1` with title `"Total Events (M)"`. Always carry the unit (GiB, events, min, count, ratio) in the title or `suffix`, a number panel renders only a bare value. -> -> **Options, stick to the minimal set.** Production reference dashboards only set `{format, precision, suffix}`. Fields like `backgroundColor` and `color` are documented in some places but are not consistently honoured by the renderer, at best silently ignored, at worst the panel renders blank or hangs. Do not add them until tested against the specific tenant. - -With trend indicator (S-25.1.5+): - -```json -{ - "graphStyle": "number", - "trendConfig": { - "enabled": true, - "indicators": { - "number": { "calculationType": "PERCENTAGE", "enabled": true }, - "arrow": { "enabled": true }, - "upwardsMeaning": "POSITIVE" - } - }, - "query": "...", - "title": "Alert volume (vs previous period)" -} -``` - ---- - -### Honeycomb panel (heat map) - -`graphStyle`: `"honeycomb"` - -Query must return at least one text column and one numeric column. Good for per-site or per-endpoint heatmaps. - -```json -{ - "title": "File creation activity by endpoint", - "graphStyle": "honeycomb", - "query": "src.process.tgtFileCreationCount=* | group total=sum(src.process.tgtFileCreationCount) by site=site.id, endpoint=agent.uuid | let max=overall_max(total), min=overall_min(total) | let normalized=((total-min)/(max-min))*100 | columns Site=site, Endpoint=endpoint, Normalized=normalized", - "honeyCombColor": { "hover": "#8ED4FB", "label": "Blue", "value": "#0998E7" }, - "honeyCombThresholds": ["0","25","50","75"], - "honeyCombGroupBy": "Site", - "honeyCombLinkTo": "/dash?page=Endpoints+-+Overview¶ms=site%3D[Site]%26endpoint%3D[Endpoint]" -} -``` - ---- - -### Heatmap panel (time OR categorical 2D matrix) - -`graphStyle`: `"heatmap"` - -A 2D matrix where color intensity is the aggregated value of each cell. As of S-26.x the x-axis can be **time OR a category**, and cells can carry **in-cell data labels**. Distinct from `honeycomb` (free-form hex cells). In every mode the **anchor column** (the field named after `on` in `transpose`) becomes the x-axis, and the transposed field's values become the y-axis rows. - -**Two x-axis modes:** - -| Mode | x-axis | Query shape | `xAxis` key | -|---|---|---|---| -| Time (classic) | timebuckets | `... \| group =count() by , timestamp=timebucket('1h') \| transpose on timestamp` | omit (or `"time"`) | -| Categorical | a category column | `... \| group =count() by , \| transpose on ` | `"grouped_data"` | - -**Heatmap options (live-confirmed on S-26.x, captured from what the SDL UI writes):** - -| Key | Value | Effect | -|---|---|---| -| `"xAxis"` | `"grouped_data"` | Render a non-time first column as a categorical x-axis. Without it the renderer expects an epoch first column, so a category query renders blank. | -| `"showDataLabels"` | `"true"` (string, not boolean) | Print each cell's value inside the cell. Omit for color-only. | -| `"colorScheme"` | `"red"` \| `"blue"` \| `"green"` (more in the UI) | Named color ramp; `red`, `blue`, `green` confirmed to render. | -| `"colorSchemeOrder"` | `"standard"` \| `"inverted"` | Direction of the ramp; `"inverted"` flips which end is the hot color. | -| `"linkConfig"` | `{ "template": "" }` | Make cells click-through to a URL, e.g. a pre-filtered Unified Alerts view. | - -**Categorical heatmap example** (alerts severity x product, data labels + click-through, live-validated): - -```json -{ - "title": "Alerts by severity and product", - "graphStyle": "heatmap", - "query": "dataSource.name='alert' severity_id=* finding_info.uid=* | group count=count() by Product=metadata.product.name, severity_id | transpose severity_id on Product", - "xAxis": "grouped_data", - "showDataLabels": "true", - "colorScheme": "red", - "colorSchemeOrder": "standard", - "numberOfRanges": 5, - "rangesCreation": "automatic", - "heatmapRangeConfig": ["-∞", "", "", "", "", "∞"], - "linkConfig": { "template": "https://.sentinelone.net/incidents/unified-alerts?viewType=all" }, - "layout": { "h": 20, "w": 30, "x": 0, "y": 0 } -} -``` - -**Time heatmap example** (classic, x-axis = time, requires `timebucket()` and the anchor column named `timestamp`): - -```json -{ - "title": "Identity Logon Activity by User [heatmap]", - "graphStyle": "heatmap", - "query": "dataSource.name='Identity' unmapped.type='Logon Success' user.name=* user.name != ''\n| group EventCount=count() by user_name=user.name, timestamp=timebucket('1h')\n| filter EventCount > 0\n| filter user_name in ('alice', 'bob', 'svc_adconnector', 'DC01$')\n| transpose user_name on timestamp", - "colorScheme": "red", - "colorSchemeOrder": "standard", - "numberOfRanges": 5, - "rangesCreation": "automatic", - "heatmapRangeConfig": ["-∞", "", "", "", "", "∞"], - "layout": { "h": 22, "w": 30, "x": 0, "y": 0 } -} -``` - -**Critical `heatmapRangeConfig` rule:** `rangesCreation: "automatic"` means SDL computes the threshold boundaries from the data. The `heatmapRangeConfig` array must use empty strings `""` for all middle elements. Providing explicit values (e.g. `"10"`, `"50"`) conflicts with automatic mode and causes the panel to render blank with no error. Correct form for 5 ranges: `["-∞", "", "", "", "", "∞"]` (N+1 elements for N ranges). Do not add explicit middle values unless you also change `rangesCreation` away from `"automatic"`. - -**Pre-filter to top-N categories before transpose:** After `transpose`, any (category, anchor) cell with no events becomes null. To keep the heatmap readable and avoid sparse null columns, use `| filter in (...)` to pin the transposed set to the most active values. Find candidates first: `| group count=count() by | sort -count | limit 15 | columns `. - -**When to use heatmap:** - -- Categorical x-axis: alerts by severity x product or asset category, detections by OS x technique, any (category x category) count matrix. -- Time x-axis: login activity per user over time (insider threat, off-hours spikes), hourly event volume across sources or endpoints, day-of-week x hour activity patterns. - ---- - -### Distribution graph - -`graphStyle`: `"distribution"` - -Shows frequency distribution of a numeric field (X = value range, Y = count). Use `filter` and `facet` (not `query`). - -```json -{ - "title": "Distribution of outbound destination ports", - "graphStyle": "distribution", - "filter": "event.network.direction='OUTGOING'", - "facet": "src.port.number" -} -``` - ---- - -### Markdown panel - -`graphStyle`: `"markdown"` - -Accepts GitHub-flavored Markdown. Good for section headers, links, or explanations. - -> **CRITICAL:** the body field is `markdown`, **not** `content`. A panel with -> `"content": "..."` is created successfully and renders as a **blank tile with -> no error**, the API accepts it, the UI just has nothing to display. Always -> use `"markdown": "..."`. -> -> **Title duplication:** the SDL UI renders the `"title"` field as a header above the panel body. Do NOT repeat the same heading inside the `"markdown"` body. A common mistake is setting `"title": "## Policy Enforcement"` (with the `##` markdown prefix) and then starting the markdown body with `## Policy Enforcement\nDescription...`; this produces the heading twice. Keep the `title` field as plain text and put only the descriptive prose (no repeated heading) inside `"markdown"`. Also: a markdown panel with NO `title` key renders an "Untitled" header in S-26.1 (observed live), so always set a short plain-text `title`. - -```json -{ - "title": "About this dashboard", - "graphStyle": "markdown", - "markdown": "This dashboard tracks **threat activity** across all managed endpoints.\n\n[Open Event Search](/logs)" -} -``` +The full per-panel JSON catalog (layout helper, line/area, stacked bar, pie/donut, table, number/gauge, honeycomb, heatmap, distribution, and markdown panels) is in [`references/panel-types.md`](references/panel-types.md), with a one-line-per-panel summary in [`references/panel-type-cheatsheet.md`](references/panel-type-cheatsheet.md). --- @@ -623,7 +261,7 @@ preemptively when authoring panels of these shapes. | Dashboard panel times out, indefinite spinner | A subquery inside the main query forces the engine to scan-and-aggregate twice. Dashboards rerun panels on every load, so the cost compounds. | Don't gate a panel query on a subquery if you can avoid it. Hardcode top-N values via inline OR clauses, or accept the full cardinality (often small after the initial filter). If a subquery is unavoidable, prefer a `lookup` against a precomputed datatable. | | Number panel slow on a busy index | Engine keeps scanning after the answer is computed | Always terminate number panels with `\| limit 1` after the `\| group` that reduces to one row | | Wide range + fine `timebucket` = thousands of points per series | E.g. `timebucket("10m")` over 7d = 1,008 points × N series | Match bucket to duration: 1d → `10m`, 7d → `1h` (minimum), 30d → `1 day` minimum | -| Two near-identical dashboards appear in *Configuration files* under `/dashboards/` and `/dashboards/id//` | The SDL UI's **Save** button writes to `/dashboards/id//`. `put_file("/dashboards/")` writes to the simpler path. Both render in the UI and both are visible to the file API; neither is access-controlled. | Pick one canonical path **before** the first deploy. Recommend the UI-native `/dashboards/id//` if the dashboard already exists in the UI; otherwise `/dashboards/`. Don't mix the two, each `put_file` to the alternate path creates a silent duplicate alongside the UI-saved copy. | +| Two or more near-identical dashboards share a name in *Configuration files* | `/dashboards/id//` is a display string, not a path; writing to it returns `no file exists at path`. Duplicates come from `addConfigFile(name:)` creating a copy instead of updating, so every name-addressed deploy adds one. A name-addressed copy (`udoId: null`) can also coexist with udoId-addressed ones. | Resolve the name with `sdl_list_files` (`pathPrefix: "/dashboards/"`) and address every update by `udoId`. Name-addressed writes are for the first create only; `sdl_put_file` refuses the rest. Delete surplus copies by `udoId`. | | `columns resources[0].name` or `vulnerabilities[0].cve.uid` returns HTTP 500 | PowerQuery does not accept bracket-array indexing in `columns`. The V1 query API exposes nested arrays as flattened keys (`resources[0].name`) for display, but those flattened keys are NOT valid PowerQuery field paths. | Use top-level scalar fields only (`severity_id`, `finding_info.title`, `metadata.product.name`, `class_name`, `time`). For first-element access inside a query, use `array_get(resources, 0).name` only inside `let`. For richer drill-down, switch from PowerQuery to the V1 query API (returns full event JSON); see `sdl-api` skill. | | `\| parse "app=$val$" from message` fails with "Start quote with no matching end quote" when the raw field value is wrapped in double quotes (e.g. `app="HTTPS.BROWSER"`) | The `\| parse` format string uses `"..."` as its outer delimiter. Any `"` character embedded in the format, to match quote-wrapped KV values common in network device logs, is treated as a string terminator. No escape sequence (backslash, single-quote outer, hex) works around this. | Use a two-pass parse: pass 1 captures the entire non-whitespace token including quotes (`{regex=\\S+}`), pass 2 extracts the clean value from that token. See **Two-pass parse for quoted KV values** below. | @@ -631,273 +269,31 @@ preemptively when authoring panels of these shapes. ## Parameters and filters (dynamic filtering) -SDL has two distinct filtering mechanisms that look similar but behave very differently depending on dashboard type. - -### `filters[]`: use this in TABBED dashboards (actually works) - -`filters[]` declared inside a tab object creates a live facet-based filter widget. Selecting a value from the dropdown applies that filter to **all panels in the tab** in real time. This is the correct filtering mechanism for `configType: "TABBED"` dashboards. Confirmed working. - -```json -{ - "tabName": "Investigation", - "filters": [ - { "facet": "metadata.product.name", "name": "Alert Product" }, - { "facet": "endpoint.name", "name": "Endpoint" } - ], - "graphs": [...] -} -``` - -The dropdown options are populated dynamically from live field values in the current time range. No query changes needed, SDL injects the filter automatically. - -### `#VarName#` substitution: works in FLAT and TABBED dashboards; refiltering applies on Search - -`#VarName#` query injection is confirmed working in flat dashboards (no `configType`, no `tabs`, top-level `parameters` and `graphs`; see `parameter_examples-v1.0.json`). - -On a `configType: "TABBED"` dashboard, live visual verification (2026-07-29): a tab-level `parameters` entry renders its dropdown, a panel query using `#VarName#` renders WITHOUT error with the `defaultValue` substituted, and selecting a dropdown value re-filters the panel to exactly that value once the user presses **Search** (the dropdown does not auto-refresh the view). Test panel: `dataSource.name=#SrcName#` with facet `dataSource.name`, defaultValue `"*"`. The earlier claim that TABBED passes the literal `#` and throws `Don't understand [#]` was wrong; remember the Search-to-apply gotcha when demoing. - -Flat dashboard example (the only context where `#VarName#` works): - -```json -{ - "parameters": [ - { - "name": "Specified Tag", - "values": [ - { "label": "All", "value": "*" }, - { "label": "Log Volumes", "value": "'logVolume'" } - ], - "defaultValue": "*" - } - ], - "graphs": [ - { - "query": "tag=#Specified Tag# | group count=count() by serverHost", - "title": "Count by Tag" - } - ] -} -``` - -Pre-quoting rule: if the field requires string matching, embed single quotes in the value string: `"'logVolume'"` so substitution produces `tag='logVolume'`. Use `"*"` (no inner quotes) for wildcard presence filter. - -### `parameters[]` in TABBED dashboards: UI-only chrome - -`parameters[]` declared inside a tab (with `facet` or `values`) renders a dropdown in the tab header but does NOT apply any filter to panel queries. It is purely decorative UI. The `filters[]` mechanism described above is the functional equivalent. - -```json -{ - "parameters": [ - { - "name": "Product", - "label": "Alert Product", - "values": [ - { "label": "STAR", "value": "STAR" }, - { "label": "CWS", "value": "CWS" }, - { "label": "EDR", "value": "EDR" } - ], - "defaultValue": "STAR" - } - ] -} -``` - -```json -{ - "parameters": [ - { - "name": "Site", - "label": "Site", - "facet": "s1_detection_metadata.site_name", - "defaultValue": "*" - } - ] -} -``` - -For user-friendly dropdown labels: - -```json -{ "name": "region", - "values": [ - { "label": "East Coast", "value": "us-east-1" }, - { "label": "West Coast", "value": "us-west-1" } - ] -} -``` - -Hide a parameter from the UI (declared but not displayed): - -```json -{ "name": "base_search", "options": { "display": "hidden" }, "defaultValue": "dataSource.name='MySource'" } -``` +SDL's two dynamic-filtering mechanisms, `filters[]` for TABBED dashboards and `#VarName#` substitution for flat and TABBED dashboards, plus the UI-only `parameters[]` behavior, are documented in [`references/parameters-and-filters.md`](references/parameters-and-filters.md). --- ## Common SDL data sources and event patterns -> ⚠️ **Schemas drift between sessions and tenants.** The patterns below are -> starting points, not a registry. **Run live schema discovery (V1 query via -> `sdl-api` skill) on every source you'll query before authoring -> panels.** PowerQuery's default projection is `timestamp + message` only; it -> cannot discover schemas. Use the V1 `query` method which returns full event -> JSON. - -### S1 internal SDL sources are OCSF-rich (NOT stubs) - -`dataSource.name` values `alert`, `vulnerability`, `misconfiguration`, `asset`, -`Identity`, and `ActivityFeed` carry **rich OCSF events**, not metadata -stubs. The fields that *look* like they should exist based on the source name -(`alert.severity`, `alert.classification`, `vulnerability.kevAvailable`, -`misconfiguration.severity`) frequently do NOT exist, the actual queryable -fields are OCSF-namespaced. - -| Source | OCSF class_uid | Severity field | Endpoint linkage | Notes | -|---|---|---|---|---| -| `alert` | 99602001 (S1 Security Alert) | `severity_id` (numeric 0-5) | `resources[].name`, `resources[].s1_metadata.site_name` (NOTE: `resources[N]` only readable via V1 query, not PowerQuery `columns`) | `finding_info.title` = alert name. `metadata.product.name` ∈ {STAR, EDR, Identity, CWS, EPP}. `class_name` = "S1 Security Alert" | -| `vulnerability` | 2002 (Vulnerability Finding) | `severity_id` + `severity_` (string, often empty) | `resource.s1_metadata.*`, `resource.uid` | `vulnerabilities[].cve.uid`, `vulnerabilities[].affected_packages[].{name,version,vendor_name}`. **No `kevAvailable` field in SDL**, KEV/EPSS metadata lives in the management console only | -| `misconfiguration` | 2003 (Compliance Finding) | `severity_id` | `resources[].s1_metadata.*` | `compliance.standards[]` (CIS_AKS, CIS_KUBERNETES, etc.), `compliance.requirements[]`, `policy.{name,uid,desc}`, `cloud.provider`, `finding_info.title` | -| `asset` | 3004 (Device Inventory) | `severity_id` + `severity_` | `device.agent.uuid`, `device.name`, `device.os.name` | 126 fields (live-confirmed). Rich endpoint inventory. Key fields: `device.agent.{uuid,version,network_status,network_status_title,is_active,is_decommissioned,is_uninstalled,network_quarantine_enabled,last_logged_in_user_name,scan_status}`, `device.os.{name,version,type}`, `device.ip_external`, `device.hw_info.*`, `device.network_interfaces[N].*`. `operation` = OPERATION_UPSERT. No `entity.uid`, no `entity_result.*`, no `agent.health.online` fields; use `device.agent.network_status` for connectivity state | -| `ActivityFeed` | n/a (Hyperautomation / mgmt activity audit) | n/a | `data.scope_id`, `site_id`, `account.id` | 41 fields (live-confirmed). Hyperautomation workflow execution audit log and management console activity log. Key fields: `activity_type` (numeric, e.g. 9207 = workflow execution event, NOT a string), `activity_uuid`, `primary_description`, `secondary_description`, `data.workflow_{id,name,execution_url}`, `data.{scope_id,scope_level,scope_name,site_name,user_id}`, `created_at`, `updated_at`, `context`. `sca:RetentionType = 'ACTIVITY_LOG'`. No `activityType` (camelCase) field. Not useful for threat hunting, use for Hyperautomation audit and compliance workflow tracking | -| `Identity` | 3002 (Authentication) | `severity_id`, `status_id` | `user.name`, `user.domain`, `src_endpoint.ip`, `dst_endpoint.hostname` | `auth_protocol` (Kerberos, NTLM), `ref_event_code` (Win Event ID like 4624), `unmapped.type` ("Logon Success"/"Logon Failure"), `type_name` ("Authentication: Logon") | -| `finding` | n/a: **NOT security findings** | n/a | n/a | `dataSource.category='metrics'`, `tag='ingestionHealth'`, `processor='ocsf-findings'`. This source is OCSF processor latency/batch metrics, not findings | - -**OCSF severity_id mapping:** 0=Unknown, 1=Informational, 2=Low, 3=Medium, -4=High, 5=Critical, 6=Fatal. Filter via `severity_id >= 4` for High+Critical. - -### Reserved-field rewrite (trailing underscore) - -Field names ending in `_` (e.g. `severity_`, `status_`, `classification_`) are -SDL's auto-rename when source data carries a field name colliding with an SDL -reserved name. The underscored form **IS** the canonical, queryable field, -not a sparse alternate. The numeric OCSF variants (`severity_id`, `status_id`, -`class_uid`) live alongside the underscored string fields. The `severity_` -string can be case-mixed (`Critical` and `CRITICAL` both appear), see -`powerquery/references/pitfalls.md` for handling. - -### EDR / XDR telemetry (endpoint events from `dataSource.name='SentinelOne'`) - -```text -dataSource.category = 'security' -event.category in ('process', 'file', 'ip', 'dns', 'indicators', 'logins', 'url', 'registry') -``` - -`event.type='Behavioral Indicators'` carries `indicator.category`, -`indicator.name`, `agent.uuid`, `endpoint.name`, `src.process.{user,cmdline,image.path}`. - -### Third-party sources - -```text -dataSource.vendor = 'Microsoft' // O365, Azure AD -dataSource.name = 'FortiGate' // field namespaces differ per event.type, validate each type separately before authoring panels - // traffic: src_endpoint.ip, dst_endpoint.ip, app_name (populated), unmapped.action, traffic.bytes_out/in - // vpn: unmapped.srcip (NOT src_endpoint.ip which is null), unmapped.action, unmapped.dstip - // app-ctrl: app_name is null (not promoted by marketplace parser); extract via two-pass parse from message field - // All types: unmapped.action for the raw action string -dataSource.name = 'Okta' // unmapped.eventType='user.session.start', status='FAILURE'/'SUCCESS', actor.user.name, src_endpoint.ip, src_endpoint.location.country -dataSource.name = 'Zscaler Internet Access' // http_request.url.categories -metadata.product.name = 'SharePoint' -``` - -Re-validate every third-party source schema in Step 2b of session init: -field namespaces vary by parser version and tenant. - -### Common PowerQuery patterns for panels - -**Top-N table** (always add a bar column with `showBarsColumn: "true"`): - -```text -event.category='indicators' | group count=count() by indicator.name | sort -count | limit 20 -``` - -**Timeline line chart** (use `timebucket` + `transpose`): - -```text -event.type='process' | group count=count() by timestamp=timebucket('1h'), endpoint.os | transpose endpoint.os on timestamp -``` - -**Single number** (estimate_distinct for cardinality): - -```text -| group estimate_distinct(agent.uuid) -``` - -**Geo enrichment**: - -```text -| group count=count() by country=geo_ip_country(src.ip.address) | sort -count -``` - -**URL deep-link in table**: - -```text -| let Threat_URL = format("https://your-console.sentinelone.net/incidents/threats/%s/overview", threat_id) -| columns Computer=data.computer_name, Threat_URL, Path=data.file_path -``` +Source-by-source field patterns and starting-point queries (S1 internal OCSF sources, EDR/XDR telemetry, third-party sources, and common panel query shapes) are in [`references/data-source-patterns.md`](references/data-source-patterns.md). Re-run live schema discovery before authoring panels; these are starting points, not a registry. --- ## Deploying a dashboard via API -Use the `sdl-api` skill to deploy. Dashboard config files live at paths like `/dashboards/my-dashboard-name`. - -### 1. Always read existing version before put_file (CAS guard) - -```python -import json, time -from sdl_client import SDLClient - -client = SDLClient() -DASH_PATH = "/dashboards/soc-overview" - -# Read existing version (or treat 404 as version=0 for a brand-new dashboard) -try: - existing = client.get_file(DASH_PATH) - cur_version = existing.get("version") - backup_path = f"/tmp/{DASH_PATH.replace('/','_')}.{cur_version}.bak.json" - open(backup_path, "w").write(existing.get("content") or "{}") -except Exception: - cur_version = None - backup_path = None - -body = json.dumps(dashboard_json, indent=2) -res = client.put_file(path=DASH_PATH, content=body, expected_version=cur_version) -assert res.get("status") == "success", res -``` - -The `expected_version` argument is a CAS guard against concurrent writes from the SDL UI or another script. - -### 2. Verify deployment by re-fetching (and grep for a canary) - -```python -time.sleep(3) # eventual-consistency window -verify = client.get_file(DASH_PATH) -deployed_content = verify.get("content", "") -assert verify.get("version") != cur_version, "version did not bump" -assert "" in deployed_content, "deploy did not include new content" -``` - -A `put_file` response of `{"status": "success"}` does not guarantee the new content was written, always re-fetch and grep for a canary string from the change. - -### 3. Avoid duplicate dashboard paths - -The SDL UI's **Save** button writes to `/dashboards/id//`. The file API can write to either that path OR the simpler `/dashboards/`. **Pick one canonical path before the first deploy and never mix.** Each deploy to the alternate path creates a silent duplicate alongside the UI-saved copy. Recommend `/dashboards/` for hand-authored dashboards and `/dashboards/id//` only for files originally saved through the UI. - -### 4. Layout coordinates accumulate - -Panels are placed by `layout: {w, h, x, y}`. When appending a new section to an existing dashboard, compute the next `y` as `max(existing_y + existing_h)` across the tab, not by visual estimation. Off-by-a-few errors stack panels on top of each other and the UI will not flag this as an error. - -### 5. Test panels in the SDL UI before declaring done - -The SDL dashboard render engine has a longer query budget than the PowerQuery MCP. A query that times out in MCP validation may still render in the UI. Conversely, a query that returns from MCP may render slowly in the UI. The final smoke test for every dashboard is: open it in the UI, watch each tab load, confirm no panel spins indefinitely. - -After deploying, open in the SDL UI: **Visibility Enhanced → Dashboards** → select the dashboard by name. +Deployment via the `sdl-api` GraphQL methods (resolve the `udoId`, write with a CAS guard, verify by re-fetch with a canary grep, and handle name-vs-udoId duplicates) is documented in [`references/deployment.md`](references/deployment.md). --- ## Reference files in this skill +- `references/panel-types.md`: full per-panel JSON catalog for every `graphStyle` (moved out of this file). - `references/panel-type-cheatsheet.md`: one-line summary of every panel type plus gotchas. +- `references/powerquery-gaps.md`: PowerQuery patterns that fail inside dashboard JSON, safe alternatives, and parse workarounds (moved out of this file). +- `references/parameters-and-filters.md`: dynamic filtering deep detail (`filters[]`, `#VarName#`, `parameters[]`) (moved out of this file). +- `references/data-source-patterns.md`: source-by-source field patterns and starting-point queries (moved out of this file). +- `references/query-performance.md`: per-query performance rules for dashboard panels (moved out of this file). +- `references/deployment.md`: deploy via API, pre-deploy validation, escalation ladder, and the pre-deploy checklist (moved out of this file). - `references/community-examples.md`: full real-world dashboard JSON examples (console audit, threat stats, alert investigation, O365, Fortinet). - `references/common-queries.md`: ready-to-paste PowerQuery snippets for common security use cases. - `references/lessons-learned.md`: source-agnostic patterns and gotchas from production engagements (PowerQuery feature gaps, full-text cost, naming hygiene, discriminator handling, validation runner shape). @@ -939,223 +335,13 @@ See `references/evidence-report-template.md` for the exact structure. ## Query performance tips -Dashboard panels run their queries in the SDL console's built-in rendering engine, not via LRQ or any external API. Every panel loads when the user opens the dashboard, so slow queries directly delay the page. Apply these rules to every query you write. - -### 1. Use `net_rfc1918()`: never hand-roll CIDR regex - -**Slow (avoid):** - -```text -| let rfc1918 = not (dst.ip.address matches '((127\\..*)|(192\\.168\\..*)|(10\\..*)|(172\\.1[6-9]\\..*)|(172\\.2[0-9]\\..*)|(172\\.3[0-1]\\..*)).*') -| filter rfc1918 = true -``` - -**Fast:** - -```text -dst.ip.address = * -| let is_external = not net_rfc1918(dst.ip.address) -| filter is_external = true -``` - -The built-in function is evaluated natively; the regex is evaluated as a string per event. - -### 2. Always add `| limit 1` to number panels - -Number panels reduce to a single row. Without `| limit 1`, the engine continues scanning after finding the answer. Always terminate: - -```json -"query": "dataSource.name='ActivityFeed' activity_type in (\"133\",\"134\") | group count() | limit 1" -``` - -### 3. Add explicit `| limit N` to every table panel - -Unbounded tables force a full scan. Always cap results: - -- Detail tables (time-sorted raw events): `| limit 200` -- Aggregated top-N tables: `| limit 20` or `| limit 25` -- Donut/pie panels: `| sort -count | limit 10` - -### 4. Use `field=*` to drop nulls: the canonical presence form - -`field=*` is the canonical SDL presence predicate: it matches any event where the field is present and non-null. Accuracy note (live-verified 2026-07-29 via the LRQ API): `field != null` returned results identical to `field=*`; the earlier claim that it hard-errors or compares against the literal string "null" was NOT reproducible on LRQ. The console/dashboard renderer engine remains unverified for `!= null`, so `field=*` stays the REQUIRED form in all dashboard JSON, rule bodies, and examples: - -```text -// Required: canonical presence check -dataSource.name='alert' severity_id=* -| group count=count() by severity_id - -// Avoid: behaved identically via LRQ (2026-07-29) but unverified in the dashboard engine -dataSource.name='alert' severity_id != null -``` - -This applies in the initial filter predicate (before the first `|`) and in `| filter` commands equally. - -### 5. Use `| filter count > 0` to suppress zero rows - -After a `| group count=count() by ...`, SDL may produce rows with `count=0` for sparse buckets (especially after `transpose` or when grouping over a large key space). These zero rows render as empty cells in heatmaps and false entries in tables. Filter them out: - -```text -| group EventCount=count() by user_name=user.name, timestamp=timebucket('1h') -| filter EventCount > 0 -``` - -Apply the same pattern to any aggregated numeric field you're visualising: `| filter bytes > 0`, `| filter value > 0`. - -### 6. `| sort` must come before `| columns`: field projection is destructive - -`| columns` removes every field not listed. Any `| sort` placed after `| columns` that references a field not in the projected set is operating on a non-existent field and silently fails or hangs the panel (bullet panels are especially prone to this): - -```text -// Wrong: severity_id is gone after | columns, sort fails silently -| group value=count(), target=..., label=... by severity_id -| columns value, target, label -| sort -severity_id ← severity_id no longer exists - -// Correct: sort while severity_id is still in scope -| group value=count(), target=..., label=... by severity_id -| sort -severity_id -| columns value, target, label -``` - -This affects any pipeline that projects away the sort key: bullet panels, donut panels with a custom label column, and any query that renames fields via `| columns alias=field`. - -### 7. Use `event.category = *` not `event.category != ''` - -`!= ''` requires evaluating the field value as a string comparison. `= *` is a cheaper is-not-null predicate: - -```text -dataSource.category = 'security' event.category = * -| group count=count() by timestamp=timebucket("1 day"), event.category -``` - -### 5. Match `timebucket` granularity to your dashboard duration - -Too-fine granularity creates thousands of data points per series, slowing both query and render: - -| Dashboard duration | Safe `timebucket` | Points per series | -|---|---|---| -| `1h` | `'1m'` | 60 | -| `4h` | `'5m'` | 48 | -| `24h` | `'1h'` | 24 | -| `7d` | `'1h'` | 168 | -| `14d` | `'1d'` | 14 | -| `30d` | `'1d'` | 30 | - -For a 24h dashboard, `'10m'` (144 points) can work for low-cardinality single-series panels but should not be the default; use `'1h'`. For a multi-series transpose, the data-point count compounds: `timebucket('10m')` on a 24h dashboard with a 7-series transpose = 1,008 cells per chart. - -**Never use `timebucket('10m')` on a 7-day dashboard**; that's 1,008 points per series. - -### 6. Push filters early: before the first pipe - -The initial filter (before the first `|`) is evaluated as an index predicate. Conditions placed there are far cheaper than `| filter` commands applied after a full scan: - -```text -// Good: index-level filter -event.category = 'ip' event.network.direction = 'OUTGOING' dataSource.category = 'security' -| group count=count() by dst.ip.address | sort -count | limit 20 - -// Bad: scans all events then filters -dataSource.category = 'security' -| filter event.category = 'ip' && event.network.direction = 'OUTGOING' -| group count=count() by dst.ip.address | sort -count | limit 20 -``` - -### 7. Use `estimate_distinct()` for cardinality: not `count(distinct …)` - -`estimate_distinct()` uses HyperLogLog and is orders of magnitude faster on high-cardinality fields like `agent.uuid`, `threat_id`, `src.process.storyline.id`. - -### 8. Avoid `nolimit` in dashboard panels - -`nolimit` raises the row cap to 3 GB and blocks concurrent queries. It is never appropriate in a dashboard panel; always use an explicit `| limit N` instead. - -### 9. Wrap string-prone numeric fields with `number()` before arithmetic - -SDL/Scalyr column types are locked at first ingest. A field that *should* be numeric, `severity_id`, `traffic.bytes_in/out`, `traffic.packets_in/out`, `unmapped.duration`, can be string-typed at the index level (because a parser declared `type: "string"` for many tenant generations, or the field was first-written before the type was set). When that happens, `sum()` / `avg()` / `max()` / `>=` predicates return NaN or fail silently *even though the values are populated and visible in Event Search*. - -**Failsafe pattern for every dashboard panel that does numeric work:** - -```text -dataSource.name='alert' severity_id=* -| let sev = number(severity_id) -| filter sev >= 4 -| group hits=count() by sev -| sort sev -``` - -```text -dataSource.name='FortiGate' unmapped.action='close' -| let bytes_out_n = number(traffic.bytes_out) -| let bytes_in_n = number(traffic.bytes_in) -| group sessions=count(), - bytes_out=sum(bytes_out_n), - bytes_in=sum(bytes_in_n), - max_session=max(bytes_out_n) -| limit 1 -``` - -`number(x)` returns 0 for null/missing and NaN for unparseable strings. Already-numeric data is unaffected. Cost is one `let` per panel; benefit is the dashboard keeps working when a parser pushes a string-typed write or a tenant column is locked. Apply this to every numeric counter / severity / port / duration field unless this session's schema discovery proved the column type with a successful unwrapped `sum()`. - -See `powerquery/references/pitfalls.md` for the full discussion of column-type lock and when the `parse "$x{regex=\\d+}$"` extraction is preferable to `number()`. +The per-query performance rules for dashboard panels (`net_rfc1918()` over hand-rolled CIDR regex, `| limit` on number and table panels, `field=*` presence checks, timebucket-vs-duration sizing, `estimate_distinct()`, and `number()` casting of string-typed numeric fields) are in [`references/query-performance.md`](references/query-performance.md). --- ## Pre-deploy validation -### The browser renderer is a separate execution path - -The SDL engine has three query surfaces: the V1 query API, the LRQ async API, and the in-browser dashboard renderer. The renderer has different timeouts, a stricter column-name parser, and a different concurrency model. A query that returns results instantly via the API can still hang the renderer. "All API queries pass" is necessary but not sufficient. The renderer is the only path that matters for dashboards, and it cannot be tested directly except by deploying and opening the page. - -The learnings below let you predict and eliminate renderer failures before deploy. - -### Parallel load test (run before every `put_file`) - -The browser fires all panel queries in parallel on load. Total dashboard load time ≈ slowest single panel + small per-panel render overhead. Always run a parallel load test before deploying a new or significantly modified dashboard: - -```python -import concurrent.futures, time - -def run_one(panel_query): - c = SDLClient() - # auth setup ... - t0 = time.time() - try: - res = c.power_query(query=panel_query, start_time="24h") - return ("OK", time.time() - t0, res.get("matchingEvents") or 0) - except Exception as e: - return ("FAIL", time.time() - t0, str(e)[:200]) - -queries = [p["query"] for tab in dashboard["tabs"] for p in tab["graphs"] - if p.get("graphStyle") != "markdown" and p.get("query")] - -wall_t0 = time.time() -with concurrent.futures.ThreadPoolExecutor(max_workers=10) as pool: - results = list(pool.map(run_one, queries)) -wall_clock = time.time() - wall_t0 - -print(f" Total serial: {sum(r[1] for r in results):.1f}s") -print(f" Wall-clock parallel: {wall_clock:.1f}s <- expect this in browser") -print(f" Slowest single: {max(r[1] for r in results):.1f}s") -``` - -**Acceptance thresholds:** slowest single panel ≤ 2s, wall-clock parallel ≤ 5s, zero failures. If the slowest panel exceeds 2s, identify it and rewrite: replace `group` with `top K`, narrow the initial filter, raise the timebucket granularity, or split the dashboard. - -### Deploy-and-verify: sleep before re-fetching - -`put_file` returns `{"status": "success"}` synchronously, but the file propagates across replicas with eventual consistency. Calling `get_file` ~100ms after a successful PUT can return HTTP 404. Always wait: - -```python -res = c.put_file(path=DASH_PATH, content=new_content, expected_version=cur_version) -assert res.get("status") == "success" - -import time -time.sleep(3) # eventual-consistency window - -post = c.get_file(DASH_PATH) -assert post.get("version") != cur_version # version bumped -``` - -Without the sleep, verification looks like a deploy failure even when the deploy succeeded. +The browser-renderer execution path, the parallel load test with its acceptance thresholds, and the deploy-and-verify sleep window are in [`references/deployment.md`](references/deployment.md). --- @@ -1187,76 +373,13 @@ This is the same V1-query schema-discovery pattern from the `sdl-api` skill, app ## Escalation ladder when a deployed dashboard hangs -1. **Log out and log back in.** The SDL UI caches panel render state in the session. After a `put_file`, the browser can serve a stale render from the prior version even though the underlying config changed. A fresh login clears session state completely. Try this before any config investigation when the query is confirmed to return data. -2. **Hard refresh** (`Ctrl+Shift+R` / `Cmd+Shift+R`). Eliminates cached state from a previous broken version. Resolves ~10% of "still hung" reports. -3. **Check dev-tools network tab.** If panel queries are NOT being fired, the renderer is stuck before any HTTP call. Cause is structural (layout/options/JSON parse), not query performance. If queries ARE firing, record the slowest and move to step 3. -4. **Run the slow panel's query in isolation via the V1 API.** If it returns fast, the issue is renderer-side (column names, `transpose`, panel options). If it is slow, optimise the query. -5. **Reduce panel count by 50%.** If the dashboard now loads, the issue was concurrency or memory in the renderer. Add panels back 25% at a time until a regression isolates the offender. -6. **Diff against a working reference dashboard in the same tenant.** `list_files /dashboards/`, `get_file` on a working dashboard, compare top-level keys, panel `layout` shape, `options` keys, and `graphStyle`-specific fields. Working dashboards in the same tenant are more reliable ground truth than any external documentation, because rendering rules drift between SDL releases. -7. **Roll back.** Always keep a backup of the prior dashboard JSON before `put_file`-ing a new version. Restore via `put_file(expected_version=current)` to unblock analysts while iterating offline. +The step-by-step escalation ladder for a deployed dashboard that hangs (session reset, hard refresh, dev-tools network check, isolate the slow panel, halve the panel count, diff against a working dashboard, roll back) is in [`references/deployment.md`](references/deployment.md). --- ## Pre-deploy checklist -Run this before every `put_file`. Items marked **[scripted]** are checked automatically by `scripts/panel_safety_check.py`. - -```text -PRE-AUTHORING -[ ] Live data-source enumeration confirms every dataSource.name used by the dashboard exists -[ ] V1-query schema discovery run for every source; field list saved for the session -[ ] Discriminator field validated for every event.type the dashboard counts -[ ] No panel relies on a field that is only present in raw_data (or, if it does, the panel - is a number/selective-table that won't time out under full-text) - -JSON STRUCTURE (scripted) -[ ] Every panel has explicit x, y, w, h in layout [scripted] -[ ] When appending to existing dashboard, next y = max(existing_y + existing_h) -[ ] No panel uses `transpose on timestamp` where values may contain hyphens -[ ] No `graphStyle: "area"` panel with a `query:` field (must use `plots:`) [scripted] -[ ] Markdown panels use `markdown:` field, not `content:` [scripted] -[ ] No content after `| transpose` (transpose must be terminal) [scripted] -[ ] No hyphenated arithmetic (`x-y` without spaces) [scripted] - -QUERY HYGIENE (scripted) -[ ] All number panels end with `| limit 1` [scripted] -[ ] All table panels end with explicit `| limit N` [scripted] -[ ] All time-series panels use a timebucket appropriate for duration -[ ] min/max(timestamp) columns wrapped in simpledateformat(...) with tz -[ ] Any millisecond-typed time field multiplied by 1000000 before simpledateformat -[ ] Hostname/value-list filters use `field in (...)` not `field matches '(...)'` -[ ] Numeric fields wrapped with number() before arithmetic / sum / avg -[ ] No use of count_if / sum(if(predicate, value, 0)) / mid-pipeline | union (union-first is allowed) / named subqueries [scripted] -[ ] No `\\s` / `\\d` regex escapes inside `matches '...'` [scripted] -[ ] No full-text predicate combined with timebucket+transpose [scripted] -[ ] Number panels use only {format, precision, suffix} in options - -NAMING & SEMANTICS -[ ] Every panel title reads as an SLA-grade claim (no overstated counts) -[ ] Distinct event populations under the same event.type live in separate sections - each with a markdown header explaining the split -[ ] Panels that may legitimately return 0 have a markdown header explaining the - SOC-positive interpretation -[ ] 3-5 sample events checked to verify which field carries the user semantic per event ID -[ ] Machine-account filter applied on user-facing panels - -PERFORMANCE & LOAD -[ ] Parallel load test passes: wall-clock <= 5s, slowest panel <= 2s, zero failures -[ ] All time-series panels obey the timebucket-vs-duration table - -DEPLOYMENT -[ ] Backup of current dashboard JSON saved (for rollback) -[ ] put_file called with expected_version of the current deployed copy -[ ] sleep(3) before re-fetching to verify deploy -[ ] Re-fetched content greps for a canary string from the change -[ ] Path is canonical (either /dashboards/ OR /dashboards/id//, never both) - -POST-DEPLOY (MANDATORY) -[ ] scripts/validate_dashboard.py run; per-panel evidence JSON persisted -[ ] Markdown evidence file emitted alongside the JSON -[ ] scripts/render_validation_pdf.py run; PDF report delivered with the dashboard -[ ] PDF Appendix lists every empty-result panel with a SOC-meaningful interpretation -``` +The full pre-deploy checklist (pre-authoring, JSON structure, query hygiene, naming and semantics, performance and load, deployment, and post-deploy items, with `scripts/panel_safety_check.py`-scripted items marked) is in [`references/deployment.md`](references/deployment.md). --- diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/common-queries.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/common-queries.md index 103deab..0945397 100644 --- a/plugins/s1-secops-skills/skills/sdl-dashboard/references/common-queries.md +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/common-queries.md @@ -7,13 +7,15 @@ Ready-to-paste queries for common security dashboard use cases. All work as `que ## Threat & Indicator Panels **Active threats by severity (pie/donut)** -``` + +```text index = "activities" activity_type in ("18","19","20","2016","4003","4009","4108","4100","4109","4110","4111","4112") | group count=count() by data.confidence_level ``` **Threat timeline by status (stacked bar, daily)** -``` + +```text index = "activities" activity_type in ("18","19","20","2016","4003","4009","4108","4100","4109","4110","4111","4112","2028") | let status = (activity_type = 2028 ? data.new_incident_status_title : "Unresolved") | group newest_status = newest(status) by threat_id, timestamp = timebucket("1 day") @@ -22,7 +24,8 @@ index = "activities" activity_type in ("18","19","20","2016","4003","4009","4108 ``` **Top noisiest endpoints by threat count (table)** -``` + +```text index = "activities" activity_type in ("18","19","20","2016","4003","4009","4108","4100","4109","4110","4111","4112") | group count = count() by Computer_Name=data.computer_name | sort -count @@ -30,14 +33,16 @@ index = "activities" activity_type in ("18","19","20","2016","4003","4009","4108 ``` **Custom STAR rule alert timeline (stacked bar)** -``` + +```text index = "activities" activity_type = 3608 dataSource.category = 'security' | group count = count() by RuleName=data.ruleName, timestamp = timebucket("1 day") | transpose RuleName on timestamp ``` **HIFI indicator hits (table)** -``` + +```text event.category = 'indicators' indicator.name contains ("blockedMimikatz","bloodHound","maliciousPowershellScript","MetasploitNamedPipeImpersonation","ransomware","brute","samSave","SPNRequestFromPowershell") dataSource.category = 'security' | group count=count() by indicator.category, indicator.name | sort -count @@ -48,7 +53,8 @@ event.category = 'indicators' indicator.name contains ("blockedMimikatz","bloodH ## Network Panels **Top outbound destinations (table with bar column)** -``` + +```text event.category = 'ip' event.network.direction = 'OUTGOING' !isempty(dst.ip.address) dataSource.category = 'security' | group count = count() by dst.ip.address | sort -count @@ -56,7 +62,8 @@ event.category = 'ip' event.network.direction = 'OUTGOING' !isempty(dst.ip.addre ``` **Outbound PowerShell (non-RFC1918) by destination IP (table)** -``` + +```text src.process.name contains 'powershell' dst.ip.address=* dataSource.category = 'security' | let rfc1918 = not (dst.ip.address matches '((127\\..*)|(192\\.168\\..*)|(10\\..*)|(172\\.1[6-9]\\..*)|(172\\.2[0-9]\\..*)|(172\\.3[0-1]\\..*)).*') | filter rfc1918 = true @@ -65,14 +72,16 @@ src.process.name contains 'powershell' dst.ip.address=* dataSource.category = 's ``` **Network connections by direction (line chart)** -``` + +```text event.category = 'ip' dataSource.category = 'security' | group count = count() by timestamp = timebucket("10m"), event.network.direction | transpose event.network.direction on timestamp ``` **Possible outbound port scan (table)** -``` + +```text event.category = "ip" event.network.direction = "OUTGOING" dataSource.category = 'security' | group distinct_dstport=estimate_distinct(dst.port.number) by dst.ip.address, endpoint.name, src.ip.address, src.process.name, src.process.storyline.id | sort -distinct_dstport @@ -81,7 +90,8 @@ event.category = "ip" event.network.direction = "OUTGOING" dataSource.category = ``` **Top DNS requests by process (table)** -``` + +```text event.category = "dns" dataSource.category = 'security' | group count = count() by src.process.name, event.dns.request | sort -count @@ -92,21 +102,24 @@ event.category = "dns" dataSource.category = 'security' ## Process Panels **Top parent processes (table)** -``` + +```text event.category = "process" dataSource.category = 'security' | group count=count() by src.process.name | sort -count ``` **Process timeline by user (line chart)** -``` + +```text event.category = "process" dataSource.category = 'security' | group count=count() by src.process.user, timestamp = timebucket("1 minute") | transpose src.process.user on timestamp ``` **Processes grouped by target command line (table)** -``` + +```text event.category = "process" dataSource.category = 'security' | let tgt_details = format("(%s) %s (%s) -> %s", src.process.user, src.process.name, src.process.storyline.id, tgt.process.cmdline) | group count = count() by tgt_details @@ -119,12 +132,14 @@ event.category = "process" dataSource.category = 'security' ## Endpoint / Agent Panels **Distinct active endpoints (number gauge)** -``` + +```text | group estimate_distinct(agent.uuid) ``` **New agents enrolled by site (stacked bar, daily)** -``` + +```text dataSource.name='ActivityFeed' activity_type = "17" | let scope = format("%s / %s", account_name, site_name) | group count = count() by timestamp = timebucket("1 day"), scope @@ -132,7 +147,8 @@ dataSource.name='ActivityFeed' activity_type = "17" ``` **Agents decommissioned (table)** -``` + +```text dataSource.name='ActivityFeed' activity_type in ("47", "49", "50", "51", "52", "54") | columns Time=created_at, Username=data.username, Scope=data.full_scope_details_path, type, Description=primary_description | sort -Time @@ -143,7 +159,8 @@ dataSource.name='ActivityFeed' activity_type in ("47", "49", "50", "51", "52", " ## User & Authentication Panels **Login attempts timeline (line chart)** -``` + +```text dataSource.name='ActivityFeed' activity_type in ("133", "134", "138", "139", "27", "3629") | let login_event = (activity_type = 27) ? "user logged in" : (activity_type = 133) ? "Login Failure (existing user)" : (activity_type = 134) ? "Login Failure (unknown user)" : "other" | group count = count() by timestamp = timebucket("1 day"), login_event @@ -151,14 +168,16 @@ dataSource.name='ActivityFeed' activity_type in ("133", "134", "138", "139", "27 ``` **Failed login count by user (table)** -``` + +```text dataSource.name='ActivityFeed' activity_type in ("133") | group count = count() by Username=data.username, Comments=comments, IP_Address=data.ip_address | sort -count ``` **Sensitive policy modifications by user (table)** -``` + +```text dataSource.name='ActivityFeed' activity_type in ("56","57","68","69","73","76","78","79","84","87","88","105","150") | group count = count() by Username=data.username | sort -count @@ -169,14 +188,16 @@ dataSource.name='ActivityFeed' activity_type in ("56","57","68","69","73","76"," ## File Activity Panels **File events timeline by type (line chart)** -``` + +```text event.category = 'file' dataSource.category = 'security' | group count = count() by timestamp = timebucket("1 minute"), event.type | transpose event.type on timestamp ``` **Distinct file interactions by process (table)** -``` + +```text event.category = "file" dataSource.category = 'security' | group distinct_sha1_count = estimate_distinct(tgt.file.sha1), distinct_name_count = estimate_distinct(tgt.file.path) by src.process.name, src.process.image.sha1, event.type, tgt.file.extension | sort -distinct_name_count @@ -188,7 +209,8 @@ event.category = "file" dataSource.category = 'security' ## Device & Network Control Panels **USB block/allow timeline (line chart)** -``` + +```text dataSource.name='ActivityFeed' activity_type in ("5125", "5126") | let usb_event = (activity_type = 5125) ? "USB-blocked" : "USB-allowed" | group count = count() by timestamp = timebucket("1 day"), usb_event @@ -196,13 +218,15 @@ dataSource.name='ActivityFeed' activity_type in ("5125", "5126") ``` **Firewall block timeline (line chart)** -``` + +```text dataSource.name='ActivityFeed' activity_type = "5232" | group count = count() by timestamp = timebucket("10 minutes") ``` **Top agents with firewall blocks (stacked bar)** -``` + +```text dataSource.name='ActivityFeed' activity_type = "5232" | group count = count() by data.computer_name | sort -count @@ -214,7 +238,8 @@ dataSource.name='ActivityFeed' activity_type = "5232" ## RemoteOps / Forensics Panels **Script executions (table)** -``` + +```text dataSource.name='ActivityFeed' activity_type in ("3618") dataSource.category = 'security' | columns Time=created_at, Username=data.username, Scope=data.full_scope_details_path, type, Description=primary_description | sort -Time @@ -225,12 +250,14 @@ dataSource.name='ActivityFeed' activity_type in ("3618") dataSource.category = ' ## Multi-Source / Enrichment Panels **Events by data source vendor (pie)** -``` + +```text | group count=count() by dataSource.vendor ``` **Geo distribution of source IPs (table)** -``` + +```text event.category = 'ip' !isempty(src.ip.address) dataSource.category = 'security' | group count = count() by country=geo_ip_country(src.ip.address) | sort -count diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/community-examples.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/community-examples.md index d4afc7f..290d494 100644 --- a/plugins/s1-secops-skills/skills/sdl-dashboard/references/community-examples.md +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/community-examples.md @@ -9,12 +9,14 @@ These are production-ready SDL dashboard JSON examples. Use them as starting poi Multi-tab dashboard covering agent lifecycle, threat stats, policy changes, exclusions, network control, device control, Ranger, RemoteOps, Marketplace, and login events. Uses `dataSource.name='ActivityFeed'` for all audit panels. **Key patterns used:** + - Stacked bar with `timebucket("1 day")` + `transpose` for timelines - Table panels with `| columns` for clean column naming - `activity_type in (...)` filtering with quoted string values - `format(...)` for computed URL deep-links **Tab: Agents & Scopes**: agent subscriptions by site/group/account/OS, agent updates, decommissions, scope moves. + ```javascript { tabs: [{"tabName":"Agents & Scopes", @@ -40,6 +42,7 @@ graphs : [ ``` **Tab: Threat Stats**: timeline by confidence/verdict/status, noisiest machines, failed mitigations, custom rule alerts. + ```javascript { graphStyle: "stacked_bar", @@ -51,6 +54,7 @@ graphs : [ ``` **Tab: Conf & Policy Changes**: sensitive policy mods by user/scope, device control changes, network quarantine, inheritance changes. + ```javascript { query: "dataSource.name='ActivityFeed' activity_type in (\"56\", \"57\", \"68\", \"69\", \"73\", \"76\", \"78\", \"79\", \"84\", \"105\", \"87\", \"88\", \"150\") \n| columns Time=created_at, Username=data.username, Scope=data.full_scope_details_path, type, Description=primary_description\n| sort -Time", @@ -65,6 +69,7 @@ graphs : [ ## 2. Purple AI Audit Dashboard Tracks Purple AI usage by analyst. + ```javascript { description: "", @@ -111,6 +116,7 @@ Tracks Purple AI usage by analyst. Multi-tab investigation dashboard with filters for `endpoint.name` and `src.process.storyline.id`. Covers event overview, process tree, file activity, network, indicators, and lateral movement. **Tab: Overview**: event category breakdown, indicator categories, file timeline, outbound IPs, DNS by process. + ```javascript { tabs: [{"tabName":"Overview","graphs":[ @@ -143,6 +149,7 @@ Multi-tab investigation dashboard with filters for `endpoint.name` and `src.proc ``` **Tab: File**: file event timeline, distinct file interactions by process, possible ransom notes detection. + ```javascript { query: "event.category = \"file\" dataSource.category = 'security' \n| let windows_path_array = array_split(tgt.file.path, \"\\\\\\\\\")\n| let windows_directory_path_array = array_slice(windows_path_array, 0, len(windows_path_array)-1)\n| let windows_directory_path_string = array_to_string(windows_directory_path_array, \"\\\\\")\n| let windows_filename_string = windows_path_array.get(len(windows_path_array)-1)\n| let unix_path_array = array_split(tgt.file.path, \"/\")\n| let unix_directory_path_array = array_slice(unix_path_array, 0, len(unix_path_array)-1)\n| let unix_directory_path_string = array_to_string(unix_directory_path_array, \"/\")\n| let unix_filename_string = unix_path_array.get(len(unix_path_array)-1)\n| let directory_path_string = (endpoint.os = \"windows\") ? windows_directory_path_string : unix_directory_path_string\n| let filename_string = (endpoint.os = \"windows\") ? windows_filename_string : unix_filename_string\n| group distinct_path_count = estimate_distinct(directory_path_string) by endpoint.name, src.process.name, src.process.image.sha1, event.type, tgt.file.extension, filename_string\n| sort -distinct_path_count\n| columns src.process.name, event.type, distinct_path_count, filename_string\n| limit 10", @@ -151,6 +158,7 @@ Multi-tab investigation dashboard with filters for `endpoint.name` and `src.proc ``` **Tab: Network**: IP timeline by direction/status, outbound/inbound scan detection, top destinations/sources. + ```javascript { query: "event.category = \"ip\" and event.network.direction = \"OUTGOING\" dataSource.category = 'security'\n| group distinct_dstip=estimate_distinct(dst.ip.address) by endpoint.name, src.ip.address, src.process.name, src.process.storyline.id\n| sort -distinct_dstip\n| columns endpoint.name, src.process.storyline.id, src.process.name, src.ip.address, distinct_dstip\n| limit 10", @@ -159,6 +167,7 @@ Multi-tab investigation dashboard with filters for `endpoint.name` and `src.proc ``` **Tab: Indicators**: indicator category breakdown, HIFI (high-fidelity) indicators, full indicator list. + ```javascript { query: "event.category = 'indicators' indicator.name contains (\"appLockerBypass\",\"blockedMimikatz\",\"bloodHound\",\"maliciousPowershellScript\",\"MetasploitNamedPipeImpersonation\",\"ransomware\",\"brute\") dataSource.category = 'security'\n| group count=count() by indicator.category, indicator.name\n| sort -count", @@ -173,6 +182,7 @@ Multi-tab investigation dashboard with filters for `endpoint.name` and `src.proc Multi-tab dashboard for Microsoft data sources. Tabs: O365 Alerts, Azure Login Activity, Azure AD lifecycle, SharePoint, OneDrive. **Tab: O365 Alerts** + ```javascript { query: "dataSource.vendor = 'Microsoft' activity_name='newAlert'\n| columns metadata.original_time, severity=unmapped.severity, finding.types, finding.title, details=unmapped.userStates, url=finding.src_url\n| sort -metadata.original_time", @@ -183,6 +193,7 @@ Multi-tab dashboard for Microsoft data sources. Tabs: O365 Alerts, Azure Login A ``` **Tab: Azure Login Activity**: logon status timeline, top 15 failing users, logins by country, failed logins by country, every login attempt detailed (with geo enrichment, OS, browser extraction from JSON arrays). + ```javascript { query: "dataSource.vendor='Microsoft' metadata.product.name='AzureActiveDirectory' event.type in ('Logon', 'UserLoginFailed') !isempty(actor.user.email_addr)\n| let ip_address = event.type='Logon' ? device.ip : unmapped.ActorIpAddress\n| let data_array = array_from_json(unmapped.DeviceProperties)\n| let os = json_object_value(data_array.get(0), \"Name\")=\"OS\" ? json_object_value(data_array.get(0), \"Value\") : \"not found\"\n| let browser = json_object_value(data_array.get(0), \"Name\")=\"BrowserType\" ? json_object_value(data_array.get(0), \"Value\") : \"not found\"\n| group count = count() by actor.user.email_addr, event.type, os, browser, ip_address, country=geo_ip_country(ip_address), unmapped.LogonError\n| sort -count", @@ -191,6 +202,7 @@ Multi-tab dashboard for Microsoft data sources. Tabs: O365 Alerts, Azure Login A ``` **Tab: OneDrive**: download timeline, top users downloading by GB and distinct file count. + ```javascript { query: "dataSource.vendor='Microsoft' metadata.product.name='OneDrive' event.type in ('FileDownloaded')\n| group downloaded_bytes = sum(unmapped.FileSizeBytes), distinct_files=estimate_distinct(unmapped.SourceFileName) by user=unmapped.UserId\n| let downloaded_gbytes = downloaded_bytes/1024/1024/1024\n| columns user, downloaded_gbytes, distinct_files\n| sort -distinct_files\n| limit 10", @@ -205,6 +217,7 @@ Multi-tab dashboard for Microsoft data sources. Tabs: O365 Alerts, Azure Login A Single-tab dashboard with parameters for dynamic filtering. Covers source/destination IPs, bytes sent/received, URL categories, protocol breakdown. Uses a hidden parameter pattern: + ```javascript { parameters: [ @@ -253,6 +266,7 @@ Uses a hidden parameter pattern: Shows external and internal login flows across country, process, user, host, and result stages. Uses `array(...).expand()` to fan each result row into multiple Sankey links. **Key patterns:** + - `array(0, 1, 2, 3).expand()` to produce one link row per stage from a single aggregated row - `sankeyColorMapping` with glob patterns (`"Inbound Login:*"`, `"*✅:*"`) for semantic coloring - `| left join` to enrich with TI indicator matches before building links @@ -591,7 +605,9 @@ Pre-quoting rule: string values must embed single quotes, `"'logVolume'"` so sub ## Panel Snippets Library ### Activity type quick reference (ActivityFeed) + Common activity types for audit dashboards: + - `"17"`: Agent subscribed (new enrollment) - `"43"`: Agent updated - `"47","49","50","51","52","54"`: Agent decommissioned/uninstalled @@ -609,24 +625,28 @@ Common activity types for audit dashboards: - `"3618"`: RemoteOps script executed ### Geo enrichment -``` + +```text | let country = geo_ip_country(src.ip.address) | let state = geo_ip_state(src.ip.address) ``` ### RFC1918 filter (exclude private IPs) -``` + +```text | let rfc1918 = not (dst.ip.address matches '((127\\..*)|(192\\.168\\..*)|(10\\..*)|(172\\.1[6-9]\\..*)|(172\\.2[0-9]\\..*)|(172\\.3[0-1]\\..*)).*') | filter rfc1918 = true ``` ### Format deep-link URL -``` + +```text | let Threat_URL = format("https://your-console.sentinelone.net/incidents/threats/%s/overview", threat_id) ``` ### Normalize values to 0-100 for honeycomb -``` + +```text | let max=overall_max(value), min=overall_min(value) | let normalized = ((value - min)/(max - min))*100 ``` diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/data-source-patterns.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/data-source-patterns.md new file mode 100644 index 0000000..a23685c --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/data-source-patterns.md @@ -0,0 +1,106 @@ +# Common SDL data sources and event patterns + +Source-by-source field patterns and starting-point queries for SDL dashboards. Referenced from `SKILL.md`. Always re-run live schema discovery before authoring panels; these are starting points, not a registry. + +## Common SDL data sources and event patterns + +> ⚠️ **Schemas drift between sessions and tenants.** The patterns below are +> starting points, not a registry. **Run live schema discovery (V1 query via +> `sdl-api` skill) on every source you'll query before authoring +> panels.** PowerQuery's default projection is `timestamp + message` only; it +> cannot discover schemas. Use the V1 `query` method which returns full event +> JSON. + +### S1 internal SDL sources are OCSF-rich (NOT stubs) + +`dataSource.name` values `alert`, `vulnerability`, `misconfiguration`, `asset`, +`Identity`, and `ActivityFeed` carry **rich OCSF events**, not metadata +stubs. The fields that *look* like they should exist based on the source name +(`alert.severity`, `alert.classification`, `vulnerability.kevAvailable`, +`misconfiguration.severity`) frequently do NOT exist, the actual queryable +fields are OCSF-namespaced. + +| Source | OCSF class_uid | Severity field | Endpoint linkage | Notes | +|---|---|---|---|---| +| `alert` | 99602001 (S1 Security Alert) | `severity_id` (numeric 0-5) | `resources[].name`, `resources[].s1_metadata.site_name` (NOTE: `resources[N]` only readable via V1 query, not PowerQuery `columns`) | `finding_info.title` = alert name. `metadata.product.name` ∈ {STAR, EDR, Identity, CWS, EPP}. `class_name` = "S1 Security Alert" | +| `vulnerability` | 2002 (Vulnerability Finding) | `severity_id` + `severity_` (string, often empty) | `resource.s1_metadata.*`, `resource.uid` | `vulnerabilities[].cve.uid`, `vulnerabilities[].affected_packages[].{name,version,vendor_name}`. **No `kevAvailable` field in SDL**, KEV/EPSS metadata lives in the management console only | +| `misconfiguration` | 2003 (Compliance Finding) | `severity_id` | `resources[].s1_metadata.*` | `compliance.standards[]` (CIS_AKS, CIS_KUBERNETES, etc.), `compliance.requirements[]`, `policy.{name,uid,desc}`, `cloud.provider`, `finding_info.title` | +| `asset` | 3004 (Device Inventory) | `severity_id` + `severity_` | `device.agent.uuid`, `device.name`, `device.os.name` | 126 fields (live-confirmed). Rich endpoint inventory. Key fields: `device.agent.{uuid,version,network_status,network_status_title,is_active,is_decommissioned,is_uninstalled,network_quarantine_enabled,last_logged_in_user_name,scan_status}`, `device.os.{name,version,type}`, `device.ip_external`, `device.hw_info.*`, `device.network_interfaces[N].*`. `operation` = OPERATION_UPSERT. No `entity.uid`, no `entity_result.*`, no `agent.health.online` fields; use `device.agent.network_status` for connectivity state | +| `ActivityFeed` | n/a (Hyperautomation / mgmt activity audit) | n/a | `data.scope_id`, `site_id`, `account.id` | 41 fields (live-confirmed). Hyperautomation workflow execution audit log and management console activity log. Key fields: `activity_type` (numeric, e.g. 9207 = workflow execution event, NOT a string), `activity_uuid`, `primary_description`, `secondary_description`, `data.workflow_{id,name,execution_url}`, `data.{scope_id,scope_level,scope_name,site_name,user_id}`, `created_at`, `updated_at`, `context`. `sca:RetentionType = 'ACTIVITY_LOG'`. No `activityType` (camelCase) field. Not useful for threat hunting, use for Hyperautomation audit and compliance workflow tracking | +| `Identity` | 3002 (Authentication) | `severity_id`, `status_id` | `user.name`, `user.domain`, `src_endpoint.ip`, `dst_endpoint.hostname` | `auth_protocol` (Kerberos, NTLM), `ref_event_code` (Win Event ID like 4624), `unmapped.type` ("Logon Success"/"Logon Failure"), `type_name` ("Authentication: Logon") | +| `finding` | n/a: **NOT security findings** | n/a | n/a | `dataSource.category='metrics'`, `tag='ingestionHealth'`, `processor='ocsf-findings'`. This source is OCSF processor latency/batch metrics, not findings | + +**OCSF severity_id mapping:** 0=Unknown, 1=Informational, 2=Low, 3=Medium, +4=High, 5=Critical, 6=Fatal. Filter via `severity_id >= 4` for High+Critical. + +### Reserved-field rewrite (trailing underscore) + +Field names ending in `_` (e.g. `severity_`, `status_`, `classification_`) are +SDL's auto-rename when source data carries a field name colliding with an SDL +reserved name. The underscored form **IS** the canonical, queryable field, +not a sparse alternate. The numeric OCSF variants (`severity_id`, `status_id`, +`class_uid`) live alongside the underscored string fields. The `severity_` +string can be case-mixed (`Critical` and `CRITICAL` both appear), see +`powerquery/references/pitfalls.md` for handling. + +### EDR / XDR telemetry (endpoint events from `dataSource.name='SentinelOne'`) + +```text +dataSource.category = 'security' +event.category in ('process', 'file', 'ip', 'dns', 'indicators', 'logins', 'url', 'registry') +``` + +`event.type='Behavioral Indicators'` carries `indicator.category`, +`indicator.name`, `agent.uuid`, `endpoint.name`, `src.process.{user,cmdline,image.path}`. + +### Third-party sources + +```text +dataSource.vendor = 'Microsoft' // O365, Azure AD +dataSource.name = 'FortiGate' // field namespaces differ per event.type, validate each type separately before authoring panels + // traffic: src_endpoint.ip, dst_endpoint.ip, app_name (populated), unmapped.action, traffic.bytes_out/in + // vpn: unmapped.srcip (NOT src_endpoint.ip which is null), unmapped.action, unmapped.dstip + // app-ctrl: app_name is null (not promoted by marketplace parser); extract via two-pass parse from message field + // All types: unmapped.action for the raw action string +dataSource.name = 'Okta' // unmapped.eventType='user.session.start', status='FAILURE'/'SUCCESS', actor.user.name, src_endpoint.ip, src_endpoint.location.country +dataSource.name = 'Zscaler Internet Access' // http_request.url.categories +metadata.product.name = 'SharePoint' +``` + +Re-validate every third-party source schema in Step 2b of session init: +field namespaces vary by parser version and tenant. + +### Common PowerQuery patterns for panels + +**Top-N table** (always add a bar column with `showBarsColumn: "true"`): + +```text +event.category='indicators' | group count=count() by indicator.name | sort -count | limit 20 +``` + +**Timeline line chart** (use `timebucket` + `transpose`): + +```text +event.type='process' | group count=count() by timestamp=timebucket('1h'), endpoint.os | transpose endpoint.os on timestamp +``` + +**Single number** (estimate_distinct for cardinality): + +```text +| group estimate_distinct(agent.uuid) +``` + +**Geo enrichment**: + +```text +| group count=count() by country=geo_ip_country(src.ip.address) | sort -count +``` + +**URL deep-link in table**: + +```text +| let Threat_URL = format("https://your-console.sentinelone.net/incidents/threats/%s/overview", threat_id) +| columns Computer=data.computer_name, Threat_URL, Path=data.file_path +``` + +--- diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/deployment.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/deployment.md new file mode 100644 index 0000000..8ae0b7c --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/deployment.md @@ -0,0 +1,226 @@ +# Deploying, validating, and troubleshooting a dashboard + +Deployment via the SDL API (udoId and CAS guard, duplicate handling), the pre-deploy parallel load test and verification, the escalation ladder for a hung dashboard, and the full pre-deploy checklist. Referenced from `SKILL.md`. + +## Deploying a dashboard via API + +Use the `sdl-api` skill to deploy. Dashboard config files live at paths like `/dashboards/my-dashboard-name`. + +### 1. Resolve the udoId, then write with a CAS guard + +```python +import json +from sdl_client import SDLClient + +client = SDLClient() +DASH_NAME = "/dashboards/soc-overview" + +# Resolve the name to its udoId. Only /dashboards/ files carry one, and a name +# can resolve to more than one file, so check the count before writing. +matches = [f for f in client.config_files() if f["name"] == DASH_NAME] +if len(matches) > 1: + raise SystemExit(f"{len(matches)} copies already share that name: {[m['udoId'] for m in matches]}") + +body = json.dumps(dashboard_json, indent=2) + +if matches: + cur = client.config_file(udo_id=matches[0]["udoId"]) # None if absent, does not raise + open(f"/tmp/{DASH_NAME.replace('/','_')}.{cur['version']}.bak.json", "w").write(cur["content"] or "{}") + res = client.put_config_file(udo_id=cur["udoId"], content=body, expected_version=cur["version"]) +else: + res = client.put_config_file(name=DASH_NAME, content=body) # first create only + +udo_id = res["udoId"] # record this; every later deploy addresses by udoId +``` + +The `expected_version` argument is a CAS guard against concurrent writes from the SDL UI or another script. + +### 2. Verify deployment by re-fetching (and grep for a canary) + +```python +time.sleep(3) # eventual-consistency window +verify = client.get_file(DASH_PATH) +deployed_content = verify.get("content", "") +assert verify.get("version") != cur_version, "version did not bump" +assert "" in deployed_content, "deploy did not include new content" +``` + +A `put_file` response of `{"status": "success"}` does not guarantee the new content was written, always re-fetch and grep for a canary string from the change. + +### 3. Never update a dashboard by name, address it by udoId + +The console's Configuration Files grid displays a dashboard as +`/dashboards/id/6554761743556608/AI Usage`. **That string is not a path.** The number is the +file's `udoId`; the real `name` is `/dashboards/AI Usage`. Writing to the display string returns +`no file exists at path`. + +`addConfigFile(name:)` updates in place for `/lookups/`, `/datatables/`, `/logParsers/` and +`/automaticLookups`, but **creates a duplicate** for `/dashboards/`. Create a dashboard by name +once (it has no `udoId` yet), then address it by `udoId` for every update after that. One tenant +reached 152 copies of `/dashboards/AI Usage` and 256 surplus dashboard files this way. +`sdl_put_file` refuses a path-addressed write to an existing dashboard. + +Resolve a name to its `udoId` with `sdl_list_files` (`pathPrefix: "/dashboards/"`). See +`sdl-api/references/config-file-graphql.md`. + +**Resolving a name can return more than one file, and they need not share a storage form.** +Measured on a live tenant: 16 dashboard names had duplicates, and in 11 of those a single +name-addressed copy (`udoId: null`) sat alongside the `udoId`-addressed ones. Two consequences: + +- Never assume a name resolves to exactly one file. Filter the listing, check the match count, + and stop for a human decision when it is greater than one. Picking the first match silently + edits an arbitrary copy. +- A name-addressed copy of a dashboard can exist. It is readable and writable by name, so a + script that only ever addressed dashboards by name may appear to work correctly while + operating on a file the console user never sees. + +Duplicates on this tenant cluster almost entirely on stock template names (`AI Usage` had 152 +copies, `EDR Data Collection Analysis` 25), while hand-authored names have none. Each install of +a template dashboard creates a new file, so a shared tenant accumulates copies over time +independently of anything the API does. + +### 4. Layout coordinates accumulate + +Panels are placed by `layout: {w, h, x, y}`. When appending a new section to an existing dashboard, compute the next `y` as `max(existing_y + existing_h)` across the tab, not by visual estimation. Off-by-a-few errors stack panels on top of each other and the UI will not flag this as an error. + +### 5. Test panels in the SDL UI before declaring done + +The SDL dashboard render engine has a longer query budget than the PowerQuery MCP. A query that times out in MCP validation may still render in the UI. Conversely, a query that returns from MCP may render slowly in the UI. The final smoke test for every dashboard is: open it in the UI, watch each tab load, confirm no panel spins indefinitely. + +After deploying, open in the SDL UI: **Visibility Enhanced → Dashboards** → select the dashboard by name. + +--- + +## Pre-deploy validation + +### The browser renderer is a separate execution path + +The SDL engine has three query surfaces: the V1 query API, the LRQ async API, and the in-browser dashboard renderer. The renderer has different timeouts, a stricter column-name parser, and a different concurrency model. A query that returns results instantly via the API can still hang the renderer. "All API queries pass" is necessary but not sufficient. The renderer is the only path that matters for dashboards, and it cannot be tested directly except by deploying and opening the page. + +The learnings below let you predict and eliminate renderer failures before deploy. + +### Parallel load test (run before every `put_file`) + +The browser fires all panel queries in parallel on load. Total dashboard load time ≈ slowest single panel + small per-panel render overhead. Always run a parallel load test before deploying a new or significantly modified dashboard: + +```python +import concurrent.futures, time + +def run_one(panel_query): + c = SDLClient() + # auth setup ... + t0 = time.time() + try: + res = c.power_query(query=panel_query, start_time="24h") + return ("OK", time.time() - t0, res.get("matchingEvents") or 0) + except Exception as e: + return ("FAIL", time.time() - t0, str(e)[:200]) + +queries = [p["query"] for tab in dashboard["tabs"] for p in tab["graphs"] + if p.get("graphStyle") != "markdown" and p.get("query")] + +wall_t0 = time.time() +with concurrent.futures.ThreadPoolExecutor(max_workers=10) as pool: + results = list(pool.map(run_one, queries)) +wall_clock = time.time() - wall_t0 + +print(f" Total serial: {sum(r[1] for r in results):.1f}s") +print(f" Wall-clock parallel: {wall_clock:.1f}s <- expect this in browser") +print(f" Slowest single: {max(r[1] for r in results):.1f}s") +``` + +**Acceptance thresholds:** slowest single panel ≤ 2s, wall-clock parallel ≤ 5s, zero failures. If the slowest panel exceeds 2s, identify it and rewrite: replace `group` with `top K`, narrow the initial filter, raise the timebucket granularity, or split the dashboard. + +### Deploy-and-verify: sleep before re-fetching + +`put_config_file` returns synchronously, but the file propagates across replicas with eventual consistency. Re-reading ~100ms after a successful write can report the file as absent. Always wait: + +```python +res = c.put_config_file(udo_id=udo_id, content=new_content, expected_version=cur_version) +assert res.get("udoId") == udo_id + +import time +time.sleep(3) # eventual-consistency window + +post = c.get_file(DASH_PATH) +assert post.get("version") != cur_version # version bumped +``` + +Without the sleep, verification looks like a deploy failure even when the deploy succeeded. + +--- + +## Escalation ladder when a deployed dashboard hangs + +1. **Log out and log back in.** The SDL UI caches panel render state in the session. After a `put_file`, the browser can serve a stale render from the prior version even though the underlying config changed. A fresh login clears session state completely. Try this before any config investigation when the query is confirmed to return data. +2. **Hard refresh** (`Ctrl+Shift+R` / `Cmd+Shift+R`). Eliminates cached state from a previous broken version. Resolves ~10% of "still hung" reports. +3. **Check dev-tools network tab.** If panel queries are NOT being fired, the renderer is stuck before any HTTP call. Cause is structural (layout/options/JSON parse), not query performance. If queries ARE firing, record the slowest and move to step 3. +4. **Run the slow panel's query in isolation via the V1 API.** If it returns fast, the issue is renderer-side (column names, `transpose`, panel options). If it is slow, optimise the query. +5. **Reduce panel count by 50%.** If the dashboard now loads, the issue was concurrency or memory in the renderer. Add panels back 25% at a time until a regression isolates the offender. +6. **Diff against a working reference dashboard in the same tenant.** `list_files /dashboards/`, `get_file` on a working dashboard, compare top-level keys, panel `layout` shape, `options` keys, and `graphStyle`-specific fields. Working dashboards in the same tenant are more reliable ground truth than any external documentation, because rendering rules drift between SDL releases. +7. **Roll back.** Always keep a backup of the prior dashboard JSON before `put_file`-ing a new version. Restore via `put_file(expected_version=current)` to unblock analysts while iterating offline. + +--- + +## Pre-deploy checklist + +Run this before every `put_file`. Items marked **[scripted]** are checked automatically by `scripts/panel_safety_check.py`. + +```text +PRE-AUTHORING +[ ] Live data-source enumeration confirms every dataSource.name used by the dashboard exists +[ ] V1-query schema discovery run for every source; field list saved for the session +[ ] Discriminator field validated for every event.type the dashboard counts +[ ] No panel relies on a field that is only present in raw_data (or, if it does, the panel + is a number/selective-table that won't time out under full-text) + +JSON STRUCTURE (scripted) +[ ] Every panel has explicit x, y, w, h in layout [scripted] +[ ] When appending to existing dashboard, next y = max(existing_y + existing_h) +[ ] No panel uses `transpose on timestamp` where values may contain hyphens +[ ] No `graphStyle: "area"` panel with a `query:` field (must use `plots:`) [scripted] +[ ] Markdown panels use `markdown:` field, not `content:` [scripted] +[ ] No content after `| transpose` (transpose must be terminal) [scripted] +[ ] No hyphenated arithmetic (`x-y` without spaces) [scripted] + +QUERY HYGIENE (scripted) +[ ] All number panels end with `| limit 1` [scripted] +[ ] All table panels end with explicit `| limit N` [scripted] +[ ] All time-series panels use a timebucket appropriate for duration +[ ] min/max(timestamp) columns wrapped in simpledateformat(...) with tz +[ ] Any millisecond-typed time field multiplied by 1000000 before simpledateformat +[ ] Hostname/value-list filters use `field in (...)` not `field matches '(...)'` +[ ] Numeric fields wrapped with number() before arithmetic / sum / avg +[ ] No use of count_if / sum(if(predicate, value, 0)) / mid-pipeline | union (union-first is allowed) / named subqueries [scripted] +[ ] No `\\s` / `\\d` regex escapes inside `matches '...'` [scripted] +[ ] No full-text predicate combined with timebucket+transpose [scripted] +[ ] Number panels use only {format, precision, suffix} in options + +NAMING & SEMANTICS +[ ] Every panel title reads as an SLA-grade claim (no overstated counts) +[ ] Distinct event populations under the same event.type live in separate sections + each with a markdown header explaining the split +[ ] Panels that may legitimately return 0 have a markdown header explaining the + SOC-positive interpretation +[ ] 3-5 sample events checked to verify which field carries the user semantic per event ID +[ ] Machine-account filter applied on user-facing panels + +PERFORMANCE & LOAD +[ ] Parallel load test passes: wall-clock <= 5s, slowest panel <= 2s, zero failures +[ ] All time-series panels obey the timebucket-vs-duration table + +DEPLOYMENT +[ ] Backup of current dashboard JSON saved (for rollback) +[ ] put_file called with expected_version of the current deployed copy +[ ] sleep(3) before re-fetching to verify deploy +[ ] Re-fetched content greps for a canary string from the change +[ ] Existing dashboard addressed by udoId from sdl_list_files, not by name (name-addressed writes are for the first create only) + +POST-DEPLOY (MANDATORY) +[ ] scripts/validate_dashboard.py run; per-panel evidence JSON persisted +[ ] Markdown evidence file emitted alongside the JSON +[ ] scripts/render_validation_pdf.py run; PDF report delivered with the dashboard +[ ] PDF Appendix lists every empty-result panel with a SOC-meaningful interpretation +``` + +--- diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/evidence-report-template.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/evidence-report-template.md index 0b46f9d..ad40ba5 100644 --- a/plugins/s1-secops-skills/skills/sdl-dashboard/references/evidence-report-template.md +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/evidence-report-template.md @@ -97,7 +97,8 @@ Sample rows (first 3): (list every panel where verdict == EMPTY, with matchCount and the operator's SOC-meaningful interpretation) -``` + +```text The Appendix is mandatory whenever any panel returns 0 rows. @@ -168,7 +169,7 @@ Pass this file to `render_validation_pdf.py` with `--interpretations`. Any panel ## 6. Worked example: how the report looks for a passing 12-panel dashboard -``` +```text Total panels: 12 (runnable: 10, markdown/skipped: 2) PASS: 8 WARN: 0 diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/lessons-learned.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/lessons-learned.md index f89885b..4af40d7 100644 --- a/plugins/s1-secops-skills/skills/sdl-dashboard/references/lessons-learned.md +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/lessons-learned.md @@ -12,7 +12,7 @@ The companion documents in this directory are tactical (cheatsheets, copy-paste Different tenants have different data sources connected; even between sessions on the same tenant, the catalogue can drift. Never start a dashboard from a remembered list of `dataSource.name` values. Re-enumerate every session: -``` +```text | group UniqueDataSourceNames = array_agg_distinct(dataSource.name) | limit 1000 ``` @@ -100,7 +100,7 @@ Rules: When a field needed for the panel is buried inside `raw_data` (JSON envelope) rather than parsed to a structured column, the only way to filter on it is a full-text predicate against `raw_data`. Example pattern: -``` +```text dataSource.name='' event.type='' '' ``` @@ -155,7 +155,7 @@ When two semantically distinct event populations exist within the same `event.ty A single source can emit multiple log subtypes under the same `event.type` (policy events vs detection events, header logs vs body logs). Don't assume `event.type` partitions the source cleanly. Run a quick exploration query: -``` +```text dataSource.name='' | group hits=count() by event.type, | sort -hits @@ -206,6 +206,7 @@ Each panel takes 1 to 4 seconds typical. Batch into chunks of about 10 per shell ### 7.3 Per-panel evidence captured For each panel, record: + - `ok`: did the query execute? - `elapsed_s`: wall-clock time - `row_count`: number of result rows @@ -236,29 +237,42 @@ The PDF must include an Appendix that lists every empty-result panel with its SO ## 8. Deployment safety -### 8.1 Always read existing version before put_file +### 8.1 Resolve the udoId, then write with a CAS guard ```python -existing = c.get_file(path) -res = c.put_file(path=path, content=body, expected_version=existing["version"]) +matches = [f for f in c.config_files() if f["name"] == name] +if len(matches) > 1: + raise SystemExit(f"{len(matches)} copies share that name: {[m['udoId'] for m in matches]}") + +if matches: + cur = c.config_file(udo_id=matches[0]["udoId"]) + res = c.put_config_file(udo_id=cur["udoId"], content=body, expected_version=cur["version"]) +else: + res = c.put_config_file(name=name, content=body) # first create only ``` -The `expected_version` argument is a CAS guard against concurrent writes from the SDL UI or another script. +`expected_version` is a CAS guard against concurrent writes from the SDL UI or another script, and +is enforced on both address forms. A name-addressed write to an existing dashboard is refused +because it creates a duplicate instead of updating. ### 8.2 Verify deployment by re-fetching ```python -verify = c.get_file(path) +verify = c.config_file(udo_id=udo_id) deployed_content = verify.get("content", "") assert verify.get("version") != cur_version, "version did not bump" assert "" in deployed_content, "deploy did not include new content" ``` -A `put_file` response of `{"status": "success"}` does not guarantee the new content was written. Always re-fetch and grep for a known canary string from the change. +A successful write response does not guarantee the new content landed. Always re-read and grep for a known canary string from the change. -### 8.3 Avoid duplicate dashboard paths +### 8.3 Never update a dashboard by name -The SDL UI's Save button writes to `/dashboards/id//`. The file API can write to either that path OR the simpler `/dashboards/`. Pick one canonical path before the first deploy and never mix. Each deploy to the alternate path creates a silent duplicate alongside the UI-saved copy. Recommend `/dashboards/` for hand-authored dashboards and `/dashboards/id//` only for files originally saved through the UI. +`/dashboards/id//` in the console grid is a display string, not a path; writing to it +returns `no file exists at path`. Duplicates come from `addConfigFile(name:)` creating a copy +rather than updating, so every name-addressed deploy adds one. Create by name once, then address +by `udoId` for every write after that. A name can also resolve to several files, including a +name-addressed copy alongside udoId-addressed ones, so check the match count before writing. ### 8.4 Layout coordinates accumulate @@ -373,7 +387,7 @@ The fabrication was only caught when the user asked to re-verify the schemas, at This is the full endpoint device inventory, not pipeline metrics. Key confirmed fields: -``` +```text device.agent.uuid device.name device.os.{name,version,type} device.agent.network_status device.agent.network_quarantine_enabled device.agent.is_active device.agent.is_decommissioned device.agent.is_uninstalled @@ -390,7 +404,7 @@ Fields that do NOT exist in this source: `entity.uid`, `entity_result.*`, `agent This is the management console activity and Hyperautomation workflow execution log (`sca:RetentionType = 'ACTIVITY_LOG'`), not pipeline metrics. Key confirmed fields: -``` +```text activity_type (numeric, e.g. 9207 = workflow execution event; NOT a string) activity_uuid primary_description secondary_description data.workflow_id data.workflow_name data.workflow_execution_url @@ -434,6 +448,7 @@ for _ in range(N): ``` Key rules: + - Never put `sleep N` inside a `start_process` call where `N * iterations > 90s`: the MCP layer will time out the whole call. - Write completion sentinels to the output file (`print("DONE: ...", flush=True)`) so polling can detect finish without relying on process exit alone. - Use `flush=True` on every progress `print()` so the output file is readable while the script is still running. @@ -447,7 +462,7 @@ Key rules: **Slow (times out on large sources):** -``` +```text dataSource.name='misconfiguration' severity_id=* | let sev = number(severity_id) | filter sev >= 4 @@ -457,7 +472,7 @@ dataSource.name='misconfiguration' severity_id=* **Fast (index-level predicate):** -``` +```text dataSource.name='misconfiguration' severity_id >= 4 | group count() | limit 1 @@ -511,7 +526,7 @@ renders as "No results found" even when PowerQuery confirms tens of thousands of **Fix:** replace any `plots`-based timeline panel that uses `unmapped.*` fields with a PowerQuery `stacked_bar` using `| transpose`: -``` +```text dataSource.name='FortiGate' event.type='traffic' unmapped.action in ('deny', 'accept', 'close') | group count=count() by timestamp=timebucket('1h'), action=unmapped.action | transpose action on timestamp @@ -525,7 +540,7 @@ The PowerQuery engine fully supports `unmapped.*` fields. The `stacked_bar` + tr The `plots` array entries must use `"facet": "count"` (no parentheses). The error when parentheses are present: -``` +```text Couldn't load content field=[DashboardPlotQuery.plotIndex] error=[Facet for plot at index: 0 is invalid] @@ -537,7 +552,7 @@ Older community examples and some internal docs show `"facet": "count()"`; this Before building a panel with a field presence filter, confirm the field is actually populated: -``` +```text dataSource.name='FortiGate' event.type='app-ctrl' app_name=* | group count=count() | limit 1 @@ -552,6 +567,7 @@ If this returns 0, every panel using `app_name=*` as a filter will return nothin The SDL UI renders the `"title"` string as the panel header. Any heading prefix (`##`) embedded in the `title` field produces raw markup in the panel header, and if the same heading also opens the `"markdown"` body, the heading appears twice. **Wrong pattern (causes doubled heading):** + ```json { "title": "## Policy Enforcement Intelligence", @@ -561,6 +577,7 @@ The SDL UI renders the `"title"` string as the panel header. Any heading prefix ``` **Correct pattern:** + ```json { "title": "Policy Enforcement Intelligence", @@ -579,7 +596,7 @@ Security operations dashboards (firewall visibility, threat triage, VPN health, When mapping a numeric code field to a human-readable label (e.g. protocol numbers to names), use ternary `?:` chaining, not `if()`: -``` +```text | let proto = connection_info.protocol_num='6' ? 'TCP' : connection_info.protocol_num='17' ? 'UDP' : connection_info.protocol_num='1' ? 'ICMP' @@ -592,7 +609,7 @@ When mapping a numeric code field to a human-readable label (e.g. protocol numbe `traffic.bytes_in` and `traffic.bytes_out` on FortiGate events are string-typed at the index level. Arithmetic without `number()` returns NaN silently: -``` +```text | let b_out = number(traffic.bytes_out) | group gb_sent = sum(b_out) / 1073741824 ``` @@ -620,7 +637,7 @@ The root cause of the `unmapped.srcip` miss: schema discovery was performed agai Minimum check before authoring any panel that uses a shared field like `src_endpoint.ip`: -``` +```text dataSource.name='FortiGate' event.type='vpn' src_endpoint.ip=* | group count=count() | limit 1 @@ -638,7 +655,7 @@ Pass 1: use `{regex=\\S+}` (non-whitespace) to capture the entire quoted value i Pass 2: use `{regex=[A-Z0-9./_-]+}` (or appropriate character class) to extract just the clean value from the quoted string, skipping the leading `"`. -``` +```text | parse "app=$raw_app{regex=\\S+}$" from message | parse "$app_name{regex=[A-Z0-9./_-]+}$" from raw_app ``` diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/panel-type-cheatsheet.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/panel-type-cheatsheet.md index 6f5d2ed..6419d26 100644 --- a/plugins/s1-secops-skills/skills/sdl-dashboard/references/panel-type-cheatsheet.md +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/panel-type-cheatsheet.md @@ -1,5 +1,7 @@ # Panel Type Cheatsheet +> Full per-panel JSON catalog with complete examples: [`panel-types.md`](panel-types.md). This file is the quick one-line-per-panel cheatsheet. + ## Complete panel type reference | graphStyle | Panel | Query type | Notes | @@ -88,6 +90,7 @@ The bullet panel shows an actual value against a target (SLA) with colored range ``` **Color tokens:** + - Semantic: `var(--g-dv-semantic-positive)`, `var(--g-dv-semantic-warning)`, `var(--g-dv-semantic-negative)`, `var(--g-dv-semantic-caution)` - Blue sequential (5-step): `var(--g-dv-sequential-default-5-step-1)` through `var(--g-dv-sequential-default-5-step-5)` @@ -124,6 +127,7 @@ Use `array(0, 1, 2).expand()` to fan a single event row into multiple Sankey lin ``` **When to use Sankey:** + - Login flow: country → process → user → host → result - Process tree: parent → child process chains - Network: file hash → IP → country (with TI enrichment markers) @@ -150,6 +154,7 @@ Use `array(0, 1, 2).expand()` to fan a single event row into multiple Sankey lin ``` **When to use bubble:** + - Port scan detection (x=distinct ports, y=distinct IPs, size=total connections) - User behaviour: login volume vs unique hosts vs failures - Process outliers: execution count vs unique endpoints vs file write count @@ -181,6 +186,7 @@ Use `array(0, 1, 2).expand()` to fan a single event row into multiple Sankey lin ``` **When to use heatmap:** + - Off-hours process execution by user (insider threat pattern) - Hourly event volume spikes across sources - Day-of-week × hour activity patterns @@ -347,7 +353,8 @@ identifier parser); use numbered prefixes like `1.` for ordering. Rows must be i stage first for vertical orientation). Live-validated stage/value shape (2026-07-29, severity thresholds on `dataSource.name='alert'`): -``` + +```text | union ( dataSource.name='alert' severity_id=* | group Count = count() | let Stage = '1. All alerts' | columns Stage, Count ), ( dataSource.name='alert' severity_id=* | let sev = number(severity_id) | filter sev >= 3 | group Count = count() | let Stage = '2. Medium and above' | columns Stage, Count ), @@ -374,11 +381,13 @@ Live-validated stage/value shape (2026-07-29, severity thresholds on `dataSource ``` **Alert lifecycle variant** (using `datasource` UQL; union-first, live-validated 2026-07-29): + ```javascript query: "| union\n( | datasource alerts | group Count = count() | let Stage = '1. All alerts' | columns Stage, Count ),\n( | datasource alerts where (status in ('NEW','IN_PROGRESS')) | group Count = count() | let Stage = '2. Unresolved' | columns Stage, Count ),\n( | datasource alerts where (status = 'NEW' AND !(assigneeUserId=*)) | group Count = count() | let Stage = '3. Unassigned' | columns Stage, Count )\n| sort Stage" ``` **When to use funnel:** + - Detection coverage: events → storylines → indicators → alerts - Alert lifecycle: total → open → unassigned → breached SLA - IR workflow: detected → triaged → investigated → resolved @@ -418,12 +427,14 @@ The "Filter" panel in the UI is implemented as a `parameters` array on the tab o ``` **Usage in a PQ panel query:** -``` + +```text endpoint.os = #OS# | group count=count() by src.process.name ``` **Key rules:** + - Parameters are tab-scoped (not global). Each tab that needs the filter must declare its own `parameters` entry. - `facet` populates dropdown choices from live SDL data. Omit `facet` for free-text input. - `defaultValue: "*"` means "match all" and typically appears as "All" in the dropdown. @@ -442,6 +453,16 @@ endpoint.os = #OS# - **Scattered bubble**: First 3 numeric columns = x, y, size. Add `label` column for point labels. - **Heatmap/multi-series line**: time mode uses `timebucket()` in `group by` with the anchor named `timestamp` (`transpose field on timestamp`). Heatmap also supports a categorical x-axis (S-26.x+): `group by , | transpose on ` plus `xAxis: "grouped_data"`. - **Bullet**: First numeric column = actual value; second numeric column = SLA/target. Third column = row label. +- **Categorical bar (`stacked_bar` over a category, not time)**: set `xAxis: "grouped_data"` and end the query with `| columns , `. There are only two valid `xAxis` values, `"time"` and `"grouped_data"`. **Anything else silently degrades to time-axis behaviour** rather than erroring on the config: the panel renders `Couldn't load content, The first column of power query output should have numeric value in epoch s/ms/µs/ns, but is `. That message points at the query, but the bug is in the panel config, so read it as "wrong `xAxis`", not "wrong query" (live-confirmed 2026-08-07 with `xAxis: "value"`). Working shape: + + ```javascript + { + graphStyle: "stacked_bar", + xAxis: "grouped_data", + query: "dataSource.name='SentinelOne' event.type=*\n| group count=count() by type=event.type\n| sort -count\n| limit 10\n| columns type, count" + } + ``` + - **Tabbed table**: `tabbed: "true"` and `tabVariant: "tile"` are both required. Outer `query` is the fallback. - **Null check: always use `field=*`, never `field != null`**: `field=*` is the SDL predicate for "field is present and non-null". `field != null` is parsed as a literal string comparison to `"null"` in old-style filter syntax and returns wrong results or a parse error. Applies in both the initial filter predicate (before the first `|`) and in `| filter` commands: `dataSource.name='alert' severity_id=*` not `severity_id != null`. - **Zero suppression: `| filter count > 0` after group**: After `| group count=count() by ...`, SDL can produce zero-count rows for sparse key combinations (common after `transpose` or wide key spaces). These render as empty heatmap cells and spurious table rows. Always add `| filter EventCount > 0` (or `| filter value > 0`) after any group that feeds a heatmap, donut, or table panel where zero rows are meaningless. diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/panel-types.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/panel-types.md new file mode 100644 index 0000000..b9acf66 --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/panel-types.md @@ -0,0 +1,300 @@ +# SDL dashboard panel types and JSON + +Full per-panel JSON catalog for every SDL dashboard `graphStyle`. This is the companion to the one-line-per-panel [`panel-type-cheatsheet.md`](panel-type-cheatsheet.md): the cheatsheet summarizes each panel, this file holds the complete JSON examples. Referenced from the Panel types section of `SKILL.md`. + +## Panel types and JSON + +Every panel is an object inside `graphs`. The `graphStyle` property picks the panel type. + +### Layout + +Every panel **must** have explicit `x`, `y`, `w`, `h` in its `layout` object. Dashboards with many panels (observed at 18+) where `x`/`y` are omitted can hang the browser renderer indefinitely, the auto-layout pass appears to loop on collision detection when panels stack at the implicit (0,0) origin. The symptom is the browser tab becoming unresponsive before any query fires. + +```json +"layout": { "w": 30, "h": 14, "x": 0, "y": 0 } +``` + +Use this helper to pack panels into the 60-wide grid when generating JSON: + +```python +class Grid: + def __init__(self, width=60): + self.W = width; self.x = 0; self.y = 0; self.row_h = 0 + def place(self, w, h): + if self.x + w > self.W: + self.y += self.row_h; self.x = 0; self.row_h = 0 + layout = {"w": w, "h": h, "x": self.x, "y": self.y} + self.x += w; self.row_h = max(self.row_h, h) + return layout + def newline(self): + if self.x > 0: + self.y += self.row_h; self.x = 0; self.row_h = 0 +``` + +--- + +### Line / Area chart (time-series, multi-plot) + +`graphStyle`: `"line"` or `"area"` (or `"stacked"` for stacked area) + +Best for: event rates over time, multi-metric comparison, trend lines. + +```json +{ + "title": "Threat confidence over time", + "graphStyle": "area", + "lineSmoothing": "straightLines", + "yScale": "linear", + "plots": [ + { "filter": "event.category='indicators' indicator.category='Ransomware'", "label": "Ransomware", "facet": "count" }, + { "filter": "event.category='indicators' indicator.category='Exploitation'", "label": "Exploitation", "facet": "count" } + ] +} +``` + +For a **PowerQuery-driven** line chart (needed for complex grouping): + +```json +{ + "title": "Login attempts over time", + "graphStyle": "line", + "lineSmoothing": "straightLines", + "query": "event.login.loginIsSuccessful=false | group count() by timestamp=timebucket('1h'), endpoint.name | transpose endpoint.name on timestamp" +} +``` + +--- + +### Stacked bar chart + +`graphStyle`: `"stacked_bar"` or `"bar"` + +Best for: category breakdowns over time, per-group counts. + +```json +{ + "title": "Threats by confidence level per day", + "graphStyle": "stacked_bar", + "xAxis": "time", + "yScale": "linear", + "query": "index='activities' activity_type in ('18','19','20') | group count=count() by timestamp=timebucket('1 day'), data.confidence_level | transpose data.confidence_level on timestamp" +} +``` + +For a **grouped-data X-axis** (not time): + +```json +{ + "graphStyle": "stacked_bar", + "xAxis": "grouped_data", + "query": "event.category='indicators' | group count=count() by indicator.category | sort -count" +} +``` + +--- + +### Pie / Donut chart + +`graphStyle`: `"pie"` or `"donut"` + +Query **must return exactly one text column and one numeric column**. + +```json +{ + "title": "Top indicator types", + "graphStyle": "donut", + "maxPieSlices": 10, + "dataLabelType": "PERCENTAGE", + "query": "event.category='indicators' | group count() by indicator.category" +} +``` + +--- + +### Table panel + +`graphStyle`: `"table"` (or omit, table is the default for PowerQuery panels) + +Best for: raw event lists, top-N tables, IOC lookups. + +```json +{ + "title": "Outbound PowerShell connections", + "graphStyle": "table", + "query": "src.process.name contains 'powershell' dst.ip.address=* | let rfc1918 = not (dst.ip.address matches '((127\\..*)|(192\\.168\\..*)|(10\\..*)|(172\\.1[6-9]\\..*)|(172\\.2[0-9]\\..*)|(172\\.3[0-1]\\..*)).*') | filter rfc1918=true | group hits=count() by IP=dst.ip.address | sort -hits" +} +``` + +--- + +### Number panel (gauge) + +`graphStyle`: `"number"` + +Query must reduce to a single number (use `group count()`, `estimate_distinct()`, etc.). + +```json +{ + "title": "Distinct active endpoints", + "graphStyle": "number", + "query": "| group estimate_distinct(agent.uuid) | limit 1", + "options": { + "format": "auto", + "precision": "0", + "suffix": " endpoints" + } +} +``` + +> **No "millions" (or thousands) number format.** SDL number panels scale only via `format: "auto"` (which may render K/M/B); there is no explicit millions option. To force a specific unit, divide in the query and label the title: `... | group ev = count() | let Events_M = ev / 1000000 | columns Events_M | limit 1` with title `"Total Events (M)"`. Always carry the unit (GiB, events, min, count, ratio) in the title or `suffix`, a number panel renders only a bare value. +> +> **Options, stick to the minimal set.** Production reference dashboards only set `{format, precision, suffix}`. Fields like `backgroundColor` and `color` are documented in some places but are not consistently honoured by the renderer, at best silently ignored, at worst the panel renders blank or hangs. Do not add them until tested against the specific tenant. + +With trend indicator (S-25.1.5+): + +```json +{ + "graphStyle": "number", + "trendConfig": { + "enabled": true, + "indicators": { + "number": { "calculationType": "PERCENTAGE", "enabled": true }, + "arrow": { "enabled": true }, + "upwardsMeaning": "POSITIVE" + } + }, + "query": "...", + "title": "Alert volume (vs previous period)" +} +``` + +--- + +### Honeycomb panel (heat map) + +`graphStyle`: `"honeycomb"` + +Query must return at least one text column and one numeric column. Good for per-site or per-endpoint heatmaps. + +```json +{ + "title": "File creation activity by endpoint", + "graphStyle": "honeycomb", + "query": "src.process.tgtFileCreationCount=* | group total=sum(src.process.tgtFileCreationCount) by site=site.id, endpoint=agent.uuid | let max=overall_max(total), min=overall_min(total) | let normalized=((total-min)/(max-min))*100 | columns Site=site, Endpoint=endpoint, Normalized=normalized", + "honeyCombColor": { "hover": "#8ED4FB", "label": "Blue", "value": "#0998E7" }, + "honeyCombThresholds": ["0","25","50","75"], + "honeyCombGroupBy": "Site", + "honeyCombLinkTo": "/dash?page=Endpoints+-+Overview¶ms=site%3D[Site]%26endpoint%3D[Endpoint]" +} +``` + +--- + +### Heatmap panel (time OR categorical 2D matrix) + +`graphStyle`: `"heatmap"` + +A 2D matrix where color intensity is the aggregated value of each cell. As of S-26.x the x-axis can be **time OR a category**, and cells can carry **in-cell data labels**. Distinct from `honeycomb` (free-form hex cells). In every mode the **anchor column** (the field named after `on` in `transpose`) becomes the x-axis, and the transposed field's values become the y-axis rows. + +**Two x-axis modes:** + +| Mode | x-axis | Query shape | `xAxis` key | +|---|---|---|---| +| Time (classic) | timebuckets | `... \| group =count() by , timestamp=timebucket('1h') \| transpose on timestamp` | omit (or `"time"`) | +| Categorical | a category column | `... \| group =count() by , \| transpose on ` | `"grouped_data"` | + +**Heatmap options (live-confirmed on S-26.x, captured from what the SDL UI writes):** + +| Key | Value | Effect | +|---|---|---| +| `"xAxis"` | `"grouped_data"` | Render a non-time first column as a categorical x-axis. Without it the renderer expects an epoch first column, so a category query renders blank. | +| `"showDataLabels"` | `"true"` (string, not boolean) | Print each cell's value inside the cell. Omit for color-only. | +| `"colorScheme"` | `"red"` \| `"blue"` \| `"green"` (more in the UI) | Named color ramp; `red`, `blue`, `green` confirmed to render. | +| `"colorSchemeOrder"` | `"standard"` \| `"inverted"` | Direction of the ramp; `"inverted"` flips which end is the hot color. | +| `"linkConfig"` | `{ "template": "" }` | Make cells click-through to a URL, e.g. a pre-filtered Unified Alerts view. | + +**Categorical heatmap example** (alerts severity x product, data labels + click-through, live-validated): + +```json +{ + "title": "Alerts by severity and product", + "graphStyle": "heatmap", + "query": "dataSource.name='alert' severity_id=* finding_info.uid=* | group count=count() by Product=metadata.product.name, severity_id | transpose severity_id on Product", + "xAxis": "grouped_data", + "showDataLabels": "true", + "colorScheme": "red", + "colorSchemeOrder": "standard", + "numberOfRanges": 5, + "rangesCreation": "automatic", + "heatmapRangeConfig": ["-∞", "", "", "", "", "∞"], + "linkConfig": { "template": "https://.sentinelone.net/incidents/unified-alerts?viewType=all" }, + "layout": { "h": 20, "w": 30, "x": 0, "y": 0 } +} +``` + +**Time heatmap example** (classic, x-axis = time, requires `timebucket()` and the anchor column named `timestamp`): + +```json +{ + "title": "Identity Logon Activity by User [heatmap]", + "graphStyle": "heatmap", + "query": "dataSource.name='Identity' unmapped.type='Logon Success' user.name=* user.name != ''\n| group EventCount=count() by user_name=user.name, timestamp=timebucket('1h')\n| filter EventCount > 0\n| filter user_name in ('alice', 'bob', 'svc_adconnector', 'DC01$')\n| transpose user_name on timestamp", + "colorScheme": "red", + "colorSchemeOrder": "standard", + "numberOfRanges": 5, + "rangesCreation": "automatic", + "heatmapRangeConfig": ["-∞", "", "", "", "", "∞"], + "layout": { "h": 22, "w": 30, "x": 0, "y": 0 } +} +``` + +**Critical `heatmapRangeConfig` rule:** `rangesCreation: "automatic"` means SDL computes the threshold boundaries from the data. The `heatmapRangeConfig` array must use empty strings `""` for all middle elements. Providing explicit values (e.g. `"10"`, `"50"`) conflicts with automatic mode and causes the panel to render blank with no error. Correct form for 5 ranges: `["-∞", "", "", "", "", "∞"]` (N+1 elements for N ranges). Do not add explicit middle values unless you also change `rangesCreation` away from `"automatic"`. + +**Pre-filter to top-N categories before transpose:** After `transpose`, any (category, anchor) cell with no events becomes null. To keep the heatmap readable and avoid sparse null columns, use `| filter in (...)` to pin the transposed set to the most active values. Find candidates first: `| group count=count() by | sort -count | limit 15 | columns `. + +**When to use heatmap:** + +- Categorical x-axis: alerts by severity x product or asset category, detections by OS x technique, any (category x category) count matrix. +- Time x-axis: login activity per user over time (insider threat, off-hours spikes), hourly event volume across sources or endpoints, day-of-week x hour activity patterns. + +--- + +### Distribution graph + +`graphStyle`: `"distribution"` + +Shows frequency distribution of a numeric field (X = value range, Y = count). Use `filter` and `facet` (not `query`). + +```json +{ + "title": "Distribution of outbound destination ports", + "graphStyle": "distribution", + "filter": "event.network.direction='OUTGOING'", + "facet": "src.port.number" +} +``` + +--- + +### Markdown panel + +`graphStyle`: `"markdown"` + +Accepts GitHub-flavored Markdown. Good for section headers, links, or explanations. + +> **CRITICAL:** the body field is `markdown`, **not** `content`. A panel with +> `"content": "..."` is created successfully and renders as a **blank tile with +> no error**, the API accepts it, the UI just has nothing to display. Always +> use `"markdown": "..."`. +> +> **Title duplication:** the SDL UI renders the `"title"` field as a header above the panel body. Do NOT repeat the same heading inside the `"markdown"` body. A common mistake is setting `"title": "## Policy Enforcement"` (with the `##` markdown prefix) and then starting the markdown body with `## Policy Enforcement\nDescription...`; this produces the heading twice. Keep the `title` field as plain text and put only the descriptive prose (no repeated heading) inside `"markdown"`. Also: a markdown panel with NO `title` key renders an "Untitled" header in S-26.1 (observed live), so always set a short plain-text `title`. + +```json +{ + "title": "About this dashboard", + "graphStyle": "markdown", + "markdown": "This dashboard tracks **threat activity** across all managed endpoints.\n\n[Open Event Search](/logs)" +} +``` + +--- diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/parameters-and-filters.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/parameters-and-filters.md new file mode 100644 index 0000000..c1500f0 --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/parameters-and-filters.md @@ -0,0 +1,108 @@ +# Parameters and filters (dynamic filtering) + +Deep detail on SDL dashboard dynamic filtering: `filters[]`, `#VarName#` substitution, and `parameters[]` behavior across flat and TABBED dashboards. Referenced from `SKILL.md`. + +## Parameters and filters (dynamic filtering) + +SDL has two distinct filtering mechanisms that look similar but behave very differently depending on dashboard type. + +### `filters[]`: use this in TABBED dashboards (actually works) + +`filters[]` declared inside a tab object creates a live facet-based filter widget. Selecting a value from the dropdown applies that filter to **all panels in the tab** in real time. This is the correct filtering mechanism for `configType: "TABBED"` dashboards. Confirmed working. + +```json +{ + "tabName": "Investigation", + "filters": [ + { "facet": "metadata.product.name", "name": "Alert Product" }, + { "facet": "endpoint.name", "name": "Endpoint" } + ], + "graphs": [...] +} +``` + +The dropdown options are populated dynamically from live field values in the current time range. No query changes needed, SDL injects the filter automatically. + +### `#VarName#` substitution: works in FLAT and TABBED dashboards; refiltering applies on Search + +`#VarName#` query injection is confirmed working in flat dashboards (no `configType`, no `tabs`, top-level `parameters` and `graphs`; see `parameter_examples-v1.0.json`). + +On a `configType: "TABBED"` dashboard, live visual verification (2026-07-29): a tab-level `parameters` entry renders its dropdown, a panel query using `#VarName#` renders WITHOUT error with the `defaultValue` substituted, and selecting a dropdown value re-filters the panel to exactly that value once the user presses **Search** (the dropdown does not auto-refresh the view). Test panel: `dataSource.name=#SrcName#` with facet `dataSource.name`, defaultValue `"*"`. The earlier claim that TABBED passes the literal `#` and throws `Don't understand [#]` was wrong; remember the Search-to-apply gotcha when demoing. + +Flat dashboard example (the only context where `#VarName#` works): + +```json +{ + "parameters": [ + { + "name": "Specified Tag", + "values": [ + { "label": "All", "value": "*" }, + { "label": "Log Volumes", "value": "'logVolume'" } + ], + "defaultValue": "*" + } + ], + "graphs": [ + { + "query": "tag=#Specified Tag# | group count=count() by serverHost", + "title": "Count by Tag" + } + ] +} +``` + +Pre-quoting rule: if the field requires string matching, embed single quotes in the value string: `"'logVolume'"` so substitution produces `tag='logVolume'`. Use `"*"` (no inner quotes) for wildcard presence filter. + +### `parameters[]` in TABBED dashboards: UI-only chrome + +`parameters[]` declared inside a tab (with `facet` or `values`) renders a dropdown in the tab header but does NOT apply any filter to panel queries. It is purely decorative UI. The `filters[]` mechanism described above is the functional equivalent. + +```json +{ + "parameters": [ + { + "name": "Product", + "label": "Alert Product", + "values": [ + { "label": "STAR", "value": "STAR" }, + { "label": "CWS", "value": "CWS" }, + { "label": "EDR", "value": "EDR" } + ], + "defaultValue": "STAR" + } + ] +} +``` + +```json +{ + "parameters": [ + { + "name": "Site", + "label": "Site", + "facet": "s1_detection_metadata.site_name", + "defaultValue": "*" + } + ] +} +``` + +For user-friendly dropdown labels: + +```json +{ "name": "region", + "values": [ + { "label": "East Coast", "value": "us-east-1" }, + { "label": "West Coast", "value": "us-west-1" } + ] +} +``` + +Hide a parameter from the UI (declared but not displayed): + +```json +{ "name": "base_search", "options": { "display": "hidden" }, "defaultValue": "dataSource.name='MySource'" } +``` + +--- diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/powerquery-gaps.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/powerquery-gaps.md new file mode 100644 index 0000000..cbeeb4b --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/powerquery-gaps.md @@ -0,0 +1,82 @@ +# PowerQuery feature gaps to design around + +PowerQuery patterns that fail or render badly inside dashboard JSON, the patterns that work instead, the two-pass quoted-KV parse, and the totals-plus-breakdown workaround. Referenced from `SKILL.md`. + +## PowerQuery feature gaps to design around + +The patterns below produce HTTP 500s or silent renderer failures on current SDL builds. They appear syntactically valid in language references and may even work in a developer's local PowerQuery preview, but they are not safe inside dashboard JSON. Treat them as red flags during code review. `scripts/panel_safety_check.py` scans for them automatically. + +| Pattern | Failure mode | +|---|---| +| `\| let x = if(predicate, then, else)` then aggregating on `x` | 500 server error | +| `count_if(predicate)` / `countif(predicate)` aggregate functions | 500 server error | +| `sum(if(predicate, value, 0))` inside `\| group ... by ...` | 500 server error | +| `concat(field_a, ' literal ', field_b)` in `\| let` bindings | 500 server error | +| `\| union (subquery)` MID-PIPELINE (after any other command) | HTTP 400 (live-verified 2026-07-29). `union` as the FIRST command of the query, one subquery per row set, IS valid and works, including with commands after it (e.g. `\| sort`). Use union-first for funnels/synthetic rows; never mid-pipeline | +| `let totals = (... \| group ...)` named subquery before main pipeline | 500 server error | +| `\| parse //` with named captures and grouping in same query | 500 server error | +| `\| matches ''` with `\\s` / `\\d` escapes inside the regex literal | 500 server error | +| Anything after `\| transpose` (terminal command) | "transpose can only be used as the last command" | +| `graphStyle: "area"` panel with a `query` field (not `plots: [...]`) | Indefinite spinner, no error surfaced | +| Hyphenated arithmetic in ANY `let`, including z-scores / ratios: `(live-base)/sd`, `total-min` without spaces | "Identifier is ambiguous", the WHOLE panel fails with "Couldn't load content". Always write `(live - base) / sd` | +| `markdown` panel with `content:` field instead of `markdown:` | Renders blank tile, no error | +| `markdown` panel with NO `title` key (S-26.1) | Header renders "Untitled". Set a short plain-text `title`; prose only in `markdown` | +| Number panel `suffix` repeats the unit already in the `title` | Reads "34 principals" under title "Active principals". Put the unit in the title OR the suffix, not both | +| `graphStyle: "bar"` / `"line"` / `"area"` with a categorical (non-time) first column | "The first column of power query output should have numeric value in epoch s/ms/us/ns" error. For a category bar chart use `"stacked_bar"` with `"xAxis": "grouped_data"` and a `(category, value)` query | + +### Patterns that DO work and should be preferred + +| Pattern | Use case | +|---|---| +| `\| group n=count() \| limit 1` | Number panel | +| `\| group n=estimate_distinct() \| limit 1` | Cardinality number panel (HyperLogLog, fast) | +| `\| group =count() by , \| sort - \| limit N` | Top-N table | +| `\| group =count() by timestamp=timebucket(''), \| transpose on timestamp` | Time-series stacked-bar / line | +| `\| group =count() by , \| transpose on ` | Cross-tab / per-category × action stacked-bar | +| Long-format table: `\| group hits=count() by , \| sort , -hits \| limit N` | When you need both dims as columns and a wide table can't be produced | +| Index-level filter (before the first pipe) | Narrow scan to relevant events; cheaper than a post-pipe `\| filter` | +| `\| filter matches ''` for selective dim filtering | Works with simple character classes; avoid `\\s` / `\\d` escapes | + +### Two-pass parse for quoted KV values + +Network device logs (FortiGate, Palo Alto, etc.) emit KV pairs where values are wrapped in double quotes: `app="HTTPS.BROWSER" appcat="Web.Client"`. The `| parse` format string is itself double-quoted, so you cannot embed a `"` to match the wrappers. The workaround is two passes: + +**Pass 1**, capture the whole non-whitespace token including its surrounding quotes: + +```text +| parse "app=$raw_app{regex=\\S+}$" from message +``` + +This extracts `"HTTPS.BROWSER"` (with quotes) into `raw_app`. + +**Pass 2**, extract the clean value by matching the alphanumeric content, which skips the leading `"`: + +```text +| parse "$app_name{regex=[A-Z0-9./_-]+}$" from raw_app +``` + +This produces `HTTPS.BROWSER` (no quotes) in `app_name`. + +Full example for an app-ctrl panel: + +```text +dataSource.name='FortiGate' event.type='app-ctrl' +| parse "app=$raw_app{regex=\\S+}$" from message +| parse "appcat=$raw_cat{regex=\\S+}$" from message +| parse "$app_name{regex=[A-Z0-9./_-]+}$" from raw_app +| parse "$app_cat{regex=[A-Za-z0-9./_-]+}$" from raw_cat +| filter app_name != '' +| group Events=count() by Application=app_name, Category=app_cat +| sort -Events +| limit 20 +``` + +**Escaping in dashboard JSON:** `\\S+` in the PowerQuery string (what the engine sees) must be written as `\\\\S+` in the JSON source because JSON applies one level of backslash-escaping before PowerQuery sees the string. + +**Always invoke the `powerquery` skill before authoring parse expressions.** It references official parse documentation and gets to the correct pattern without trial-and-error. + +### Workaround for "I need totals AND breakdown in one panel" + +When `sum(if())` and `count_if()` fail, first try a union-FIRST query (one subquery per row set; live-verified 2026-07-29). If that does not fit, the cleanest substitute is two adjacent panels: one for totals, one for the per-action breakdown (long-format). Lay them side-by-side at half-width so they read as a single visual unit. Trying to force a single wide table with both columns generally requires one of the unsupported patterns above. + +--- diff --git a/plugins/s1-secops-skills/skills/sdl-dashboard/references/query-performance.md b/plugins/s1-secops-skills/skills/sdl-dashboard/references/query-performance.md new file mode 100644 index 0000000..f95fc77 --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-dashboard/references/query-performance.md @@ -0,0 +1,166 @@ +# Query performance tips + +Per-query performance rules for SDL dashboard panels: presence checks, limits, timebucket sizing, `number()` casting, and more. Referenced from `SKILL.md`. + +## Query performance tips + +Dashboard panels run their queries in the SDL console's built-in rendering engine, not via LRQ or any external API. Every panel loads when the user opens the dashboard, so slow queries directly delay the page. Apply these rules to every query you write. + +### 1. Use `net_rfc1918()`: never hand-roll CIDR regex + +**Slow (avoid):** + +```text +| let rfc1918 = not (dst.ip.address matches '((127\\..*)|(192\\.168\\..*)|(10\\..*)|(172\\.1[6-9]\\..*)|(172\\.2[0-9]\\..*)|(172\\.3[0-1]\\..*)).*') +| filter rfc1918 = true +``` + +**Fast:** + +```text +dst.ip.address = * +| let is_external = not net_rfc1918(dst.ip.address) +| filter is_external = true +``` + +The built-in function is evaluated natively; the regex is evaluated as a string per event. + +### 2. Always add `| limit 1` to number panels + +Number panels reduce to a single row. Without `| limit 1`, the engine continues scanning after finding the answer. Always terminate: + +```json +"query": "dataSource.name='ActivityFeed' activity_type in (\"133\",\"134\") | group count() | limit 1" +``` + +### 3. Add explicit `| limit N` to every table panel + +Unbounded tables force a full scan. Always cap results: + +- Detail tables (time-sorted raw events): `| limit 200` +- Aggregated top-N tables: `| limit 20` or `| limit 25` +- Donut/pie panels: `| sort -count | limit 10` + +### 4. Use `field=*` to drop nulls: the canonical presence form + +`field=*` is the canonical SDL presence predicate: it matches any event where the field is present and non-null. Accuracy note (live-verified 2026-07-29 via the LRQ API): `field != null` returned results identical to `field=*`; the earlier claim that it hard-errors or compares against the literal string "null" was NOT reproducible on LRQ. The console/dashboard renderer engine remains unverified for `!= null`, so `field=*` stays the REQUIRED form in all dashboard JSON, rule bodies, and examples: + +```text +// Required: canonical presence check +dataSource.name='alert' severity_id=* +| group count=count() by severity_id + +// Avoid: behaved identically via LRQ (2026-07-29) but unverified in the dashboard engine +dataSource.name='alert' severity_id != null +``` + +This applies in the initial filter predicate (before the first `|`) and in `| filter` commands equally. + +### 5. Use `| filter count > 0` to suppress zero rows + +After a `| group count=count() by ...`, SDL may produce rows with `count=0` for sparse buckets (especially after `transpose` or when grouping over a large key space). These zero rows render as empty cells in heatmaps and false entries in tables. Filter them out: + +```text +| group EventCount=count() by user_name=user.name, timestamp=timebucket('1h') +| filter EventCount > 0 +``` + +Apply the same pattern to any aggregated numeric field you're visualising: `| filter bytes > 0`, `| filter value > 0`. + +### 6. `| sort` must come before `| columns`: field projection is destructive + +`| columns` removes every field not listed. Any `| sort` placed after `| columns` that references a field not in the projected set is operating on a non-existent field and silently fails or hangs the panel (bullet panels are especially prone to this): + +```text +// Wrong: severity_id is gone after | columns, sort fails silently +| group value=count(), target=..., label=... by severity_id +| columns value, target, label +| sort -severity_id ← severity_id no longer exists + +// Correct: sort while severity_id is still in scope +| group value=count(), target=..., label=... by severity_id +| sort -severity_id +| columns value, target, label +``` + +This affects any pipeline that projects away the sort key: bullet panels, donut panels with a custom label column, and any query that renames fields via `| columns alias=field`. + +### 7. Use `event.category = *` not `event.category != ''` + +`!= ''` requires evaluating the field value as a string comparison. `= *` is a cheaper is-not-null predicate: + +```text +dataSource.category = 'security' event.category = * +| group count=count() by timestamp=timebucket("1 day"), event.category +``` + +### 5. Match `timebucket` granularity to your dashboard duration + +Too-fine granularity creates thousands of data points per series, slowing both query and render: + +| Dashboard duration | Safe `timebucket` | Points per series | +|---|---|---| +| `1h` | `'1m'` | 60 | +| `4h` | `'5m'` | 48 | +| `24h` | `'1h'` | 24 | +| `7d` | `'1h'` | 168 | +| `14d` | `'1d'` | 14 | +| `30d` | `'1d'` | 30 | + +For a 24h dashboard, `'10m'` (144 points) can work for low-cardinality single-series panels but should not be the default; use `'1h'`. For a multi-series transpose, the data-point count compounds: `timebucket('10m')` on a 24h dashboard with a 7-series transpose = 1,008 cells per chart. + +**Never use `timebucket('10m')` on a 7-day dashboard**; that's 1,008 points per series. + +### 6. Push filters early: before the first pipe + +The initial filter (before the first `|`) is evaluated as an index predicate. Conditions placed there are far cheaper than `| filter` commands applied after a full scan: + +```text +// Good: index-level filter +event.category = 'ip' event.network.direction = 'OUTGOING' dataSource.category = 'security' +| group count=count() by dst.ip.address | sort -count | limit 20 + +// Bad: scans all events then filters +dataSource.category = 'security' +| filter event.category = 'ip' && event.network.direction = 'OUTGOING' +| group count=count() by dst.ip.address | sort -count | limit 20 +``` + +### 7. Use `estimate_distinct()` for cardinality: not `count(distinct …)` + +`estimate_distinct()` uses HyperLogLog and is orders of magnitude faster on high-cardinality fields like `agent.uuid`, `threat_id`, `src.process.storyline.id`. + +### 8. Avoid `nolimit` in dashboard panels + +`nolimit` raises the row cap to 3 GB and blocks concurrent queries. It is never appropriate in a dashboard panel; always use an explicit `| limit N` instead. + +### 9. Wrap string-prone numeric fields with `number()` before arithmetic + +SDL/Scalyr column types are locked at first ingest. A field that *should* be numeric, `severity_id`, `traffic.bytes_in/out`, `traffic.packets_in/out`, `unmapped.duration`, can be string-typed at the index level (because a parser declared `type: "string"` for many tenant generations, or the field was first-written before the type was set). When that happens, `sum()` / `avg()` / `max()` / `>=` predicates return NaN or fail silently *even though the values are populated and visible in Event Search*. + +**Failsafe pattern for every dashboard panel that does numeric work:** + +```text +dataSource.name='alert' severity_id=* +| let sev = number(severity_id) +| filter sev >= 4 +| group hits=count() by sev +| sort sev +``` + +```text +dataSource.name='FortiGate' unmapped.action='close' +| let bytes_out_n = number(traffic.bytes_out) +| let bytes_in_n = number(traffic.bytes_in) +| group sessions=count(), + bytes_out=sum(bytes_out_n), + bytes_in=sum(bytes_in_n), + max_session=max(bytes_out_n) +| limit 1 +``` + +`number(x)` returns 0 for null/missing and NaN for unparseable strings. Already-numeric data is unaffected. Cost is one `let` per panel; benefit is the dashboard keeps working when a parser pushes a string-typed write or a tenant column is locked. Apply this to every numeric counter / severity / port / duration field unless this session's schema discovery proved the column type with a successful unwrapped `sum()`. + +See `powerquery/references/pitfalls.md` for the full discussion of column-type lock and when the `parse "$x{regex=\\d+}$"` extraction is preferable to `number()`. + +--- diff --git a/plugins/s1-secops-skills/skills/sdl-log-parser/SKILL.md b/plugins/s1-secops-skills/skills/sdl-log-parser/SKILL.md index 20dd1a3..afca8e1 100644 --- a/plugins/s1-secops-skills/skills/sdl-log-parser/SKILL.md +++ b/plugins/s1-secops-skills/skills/sdl-log-parser/SKILL.md @@ -114,50 +114,9 @@ A `mappings.constant` overrides the parser-root `attributes:` value when its pre ## Default output schema: OCSF (mandatory reference) -Unless the user explicitly asks for vendor-native names, **emit OCSF-shaped events**. This means: - -- Name captured fields with their OCSF dotted paths (e.g. `src_endpoint.ip`, `dst_endpoint.port`, `connection_info.protocol_num`, `app_name`, `actor.user.name`). -- Tag every event with the class metadata on `attributes`: `class_uid`, `class_name`, `category_uid`, `category_name`, and `metadata.product.vendor_name` / `metadata.product.name` / `metadata.log_provider`, all IN ADDITION TO the four mandatory attributes above. -- Put the per-event subtype (`activity_id` + `activity_name`) on the **format**, not the top-level attributes, because one parser often handles multiple subtypes (SESSION_CREATE vs SESSION_CLOSE). - -**OCSF mapping MUST always be done using `references/ocsf-schema-documentation.md`.** This file is the authoritative SentinelOne community-documented OCSF field catalog (7 categories, 96 articles, ~25,759 field entries across every event class). Before emitting any OCSF field name, grep this file for the exact dotted path. Do **not** invent field names, do **not** copy from memory, do **not** trust catalog parsers to have the right name (many community parsers in ai-siem use vendor-native or stale OCSF names that need correcting). Workflow: - -1. Pick the OCSF class (see Quick picker below). -2. Open `references/ocsf-schema-documentation.md` and grep for the class number / category to find the field block. -3. Copy the dotted path verbatim into your parser. -4. If unsure between two candidate fields, confirm with the user rather than guessing. - -`references/ocsf-mapping.md` covers the two authoring idioms (capture-directly-into-dotted vs capture-vendor-then-rename) and the most common class-specific tables (Network Activity, Authentication, File Activity). - -Why this matters: downstream PowerQuery hunts, STAR rules, dashboards, and Marketplace integrations assume OCSF. Vendor-native names force every consumer to learn each source format and break portability. A wrong dotted path (`source.ip` instead of `src_endpoint.ip`, `dst.port` instead of `dst_endpoint.port`) is silently wrong: it ingests, but every downstream consumer fails to match. - -Quick picker (use this to find the class number, then look up fields in `ocsf-schema-documentation.md`): - -- Network firewall / NAT / flow → `4001` Network Activity -- HTTP / web / proxy → `4002` HTTP Activity -- DNS → `4003` DNS Activity -- DHCP → `4004` DHCP Activity -- RDP session → `4005` RDP Activity -- SMB / file-share traffic → `4006` SMB Activity -- SSH session → `4007` SSH Activity -- TLS / SSL handshake → no TLS class exists in the bundled catalog; TLS attributes ride on `tls.*` / `connection_info` under 4001/4002. For anything not listed here, check `references/ocsf-schema-documentation.md` -- Email → `4009` Email Activity -- Authentication → `3002` Authentication -- Account change → `3001` Account Change -- API activity → `6003` API Activity -- File system ops → `1001` File System Activity -- Kernel ops → `1003` Kernel Activity -- Memory ops → `1004` Memory Activity -- Module ops → `1005` Module Activity -- Process ops → `1007` Process Activity -- Registry ops → `201001` (Windows Registry) -- Detection finding → `2004` Detection Finding -- Compliance finding → `2003` Compliance Finding -- Vulnerability finding → `2002` Vulnerability Finding -- Inventory / device → `5001` Device Inventory Info -- Email / file finding → `2007` (Email Finding) / `2006` (File Hosting Finding) - -When the source could reasonably belong to multiple classes (proxy logs, EDR alerts), confirm with the user rather than picking silently. +Unless the user explicitly asks for vendor-native names, **emit OCSF-shaped events**: name captured fields with their OCSF dotted paths and tag every event with the class metadata (`class_uid`, `class_name`, `category_uid`, `category_name`, `metadata.product.*`, plus per-subtype `activity_id` on the format). OCSF is the mandatory default output schema for this skill. + +**OCSF mapping MUST always be done using `references/ocsf-schema-documentation.md`**, the authoritative catalog (7 categories, ~25,759 field entries). Grep it for the exact dotted path before emitting any OCSF field name; never invent a name, never trust memory or a catalog parser. `references/ocsf-mapping.md` holds the OCSF class quick-picker (4001/4002/3002/1001, etc.), the field-to-dotted-path tables, and the two authoring idioms (capture-directly-into-dotted vs capture-vendor-native-then-rename). Consult both before writing any mapping. ## Top-level parser structure @@ -324,7 +283,7 @@ These are quirks of the ingest pipeline itself, not the parser DSL. They bite yo 2. Send each logical event as a single HEC event so newlines inside the event body are preserved. - **The `server-host` upload header is unreliable for isolating a test.** SDL sometimes overrides the header to the literal source name, and if the parser extracts a `host` field from the log itself that wins too. Do NOT filter your validation query by `host='parser-test-'` alone. Safer: filter by `parser='claude_test_'` and `_bytes > 0`, and use a unique nonce in the payload to double-check isolation. - **`{parse=dottedJson}` prefixes subfields when the field has a non-empty name.** `$payload{parse=dottedJson}$` on `{"user":"alice"}` yields `payload.user = "alice"`. If you want top-level fields, capture into an empty name: `${parse=dottedJson}$`. Same applies to `json`, `escapedJson`, `urlEncodedJson`, `base64EncodedJson`. -- **`getFile` on a missing config path raises HTTP 404**, it does not return `success=false` with `noSuchFile`. Wrap existence checks in a try/except when scripting cleanup. +- **Existence checks: `config_file(name=...)` returns `None` for a missing path**, it does not raise. The legacy REST `get_file` raises HTTP 404 instead of returning `success=false` with `noSuchFile`. Prefer the GraphQL method and test for `None`. Parsers live at `/logParsers/` and are name-addressed, so no `udoId` is involved. ### `mappings` block gotchas @@ -477,6 +436,8 @@ When a question goes deeper than this file, read the relevant reference. Each is - `references/builtin-parsers.md`: Catalog of all 16 built-in parsers (`accessLog`, `cloudfront`, `json`, `dottedJson`, `dottedEscapedJson`, `elb-access`, `heroku-logplex`, `keyValue`, `leveldbLog`, `mysqlGeneralQueryLog`, `mysqlSlowQueryLog`, `postgresLog`, `redshift`, `s3_bucket_access`, `spot_instance_data`, `systemLog`) and when to alias vs override. Read first when sizing up a new log source; you may not need to write a parser at all. - `references/mappers.md`: `mappings` block (gron-style transformations: `cast`, `copy`, `copy_tree`, `drop`, `drop_tree`, `hash`, `reduce_array`, `rename`, `rename_tree`, `replace`, `zip`), array index syntax, predicate semantics. Read when restructuring events to OCSF or another target schema. - `references/testing-workflow.md`: Detailed validation recipe with the `sdl-api` skill, including how to scope queries with a unique host tag, how to interpret common error responses, and how to clean up. +- `references/per-app-sentinel.md`: The per-app sentinel pattern for a single parser that fans many services or applications out to their own OCSF classes: pattern overview, the add-a-service checklist, and the periodic catch-all audit query. +- `references/onboarding-learnings.md`: Tenant-validated learnings from onboarding new sources via the `sdl-solutions` playbook (dotted-prefix JSON capture, `mappings` version requirement, account-level parser scope, activation latency, sourcetype binding, IP-keyed network-source enrichment). ## Bundled examples @@ -507,61 +468,8 @@ to `*.sentinelone.net` without proxy interference. ## Per-app sentinel pattern (multi-tenant / multi-service parsers) -Use this pattern when a single parser handles events from many distinct services or applications, each needing its own OCSF class assignment. - -### Pattern overview - -1. **Extract a discriminator field** (e.g. `app_name` from a raw JSON `app_id` key) using a two-segment capture format. -2. **Create one format-id sentinel per service**: `{ id: "my_app", format: "$_scratch{regex=.*\"app_id\":\"my-app-id\"}$" }`. This sets `my_app='true'` on matching events. -3. **List all sentinel field names in `discardAttributes`** so they don't appear in the output event. -4. **Add one v1 mappings block per sentinel** with the OCSF constants and any drop ops. Because v1 is first-match-wins, drops cannot be factored into a shared block, duplicate them in every block including the catch-all. -5. **End with a `predicate: "true"` catch-all** that applies drops but assigns no class. Place it last or it will consume every event. - -### How to add a new service - -1. Confirm the new service's discriminator value (e.g. sample a few raw events via PowerQuery). -2. Check whether it already fires an existing sentinel (e.g. a shared type field that already has a sentinel). If yes, no new entry needed. -3. Add the sentinel ID to `discardAttributes`. -4. Add a format entry before the catch-all sentinels. -5. Add a mapping block before `predicate: "true"` with the right OCSF constants and all noise drops. -6. Bump `metadata.version` (minor bump for new service; patch for fixes). -7. Deploy via `sdl_put_file` with the current `expectedVersion` from `sdl_get_file`. -8. Wait ~3 min for propagation, then verify on a short window (5 min): `dataSource.name = 'MySource' | group count=count(), has_class=count(class_uid) by app_name | filter app_name = 'my-new-service'`. - -### Periodic audit query - -Run this periodically to catch new services that have accumulated in the catch-all: - -```text -dataSource.name = 'MySource' app_name = * -| group count=count(), has_class=count(class_uid) by app_name -| filter has_class == 0 -| sort -count -``` - -Any `app_name` with `has_class == 0` and meaningful volume is a candidate for a new sentinel. +When one parser handles events from many distinct services or applications, each needing its own OCSF class assignment, use the per-app sentinel pattern: extract a discriminator field, add one format-id sentinel per service, list every sentinel field in `discardAttributes`, add one v1 first-match-wins `mappings` block per sentinel (drops duplicated into each block because v1 is first-match-wins), and end with a `predicate: "true"` catch-all placed last. The full pattern overview, the how-to-add-a-new-service checklist, and the periodic audit query for services stuck in the catch-all are in `references/per-app-sentinel.md`. ## Onboarding learnings (tenant-validated 2026-06-13, usea1-purple) -These came out of onboarding Cisco Meraki via the `sdl-solutions` onboarding playbook. - -- **JSON-per-line flatten needs a dotted-prefix capture.** `format: "$unmapped.=json{parse=dottedJson}$"` - flattens the body into `unmapped.*` queryable fields. A non-prefix capture name like - `$json{parse=json}$` captures the raw JSON string and emits NO subfields, so every field reads - null after deploy and only the parser-root `attributes` (e.g. `dataSource.name`) apply. This is - already shown in `examples/02-json-with-envelope.json`; reach for it first for any JSON source. -- **`mappings` requires `version: 1` and `transformations`.** The error - `Got unsupported event mapper version -1` on `putFile` means the `mappings` block is missing - `version`. Ops go inside `transformations: [...]`, each as `{ : {...} }`. -- **Parsers are account-level.** Deploy at account scope even when the data ingests at a site. - There is no site-scoped `/logParsers/` file; the sourcetype label binds events to the parser. -- **Activation latency is 3 to 5 minutes per deploy** on this tenant, not seconds. Batch parser - edits and wait out the window before validating, rather than iterating one field at a time. -- **A `parser=` label with no `/logParsers/` file and no `dataSource.name`** means the - events were tagged with a sourcetype but never transformed (e.g. a marketplace `*-latest` label - with no editable file). Creating the parser at that exact path normalises the live stream. The - initial `sdl_get_file` 404 is a "create me", not an error. -- **Network-source enrichment keys on IP.** Build an IP-keyed endpoint lookup - (`datasource assets from 'surface/endpoint'`, keyed on `agentLastReportedIp`) and join in the - `computeFields` rewrite `by device_ip = unmapped.src_ip` using the pre-rename `unmapped.*` field, - since the rewrite runs before `mappings` renames. +Tenant-validated learnings from onboarding new sources through the `sdl-solutions` playbook, JSON-per-line dotted-prefix capture (`$unmapped.=json{parse=dottedJson}$`), the `mappings` `version: 1` plus `transformations` requirement, account-level parser scope, 3 to 5 minute activation latency per deploy, sourcetype-to-parser binding, and IP-keyed network-source enrichment, are in `references/onboarding-learnings.md`. diff --git a/plugins/s1-secops-skills/skills/sdl-log-parser/references/mappers.md b/plugins/s1-secops-skills/skills/sdl-log-parser/references/mappers.md index 5234f8f..6070cd3 100644 --- a/plugins/s1-secops-skills/skills/sdl-log-parser/references/mappers.md +++ b/plugins/s1-secops-skills/skills/sdl-log-parser/references/mappers.md @@ -274,6 +274,7 @@ Algorithms: `sha1`, `sha256`. Output is lowercase hex with no `0x` prefix. Supports `$1`…`$n` backreferences. Unchanged if no match. > **Two confirmed traps (tenant-validated 2026-06-01).** +> > 1. The regex key is **`regexp`**, NOT `pattern`. A `pattern` key returns `400: Missing required key 'regexp'` on `putFile`. (`reduce_array { kind: "find", regexp: ... }` uses the same `regexp` key, be consistent.) > 2. Even with the correct `regexp` key, the `replace` mapper op was observed to be a **runtime no-op** on this tenant; it passes schema validation and deploys, but the field value is unchanged at ingest (tested on both a dotted target like `process.cmd_line` and a flat scratch field). If your `replace` "succeeds" but the data is untransformed, this is why. The working substitute is a **`computeFields` rewrite** on the format that captures the field, calling the PowerQuery `replace(field, regex, replacement)` string function (PowerQuery string literals are single-quoted, so a double-quote inside the regex needs no escaping): `expression: "| let _cmdline = replace(_cmdline, 'a[0-9]+=\"([^\"]*)\"', '$1')"`. Capture into a flat scratch field, clean it with computeFields, then `rename` it to the dotted OCSF target. See `examples/12-linux-auditd-ocsf.json` (EXECVE arg vector → `process.cmd_line`). diff --git a/plugins/s1-secops-skills/skills/sdl-log-parser/references/ocsf-mapping.md b/plugins/s1-secops-skills/skills/sdl-log-parser/references/ocsf-mapping.md index 7e36f49..d18f7cd 100644 --- a/plugins/s1-secops-skills/skills/sdl-log-parser/references/ocsf-mapping.md +++ b/plugins/s1-secops-skills/skills/sdl-log-parser/references/ocsf-mapping.md @@ -9,6 +9,7 @@ The Open Cybersecurity Schema Framework (OCSF) is the schema this skill emits ** > Before emitting any OCSF dotted path in a parser, **grep `references/ocsf-schema-documentation.md` for the exact string**. That file is the single source of truth for the ~25,759 documented OCSF field names. Do not invent. Do not copy from a catalog parser without verifying. Do not rely on memory or on the tables in this document; they are summaries for common cases, not the catalog. > > The two reasons people drift from this rule: +> > 1. "I'm sure it's `source.ip`." It is not. It is `src_endpoint.ip`. Many vendors use `source.ip` natively. OCSF does not. Always grep. > 2. "The catalog parser uses `attacker_ip`, so I'll keep it." Catalog parsers are full of vendor-native names. Replace them with the OCSF dotted path from the schema reference, then map via `mappings.rename`. @@ -25,6 +26,7 @@ Before inventing any OCSF field name, look it up in `references/ocsf-schema-docu - `## Unified Alert Management` **How to use it:** + 1. Identify the class from the quick-pick table below (or from the section headers in `ocsf-schema-documentation.md`). 2. Grep the relevant section of that file for the OCSF concept you want (e.g., `src_endpoint`, `actor.user.email`, `file.hashes`). Each article lists every dotted field at that level, copy the name verbatim. 3. Use the tables lower in THIS file only as a shortcut for the most common classes. For anything beyond Network/Auth/File, consult `ocsf-schema-documentation.md` first. @@ -36,6 +38,7 @@ Field names in OCSF are lowercase dotted-path, e.g. `actor.user.email_addr`, `sr You have two choices for emitting OCSF names, pick based on parser complexity: ### Option A: Capture directly into OCSF dotted names + For simple line formats, name the captures with the OCSF dotted path: ```js @@ -47,6 +50,7 @@ formats: [ SDL allows `.` and `_` in field names. Dotted names show up as nested objects in queries. Use this for clean, single-line formats. ### Option B: Capture vendor-native, then `mappings` block to rename + For multi-format or complex parsers, capture vendor-native names first (matches the source log's terminology, easier to reason about during authoring), then use a `mappings` block (see `mappers.md`) to rename to OCSF in one place. This is the cleanest pattern for repeated patterns and CEF/LEEF/structured-syslog where dozens of fields need renaming. Often a hybrid is best: capture the framing (timestamp, host, log-type) directly with OCSF names, and use `mappings.rename` for the long tail of vendor-specific fields inside the body. @@ -54,6 +58,7 @@ Often a hybrid is best: capture the framing (timestamp, host, log-type) directly ## Required attributes on every parser Tag every event with: + 1. The four pipeline-required defaults (`metadata.version`, `dataSource.category`, `dataSource.name`, `dataSource.vendor`). 2. The OCSF class metadata (`class_uid`, `class_name`, `category_uid`, `category_name`). 3. Vendor/product metadata (`metadata.product.*`, `metadata.log_provider`). @@ -223,3 +228,35 @@ See `references/mappers.md` for the full authoritative mapper syntax (it differs ## Confirm with the user when class is ambiguous If the source could reasonably belong to multiple OCSF classes (e.g., a proxy log is HTTP Activity 4002 *or* Network Activity 4001 depending on what fields you keep), surface the choice rather than picking silently. Once chosen, stick with it for the whole parser, mixed classes break downstream filters. + +## OCSF class quick-picker (full list, relocated from SKILL.md) + +Use this to find the class number, then look up the fields in `ocsf-schema-documentation.md`. This is the consolidated picker (it includes Findings and Registry classes not tabulated above). + +Quick picker (use this to find the class number, then look up fields in `ocsf-schema-documentation.md`): + +- Network firewall / NAT / flow → `4001` Network Activity +- HTTP / web / proxy → `4002` HTTP Activity +- DNS → `4003` DNS Activity +- DHCP → `4004` DHCP Activity +- RDP session → `4005` RDP Activity +- SMB / file-share traffic → `4006` SMB Activity +- SSH session → `4007` SSH Activity +- TLS / SSL handshake → no TLS class exists in the bundled catalog; TLS attributes ride on `tls.*` / `connection_info` under 4001/4002. For anything not listed here, check `references/ocsf-schema-documentation.md` +- Email → `4009` Email Activity +- Authentication → `3002` Authentication +- Account change → `3001` Account Change +- API activity → `6003` API Activity +- File system ops → `1001` File System Activity +- Kernel ops → `1003` Kernel Activity +- Memory ops → `1004` Memory Activity +- Module ops → `1005` Module Activity +- Process ops → `1007` Process Activity +- Registry ops → `201001` (Windows Registry) +- Detection finding → `2004` Detection Finding +- Compliance finding → `2003` Compliance Finding +- Vulnerability finding → `2002` Vulnerability Finding +- Inventory / device → `5001` Device Inventory Info +- Email / file finding → `2007` (Email Finding) / `2006` (File Hosting Finding) + +When the source could reasonably belong to multiple classes (proxy logs, EDR alerts), confirm with the user rather than picking silently. diff --git a/plugins/s1-secops-skills/skills/sdl-log-parser/references/ocsf-schema-documentation.md b/plugins/s1-secops-skills/skills/sdl-log-parser/references/ocsf-schema-documentation.md index 5c7c10d..95f41b6 100644 --- a/plugins/s1-secops-skills/skills/sdl-log-parser/references/ocsf-schema-documentation.md +++ b/plugins/s1-secops-skills/skills/sdl-log-parser/references/ocsf-schema-documentation.md @@ -1,7 +1,7 @@ # OCSF Schema Documentation **Source:** SentinelOne Customer Community Knowledge Base -**Overview article:** https://community.sentinelone.com/s/article/000010516 +**Overview article:** **Compiled:** Auto-extracted summary (per-article field-name lists) ## About OCSF @@ -37,7 +37,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - File System Activity - Part 1 - **Article ID:** `000010500` -- **URL:** https://community.sentinelone.com/s/article/000010500 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -47,7 +47,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - File System Activity - Part 2 - **Article ID:** `000010501` -- **URL:** https://community.sentinelone.com/s/article/000010501 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -57,7 +57,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - File System Activity - Part 3 - **Article ID:** `000010502` -- **URL:** https://community.sentinelone.com/s/article/000010502 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -71,7 +71,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Kernel Extension Activity - Part 1 - **Article ID:** `000010503` -- **URL:** https://community.sentinelone.com/s/article/000010503 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -81,7 +81,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Kernel Extension Activity - Part 2 - **Article ID:** `000010504` -- **URL:** https://community.sentinelone.com/s/article/000010504 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -95,7 +95,7 @@ This event category spans 1 article with a combined 7 documented field entries. #### OCSF Event - Kernel Activity - **Article ID:** `000010505` -- **URL:** https://community.sentinelone.com/s/article/000010505 +- **URL:** - **Field count:** 7 **Fields:** @@ -109,7 +109,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Memory Activity - Part 1 - **Article ID:** `000010506` -- **URL:** https://community.sentinelone.com/s/article/000010506 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -119,7 +119,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Memory Activity - Part 2 - **Article ID:** `000010507` -- **URL:** https://community.sentinelone.com/s/article/000010507 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -133,7 +133,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Module Activity - Part 1 - **Article ID:** `000010508` -- **URL:** https://community.sentinelone.com/s/article/000010508 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -143,7 +143,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Module Activity - Part 2 - **Article ID:** `000010509` -- **URL:** https://community.sentinelone.com/s/article/000010509 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -157,7 +157,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Scheduled Job Activity - Part 1 - **Article ID:** `000010510` -- **URL:** https://community.sentinelone.com/s/article/000010510 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -167,7 +167,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Scheduled Job Activity - Part 2 - **Article ID:** `000010511` -- **URL:** https://community.sentinelone.com/s/article/000010511 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -181,7 +181,7 @@ This event category spans 4 articles with a combined 1200 documented field entri #### OCSF Event - Process Activity - Part 1 - **Article ID:** `000010512` -- **URL:** https://community.sentinelone.com/s/article/000010512 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -191,7 +191,7 @@ This event category spans 4 articles with a combined 1200 documented field entri #### OCSF Event - Process Activity - Part 2 - **Article ID:** `000010513` -- **URL:** https://community.sentinelone.com/s/article/000010513 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -201,7 +201,7 @@ This event category spans 4 articles with a combined 1200 documented field entri #### OCSF Event - Process Activity - Part 3 - **Article ID:** `000010514` -- **URL:** https://community.sentinelone.com/s/article/000010514 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -211,7 +211,7 @@ This event category spans 4 articles with a combined 1200 documented field entri #### OCSF Event - Process Activity - Part 4 - **Article ID:** `000010515` -- **URL:** https://community.sentinelone.com/s/article/000010515 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -229,7 +229,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Security Finding - Part 1 - **Article ID:** `000010452` -- **URL:** https://community.sentinelone.com/s/article/000010452 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -239,7 +239,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Security Finding - Part 2 - **Article ID:** `000010453` -- **URL:** https://community.sentinelone.com/s/article/000010453 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -253,7 +253,7 @@ This event category spans 1 article with a combined 19 documented field entries. #### OCSF Events for Vulnerabilities - **Article ID:** `000011414` -- **URL:** https://community.sentinelone.com/s/article/000011414 +- **URL:** - **Field count:** 19 **Fields:** @@ -267,7 +267,7 @@ This event category spans 1 article with a combined 59 documented field entries. #### OCSF Events for Misconfigurations - **Article ID:** `000011415` -- **URL:** https://community.sentinelone.com/s/article/000011415 +- **URL:** - **Field count:** 59 **Fields:** @@ -285,7 +285,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Account Change - Part 1 - **Article ID:** `000010467` -- **URL:** https://community.sentinelone.com/s/article/000010467 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -295,7 +295,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Account Change - Part 2 - **Article ID:** `000010468` -- **URL:** https://community.sentinelone.com/s/article/000010468 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -309,7 +309,7 @@ This event category spans 4 articles with a combined 1200 documented field entri #### OCSF Event - Authentication - Part 1 - **Article ID:** `000010469` -- **URL:** https://community.sentinelone.com/s/article/000010469 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -319,7 +319,7 @@ This event category spans 4 articles with a combined 1200 documented field entri #### OCSF Event - Authentication - Part 2 - **Article ID:** `000010470` -- **URL:** https://community.sentinelone.com/s/article/000010470 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -329,7 +329,7 @@ This event category spans 4 articles with a combined 1200 documented field entri #### OCSF Event - Authentication - Part 3 - **Article ID:** `000010471` -- **URL:** https://community.sentinelone.com/s/article/000010471 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -339,7 +339,7 @@ This event category spans 4 articles with a combined 1200 documented field entri #### OCSF Event - Authentication - Part 4 - **Article ID:** `000010472` -- **URL:** https://community.sentinelone.com/s/article/000010472 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -353,7 +353,7 @@ This event category spans 1 article with a combined 61 documented field entries. #### OCSF Event - Authorize Session - **Article ID:** `000010473` -- **URL:** https://community.sentinelone.com/s/article/000010473 +- **URL:** - **Field count:** 61 **Fields:** @@ -367,7 +367,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Entity Management - Part 1 - **Article ID:** `000010474` -- **URL:** https://community.sentinelone.com/s/article/000010474 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -377,7 +377,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Entity Management - Part 2 - **Article ID:** `000010475` -- **URL:** https://community.sentinelone.com/s/article/000010475 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -391,7 +391,7 @@ This event category spans 1 article with a combined 59 documented field entries. #### OCSF Event - User Access Management - **Article ID:** `000010476` -- **URL:** https://community.sentinelone.com/s/article/000010476 +- **URL:** - **Field count:** 59 **Fields:** @@ -405,7 +405,7 @@ This event category spans 1 article with a combined 64 documented field entries. #### OCSF Event - Group Management - **Article ID:** `000010477` -- **URL:** https://community.sentinelone.com/s/article/000010477 +- **URL:** - **Field count:** 64 **Fields:** @@ -423,7 +423,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Network Activity - Part 1 - **Article ID:** `000010478` -- **URL:** https://community.sentinelone.com/s/article/000010478 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -433,7 +433,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Network Activity - Part 2 - **Article ID:** `000010479` -- **URL:** https://community.sentinelone.com/s/article/000010479 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -443,7 +443,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Network Activity - Part 3 - **Article ID:** `000010480` -- **URL:** https://community.sentinelone.com/s/article/000010480 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -457,7 +457,7 @@ This event category spans 1 article with a combined 27 documented field entries. #### OCSF Event - HTTP Activity - **Article ID:** `000010481` -- **URL:** https://community.sentinelone.com/s/article/000010481 +- **URL:** - **Field count:** 27 **Fields:** @@ -471,7 +471,7 @@ This event category spans 1 article with a combined 59 documented field entries. #### OCSF Event - DNS Activity - **Article ID:** `000010482` -- **URL:** https://community.sentinelone.com/s/article/000010482 +- **URL:** - **Field count:** 59 **Fields:** @@ -485,7 +485,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - DHCP Activity - Part 1 - **Article ID:** `000010483` -- **URL:** https://community.sentinelone.com/s/article/000010483 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -495,7 +495,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - DHCP Activity - Part 2 - **Article ID:** `000010484` -- **URL:** https://community.sentinelone.com/s/article/000010484 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -505,7 +505,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - DHCP Activity - Part 3 - **Article ID:** `000010485` -- **URL:** https://community.sentinelone.com/s/article/000010485 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -519,7 +519,7 @@ This event category spans 1 article with a combined 114 documented field entries #### OCSF Event - RDP Activity - **Article ID:** `000010486` -- **URL:** https://community.sentinelone.com/s/article/000010486 +- **URL:** - **Field count:** 114 **Fields:** @@ -533,7 +533,7 @@ This event category spans 1 article with a combined 158 documented field entries #### OCSF Event - SMB Activity - **Article ID:** `000010487` -- **URL:** https://community.sentinelone.com/s/article/000010487 +- **URL:** - **Field count:** 158 **Fields:** @@ -547,7 +547,7 @@ This event category spans 1 article with a combined 9 documented field entries. #### OCSF Event - SSH Activity - **Article ID:** `000010488` -- **URL:** https://community.sentinelone.com/s/article/000010488 +- **URL:** - **Field count:** 9 **Fields:** @@ -561,7 +561,7 @@ This event category spans 1 article with a combined 7 documented field entries. #### OCSF Event - FTP Activity - **Article ID:** `000010489` -- **URL:** https://community.sentinelone.com/s/article/000010489 +- **URL:** - **Field count:** 7 **Fields:** @@ -575,7 +575,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Email Activity - Part 1 - **Article ID:** `000010490` -- **URL:** https://community.sentinelone.com/s/article/000010490 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -585,7 +585,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Email Activity - Part 2 - **Article ID:** `000010491` -- **URL:** https://community.sentinelone.com/s/article/000010491 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -595,7 +595,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Email Activity - Part 3 - **Article ID:** `000010492` -- **URL:** https://community.sentinelone.com/s/article/000010492 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -609,7 +609,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Network File Activity - Part 1 - **Article ID:** `000010493` -- **URL:** https://community.sentinelone.com/s/article/000010493 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -619,7 +619,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Network File Activity - Part 2 - **Article ID:** `000010494` -- **URL:** https://community.sentinelone.com/s/article/000010494 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -633,7 +633,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Email File Activity - Part 1 - **Article ID:** `000010495` -- **URL:** https://community.sentinelone.com/s/article/000010495 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -643,7 +643,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Email File Activity - Part 2 - **Article ID:** `000010496` -- **URL:** https://community.sentinelone.com/s/article/000010496 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -653,7 +653,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Email File Activity - Part 3 - **Article ID:** `000010497` -- **URL:** https://community.sentinelone.com/s/article/000010497 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -667,7 +667,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Email URL Activity - Part 1 - **Article ID:** `000010498` -- **URL:** https://community.sentinelone.com/s/article/000010498 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -677,7 +677,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Email URL Activity - Part 2 - **Article ID:** `000010499` -- **URL:** https://community.sentinelone.com/s/article/000010499 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -695,7 +695,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Device Inventory Info - Part 1 - **Article ID:** `000010454` -- **URL:** https://community.sentinelone.com/s/article/000010454 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -705,7 +705,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Device Inventory Info - Part 2 - **Article ID:** `000010455` -- **URL:** https://community.sentinelone.com/s/article/000010455 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -719,7 +719,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Device Config State - Part 1 - **Article ID:** `000010456` -- **URL:** https://community.sentinelone.com/s/article/000010456 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -729,7 +729,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Device Config State - Part 2 - **Article ID:** `000010457` -- **URL:** https://community.sentinelone.com/s/article/000010457 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -747,7 +747,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Web Resources Activity - Part 1 - **Article ID:** `000010458` -- **URL:** https://community.sentinelone.com/s/article/000010458 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -757,7 +757,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Web Resources Activity - Part 2 - **Article ID:** `000010459` -- **URL:** https://community.sentinelone.com/s/article/000010459 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -771,7 +771,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Application Lifecycle - Part 1 - **Article ID:** `000010460` -- **URL:** https://community.sentinelone.com/s/article/000010460 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -781,7 +781,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - Application Lifecycle - Part 2 - **Article ID:** `000010461` -- **URL:** https://community.sentinelone.com/s/article/000010461 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -795,7 +795,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - API Activity - Part 1 - **Article ID:** `000010462` -- **URL:** https://community.sentinelone.com/s/article/000010462 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -805,7 +805,7 @@ This event category spans 2 articles with a combined 600 documented field entrie #### OCSF Event - API Activity - Part 2 - **Article ID:** `000010463` -- **URL:** https://community.sentinelone.com/s/article/000010463 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -819,7 +819,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Web Resource Access Activity - Part 1 - **Article ID:** `000010464` -- **URL:** https://community.sentinelone.com/s/article/000010464 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -829,7 +829,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Web Resource Access Activity - Part 2 - **Article ID:** `000010465` -- **URL:** https://community.sentinelone.com/s/article/000010465 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** @@ -839,7 +839,7 @@ This event category spans 3 articles with a combined 900 documented field entrie #### OCSF Event - Web Resource Access Activity - Part 3 - **Article ID:** `000010466` -- **URL:** https://community.sentinelone.com/s/article/000010466 +- **URL:** - **Field count:** 300 (paginated, more fields in subsequent parts) **Fields:** diff --git a/plugins/s1-secops-skills/skills/sdl-log-parser/references/onboarding-learnings.md b/plugins/s1-secops-skills/skills/sdl-log-parser/references/onboarding-learnings.md new file mode 100644 index 0000000..83ea34e --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-log-parser/references/onboarding-learnings.md @@ -0,0 +1,28 @@ +# Onboarding learnings + +Reference detail for the onboarding-learnings pointer in `SKILL.md`. Tenant-validated learnings captured while onboarding data sources through the `sdl-solutions` playbook. + +## Onboarding learnings (tenant-validated 2026-06-13, usea1-purple) + +These came out of onboarding Cisco Meraki via the `sdl-solutions` onboarding playbook. + +- **JSON-per-line flatten needs a dotted-prefix capture.** `format: "$unmapped.=json{parse=dottedJson}$"` + flattens the body into `unmapped.*` queryable fields. A non-prefix capture name like + `$json{parse=json}$` captures the raw JSON string and emits NO subfields, so every field reads + null after deploy and only the parser-root `attributes` (e.g. `dataSource.name`) apply. This is + already shown in `examples/02-json-with-envelope.json`; reach for it first for any JSON source. +- **`mappings` requires `version: 1` and `transformations`.** The error + `Got unsupported event mapper version -1` on `putFile` means the `mappings` block is missing + `version`. Ops go inside `transformations: [...]`, each as `{ : {...} }`. +- **Parsers are account-level.** Deploy at account scope even when the data ingests at a site. + There is no site-scoped `/logParsers/` file; the sourcetype label binds events to the parser. +- **Activation latency is 3 to 5 minutes per deploy** on this tenant, not seconds. Batch parser + edits and wait out the window before validating, rather than iterating one field at a time. +- **A `parser=` label with no `/logParsers/` file and no `dataSource.name`** means the + events were tagged with a sourcetype but never transformed (e.g. a marketplace `*-latest` label + with no editable file). Creating the parser at that exact path normalises the live stream. The + initial `sdl_get_file` 404 is a "create me", not an error. +- **Network-source enrichment keys on IP.** Build an IP-keyed endpoint lookup + (`datasource assets from 'surface/endpoint'`, keyed on `agentLastReportedIp`) and join in the + `computeFields` rewrite `by device_ip = unmapped.src_ip` using the pre-rename `unmapped.*` field, + since the rewrite runs before `mappings` renames. diff --git a/plugins/s1-secops-skills/skills/sdl-log-parser/references/parse-directives.md b/plugins/s1-secops-skills/skills/sdl-log-parser/references/parse-directives.md index 1f6768b..584831c 100644 --- a/plugins/s1-secops-skills/skills/sdl-log-parser/references/parse-directives.md +++ b/plugins/s1-secops-skills/skills/sdl-log-parser/references/parse-directives.md @@ -5,6 +5,7 @@ A `{parse=X}` directive inside a field matcher runs a secondary parser on the ca > **NOT a valid directive: `{parse=keyValue}`.** It returns `400: Syntax error: Unknown parser "keyValue"`. The `keyValue` built-in parser exists, but it is only usable as a root-level `aliasTo: "keyValue"` (a one-line alias parser). To extract key=value pairs INSIDE a format string, use the repeating idiom `".*$_=identifier$=$_=quoteOrSpace$"` with `repeat: true`, see SKILL.md decision-tree step 5 and `examples/03-key-value.json`. The leading `.*` is mandatory; without it the format anchors at position 0 of the message and captures nothing on any line that doesn't start with a bare `identifier=`. Tenant-validated 2026-05-26 on FortiGate syslog. **None of the other built-ins** (`accessLog`, `cloudfront`, `json`, `dottedJson`, `dottedEscapedJson`, `elb-access`, `heroku-logplex`, `leveldbLog`, `mysqlGeneralQueryLog`, `mysqlSlowQueryLog`, `postgresLog`, `redshift`, `s3_bucket_access`, `spot_instance_data`, `systemLog`) are accepted as `{parse=...}` directives either, they're root-level parsers only. The valid `{parse=X}` set is the one documented below (URI variants, JSON variants, key=value list, time/byte/syslog parsers). > **Subfield naming convention (per SDL KB 000006743):** generated fields are named ``, with the source key's first letter uppercased and concatenated to the parent field name. There is NO underscore, dot, or `Query_` separator between prefix and key. Examples for a field captured as `uri`: +> > - `uriPath` (the parsed URL path component, from `{parse=uri}`) > - `uriFoo` for query param `foo` (from `{parse=uri}` or `{parse=uriAttributes}` on `?foo=...`) > - `uriBusinessProfile` for query param `businessProfile` @@ -15,6 +16,7 @@ A `{parse=X}` directive inside a field matcher runs a secondary parser on the ca > **`attrWhitelist` / `attrBlacklist` scope**; these only filter the SUBFIELDS produced by the `{parse=...}` directive, not top-level fields you captured by name in the format string. To drop a top-level field you named explicitly, use `discardAttributes: ["fieldname"]` at the parser root, NOT a blacklist on the parse directive. Catalog parsers like `cisco_firewall-latest` show liberal use of blacklists to drop noisy nested arrays (`{attrBlacklist=(targetResources)}`, `{attrBlacklist=(threatsInfoMap|messageParts)}`); the same authors use `discardAttributes: ["message"]` separately to drop the raw event body. > **Pipe vs parenthesized list syntax for `attrBlacklist=`**, both work in the wild: +> > - Parenthesized: `{attrBlacklist=(field1|field2|field3)}`, common in Microsoft Eventhub parsers. > - Bare list: `{attrBlacklist=field1|field2|field3}`, common in FortiGate / Cisco parsers. > - Single field: `{attrBlacklist=tranip}`; no delimiters needed. @@ -86,7 +88,7 @@ This is how the community PARSER_TEMPLATE captures the entire event into `unmapp **gron DOES expand arrays, into `[N]`-indexed attributes, not lossy strings. Index with `[N]`, never `.N.`** This corrects the common misconception (including elsewhere in this skill) that "gron drops arrays" or "flattens arrays to a lossy string." gron emits one queryable attribute per array leaf, using bracket-index notation: -``` +```json "signInEventTypes":["nonInteractiveUser"] -> unmapped.signInEventTypes[0] = "nonInteractiveUser" "additionalDetails":[{"key":"UserType","value":"Member"}] -> unmapped.additionalDetails[0].key = "UserType" @@ -100,12 +102,13 @@ This is how the community PARSER_TEMPLATE captures the entire event into `unmapp The trap that makes it *look* like arrays were dropped: querying with dot-index (`unmapped.targetResources.0.id`) returns null. The actual key uses brackets (`unmapped.targetResources[0].id`). Tenant-validated 2026-06-22 (Microsoft Entra ID sign-in + directory-audit logs, deeply nested arrays). Choose the capture per goal: + - **gron / dottedJson** -> every array element becomes its own `[N]`-indexed attribute. Use this when you want each leaf queryable / mappable (this is almost always what "extract all fields" means). - **strictDottedJson / strict\*** -> the array is preserved as one JSON value (consumable by PowerQuery `array_from_json()` / `array_get()`), NOT split into per-element attributes. Scalar and nested-object flattening is identical across gron, dottedJson, and the strict variants; they differ ONLY in array handling. -**Querying `[N]` attributes: they are stored but a raw PowerQuery `columns`/`filter` clause cannot type the `[`.** `| columns unmapped.x[0].y` fails to parse ("Unable to parse the entire query"); backticks fail ("Don't understand [`]"); double-quotes turn the name into a string literal. The fields ARE present and queryable through the Event Search field picker and the V1 query / `powerquery_schema_discover` endpoint, so do not conclude extraction failed just because `columns` rejects the name. To confirm `[N]` fields during validation, use `powerquery_schema_discover` (returns full event JSON) rather than a `columns` projection. Tenant-validated 2026-06-22. +**Querying `[N]` attributes: they are stored but a raw PowerQuery `columns`/`filter` clause cannot type the `[`.** `| columns unmapped.x[0].y` fails to parse ("Unable to parse the entire query"); backticks fail ("Don't understand [`]"); double-quotes turn the name into a string literal. The fields ARE present and queryable through the Event Search field picker and the V1 query /`powerquery_schema_discover` endpoint, so do not conclude extraction failed just because `columns` rejects the name. To confirm `[N]` fields during validation, use `powerquery_schema_discover` (returns full event JSON) rather than a `columns` projection. Tenant-validated 2026-06-22. See `references/ai-siem-catalog.md` §"Useful reference parsers by shape" for the canonical example, and `examples/08-gron-capture-template.json` for a ready-to-use scaffold. @@ -184,7 +187,7 @@ Accept optional unit suffixes and normalize to a base unit: After a structural parse, the generated subfields may explode your schema. Filter them: -``` +```text $payload{parse=dottedJson}{attrWhitelist=user\\.(id|email|name)}$ ``` diff --git a/plugins/s1-secops-skills/skills/sdl-log-parser/references/per-app-sentinel.md b/plugins/s1-secops-skills/skills/sdl-log-parser/references/per-app-sentinel.md new file mode 100644 index 0000000..a16aa78 --- /dev/null +++ b/plugins/s1-secops-skills/skills/sdl-log-parser/references/per-app-sentinel.md @@ -0,0 +1,39 @@ +# Per-app sentinel pattern + +Reference detail for the per-app sentinel pointer in `SKILL.md`. Use this pattern when one parser handles events from many distinct services or applications, each needing its own OCSF class assignment. + +## Per-app sentinel pattern (multi-tenant / multi-service parsers) + +Use this pattern when a single parser handles events from many distinct services or applications, each needing its own OCSF class assignment. + +### Pattern overview + +1. **Extract a discriminator field** (e.g. `app_name` from a raw JSON `app_id` key) using a two-segment capture format. +2. **Create one format-id sentinel per service**: `{ id: "my_app", format: "$_scratch{regex=.*\"app_id\":\"my-app-id\"}$" }`. This sets `my_app='true'` on matching events. +3. **List all sentinel field names in `discardAttributes`** so they don't appear in the output event. +4. **Add one v1 mappings block per sentinel** with the OCSF constants and any drop ops. Because v1 is first-match-wins, drops cannot be factored into a shared block, duplicate them in every block including the catch-all. +5. **End with a `predicate: "true"` catch-all** that applies drops but assigns no class. Place it last or it will consume every event. + +### How to add a new service + +1. Confirm the new service's discriminator value (e.g. sample a few raw events via PowerQuery). +2. Check whether it already fires an existing sentinel (e.g. a shared type field that already has a sentinel). If yes, no new entry needed. +3. Add the sentinel ID to `discardAttributes`. +4. Add a format entry before the catch-all sentinels. +5. Add a mapping block before `predicate: "true"` with the right OCSF constants and all noise drops. +6. Bump `metadata.version` (minor bump for new service; patch for fixes). +7. Deploy via `sdl_put_file` with the current `expectedVersion` from `sdl_get_file`. +8. Wait ~3 min for propagation, then verify on a short window (5 min): `dataSource.name = 'MySource' | group count=count(), has_class=count(class_uid) by app_name | filter app_name = 'my-new-service'`. + +### Periodic audit query + +Run this periodically to catch new services that have accumulated in the catch-all: + +```text +dataSource.name = 'MySource' app_name = * +| group count=count(), has_class=count(class_uid) by app_name +| filter has_class == 0 +| sort -count +``` + +Any `app_name` with `has_class == 0` and meaningful volume is a candidate for a new sentinel. diff --git a/plugins/s1-secops-skills/skills/sdl-log-parser/references/syntax.md b/plugins/s1-secops-skills/skills/sdl-log-parser/references/syntax.md index ed840fe..ac234da 100644 --- a/plugins/s1-secops-skills/skills/sdl-log-parser/references/syntax.md +++ b/plugins/s1-secops-skills/skills/sdl-log-parser/references/syntax.md @@ -68,7 +68,7 @@ When a fragment format pulls a token out of the middle of a line, the prefix's r 1. **The literal anchor must live INSIDE the prefix's regex, not as separate format-string text.** A pattern like `$_pre{regex=[\s\S]*}$/actuator/health$_suf{regex=[\s\S]*}$` (greedy `[\s\S]*` followed by literal `/actuator/health` between the two field markers) does NOT match. The format engine commits each captured field's regex independently and does not backtrack the previous field once a literal-text gap has been crossed. The working form puts the literal inside the regex of the prefix itself: `$_pre{regex=[\s\S]*\/actuator\/health}$$_suf{regex=[\s\S]*}$`. Phase-10-style IOC formats do this with anchors like `[\s\S]*X-Forwarded-For=[\[]?` baked into the prefix regex. -2. **`[\s\S]*ANCHOR` is greedy and lands on the LAST occurrence of `ANCHOR`.** That is correct when the desired token always follows the rightmost anchor on the line, Phase-5-style Dropwizard formats (`[\s\S]*\[dw-[0-9]+ - `) work because `[dw-N - ` is unique to the request-line bracket. It fails when the anchor is too generic: `[\s\S]*\[` followed by a capture of `UT-[0-9]+` will fail on any line where a later `[` exists (e.g. `[#033[36mClassName#033[0;39m]` ANSI brackets), because greedy match commits to the last `[` and `UT-` does not follow there. Fix: make the anchor multi-character and discriminating (`[\s\S]*\[UT-` so only the bracket that actually opens a UT- token is matched), accepting that the discriminating literal is consumed by the anchor and your capture holds only the trailing portion (`$user_task_id_num{regex=[0-9]+}$`). +2. **`[\s\S]*ANCHOR` is greedy and lands on the LAST occurrence of `ANCHOR`.** That is correct when the desired token always follows the rightmost anchor on the line, Phase-5-style Dropwizard formats (`[\s\S]*\[dw-[0-9]+ -`) work because `[dw-N -` is unique to the request-line bracket. It fails when the anchor is too generic: `[\s\S]*\[` followed by a capture of `UT-[0-9]+` will fail on any line where a later `[` exists (e.g. `[#033[36mClassName#033[0;39m]` ANSI brackets), because greedy match commits to the last `[` and `UT-` does not follow there. Fix: make the anchor multi-character and discriminating (`[\s\S]*\[UT-` so only the bracket that actually opens a UT- token is matched), accepting that the discriminating literal is consumed by the anchor and your capture holds only the trailing portion (`$user_task_id_num{regex=[0-9]+}$`). 3. **DO NOT use `[\s\S]*?` lazy quantifier at the start of a prefix regex.** Same family as the documented `.*?` orphaned-`?` trap, but no error: the format silently matches almost nothing (tenant-validated 2026-05-13, `[\s\S]*?` prefixes hit 365 of ~190K events when greedy + literal anchor hit close to all). The SDL regex engine does not backtrack the lazy quantifier across field boundaries to find a position where the next field's regex matches. Always use greedy `[\s\S]*` plus a discriminating literal anchor (rule 2 above). If you actually need first-match-on-line semantics, encode it with a literal that only appears once per line (e.g. `[\s\S]*\"merchant_transaction_id\":\"` to anchor on a unique JSON key), do not reach for `*?`. @@ -76,7 +76,7 @@ When a fragment format pulls a token out of the middle of a line, the prefix's r Inside a format string, `$name=pattern{opt1}{opt2}$` captures a named field. All parts except `$name$` are optional: -``` +```text $fieldName = patternName {parse=json} {regex=\\d+} {attrWhitelist=foo.*} {timezone=UTC} $ ``` @@ -101,7 +101,7 @@ A `$field$` followed by a **space** with no explicit pattern defaults to the `qu ### Escaping literals - Literal `$`: `\$$` (backslash, dollar, terminator). -- Literal single space: `\\ ` (backslash-space); a bare space matches `\s+`. +- Literal single space: `\\` (backslash-space); a bare space matches `\s+`. - Literal backslash: `\\\\`. ## Patterns @@ -191,6 +191,7 @@ rewrites: [ ``` Notes: + - `$1..$n` reference regex capture groups. - `replaceAll: true` replaces every match, not just the first. - `outputIfNoMatch: false` suppresses the output field when the regex doesn't match (default is to copy `input` verbatim). diff --git a/plugins/s1-secops-skills/skills/sdl-solutions/SKILL.md b/plugins/s1-secops-skills/skills/sdl-solutions/SKILL.md index 10011de..3cc9565 100644 --- a/plugins/s1-secops-skills/skills/sdl-solutions/SKILL.md +++ b/plugins/s1-secops-skills/skills/sdl-solutions/SKILL.md @@ -61,6 +61,7 @@ This skill orchestrates the SentinelOne primitive skills. Load the ones a playbo - **Empty suppression.** Inventory empties (for example `riskFactors` as the string `"[]"`) are converted to null in the savelookup so enrichment never writes an empty field. - **Preview before deploy.** Always show rendered config and an example enriched record first. - **Idempotence.** Read the current version of any SDL config file before overwriting, and pass it as the expected version. Hyperautomation import always creates a new workflow, so to update one, replace it rather than re-import blindly. +- **Dashboards: create by name once, update by udoId.** Only `/dashboards/` files carry a `udoId`; everything else (`/lookups/`, `/datatables/`, `/logParsers/`, `/automaticLookups`) is name-addressed. A name-addressed write to an existing dashboard creates a duplicate instead of updating it, so record the `udoId` returned by the initial create and address the dashboard by `udoId` on every later write. Solutions are re-runnable, so a name-addressed re-deploy silently doubles the dashboard on each run. - **Asset mapping is built in.** Any parser or detection a solution creates must carry the minimum attributes that let an alert bind its Target Asset, so alerts are not "Unknown Device". The endpoint lookup captures `device_agentid` (the numeric console agent id); the parser stamps `device.uid` = `device_agentid` plus an endpoint `class_uid`; and scheduled detections set `entityMappings` on the device identity columns (`device_host` / `device_agentid` / `device_agentuuid`). Binding reconciles `device.uid` against the live Asset Inventory, so a real enrolled agent id is required (a fabricated id stays Unknown Device). The tested binding matrix and the minimum set live in `powerquery/references/detection-rules.md`. ## Reference files diff --git a/plugins/s1-secops-skills/skills/sdl-solutions/references/alert-noise-reduction.md b/plugins/s1-secops-skills/skills/sdl-solutions/references/alert-noise-reduction.md index 34ae9b7..9db7367 100644 --- a/plugins/s1-secops-skills/skills/sdl-solutions/references/alert-noise-reduction.md +++ b/plugins/s1-secops-skills/skills/sdl-solutions/references/alert-noise-reduction.md @@ -59,7 +59,7 @@ four-way classification of each pair: ## Step 0: confirm the alert class and window -``` +```text class_uid=99602001 | group c=count() | limit 1 ``` @@ -73,29 +73,35 @@ Run these with the confirmed filter and window. Each is parameterized on ` finding_info.title=* | group Alerts=count() by Product=metadata.product.name | sort -Alerts ``` Ingested vs S1-native (the "is this even our detection" test): -``` + +```text | group Alerts=count() by Product=metadata.product.name, LogName=metadata.log_name, AnalyticType=finding_info.analytic.type, TypeId=finding_info.analytic.type_id | sort -Alerts ``` + Confirm ingestion for a suspected source with a rule-uid presence check (0 rows = ingested, not a rule): ` metadata.product.name='' finding_info.analytic.uid=* | group c=count() | limit 1`. Severity split: -``` + +```text severity_id=* | group Alerts=count() by severity_id | sort severity_id ``` Noisiest signatures: -``` + +```text finding_info.title=* | group Alerts=count() by Detection=finding_info.title, Product=metadata.product.name, Category=finding_info.analytic.category | sort -Alerts | limit 25 ``` Time series (spot an onboarding spike that explains a step change in volume): -``` + +```text | group Alerts=count() by timestamp=timebucket('1 day') | sort timestamp ``` @@ -104,9 +110,11 @@ Time series (spot an onboarding spike that explains a step change in volume): For the noisiest ingested source, schema-discover its raw feed (`dataSource.name=''`) to find the field that records what the source did with the event (commonly `unmapped.action`). Then break the dominators down by signature x action x severity: -``` + +```text dataSource.name='' finding_info.title=* | group Alerts=count() by Signature=finding_info.title, Action=, Severity=severity_id | sort -Alerts | limit 25 ``` + Label each dominator: already-actioned (action in the discovered block/drop/sinkhole/reset set), informational-only, or signal-worth-keeping. This table is the evidence for every recommendation. diff --git a/plugins/s1-secops-skills/skills/sdl-solutions/references/asset-enrichment.md b/plugins/s1-secops-skills/skills/sdl-solutions/references/asset-enrichment.md index eaa3e3e..93e5fb3 100644 --- a/plugins/s1-secops-skills/skills/sdl-solutions/references/asset-enrichment.md +++ b/plugins/s1-secops-skills/skills/sdl-solutions/references/asset-enrichment.md @@ -63,7 +63,7 @@ Cloud, confirm the real source columns first with `| datasource [from username= action=logon outcome=success ``` -``` +```text dataSource.name = '{{DATASOURCE_NAME}}' | sort -timestamp | limit 10 | columns timestamp, metadata.version, {{HOSTNAME_FIELD}}, {{USERNAME_FIELD}}, diff --git a/plugins/s1-secops-skills/skills/sdl-solutions/references/custom-detection-exclusions.md b/plugins/s1-secops-skills/skills/sdl-solutions/references/custom-detection-exclusions.md index 076421c..a1db63b 100644 --- a/plugins/s1-secops-skills/skills/sdl-solutions/references/custom-detection-exclusions.md +++ b/plugins/s1-secops-skills/skills/sdl-solutions/references/custom-detection-exclusions.md @@ -69,13 +69,13 @@ A single-event rule body is one boolean S1QL expression with NO pipes, so there Body shape (goes in `data.s1ql`): -``` +```text AND NOT ( in:anycase ('','',...)) ``` Worked example (encoded PowerShell, excluding engineering accounts), tenant-validated 2026-06-24: -``` +```text dataSource.name = 'SentinelOne' AND event.type = 'Process Creation' AND src.process.name in ('powershell.exe','pwsh.exe') AND src.process.cmdline matches '(?i)\s-(e|en|enc|enco|encod|encode|encoded|encodedc|encodedco|encodedcom|encodedcomm|encodedcomma|encodedcomman|encodedcommand)\b' @@ -119,7 +119,7 @@ A correlation rule matches multiple events across a time window: `s1ql` stays em Sub-query body shape (each entry in `subQueries[]`): -``` +```text AND NOT ( in:anycase ('','',...)) ``` @@ -161,7 +161,7 @@ If the analyst chose correlation, you are done after deploying and validating th A lookup join tags each candidate event with the matching exclusion-list value (or null if the event is not on the list). The rule then keeps only the null rows: -``` +```text | lookup excl = reason from
.csv by | filter excl = null // kept rows had NO exclusion-list entry @@ -275,11 +275,12 @@ Two ways, pick per the prompt: `sdl_put_file`. The header row names the columns; the key column is referenced by name in the `lookup`. A useful shape carries the match key plus context: - ``` + ```text value,reason,owner,added # custom-value list www.example.com,Sanctioned corporate domain,SecOps,2026-06-22 ``` - ``` + + ```text cidr,reason,owner,added # asset list (IP / subnet) 10.0.0.0/8,Internal RFC1918 client range,NetOps,2026-06-22 1.2.3.4/32,Known vulnerability scanner,SecOps,2026-06-22 @@ -292,7 +293,7 @@ Two ways, pick per the prompt: example every asset tagged `scanner` in the Asset Inventory), build it with a `datasource` + `savelookup` query and refresh it on a schedule (Step 4). Example asset-tag list builder: - ``` + ```text | datasource assets from 'surface/endpoint' | filter array_contains(tags, 'allowlist') | columns cidr = agentLastReportedIp, reason = 'Asset Inventory allowlist tag', owner = s1SiteName @@ -314,7 +315,7 @@ anti-join. Because the STAR scheduled-rule validator accepts only `=` and `=:any below uses the hostname `=:anycase` asset variant. Demo body (Akamai DNS, asset list AND domain list chained): -``` +```text dataSource.name='{{SOURCE}}' {{BASE_FILTER}} {{KEY_FIELD}} = * | lookup excl_asset = reason from {{ASSET_TABLE}} by host =:anycase device.name | filter excl_asset = null @@ -444,7 +445,7 @@ deployed through the matching primitive skill. The `` is the solution/cu | Correlation detection rule | `assets/exclusion_detection_correlation.template.json` | STAR rule via `POST /web/api/v2.1/cloud-detection/rules` (`queryType: correlation`) | Multi-event correlation (thresholds / sequences) with the exclusion as an inline hardcoded `AND NOT ( in:anycase (...))` negative list appended to each sub-query in `data.correlationParams`. `s1ql` empty, `queryLang: 2.0`. No lookup table, no dashboard. Supports mitigation. Tenant-validated 2026-07-01 | | Scheduled detection rule | `assets/exclusion_detection.template.json` | STAR rule via `POST /web/api/v2.1/cloud-detection/rules` (`queryType: scheduled`) | Base detection wrapped with the lookup anti-join (`\| lookup ... \| filter excl = null`). Supports `=` and `=:anycase` only | | CIDR/wildcard detection + UAM alert | `assets/exclusion_detection_ha_workflow.template.json` | Hyperautomation workflow (account/site scope) | Runs the `=:cidr` / `=:wildcard` exclusion the STAR validator rejects, via the SDL LRQ (launch + poll), then posts a self-contained OCSF S1 SecurityAlert (`class_uid 99602001`) to UAM with the offender mapped as indicator + asset | -| Exclusion-effectiveness dashboard | `assets/exclusion_dashboard.template.json` | `sdl_put_file /dashboards/ Exclusions` | Total vs excluded vs net, exclusion rate, excluded over time, by list / reason / value, plus the post-exclusion threat view | +| Exclusion-effectiveness dashboard | `assets/exclusion_dashboard.template.json` | `sdl_put_file /dashboards/ Exclusions` (create); re-deploy by `udoId` | Total vs excluded vs net, exclusion rate, excluded over time, by list / reason / value, plus the post-exclusion threat view | | List-refresh workflow (optional) | `assets/exclusion_refresh_workflow.template.json` | Hyperautomation workflow (account/site scope) | Nightly rebuild of a source-of-truth (savelookup) exclusion list; not needed for static analyst-supplied CSVs | Common tokens across templates: `{{PREFIX}}`, `{{SOURCE}}`, `{{BASE_FILTER}}`, `{{KEY_FIELD}}`, diff --git a/plugins/s1-secops-skills/skills/sdl-solutions/references/data-source-onboarding.md b/plugins/s1-secops-skills/skills/sdl-solutions/references/data-source-onboarding.md index 800f6bc..8c20380 100644 --- a/plugins/s1-secops-skills/skills/sdl-solutions/references/data-source-onboarding.md +++ b/plugins/s1-secops-skills/skills/sdl-solutions/references/data-source-onboarding.md @@ -32,14 +32,14 @@ question at Step 4. Do not front-load a long form. The editability of a source in SDL is decided by one signal: the `parser` attribute on its events. -``` +```text parser=* | group events=count() by parser | sort -events | limit 200 ``` Then map parser to the source by pulling a sample. Because an un-normalised source has no `dataSource.name` yet, do not search by `dataSource.name`; search by the parser: -``` +```text parser='' | sort -timestamp | limit 20 ``` @@ -95,11 +95,11 @@ against `sdl-log-parser/references/ocsf-schema-documentation.md`, never invented block needs `version: 1` and ops under `transformations` (error `unsupported event mapper version -1` means `version:` is missing). -3. **Set the four attributes and map to OCSF.** Give the source a clean `dataSource.name` and +1. **Set the four attributes and map to OCSF.** Give the source a clean `dataSource.name` and `dataSource.vendor`, and map its fields to OCSF (`src_endpoint.ip`, `dst_endpoint.ip`, `actor.user.name`, `network_activity` class fields, etc.) so it is queryable with the same schema as the rest of the lake. -4. **Add asset enrichment.** Asset attributes (OS, agent UUID, criticality, AD groups, SID, risk +2. **Add asset enrichment.** Asset attributes (OS, agent UUID, criticality, AD groups, SID, risk factors) are not in the source telemetry. They come from the Asset Inventory through the PowerQuery `datasource` command, which is a query, not a REST call: see `powerquery/references/datasource-command.md`. Reuse the asset-enrichment solution: @@ -112,9 +112,9 @@ against `sdl-log-parser/references/ocsf-schema-documentation.md`, never invented `device_agentid` plus an endpoint `class_uid` in the parser `mappings` so events-type rules auto-bind (the tested minimum); for network/identity sources, bind via the scheduled-rule `entityMappings` path in Step 3 instead. -5. **Bump `metadata.version`** on every change. This is the propagation canary in Step 2. -6. **Deploy** with `sdl_put_file` passing the version you read as `expectedVersion`. -7. **Validate** by HEC re-ingesting one real sample line with `?sourcetype=` and +3. **Bump `metadata.version`** on every change. This is the propagation canary in Step 2. +4. **Deploy** with `sdl_put_file` passing the version you read as `expectedVersion`. +5. **Validate** by HEC re-ingesting one real sample line with `?sourcetype=` and querying it back, confirming `dataSource.name`, the OCSF fields, and the enriched `device_*` / `user_*` fields populate, and that empty inventory values are null rather than `"[]"`. @@ -125,7 +125,7 @@ live stream (tenant-validated). Sleep about 5 minutes, then poll until the new ` appears on fresh events. Each subsequent parser edit incurs the same 3 to 5 minute wait, so batch parser changes rather than deploying one field at a time: -``` +```text dataSource.name='' | group events=count() by metadata.version | sort -events ``` @@ -307,7 +307,7 @@ A full deployment produces the artifacts below. Each renders from a template in | Endpoint lookup builder | `assets/savelookup_endpoint.pq` | SDL datatable `/datatables/EndpointLookup` | Persist device context keyed by hostname for the parser `lookup` | | Identity lookup builder | `assets/savelookup_identity.pq` | SDL datatable `/datatables/IdentityLookup` | Persist AD/user context keyed by samAccountName for the parser `lookup` | | IP-keyed endpoint builder | `assets/savelookup_endpoint_byip.pq` | SDL datatable `/datatables/EndpointByIp` | Device context keyed by IP for network sources that key enrichment on client IP | -| Source dashboard | `assets/onboarding_dashboard.template.json` | `sdl_put_file /dashboards/ Overview` | Operational view: ingest volume, action breakdown, top talkers/ports/users/devices, geo, signatures | +| Source dashboard | `assets/onboarding_dashboard.template.json` | `sdl_put_file /dashboards/ Overview` (create); re-deploy by `udoId` | Operational view: ingest volume, action breakdown, top talkers/ports/users/devices, geo, signatures | | Source detections | `assets/onboarding_detection.template.json` | STAR rule via `POST /web/api/v2.1/cloud-detection/rules` | MITRE-mapped scheduled detections for the source class with `entityMappings` Target-Asset binding | | Threat-response workflow | `assets/threat_response_workflow.template.json` | Hyperautomation workflow import | Alert-triggered SOC playbook: extract IOCs, VT-gate, contain, document, notify | | Refresh workflow | `assets/refresh_workflow.template.json` | Hyperautomation workflow import | Re-run the savelookup builders on a schedule so enrichment tables stay current | diff --git a/plugins/s1-secops-skills/skills/sdl-solutions/references/detection-as-code.md b/plugins/s1-secops-skills/skills/sdl-solutions/references/detection-as-code.md index 56b93db..e741b1a 100644 --- a/plugins/s1-secops-skills/skills/sdl-solutions/references/detection-as-code.md +++ b/plugins/s1-secops-skills/skills/sdl-solutions/references/detection-as-code.md @@ -80,7 +80,7 @@ scope at account level. Confirm the console/tenant before writing anything. Copy `assets/detection-as-code-starter/` to the user's `REPO_TARGET` and render it: -``` +```text detection-as-code/ ├── detections/{endpoint,identity,cloud}/ # one rule per .toml, grouped by target system ├── scripts/dac_sync.py # validate + convert + idempotent sync (CI calls this) @@ -148,7 +148,7 @@ lint, not in the console. | Type | `query_type` | Body field | Fires | Mitigation | API specifics the converter enforces | |---|---|---|---|---|---| -| Single event (STAR) | `events` | `s1ql` (boolean S1QL) | per event, real time | yes | rejects a pipe `|` (that is PowerQuery); converter sets `queryLang` 2.0, so bodies must use 2.0 operators (`contains:anycase`, `in:anycase`), not 1.0 forms (`ContainsCIS`, `In`), which are accepted at create but never fire | +| Single event (STAR) | `events` | `s1ql` (boolean S1QL) | per event, real time | yes | rejects a pipe `|` (that is PowerQuery); converter sets `queryLang`2.0, so bodies must use 2.0 operators (`contains:anycase`,`in:anycase`), not 1.0 forms (`ContainsCIS`,`In`), which are accepted at create but never fire | | Correlation | `correlation` | `[correlation]` + `[[correlation.subqueries]]` | when subqueries match in window | yes | requires `entity`, `match_in_order`, 1 to 10 subqueries; `window_minutes` in {1,5,10,30,60,240,480,720}; converter sets `queryLang` 2.0 (the API requires it) | | Scheduled | `scheduled` | `[scheduled].query` (PowerQuery) | on interval over lookback | no | forces `queryLang` 2.0, `treatAsThreat` UNDEFINED, `networkQuarantine` false; checks run-interval vs lookback | diff --git a/plugins/s1-secops-skills/skills/sdl-solutions/references/ingest-health-monitoring.md b/plugins/s1-secops-skills/skills/sdl-solutions/references/ingest-health-monitoring.md index 872083d..133f103 100644 --- a/plugins/s1-secops-skills/skills/sdl-solutions/references/ingest-health-monitoring.md +++ b/plugins/s1-secops-skills/skills/sdl-solutions/references/ingest-health-monitoring.md @@ -19,6 +19,7 @@ Device identity coalesce (one key for all source types, falls back to source): hostname ? ... : dataSource.name`. Two tenant-level savelookup tables: + - `ingestHealthBaseline` key `srckey`=`source||device||hour-of-day`: `exp_gib, sig_gib, exp_ev, sig_ev` (detections and dashboard read sigma from here). - `ingestHealthSourceStats` key `devkey`=`source||device`: `sig_gib, sig_ev` (pooled across all hours; @@ -59,7 +60,7 @@ Which sources are monitored is controlled by ONE editable CSV lookup, not by har detection rules, the watchdog live subquery, and every dashboard panel) carries the same anti-join right after its opener: -``` +```text | lookup ih_excl_n = reason from ingestHealthExclusions.csv by value =:anycase dataSource.name | lookup ih_excl_v = reason from ingestHealthExclusions.csv by value =:anycase dataSource.vendor | filter (ih_excl_n = null and ih_excl_v = null) @@ -72,6 +73,7 @@ row (`Zscaler,vendor,decommissioned`); to resume, delete the row. No query edits table (header only) excludes nothing. Mechanics: + - The anti-join tests `value` against BOTH `dataSource.name` and `dataSource.vendor`, so `match_type` is documentation only: a row matches if its value equals either field (case-insensitive via `=:anycase`). - This is the right place to drop the platform's own internal streams, which a vendor filter misses. @@ -167,7 +169,6 @@ missing milliseconds alone; carrying `tz` does not compensate for a millisecond- `start_at`. Keep `"tz": "UTC"` in the template and always render `{{START_AT}}` with milliseconds. - ## Example alert emails Real Watchdog alert emails from the demo tenant (hourly runs, 2026-07-31 and 2026-08-01). @@ -236,5 +237,5 @@ A full deployment produces the artifacts below. Each renders from a template in | Ingest health detections (unified) | `assets/ingesthealth_detections.template.json` | STAR rule via `POST /web/api/v2.1/cloud-detection/rules` | Unified Volume Spike, Volume Drop, Ingest Lag scheduled rules vs the seasonal baseline; each handles both levels and tags alerts with a `level` column | | Parser Drift (OPTIONAL) | `assets/ingesthealth_detections_parser_drift_optional.template.json` | STAR rule via `POST /web/api/v2.1/cloud-detection/rules` | Per-parser drift detector. Environment-specific, ships Disabled, do NOT deploy by default; tune `drift_ratio` first | | Ingest Loss Watchdog workflow | `assets/ingesthealth_watchdog.workflow.template.json` | Hyperautomation workflow import | Hourly per-device anti-join that emails when a baselined device stops sending logs | -| Ingest health dashboard | `assets/ingesthealth_dashboard.template.json` | `sdl_put_file /dashboards/Ingest Health Monitoring` | Five-tab view: Overview, Devices, Volume & Sources, Latency & Lag, Parser Health | +| Ingest health dashboard | `assets/ingesthealth_dashboard.template.json` | `sdl_put_file /dashboards/Ingest Health Monitoring` (create); re-deploy by `udoId` | Five-tab view: Overview, Devices, Volume & Sources, Latency & Lag, Parser Health | | Alert Notifier workflow | `assets/ingesthealth_alert_notifier.workflow.template.json` | Hyperautomation workflow import | Alert-triggered email on any "Ingest Health" detection | diff --git a/plugins/s1-secops-skills/skills/sdl-solutions/references/risk-based-alerting.md b/plugins/s1-secops-skills/skills/sdl-solutions/references/risk-based-alerting.md index 05c6d88..6bdf791 100644 --- a/plugins/s1-secops-skills/skills/sdl-solutions/references/risk-based-alerting.md +++ b/plugins/s1-secops-skills/skills/sdl-solutions/references/risk-based-alerting.md @@ -52,7 +52,7 @@ HEC (HTTP Event Collector) is SentinelOne's HTTP event-ingest endpoint; the risk The risk index is created implicitly on first publish: ingest one sample risk event to materialise it, then confirm it queries back: -``` +```json POST {{HEC_INGEST_URL}}/services/collector/event?isParsed=true Authorization: Bearer S1-Scope: {{ACCOUNT_ID}} @@ -62,7 +62,7 @@ S1-Scope: {{ACCOUNT_ID}} `isParsed=true` indexes the JSON keys directly (no parser). The dotted `dataSource.name` key makes it land as the `risk` source. Confirm: -``` +```text dataSource.name='risk' risk_object=* | group events=count(), score=sum(number(risk_score)), tactics=estimate_distinct(mitre_tactic) by risk_object, risk_object_type | sort -score | limit 25 ``` @@ -72,7 +72,7 @@ dataSource.name='risk' risk_object=* | group events=count(), score=sum(number(ri Write `{{PREFIX}}RiskFactors.csv` to `/datatables/`. Header `factor_key,factor_type,risk_multiplier,reason`. Build it from the Asset Inventory (criticality / privileged / riskFactors) plus optional hand-edited overrides. The contributor PQ joins it: -``` +```text | lookup mult=risk_multiplier from {{PREFIX}}RiskFactors.csv by factor_key =:anycase risk_object | let risk_score = base_score * (mult ? number(mult) : 1) ``` @@ -85,7 +85,7 @@ This is the SentinelOne equivalent of Splunk RBA "importing customer assets via Build the factor table from AD with a `savelookup`: -``` +```text | datasource assets from 'surface/identity' | filter resourceType='AD User' principalName=* | let mult = privileged=true ? 2.0 : (number(adminCount) > 0 ? 1.5 : (serviceAccount=true ? 0.5 : 1.0)) @@ -103,7 +103,7 @@ Build the factor table from AD with a `savelookup`: Render the contributor PowerQueries from `rba_contributors.json`. Each ends in: -``` +```text | columns risk_object, risk_object_type, risk_score, base_score, mitre_tactic, mitre_technique, contributor, threat_object, threat_object_type, endpoint, risk_message | limit 1000 ``` @@ -129,7 +129,7 @@ Deploy the four STAR scheduled rules (`assets/rba_incident_cumulative_score.temp Cumulative-score body (per object type): -``` +```text dataSource.name='risk' risk_object_type='{{RISK_OBJECT_TYPE}}' risk_object=* | group risk_score_total=sum(number(risk_score)), tactics=estimate_distinct(mitre_tactic), techniques=estimate_distinct(mitre_technique), contributors=estimate_distinct(contributor), events=count(), first_seen=oldest(timestamp), last_seen=newest(timestamp), top_message=max_by(risk_message, number(risk_score)) by risk_object | filter risk_score_total >= {{THRESHOLD}} @@ -199,7 +199,7 @@ The amplification is the point. The same four observations on a standard, non-pr | Risk collector | `assets/rba_collector.workflow.template.json` | Hyperautomation (scheduled, account/site) | Runs contributors and publishes risk events | | Incident rule, cumulative score (user + host) | `assets/rba_incident_cumulative_score.template.json` | STAR scheduled rule via `/cloud-detection/rules` | Fire when 24h cumulative score per object >= threshold | | Incident rule, multi-tactic (user + host) | `assets/rba_incident_multitactic.template.json` | STAR scheduled rule via `/cloud-detection/rules` | Fire when 7d distinct MITRE tactics per object >= threshold | -| RBA dashboard | `assets/rba_dashboard.template.json` | `sdl_put_file /dashboards/{{PREFIX}}-RBA` | Leaderboard, score over time, MITRE / contributor / threat-object breakdowns, timeline | +| RBA dashboard | `assets/rba_dashboard.template.json` | `sdl_put_file /dashboards/{{PREFIX}}-RBA` (create); re-deploy by `udoId` | Leaderboard, score over time, MITRE / contributor / threat-object breakdowns, timeline | | Response flow (optional) | reuse `assets/threat_response_workflow.template.json` | Hyperautomation (alert-triggered) | VT-gated containment off an RBA incident alert | | Demo console (optional) | `assets/rba_console/` | presenter laptop (`python3 server.py`) | Browser UI: talk track, artefacts, live leaderboard, and a read/write risk-factor editor | | Risk register (optional) | `assets/rba_risk_register.savelookup.pq` | daily scheduled flow to `{{PREFIX}}RiskRegister` | Persisted decayed running risk per object for long-horizon trending | diff --git a/plugins/s1-secops-skills/skills/soc-investigator/SKILL.md b/plugins/s1-secops-skills/skills/soc-investigator/SKILL.md index c552351..808673e 100644 --- a/plugins/s1-secops-skills/skills/soc-investigator/SKILL.md +++ b/plugins/s1-secops-skills/skills/soc-investigator/SKILL.md @@ -213,345 +213,21 @@ Always show the optional third-party block at the bottom so the user knows it's If approval gates are OFF, omit the `→ APPROVAL GATE` lines from the plan. -After the user confirms, begin Phase 1 immediately. +After the user confirms, begin Phase 1 immediately, following the phase-by-phase run instructions for the chosen mode in `references/investigation-modes.md`. --- -## Workflow: SHORT Mode (5 min) +## Investigation modes -**Goal**: Fast triage. What happened at a glance. +The skill runs one of three cumulative SentinelOne-focused modes, chosen at intake. The summary below sizes each mode; the full phase-by-phase run instructions (fetch steps, example JSON, and PowerQuery templates) live in `references/investigation-modes.md`. Read the chosen mode's phases there before executing, and follow them in order. -### Phase 1: Alert Ingest & Entity Extraction (5 min) +| Mode | Duration | Tokens | What it does | +|------|----------|--------|--------------| +| SHORT | ~5 min | ~2k | SentinelOne alert data only: fetch alerts, extract entities (users, endpoints, IPs, hashes), draft timeline, infer MITRE. Output: entities.json, timeline_draft.csv, mitre_draft.json, summary.md. | +| MEDIUM | ~15 min | ~8k | SHORT plus IOC enrichment (VirusTotal + S1 IOC API) and one PowerQuery per endpoint (process tree + network). Output: threat_intel.json, powerquery_results.jsonl, timeline_enriched.json, report.md, timeline.csv. | +| LONG | ~45 min | ~30k | MEDIUM plus four deep PowerQueries per endpoint (process, files, registry, network), SDL threat-intel correlation, and full MITRE refinement. Output: threat_intel_complete.json, full_report.md, timeline_forensic.csv. | -#### 1.1 Fetch alerts - -```text -Use: mgmt-console-api -Query: GET /threats (filtered by user input) -Output: alerts.jsonl -``` - -#### 1.2 Single-pass entity extraction - -```json -{ - "alerts_processed": 42, - "entities": { - "users": [{"user": "john.doe", "alert_count": 5, "first_seen": "2025-06-16T10:00:00Z"}], - "endpoints": [{"agentId": "...", "agentName": "DESKTOP-ABC123", "alert_count": 12}], - "ips": [{"ip": "192.168.1.100", "type": "src", "alert_count": 5}], - "iocs": [{"value": "hash_abc123...", "type": "hash", "alert_count": 3}] - } -} -``` - -#### 1.3 Draft timeline (alert order, not forensic) - -```text -- 2025-06-16 10:00 | HIGH | DESKTOP-ABC123 | john.doe | Suspicious Process Execution (powershell) -- 2025-06-16 10:05 | MEDIUM | DESKTOP-ABC123 | john.doe | Registry Modification (persistence) -- 2025-06-16 10:10 | HIGH | DESKTOP-XYZ789 | jane.smith | File Download (executable) -``` - -#### 1.4 Draft MITRE (simple inference from alert type) - -```json -{ - "tactics": ["Execution", "Persistence"], - "techniques": [ - {"id": "T1059", "name": "Command and Scripting Interpreter", "confidence": 0.8}, - {"id": "T1112", "name": "Modify Registry", "confidence": 0.7} - ] -} -``` - -**Output files**: - -- `entities.json` - extracted entities -- `timeline_draft.csv` - simple chronological alert list -- `mitre_draft.json` - inferred tactics/techniques -- `summary.md` - 1-page overview for quick review - -**Approval gate** (if enabled): - -```text -✓ SHORT investigation complete. - - 42 alerts processed - - 7 unique users, 5 endpoints, 12 IPs, 8 IOCs extracted - - Draft timeline and MITRE mapping ready - Ready to proceed? [YES | MODIFY INPUT | CANCEL] -``` - ---- - -## Workflow: MEDIUM Mode (15 min) - -**Goal**: Contextual investigation. Understand what the IOCs are and what the process chain looked like. - -Includes SHORT, plus: - -### Phase 2: IOC Enrichment (5 min) - -#### 2.1 Batch IOC lookups - -```text -Use: Purple MCP (VirusTotal) + mgmt-console-api (S1 IOC API) - -For each IOC in entities.json: - - VirusTotal: GET /files/{hash}, /domains/{domain}, /ip_addresses/{ip} - Extract: detections, verdict, prevalence, last_analysis_date - - S1 IOC API: GET /iocs (match by hash/domain/ip) - Extract: verdict, threat_type, confidence -``` - -#### 2.2 Sample if >20 IOCs - -```text -If IOC count > 20: - Sort by alert_count (descending) - Lookup only top 20 IOCs - Note in output: "Sampled 20/52 IOCs by frequency" -``` - -**Output**: `threat_intel.json` - -### Phase 3: Endpoint Process Context (5 min) - -#### 3.1 One PowerQuery per unique endpoint - -```text -Use: powerquery skill - -Query A: Process tree for user during alert window - src.process.user = '' - AND event.timestamp >= '' - | columns ts=event.timestamp, pid=src.process.pid, - cmd=src.process.commandline, user=src.process.user, - parent=src.process.parent.commandline - | sort -ts - | limit 100 - -Query B: Network behavior for same user - src.process.user = '' - AND event.type in ('dns_request', 'network_connect') - AND event.timestamp >= '' - | columns ts=event.timestamp, action=event.type, - process=src.process.commandline, domain=network.dns.domain, - dstIp=tgt.ip.address, dstPort=tgt.port - | sort -ts - | limit 100 -``` - -**Store results**: `powerquery_results.jsonl` (one result per query) - -### Phase 4: Enriched Analysis (3 min) - -**4.1 Merge alert + IOC + process context** into `timeline_enriched.json` - -**4.2 Refine MITRE with IOC context** - add evidence chains and confidence scores - -**Output files**: - -- `threat_intel.json` - IOC lookups -- `powerquery_results.jsonl` - process/network queries -- `timeline_enriched.json` - merged context -- `report.md` - formatted investigation report (1-2 pages) -- `timeline.csv` - timeline for import to Excel/Splunk - -**Approval gate + Third-Party Option** (if enabled): - -```text -✓ MEDIUM investigation complete. - - 8 IOCs looked up (3 malicious, 2 suspicious, 3 clean) - - 5 endpoints queried (process trees + network behavior) - - Enriched timeline with IOC context ready - -Next steps: - [1] Review findings and stop here - [2] Dig into third-party sources (M365, Entra, Sharepoint, etc.) - [3] Cancel and refine input - -Choose: [1 | 2 | 3] -``` - ---- - -## Workflow: LONG Mode (45 min) - -**Goal**: Exhaustive investigation. Every lead followed, full forensic timeline, visualizations. - -Includes MEDIUM, plus: - -### Phase 5: Deep Forensic Queries (25 min, PARALLEL) - -#### 5.1 For each endpoint in alerts, run 4 deep PowerQueries - -```text -Per endpoint: - -Query A: Full process execution tree (parent, siblings, children) - agent.uuid = '' - AND event.timestamp >= '' - | columns ts, pid, ppid, cmd, user, cmdline_hash - | limit 500 - -Query B: File operations (writes, moves, deletes, renames) - agent.uuid = '' - AND event.type in ('file_write', 'file_move', 'file_delete', 'file_rename') - AND event.timestamp >= '' - | columns ts, action, path, sha256, size - | limit 500 - -Query C: Registry operations (if Windows) - agent.uuid = '' - AND event.type = 'registry_operation' - AND event.timestamp >= '' - | columns ts, operation, registry_path, registry_value - | limit 500 - -Query D: Full network behavior - agent.uuid = '' - AND event.type in ('dns_request', 'network_connect', 'http_request', 'tls_handshake') - AND event.timestamp >= '' - | columns ts, event_type, process, src_ip, dst_ip, dst_port, domain, url - | limit 500 -``` - -**Run in parallel** across all endpoints. Store results: `powerquery_results.jsonl` - -### Phase 6: Threat Intelligence Deep-Dive (10 min, PARALLEL) - -#### 6.1 SDL threat intelligence correlation - -```text -Use: powerquery skill - -For each IOC in entities.json: - indicator.hash = '' OR indicator.domain = '' OR indicator.ip = '' - AND event.timestamp >= '' - | group threat_count=count() by agent.uuid, indicator.threat_type, indicator.source - | sort -threat_count - | limit 100 -``` - -**Goal**: Find other endpoints/users that encountered same IOCs (lateral spread, supply chain). - -#### 6.2 Expand IOC lookups (all IOCs, not sampled) - -VirusTotal: Full report for all hashes, domains, IPs + S1 IOC API: All IOCs - -**Store results**: `threat_intel_complete.json` - -### Phase 7: Forensic Analysis & Synthesis (5 min) - -**7.1 Build forensic timeline** - merge raw alerts + PowerQuery results + IOC hits + SDL correlations - -**7.2 Refine MITRE with full forensic evidence** - add confidence scores and evidence chains - -**Output files**: - -- `full_report.md` - comprehensive investigation report -- `timeline_forensic.csv` - full timeline for SIEM/Excel import -- All JSON outputs from earlier phases - -**Approval gate + Third-Party Option** (if enabled): - -```text -✓ LONG investigation complete. - - 42 alerts processed - - 12 endpoints deep-queried (4 queries each = 48 queries) - - 8 IOCs fully enriched (VirusTotal + SDL correlation) - - Cross-tenant IOC correlation identified (7 other incidents) - - Full forensic timeline with visualizations ready - -Next steps: - [1] Review findings and stop here - [2] Deep-dive third-party interrogation (M365, Entra, Sharepoint, etc.) - [3] Cancel and refine input - -Choose: [1 | 2 | 3] -``` - ---- - -## Workflow: THIRD-PARTY INVESTIGATION (Optional, Iterative) - -**Triggered by**: User selects option [2] from MEDIUM or LONG approval gate - -**Goal**: Correlate extracted entities (users, IPs, domains) across third-party data sources and detect anomalies. - -### Phase 1: Discover Available Data Sources (2 min) - -#### 1.1 Query all available data sources - -```text -Use: powerquery skill - -dataSource.name = * -| group ct=count() by dataSource.name -| sort -ct -| limit 50 -``` - -#### 1.2 Ask user which sources to investigate - -```text -Available data sources detected: - ✓ Microsoft 365 (1.2M events) - ✓ Entra ID (567K events) - ✓ SharePoint Online (234K events) - ✓ Exchange Online (456K events) - ... - -Which sources are relevant to this incident? - [Select all that apply, or "all"] -``` - -### Phase 2: Schema Exploration (3 min per source) - -For each selected source: - -- Query A: list all activities (`| group ct=count() by activity_name | sort -ct | limit 30`) -- Query B: sample 10 events to see available fields - -Ask user which activities/fields to focus on before running correlation queries. - -### Phase 3: Entity Correlation (5 min per source) - -For each user, IP, and domain in `entities.json`, run targeted queries against the selected sources. - -**Output**: `third_party_correlation.json` - -### Phase 4: Anomaly Detection (5 min per source) - -For each user + activity combination, run timeseries analysis: - -```text -| let hour = timebucket('1h') -| group ct=count() by hour -| sort +hour -``` - -Compare incident period against 7-day baseline. Flag spikes > 2σ and never-before-seen patterns. - -**Output**: Append `anomalies` block to `third_party_correlation.json` - -### Phase 5: Deep Interrogation (Optional, user-driven) - -Surface significant findings, let the user choose which to deep-dive with targeted queries. Repeat until user is satisfied. - -**Output**: `deep_dive_.json` per chosen finding - -### Phase 6: Final Third-Party Report (2 min) - -Synthesize all findings into `third_party_report.md`. - -**Output files**: - -- `third_party_correlation.json` - full correlation matrix -- `anomalies.json` - timeseries spikes -- `deep_dive_*.json` - user-selected deep-dives -- `third_party_report.md` - synthesis +After MEDIUM or LONG completes, the operator may expand into third-party sources. That optional, iterative playbook (source discovery, entity correlation, anomaly detection, deep interrogation) is documented in `references/third-party-playbook.md`; run it only when the operator selects the third-party option at a MEDIUM or LONG approval gate. --- @@ -574,30 +250,9 @@ Synthesize all findings into `third_party_report.md`. --- -## Output Structure +## Output structure -```text -investigation_/ -├── INTAKE.txt # User intake responses -├── entities.json # Extracted entities -├── timeline_draft.csv # Alert timeline (SHORT+) -├── mitre_draft.json # Draft MITRE (SHORT+) -├── summary.md # Summary (SHORT) -├── threat_intel.json # IOC lookups (MEDIUM+) -├── powerquery_results.jsonl # PQ outputs (MEDIUM+) -├── timeline_enriched.json # Merged context (MEDIUM+) -├── report.md # Investigation report (MEDIUM) -├── timeline.csv # Timeline export (MEDIUM) -├── threat_intel_complete.json # Full IOC + SDL (LONG) -├── full_report.md # Full report (LONG) -├── timeline_forensic.csv # Full timeline (LONG) -├── datasources_available.json # Available third-party sources -├── schema_*.json # Schema for each source explored -├── sample_*.jsonl # Sample events per source -├── third_party_correlation.json # Entity correlation + anomalies -├── deep_dive_*.json # User-selected deep-dives -└── third_party_report.md # Third-party synthesis -``` +All phases write their artifacts into a single `investigation_/` directory (INTAKE.txt, entities.json, the per-mode timelines, reports, IOC and PowerQuery outputs, and the third-party correlation files). The full annotated file tree is in `references/output-structure.md`. --- @@ -607,9 +262,9 @@ investigation_/ 2. **Tool Discovery**: Verify required skills are available; warn on missing ones 3. **Intake**: Answer questions (alerts, mode, approvals) 4. **Investigation Plan**: Display full phase-by-phase plan; user confirms before execution begins -5. **Investigation**: Run SHORT/MEDIUM/LONG +5. **Investigation**: Run SHORT/MEDIUM/LONG per the phase instructions in `references/investigation-modes.md` 6. **Approval + Expansion Choice**: Review findings, choose to dig into third-party or stop -7. **Third-Party (optional)**: Discover sources, correlate entities, detect anomalies +7. **Third-Party (optional)**: Discover sources, correlate entities, detect anomalies (see `references/third-party-playbook.md`) 8. **Deep-Dive (optional)**: User-driven interrogation of specific findings 9. **Output**: All files saved to `investigation_/` 10. **Review**: User reviews outputs for containment/hunting/reporting diff --git a/plugins/s1-secops-skills/skills/soc-investigator/references/correlation-and-hunt-methodology.md b/plugins/s1-secops-skills/skills/soc-investigator/references/correlation-and-hunt-methodology.md index c078f21..780c8b2 100644 --- a/plugins/s1-secops-skills/skills/soc-investigator/references/correlation-and-hunt-methodology.md +++ b/plugins/s1-secops-skills/skills/soc-investigator/references/correlation-and-hunt-methodology.md @@ -83,7 +83,7 @@ time. ## Incident report structure -``` +```text Executive Summary (with a Key Verdicts table) Scope & Data Threat-Hunting & Correlation Methodology diff --git a/plugins/s1-secops-skills/skills/soc-investigator/references/investigation-modes.md b/plugins/s1-secops-skills/skills/soc-investigator/references/investigation-modes.md new file mode 100644 index 0000000..dbe43d1 --- /dev/null +++ b/plugins/s1-secops-skills/skills/soc-investigator/references/investigation-modes.md @@ -0,0 +1,260 @@ +# SOC Investigator: Investigation Mode Run Instructions + +Phase-by-phase run instructions for the SHORT, MEDIUM, and LONG investigation modes. `SKILL.md` loads this file once the operator has chosen a mode in Stage 1. Run the phases for the selected mode in order. The evidence-discipline, verdict-gate, and query-appendix rules stated in `SKILL.md` apply to every phase here. Each mode is cumulative: MEDIUM includes SHORT, and LONG includes MEDIUM. + +## Workflow: SHORT Mode (5 min) + +**Goal**: Fast triage. What happened at a glance. + +### Phase 1: Alert Ingest & Entity Extraction (5 min) + +#### 1.1 Fetch alerts + +```text +Use: mgmt-console-api +Query: GET /threats (filtered by user input) +Output: alerts.jsonl +``` + +#### 1.2 Single-pass entity extraction + +```json +{ + "alerts_processed": 42, + "entities": { + "users": [{"user": "john.doe", "alert_count": 5, "first_seen": "2025-06-16T10:00:00Z"}], + "endpoints": [{"agentId": "...", "agentName": "DESKTOP-ABC123", "alert_count": 12}], + "ips": [{"ip": "192.168.1.100", "type": "src", "alert_count": 5}], + "iocs": [{"value": "hash_abc123...", "type": "hash", "alert_count": 3}] + } +} +``` + +#### 1.3 Draft timeline (alert order, not forensic) + +```text +- 2025-06-16 10:00 | HIGH | DESKTOP-ABC123 | john.doe | Suspicious Process Execution (powershell) +- 2025-06-16 10:05 | MEDIUM | DESKTOP-ABC123 | john.doe | Registry Modification (persistence) +- 2025-06-16 10:10 | HIGH | DESKTOP-XYZ789 | jane.smith | File Download (executable) +``` + +#### 1.4 Draft MITRE (simple inference from alert type) + +```json +{ + "tactics": ["Execution", "Persistence"], + "techniques": [ + {"id": "T1059", "name": "Command and Scripting Interpreter", "confidence": 0.8}, + {"id": "T1112", "name": "Modify Registry", "confidence": 0.7} + ] +} +``` + +**Output files**: + +- `entities.json` - extracted entities +- `timeline_draft.csv` - simple chronological alert list +- `mitre_draft.json` - inferred tactics/techniques +- `summary.md` - 1-page overview for quick review + +**Approval gate** (if enabled): + +```text +✓ SHORT investigation complete. + - 42 alerts processed + - 7 unique users, 5 endpoints, 12 IPs, 8 IOCs extracted + - Draft timeline and MITRE mapping ready + Ready to proceed? [YES | MODIFY INPUT | CANCEL] +``` + +--- + +## Workflow: MEDIUM Mode (15 min) + +**Goal**: Contextual investigation. Understand what the IOCs are and what the process chain looked like. + +Includes SHORT, plus: + +### Phase 2: IOC Enrichment (5 min) + +#### 2.1 Batch IOC lookups + +```text +Use: Purple MCP (VirusTotal) + mgmt-console-api (S1 IOC API) + +For each IOC in entities.json: + - VirusTotal: GET /files/{hash}, /domains/{domain}, /ip_addresses/{ip} + Extract: detections, verdict, prevalence, last_analysis_date + - S1 IOC API: GET /iocs (match by hash/domain/ip) + Extract: verdict, threat_type, confidence +``` + +#### 2.2 Sample if >20 IOCs + +```text +If IOC count > 20: + Sort by alert_count (descending) + Lookup only top 20 IOCs + Note in output: "Sampled 20/52 IOCs by frequency" +``` + +**Output**: `threat_intel.json` + +### Phase 3: Endpoint Process Context (5 min) + +#### 3.1 One PowerQuery per unique endpoint + +```text +Use: powerquery skill + +Query A: Process tree for user during alert window + src.process.user = '' + AND event.timestamp >= '' + | columns ts=event.timestamp, pid=src.process.pid, + cmd=src.process.commandline, user=src.process.user, + parent=src.process.parent.commandline + | sort -ts + | limit 100 + +Query B: Network behavior for same user + src.process.user = '' + AND event.type in ('dns_request', 'network_connect') + AND event.timestamp >= '' + | columns ts=event.timestamp, action=event.type, + process=src.process.commandline, domain=network.dns.domain, + dstIp=tgt.ip.address, dstPort=tgt.port + | sort -ts + | limit 100 +``` + +**Store results**: `powerquery_results.jsonl` (one result per query) + +### Phase 4: Enriched Analysis (3 min) + +**4.1 Merge alert + IOC + process context** into `timeline_enriched.json` + +**4.2 Refine MITRE with IOC context** - add evidence chains and confidence scores + +**Output files**: + +- `threat_intel.json` - IOC lookups +- `powerquery_results.jsonl` - process/network queries +- `timeline_enriched.json` - merged context +- `report.md` - formatted investigation report (1-2 pages) +- `timeline.csv` - timeline for import to Excel/Splunk + +**Approval gate + Third-Party Option** (if enabled): + +```text +✓ MEDIUM investigation complete. + - 8 IOCs looked up (3 malicious, 2 suspicious, 3 clean) + - 5 endpoints queried (process trees + network behavior) + - Enriched timeline with IOC context ready + +Next steps: + [1] Review findings and stop here + [2] Dig into third-party sources (M365, Entra, Sharepoint, etc.) + [3] Cancel and refine input + +Choose: [1 | 2 | 3] +``` + +--- + +## Workflow: LONG Mode (45 min) + +**Goal**: Exhaustive investigation. Every lead followed, full forensic timeline, visualizations. + +Includes MEDIUM, plus: + +### Phase 5: Deep Forensic Queries (25 min, PARALLEL) + +#### 5.1 For each endpoint in alerts, run 4 deep PowerQueries + +```text +Per endpoint: + +Query A: Full process execution tree (parent, siblings, children) + agent.uuid = '' + AND event.timestamp >= '' + | columns ts, pid, ppid, cmd, user, cmdline_hash + | limit 500 + +Query B: File operations (writes, moves, deletes, renames) + agent.uuid = '' + AND event.type in ('file_write', 'file_move', 'file_delete', 'file_rename') + AND event.timestamp >= '' + | columns ts, action, path, sha256, size + | limit 500 + +Query C: Registry operations (if Windows) + agent.uuid = '' + AND event.type = 'registry_operation' + AND event.timestamp >= '' + | columns ts, operation, registry_path, registry_value + | limit 500 + +Query D: Full network behavior + agent.uuid = '' + AND event.type in ('dns_request', 'network_connect', 'http_request', 'tls_handshake') + AND event.timestamp >= '' + | columns ts, event_type, process, src_ip, dst_ip, dst_port, domain, url + | limit 500 +``` + +**Run in parallel** across all endpoints. Store results: `powerquery_results.jsonl` + +### Phase 6: Threat Intelligence Deep-Dive (10 min, PARALLEL) + +#### 6.1 SDL threat intelligence correlation + +```text +Use: powerquery skill + +For each IOC in entities.json: + indicator.hash = '' OR indicator.domain = '' OR indicator.ip = '' + AND event.timestamp >= '' + | group threat_count=count() by agent.uuid, indicator.threat_type, indicator.source + | sort -threat_count + | limit 100 +``` + +**Goal**: Find other endpoints/users that encountered same IOCs (lateral spread, supply chain). + +#### 6.2 Expand IOC lookups (all IOCs, not sampled) + +VirusTotal: Full report for all hashes, domains, IPs + S1 IOC API: All IOCs + +**Store results**: `threat_intel_complete.json` + +### Phase 7: Forensic Analysis & Synthesis (5 min) + +**7.1 Build forensic timeline** - merge raw alerts + PowerQuery results + IOC hits + SDL correlations + +**7.2 Refine MITRE with full forensic evidence** - add confidence scores and evidence chains + +**Output files**: + +- `full_report.md` - comprehensive investigation report +- `timeline_forensic.csv` - full timeline for SIEM/Excel import +- All JSON outputs from earlier phases + +**Approval gate + Third-Party Option** (if enabled): + +```text +✓ LONG investigation complete. + - 42 alerts processed + - 12 endpoints deep-queried (4 queries each = 48 queries) + - 8 IOCs fully enriched (VirusTotal + SDL correlation) + - Cross-tenant IOC correlation identified (7 other incidents) + - Full forensic timeline with visualizations ready + +Next steps: + [1] Review findings and stop here + [2] Deep-dive third-party interrogation (M365, Entra, Sharepoint, etc.) + [3] Cancel and refine input + +Choose: [1 | 2 | 3] +``` + +--- diff --git a/plugins/s1-secops-skills/skills/soc-investigator/references/output-structure.md b/plugins/s1-secops-skills/skills/soc-investigator/references/output-structure.md new file mode 100644 index 0000000..5db86d5 --- /dev/null +++ b/plugins/s1-secops-skills/skills/soc-investigator/references/output-structure.md @@ -0,0 +1,28 @@ +# SOC Investigator: Output Structure + +Reference file tree for the investigation output directory produced across the SHORT, MEDIUM, LONG, and third-party phases. Files are written under `investigation_/`. + +## Output Structure + +```text +investigation_/ +├── INTAKE.txt # User intake responses +├── entities.json # Extracted entities +├── timeline_draft.csv # Alert timeline (SHORT+) +├── mitre_draft.json # Draft MITRE (SHORT+) +├── summary.md # Summary (SHORT) +├── threat_intel.json # IOC lookups (MEDIUM+) +├── powerquery_results.jsonl # PQ outputs (MEDIUM+) +├── timeline_enriched.json # Merged context (MEDIUM+) +├── report.md # Investigation report (MEDIUM) +├── timeline.csv # Timeline export (MEDIUM) +├── threat_intel_complete.json # Full IOC + SDL (LONG) +├── full_report.md # Full report (LONG) +├── timeline_forensic.csv # Full timeline (LONG) +├── datasources_available.json # Available third-party sources +├── schema_*.json # Schema for each source explored +├── sample_*.jsonl # Sample events per source +├── third_party_correlation.json # Entity correlation + anomalies +├── deep_dive_*.json # User-selected deep-dives +└── third_party_report.md # Third-party synthesis +``` diff --git a/plugins/s1-secops-skills/skills/soc-investigator/references/third-party-playbook.md b/plugins/s1-secops-skills/skills/soc-investigator/references/third-party-playbook.md new file mode 100644 index 0000000..35a65fc --- /dev/null +++ b/plugins/s1-secops-skills/skills/soc-investigator/references/third-party-playbook.md @@ -0,0 +1,84 @@ +# SOC Investigator: Third-Party Investigation Playbook + +Optional iterative deep-dive into third-party data sources (M365, Entra ID, Okta, SharePoint, firewalls, and any other source ingesting into SDL) for cross-source entity correlation and anomaly detection. `SKILL.md` loads this file only when the operator selects the third-party option at a MEDIUM or LONG approval gate. The evidence-discipline, verdict-gate, and query-appendix rules stated in `SKILL.md` apply throughout. + +## Workflow: THIRD-PARTY INVESTIGATION (Optional, Iterative) + +**Triggered by**: User selects option [2] from MEDIUM or LONG approval gate + +**Goal**: Correlate extracted entities (users, IPs, domains) across third-party data sources and detect anomalies. + +### Phase 1: Discover Available Data Sources (2 min) + +#### 1.1 Query all available data sources + +```text +Use: powerquery skill + +dataSource.name = * +| group ct=count() by dataSource.name +| sort -ct +| limit 50 +``` + +#### 1.2 Ask user which sources to investigate + +```text +Available data sources detected: + ✓ Microsoft 365 (1.2M events) + ✓ Entra ID (567K events) + ✓ SharePoint Online (234K events) + ✓ Exchange Online (456K events) + ... + +Which sources are relevant to this incident? + [Select all that apply, or "all"] +``` + +### Phase 2: Schema Exploration (3 min per source) + +For each selected source: + +- Query A: list all activities (`| group ct=count() by activity_name | sort -ct | limit 30`) +- Query B: sample 10 events to see available fields + +Ask user which activities/fields to focus on before running correlation queries. + +### Phase 3: Entity Correlation (5 min per source) + +For each user, IP, and domain in `entities.json`, run targeted queries against the selected sources. + +**Output**: `third_party_correlation.json` + +### Phase 4: Anomaly Detection (5 min per source) + +For each user + activity combination, run timeseries analysis: + +```text +| let hour = timebucket('1h') +| group ct=count() by hour +| sort +hour +``` + +Compare incident period against 7-day baseline. Flag spikes > 2σ and never-before-seen patterns. + +**Output**: Append `anomalies` block to `third_party_correlation.json` + +### Phase 5: Deep Interrogation (Optional, user-driven) + +Surface significant findings, let the user choose which to deep-dive with targeted queries. Repeat until user is satisfied. + +**Output**: `deep_dive_.json` per chosen finding + +### Phase 6: Final Third-Party Report (2 min) + +Synthesize all findings into `third_party_report.md`. + +**Output files**: + +- `third_party_correlation.json` - full correlation matrix +- `anomalies.json` - timeseries spikes +- `deep_dive_*.json` - user-selected deep-dives +- `third_party_report.md` - synthesis + +---