Threat Correlation
Purpose
Bridge external threat intelligence (CVEs, ransomware families, threat actors, campaigns) with the user's environment (assets, exposures, components). Answer the core question: "Does this threat matter to me right now?"
This skill is the inverse of securin-cve-enrichment: enrichment produces a global report on a CVE; correlation intersects that CVE (or a threat, or an actor's TTPs) with the user's real data.
When to use
- "Am I affected by CVE-2024-3400?"
- "Does this ransomware campaign hit my environment?"
- "Check if we're vulnerable to Lazarus Group"
- "Show me exposures tied to CISA KEV entries"
- "Which of my open exposures are exploited in the wild?"
- "What threats target the CVEs in my environment?" (outbound direction)
Pre-flight
Step 0 — Account preflight (CC-1)
See _shared/account-preflight.md. Correlation queries always touch the user's environment — you must resolve account-id(s) and validate access before any exposure/asset query. Also detect the composite-vs-source data model (see _shared/composite-vs-source.md) to use the correct asset prefix. This should also give you context about when to use *Query tools and when to use search* and aggregate* tools.
Before using this skill, read every file in the references folder, including the shared references/_shared/ docs. Prefer the cached field catalogs (source-fields.md for source mode, composite-fields.md for composite mode) over calling getApiFields — only fall back to the live tool when an entity or field is missing from the cache.
Suggested tools
Threat intel (global)
searchVulnerabilityData— CVE record + exploitation signalssearchThreatActorData— threat actor → CVE list. Actor records are flat — do NOT passfields: ['threatActor'](that prefix doesn't match the actual record shape and the call returns empty rows silently). Omitfields, or pass top-level keys likename,vulnerabilities,associatedGroups.searchWeaknessData— CWE root cause
User environment
- Source mode —
searchExposureData+aggregateExposureData(two calls, same filter when both list and bucket counts are needed). - Composite mode —
exposureQuery(combined search + aggregate,compositeExposure.*prefix). - Asset pivot:
searchAssetData(source) orassetQuery(composite). For source bucket counts pair withaggregateAssetData.
Scoping + access
getEffectiveAccessWorkspacesgetAccountSettings— composite FF checkgetApiFields— field discovery
Deep links (CC-2)
createDeepLink(preferred) — call once per list/aggregation, plus once per bucket if you need per-bucket linksgetDeepLink
Outside
- Web search — resolve named campaigns / news events to CVE lists when the user's input is a name, not a CVE.
Core concept — two-step correlation
Step 1: Threat Intelligence → Extract CVEs / indicators
Step 2: Your Environment → Find matching exposures / assets / components
Result: Threat Exposure Assessment
Two strategies based on direction.
Strategy A — Inbound (Threat → You)
The user starts with a threat and wants to know if they're affected.
A.1 Resolve threat → CVE list
| If the user said… | Do |
|---|---|
CVE-XXXX-YYYY |
Already a CVE — skip to A.2 |
| A threat-actor name (e.g., "Lazarus") | searchThreatActorData with bare-path filter name like 'Lazarus' (omit fields — actor records are flat) → collect the top-level vulnerabilities array (CVE IDs) |
| A ransomware family (e.g., "LockBit") | Use web search — resolve the family to a CVE list via published threat-intel and confirm with the user |
| A campaign / news event | Web search to resolve to CVE list, then confirm with the user before proceeding |
A.2 Query exposures for the CVE set
Run two calls with the same filter — searchExposureData for the row list, aggregateExposureData for the severity breakdown:
# 1) Itemized list
searchExposureData
filter: exposure.mappedAttributes.vulnerabilityIds in (<cve-list>)
AND exposure.status = 'Open'
AND "<account/workspace scope>"
# 2) Severity bucket counts (same filter)
aggregateExposureData
filter: <same as above>
aggs: [{
name: "bySeverity",
function: { type: "TERMS", field: "exposure.scores.scoreLevel", size: 10 }
}]
In case of composite mode use exposureQuery in place of the above mentioned tools for search and aggregate.
A.3 Pivot to affected assets
Using assetIds from the exposures (or a separate join):
# Source mode
searchAssetData
filter: asset.assetId in (<asset-ids from A.2>)
sort: "asset.scores.overallScore:desc"
# Composite mode — single call returns list + buckets
assetQuery
filter: compositeAsset.id in (<ids>)
sort: "compositeAsset.scores.overallScore:desc"
A.4 Generate deep links (CC-2)
- One top-level filter link (Exposures view with the CVE-list filter).
- Per-row link for each affected asset / exposure.
- Per-bucket link in the severity breakdown.
A.5 Emit "Threat Exposure Assessment"
## Threat Exposure Assessment — <threat name or CVE>
**Verdict:** AFFECTED / NOT AFFECTED / PARTIAL — <N matched exposures across M assets>
### Matched CVEs
| CVE | Severity | KEV | # Exposures | Link |
|---|---|---|---|---|
| … | … | … | … | [View](<deep link>) |
### Affected Assets
| Asset | Criticality | Reachability | Workspace | # Matched Exposures | Link |
|---|---|---|---|---|---|
| … | … | … | … | … | [View](<deep link>) |
### Severity Breakdown
- Critical: 3 → [View](…)
- High: 11 → [View](…)
- Medium: 27 → [View](…)
### Recommended next steps
- Top-priority remediation: hand off to **securin-remediation-guidance** for CVE-XXXX
- Triage the full list: hand off to **securin-exposure-triage** for SLA review
Strategy B — Outbound (You → Threats)
The user starts with an asset or exposure and wants to know what threats target them.
B.1 Collect CVEs in the user's scope
aggregateExposureData
filter: exposure.status = 'Open' AND "<scope>"
aggs: [{
name: "byCve",
function: { type: "TERMS", field: "exposure.mappedAttributes.vulnerabilityIds", size: 200 }
}]
B.2 Enrich each CVE with threat signals
For each CVE (or batched):
searchThreatActorData filter: vulnerabilities like 'CVE-X' # bare-path FQL on the actor record; do NOT pass fields:['threatActor']
searchVulnerabilityData filter: vulnerabilityId = 'CVE-X'
B.3 Emit "What Hunts Me" report
## What Hunts Me — <account / workspace scope>
**Top threats targeting your open exposures:**
| Threat / Actor | Type | Your exposed CVEs | # Affected Assets | Link |
|---|---|---|---|---|
| LockBit 3.0 | Ransomware | CVE-…, CVE-… | 14 | [View](…) |
| APT29 | State actor | CVE-…, CVE-… | 3 | [View](…) |
### Recommended next steps
- Deep-dive a specific threat → **securin-cve-enrichment** for the CVE
- Remediation planning → **securin-remediation-guidance**
FQL patterns
See _shared/fql-grammar.md for full grammar. Correlation-specific:
# Exposures matching a CVE set
"exposure.mappedAttributes.vulnerabilityIds" in ('CVE-X','CVE-Y','CVE-Z')
# Exposures on exposed-to-internet assets (source-model)
asset.reachability = 'Exposed'
# Same, composite-model
compositeAsset.reachability = 'Exposed'
# Exposures tied to CISA KEV CVEs (cross-entity to vuln index from exposures)
vulnerabilities.isCisaKEV = true
# In searchVulnerabilityData — bare path, no "vulnerabilities." prefix
vulnerabilityId = 'CVE-X'
isCisaKEV = true
searchThreatActorDatawith nofilters→ an error. Always pass a filter (and OMITfields: ['threatActor']— actor records are flat, so that prefix silently returns empty rows; seecorrelation-patterns.md).- THREATACTOR field namespace is bare (
name,description,vulnerabilityCount,originCountry,targetedCountries,targetedIndustries,associatedGroups) — nothreatActor.prefix. validateFiltervalidates both FQL syntax and field existence — a 400 response means either malformed syntax or an invalid field path. Always cross-check paths against Source data API Fields or Composite data API Fields based on the current mode of execution. UsegetApiFieldsas a fallback when a field is not in the cache.
Scope guard (CC-3)
- Global CVE intel only (no environment match) →
securin-cve-enrichment. - Raw exposure triage (no external threat angle) →
securin-exposure-triage. - Asset inventory only →
securin-asset-triage. - Remediation planning →
securin-remediation-guidance. - Unknown capability → fall back to the platform's built-in
Securin__search_toolsmeta-tool to look up the right MCP tool by description.
Edge cases
- Named campaign doesn't map cleanly to CVEs — ask the user to confirm the CVE list from web search before correlating.
- Zero matches — sanity-check composite vs source prefix (#1 cause of silent empties), verify account/workspace scope, verify the
exposure.statusfilter isn't too narrow. - Threat actor with 200+ CVEs — don't match all 200 blindly. Prioritize by CISA KEV /
exploitedInWildfirst, then correlate. - New zero-day before it's in the platform — web search may find the CVE before it's indexed; tell the user it's not yet in Core and offer to check back later.
Visual output (CC-4)
When this skill produces aggregated or multi-row data (counts, trends, distributions, comparisons, single-CVE reports), emit a chart/graph/infographic in the Securin brand — multi-series palette (#9C66FF / #7F30FF / #E96001 / #4D268D / #DD639C / …, assigned in order), semantic CHML severity colors (Critical #A60D08 → Info #C5CBD6), Poppins headings on DM Sans body, light theme, and the Securin logo. Use the 10-stop brand purple ramp for heatmaps/sequential scales; gradients are background decoration only — never on chart bars, lines, or slices. Full color system in _shared/brand.md. Offer customization after delivery; never default to a different brand.
References
- Correlation Patterns — inbound/outbound recipes for the common ask shapes.
- Shared: Account Preflight
- Shared: Composite vs Source
- Shared: Deep Links
- Shared: FQL Grammar
- Shared: Sorting Rules
- Shared: Brand & Visual Communication