# Securin Asset Triage

> Use this skill when the user asks to "find assets where...", "list my assets matching...", "how many assets...", "break down assets by...", "aggregate assets by type/criticality/workspace/cloud provider", "show asset distribution", "search my asset inventory", or any ad-hoc asset search / filter / aggregation against the Securin Platform. Requires the Securin Platform MCP server.

- Skill: `securin-public/securin-asset-triage` (Agent Skill, multi-file: 20 files)
- Install (CLI): `npx skillmds@latest add securin-public/securin-asset-triage`
- Raw SKILL.md: https://api.skillmd.com/api/skills/securin-public/securin-asset-triage/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: securin-public (https://skillmd.com/u/securin-public)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/securin-public/securin-asset-triage

---


# Asset Triage

## Purpose

Ad-hoc asset search, filtering, and aggregation. Translate natural-language questions about the user's asset inventory ("show me exposed-to-internet prod assets with critical exposures") into the correct Securin MCP `search*Data` / `aggregate*Data` call, with proper account scoping and deep links back to the platform.

## When to use

- "Find all Linux servers in `prod-cloud` workspace"
- "How many assets do I have by cloud provider?"
- "Break down assets by criticality and workspace"
- "Show assets discovered in the last 30 days"
- "List credentialed vs non-credentialed scan coverage"
- "Which assets are exposed-to-internet AND have open critical exposures?" *(compound filter)*

## Pre-flight

### Step 0 — Account preflight (CC-1)

See [_shared/account-preflight.md](references/_shared/account-preflight.md). Resolve the account-id(s), validate access, and hold them for the rest of the turn. If the question implies a workspace subset ("prod", "EU BU"), also resolve workspace-ids via `getEffectiveAccessWorkspaces`.

