Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.12",
"version": "1.2.14",
"author": {
"name": "Prithvi Moses",
"email": "prithvi.moses@sentinelone.com"
Expand Down
2 changes: 1 addition & 1 deletion plugins/s1-secops-skills/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "s1-secops-skills",
"version": "1.2.13",
"version": "1.2.14",
"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",
Expand Down
Binary file modified plugins/s1-secops-skills/dist/hyperautomation.skill
Binary file not shown.
Binary file modified plugins/s1-secops-skills/dist/mgmt-console-api.skill
Binary file not shown.
Binary file modified plugins/s1-secops-skills/dist/powerquery.skill
Binary file not shown.
Binary file not shown.
Binary file modified plugins/s1-secops-skills/dist/sdl-api.skill
Binary file not shown.
Binary file modified plugins/s1-secops-skills/dist/sdl-dashboard.skill
Binary file not shown.
Binary file modified plugins/s1-secops-skills/dist/sdl-log-parser.skill
Binary file not shown.
Binary file modified plugins/s1-secops-skills/dist/sdl-solutions.skill
Binary file not shown.
Binary file modified plugins/s1-secops-skills/dist/soc-investigator.skill
Binary file not shown.
14 changes: 14 additions & 0 deletions plugins/s1-secops-skills/skills/hyperautomation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ Just describe the workflow in plain language:

Claude will ask clarifying questions if needed, warn about any integrations that require pre-configuration, generate the workflow JSON, and optionally push it directly to your console.

### Autonomous SOC (auto-investigate and respond)

Describe an end-to-end auto-response flow and Claude builds the canonical alert to investigate to triage to decide to respond shape, with reusable response snippets and dynamic dispatch:

- "Build an autonomous SOC workflow that investigates and responds to alerts on its own"
- "Auto-triage every high/critical alert, add a verdict note, open a ticket, and remediate"
- "On a ransomware alert, isolate the device and notify the SOC; auto-close false positives"
- "Let an LLM pick isolate vs quarantine vs close-as-false-positive per alert"

Full pattern and the reusable-snippet library: [`references/autonomous-soc-template.md`](references/autonomous-soc-template.md).

## Layout

- `SKILL.md`: instructions Claude reads when the skill triggers
Expand All @@ -55,6 +66,9 @@ Claude will ask clarifying questions if needed, warn about any integrations that
- `references/functions-reference.md`: `{{Function.X()}}` syntax and PowerQuery patterns
- `references/validation-rules.md`: pre-output checklist
- `references/api-integration.md`: Hyperautomation API reference (import, activate, trigger, list)
- `references/snippets.md`: authoring and calling reusable snippets (`snippet_20` dispatch, lifecycle API)
- `references/autonomous-soc-template.md`: the autonomous SOC pattern (investigate to decide to respond), response-snippet library, and a branded SOC-email snippet
- `references/connections.md`: creating an integration connection via API and cloning it across sites

## Credit

Expand Down
16 changes: 15 additions & 1 deletion plugins/s1-secops-skills/skills/hyperautomation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ description: >
security task. Also triggers when the user asks to import, export, test, validate, or submit
a workflow to a SentinelOne console via API. Always use this skill for any task involving
SentinelOne workflow JSON — even if phrased casually (e.g., "build me a thing that disables
a user when an alert fires"). When in doubt about whether this skill applies, use it.
a user when an alert fires"). Also triggers for autonomous / auto-response SOC requests:
"autonomous SOC", "SOC in a box", "auto-triage", "investigate and respond automatically",
"auto-isolate on a critical alert", "auto-close false positives and escalate real threats",
or letting an LLM decide isolate vs quarantine vs close per alert (the canonical
investigate-decide-respond shape lives in references/autonomous-soc-template.md).
When in doubt about whether this skill applies, use it.
---

# SentinelOne Hyperautomation Skill
Expand Down Expand Up @@ -165,6 +170,9 @@ A workflow imported or created via the API is a **Private Draft owned by the tok
| `references/functions-reference.md` | Using `{{Function.X()}}` syntax or PowerQuery patterns |
| `references/validation-rules.md` | Before outputting any workflow — run the checklist |
| `references/api-integration.md` | User wants to import/export/submit to a live console |
| `references/snippets.md` | Building or calling a **snippet** (reusable sub-workflow): authoring rules, static vs dynamic `snippet_20` calls, and the snippet lifecycle API |
| `references/autonomous-soc-template.md` | Building an **autonomous SOC** / auto-response workflow: canonical alert→investigate→triage→decide→respond shape, the reusable response-snippet library, dynamic-snippet dispatch, and a branded SOC-email snippet example |
| `references/connections.md` | Creating an integration **connection** via API (endpoint + body), cloning a connection across sites, and the integration-vs-connection binding rule |

## Decision guide: pick the right pattern by use case

Expand All @@ -183,13 +191,19 @@ table to jump straight to the right starting point:
| "Summarize this evidence with an LLM" | B12 (OpenAI) | B6 add-note |
| "Create a Threat Intelligence indicator" | B9 (TI IOC create) | B4 accumulator inside loop |
| "Add a note on the alert" | B6 (UAM GraphQL addAlertNote) | always wrap text in `Function.HTML_ENCODE` |
| "Auto-investigate and respond to alerts end to end" | `references/autonomous-soc-template.md` | reusable response snippets + dynamic `snippet_20` dispatch, poll-until-complete loop |
| "Reuse this step across several flows" / "stop copy-pasting this logic" | `references/snippets.md` (author a snippet, call it with `snippet_20`) | `use_latest_snippet_version` to auto-track edits |

**Reuse via snippets is the default for shared logic.** Whenever the same action graph would appear in more than one workflow (a response action, a notification, a poll loop), build it once as a snippet and call it with a `snippet_20` node instead of duplicating it. See `references/snippets.md`.

When in doubt, the load-bearing 17 atoms are:
`http_request`, `variable`, `condition`, `loop`, `singularity_response_trigger`,
`data_formation`, `send_email`, `snippet`, `break_loop`, `manual_trigger`, `wait_for_slack`,
`delay`, `http_trigger`, `scheduled_trigger`, `create_interaction`, `wait_for_interaction`,
`email_trigger`. Anything outside this set is exotic; confirm it exists before generating.

> **Snippet node types.** *Calling* a snippet from a workflow uses a `snippet_20` node (not `snippet`). *Authoring* a snippet uses a `snippet_trigger` (inputs) + `snippet_output` (returns) in place of a normal trigger. See `references/snippets.md`.

## Example workflows (in references/examples/)

Annotated real examples to use as structural references:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Autonomous SOC workflow template

An autonomous SOC workflow takes an alert from detection to a decided, executed response with no
analyst in the loop.

## Example prompts that should trigger this template

Use this template when a user asks for end-to-end alert handling, auto-response, or a "SOC in a
box" — even when phrased casually. Representative prompts:

- "Build an autonomous SOC workflow that investigates and responds to alerts on its own."
- "When a critical alert fires, run Purple AI, decide the response, and act, no analyst needed."
- "Auto-triage every high/critical alert, add a verdict note, open a ticket, and remediate."
- "Set up automatic containment: on a ransomware alert, isolate the device and notify the SOC."
- "Create a flow that lets an LLM pick isolate vs quarantine vs close-as-false-positive per alert."
- "Wait for the agentic investigation to finish, summarise it, then take the right action."
- "Auto-close false positives and only escalate real threats to a human."
- "When an identity alert comes in, revoke the user's SaaS sessions and lock their machine."
- "Notify the SOC by email with the verdict and recommended actions whenever we auto-respond."
- "Stop copy-pasting the isolate/quarantine logic into every flow, make them reusable."

The last two map to the reusable-snippet best practice below (a notify-SOC snippet, and factoring
each response into its own snippet). Anything of the form "investigate → decide → respond
automatically" is this template.

## Best practice: factor reusable logic into snippets

**Any part of a flow that other flows also need should be a snippet, not inline actions.** Build the
logic once as a snippet (`references/snippets.md`), publish it, and have every workflow call it.
This removes duplicated logic across flows: when the logic changes you edit one snippet instead of
editing every workflow that copied it, and callers on `use_latest_snippet_version: true` pick the
change up automatically. Reusable pieces in a SOC context are almost always: the response actions
(isolate, quarantine, rollback, revoke, close-as-FP), the collaboration steps (open a channel,
create a ticket, escalate), and shared utilities (poll until an investigation completes). Reach for
a snippet whenever you notice the same action graph appearing in more than one workflow.

## Canonical shape (alert-triggered)

1. **Singularity Response Trigger** — fire on the target alert (filter by name / severity).
2. **Trigger Agentic Investigation** — `http_request` to Unified Alerts GraphQL, mutation
`alertTriggerActions` with action `S1/aiInvestigation/run`
(`payload.aiInvestigation{tenantId, consoleVersion:"HyperAutomation", userAgent:"SentinelOne-HyperAutomation"}`).
3. **Pause for Investigation Completion** — a **static** snippet call that polls until the
investigation finishes (poll-until-complete loop below). Shared by every alert workflow.
4. **Get Investigation Summary** — `http_request` GraphQL `aiInvestigations { status verdict result … }`
once complete.
5. *(optional)* **IOC enrichment** — extract hashes into a variable, branch SHA1 / SHA256 / MD5
checks, enrich with an LLM or a threat-intel action.
6. **Triage & Summary** — an `llm` action turns the investigation + alert into a verdict/summary.
7. **Summary markdown** — an `llm` action formats a human-readable summary.
8. **Add Note / Set Status** — `http_request` GraphQL `addAlertNote` + `analystVerdictUpdate` /
`statusUpdate` (wrap note text in `Function.HTML_ENCODE`).
9. *(optional)* **Ticket + collaboration** — create a ticket, open a Slack channel (snippet), invite,
notify.
10. **Response Decision** — an `llm` action returns ONE response action, expressed as the **name of a
response snippet**.
11. **Response** — a **dynamic** `snippet_20` (`is_dynamic: true`,
`dynamic_snippet_name: "{{response-decision.data}}"`) that dispatches to the chosen response
snippet, passing the shared input contract.
12. **Wrap-up** — `llm` action summary → update alert notes → update the collaboration channel.

## The reusable snippet library

Each response and each shared step is its own snippet, so any workflow can call it:

- **Utility:** Pause for Investigation Completion (poll loop), Create Slack Channel, Create incident.
- **Containment:** Isolate Device / Isolate from Network, Quarantine Alert, Lock User's Machine.
- **Identity:** Revoke SaaS Session.
- **Recovery:** Rollback.
- **Triage:** Close as False Positive.
- **Escalation:** Escalate to Human.

### Shared input contract (this is what enables dynamic dispatch)

Give every response snippet the SAME `snippet_trigger` inputs so ONE dynamic `snippet_20` can route
to any of them with one `inputs` map. A workable contract:

`Singularity-ID`, `Response-Input`, `Device-UUID`, `Group-ID`, `Channel-ID`, `Alert-Input`.

The Response Decision LLM returns the name of one snippet in the library; the dynamic node runs it
with these inputs. Adding a new response is then just: author a new snippet against the same
contract and teach the LLM its name — no change to the parent workflow.

## Poll-until-complete loop (used by the Pause snippet)

A while-loop that polls a status and breaks when done:

- **loop node:** `data { action_type:"loop", loop_type:"while", number_of_iterations:"1",
object_to_iterate:null, is_parallel:false }`. For a while-loop `number_of_iterations` is ignored;
it loops until a `break_loop` fires.
- The loop node's `connected_to` carries BOTH `{target:<first inner node>, custom_handle:"inner"}`
AND `{target:<node after the loop>, custom_handle:null}` (the post-loop continuation). Omit the
default edge and nothing runs after the loop.
- **Inner chain** (each node `parent_action` = the loop's `export_id`):
`[delay] → Get Status (http) → condition(status == "COMPLETED") ─true→ break_loop`. The
condition's false path has no edge, so the loop iterates again.
- Read the full result AFTER the loop (a fresh Get) — a node placed after the loop can read the
loop's own outputs but not a loop-internal node's output.

## Build order

1. Create + publish + activate each reusable snippet (import each with `snippet_trigger` +
the shared-contract inputs + `snippet_output`).
2. Build the parent workflow: **static**-call the utility snippets (Pause, Create Channel) and
**dynamic**-call the response library through one `snippet_20` driven by the Response Decision LLM.
3. Publish/activate the parent and confirm bindings resolve
(`GET /workflow-actions/snippets-versions/{id}/{versionId}?query={}`).

## Example: SOC notification snippet (branded HTML email)

A reusable "notify the SOC" snippet: `snippet_trigger` (inputs `Verdict`, `Severity`,
`Recommendation`, `Alert-ID`) → `send_email` → `snippet_output`. The email body is a self-contained,
inline-styled HTML table (email clients need inline styles; no `<style>` blocks, no external CSS)
that reads its values from the snippet inputs. `send_email` is a `core_action` (no connection
needed). Keep the output non-empty (a literal such as `"notified"`).

```html
<div style="margin:0;padding:20px 12px;background:#eef0f4;font-family:Segoe UI,Roboto,Arial,sans-serif;">
<table role="presentation" width="640" align="center" cellpadding="0" cellspacing="0" style="margin:0 auto;max-width:640px;background:#ffffff;border-radius:12px;overflow:hidden;">
<tr><td style="background:#2c1a5e;padding:20px 28px;color:#ffffff;font-size:17px;font-weight:700;">SentinelOne <span style="color:#b79cff;">Purple AI</span>
<span style="float:right;color:#c9bdf0;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:1px;">Automated SOC Response</span></td></tr>
<tr><td style="padding:26px 28px 6px;">
<span style="display:inline-block;background:#fde8e6;color:#b31d13;font-size:11px;font-weight:700;padding:6px 13px;border-radius:20px;text-transform:uppercase;">Verdict: {{Function.HTML_ENCODE(snippet-input.Verdict)}}</span>
<h1 style="margin:14px 0 4px;font-size:21px;color:#181430;">Alert triaged</h1></td></tr>
<tr><td style="padding:14px 28px 26px;">
<table role="presentation" width="100%" style="border-collapse:collapse;font-size:13px;">
<tr><td style="padding:10px 0;color:#8a8a96;width:150px;border-bottom:1px solid #eeeef3;">Severity</td><td style="padding:10px 0;color:#1c1830;font-weight:600;border-bottom:1px solid #eeeef3;">{{Function.HTML_ENCODE(snippet-input.Severity)}}</td></tr>
<tr><td style="padding:10px 0;color:#8a8a96;">Alert ID</td><td style="padding:10px 0;color:#4a4a58;font-family:monospace;font-size:11px;">{{snippet-input.Alert-ID}}</td></tr>
</table>
<div style="margin-top:14px;background:#f5f2fd;border-left:4px solid #6b3fd4;padding:16px 18px;">
<div style="font-size:11px;font-weight:700;color:#4a1fb8;text-transform:uppercase;letter-spacing:0.7px;margin-bottom:8px;">Recommended actions</div>
<div style="font-size:13px;color:#2a2636;line-height:1.65;">{{snippet-input.Recommendation}}</div>
</div></td></tr>
</table></div>
```

Notes: wrap free-text inputs shown as text (verdict, severity) in `Function.HTML_ENCODE`; leave a
pre-formatted recommendation raw so its line breaks render. Per the global style, avoid em-dashes in
copy. This snippet is then called from any workflow with a `snippet_20` node (static or dynamic).
Original file line number Diff line number Diff line change
Expand Up @@ -607,26 +607,36 @@ live import API on 2026-06-11:

---

## A15. Snippet
## A15. Snippet (call a reusable sub-workflow)

**Purpose**: invoke a saved sub-workflow as a single action. Drives composability, e.g. one
"Variables From Column GSheet" snippet is reused across 75+ workflows.
**Purpose**: invoke a reusable sub-workflow as a single action, so shared logic lives in one place
instead of being duplicated across flows. Full authoring + calling + lifecycle: `references/snippets.md`.

**Real shape**:
**Calling node is `snippet_20`** (not `snippet`). Static call, pinned to a version:
```json
{
"type": "snippet",
"tag": "core_action",
"data": { "name": "Variables From Column GSheet", "action_type": "snippet" },
"snippet_workflow_id": null,
"snippet_version_id": null
"type": "snippet_20", "tag": "core_action",
"snippet_workflow_id": "<snippet id>", "snippet_version_id": "<version>",
"data": {
"name": "Isolate Device", "action_type": "snippet_20",
"inputs": "{\"Singularity-ID\":\"{{singularity-response-trigger.data.id}}\"}",
"use_latest_snippet_version": false, "is_dynamic": false,
"dynamic_snippet_name": "Isolate Device"
}
}
```

**Important caveat from the corpus**: when a workflow is exported, **`snippet_workflow_id` and
`snippet_version_id` come back null** (0 of 194 snippets in the corpus carried IDs). On import,
the user re-binds the snippet by name in the console UI. So when you generate a workflow that
uses a snippet, make the `name` exactly match the snippet's name on the target tenant.
- `data.inputs` is a JSON **string** mapping the snippet's input params to parent references.
- `use_latest_snippet_version: true` auto-tracks the snippet's newest published version.
- **Dynamic dispatch:** set `is_dynamic: true` and `dynamic_snippet_name` to a mustache expression
(e.g. `"{{response-decision.data}}"`) to route to whichever snippet that name resolves to at
runtime. All candidate snippets must share one input contract. See `references/snippets.md` and
the autonomous-SOC pattern in `references/autonomous-soc-template.md`.
- Bindings survive export as `snippet_workflow_id` / `snippet_version_id`; resolve them with
`GET /workflow-actions/snippets-versions/{workflowId}/{versionId}?query={}`.

**Authoring a snippet** uses `snippet_trigger` (inputs) + `snippet_output` (returns) — see
`references/snippets.md` for the node shapes and the import rules.

---

Expand Down Expand Up @@ -1094,11 +1104,15 @@ single-tenant flows, broken on transfer. Prefer:
array**, not a raw array. Verified: all 1 in-operator usage in the corpus uses
`"[\"HIGH\",\"CRITICAL\"]"` style. Don't write `["HIGH","CRITICAL"]` directly.

## E5. Snippet IDs missing on import
## E5. Snippet authoring/calling traps (import 422 with no field named)

When you import a workflow that uses snippets, the `snippet_workflow_id` and
`snippet_version_id` are null. The user must re-bind in the console. **Match the snippet's
`name` to the snippet's name on the target tenant** so re-binding is one click.
- Call a snippet with a **`snippet_20`** node bound via `snippet_workflow_id` + `snippet_version_id`
(these persist through export). Do NOT use type `"snippet"` and do NOT embed the snippet's inner
actions into the parent — embedding orphans a node.
- Authoring a snippet: `export_id 0` must be a real body action, not the `snippet_trigger`;
`snippet_output` values must be non-empty (a bare `{{slug.}}` imports but is invalid at runtime,
use a literal or a real `{{slug.field}}`); the workflow name must not contain parentheses `()`.
Full detail: `references/snippets.md`.

## E6. Forgetting `parent_action` on inner loop steps

Expand Down
Loading
Loading