tracecathq/tracecat/detection-event-case-lifecycle

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Load when the prompt starts with "Detection event <alert_id>" followed by the alert as JSON. Takes one detection event from the SIEM (in either the API shape or the webhook shape) to a triaged Tracecat case and a published Slack alert. You read the alert from the prompt, dedupe it, attach it to a case or create one, and stop when the alert is already published or a person has already decided. Otherwise you investigate the underlying events in the SIEM, write the case, post or refresh the Work Object card, the brief, the evidence table and the owner ask, and keep the slack_work_objects row current. Load with business-context (when that skill exists), hypothesis-driven-triage, aws-cloud-incident-response-core and case-output.

Install

npx vskill@latest install tracecathq/tracecat/detection-event-case-lifecycle

SKILL.md

Detection event case lifecycle

You own a detection event from the moment it reaches you until the team can act on it in Slack. A detection event is one alert raised by a detection rule in the SIEM. The only deterministic steps before you are the receiver, which stores each alert in the detection_events table, and Triage alerts, which reads the row and starts one run of you per alert. Every decision after that is yours, and so is every write: the case, the tags, the Slack card, the brief, the evidence table, the owner ask and the table row that answers the card's details panel.

Work through the stages below in order. Each stage says when to stop. When you stop, return one plain line for the run record saying what you did, for example CASE-0001 created, Open, card, brief and owner ask posted. or Alert <id> already published on CASE-0001; nothing to do.

Fixed values

NameValue
Workspace case URLhttps://<your-tracecat-host>/workspaces/<workspace-id>/cases/<case_id>
Triage channel#security-alerts, C0123456789
Work Object tableslack_work_objects
Alert linkthe alert's resultLink when it has one, else <siem-alert-url> built from the alert id
Time format in anything a reader sees17 Sep 2026, 03:47 UTC

AWS calls, when you need one, use tools.aws_boto3.call_api with service_name, method_name, params and region_name. The configured credential already lands in your organisation's security audit account through <your-read-only-audit-role>, so leave role_arn empty. Call only read methods (get_*, list_*, describe_*).

1. Read the alert

The prompt is Detection event <alert_id> on the first line, then the alert as one JSON object. Nothing needs fetching: the JSON is the alert. It arrives in one of two shapes. Tell them apart by the top-level detection key. The field names below are one SIEM's alert format: <adapt to your SIEM>.

ValueAPI shape (no detection key)Webhook shape (alert under detection)
alert ididdetection.id
detection namedetectionNamedetection.name
detection titlethe detection namedetection.displayName
what the rule detectsnot carried; use the name and categoriesdetection.description
rule iddetectionIDdetection.detectionID
alert severityseveritydetection.severity, with detection.riskScore
raised atcreatedAtdetection.executedAt
categories, techniquescategories, mitreAttacksdetection.categories, detection.mitreAttacks, detection.mitreTechniques
rowsresults is a JSON string holding one row as an array. Parse it and pair it with columnNames by position to get a row object. recordsReturned is the row countdetection.results is a list of row objects; detection.resultCount is the count
query windownot carrieddetection.params.from and detection.params.to
actor, source addressactor (id, email, username), srcIPfrom the rows
linkbuild it from the alert iddetection.resultLink
run idscheduledRunIDnot carried

A column name can repeat in columnNames (for example eventName twice). Keep the first value for each name.

The field mapping in this stage is one example. For an alert from another source (a cloud threat finding, an identity alert, an endpoint alert), map that payload to the same case fields. Leave a field empty when the payload does not have it, and say so in the case.

Read these from the alert and its row and keep them for every later stage. Column names differ by rule, so take the first that is present, and write not recorded when none is:

ValueWhere it comes from
id, detection name, alert severity, raised atthe table above
accountrow recipientAccountId, else userIdentity.accountId, else an accountId or account column
regionrow awsRegion, else region, else the first entry of a regions column
principalrow userIdentity.arn, else actor.id, else userIdentity.principalId, else a username, user or email column
resourcethe first present of the row's request target columns (requestParameters.roleName, requestParameters.bucketName, requestParameters.groupId, requestParameters.instanceId, requestParameters.policyArn), else a resource column, else not recorded
first seen, last seenrow first_event and last_event, else eventTime for both, else the query window, else raised at for both
countthe row's own count column when it has one (creation_count, count, events), else the row count
indicatorsrcIP, else row sourceIPAddress, when it is a public address and not an AWS service name; a domain column when the rule has one

The row is the starting point, not the evidence. The events behind it are in the SIEM. Stage 5 reads them through the SIEM MCP over the alert's own window.

A GuardDuty finding relayed through the SIEM. When the detection is a GuardDuty finding passed along (the name or categories say guardduty, or the row carries a finding id or a finding ARN), take the finding id, region and detector id from the row and read the finding in full with guardduty get_findings, as guardduty-case-lifecycle stage 1 describes. Use it as evidence in stage 5. The case identity, tags and Slack messages stay as this skill defines them.

2. Name the group

The detection name is the family. It feeds the group tag, and in hypothesis-driven-triage's hypothesis-library.md you choose the family whose questions are closest to what the rule detects (a policy or permission change reads as config-change, use of credentials as credential-use, a command in a cluster as k8s-operator); when none fits, use generic.