Before using this skill, read every file in the [references folder](references/), including the shared [references/_shared/](references/_shared/) docs. **Prefer the cached field catalogs** ([source-fields.md](references/_shared/source-fields.md) for source mode, [composite-fields.md](references/_shared/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.

### Step 0.5 — Detect composite vs source data model (critical)

See [_shared/composite-vs-source.md](references/_shared/composite-vs-source.md). Call `getAccountSettings(account-id, settings: ["COMPOSITE_ASSET_LIST_VIEW"])` — `"true"` means composite, anything else means source. This determines:

- Tool to call: `assetQuery` (composite) or `searchAssetData` (source).
- Field prefix: `compositeAsset.*` vs `asset.*`.

Cache the flag for the turn. **Picking the wrong model returns empty results with no error.**

## Suggested tools

### Primary (pick one pair based on data model)

**Composite accounts:**
- `assetQuery` — flat list and single-field bucketed count

**Source accounts:**
- `searchAssetData` — flat list
- `aggregateAssetData` — single-field bucketed count

> There is no compound-filter + aggregation tool. For "filtered list **and** bucket counts", run the search and the aggregate as two sequential calls and combine client-side.

### Supporting
- `getApiFields` with `entityType: ["ASSET"]` — field discovery when unsure of the field name
- `getGroupByFields` — valid aggregation dimensions
- `getTopValues` — enum-like value discovery for a field (build `in [...]` sets)
- `getAccountSettings` / `getAccountPreferences` — composite FF detection
- `getEffectiveAccessWorkspaces` / `getWorkspacesByAccountId` — workspace scoping
- `validateFilter` — FQL syntax validation

### Deep links (CC-2)
- `createDeepLink` (preferred) — build a URL from entity type + filter (call once per list/aggregation, plus once per bucket if you need per-bucket links)
- `getDeepLink` — URL for a known assetId

See [_shared/deep-links.md](references/_shared/deep-links.md).

## Workflow

### Step 1 — Understand the ask

Classify the question into one of:

| Shape | Example | Tool |
|---|---|---|
| Flat list | "Show me all assets where X" | `search*Data` / `assetQuery` |
| Bucketed count (one group-by) | "How many assets by type?" | `aggregate*Data` /`assetQuery` |
| Compound filter + aggregation | "Exposed-to-internet AND critical, grouped by workspace" | `search*Data` **and** `aggregate*Data` (two calls; combine client-side) **or** `assetQuery` in case of composite mode |
| Aggregation + deep links per bucket | "Bucketed counts I can click into" | `createDeepLink` |

### Step 2 — Discover fields if uncertain

If you're unsure of a field path or acceptable value:

```text
getApiFields(entityType=["ASSET"])
getTopValues(field="asset.mappedAttributes.cloudProperties.provider")   # or compositeAsset equivalent
getGroupByFields(entityType="ASSET")
```

### Step 3 — Compose FQL

Follow [_shared/fql-grammar.md](references/_shared/fql-grammar.md). Critical rules:

- Use the correct prefix (`asset.*` or `compositeAsset.*`) based on Step 0.5.
- Compound filters use parentheses and explicit `AND`/`OR`.
- Bare field names on LHS, single-quoted string values on RHS. `asset.criticality` is **numeric** — compare with `>=`, `=`, `<` using integers (e.g., `asset.criticality >= 4`).

Optional sanity check: `validateFilter` before firing the actual query.

### Step 4 — Pick and call the right tool

Apply the Step 1 classification. For compound filters + aggregation, fire `search*Data` and `aggregate*Data` with the same `filters` string — the search returns the row list, the aggregate returns the bucket counts.

Use `*Query` in case of composite mode. 

### Step 5 — Deep link every result (CC-2)

- For each list: one `createDeepLink` for the filtered Assets view.
- For aggregations: one `createDeepLink` per bucket, with the bucket's value narrowed into the filter (e.g. add `AND <field> = '<bucket-value>'`).
- For a single asset drill-down: `getDeepLink(assetId)`.

### Step 6 — Emit response

```markdown
**Query:** <plain-English restatement>
**Account:** <account name + id> (composite / source)
**Filter:** `<FQL>`

<table of results with a "View in Platform" column>

[View full list in Platform](<top-level deep link>)
```

## Tool-selection cheat sheet

| Filter shape | No aggs | With aggs |
|---|---|---|
| `asset.status = 'active'` (single) | `searchAssetData` | `aggregateAssetData` |
| `asset.reachability = 'Exposed' AND asset.criticality >= 4` (compound, numeric criticality) | `searchAssetData` | `searchAssetData` + `aggregateAssetData` (two calls, same filter) |

For composite accounts there's no separate `searchCompositeAssetData` / `aggregateCompositeAssetData` — use the single `assetQuery` tool with `compositeAsset.*` prefixes; it returns the row list and bucket counts in one call.

## Common recipes

### "Asset distribution by business unit / workspace"

```text
# Source mode
aggregateAssetData
aggs: [{ name: "byWorkspace", function: { type: "TERMS", field: "asset.workspaces.name", size: 25 } }]

# Composite mode
assetQuery
aggs: [{ name: "byWorkspace", function: { type: "TERMS", field: "compositeAsset.workspaces.name", size: 25 } }]
```

Enrich workspace-ids → names via `getWorkspacesByAccountId`.

### "Exposed-to-internet prod assets with open critical exposures"

There are no `asset.exposure.*` rollup fields — the asset record does not carry exposure counts/severity. Run a two-step pattern: query EXPOSURE first, then ASSET (use composite variants if the account is composite):

```text
# 1) Find exposures matching the criteria, collect assetIds
searchExposureData
filters: exposure.status = 'Open'
         AND exposure.scores.scoreLevel = 'Critical'
fields: ["exposure.assetId"]

# 2) Pull the asset records for those ids, scoped to prod workspaces and exposed-to-internet
#    NOTE: FQL list literals use parentheses, not square brackets.
searchAssetData
filters: asset.assetId in ('<id1>','<id2>',...)
         AND asset.reachability = 'Exposed'
         AND asset.workspaces.id in (<prod-ws-id-1>, <prod-ws-id-2>)

# 3) Bucket counts on the same asset filter (e.g., by asset type).
#    aggs entries are {name, function: {type, field, size?}}.
#    `function` is an object with a `type` discriminator (TERMS for
#    bucketing, COUNT/SUM/MIN/MAX/AVG for metrics, DATE_HISTOGRAM for
#    time series). String-form `function: "TERMS"` is rejected.
aggregateAssetData
filters: <same as step 2>
aggs: [{ name: "by_type", function: { type: "TERMS", field: "asset.assetType", size: 25 } }]
```

### "Assets discovered in last 30 days"

```text
searchAssetData
filter: "asset.firstDiscoveredOn" >= "<ISO-8601 30 days ago>"
sort: "asset.firstDiscoveredOn:desc"
```

### "Credentialed vs non-credentialed scan coverage"

```text
aggregateAssetData
aggs: [{
  name: "byCredentialed",
  function: { type: "TERMS", field: "asset.mappedAttributes.isCredentialed", size: 2 }
}]
# or asset.isCredentialedAsset — confirm the active field via getApiFields
```

## Scope guard (CC-3)

- If the user asks about threat actors, ransomware, or CVE-level intel → hand off to `securin-cve-enrichment` or `securin-threat-correlation`.
- If the user asks about specific exposure records (as opposed to assets with exposures) → hand off to `securin-exposure-triage`.
- If no skill fits and the user needs a capability we don't cover → fall back to the platform's built-in `Securin__search_tools` meta-tool to find the right MCP tool by description.

## Edge cases

- **Zero results** — double-check the composite/source routing (most common cause) before reporting empty.
- **Very large result sets** — paginate. Ask the user if they want aggregate instead of listing.
- **Field name ambiguity** — always confirm via `getApiFields`. Don't guess field paths across accounts; custom attributes differ.
- **Workspace access mismatch** — if the user asks about a workspace they can't see, `getEffectiveAccessWorkspaces` will omit it; surface the gap.

## 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](references/_shared/brand.md). Offer customization after delivery; never default to a different brand.

## References

- [Asset Fields Quickref](references/asset-fields.md) — common asset fields + aggregation dimensions
- [Shared: Account Preflight](references/_shared/account-preflight.md)
- [Shared: Composite vs Source](references/_shared/composite-vs-source.md)
- [Shared: Deep Links](references/_shared/deep-links.md)
- [Shared: FQL Grammar](references/_shared/fql-grammar.md)
- [Shared: Sorting Rules](references/_shared/sorting-rules.md)
- [Shared: Brand & Visual Communication](references/_shared/brand.md)

