@query-ai/digital-workers 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +27 -0
- package/.claude-plugin/plugin.json +11 -0
- package/README.md +430 -0
- package/hooks/hooks.json +16 -0
- package/hooks/run-hook.cmd +4 -0
- package/hooks/session-start +32 -0
- package/package.json +16 -0
- package/skills/alert-classifier/SKILL.md +111 -0
- package/skills/alert-investigation/SKILL.md +838 -0
- package/skills/detection-engineer/SKILL.md +170 -0
- package/skills/evidence-quality-checker/SKILL.md +109 -0
- package/skills/fsql-expert/SKILL.md +308 -0
- package/skills/fsql-expert/fsql-reference.md +525 -0
- package/skills/hunt-pattern-analyzer/SKILL.md +150 -0
- package/skills/hunt-quality-checker/SKILL.md +105 -0
- package/skills/hypothesis-builder/SKILL.md +303 -0
- package/skills/identity-investigator/SKILL.md +172 -0
- package/skills/itdr/SKILL.md +1178 -0
- package/skills/network-investigator/SKILL.md +196 -0
- package/skills/report-writer/SKILL.md +158 -0
- package/skills/senior-analyst-review/SKILL.md +199 -0
- package/skills/severity-scorer/SKILL.md +131 -0
- package/skills/templates/org-policy-template.md +516 -0
- package/skills/templates/runbook-template.md +300 -0
- package/skills/threat-hunt/SKILL.md +628 -0
- package/skills/threat-intel-enricher/SKILL.md +127 -0
- package/skills/using-digital-workers/SKILL.md +76 -0
|
@@ -0,0 +1,1178 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: itdr
|
|
3
|
+
description: Use when running an identity threat assessment, checking for identity-based attacks across IdPs, or proactively evaluating identity security posture
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Identity Threat Detection & Response
|
|
7
|
+
|
|
8
|
+
## ABSOLUTE RULES — Read These First
|
|
9
|
+
|
|
10
|
+
**1. NEVER use Bash, cat, python, jq, or any shell command to process data.** Not for MCP results. Not for saved files. NEVER. If results overflow to a file, the query was too broad — re-query with specific field selectors.
|
|
11
|
+
|
|
12
|
+
**2. NEVER use `**` on broad queries.** Use specific field selectors. Use `**` only when scoped to a single host AND single event type AND narrow time window.
|
|
13
|
+
|
|
14
|
+
**3. ALWAYS validate before execute.** Every FSQL query goes through `Validate_FSQL_Query` before `Execute_FSQL_Query`. No exceptions.
|
|
15
|
+
|
|
16
|
+
**4. ALWAYS save artifacts.** Write evidence files at every phase exit using the Write tool.
|
|
17
|
+
|
|
18
|
+
**5. LOG EVERY QUERY.** Every `Execute_FSQL_Query` call — success, failure, or empty — MUST be appended to `queries.md` with query text, result count, and 1-line summary.
|
|
19
|
+
|
|
20
|
+
**6. Specialist invocation is a MANDATORY step, not a judgment call.** After classifying any finding as SUSPICIOUS or ACTIVE THREAT, execute these steps in order:
|
|
21
|
+
- a. `threat-intel-enricher`: Pass every external IP from the finding. Instruction: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
22
|
+
- b. `identity-investigator`: Pass every compromised or suspicious account. Instruction: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
23
|
+
- c. `network-investigator`: If cross-domain pivot reveals `network_activity`, `http_activity`, or `dns_activity` hits, pass the relevant IPs/domains. Instruction: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
24
|
+
- d. If steps a-c are independent, invoke them **in parallel**.
|
|
25
|
+
|
|
26
|
+
**Phase 2 exit check:** Before exiting Phase 2, verify: "Did I invoke threat-intel-enricher on every IOC IP? Did I invoke identity-investigator on every compromised account? If any were skipped, invoke them now before proceeding to Phase 3."
|
|
27
|
+
|
|
28
|
+
**7. Call MCP query tools directly.** The ITDR orchestrator validates and executes queries itself using `Validate_FSQL_Query` and `Execute_FSQL_Query` MCP tools. Do NOT dispatch to `digital-workers:fsql-expert` as a sub-skill — the sub-skill handoff causes orchestration stalls. Only invoke `fsql-expert` if you encounter a schema error you cannot resolve with `Search_FSQL_SCHEMA` directly.
|
|
29
|
+
|
|
30
|
+
**8. Volume-aware query strategy.** After Phase 1 discovery, classify each identity connector by volume:
|
|
31
|
+
- **HIGH VOLUME:** `message, time` overflowed at 7d → see time window guidance below
|
|
32
|
+
- **MEDIUM VOLUME:** `message, time` returned inline at 7d, >100 records → standard 7d queries OK
|
|
33
|
+
- **LOW VOLUME:** `message, time` returned <100 records at 7d → can use broader selectors
|
|
34
|
+
|
|
35
|
+
**Time window guidance for HIGH VOLUME connectors:**
|
|
36
|
+
- **Filtered queries** (`status_id = FAILURE`, per-user, per-IP): start at **2h**. Filters remove 80-95% of records. Only narrow below 2h if 2h overflows.
|
|
37
|
+
- **Unfiltered queries** (all auth, all successes): start at **15-30min**. These hit full connector volume.
|
|
38
|
+
- After any overflow, halve the time window for the next query on that connector.
|
|
39
|
+
|
|
40
|
+
Never fire 3+ queries in the same parallel batch against a HIGH VOLUME connector. When a connector overflows on discovery, switch to per-IP or per-user queries instead of broader time windows. Record volume classification in `identity-data-map.md`.
|
|
41
|
+
|
|
42
|
+
**9. ALWAYS resolve IP field paths in Phase 1.** On dynamic connectors, the IP field may be `src_endpoint.ip`, `device.ip`, or another path. During Phase 1 schema verification, query BOTH paths and record which one is populated. If `src_endpoint.ip` is empty, check `device.ip`. If both are empty, document as a data gap. Record the verified IP path in `identity-data-map.md` for each connector and use ONLY the verified path in all subsequent queries.
|
|
43
|
+
|
|
44
|
+
These rules are non-negotiable. If you find yourself reaching for cat, python, or `**` on a broad query, STOP and re-read this section.
|
|
45
|
+
|
|
46
|
+
## Iron Law
|
|
47
|
+
|
|
48
|
+
**EVERY PATTERN RUNS. EVERY GAP IS DOCUMENTED. DATA GAPS ARE FIRST-CLASS FINDINGS.**
|
|
49
|
+
|
|
50
|
+
An ITDR assessment that skips patterns or hides gaps gives false confidence. Gaps tell the customer what to fix. They are as valuable as threat findings.
|
|
51
|
+
|
|
52
|
+
## Overview
|
|
53
|
+
|
|
54
|
+
You are the orchestrator for the Digital Workers Identity Threat Detection & Response workflow. You sweep 8 identity attack patterns across all identity providers in the mesh, correlate findings cross-source, and produce an investigation-grade report that no single-vendor ITDR tool can match.
|
|
55
|
+
|
|
56
|
+
This skill is analyst-initiated. The analyst asks for an identity threat assessment, and you run the full sweep autonomously.
|
|
57
|
+
|
|
58
|
+
**What makes this different from `threat-hunt`:** Threat hunts test one hypothesis. This tests 8 identity-specific patterns in a single sweep, correlates across them, and produces a unified scorecard. It is a comprehensive identity threat assessment, not a single-hypothesis hunt.
|
|
59
|
+
|
|
60
|
+
**What makes this different from `identity-investigator`:** The identity-investigator deep-dives a single account. This skill discovers which accounts need investigation by sweeping patterns across the entire identity surface, then invokes identity-investigator for the deep dives.
|
|
61
|
+
|
|
62
|
+
**Sub-skills invoked:**
|
|
63
|
+
- `Validate_FSQL_Query` + `Execute_FSQL_Query` MCP tools — called directly by the orchestrator (do NOT dispatch to fsql-expert sub-skill)
|
|
64
|
+
- `Search_FSQL_SCHEMA` MCP tool — for schema lookups on unfamiliar event types
|
|
65
|
+
- `digital-workers:identity-investigator` — deep dives on suspicious accounts (pass instruction: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input.")
|
|
66
|
+
- `digital-workers:threat-intel-enricher` — IOC reputation on suspicious IPs (pass instruction: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input.")
|
|
67
|
+
- `digital-workers:network-investigator` — network/proxy/DNS analysis on cross-domain pivot hits (pass instruction: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input.")
|
|
68
|
+
- `digital-workers:alert-investigation` — escalation target if active threat confirmed
|
|
69
|
+
- `digital-workers:senior-analyst-review` — quality review on Active Threat or HIGH findings
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Phase 1: Discovery & Data Mapping
|
|
74
|
+
|
|
75
|
+
**Goal:** Discover all identity connectors in the mesh, verify schema coverage, and build the Identity Data Map that determines which patterns are viable.
|
|
76
|
+
|
|
77
|
+
**Time budget:** ~5 minutes
|
|
78
|
+
|
|
79
|
+
### Process
|
|
80
|
+
|
|
81
|
+
**Step 1: Create the investigation directory**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
mkdir -p docs/investigations/YYYY-MM-DD-itdr-assessment/
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use today's date. This directory holds all artifacts for the assessment.
|
|
88
|
+
|
|
89
|
+
**Step 2: Discover connectors**
|
|
90
|
+
|
|
91
|
+
Call `FSQL_Connectors` to get the full connector landscape. Identify every connector that produces identity-relevant event types:
|
|
92
|
+
- `authentication` — login events, MFA challenges, SSO
|
|
93
|
+
- `user_inventory` — user account metadata, last sign-in, account status
|
|
94
|
+
- `entity_management` / `account_change` — privilege changes, role grants, account modifications
|
|
95
|
+
- `api_activity` — admin API calls, service account usage
|
|
96
|
+
|
|
97
|
+
Classify each connector as:
|
|
98
|
+
- **Static schema** — fixed API contract, predictable OCSF mapping (e.g., CrowdStrike, SentinelOne)
|
|
99
|
+
- **Dynamic schema** — customer-defined OCSF mappings, requires verification via `Search_FSQL_SCHEMA`
|
|
100
|
+
|
|
101
|
+
**Step 3: Verify schema for identity connectors**
|
|
102
|
+
|
|
103
|
+
For each identity connector, run `Search_FSQL_SCHEMA` to verify field population. Check these specific fields:
|
|
104
|
+
- `authentication.status_id`
|
|
105
|
+
- `authentication.src_endpoint.ip`
|
|
106
|
+
- `authentication.http_request.user_agent`
|
|
107
|
+
- `authentication.user.username` / `authentication.user.email_addr`
|
|
108
|
+
- `authentication.actor.user.email_addr` (dynamic connectors often use this instead of `user.email_addr`)
|
|
109
|
+
- `authentication.time`
|
|
110
|
+
- MFA-related fields (varies by connector)
|
|
111
|
+
- Geo-location fields (varies by connector)
|
|
112
|
+
|
|
113
|
+
**IP field resolution (Absolute Rule 9 — CRITICAL):**
|
|
114
|
+
|
|
115
|
+
For each authentication connector with data, you MUST determine which IP field path is populated. Dynamic connectors vary — do NOT assume `src_endpoint.ip` works.
|
|
116
|
+
|
|
117
|
+
**Skip connectors marked `no_data` in the environment profile** (HIGH confidence). If the profile says a connector has no auth data, don't waste queries probing it for field paths. Only probe connectors that either have confirmed data or are not yet profiled.
|
|
118
|
+
|
|
119
|
+
Run a small test query (e.g., 10min window) with BOTH IP field paths:
|
|
120
|
+
```
|
|
121
|
+
QUERY authentication.src_endpoint.ip, authentication.device.ip,
|
|
122
|
+
authentication.actor.user.email_addr, authentication.time
|
|
123
|
+
FROM '<connector_name>' WITH authentication.status_id = FAILURE AFTER 10min
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Examine the results:
|
|
127
|
+
- If `src_endpoint.ip` has values → use `src_endpoint.ip` for this connector
|
|
128
|
+
- If `src_endpoint.ip` is null/empty but `device.ip` has values → use `device.ip` for this connector
|
|
129
|
+
- If both are empty → document as DATA GAP ("no source IP available")
|
|
130
|
+
- If both have values → prefer `src_endpoint.ip` (standard OCSF path)
|
|
131
|
+
|
|
132
|
+
Record the result in `identity-data-map.md`:
|
|
133
|
+
```
|
|
134
|
+
IP field path: device.ip (src_endpoint.ip is empty on this connector)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**All subsequent queries for this connector MUST use the verified IP path.** Do NOT use `src_endpoint.ip` if you determined it's empty — use the path that actually has data.
|
|
138
|
+
|
|
139
|
+
Similarly, resolve the user identity field:
|
|
140
|
+
- Check both `authentication.user.email_addr` and `authentication.actor.user.email_addr`
|
|
141
|
+
- Record which one is populated in the data map
|
|
142
|
+
|
|
143
|
+
**Step 4: Build the Identity Data Map**
|
|
144
|
+
|
|
145
|
+
Determine which of the 8 patterns are viable based on available fields and connectors.
|
|
146
|
+
|
|
147
|
+
Write `identity-data-map.md` using the template below.
|
|
148
|
+
|
|
149
|
+
**Step 5: Read environment profile**
|
|
150
|
+
|
|
151
|
+
Read `digital-workers/learned/environment-profile.json` if it exists. Cross-reference known gaps, known false positives, and any identity-specific notes from prior assessments.
|
|
152
|
+
|
|
153
|
+
### Identity Data Map Template
|
|
154
|
+
|
|
155
|
+
The data map has two sections: the **QUERY CONFIG** (used by Phase 2 for every query) and the **connector details** (reference). The QUERY CONFIG is the most important output of Phase 1 — it determines whether Phase 2 queries return data or return empty.
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
IDENTITY DATA MAP
|
|
159
|
+
━━━━━━━━━━━━━━━━━
|
|
160
|
+
Assessment Date: YYYY-MM-DD
|
|
161
|
+
|
|
162
|
+
QUERY CONFIG — Phase 2 MUST read this before writing any query
|
|
163
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
164
|
+
Copy these exact field paths into all Pattern queries for each connector.
|
|
165
|
+
|
|
166
|
+
[Connector Name]:
|
|
167
|
+
user_field: [authentication.actor.user.email_addr OR authentication.user.email_addr]
|
|
168
|
+
ip_field: [authentication.device.ip OR authentication.src_endpoint.ip OR "NONE — no IP data"]
|
|
169
|
+
ip_note: [e.g., "public IPs" or "internal proxy IPs only — no geo value" or "empty"]
|
|
170
|
+
volume: [HIGH / MEDIUM / LOW]
|
|
171
|
+
time_guidance: [e.g., "failures at 2h, successes at 15min"]
|
|
172
|
+
|
|
173
|
+
[Repeat for each identity connector with auth data]
|
|
174
|
+
|
|
175
|
+
IDENTITY CONNECTORS:
|
|
176
|
+
[Connector Name] ([static/dynamic])
|
|
177
|
+
Event types: [authentication, user_inventory, etc.]
|
|
178
|
+
Key fields verified: [list confirmed fields]
|
|
179
|
+
MFA fields: [available/not available — list if available]
|
|
180
|
+
Geo fields: [available/not available — list if available]
|
|
181
|
+
Notes: [any caveats]
|
|
182
|
+
|
|
183
|
+
PATTERN VIABILITY:
|
|
184
|
+
Pattern 1 (Password Spraying): [VIABLE / PARTIAL / NOT VIABLE] — [reason]
|
|
185
|
+
Pattern 2 (Credential Stuffing): [VIABLE / PARTIAL / NOT VIABLE] — [reason]
|
|
186
|
+
Pattern 3 (Impossible Travel): [VIABLE / PARTIAL / NOT VIABLE] — [reason]
|
|
187
|
+
Pattern 4 (MFA Fatigue): [VIABLE / PARTIAL / NOT VIABLE] — [reason]
|
|
188
|
+
Pattern 5 (Dormant Accounts): [VIABLE / PARTIAL / NOT VIABLE] — [reason]
|
|
189
|
+
Pattern 6 (Cross-IdP Anomaly): [VIABLE / PARTIAL / NOT VIABLE] — [reason]
|
|
190
|
+
Pattern 7 (Privilege Escalation): [VIABLE / PARTIAL / NOT VIABLE] — [reason]
|
|
191
|
+
Pattern 8 (Service Account Abuse):[VIABLE / PARTIAL / NOT VIABLE] — [reason]
|
|
192
|
+
|
|
193
|
+
DATA GAPS IDENTIFIED:
|
|
194
|
+
1. [description] -> Impact: [which patterns blocked/degraded]
|
|
195
|
+
2. ...
|
|
196
|
+
|
|
197
|
+
NON-IDENTITY CONNECTORS (for cross-domain pivots):
|
|
198
|
+
[Connector Name] — [event_type]
|
|
199
|
+
Pivot keys available: [user, IP, device, domain — based on objects list]
|
|
200
|
+
Volume classification: [HIGH / MEDIUM / LOW / UNKNOWN]
|
|
201
|
+
Notes: [any caveats]
|
|
202
|
+
|
|
203
|
+
PIVOT VIABILITY:
|
|
204
|
+
Proxy/Web pivot: [VIABLE / NOT AVAILABLE] — [connector name or "no http_activity connectors"]
|
|
205
|
+
Network pivot: [VIABLE / NOT AVAILABLE] — [connector name or "no network_activity connectors"]
|
|
206
|
+
Process/EDR pivot: [VIABLE / NOT AVAILABLE] — [connector name or "no process_activity connectors"]
|
|
207
|
+
DNS pivot: [VIABLE / NOT AVAILABLE] — [connector name or "no dns_activity connectors"]
|
|
208
|
+
Email pivot: [VIABLE / NOT AVAILABLE] — [connector name or "no email_activity connectors"]
|
|
209
|
+
DLP pivot: [VIABLE / NOT AVAILABLE] — [connector name or "no data_security_finding connectors"]
|
|
210
|
+
Cloud API pivot: [VIABLE / NOT AVAILABLE] — [connector name or "no api_activity connectors"]
|
|
211
|
+
Detection pivot: [VIABLE / NOT AVAILABLE] — [connector name or "no detection_finding connectors"]
|
|
212
|
+
|
|
213
|
+
ENVIRONMENT PROFILE NOTES:
|
|
214
|
+
[anything relevant from environment-profile.json, or "No profile found"]
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Hard Gate — Phase 1 Exit
|
|
218
|
+
|
|
219
|
+
Before proceeding to Phase 2, verify:
|
|
220
|
+
- `identity-data-map.md` has been written to the investigation directory
|
|
221
|
+
- At least one identity connector discovered
|
|
222
|
+
- Pattern viability determined for all 8 patterns
|
|
223
|
+
|
|
224
|
+
If `identity-data-map.md` does not exist, STOP. Write it now. Do NOT proceed to Phase 2 without it.
|
|
225
|
+
|
|
226
|
+
**Failure mode — no identity connectors:** If no connectors produce identity-relevant event types (`authentication`, `user_inventory`, `entity_management`, `api_activity`), do NOT proceed. Present a clear message to the analyst:
|
|
227
|
+
> "No identity connectors found in the mesh. An ITDR assessment requires at least one connector producing `authentication` events. Recommend onboarding an identity provider (Entra ID, Okta, Google Workspace, etc.) to enable identity threat detection."
|
|
228
|
+
|
|
229
|
+
Continue immediately to Phase 2 — do not stop or wait for user input.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Phase 2: Pattern Sweep
|
|
234
|
+
|
|
235
|
+
**Goal:** Execute all 8 identity attack patterns against the mesh, classify findings, and invoke specialists for deep dives.
|
|
236
|
+
|
|
237
|
+
**Time budget:** ~25-30 minutes
|
|
238
|
+
|
|
239
|
+
**Step 0 — Read the QUERY CONFIG (MANDATORY before any pattern query):**
|
|
240
|
+
|
|
241
|
+
Read `identity-data-map.md` and extract the QUERY CONFIG block. This block contains the verified field paths and volume classifications for each connector. **Use these exact paths in all queries.** Do not substitute `src_endpoint.ip` if the config says `device.ip`. Do not use `user.email_addr` if the config says `actor.user.email_addr`.
|
|
242
|
+
|
|
243
|
+
If the QUERY CONFIG block does not exist in `identity-data-map.md`, STOP. Go back to Phase 1 and complete the IP field resolution (Absolute Rule 9).
|
|
244
|
+
|
|
245
|
+
Call `Validate_FSQL_Query` and `Execute_FSQL_Query` MCP tools directly (Absolute Rule 7). The FSQL examples in pattern definitions use placeholder paths like `<verified_ip_field>` — replace these with the actual paths from the QUERY CONFIG.
|
|
246
|
+
|
|
247
|
+
### Execution Order
|
|
248
|
+
|
|
249
|
+
Run patterns in this order:
|
|
250
|
+
|
|
251
|
+
1. Password Spraying (T1110.003)
|
|
252
|
+
2. Credential Stuffing (T1110.004) — reuses Pattern 1 failure data
|
|
253
|
+
3. Impossible Travel (T1078)
|
|
254
|
+
4. MFA Fatigue (T1621)
|
|
255
|
+
5. Dormant Account Activation (T1078)
|
|
256
|
+
6. Cross-IdP Anomaly (T1078) — reuses Pattern 3 success data
|
|
257
|
+
7. Privilege Escalation (T1098)
|
|
258
|
+
8. Service Account Abuse (T1078.001)
|
|
259
|
+
|
|
260
|
+
### Per-Pattern Execution Flow
|
|
261
|
+
|
|
262
|
+
For each pattern:
|
|
263
|
+
|
|
264
|
+
1. **Layer 1a discovery** — lightweight volume gauge (`*.message, *.time` with relevant filters)
|
|
265
|
+
2. **Layer 1b targeted query** — specific field selectors on event types identified in Layer 1a
|
|
266
|
+
3. **Layer 1c aggregation (when valuable)** — use SUMMARIZE for threshold analysis instead of manually counting Layer 1b results:
|
|
267
|
+
```
|
|
268
|
+
-- Pattern 1 (Password Spray): Distinct usernames per source IP
|
|
269
|
+
SUMMARIZE COUNT DISTINCT authentication.actor.user.email_addr
|
|
270
|
+
GROUP BY authentication.src_endpoint.ip
|
|
271
|
+
WITH authentication.status_id = FAILURE AFTER 24h
|
|
272
|
+
|
|
273
|
+
-- Pattern 3 (Impossible Travel): Distinct IPs per user
|
|
274
|
+
SUMMARIZE COUNT DISTINCT authentication.device.ip
|
|
275
|
+
GROUP BY authentication.actor.user.email_addr
|
|
276
|
+
WITH authentication.status_id = SUCCESS AFTER 24h
|
|
277
|
+
|
|
278
|
+
-- Pattern 4 (MFA Fatigue): Auth attempts per user
|
|
279
|
+
SUMMARIZE COUNT authentication.user.uid
|
|
280
|
+
GROUP BY authentication.actor.user.email_addr
|
|
281
|
+
WITH authentication.message ICONTAINS 'MFA' AND authentication.status_id = FAILURE AFTER 24h
|
|
282
|
+
|
|
283
|
+
-- Pattern 8 (Service Account Anomaly): Distinct source IPs per service account
|
|
284
|
+
SUMMARIZE COUNT DISTINCT authentication.src_endpoint.ip
|
|
285
|
+
GROUP BY authentication.actor.user.email_addr
|
|
286
|
+
WITH authentication.actor.user.email_addr ICONTAINS 'svc' AFTER 7d
|
|
287
|
+
```
|
|
288
|
+
Use SUMMARIZE to identify which entities exceed pattern thresholds, then follow up with QUERY on flagged entities for detailed timeline analysis. Use the IP and user field paths verified in Phase 1 (Absolute Rule 9) — the examples above use common paths but your environment may differ.
|
|
289
|
+
|
|
290
|
+
> **Constraints:** SUMMARIZE has known execution limits — `status_id` filtering fails on detection_finding (use GROUP BY instead), `FROM` not supported, high-cardinality GROUP BY can overflow. If SUMMARIZE returns empty, fall back to QUERY. See fsql-expert Layer 1c for workarounds and check `summarize_support` in the environment profile.
|
|
291
|
+
|
|
292
|
+
4. **Classify** — apply thresholds to determine: CLEAN, SUSPICIOUS, or ACTIVE THREAT
|
|
293
|
+
5. **If SUSPICIOUS or ACTIVE THREAT — cross-domain pivot and specialist invocation:**
|
|
294
|
+
- a. **Cross-domain pivot** — run Layer 1a discovery scans (`*.message, *.time`) against non-identity event types using the compromised user or attacker IP as the pivot key (see Cross-Domain Pivot Table below)
|
|
295
|
+
- b. **Follow up on pivot hits** — if the pivot returns records for non-identity event types, run targeted Layer 1b queries with specific field selectors. Use `Search_FSQL_SCHEMA` if querying an unfamiliar event type.
|
|
296
|
+
- c. **Invoke threat-intel-enricher** on all IOC IPs (Absolute Rule 6a)
|
|
297
|
+
- d. **Invoke identity-investigator** on all compromised accounts (Absolute Rule 6b)
|
|
298
|
+
- e. **Invoke network-investigator** if pivot reveals `network_activity`, `http_activity`, or `dns_activity` hits (Absolute Rule 6c)
|
|
299
|
+
- f. Invoke independent specialists **in parallel**
|
|
300
|
+
6. **Log all queries** — append to `queries.md` immediately after every query execution, including pivot queries under a "Cross-Domain Pivot" subheading
|
|
301
|
+
|
|
302
|
+
### Query Sharing Rules
|
|
303
|
+
|
|
304
|
+
- **Patterns 1 & 2 share failure data:** Pattern 1 queries authentication failures. Pattern 2 reuses the same failure data for its analysis — do NOT re-query.
|
|
305
|
+
- **Patterns 3 & 6 share success data:** Pattern 3 queries successful authentications with IP and geo data. Pattern 6 reuses this data for cross-IdP correlation — do NOT re-query.
|
|
306
|
+
|
|
307
|
+
### Live Progress Reporting
|
|
308
|
+
|
|
309
|
+
After EVERY query, report status to the analyst:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
━━━ ITDR STATUS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
313
|
+
Phase 2: Pattern Sweep | Pattern 3/8 (Impossible Travel) | Query 12
|
|
314
|
+
Patterns complete: P1 CLEAN | P2 CLEAN | P3 running...
|
|
315
|
+
Findings so far: 0 suspicious, 0 active threats
|
|
316
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Escalation and circuit breaker rules apply during this phase.** See the Escalation Rules section below for Active Threat hard gate, senior analyst review triggers, and circuit breaker checkpoints.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Pattern Definitions
|
|
324
|
+
|
|
325
|
+
### Pattern 1: Password Spraying (T1110.003)
|
|
326
|
+
|
|
327
|
+
**What it detects:** An attacker trying a small number of common passwords against many accounts — the inverse of brute force. Designed to stay below per-account lockout thresholds.
|
|
328
|
+
|
|
329
|
+
**MITRE technique:** T1110.003 — Brute Force: Password Spraying
|
|
330
|
+
|
|
331
|
+
**Query approach:**
|
|
332
|
+
|
|
333
|
+
Layer 1a — gauge authentication failure volume:
|
|
334
|
+
```
|
|
335
|
+
-- Discovery: how many auth failures exist in the window?
|
|
336
|
+
QUERY authentication.message, authentication.time
|
|
337
|
+
WITH authentication.status_id = FAILURE AFTER 7d
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
If failure volume exceeds ~1000 records, narrow with per-IdP queries or shorter time windows.
|
|
341
|
+
|
|
342
|
+
Layer 1b — specific fields for spray analysis (use verified field paths from Phase 1):
|
|
343
|
+
```
|
|
344
|
+
QUERY authentication.<verified_ip_field>, authentication.<verified_user_field>,
|
|
345
|
+
authentication.time, authentication.status_id, authentication.message
|
|
346
|
+
WITH authentication.status_id = FAILURE AFTER 7d
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Use the IP and user field paths verified in Phase 1 (Absolute Rule 9).** If Phase 1 determined `device.ip` is the populated IP field, use `device.ip` — NOT `src_endpoint.ip`. For HIGH VOLUME connectors, start at 2h (failures are filtered, lower volume than all auth).
|
|
350
|
+
|
|
351
|
+
**Analysis logic:** Group results by source IP. Flag any IP that attempted 5+ distinct usernames within a 1-hour window. This is the spray signature — many users, few passwords, same source.
|
|
352
|
+
|
|
353
|
+
**Thresholds:**
|
|
354
|
+
|
|
355
|
+
| Level | Criteria |
|
|
356
|
+
|-------|----------|
|
|
357
|
+
| SUSPICIOUS | 5-15 distinct usernames per source IP per hour |
|
|
358
|
+
| ACTIVE THREAT | 15+ distinct usernames per source IP per hour, OR spray followed by successful authentication from the same IP |
|
|
359
|
+
|
|
360
|
+
**Follow-up actions:**
|
|
361
|
+
- Check for subsequent successful authentication from the same source IPs
|
|
362
|
+
- Invoke `digital-workers:threat-intel-enricher` on flagged source IPs: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
363
|
+
- If success after spray detected, invoke `digital-workers:identity-investigator` on the compromised account: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
364
|
+
|
|
365
|
+
**Targeted actor search (critical):** After identifying spray source IPs, do NOT stop at aggregate analysis. For each spray source IP with successful authentications:
|
|
366
|
+
1. Extract the specific usernames that succeeded from that IP
|
|
367
|
+
2. Query each compromised user's full auth history across ALL IdPs (7d, per-user queries to avoid overflow)
|
|
368
|
+
3. These users become priority targets for Pattern 3 (impossible travel) and Pattern 6 (cross-IdP)
|
|
369
|
+
4. Run cross-domain pivot on each compromised user (see Cross-Domain Pivot Table)
|
|
370
|
+
|
|
371
|
+
This ensures the skill finds the specific attack actors instead of getting lost in aggregate noise.
|
|
372
|
+
|
|
373
|
+
**Note:** Pattern 2 reuses this failure data — do not discard.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
### Pattern 2: Credential Stuffing (T1110.004)
|
|
378
|
+
|
|
379
|
+
**What it detects:** An attacker using stolen credential pairs (from breaches) against your identity providers. Distinguished from spraying by volume, velocity, and cross-IdP indicators.
|
|
380
|
+
|
|
381
|
+
**MITRE technique:** T1110.004 — Brute Force: Credential Stuffing
|
|
382
|
+
|
|
383
|
+
**Query approach:** Reuses Pattern 1 failure data. No redundant query needed. Focus analysis on:
|
|
384
|
+
- **Volume + velocity:** 50+ failures in a 10-minute window from a single IP or small IP range
|
|
385
|
+
- **Cross-IdP signal:** Same IP stuffing both Entra ID and Okta (or any two IdPs). This is the strongest indicator — legitimate users do not authenticate across multiple IdPs from the same IP in rapid succession.
|
|
386
|
+
|
|
387
|
+
If Pattern 1 did not surface sufficient data, query additional IdPs:
|
|
388
|
+
```
|
|
389
|
+
QUERY authentication.src_endpoint.ip, authentication.user.username,
|
|
390
|
+
authentication.time, authentication.status_id
|
|
391
|
+
WITH authentication.status_id = FAILURE AFTER 7d
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Thresholds:**
|
|
395
|
+
|
|
396
|
+
| Level | Criteria |
|
|
397
|
+
|-------|----------|
|
|
398
|
+
| SUSPICIOUS | 50+ failures in 10 minutes from one IP, OR 20+ failures across 2+ IdPs from the same IP |
|
|
399
|
+
| ACTIVE THREAT | Stuffing failures followed by successful authentication from the same IP |
|
|
400
|
+
|
|
401
|
+
**Follow-up actions:**
|
|
402
|
+
- Check for post-stuffing successes — query successful auths from the same source IPs within 1 hour of the stuffing burst
|
|
403
|
+
- If compromised accounts found, invoke `digital-workers:identity-investigator`: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
404
|
+
- Invoke `digital-workers:threat-intel-enricher` on stuffing IPs: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
### Pattern 3: Impossible Travel (T1078)
|
|
409
|
+
|
|
410
|
+
**What it detects:** A user authenticating from two geographic locations that are physically impossible to travel between in the elapsed time — strong indicator of credential compromise or session hijacking.
|
|
411
|
+
|
|
412
|
+
**MITRE technique:** T1078 — Valid Accounts
|
|
413
|
+
|
|
414
|
+
**Query approach:**
|
|
415
|
+
|
|
416
|
+
Layer 1a — gauge successful authentication volume:
|
|
417
|
+
```
|
|
418
|
+
QUERY authentication.message, authentication.time
|
|
419
|
+
WITH authentication.status_id = SUCCESS AFTER 7d
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Layer 1b — per-IdP query with username, IP, and time (shared with Pattern 6). **Use verified field paths from Phase 1:**
|
|
423
|
+
```
|
|
424
|
+
QUERY authentication.<verified_user_field>, authentication.<verified_ip_field>,
|
|
425
|
+
authentication.time, authentication.status_id, authentication.message
|
|
426
|
+
WITH authentication.status_id = SUCCESS AFTER 7d
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**If the verified IP field for a connector is `device.ip`, use that — NOT `src_endpoint.ip`.** A connector with empty `src_endpoint.ip` is NOT a reason to skip impossible travel — check `device.ip` first.
|
|
430
|
+
|
|
431
|
+
**Geo resolution strategy (in order of preference):**
|
|
432
|
+
1. **Geo fields in auth events** — check if the IdP connector provides geo fields (e.g., `authentication.src_endpoint.location`). Use if available.
|
|
433
|
+
2. **TI connector fallback** — invoke `digital-workers:threat-intel-enricher` to resolve IP geolocation: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
434
|
+
3. **Raw IP comparison** — if geo is unavailable, flag same user authenticating from different IPs within a short window. Reduced confidence — note in findings.
|
|
435
|
+
|
|
436
|
+
**Analysis logic:** For each user, sort authentications by time. Compare consecutive logins — if two logins from different locations are 500+ miles apart and within 1 hour, flag as impossible travel.
|
|
437
|
+
|
|
438
|
+
**Priority order for actor checks (critical):** Do not analyze users at random. Check in this order:
|
|
439
|
+
1. **Users identified as spray victims** (from Pattern 1 targeted actor search) — highest priority
|
|
440
|
+
2. **Users with auth from known-malicious IPs** (from Pattern 1 IOCs or TI enrichment)
|
|
441
|
+
3. **Users with auth from cloud/VPS IP ranges** (DigitalOcean, AWS, Azure, GCP, Linode, Vultr, OVH, Hetzner, Tencent, UCloud)
|
|
442
|
+
4. **Broad per-user analysis** only if the above don't produce findings
|
|
443
|
+
|
|
444
|
+
This priority order ensures seeded/targeted attack actors are checked before background noise drowns them out.
|
|
445
|
+
|
|
446
|
+
**Thresholds:**
|
|
447
|
+
|
|
448
|
+
| Level | Criteria |
|
|
449
|
+
|-------|----------|
|
|
450
|
+
| SUSPICIOUS | 500+ miles between logins within 1 hour |
|
|
451
|
+
| ACTIVE THREAT | Impossible travel followed by post-authentication activity (data access, privilege changes, lateral movement) |
|
|
452
|
+
|
|
453
|
+
**Follow-up actions:**
|
|
454
|
+
- Invoke `digital-workers:identity-investigator` on the user: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
455
|
+
- Invoke `digital-workers:threat-intel-enricher` on the anomalous IP: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
456
|
+
|
|
457
|
+
**Note:** Pattern 6 reuses this successful authentication data — do not discard.
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### Pattern 4: MFA Fatigue (T1621)
|
|
462
|
+
|
|
463
|
+
**What it detects:** An attacker triggering repeated MFA push notifications to exhaust the user into approving one — a social engineering technique that bypasses MFA entirely.
|
|
464
|
+
|
|
465
|
+
**MITRE technique:** T1621 — Multi-Factor Authentication Request Generation
|
|
466
|
+
|
|
467
|
+
**Schema-dependent gate:** Phase 1 must verify that MFA-specific fields exist in at least one identity connector. MFA fields vary by IdP — check for challenge/response events, push notification events, or MFA status fields.
|
|
468
|
+
|
|
469
|
+
**If MFA fields exist:**
|
|
470
|
+
|
|
471
|
+
```
|
|
472
|
+
-- Query for MFA challenge events per user
|
|
473
|
+
QUERY authentication.user.username, authentication.time, authentication.message,
|
|
474
|
+
authentication.status_id
|
|
475
|
+
WITH [MFA-specific filter based on available fields] AFTER 7d
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Analysis logic:** Group MFA challenges by user. Flag any user with 5+ MFA challenges within a 10-minute window.
|
|
479
|
+
|
|
480
|
+
**If MFA fields do NOT exist:** Mark as DATA GAP with specific remediation guidance:
|
|
481
|
+
> "MFA Fatigue pattern requires MFA challenge/response event data. Current identity connectors do not expose MFA-specific fields. Remediation: Enable MFA audit logging in [IdP name] and verify OCSF mapping includes MFA event fields."
|
|
482
|
+
|
|
483
|
+
**Thresholds:**
|
|
484
|
+
|
|
485
|
+
| Level | Criteria |
|
|
486
|
+
|-------|----------|
|
|
487
|
+
| SUSPICIOUS | 5-10 MFA challenges per user within 10 minutes |
|
|
488
|
+
| ACTIVE THREAT | 10+ MFA challenges followed by a successful authentication (user approved the push) |
|
|
489
|
+
|
|
490
|
+
**Follow-up actions:**
|
|
491
|
+
- Invoke `digital-workers:identity-investigator` on the targeted user: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
492
|
+
- Check post-authentication activity — if the user approved under fatigue, what did the attacker do next?
|
|
493
|
+
|
|
494
|
+
**Expected result in many environments:** DATA GAP. Most IdP connectors do not yet expose granular MFA challenge events. This is a valuable finding — it tells the customer what to enable.
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
### Pattern 5: Dormant Account Activation (T1078)
|
|
499
|
+
|
|
500
|
+
**What it detects:** An account that has been inactive for an extended period suddenly authenticating — could indicate a compromised dormant account being activated by an attacker.
|
|
501
|
+
|
|
502
|
+
**MITRE technique:** T1078 — Valid Accounts
|
|
503
|
+
|
|
504
|
+
**Query approach:**
|
|
505
|
+
|
|
506
|
+
Step 1 — Authentication-as-baseline (ALWAYS run this, regardless of user_inventory):
|
|
507
|
+
|
|
508
|
+
This is the primary dormancy detection method. It works even when user_inventory has a different domain or no `last_sign_in` field.
|
|
509
|
+
```
|
|
510
|
+
-- Get authentication activity for the past 7 days (use verified field paths from Phase 1)
|
|
511
|
+
QUERY authentication.<verified_user_field>, authentication.time,
|
|
512
|
+
authentication.<verified_ip_field>, authentication.status_id
|
|
513
|
+
WITH authentication.status_id = SUCCESS AFTER 7d
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Use the verified IP and user field paths from Phase 1.** If Phase 1 determined that `device.ip` is the populated IP field, use `device.ip` here — NOT `src_endpoint.ip`.
|
|
517
|
+
|
|
518
|
+
**HIGH VOLUME connector strategy:** The 7d query will overflow on HIGH VOLUME connectors. Instead of giving up, use this approach:
|
|
519
|
+
1. Query the **most recent 2h** of successes (per QUERY CONFIG time guidance) — this is your "recent set"
|
|
520
|
+
2. Query a **2h window from 3-5 days ago** (e.g., `AFTER 5d` with a 2h slice) — this is your "baseline set"
|
|
521
|
+
3. Extract distinct usernames from each set
|
|
522
|
+
4. Any user in the "recent set" who does NOT appear in the "baseline set" is a candidate dormant activation
|
|
523
|
+
|
|
524
|
+
If the baseline query also overflows, narrow to per-connector 1h windows. The goal is two comparable time slices — you do NOT need the full 7d dataset.
|
|
525
|
+
|
|
526
|
+
**Do NOT mark Pattern 5 as INCOMPLETE because of overflow.** Overflow is expected on HIGH VOLUME connectors. The chunked approach above works within the volume constraints. Only mark INCOMPLETE if both time slices overflow even at 1h windows.
|
|
527
|
+
|
|
528
|
+
Partition approach for non-HIGH-VOLUME connectors (7d query fits inline):
|
|
529
|
+
- **Baseline set** = users who authenticated between 2d and 7d ago
|
|
530
|
+
- **Recent set** = users who authenticated in the last 1d
|
|
531
|
+
- Any user in the "recent set" who does NOT appear in the "baseline set" is a candidate dormant activation
|
|
532
|
+
- This is an analytical partition of the 7d query results, not separate queries. FSQL does not support BEFORE clauses.
|
|
533
|
+
|
|
534
|
+
For each candidate dormant activation, check the source IP:
|
|
535
|
+
- Suspicious geolocation (Russia, China, North Korea, Iran for accounts that normally authenticate domestically) → SUSPICIOUS
|
|
536
|
+
- Cloud/VPS IP → SUSPICIOUS
|
|
537
|
+
- Use the `Geolocate IPs` connector if available: `QUERY *.message, *.time WITH %ip = '<activation_ip>'` against the Geolocate IPs connector to resolve geography
|
|
538
|
+
|
|
539
|
+
Step 2 (supplemental) — Check user_inventory for `last_sign_in` if available:
|
|
540
|
+
|
|
541
|
+
If user_inventory has a `last_sign_in` or equivalent timestamp AND the user domain matches the auth domain, use it to extend dormancy detection beyond 7 days. Accounts with `last_sign_in` older than 90 days that suddenly authenticate are dormant activations.
|
|
542
|
+
|
|
543
|
+
**Domain mismatch is NOT a blocker for Step 1.** The auth-as-baseline approach works entirely within authentication data — it does not need user_inventory. If user_inventory has a different domain, Step 1 still runs. Step 2 is supplemental.
|
|
544
|
+
|
|
545
|
+
**Caveat:** With a 7-day authentication window, Step 1 can only detect accounts dormant for 7+ days that activated within the window. Step 2 (if `last_sign_in` is available) extends this. Document this limitation in findings.
|
|
546
|
+
|
|
547
|
+
**Thresholds:**
|
|
548
|
+
|
|
549
|
+
| Level | Criteria |
|
|
550
|
+
|-------|----------|
|
|
551
|
+
| SUSPICIOUS | Account dormant 6+ days authenticates from an unknown or unusual IP |
|
|
552
|
+
| ACTIVE THREAT | Dormant account activates AND shows post-authentication activity (data access, account changes, lateral movement) |
|
|
553
|
+
|
|
554
|
+
**Follow-up actions:**
|
|
555
|
+
- Invoke `digital-workers:identity-investigator` on the activated account: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
556
|
+
- Check for account modifications — was the password reset before activation? Were MFA settings changed?
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
### Pattern 6: Cross-IdP Anomaly (T1078) — THE DIFFERENTIATOR
|
|
561
|
+
|
|
562
|
+
**What it detects:** The same user authenticating to different identity providers from different IP addresses within a short time window. No single-vendor ITDR tool can detect this because vendors only see their own IdP. Query federates across all IdPs in the mesh.
|
|
563
|
+
|
|
564
|
+
**MITRE technique:** T1078 — Valid Accounts
|
|
565
|
+
|
|
566
|
+
**This is the primary differentiator of the ITDR skill.** It exploits Query's unique position as a cross-IdP data mesh to detect identity threats that are invisible to any individual IdP vendor.
|
|
567
|
+
|
|
568
|
+
**Query approach:** Reuses Pattern 3 successful authentication data. Query each IdP's authentication events separately if not already captured. **Use verified field paths from Phase 1:**
|
|
569
|
+
|
|
570
|
+
```
|
|
571
|
+
-- Per-IdP authentication with username and IP (use verified paths)
|
|
572
|
+
QUERY authentication.<verified_user_field>, authentication.<verified_ip_field>,
|
|
573
|
+
authentication.time, authentication.message
|
|
574
|
+
WITH authentication.status_id = SUCCESS AFTER 7d
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**If one IdP uses `device.ip` and another uses `src_endpoint.ip`, that's fine — use the correct path per connector.** The cross-IdP comparison is by username and IP value, not by field path.
|
|
578
|
+
|
|
579
|
+
**Analysis logic:** Correlate by username across IdPs. For each user who appears in multiple IdPs, compare source IPs and timestamps. Flag any user authenticating to different IdPs from different IPs within a 30-minute window.
|
|
580
|
+
|
|
581
|
+
**Single-IdP handling:** If the mesh contains only one identity provider, mark Pattern 6 as NOT APPLICABLE (not CLEAN, not DATA GAP). Cross-IdP correlation requires 2+ IdPs by definition.
|
|
582
|
+
|
|
583
|
+
**Thresholds:**
|
|
584
|
+
|
|
585
|
+
| Level | Criteria |
|
|
586
|
+
|-------|----------|
|
|
587
|
+
| SUSPICIOUS | Same user, different IdPs, different source IPs within 30 minutes |
|
|
588
|
+
| ACTIVE THREAT | Different geolocations across IdPs AND post-authentication anomalies (privilege changes, unusual data access) |
|
|
589
|
+
|
|
590
|
+
**Follow-up actions:**
|
|
591
|
+
- Invoke `digital-workers:identity-investigator` for a full deep dive on the user: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
592
|
+
- Invoke `digital-workers:threat-intel-enricher` on both source IPs: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
593
|
+
- Determine if this is a legitimate scenario (user on VPN + mobile) or compromise
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
### Pattern 7: Privilege Escalation (T1098)
|
|
598
|
+
|
|
599
|
+
**What it detects:** Unauthorized or anomalous privilege grants — admin role assignments, group membership changes, permission escalations that may indicate an attacker consolidating access.
|
|
600
|
+
|
|
601
|
+
**MITRE technique:** T1098 — Account Manipulation
|
|
602
|
+
|
|
603
|
+
**Schema-dependent gate:** Phase 1 must verify that `entity_management` or `account_change` events exist.
|
|
604
|
+
|
|
605
|
+
**If entity_management/account_change exists:**
|
|
606
|
+
```
|
|
607
|
+
QUERY entity_management.message, entity_management.time,
|
|
608
|
+
entity_management.user.username, entity_management.type_name
|
|
609
|
+
AFTER 30d
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
Or for account_change:
|
|
613
|
+
```
|
|
614
|
+
QUERY account_change.message, account_change.time,
|
|
615
|
+
account_change.user.username, account_change.type_name
|
|
616
|
+
AFTER 30d
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**30-day lookback is intentional.** Privilege escalation is low-volume, high-impact. A 7-day window may miss slow-burn attacks.
|
|
620
|
+
|
|
621
|
+
**Fallback:** If neither `entity_management` nor `account_change` exist, try `api_activity` for admin API calls:
|
|
622
|
+
```
|
|
623
|
+
QUERY api_activity.message, api_activity.time,
|
|
624
|
+
api_activity.user.username, api_activity.type_name
|
|
625
|
+
AFTER 30d
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
**If none of these event types exist:** Mark as DATA GAP with remediation guidance.
|
|
629
|
+
|
|
630
|
+
**Thresholds:**
|
|
631
|
+
|
|
632
|
+
| Level | Criteria |
|
|
633
|
+
|-------|----------|
|
|
634
|
+
| SUSPICIOUS | Admin role granted outside business hours, OR granted by a non-admin account, OR self-grant of elevated privileges |
|
|
635
|
+
| ACTIVE THREAT | Privilege grant to a recently compromised account (flagged in another pattern), OR multiple escalations in a short window |
|
|
636
|
+
|
|
637
|
+
**Follow-up actions:**
|
|
638
|
+
- Invoke `digital-workers:identity-investigator` on BOTH the granting account and the receiving account: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
639
|
+
- Check if the receiving account was flagged in Patterns 1-6
|
|
640
|
+
|
|
641
|
+
**Expected result in many environments:** DATA GAP. Many IdP connectors do not yet map privilege change events to OCSF `entity_management` or `account_change`. Document this gap with specific remediation.
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
### Pattern 8: Service Account Abuse (T1078.001)
|
|
646
|
+
|
|
647
|
+
**What it detects:** Service accounts being used interactively — a strong indicator of credential theft or misuse, since service accounts should authenticate only via automated processes.
|
|
648
|
+
|
|
649
|
+
**MITRE technique:** T1078.001 — Valid Accounts: Default Accounts
|
|
650
|
+
|
|
651
|
+
**Query approach:**
|
|
652
|
+
|
|
653
|
+
Step 1 — Identify service accounts from TWO sources:
|
|
654
|
+
|
|
655
|
+
**Source A: user_inventory** (if available and same domain as auth events):
|
|
656
|
+
```
|
|
657
|
+
QUERY user_inventory.user.username, user_inventory.user.type,
|
|
658
|
+
user_inventory.message, user_inventory.time
|
|
659
|
+
AFTER 1d
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Look for naming conventions (svc_, service_, sa-), account type fields, or accounts flagged as non-interactive.
|
|
663
|
+
|
|
664
|
+
**Source B: authentication data directly** (ALWAYS run this, even if Source A returns results):
|
|
665
|
+
```
|
|
666
|
+
-- Search for service account naming patterns in auth events
|
|
667
|
+
QUERY authentication.<verified_user_field>, authentication.<verified_ip_field>,
|
|
668
|
+
authentication.http_request.user_agent, authentication.time,
|
|
669
|
+
authentication.status_id, authentication.message
|
|
670
|
+
WITH authentication.<verified_user_field> ICONTAINS 'svc' AFTER 7d
|
|
671
|
+
|
|
672
|
+
-- Also search for 'service' and 'sa-' patterns
|
|
673
|
+
QUERY authentication.<verified_user_field>, authentication.<verified_ip_field>,
|
|
674
|
+
authentication.time, authentication.message
|
|
675
|
+
WITH authentication.<verified_user_field> ICONTAINS 'service' AFTER 7d
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
Run Source B against EVERY IdP connector. Service accounts may exist in one IdP but not another. **Do NOT skip Source B even if Source A found nothing** — service accounts authenticate even when they're not in user_inventory.
|
|
679
|
+
|
|
680
|
+
Step 2 — For each service account found (from either source), query its full auth history:
|
|
681
|
+
```
|
|
682
|
+
QUERY authentication.<verified_user_field>, authentication.<verified_ip_field>,
|
|
683
|
+
authentication.http_request.user_agent, authentication.time,
|
|
684
|
+
authentication.status_id, authentication.message
|
|
685
|
+
WITH authentication.<verified_user_field> = '<service_account>' AFTER 7d
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**Analysis logic:** Flag service accounts with:
|
|
689
|
+
- Browser user agents (interactive login)
|
|
690
|
+
- Workstation/desktop source IPs (not server IPs)
|
|
691
|
+
- Authentication from unexpected networks
|
|
692
|
+
- Off-hours authentication (if the service should run on a schedule)
|
|
693
|
+
|
|
694
|
+
**Thresholds:**
|
|
695
|
+
|
|
696
|
+
| Level | Criteria |
|
|
697
|
+
|-------|----------|
|
|
698
|
+
| SUSPICIOUS | Service account with browser user agent or workstation source IP |
|
|
699
|
+
| ACTIVE THREAT | Service account source IP was flagged in another pattern (spraying source, impossible travel endpoint, etc.) |
|
|
700
|
+
|
|
701
|
+
**Follow-up actions:**
|
|
702
|
+
- Invoke `digital-workers:identity-investigator` on the service account: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
703
|
+
- Determine blast radius — what systems does this service account have access to?
|
|
704
|
+
- **Identify the human user on the workstation.** If the anomalous login came from a workstation IP, query auth events from that IP to find the regular user who was logged in at the time:
|
|
705
|
+
```
|
|
706
|
+
QUERY authentication.<verified_user_field>, authentication.time,
|
|
707
|
+
authentication.status_id, authentication.message
|
|
708
|
+
WITH authentication.<verified_ip_field> = '<workstation_ip>' AFTER 24h
|
|
709
|
+
```
|
|
710
|
+
Filter out the service account itself — the remaining users are the workstation's regular operators. The user logged in closest to the anomalous service account login time is the most likely person who used the credentials.
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## Cross-Domain Pivot Table
|
|
715
|
+
|
|
716
|
+
When a pattern classifies a finding as SUSPICIOUS or ACTIVE THREAT, use this table to determine the next best move. The pivot extends the investigation beyond identity data into proxy, network, process, and other domains — following the attack chain where no single-vendor ITDR tool can go.
|
|
717
|
+
|
|
718
|
+
### Pivot Lookup
|
|
719
|
+
|
|
720
|
+
| Identity Finding | Pivot Key | Pivot Query | What You're Looking For |
|
|
721
|
+
|---|---|---|---|
|
|
722
|
+
| Compromised account (spray success, impossible travel, cross-IdP) | `%email` (the compromised user) | `QUERY *.message, *.time WITH %email = '<user>' AFTER 24h` | Which non-identity event types contain this user's activity — proxy browsing, file access, process execution, cloud API calls |
|
|
723
|
+
| Compromised account | `%ip` (the attacker IP) | `QUERY *.message, *.time WITH %ip = '<attacker_ip>' AFTER 7d` | What else has this IP touched — other users, other systems, other event types |
|
|
724
|
+
| Service account abuse | `%email` (svc account) | `QUERY *.message, *.time WITH %email = '<svc_account>' AFTER 24h` | What did the service account access after the interactive login — cloud admin consoles, internal systems, data stores |
|
|
725
|
+
| Service account abuse | `%ip` (workstation IP) | `QUERY *.message, *.time WITH %ip = '<workstation_ip>' AFTER 24h` | What else originated from the workstation that accessed the service account |
|
|
726
|
+
| Any malicious IP (from TI enrichment) | `%ip` | `QUERY *.message, *.time WITH %ip = '<malicious_ip>' AFTER 7d` | Full mesh-wide footprint of this IP across all event types |
|
|
727
|
+
| Cross-IdP anomaly | `%email` + both IPs | Same as compromised account pivots | Verify the IP discrepancy, check for post-auth activity from both IPs |
|
|
728
|
+
|
|
729
|
+
### Reading Pivot Results
|
|
730
|
+
|
|
731
|
+
The Layer 1a pivot returns records with the `__event` field showing which event types matched. Here's what each event type means and what to look for in the Layer 1b follow-up:
|
|
732
|
+
|
|
733
|
+
| Event Type | Domain | What To Look For |
|
|
734
|
+
|---|---|---|
|
|
735
|
+
| `http_activity` | Proxy/Web | C2 beaconing (regular interval connections), phishing clicks, cloud admin console access (AWS/Azure/GCP consoles), exfil to cloud storage (Dropbox, OneDrive, IceDrive) |
|
|
736
|
+
| `network_activity` | Network flow/IPS | Lateral movement (SMB port 445, RDP 3389, SSH 22, WinRM 5985/5986), unusual ports, data exfil volume, scanning patterns |
|
|
737
|
+
| `process_activity` | Endpoint/EDR | Post-compromise tooling, LOLBins, credential dumping, persistence mechanisms |
|
|
738
|
+
| `file_activity` | File system | Staging directories, exfil preparation, malware drops, sensitive file access |
|
|
739
|
+
| `dns_activity` | DNS | Beaconing patterns (regular interval queries to same domain), DGA domains, DNS tunneling (TXT record queries) |
|
|
740
|
+
| `email_activity` | Email | Phishing delivery, BEC, forwarding rule creation |
|
|
741
|
+
| `data_security_finding` | DLP | Exfiltration attempts, policy violations, large outbound transfers |
|
|
742
|
+
| `api_activity` | Cloud control plane | Privilege escalation, resource creation, key generation, IAM changes |
|
|
743
|
+
| `detection_finding` | Security alerts | Existing alerts the SOC may have missed or not correlated with the identity finding |
|
|
744
|
+
|
|
745
|
+
### Pivot Execution Rules
|
|
746
|
+
|
|
747
|
+
1. **Trigger condition:** Pivot runs ONLY when a pattern classifies a finding as SUSPICIOUS or ACTIVE THREAT. CLEAN and DATA GAP patterns do not trigger pivots.
|
|
748
|
+
2. **Always Layer 1a first.** The pivot query is always `*.message, *.time` with an observable filter. This returns the `__event` field showing which event types matched — without overflowing.
|
|
749
|
+
3. **Follow up on hits.** If Layer 1a returns records for a non-identity event type, run a targeted Layer 1b query with specific field selectors for that event type. Use `Search_FSQL_SCHEMA` if you haven't queried this event type before.
|
|
750
|
+
4. **Maximum 3 pivot queries per finding.** The pivot is investigative triage, not a full incident investigation. If the pivot reveals a complex attack chain, document what you found and note it for the analyst — don't try to investigate everything.
|
|
751
|
+
5. **Log every pivot query in queries.md** under a "Cross-Domain Pivot" subheading for the pattern.
|
|
752
|
+
6. **Pivot queries count toward the circuit breaker.** The 25-minute checkpoint and 45-minute hard pause include pivot query time.
|
|
753
|
+
7. **Run independent pivot queries in parallel.** If pivoting on both a user email and an IP for the same finding, batch them in a single parallel call.
|
|
754
|
+
8. **Pivot time windows are defaults, subject to Rule 8.** If the target connector is HIGH VOLUME, start with shorter windows per the volume heuristic.
|
|
755
|
+
9. **All pivot queries are called directly** via `Validate_FSQL_Query` and `Execute_FSQL_Query` MCP tools per Absolute Rule 7. Do NOT dispatch to fsql-expert sub-skill for pivot queries.
|
|
756
|
+
10. **Check pivot viability first.** Consult the Pivot Viability section of `identity-data-map.md` before running pivots. If no connectors produce the target event type, skip that pivot and document the gap.
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## Phase 2 Exit Gate
|
|
761
|
+
|
|
762
|
+
### Hard Gate — Phase 2 Exit
|
|
763
|
+
|
|
764
|
+
Before proceeding to Phase 3, verify:
|
|
765
|
+
- `queries.md` has entries for every pattern attempted (8 patterns, even if some are DATA GAP or NOT APPLICABLE)
|
|
766
|
+
- `iocs.md` has been written if any IOCs were discovered
|
|
767
|
+
- Every pattern has a classification: ACTIVE THREAT, SUSPICIOUS, CLEAN, DATA GAP, ERROR, INCOMPLETE, or NOT APPLICABLE
|
|
768
|
+
|
|
769
|
+
**Phase 2 exit verification checklist:**
|
|
770
|
+
- [ ] All 8 patterns attempted or documented as NOT VIABLE (from Phase 1 data map)
|
|
771
|
+
- [ ] Every query logged in `queries.md`
|
|
772
|
+
- [ ] Specialist skills invoked for all SUSPICIOUS and ACTIVE THREAT findings
|
|
773
|
+
- [ ] IOCs written to `iocs.md` if any discovered
|
|
774
|
+
- [ ] No undocumented queries
|
|
775
|
+
|
|
776
|
+
If `queries.md` does not have entries for all patterns, STOP. Complete the missing patterns or document why they were skipped.
|
|
777
|
+
|
|
778
|
+
Continue immediately to Phase 3 — do not stop or wait for user input.
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## Phase 3: Synthesis & Report
|
|
783
|
+
|
|
784
|
+
**Goal:** Correlate findings across patterns, build the scorecard, construct the attack narrative, and produce the final assessment report.
|
|
785
|
+
|
|
786
|
+
### Process
|
|
787
|
+
|
|
788
|
+
**Step 1: Cross-pattern correlation**
|
|
789
|
+
|
|
790
|
+
Look for accounts and IPs that appear in multiple patterns:
|
|
791
|
+
- Account flagged in Pattern 1 (spraying target) AND Pattern 5 (dormant activation) = likely compromised dormant account
|
|
792
|
+
- IP flagged in Pattern 1 (spray source) AND Pattern 8 (service account login source) = attacker pivoting from spraying to service account abuse
|
|
793
|
+
- Account flagged in Pattern 3 (impossible travel) AND Pattern 7 (privilege escalation) = attacker with stolen credentials escalating access
|
|
794
|
+
|
|
795
|
+
Document every cross-pattern correlation. These are the highest-confidence findings.
|
|
796
|
+
|
|
797
|
+
**Step 2: Build the scorecard**
|
|
798
|
+
|
|
799
|
+
Create the per-pattern status table (see scorecard template below).
|
|
800
|
+
|
|
801
|
+
**Step 3: Construct attack narrative**
|
|
802
|
+
|
|
803
|
+
Build a chronological timeline across all IdPs, connecting findings into a coherent narrative. If multiple patterns fire for the same actor, this is where the kill chain emerges.
|
|
804
|
+
|
|
805
|
+
**Step 4: Score overall risk**
|
|
806
|
+
|
|
807
|
+
| Risk Level | Criteria |
|
|
808
|
+
|------------|----------|
|
|
809
|
+
| HEALTHY | All patterns CLEAN or DATA GAP, no SUSPICIOUS findings |
|
|
810
|
+
| ELEVATED RISK | One or more SUSPICIOUS findings, no ACTIVE THREAT |
|
|
811
|
+
| ACTIVE THREAT | One or more ACTIVE THREAT findings confirmed |
|
|
812
|
+
|
|
813
|
+
**Step 5: Generate the report**
|
|
814
|
+
|
|
815
|
+
Write `report.md` following the report template below. Display the report inline in the conversation — the analyst reads and acts in the conversation. The file is the durable audit trail.
|
|
816
|
+
|
|
817
|
+
**Step 6: Invoke senior analyst review (MANDATORY HARD GATE)**
|
|
818
|
+
|
|
819
|
+
Before presenting the report to the analyst, check these conditions:
|
|
820
|
+
- If `overall_risk` = ACTIVE THREAT → invoke `senior-analyst-review`. **NOT OPTIONAL.**
|
|
821
|
+
- If `finding_count(SUSPICIOUS)` >= 2 → invoke `senior-analyst-review`.
|
|
822
|
+
- If cross-pattern correlation identifies a kill chain → invoke `senior-analyst-review`.
|
|
823
|
+
- If analyst explicitly requests it → invoke `senior-analyst-review`.
|
|
824
|
+
|
|
825
|
+
Invoke with instruction: "Return results to the ITDR orchestrator and continue. Do not present to the user or wait for input."
|
|
826
|
+
|
|
827
|
+
The review skill writes `review.md`. Wait for it to complete before presenting the report. This gate exists because ACTIVE THREAT findings have compliance and escalation implications. The senior analyst review catches blind spots, false escalations, and missing context before the analyst acts on the report.
|
|
828
|
+
|
|
829
|
+
**Step 7: Save all artifacts**
|
|
830
|
+
|
|
831
|
+
Write using the Write tool (never Bash/echo):
|
|
832
|
+
- `report.md` — full assessment report
|
|
833
|
+
- `queries.md` — should already be complete from Phase 2
|
|
834
|
+
- `iocs.md` — all IOCs discovered (should already exist if IOCs found)
|
|
835
|
+
- `scorecard.md` — machine-parseable scorecard (YAML format)
|
|
836
|
+
|
|
837
|
+
**Step 8: Update environment profile**
|
|
838
|
+
|
|
839
|
+
If `digital-workers/learned/environment-profile.json` exists, note identity-specific findings:
|
|
840
|
+
- Identity connectors discovered and their capabilities
|
|
841
|
+
- Data gaps identified
|
|
842
|
+
- Patterns that worked well vs. those that need better data
|
|
843
|
+
- Any false positive patterns to note for future assessments
|
|
844
|
+
|
|
845
|
+
**Step 9: Present report inline**
|
|
846
|
+
|
|
847
|
+
Display the full report in the conversation. The analyst reads and acts here. The saved files are the audit trail — the conversation is the workspace.
|
|
848
|
+
|
|
849
|
+
### Report Template
|
|
850
|
+
|
|
851
|
+
```markdown
|
|
852
|
+
# ITDR Assessment Report
|
|
853
|
+
|
|
854
|
+
## 1. Business Summary
|
|
855
|
+
|
|
856
|
+
[2-3 sentences in plain English. What did the assessment find? What is the overall risk posture?
|
|
857
|
+
What are the top 1-2 actions the security team should take?]
|
|
858
|
+
|
|
859
|
+
## 2. Scorecard
|
|
860
|
+
|
|
861
|
+
- **Environment:** [customer/mesh name]
|
|
862
|
+
- **Identity Providers:** [list of IdPs discovered]
|
|
863
|
+
- **Users analyzed:** [count from user_inventory or authentication data]
|
|
864
|
+
- **Time range:** [lookback period]
|
|
865
|
+
- **Overall risk:** [HEALTHY / ELEVATED RISK / ACTIVE THREAT]
|
|
866
|
+
|
|
867
|
+
| # | Pattern | MITRE | Status | Confidence | Finding Summary |
|
|
868
|
+
|---|---------|-------|--------|------------|-----------------|
|
|
869
|
+
| 1 | Password Spraying | T1110.003 | [status] | [HIGH/MEDIUM/LOW] | [1-line summary] |
|
|
870
|
+
| 2 | Credential Stuffing | T1110.004 | [status] | [HIGH/MEDIUM/LOW] | [1-line summary] |
|
|
871
|
+
| 3 | Impossible Travel | T1078 | [status] | [HIGH/MEDIUM/LOW] | [1-line summary] |
|
|
872
|
+
| 4 | MFA Fatigue | T1621 | [status] | [HIGH/MEDIUM/LOW] | [1-line summary] |
|
|
873
|
+
| 5 | Dormant Account Activation | T1078 | [status] | [HIGH/MEDIUM/LOW] | [1-line summary] |
|
|
874
|
+
| 6 | Cross-IdP Anomaly | T1078 | [status] | [HIGH/MEDIUM/LOW] | [1-line summary] |
|
|
875
|
+
| 7 | Privilege Escalation | T1098 | [status] | [HIGH/MEDIUM/LOW] | [1-line summary] |
|
|
876
|
+
| 8 | Service Account Abuse | T1078.001 | [status] | [HIGH/MEDIUM/LOW] | [1-line summary] |
|
|
877
|
+
|
|
878
|
+
## 3. Findings
|
|
879
|
+
|
|
880
|
+
[Ordered by severity — ACTIVE THREAT first, then SUSPICIOUS]
|
|
881
|
+
|
|
882
|
+
### Finding [N]: [Title]
|
|
883
|
+
|
|
884
|
+
- **Severity:** [ACTIVE THREAT / SUSPICIOUS]
|
|
885
|
+
- **MITRE Technique:** [ID — Name]
|
|
886
|
+
- **Pattern:** [which pattern detected this]
|
|
887
|
+
- **What happened:** [description of the finding]
|
|
888
|
+
- **Evidence:**
|
|
889
|
+
- Query: `[exact FSQL query]`
|
|
890
|
+
- Result: [count] records, [summary of key data points]
|
|
891
|
+
- **Identity Investigation:** [results from identity-investigator, or "Not applicable"]
|
|
892
|
+
- **Threat Intel:** [results from threat-intel-enricher, or "Not applicable"]
|
|
893
|
+
- **Recommended Action:** [specific remediation steps]
|
|
894
|
+
|
|
895
|
+
## 4. Cross-Pattern Correlation
|
|
896
|
+
|
|
897
|
+
### Entity Correlation Table
|
|
898
|
+
|
|
899
|
+
Build a correlation table linking every entity (user, IP, host, service account) discovered across patterns and pivots. This table is the connective tissue of the investigation — it shows how identity findings link to network, endpoint, and application activity.
|
|
900
|
+
|
|
901
|
+
| Entity | Type | Patterns | Pivots | Context |
|
|
902
|
+
|--------|------|----------|--------|---------|
|
|
903
|
+
| [e.g., svc-backup@...] | Service Account | P8 | Okta auth, DHCP (LAP-890) | Interactive browser login from workstation |
|
|
904
|
+
| [e.g., 172.16.82.127] | IP (Workstation) | P8 | DHCP, HTTP, network, vuln | LAP-890 laptop, CVE-2013-1985, regular user + svc-backup@ |
|
|
905
|
+
| [e.g., barbara.salazar@...] | User | P1, P3, P6 | email, HTTP, proxy | Spray victim, impossible travel VA→Singapore, C2 beaconing |
|
|
906
|
+
|
|
907
|
+
Include EVERY entity that appeared in a finding or pivot — users, IPs, hostnames, service accounts, domains. The table should let the analyst trace connections: "this IP maps to this host, which was used by this user, who also appeared in this pattern."
|
|
908
|
+
|
|
909
|
+
### Accounts in Multiple Patterns
|
|
910
|
+
[List accounts that appeared in 2+ patterns with what was found in each]
|
|
911
|
+
|
|
912
|
+
### IPs in Multiple Patterns
|
|
913
|
+
[List IPs that appeared in 2+ patterns with what was found in each]
|
|
914
|
+
|
|
915
|
+
### Kill Chain Assessment
|
|
916
|
+
[If cross-pattern findings suggest a coherent attack chain, describe it here.
|
|
917
|
+
If no cross-pattern correlation found, state: "No cross-pattern correlation identified."]
|
|
918
|
+
|
|
919
|
+
## 5. Attack Narrative
|
|
920
|
+
|
|
921
|
+
[Chronological timeline connecting all findings across all IdPs]
|
|
922
|
+
|
|
923
|
+
| Timestamp | Event | Source | Significance |
|
|
924
|
+
|-----------|-------|--------|-------------|
|
|
925
|
+
| [time] | [what happened] | [which IdP/connector] | [why it matters] |
|
|
926
|
+
|
|
927
|
+
[If no attack narrative emerges, state: "No coherent attack narrative identified. Findings are isolated events."]
|
|
928
|
+
|
|
929
|
+
## 6. Data Gaps & Recommendations
|
|
930
|
+
|
|
931
|
+
[Per gap identified]
|
|
932
|
+
|
|
933
|
+
### Gap [N]: [Title]
|
|
934
|
+
|
|
935
|
+
- **Impact:** [which patterns were blocked or degraded]
|
|
936
|
+
- **Blocked techniques:** [MITRE technique IDs that cannot be tested]
|
|
937
|
+
- **Remediation:** [specific steps to close the gap]
|
|
938
|
+
- **Priority:** [CRITICAL / HIGH / MEDIUM / LOW]
|
|
939
|
+
|
|
940
|
+
## 7. MITRE ATT&CK Coverage
|
|
941
|
+
|
|
942
|
+
| Technique | Status | Finding |
|
|
943
|
+
|-----------|--------|---------|
|
|
944
|
+
| T1110.003 — Password Spraying | [TESTED / GAP] | [summary or gap reason] |
|
|
945
|
+
| T1110.004 — Credential Stuffing | [TESTED / GAP] | [summary or gap reason] |
|
|
946
|
+
| T1078 — Valid Accounts | [TESTED / GAP] | [summary or gap reason] |
|
|
947
|
+
| T1621 — MFA Request Generation | [TESTED / GAP] | [summary or gap reason] |
|
|
948
|
+
| T1098 — Account Manipulation | [TESTED / GAP] | [summary or gap reason] |
|
|
949
|
+
| T1078.001 — Default Accounts | [TESTED / GAP] | [summary or gap reason] |
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
### Scorecard YAML Format (scorecard.md)
|
|
953
|
+
|
|
954
|
+
```yaml
|
|
955
|
+
itdr_assessment:
|
|
956
|
+
date: "YYYY-MM-DD"
|
|
957
|
+
environment: "[mesh name]"
|
|
958
|
+
identity_providers:
|
|
959
|
+
- name: "[IdP name]"
|
|
960
|
+
connector: "[connector name]"
|
|
961
|
+
type: "[static/dynamic]"
|
|
962
|
+
overall_risk: "[HEALTHY/ELEVATED RISK/ACTIVE THREAT]"
|
|
963
|
+
patterns:
|
|
964
|
+
- id: 1
|
|
965
|
+
name: "Password Spraying"
|
|
966
|
+
mitre: "T1110.003"
|
|
967
|
+
status: "[ACTIVE THREAT/SUSPICIOUS/CLEAN/DATA GAP/ERROR/INCOMPLETE/NOT APPLICABLE]"
|
|
968
|
+
confidence: "[HIGH/MEDIUM/LOW]"
|
|
969
|
+
finding_count: 0
|
|
970
|
+
summary: "[1-line summary]"
|
|
971
|
+
- id: 2
|
|
972
|
+
name: "Credential Stuffing"
|
|
973
|
+
mitre: "T1110.004"
|
|
974
|
+
status: "[status]"
|
|
975
|
+
confidence: "[confidence]"
|
|
976
|
+
finding_count: 0
|
|
977
|
+
summary: "[summary]"
|
|
978
|
+
- id: 3
|
|
979
|
+
name: "Impossible Travel"
|
|
980
|
+
mitre: "T1078"
|
|
981
|
+
status: "[status]"
|
|
982
|
+
confidence: "[confidence]"
|
|
983
|
+
finding_count: 0
|
|
984
|
+
summary: "[summary]"
|
|
985
|
+
- id: 4
|
|
986
|
+
name: "MFA Fatigue"
|
|
987
|
+
mitre: "T1621"
|
|
988
|
+
status: "[status]"
|
|
989
|
+
confidence: "[confidence]"
|
|
990
|
+
finding_count: 0
|
|
991
|
+
summary: "[summary]"
|
|
992
|
+
- id: 5
|
|
993
|
+
name: "Dormant Account Activation"
|
|
994
|
+
mitre: "T1078"
|
|
995
|
+
status: "[status]"
|
|
996
|
+
confidence: "[confidence]"
|
|
997
|
+
finding_count: 0
|
|
998
|
+
summary: "[summary]"
|
|
999
|
+
- id: 6
|
|
1000
|
+
name: "Cross-IdP Anomaly"
|
|
1001
|
+
mitre: "T1078"
|
|
1002
|
+
status: "[status]"
|
|
1003
|
+
confidence: "[confidence]"
|
|
1004
|
+
finding_count: 0
|
|
1005
|
+
summary: "[summary]"
|
|
1006
|
+
- id: 7
|
|
1007
|
+
name: "Privilege Escalation"
|
|
1008
|
+
mitre: "T1098"
|
|
1009
|
+
status: "[status]"
|
|
1010
|
+
confidence: "[confidence]"
|
|
1011
|
+
finding_count: 0
|
|
1012
|
+
summary: "[summary]"
|
|
1013
|
+
- id: 8
|
|
1014
|
+
name: "Service Account Abuse"
|
|
1015
|
+
mitre: "T1078.001"
|
|
1016
|
+
status: "[status]"
|
|
1017
|
+
confidence: "[confidence]"
|
|
1018
|
+
finding_count: 0
|
|
1019
|
+
summary: "[summary]"
|
|
1020
|
+
data_gaps:
|
|
1021
|
+
- pattern: "[pattern name]"
|
|
1022
|
+
impact: "[description]"
|
|
1023
|
+
remediation: "[steps]"
|
|
1024
|
+
priority: "[CRITICAL/HIGH/MEDIUM/LOW]"
|
|
1025
|
+
cross_pattern_correlations:
|
|
1026
|
+
- accounts: ["[username]"]
|
|
1027
|
+
patterns: [1, 5]
|
|
1028
|
+
description: "[what was found]"
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
### Artifact Structure
|
|
1032
|
+
|
|
1033
|
+
```
|
|
1034
|
+
docs/investigations/YYYY-MM-DD-itdr-assessment/
|
|
1035
|
+
identity-data-map.md <- Phase 1 output
|
|
1036
|
+
queries.md <- Every query (all phases)
|
|
1037
|
+
iocs.md <- IOCs discovered (if any)
|
|
1038
|
+
scorecard.md <- Machine-parseable scorecard (YAML)
|
|
1039
|
+
report.md <- Full report (displayed inline + saved)
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
Create the directory at Phase 1 entry. Write artifacts at each phase exit using the Write tool (never Bash/echo). The `review.md` file is written by the `senior-analyst-review` skill when invoked at Phase 3, Step 6 — the ITDR orchestrator does not write it directly.
|
|
1043
|
+
|
|
1044
|
+
---
|
|
1045
|
+
|
|
1046
|
+
## Escalation Rules
|
|
1047
|
+
|
|
1048
|
+
### Active Threat Hard Gate
|
|
1049
|
+
|
|
1050
|
+
If ANY pattern classifies a finding as ACTIVE THREAT at any point during the sweep:
|
|
1051
|
+
|
|
1052
|
+
1. **STOP the pattern sweep immediately.** Do not continue to the next pattern.
|
|
1053
|
+
2. Package the evidence gathered so far — all queries, findings, IOCs.
|
|
1054
|
+
3. Present the analyst with three options:
|
|
1055
|
+
|
|
1056
|
+
```
|
|
1057
|
+
ACTIVE THREAT DETECTED — Pattern [N]: [Pattern Name]
|
|
1058
|
+
[Description of the finding]
|
|
1059
|
+
Evidence: [key data points]
|
|
1060
|
+
|
|
1061
|
+
Options:
|
|
1062
|
+
1. "escalate" — Hand off to alert-investigation for formal incident triage
|
|
1063
|
+
2. "continue" — Continue the ITDR sweep (threat documented, analyst accepts risk)
|
|
1064
|
+
3. "wrap up" — Stop the sweep, produce report with current findings
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
4. Wait for analyst decision. This is the ONE place where the skill pauses for input.
|
|
1068
|
+
|
|
1069
|
+
### Senior Analyst Review Triggers
|
|
1070
|
+
|
|
1071
|
+
Senior analyst review is now a **mandatory hard gate** in Phase 3, Step 6. See that section for the full trigger conditions and invocation instructions. It is NOT optional when ACTIVE THREAT findings exist.
|
|
1072
|
+
|
|
1073
|
+
### Circuit Breaker
|
|
1074
|
+
|
|
1075
|
+
- **25-minute checkpoint:** Pause and report progress. How many patterns complete? How many remaining? Any findings so far?
|
|
1076
|
+
- **45-minute hard pause:** If the sweep has been running for 45 minutes, stop and present status:
|
|
1077
|
+
|
|
1078
|
+
```
|
|
1079
|
+
ITDR SWEEP PAUSED — Circuit breaker at [time] minutes
|
|
1080
|
+
Patterns complete: [N]/8
|
|
1081
|
+
Patterns remaining: [list]
|
|
1082
|
+
Findings so far: [summary]
|
|
1083
|
+
|
|
1084
|
+
Options:
|
|
1085
|
+
"continue" — extend the sweep to complete remaining patterns
|
|
1086
|
+
"wrap up" — produce report with current findings (incomplete noted)
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
---
|
|
1090
|
+
|
|
1091
|
+
## Error Handling
|
|
1092
|
+
|
|
1093
|
+
### Pattern Status Taxonomy
|
|
1094
|
+
|
|
1095
|
+
Every pattern MUST be assigned one of these 7 statuses:
|
|
1096
|
+
|
|
1097
|
+
| Status | Meaning |
|
|
1098
|
+
|--------|---------|
|
|
1099
|
+
| ACTIVE THREAT | Confirmed malicious activity detected |
|
|
1100
|
+
| SUSPICIOUS | Anomalous behavior that warrants investigation but is not confirmed malicious |
|
|
1101
|
+
| CLEAN | Pattern tested, no evidence of the attack. Confidence level attached (HIGH = good data, MEDIUM = limited data, LOW = minimal data) |
|
|
1102
|
+
| DATA GAP | Required data not available. Specific remediation documented. |
|
|
1103
|
+
| ERROR | Query failed after retry. Error logged, pattern not tested. |
|
|
1104
|
+
| INCOMPLETE | Pattern partially tested — some queries succeeded, some failed or timed out |
|
|
1105
|
+
| NOT APPLICABLE | Pattern cannot apply to this environment (e.g., Cross-IdP with only one IdP) |
|
|
1106
|
+
|
|
1107
|
+
### Query Error Handling
|
|
1108
|
+
|
|
1109
|
+
When a query fails:
|
|
1110
|
+
1. Log the error in `queries.md` with the full error message
|
|
1111
|
+
2. Run `Search_FSQL_SCHEMA` to verify field paths
|
|
1112
|
+
3. Retry with corrected field paths
|
|
1113
|
+
4. If retry also fails, mark the pattern as ERROR and move to the next pattern
|
|
1114
|
+
|
|
1115
|
+
### Empty Result Handling
|
|
1116
|
+
|
|
1117
|
+
When a query returns 0 records:
|
|
1118
|
+
- **Static schema connector:** CLEAN with HIGH confidence — the data source is reliable, empty means no matching events
|
|
1119
|
+
- **Dynamic schema connector:** CLEAN with MEDIUM confidence — add caveat that empty results on a dynamic connector may mean data is mapped differently. Document possible explanations:
|
|
1120
|
+
- Data genuinely does not exist
|
|
1121
|
+
- Data exists but mapped to a different OCSF path
|
|
1122
|
+
- Time range or filter excluded records
|
|
1123
|
+
|
|
1124
|
+
### Specialist Invocation Failure
|
|
1125
|
+
|
|
1126
|
+
If a specialist skill fails to return results:
|
|
1127
|
+
1. Log the failure in `queries.md`
|
|
1128
|
+
2. Continue the sweep — do not block on specialist failure
|
|
1129
|
+
3. Note in the report that specialist analysis was attempted but unavailable
|
|
1130
|
+
|
|
1131
|
+
### No Identity Connectors
|
|
1132
|
+
|
|
1133
|
+
If Phase 1 discovers zero identity connectors, do not proceed to Phase 2. Present a clear message (see Phase 1 failure mode). This is NOT an error — it is a legitimate finding about the customer's mesh configuration.
|
|
1134
|
+
|
|
1135
|
+
---
|
|
1136
|
+
|
|
1137
|
+
## Hard Gates
|
|
1138
|
+
|
|
1139
|
+
| Gate | Phase | Required Artifacts | If Missing |
|
|
1140
|
+
|------|-------|--------------------|------------|
|
|
1141
|
+
| 1 | Phase 1 Exit | `identity-data-map.md` | STOP. Write the data map. Do NOT proceed to Phase 2. |
|
|
1142
|
+
| 2 | Phase 2 Exit | `queries.md` with entries for all 8 patterns | STOP. Complete missing pattern queries or document why skipped. |
|
|
1143
|
+
| 3 | Phase 2 Exit | `iocs.md` (if IOCs found) | STOP. Write IOC file before Phase 3. |
|
|
1144
|
+
| 4 | Phase 3 Exit | `report.md`, `scorecard.md` | STOP. Generate the report and scorecard. Do NOT present without artifacts saved. |
|
|
1145
|
+
| 5 | Phase 3 Exit | `review.md` (if ACTIVE THREAT) | STOP. Invoke senior-analyst-review before presenting. Do NOT skip this on ACTIVE THREAT findings. |
|
|
1146
|
+
|
|
1147
|
+
---
|
|
1148
|
+
|
|
1149
|
+
## Red Flags
|
|
1150
|
+
|
|
1151
|
+
| Red Flag | Correct Action |
|
|
1152
|
+
|----------|---------------|
|
|
1153
|
+
| "Only 3 patterns detected threats, skip the other 5" | STOP. Every pattern runs. Every gap is documented. The Iron Law is non-negotiable. |
|
|
1154
|
+
| "No data for this pattern, marking as CLEAN" | STOP. No data is DATA GAP, not CLEAN. These are fundamentally different statuses with different implications. |
|
|
1155
|
+
| "This is just a service account, skip it" | STOP. Service account compromise is often MORE severe than user compromise. Run Pattern 8 fully. |
|
|
1156
|
+
| "Only one IdP in the mesh, skip Cross-IdP" | Mark as NOT APPLICABLE, not skipped. Document why and note the gap in cross-IdP visibility. |
|
|
1157
|
+
| "The spray volume is low, probably not an attack" | STOP. Low-volume sprays are intentionally designed to evade thresholds. Complete the analysis. Apply the thresholds. |
|
|
1158
|
+
| Starting Phase 2 without the identity data map | STOP. Go back to Phase 1. The data map determines which patterns are viable. Without it, you are guessing. |
|
|
1159
|
+
| Using `**` on authentication queries across all IdPs | STOP. Authentication events across multiple IdPs will overflow. Use specific field selectors. |
|
|
1160
|
+
| Processing MCP results with Bash/python/jq | STOP. You are an LLM. Parse JSON natively. Re-read Absolute Rule 1. |
|
|
1161
|
+
| Skipping specialist invocation for SUSPICIOUS findings | STOP. Absolute Rule 6 requires specialist invocation. The identity-investigator and threat-intel-enricher exist for a reason. |
|
|
1162
|
+
| Not logging a query because it returned empty | STOP. Every query is logged — success, failure, or empty. Absolute Rule 5. |
|
|
1163
|
+
| "INCIDENT DECLARED" in the report | STOP. Use "RECOMMEND ESCALATION" or "Proposed Disposition." The worker recommends — humans declare incidents. |
|
|
1164
|
+
| Presenting findings without saving artifact files | STOP. Hard Gate 4 requires saved artifacts. Write the files first, then present inline. |
|
|
1165
|
+
| Marking a pattern CLEAN when query returned an error | STOP. Error ≠ Clean. Use the ERROR status. Errors mean data may exist but couldn't be accessed. |
|
|
1166
|
+
| Marking CLEAN on a dynamic connector without a confidence caveat | STOP. Dynamic connectors have customer-defined mappings. Empty results could mean the data is mapped differently. Mark CLEAN with MEDIUM confidence and document the caveat. |
|
|
1167
|
+
| Not querying all available IdPs for each pattern | STOP. Every pattern runs against every identity source in the data map. A pattern only tested against Entra but not Okta is incomplete. |
|
|
1168
|
+
| Producing the report without an attack narrative for findings | STOP. The attack narrative is the differentiator. If there are findings, there must be a chronological timeline. This is what no other ITDR tool produces. |
|
|
1169
|
+
| Generating the scorecard without confidence levels | STOP. Confidence tells the analyst how much to trust each result. HIGH for static connectors, MEDIUM for dynamic, N/A for DATA GAP. Always include it. |
|
|
1170
|
+
| Finding a compromised account but not checking what they did next | STOP. Run the cross-domain pivot. Identity is the starting point, not the endpoint. Follow the attack chain into proxy, network, process, and cloud API logs. |
|
|
1171
|
+
| Documenting "Identity Investigation: Not invoked" or "Threat Intel: Not invoked" in the report | STOP. Absolute Rule 6 makes specialist invocation mandatory on SUSPICIOUS and ACTIVE THREAT findings. "Not invoked" means you skipped a required step. Go back and invoke them. |
|
|
1172
|
+
| Analyzing only aggregate data without drilling into specific actors | STOP. Aggregate analysis finds patterns; targeted actor search finds the attackers. After identifying suspicious IPs, extract the specific users and investigate each one. |
|
|
1173
|
+
| Skipping the cross-domain pivot because "this is an identity assessment" | STOP. The cross-domain pivot is what makes this assessment valuable. Identity compromise → what happened next? That's the question only Query can answer across the mesh. |
|
|
1174
|
+
| Completing the assessment without invoking senior-analyst-review on ACTIVE THREAT | STOP. Phase 3 Step 6 is a mandatory hard gate. The review catches blind spots before the analyst acts on the report. |
|
|
1175
|
+
| Marking a pattern CLEAN because `src_endpoint.ip` is empty, without checking `device.ip` | STOP. Absolute Rule 9 requires IP field resolution in Phase 1. If you skipped it, go back and check both paths. Empty `src_endpoint.ip` does NOT mean no IP data exists. |
|
|
1176
|
+
| Pattern 8: "No service accounts found in user_inventory, marking CLEAN" | STOP. User_inventory is only one source. You MUST also search authentication data directly for svc/service/sa- naming patterns across ALL IdPs. Service accounts authenticate even when they're not in user_inventory. |
|
|
1177
|
+
| Pattern 5: "Domain mismatch, marking CLEAN" | STOP. Domain mismatch does NOT block Pattern 5. The auth-as-baseline approach (Step 1) works entirely within authentication data and does not need user_inventory. Run it. |
|
|
1178
|
+
| Using `src_endpoint.ip` in queries when Phase 1 determined that `device.ip` is the populated field | STOP. Use the verified IP path from Phase 1. Using the wrong path returns empty results and leads to false CLEAN classifications. |
|