Apex Azure Kusto
jonathan-vella/apex
ANALYSIS SKILL — Query and analyze data in Azure Data Explorer (Kusto/ADX) using KQL.
A skill your agent uses when asked to write, create, or help with KQL (Kusto Query Language) queries for Microsoft Sentinel, Defender XDR, or Azure Data Explorer.
$ npx skills add SCStelz/security-investigator --skill kql-query-authoring -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install SCStelz/security-investigator kql-query-authoring --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.github/skills/kql-query-authoring .claude/skills/kql-query-authoring && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "kql-query-authoring" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/kql-query-authoring into .claude/skills/kql-query-authoring/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql-query-authoring", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/SCStelz/security-investigator/tree/main/.github/skills/kql-query-authoringType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add SCStelz/security-investigator --skill kql-query-authoring -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install SCStelz/security-investigator kql-query-authoring --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.github/skills/kql-query-authoring .agents/skills/kql-query-authoring && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "kql-query-authoring" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/kql-query-authoring into .agents/skills/kql-query-authoring/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql-query-authoring", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SCStelz/security-investigator --skill kql-query-authoring -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install SCStelz/security-investigator kql-query-authoring --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.github/skills/kql-query-authoring .cursor/skills/kql-query-authoring && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "kql-query-authoring" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/kql-query-authoring into .cursor/skills/kql-query-authoring/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql-query-authoring", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/SCStelz/security-investigator.git --path .github/skills/kql-query-authoring--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add SCStelz/security-investigator --skill kql-query-authoring -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install SCStelz/security-investigator kql-query-authoring --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.github/skills/kql-query-authoring .gemini/skills/kql-query-authoring && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "kql-query-authoring" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/kql-query-authoring into .gemini/skills/kql-query-authoring/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql-query-authoring", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install SCStelz/security-investigator kql-query-authoringInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add SCStelz/security-investigator --skill kql-query-authoring -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .github/skills && cp -r skills-src/.github/skills/kql-query-authoring .github/skills/kql-query-authoring && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "kql-query-authoring" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/kql-query-authoring into .github/skills/kql-query-authoring/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql-query-authoring", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SCStelz/security-investigator --skill kql-query-authoring -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install SCStelz/security-investigator kql-query-authoring --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.github/skills/kql-query-authoring .opencode/skills/kql-query-authoring && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "kql-query-authoring" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/kql-query-authoring into .opencode/skills/kql-query-authoring/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kql-query-authoring", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
kql-query-authoringA skill your agent uses when asked to write, create, or help with KQL (Kusto Query Language) queries for Microsoft Sentinel, Defender XDR, or Azure Data Explorer.
Kql Query Authoring is an agent skill from SCStelz/security-investigator. Use this skill when asked to write, create, or help with KQL (Kusto Query Language) queries for Microsoft Sentinel, Defender XDR, or Azure Data Explorer. Triggers on keywords like "write KQL", "create KQL query", "help with KQL", "query [table]", "KQL for [scenario]", or when a user requests queries for specific data analysis scenarios. This skill uses schema validation, Microsoft Learn documentation, and community examples to generate production-ready KQL queries.
Its SKILL.md is about 5.7k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Data & Analytics, covering Forms and validation and Data analysis. It works with Microsoft Sentinel, Microsoft Defender, Microsoft Azure and Model Context Protocol. The repository describes itself as: Automated security investigation tool using Microsoft MCP Servers, GitHub Copilot, Python Modules and custom copilot-instructions. The licence is MIT.
8 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 51e1385. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
pythonnpmFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
npmjs.comgithub.comlearn.microsoft.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Kql Query Authoring loads about 5.7k tokens when it runs. Until then it costs about 122 tokens; SKILL.md has 2,133 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from SCStelz/security-investigator at commit 51e1385, republished under its MIT licence (© SCStelz). 2,133 words, ~5,749 tokens.
.claude/skills/kql-query-authoring/SKILL.md (or your agent's skills folder).Generate validated, production-ready KQL queries by combining schema validation (331+ indexed tables), Microsoft Learn documentation, community examples, and performance best practices.
Required MCP Servers:
KQL Search MCP Server — Schema validation, query examples, table discovery
npm install -g kql-search-mcp (npm)Microsoft Docs MCP Server — Official Microsoft Learn documentation and code samples
Verification: Tools should be available as mcp_kql-search_* and mcp_microsoft-lea_*.
search_favorite_repos Bug (v1.0.5)❌ Broken — ERROR_TYPE_QUERY_PARSING_FATAL. Use mcp_kql-search_search_github_examples_fallback instead.
Validate table schema FIRST — mcp_kql-search_get_table_schema to verify table exists, column names, and data types.
Check platform schema — Sentinel uses TimeGenerated; Defender XDR uses Timestamp. Microsoft Learn examples default to XDR syntax — always convert before testing on Sentinel.
Check local query library FIRST — Use the discovery manifest (.github/manifests/discovery-manifest.yaml) for domain/MITRE lookups and grep_search for table-name/keyword lookups. See the KQL Pre-Flight Checklist in copilot-instructions.md for the full priority order.
Query file structure: NO placeholder TOC — When creating a new query file, do NOT add a ## Quick Reference — Query Index heading or placeholder. scripts/generate_tocs.py creates the heading and table itself. Pre-creating it confuses the strip-and-reinsert logic and produces duplicated content. See ## Creating Query Files below for full file structure rules.
Use multiple sources — Schema (authoritative column names) + Microsoft Learn (official patterns) + community queries (real-world examples).
Test using the correct execution tool — Follow the Tool Selection Rule in copilot-instructions.md:
Test queries before presenting to user — Run with | take 5 via live execution. Use mcp_kql-search_validate_kql_query as fallback if live testing unavailable.
Provide context — Explain what the query does, expected results, and any limitations.
Read the complete workflow below before starting.
📋 Inherited rules: This skill inherits the KQL Pre-Flight Checklist, Tool Selection Rule (Data Lake vs Advanced Hunting), and Known Table Pitfalls from
copilot-instructions.md. Those rules are authoritative — do not contradict them here.
Extract key information:
EntraIdSignInEvents, EmailEvents, SecurityAlert)Custom Detection Intent Detection:
If the user mentions "custom detection", "detection rule", "deploy as detection", "CD rule", "author detections for", or "deploy to Defender":
.github/skills/detection-authoring/SKILL.md) — Critical Rules and CD Metadata Contract sectionsTimeGenerated, DeviceName, ReportId), no bare summarizecd-metadata blocks in the output file (see Step 8)let variables, 7d lookback) — adaptation to CD format happens at deployment time via the detection-authoring skillSearch for existing verified queries before writing from scratch. Use two complementary methods:
.github/manifests/discovery-manifest.yaml and match by domain tag (e.g., identity, endpoint, email) or MITRE technique ID (e.g., T1078, T1566). Best when you know the security domain or ATT&CK technique.grep_search (table/keyword): grep_search for the specific table name (e.g., CloudAppEvents, OfficeActivity) or operation keyword (e.g., New-InboxRule, SecretGet) scoped to queries/** and .github/skills/**. The manifest lacks table-name and keyword fields — grep fills this gap.copilot-instructions.mdWhen to use which: Domain/technique known → manifest first. Table name/operation known → grep first. Both can be used together — manifest for breadth, grep for precision.
If a suitable query is found, adapt it and skip to Step 6. These queries encode known pitfalls and schema quirks.
mcp_kql-search_get_table_schema("<table_name>")Returns: category, description, all columns with data types, and example queries. Use this to verify column names and understand data types.
mcp_microsoft-lea_microsoft_code_sample_search(
query: "<table_name> <scenario description>",
language: "kusto"
)Include table name + scenario in the query (e.g., "EmailEvents phishing detection").
mcp_kql-search_search_github_examples_fallback(
table_name: "<table_name>",
description: "<goal description>"
)Also available: mcp_kql-search_search_kql_repositories to find KQL-focused repos.
Combine insights: schema for column names, Learn for patterns, community for techniques.
Standalone queries rule: When generating MULTIPLE separate queries, each must start directly with the table name — never use shared let variables across separate queries (they run independently). Use let variables only within a single complex query.
Test queries against live data before presenting to the user.
Timestamp → TimeGenerated if adapting MS Learn examples for Sentinelmcp_sentinel-data_query_lake or RunAdvancedHuntingQuery with | take 5Common errors:
| Error | Fix |
|---|---|
Failed to resolve column 'Timestamp' | Use TimeGenerated (Sentinel) |
Failed to resolve column 'TimeGenerated' | Use Timestamp (XDR AH) |
Table not found | Verify with get_table_schema; try the other execution tool |
expected string expression | Add tostring() after mv-expand or parse_json |
| Query timeout / too many results | Add datetime filter + take or summarize |
Fallback validation: mcp_kql-search_validate_kql_query("<query>") — syntax/schema check only, no live data.
Single query: Provide directly in chat with brief explanation and expected results.
Multiple queries (3+): Create a markdown file in queries/<subfolder>/ with the standardized metadata header. This header is mandatory — build_manifest.py parses it to index the file for discovery by threat-pulse and other skills.
File naming: queries/<subfolder>/<topic>.md — e.g., queries/email/email_threat_detection.md
Required metadata header template (first 10 lines of every query file):
# <Descriptive Title>
**Created:** YYYY-MM-DD
**Platform:** Microsoft Sentinel | Microsoft Defender XDR | Both
**Tables:** <comma-separated exact KQL table names>
**Keywords:** <comma-separated searchable terms — attack techniques, scenarios, field names>
**MITRE:** <comma-separated technique IDs, e.g., T1098.001, T1136.003, TA0008>
**Domains:** <comma-separated domain tags from the valid set below>
**Timeframe:** Last N days (configurable) Valid domain tags: incidents, identity, spn, endpoint, email, admin, cloud, exposure
| Field | Purpose | Parsed By |
|---|---|---|
Tables: | Exact KQL table names for grep_search discovery | build_manifest.py (full manifest) |
Keywords: | Searchable terms for attack scenarios, operations, field names | build_manifest.py (full manifest) |
MITRE: | ATT&CK technique/tactic IDs for cross-referencing | build_manifest.py (slim + full) |
Domains: | Domain tags for threat-pulse cross-referencing | build_manifest.py (slim + full) — missing = validation error |
After creating a new query file: Run python .github/manifests/build_manifest.py to regenerate the discovery manifest, then run python scripts/generate_tocs.py to auto-generate the Quick Reference TOC. The validator will flag any missing required fields.
Subfolder selection: Place files in the subfolder matching the primary data source: identity/, endpoint/, email/, network/, cloud/.
Include per-query documentation with Purpose, Thresholds, Expected Results, and Tuning guidance.
Heading format for TOC compatibility: The generate_tocs.py script auto-generates a Quick Reference TOC by scanning ### and ## Query headings that have a KQL code block within 40 lines. To ensure clean TOC output:
### Query N: <Title> or ## Query N: <Title> for query headings — the number prefix ensures proper TOC ordering## heading (e.g., ## Queries, ## Part A:, ## Hunts) immediately before the first ### Query N: if the file has preamble content (Overview, Table Selection, etc.). The TOC generator uses a --- → ## heading pair as its insertion anchor — without it, the script inserts the TOC at the bottom of the file.### Deployment, ### Tuning, ### References) — these are automatically filtered out by the TOC generator## Quick Reference — Query Index heading or placeholder yourself — the script creates the heading and table. Pre-existing placeholders cause duplicated content and a broken file structure. (This is also enforced as Critical Rule #4 above.)### headings for non-query content that contains a KQL code block within 40 lines — the TOC generator uses KQL proximity to detect query headings and will incorrectly include themInvestigation shortcuts (optional): Query files can include an **Investigation shortcuts:** bulleted list between the ## Quick Reference heading and the TOC table. These document recommended query combos for common investigation scenarios (e.g., "Delivered phishing drill-down: Q2.4 + Q7.6 + Q3.3"). Shortcuts are preserved by generate_tocs.py across re-runs. Don't add them to new files — they're a refinement added after real investigations reveal which query combos work best together.
When CD intent is detected (Step 1), each query MUST include a <!-- cd-metadata --> HTML comment block. The full schema is in .github/skills/detection-authoring/SKILL.md under CD Metadata Contract.
Valid cd-metadata fields (exhaustive list):
| Field | Required | Notes |
|---|---|---|
cd_ready | Always | true or false |
schedule | If cd_ready | "0" (NRT), "1H", "3H", "12H", "24H" |
category | If cd_ready | MITRE tactic (e.g., Persistence, CredentialAccess) |
title | Optional | Dynamic title with {{Column}} placeholders (max 3 unique columns across title + description) |
impactedAssets | If cd_ready | Array of type + identifier pairs |
recommendedActions | Optional | Triage and response guidance string |
adaptation_notes | Optional | What needs to change for CD format |
⛔ responseActions is NOT a valid cd-metadata field. It shares a name with the Graph API field that is explicitly prohibited in LLM-authored detections ("responseActions": [] is mandatory). Do not include it. Put incident response guidance in recommendedActions instead.
<!-- cd-metadata
cd_ready: true
schedule: "1H"
category: "Persistence"
title: "Suspicious Scheduled Task on {{DeviceName}}"
impactedAssets:
- type: device
identifier: DeviceName
recommendedActions: "Investigate the task XML and decode any encoded payloads."
adaptation_notes: "Remove let blocks, add mandatory columns"
-->For queries not suitable for CD (baseline/statistical):
<!-- cd-metadata
cd_ready: false
adaptation_notes: "Statistical baseline — requires bare summarize, not CD-compatible"
-->Summary table: Include a CD column in the Implementation Priority table: ✅ 1H / ❌.
| Tool | Purpose |
|---|---|
mcp_kql-search_get_table_schema | Get table columns, types, example queries (Step 3) |
mcp_microsoft-lea_microsoft_code_sample_search | Official MS Learn KQL samples — use language: "kusto" (Step 4) |
mcp_kql-search_search_github_examples_fallback | Community KQL examples by table name (Step 5) |
mcp_kql-search_search_kql_repositories | Find GitHub repos with KQL collections |
mcp_kql-search_validate_kql_query | Syntax/schema validation (fallback for Step 7) |
mcp_kql-search_find_column | Find which tables contain a specific column |
mcp_kql-search_generate_kql_query | Auto-generate schema-validated query from natural language |
mcp_sentinel-data_query_lake | Execute KQL against live Sentinel (primary validation) |
mcp_sentinel-data_search_tables | Discover tables using natural language |
| Platform | Timestamp Column | Notes |
|---|---|---|
| Sentinel / Log Analytics | TimeGenerated | All ingested logs |
| Defender XDR (Advanced Hunting) | Timestamp | XDR-native tables only; Sentinel tables in AH still use TimeGenerated |
Other common differences: Identity/UserPrincipalName (Sentinel) vs AccountUpn/AccountName (XDR); IPAddress (Sentinel) vs RemoteIP/LocalIP (XDR). Always verify with get_table_schema.
Sign-in queries are the most common query type. Use this decision rule:
| Scenario | Table | Key Differences |
|---|---|---|
| AH query, ≤30d | EntraIdSignInEvents (single table) | Covers both interactive + non-interactive. ErrorCode (int), AccountUpn, Country/City (direct strings), LogonType (JSON array — use has), Timestamp |
| Data Lake / >30d | SigninLogs + AADNonInteractiveUserSignInLogs (union) | ResultType (string), UserPrincipalName, parse_json(LocationDetails) needed for geo, IsInteractive (bool), TimeGenerated |
Common mistakes:
union SigninLogs, AADNonInteractiveUserSignInLogs in AH queries — unnecessary, EntraIdSignInEvents covers bothLogonType == "nonInteractiveUser" — values are JSON arrays (["nonInteractiveUser"]), use hasResultType on EntraIdSignInEvents — column is ErrorCode (int), not stringFull details: See
copilot-instructions.md→ Known Table Pitfalls →EntraIdSignInEvents (AH table preference rule)for complete column mapping and additional pitfalls.
Full table pitfalls (dynamic field parsing, immutable fields, table casing, deprecated tables) are documented in
copilot-instructions.mdunder Known Table Pitfalls. Refer there forSecurityAlert.Status,AuditLogs.InitiatedBy,SigninLogs.DeviceDetail, and 20+ other table-specific gotchas.
Reference: KQL Best Practices — Microsoft Learn
The most important optimization. Datetime predicates use efficient index-based shard elimination, skipping entire data partitions without scanning.
// ✅ Correct — datetime first, then selective string filters
SigninLogs
| where TimeGenerated > ago(7d)
| where UserPrincipalName =~ "user@domain.com"
// ❌ Wrong — string filter before datetime
SigninLogs
| where UserPrincipalName =~ "user@domain.com"
| where TimeGenerated > ago(7d)has over contains for token matchinghas uses the term index for full-token lookup. contains scans every character — dramatically slower on large tables.
// ✅ Faster — term-level index lookup
| where UserPrincipalName has "admin"
// ❌ Slower — full substring scan
| where UserPrincipalName contains "admin"Use contains only when you genuinely need substring matching (e.g., fragments inside URL paths).
Case-sensitive comparisons (==, in, has_cs) are faster than case-insensitive (=~, in~, has). Use case-insensitive only when casing is unpredictable.
// ✅ Faster — ActionType, Operation, OfficeWorkload have consistent casing
| where ActionType == "LogonFailed"
| where Operation in ("New-InboxRule", "Set-InboxRule")
| where OfficeWorkload == "Exchange"
// 🔵 Use =~ only when casing varies (e.g., user-entered UPNs)
| where UserPrincipalName =~ "user@domain.com"Common fields with consistent casing (always use == / in): ActionType, Operation, OfficeWorkload, EventID, ResultType, DeliveryAction, EmailDirection, LogonType, Severity, Status, Classification.
Pre-filter both sides of a join to reduce data volume. Move where clauses into subqueries.
// ✅ Correct — filter KB table before joining
DeviceTvmSoftwareVulnerabilities
| join kind=inner (
DeviceTvmSoftwareVulnerabilitiesKB
| where IsExploitAvailable == true
| where CvssScore >= 8.0
) on CveId
// ❌ Wrong — joins full tables, filters after
DeviceTvmSoftwareVulnerabilities
| join kind=inner DeviceTvmSoftwareVulnerabilitiesKB on CveId
| where IsExploitAvailable == trueJoin sizing rules:
hint.strategy=broadcast when left is small)in instead of left semi join for single-column filteringlookup instead of join when right side is small (<50 MB)hint.shufflekey=<key> when both sides are large with high-cardinality join keymaterialize() for multi-referenced let statementsWithout materialize(), the engine may recompute the let expression each time it's referenced.
// ✅ Computed once, reused twice
let SprayFailures = materialize(
EntraIdSignInEvents
| where Timestamp > ago(7d)
| where ErrorCode in (50126, 50053, 50057)
| summarize FailedAttempts = count(), TargetUsers = dcount(AccountUpn)
by SourceIP = IPAddress
| where TargetUsers >= 5);arg_max to only needed columnsarg_max(TimeGenerated, *) materializes every column. Specify only what you use.
// ✅ Only 5 columns materialized
SecurityAlert
| where TimeGenerated > ago(30d)
| summarize arg_max(TimeGenerated, Entities, Tactics, Techniques, AlertName, AlertSeverity) by SystemAlertId
// ❌ Materializes all 30+ columns
SecurityAlert
| summarize arg_max(TimeGenerated, *) by SystemAlertIdFor rare key/value lookups in dynamic columns, use has to eliminate rows before expensive parse_json().
// ✅ Term filter first, JSON parse on survivors
AuditLogs
| where tostring(TargetResources) has "MyApp"
| extend Target = tostring(parse_json(tostring(TargetResources[0])).displayName)
| where Target == "MyApp"Filtering on native columns enables index usage; calculated columns force full scans.
// ✅ Filter on native column
SecurityEvent | where EventID == 4625
// ❌ Filter on calculated column
SecurityEvent | extend Cat = case(EventID == 4625, "Fail", ...) | where Cat == "Fail"Drop unnecessary columns before expensive operators (join, summarize, mv-expand) to reduce memory and shuffling.
take or summarize to limit resultsUnbounded queries on large tables consume excessive resources.
In AH, AuditLogs.InitiatedBy and TargetResources are native dynamic — use direct dot-notation. In Data Lake, they may be string-typed requiring parse_json().
// ✅ Advanced Hunting — direct access
| extend Actor = tostring(InitiatedBy.user.userPrincipalName)
// ✅ Data Lake — parse_json wrapper
| extend Actor = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
// 🔵 Safe in both — stringify full field
| where tostring(InitiatedBy) has "user@domain.com"strcat(substring(UPN, 0, 3), "***") when appropriatelet SuspiciousIPs = ... not let x = ...let variables across queries the user will run independentlyCommon "expected string expression" error: After mv-expand, parse_json, or split, values are dynamic — string functions fail. Always convert first:
// After mv-expand
| mv-expand AuthDetails
| extend AuthMethod = tostring(AuthDetails.authenticationMethod)
// After split
| extend Parts = split(UPN, "@")
| extend Domain = tostring(Parts[1])Rule of thumb: If you get "expected string expression", add tostring().
© SCStelz, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .github/skills/kql-query-authoring of SCStelz/security-investigator.
Open the folder on GitHubat commit 51e1385
Kql Query Authoring next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Kql Query Authoring this skillSCStelz/security-investigator | 249 | — | ~5.7k | Automated safety check: Pass | MIT | |
| Apex Azure Kustojonathan-vella/apex | 217 | — | ~984 | Automated safety check: Pass | MIT | |
| Kqlmicrosoft/fabric-rti-mcp | 131 | — | ~6.2k | Automated safety check: Pass | MIT | |
| Azure Kustomicrosoft/GitHub-Copilot-for-Azure | 255 | 1 repos | ~2.1k | Automated safety check: Pass | MIT | |
| Kqlmicrosoft/skills | 3.1k | — | ~4.7k | Automated safety check: Pass | MIT | |
| Posthog Product Health Auditboardsesh/boardsesh | 164 | — | ~2.1k | Automated safety check: Pass | Apache-2.0 |
jonathan-vella/apex
ANALYSIS SKILL — Query and analyze data in Azure Data Explorer (Kusto/ADX) using KQL.
microsoft/fabric-rti-mcp
KQL language expertise for writing correct, efficient Kusto queries using the Fabric RTI MCP tools.
microsoft/GitHub-Copilot-for-Azure
Query and analyze data in Azure Data Explorer (Kusto/ADX) using KQL for log analytics, telemetry, and time series analysis.
microsoft/skills
KQL language expertise for writing correct, efficient Kusto Query Language queries.
boardsesh/boardsesh
Mine Boardsesh's PostHog telemetry (error tracking, session recordings, product analytics) with a multi-agent workflow, then file verified, deduplicated, severity-labelled GitHub issues.
awslabs/cli-agent-orchestrator
Author and run CAO Python workflow scripts — multi-step, parameterized, fan-out orchestrations executed by cao workflow run.
SCStelz/security-investigator
A skill your agent uses when asked to investigate Conditional Access policy changes, sign-in failures related to CA policies (error codes 53000, 50074, 530032), or suspected policy…
SCStelz/security-investigator
Weekly review of an investigation tenant-context memory file against the most recent SOC scan reports (e.g.
SCStelz/security-investigator
A skill your agent uses when asked to create heatmaps, visualize patterns over time, show activity grids, or display aggregated data in a matrix format.
SCStelz/security-investigator
Report/investigate RUNTIME ACTIVITY of AI agents (Agent 365 / Copilot Studio / M365 Copilot / Work IQ) — agents used, tools/connectors, channels, tokens, prompt/reply content, and Prompt Shield…
SCStelz/security-investigator
Audit or report on AI agent security posture across Copilot Studio, Microsoft 365 Copilot, Microsoft Foundry, and third-party agents.
SCStelz/security-investigator
Audit Entra ID app registration and service principal security posture.
A skill your agent uses when asked to write, create, or help with KQL (Kusto Query Language) queries for Microsoft Sentinel, Defender XDR, or Azure Data Explorer. Kql Query Authoring is an agent skill from SCStelz/security-investigator. Use this skill when asked to write, create, or help with KQL (Kusto Query Language) queries for Microsoft Sentinel, Defender XDR, or Azure Data Explorer.
Kql Query Authoring fits situations like: help with KQL (Kusto Query Language) queries for Microsoft Sentinel; azure Data Explorer; keywords like write KQL; create KQL query.
Run `npx skills add SCStelz/security-investigator --skill kql-query-authoring -a claude-code`. Or copy the skill folder (.github/skills/kql-query-authoring in SCStelz/security-investigator) into .claude/skills/kql-query-authoring in your project. Claude Code loads it when a task matches its description.
Run `npx skills add SCStelz/security-investigator --skill kql-query-authoring -a codex`. Or copy the skill folder (.github/skills/kql-query-authoring in SCStelz/security-investigator) into .agents/skills/kql-query-authoring in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add SCStelz/security-investigator --skill kql-query-authoring -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/kql-query-authoring, .gemini/skills/kql-query-authoring, .github/skills/kql-query-authoring and .opencode/skills/kql-query-authoring in your project.
Going by SKILL.md and its folder, Kql Query Authoring needs the command-line tools its instructions call (python and npm). Our summary lists: Python 3; Node.js.
SKILL.md names 3 domains. As links in the text: npmjs.com, github.com and learn.microsoft.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Kql Query Authoring is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.7k tokens (SKILL.md is roughly 23k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Kql Query Authoring: Apex Azure Kusto (jonathan-vella/apex, 217 stars), Kql (microsoft/fabric-rti-mcp, 131 stars), Azure Kusto (microsoft/GitHub-Copilot-for-Azure, 255 stars) and Kql (microsoft/skills, 3.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
SCStelz (a GitHub user) maintains it in SCStelz/security-investigator, which has 249 GitHub stars. The repository holds 22 skills in this directory. The repository was last updated on October 8, 2026.
Source: SCStelz/security-investigator on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.