.github/skills/ioc-investigation/SKILL.md
Use this skill when asked to investigate an Indicator of Compromise (IoC) such as an IP address, DNS domain, URL, or file hash. Triggers on keywords like "investigate IP", "check domain", "IoC investigation", "threat intel", "is this malicious", "suspicious URL", or when an IP/domain/URL/hash is mentioned with investigation context. This skill provides comprehensive IoC analysis using Microsoft Defender Threat Intelligence, Sentinel Threat Intel tables, Advanced Hunting, organizational exposure assessment, CVE correlation, and affected device enumeration.
npx skillsauth add scstelz/security-investigator ioc-investigationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill performs comprehensive security investigations on Indicators of Compromise (IoCs) including:
The investigation correlates IoCs with Microsoft Defender Threat Intelligence, identifies associated CVEs, and enumerates organizational assets affected by those vulnerabilities.
Investigation shortcuts:
enrich_ips.py⛔ Shortcut Default Rule: When a matching shortcut exists for the investigation context, use it — don't run the full workflow. Only run the full query set when the user explicitly requests "full investigation", "comprehensive", or "deep dive". Shortcuts render only the report sections relevant to their query chain (plus Executive Summary and Recommendations, always).
Before starting ANY IoC investigation:
create_file for JSON export (NEVER use PowerShell terminal commands)This skill requires a Sentinel workspace to execute queries. Follow these rules STRICTLY:
SELECTED_WORKSPACE_IDS passed from the parent skilllist_sentinel_workspaces MCP tool FIRSTIF query returns "Failed to resolve table" or similar error:
- STOP IMMEDIATELY
- Report: "⚠️ Query failed on workspace [NAME] ([ID]). Error: [ERROR_MESSAGE]"
- Display: "Available workspaces: [LIST_ALL_WORKSPACES]"
- ASK: "Which workspace should I use instead?"
- WAIT for explicit user response
- DO NOT retry with a different workspace automatically
🔴 PROHIBITED ACTIONS:
IoC Type Detection Rules:
| Pattern | IoC Type | Normalization |
|---------|----------|---------------|
| \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} | IPv4 Address | Validate octets ≤255 |
| [a-fA-F0-9:]+ (with multiple colons) | IPv6 Address | Lowercase, expand if needed |
| [a-zA-Z0-9][-a-zA-Z0-9]*\.[a-zA-Z]{2,} | Domain | Lowercase, remove trailing dot |
| https?://.* or starts with www. | URL | Extract domain for separate analysis |
| 32 hex chars | MD5 Hash | Lowercase |
| 40 hex chars | SHA1 Hash | Lowercase |
| 64 hex chars | SHA256 Hash | Lowercase |
Date Range Rules:
datetime(2026-01-16) to datetime(2026-01-25)When to use: Suspicious inbound/outbound connections, firewall alerts, sign-in anomalies
Example prompts:
Data sources:
enrich_ips.py (3rd-party enrichment: ipinfo.io geo/ISP, vpnapi.io VPN/proxy/Tor, AbuseIPDB abuse score & reports, Shodan ports/services/CVEs/tags)When to use: Suspicious DNS queries, phishing domains, C2 communication
Example prompts:
Data sources:
When to use: Phishing links, malicious downloads, suspicious redirects
Example prompts:
Data sources:
When to use: Malware analysis, suspicious executables, file reputation
Example prompts:
Data sources:
When a user requests an IoC investigation:
Identify & Normalize IoC:
- Detect IoC type (IP/Domain/URL/Hash)
- Normalize format (lowercase, validate)
- Extract embedded IoCs (domain from URL)
Run Parallel Queries (Batch 1 - Threat Intel):
Run 3rd-Party IP Enrichment (IP IoCs only):
python enrich_ips.py <IP_ADDRESS>
c2, eol-os, self-signed)Run Parallel Queries (Batch 2 - Activity):
CVE & Vulnerability Correlation:
Export to JSON & Generate Summary:
temp/ioc_investigation_{ioc_normalized}_{timestamp}.json
YOU MUST TRACK AND REPORT TIME AFTER EVERY MAJOR STEP:
[MM:SS] ✓ Step description (XX seconds)
Required Reporting Points:
Step 1.1: Detect IoC Type
# Regex patterns for IoC detection
IPv4: r'^(\d{1,3}\.){3}\d{1,3}$'
IPv6: r'^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$'
Domain: r'^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
URL: r'^https?://'
MD5: r'^[a-fA-F0-9]{32}$'
SHA1: r'^[a-fA-F0-9]{40}$'
SHA256: r'^[a-fA-F0-9]{64}$'
Step 1.2: Normalize IoC
Step 1.3: Create Investigation Context
{
"ioc_type": "ip|domain|url|hash",
"ioc_value": "<normalized_value>",
"ioc_original": "<user_provided_value>",
"extracted_domain": "<if_url>",
"investigation_start": "<timestamp>",
"date_range_start": "<StartDate>",
"date_range_end": "<EndDate>"
}
MANDATORY for all IP address investigations. Run enrich_ips.py to get external threat intelligence context that is NOT available from Defender/Sentinel native tools.
python enrich_ips.py <IP_ADDRESS_1> <IP_ADDRESS_2> ...
What it provides:
| Source | Intelligence |
|--------|--------------|
| ipinfo.io | Geolocation (city, country, coordinates), ISP/ASN, organization, hosting provider detection |
| vpnapi.io | VPN, proxy, Tor exit node, relay detection |
| AbuseIPDB | Abuse confidence score (0-100), total reports, last reported date, recent reporter comments with attack categories |
| Shodan | Open ports, service/banner details, OS detection, known CVEs, tags (e.g., c2, eol-os, self-signed, honeypot), CPEs, hostnames |
Output: Per-IP detailed results printed to terminal + JSON export saved to temp/.
Integration with investigation:
c2: 🔴 Known C2 infrastructure — escalate immediatelyNote: For domain and URL IoCs, extract the resolved IP(s) from DeviceNetworkEvents results and run enrichment on those IPs as a follow-up step.
CRITICAL: Run ALL threat intel queries in parallel for speed!
| Query | Tool/API | IoC Types |
|-------|----------|-----------|
| Defender IOC List | ListDefenderIndicators ⚠️ | IP, Domain, URL |
| Defender IP Alerts | GetDefenderIpAlerts | IP |
| Defender IP Statistics | GetDefenderIpStatistics | IP |
| Defender File Alerts | GetDefenderFileAlerts | Hash |
| Defender File Info | GetDefenderFileInfo | Hash |
| Defender File Statistics | GetDefenderFileStatistics | Hash |
| Defender File Machines | GetDefenderFileRelatedMachines | Hash |
⚠️ ListDefenderIndicators Note: If result is written to file (>50KB), you MUST read and filter the file manually. See Custom IOC Management for required processing steps.
| Query | Table | IoC Types | |-------|-------|-----------| | TI Indicators Match | ThreatIntelIndicators | All | | Network Connections | DeviceNetworkEvents | IP, Domain, URL | | Alert Evidence | AlertEvidence | All | | Security Alerts | SecurityAlert | All | | Email URLs | EmailUrlInfo | Domain, URL |
Step 4.1: Extract CVE IDs from Threat Intel AND Enrichment
CVE-\d{4}-\d{4,})shodan_vulns field from enrich_ips.py output)Step 4.2: Query Affected Devices per CVE
For each CVE_ID found:
→ ListDefenderMachinesByVulnerability(cveId: CVE_ID)
→ Collect: deviceId, deviceName, osPlatform, exposureLevel
Step 4.3: Aggregate Device Exposure
{
"cve_correlation": {
"cve_ids_found": ["CVE-2024-1234", "CVE-2024-5678"],
"affected_devices_by_cve": {
"CVE-2024-1234": [
{"deviceId": "...", "deviceName": "...", "osPlatform": "..."}
]
},
"total_unique_affected_devices": 15,
"critical_cves": 2,
"high_cves": 3
}
}
For IP Address IoCs:
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let IPAddress = '<IP_ADDRESS>';
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteIP == IPAddress or LocalIP == IPAddress
| summarize
ConnectionCount = count(),
UniqueDevices = dcount(DeviceId),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Ports = make_set(RemotePort),
Protocols = make_set(Protocol)
by ActionType
| order by ConnectionCount desc
For Domain IoCs:
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let Domain = '<DOMAIN>';
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteUrl has Domain
| summarize
ConnectionCount = count(),
UniqueDevices = dcount(DeviceId),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
UniqueURLs = make_set(RemoteUrl, 10)
by DeviceName
| order by ConnectionCount desc
For File Hash IoCs:
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let Hash = '<HASH>';
union withsource=SourceTable DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents, DeviceRegistryEvents, DeviceLogonEvents, DeviceImageLoadEvents, DeviceEvents
| where Timestamp between (start .. end)
| where SHA1 =~ Hash or SHA256 =~ Hash or MD5 =~ Hash or InitiatingProcessSHA256 =~ Hash
| summarize
EventCount = count(),
UniqueDevices = dcount(DeviceId),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
FileNames = make_set(FileName),
FolderPaths = make_set(FolderPath, 5)
by ActionType
| order by EventCount desc
Create single JSON file: temp/ioc_investigation_{ioc_type}_{ioc_normalized}_{timestamp}.json
Use these exact patterns with appropriate MCP tools. Replace <IOC_VALUE>, <StartDate>, <EndDate>.
⚠️ CRITICAL: START WITH THESE EXACT QUERY PATTERNS These queries have been tested and validated. Use them as your PRIMARY reference.
🔴 STEP 0: GET CURRENT DATE FIRST (MANDATORY) 🔴
RULE 1: Real-Time/Recent Searches (Current Activity)
datetime(2026-01-25) as end dateRULE 2: Historical Searches (User-Specified Dates)
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let ioc_value = '<IOC_VALUE>';
ThreatIntelIndicators
| where TimeGenerated between (start .. end)
| where IsActive == true and IsDeleted == false
| summarize arg_max(TimeGenerated, *) by Id
| where ObservableValue =~ ioc_value
or Pattern has ioc_value
| project
TimeGenerated,
Id,
ObservableKey,
ObservableValue,
Pattern,
Confidence,
ValidFrom,
ValidUntil,
Tags,
Data
| order by TimeGenerated desc
| take 20
let target_ip = '<IP_ADDRESS>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteIP == target_ip or LocalIP == target_ip
| extend Direction = iff(RemoteIP == target_ip, "Outbound", "Inbound")
| summarize
TotalConnections = count(),
UniqueDevices = dcount(DeviceId),
UniquePorts = dcount(RemotePort),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Devices = make_set(DeviceName, 10),
Ports = make_set(RemotePort, 20),
Protocols = make_set(Protocol),
ActionTypes = make_set(ActionType),
InitiatingProcesses = make_set(InitiatingProcessFileName, 10),
Direction = make_set(Direction,2)
let target_ip = '<IP_ADDRESS>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteIP == target_ip or LocalIP == target_ip
| project
Timestamp,
DeviceName,
DeviceId,
ActionType,
RemoteIP,
RemotePort,
RemoteUrl,
LocalIP,
LocalPort,
Protocol,
InitiatingProcessFileName,
InitiatingProcessCommandLine,
InitiatingProcessAccountName
| order by Timestamp desc
| take 20
let target_domain = '<DOMAIN>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteUrl has target_domain
| summarize
TotalConnections = count(),
UniqueDevices = dcount(DeviceId),
UniqueUsers = dcount(InitiatingProcessAccountName),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Devices = make_set(DeviceName, 10),
URLs = make_set(RemoteUrl, 20),
Ports = make_set(RemotePort),
InitiatingProcesses = make_set(InitiatingProcessFileName, 10)
let target_domain = '<DOMAIN>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
DeviceNetworkEvents
| where Timestamp between (start .. end)
| where RemoteUrl has target_domain
| project
Timestamp,
DeviceName,
InitiatingProcessAccountName,
ActionType,
RemoteUrl,
RemoteIP,
RemotePort,
Protocol,
InitiatingProcessFileName,
InitiatingProcessCommandLine
| order by Timestamp desc
| take 20
let target_url = '<URL>';
let target_domain = '<DOMAIN>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
EmailUrlInfo
| where TimeGenerated between (start .. end)
| where Url == target_url or Url has target_domain or UrlDomain =~ target_domain
| summarize
EmailCount = dcount(NetworkMessageId),
UniqueURLs = make_set(Url, 10),
UrlLocations = make_set(UrlLocation),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by UrlDomain
| order by EmailCount desc
let target_hash = '<HASH>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
union withsource=SourceTable DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents, DeviceRegistryEvents, DeviceLogonEvents, DeviceImageLoadEvents, DeviceEvents
| where Timestamp between (start .. end)
| where SHA1 =~ target_hash or SHA256 =~ target_hash or MD5 =~ target_hash or InitiatingProcessSHA256 =~ target_hash
| summarize
EventCount = count(),
UniqueDevices = dcount(DeviceId),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp),
Devices = make_set(DeviceName, 10),
FileNames = make_set(FileName, 10),
FolderPaths = make_set(FolderPath, 10),
ActionTypes = make_set(ActionType)
| extend HashType = case(
isnotempty(target_hash) and strlen(target_hash) == 32, "MD5",
isnotempty(target_hash) and strlen(target_hash) == 40, "SHA1",
isnotempty(target_hash) and strlen(target_hash) == 64, "SHA256",
"Unknown")
let ioc_value = '<IOC_VALUE>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
AlertEvidence
| where TimeGenerated between (start .. end)
| where RemoteIP == ioc_value
or RemoteUrl has ioc_value
or SHA1 =~ ioc_value
or SHA256 =~ ioc_value
or FileName has ioc_value
or Title has ioc_value
or Categories has ioc_value
| project
TimeGenerated,
AlertId,
Title,
Severity,
Categories,
ServiceSource,
EntityType,
EvidenceRole,
RemoteIP,
RemoteUrl,
FileName,
SHA1,
SHA256,
DeviceName,
AccountName
| order by TimeGenerated desc
| take 20
let ioc_value = '<IOC_VALUE>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
AlertEvidence
| where TimeGenerated between (start .. end)
| where RemoteIP == ioc_value
or RemoteUrl has ioc_value
or SHA1 =~ ioc_value
or SHA256 =~ ioc_value
or FileName has ioc_value
or Title has ioc_value
or Categories has ioc_value
| join AlertInfo on AlertId
| extend HostFullName = strcat(parse_json(parse_json(AdditionalFields).Host).HostName,".", parse_json(parse_json(AdditionalFields).Host).DnsDomain)
| extend OS = strcat(parse_json(parse_json(AdditionalFields).Host).OSFamily," ", parse_json(parse_json(AdditionalFields).Host).OSVersion)
| extend IsDomainJoined = parse_json(parse_json(AdditionalFields).Host).IsDomainJoined
| extend AffectedDevice = strcat(HostFullName,",", OS, ",IsDomainJoined: ", IsDomainJoined)
| summarize
AlertCount = dcount(AlertId),
Alerts = make_set(Title, 10),
Severities = make_set(Severity),
Categories = make_set(Category),
AttackTechniques = make_set(AttackTechniques),
AffectedDevices = make_set(AffectedDevice, 10)
// Use Defender API: ListDefenderIndicators with filters
// indicatorType: "IpAddress" | "DomainName" | "Url" | "FileSha1" | "FileSha256" | "FileMd5"
// indicatorValue: "<IOC_VALUE>"
let target_ip = '<IP_ADDRESS>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
union isfuzzy=true SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated between (start .. end)
| where IPAddress == target_ip
| summarize
SignInCount = count(),
UniqueUsers = dcount(UserPrincipalName),
SuccessCount = countif(ResultType == '0'),
FailureCount = countif(ResultType != '0'),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
Users = make_set(UserPrincipalName, 10),
Apps = make_set(AppDisplayName, 10),
ResultTypes = make_set(ResultType)
| extend SuccessRate = round(100.0 * SuccessCount / SignInCount, 2)
let ioc_value = '<IOC_VALUE>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
AlertEvidence
| where TimeGenerated between (start .. end)
| where RemoteIP == ioc_value
or RemoteUrl has ioc_value
or SHA1 =~ ioc_value
or SHA256 =~ ioc_value
or FileName has ioc_value
or Title has ioc_value
or Categories has ioc_value
| extend CVEs = extract_all(@"(CVE-\d{4}-\d{4,})", tostring(AttackTechniques))
| mv-expand CVE = CVEs
| where isnotempty(CVE)
| summarize
CVECount = dcount(tostring(CVE)),
CVEs = make_set(tostring(CVE)),
AlertCount = dcount(AlertId),
Alerts = make_set(Title, 5)
Get Alerts for IP:
Tool: GetDefenderIpAlerts (MCP)
Parameter: ipAddress = "<IP_ADDRESS>"
Returns: All security alerts associated with the IP
Get IP Statistics:
Tool: activate_file_and_ip_statistics_tools → GetDefenderIpStatistics
Parameter: ipAddress = "<IP_ADDRESS>"
Returns: Organization prevalence, device count, communication stats
Find Devices by IP:
Tool: FindDefenderMachinesByIp (MCP)
Parameters:
ipAddress = "<IP_ADDRESS>"
timestamp = "<DATETIME>" (ISO 8601 format)
Returns: Devices that communicated with IP ±15 minutes of timestamp
Get File Info:
Tool: activate_file_and_ip_statistics_tools → GetDefenderFileInfo
Parameter: fileHash = "<SHA1_OR_SHA256>"
Returns: File details, global prevalence, threat determination
Get File Statistics:
Tool: activate_file_and_ip_statistics_tools → GetDefenderFileStatistics
Parameter: fileHash = "<SHA1_OR_SHA256>"
Returns: Organization statistics, device count, global stats
Get File Alerts:
Tool: GetDefenderFileAlerts (MCP)
Parameter: fileHash = "<SHA1_OR_SHA256>"
Returns: All alerts associated with the file
Get Devices with File:
Tool: GetDefenderFileRelatedMachines (MCP)
Parameter: fileHash = "<SHA1_OR_SHA256>"
Returns: All devices where file was observed
List Devices Affected by CVE:
Tool: ListDefenderMachinesByVulnerability (MCP)
Parameter: cveId = "CVE-YYYY-NNNNN"
Returns: All devices vulnerable to the CVE with exposure details
Get Device Vulnerabilities:
Tool: GetDefenderMachineVulnerabilities (MCP)
Parameter: id = "<DEVICE_ID>"
Returns: All CVEs affecting the specific device
Search Existing IOCs:
Tool: ListDefenderIndicators (MCP)
Parameters (all optional):
indicatorType = "IpAddress" | "DomainName" | "Url" | "FileSha1" | "FileSha256" | "FileMd5"
indicatorValue = "<IOC_VALUE>"
action = "Alert" | "Block" | "Allow"
severity = "Informational" | "Low" | "Medium" | "High"
Returns: Matching custom indicators in tenant
⚠️ CRITICAL: Processing Large ListDefenderIndicators Results
The ListDefenderIndicators API may return ALL custom indicators in the tenant regardless of filter parameters. When results are large (>50KB), they are written to a temporary file instead of returned inline.
MANDATORY Processing Steps:
If result says "Large tool result written to file":
read_file tool to read the content file path providedvalue array# Filter logic for IP address
matches = [ind for ind in indicators["value"]
if ind.get("indicatorValue", "").lower() == target_ioc.lower()]
If result is inline JSON with empty value array:
🔴 PROHIBITED:
Example - Correct Processing:
1. Call: ListDefenderIndicators(indicatorType: "IpAddress", indicatorValue: "203.0.113.42")
2. Result: "Large tool result (69KB) written to file: /path/to/content.json"
3. Action: read_file(/path/to/content.json)
4. Parse: Extract value array from JSON
5. Filter: Search for indicatorValue == "203.0.113.42" (case-insensitive)
6. Report: "No custom indicators match 203.0.113.42" OR "Found 1 custom indicator: [details]"
Create file: temp/ioc_investigation_{ioc_type}_{ioc_normalized}_{timestamp}.json
{
"investigation_metadata": {
"ioc_type": "ip|domain|url|hash",
"ioc_value": "<normalized_value>",
"ioc_original": "<user_input>",
"investigation_timestamp": "<ISO8601>",
"date_range_start": "<StartDate>",
"date_range_end": "<EndDate>",
"elapsed_time_seconds": 45
},
"threat_intelligence": {
"sentinel_ti_matches": [],
"defender_ioc_matches": [],
"defender_alerts": [],
"threat_families": [],
"confidence_score": 0-100,
"verdict": "Malicious|Suspicious|Clean|Unknown"
},
"ip_enrichment": {
"geo": { "city": "", "country": "", "org": "", "isp": "" },
"vpn_proxy_tor": { "is_vpn": false, "is_proxy": false, "is_tor": false },
"abuseipdb": { "abuse_confidence_score": 0, "total_reports": 0, "last_reported": "", "recent_categories": [] },
"shodan": { "ports": [], "services": [], "vulns": [], "tags": [], "os": "", "hostnames": [], "cpes": [] }
},
"activity_analysis": {
"network_connections": {
"total_connections": 0,
"unique_devices": 0,
"unique_users": 0,
"first_seen": "<datetime>",
"last_seen": "<datetime>",
"top_devices": [],
"top_ports": [],
"top_processes": []
},
"email_delivery": {
"email_count": 0,
"unique_urls": [],
"delivery_locations": []
},
"file_activity": {
"event_count": 0,
"unique_devices": 0,
"file_names": [],
"folder_paths": [],
"action_types": []
},
"signin_activity": {
"signin_count": 0,
"unique_users": 0,
"success_rate": 0,
"affected_users": []
}
},
"alert_correlation": {
"total_alerts": 0,
"severity_breakdown": {
"high": 0,
"medium": 0,
"low": 0,
"informational": 0
},
"alert_titles": [],
"attack_techniques": [],
"affected_entities": []
},
"cve_correlation": {
"cve_ids_found": [],
"affected_devices_by_cve": {},
"total_unique_affected_devices": 0,
"cve_severity_breakdown": {
"critical": 0,
"high": 0,
"medium": 0,
"low": 0
}
},
"organizational_exposure": {
"total_affected_devices": 0,
"affected_device_list": [],
"exposure_level": "High|Medium|Low|None",
"recommended_actions": []
},
"risk_assessment": {
"overall_risk": "Critical|High|Medium|Low|Informational",
"risk_factors": [],
"mitigating_factors": [],
"confidence": "High|Medium|Low"
}
}
| Issue | Solution |
|-------|----------|
| No TI matches found | IoC may be unknown; proceed with activity analysis |
| Defender API returns 404 | IoC not in organization's scope; check Sentinel data |
| Empty DeviceNetworkEvents | Expand date range or check if MDE is deployed |
| CVE not found in vulnerability DB | CVE may be too new or not applicable to org assets |
| Multiple IoC types detected | Investigate each separately, correlate results |
| Rate limiting on API calls | Add delays between API calls, batch where possible |
| ListDefenderIndicators returns large file | Read file with read_file, parse JSON, manually filter for target IoC value |
If queries return no results, use these defaults:
{
"threat_intelligence": {
"sentinel_ti_matches": [],
"defender_alerts": [],
"verdict": "Unknown",
"confidence_score": 0
},
"activity_analysis": {
"network_connections": {
"total_connections": 0,
"unique_devices": 0
}
},
"cve_correlation": {
"cve_ids_found": [],
"affected_devices_by_cve": {},
"total_unique_affected_devices": 0
}
}
User says: "Investigate IP 203.0.113.42 for the last 7 days"
Workflow:
203.0.113.42python enrich_ips.py 203.0.113.42
→ Get geo, ISP, VPN/proxy/Tor flags, AbuseIPDB score, Shodan ports/CVEs/tagsGetDefenderIpAlerts(ipAddress: "203.0.113.42")ListDefenderIndicators(indicatorType: "IpAddress", indicatorValue: "203.0.113.42")ListDefenderMachinesByVulnerabilityUser says: "Is evil-malware.com in our environment?"
Workflow:
evil-malware.comListDefenderIndicators(indicatorType: "DomainName", indicatorValue: "evil-malware.com")User says: "Investigate SHA256 a1b2c3... and check which devices are vulnerable"
Workflow:
a1b2c3...GetDefenderFileInfo(fileHash: "a1b2c3...")GetDefenderFileAlerts(fileHash: "a1b2c3...")GetDefenderFileStatistics(fileHash: "a1b2c3...")GetDefenderFileRelatedMachines(fileHash: "a1b2c3...")ListDefenderMachinesByVulnerabilityThis skill can be combined with:
Cross-skill pivot example: "Investigate IP 203.0.113.42" → Found in user sign-ins → "Investigate [email protected]" using user-investigation skill
development
Use this skill when asked to investigate a computer, device, endpoint, or machine for security issues, suspicious activity, malware, or compliance review. Triggers on keywords like "investigate computer", "investigate device", "investigate endpoint", "check machine", "device security", "endpoint investigation", or when a device name/hostname is mentioned with investigation context. This skill provides comprehensive device security analysis including Defender alerts, sign-in patterns, logged-on users, vulnerabilities, software inventory, compliance status, network activity, and automated investigation tracking for Entra Joined, Hybrid Joined, and Entra Registered devices.
development
Recommended starting point for new users and daily SOC operations. Quick 15-minute security posture scan across 7 domains: active incidents, identity (human + NonHuman), endpoint, email threats, admin & cloud ops, and exposure. 12 queries executed in parallel batches, producing a prioritized Threat Pulse Dashboard with color-coded verdicts (🔴 Escalate / 🟠 Investigate / 🟡 Monitor / ✅ Clear) and drill-down recommendations pointing to specialized skills. Trigger on getting-started questions like "what can you do", "where do I start", "help me investigate". Supports inline chat and markdown file output
development
Use this skill when asked to investigate a user account for security issues, suspicious activity, or compliance review. Triggers on keywords like "investigate user", "security investigation", "user investigation", "check user activity", "analyze sign-ins", or when a UPN/email is mentioned with investigation context. This skill provides comprehensive Entra ID user security analysis including sign-in anomalies, MFA status, device compliance, audit logs, security incidents, Identity Protection risk, and automated reports (HTML, markdown file, or inline chat).
development
Use this skill when asked to generate SVG data visualization dashboards from investigation data or skill reports. Triggers on keywords like "generate SVG dashboard", "create a visual dashboard", "visualize this report", "SVG from the report", "visualize results", "create SVG chart", "SVG from this data". Supports two modes: manifest-driven structured dashboards (from skill reports with svg-widgets.yaml) and freeform adaptive visualizations from ad-hoc investigation data. Component library includes KPI cards, score cards, bar charts, line charts, donut charts, waterfall charts, tables, recommendation cards, assessment banners. SharePoint Dark Theme default palette.