The group key is <detection name>-<account>. With no account, use the principal's last path segment in lower case, with every character outside a-z, 0-9 and - replaced by -, cut to 40 characters. With neither, the group key is the detection name alone.

3. Find the case

  1. Same alert. Call core.cases.search_cases with tags ["alert-<id>"], status ["new", "in_progress", "on_hold"], start_time 90 days ago, order_by created_at, sort desc, limit 1. It is a hit when the returned case carries that tag.
  2. Already published. An alert does not change after it is raised. On a same-alert hit, call core.table.lookup on slack_work_objects with column external_ref_id and the case id. With a row, the alert is triaged and published. Return Alert <id> already published on <short_id>; nothing to do. and stop. With no row, an earlier run did not finish: continue on that case.
  3. Same group. With no alert hit, search tags ["group-<group key>"] with the same statuses, start_time 24 hours ago, newest first, limit 1. It is a hit when the case carries that tag and carries neither owner-confirmed nor owner-denied.
  4. Attach on either hit. Call core.cases.update_case on that case. Set these fields:
  • finding_ids: the existing list, with this alert id appended if absent. The field is labelled Finding IDs; for a detection event it holds alert ids.
  • first_seen: keep the existing value; set it only when empty.
  • finding_count, finding_type (the detection name), last_seen, principal, region, resource: from this alert. Leave out any that is not recorded.
  • payload.finding_summary: {source: "<siem>", detection_id, account, count, finding_ids, first_seen, last_seen, region, resource_id, severity, type}, where type is the detection name and severity the alert severity.
  1. Create when neither search hit. Call core.cases.create_case with:
  • summary Triage in progress: <detection title>
  • description Triage in progress.
  • status new, severity low
  • create_missing_tags true
  • tags detection-event, source-<siem>, detection-<detection name>, group-<group key>, alert-<id>, and account-<account> when the row gives an account
  • fields aws_account (only when the account is an AWS account id), finding_count, finding_ids ([<id>]), finding_type, first_seen, last_seen, principal, region, resource, leaving out any that is not recorded
  • the same payload.finding_summary

Keep the case id, its short id (CASE-0001), its URL, and whether you created it or attached to it.

4. Stop if a person has already decided

An attached case counts as decided when either is true:

  • it carries owner-confirmed or owner-denied;
  • its disposition field is Inconclusive.

On a decided case, add one comment with core.cases.create_comment and stop:

Alert <id> (<detection name>) was raised again with <count> events, last seen <last seen, reader format>, and was attached. A person has already decided this case (<the decided tags and disposition, comma separated>), so the description, disposition and severity were left unchanged and no new triage ran.

Write 1 event when the count is 1.

5. Investigate

Load business-context if you have that skill, then hypothesis-driven-triage and aws-cloud-incident-response-core, and follow the method there. Those skills are written around GuardDuty findings. Read "finding" as "alert", "finding type" as "detection name", and where they say to fetch the finding with boto3, read the alert you were given and the events behind it in the SIEM instead. Build the investigation record that aws-cloud-incident-response-core defines. Gather these facts yourself:

  • Every alert on the case. This alert is in the prompt. For each other id in finding_ids, the facts recorded in the case description and payload.finding_summary are what you have; say so where they limit a claim. Triage the case, not only this alert.
  • The events behind the alert. With the SIEM MCP, read the source records the rule matched, for the alert's principal and resource, from 15 minutes before first seen to 15 minutes after last seen. Establish what was done, whether it succeeded, from where, and with which session. The row tells you which records: an eventSource and eventName mean the account activity records. Then widen to what the same principal did in the hour around it.
  • Report times.
  • Report time is now.
  • The due time is now plus 3 days; the next update is now plus 1 day.
  • The activity is still happening when the last event is within 24 hours of now.
  • The alert's own indicator. Look it up with the threat enrichment tools you have (domain first, else the address), and with an IP reputation tool for an address.
  • A first-seen date within a few minutes of now means the enrichment service had never seen the indicator before this lookup. Zero detections then means no prior record, not a clean verdict.
  • An alert with no domain and no public address has no external indicator. Say so rather than leaving the field blank.
  • History. Prior cases for the same rule (core.cases.search_cases with tags ["detection-<detection name>"], 90 days) with their outcomes, and how often the same principal did the same thing over 90 days in the SIEM. A rule that fires on the same automated principal every day is a pattern to name, with its count.
  • Shared workload role sessions, only when the actor is a shared workload role. Some organisations run many tenants or jobs through one shared role that assumes other roles on their behalf. business-context names that role and its session naming pattern when your organisation has one. When the principal is that role or a session it assumed, query the SIEM for the role's cross-account AssumeRole sessions from 2 minutes before first seen to 2 minutes after last seen, grouped by tenant session prefix and role. The query below shows the logic in one SQL dialect; table, column and function names are <adapt to your SIEM>:

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/tracecathq/tracecat/detection-event-case-lifecycle/badge)](https://verified-skill.com/skills/tracecathq/tracecat/detection-event-case-lifecycle)

Something wrong with this skill or its page? Tell us.