
Incident Response
- 602 installs
- 23.5k repo stars
- Updated July 17, 2026
- alirezarezvani/claude-skills
incident-response is a security operations skill that retrieves regulatory notification deadlines and compliance rules for developers handling security incidents or data breaches under major frameworks.
About
incident-response is an agent skill providing a reference table of regulatory notification deadlines for security incidents and data breaches under major compliance frameworks. The notification clock starts at incident declaration, not investigation completion, and the skill instructs teams to assume the most restrictive applicable deadline when breach scope is unclear at declaration time. Developers reach for incident-response during active breach response when they must document assumptions, meet recipient notification windows, and avoid non-compliance penalties. The deadline summary table covers framework, jurisdiction, incident type, notification deadline, recipient, and penalty columns for quick lookup. It complements broader security suites by focusing narrowly on time-critical regulatory obligations during live incidents.
- Provides a ready-to-use reference table for GDPR, PCI-DSS, and HIPAA notification deadlines
- Includes jurisdiction, incident type, recipient, and exact penalty amounts
- Operational rule: assume most restrictive deadline when breach scope is unclear
- Documents assumptions and resolutions inside the incident record
- Helps prevent multi-million euro or dollar fines by keeping the notification clock visible
Incident Response by the numbers
- 602 all-time installs (skills.sh)
- Ranked #481 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: CRITICAL risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alirezarezvani/claude-skills --skill incident-responseAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 602 |
|---|---|
| repo stars | ★ 23.5k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 17, 2026 |
| Repository | alirezarezvani/claude-skills ↗ |
What are data breach notification deadlines by regulation?
Instantly retrieve regulatory notification deadlines and compliance rules when handling security incidents or data breaches.
Who is it for?
Engineers and responders handling declared security incidents who need fast regulatory deadline lookup.
Skip if: Routine pre-release vulnerability scanning without an active declared incident or breach.
When should I use this skill?
A developer declares a security incident or data breach and needs regulatory notification deadlines and compliance rules immediately.
What you get
Incident record with applicable deadlines, recipient requirements, and documented scope assumptions.
- incident record
- notification deadline checklist
Files
Incident Response
Incident response skill for the full lifecycle from initial triage through forensic collection, severity declaration, and escalation routing. This is NOT threat hunting (see threat-detection) or post-incident compliance mapping (see governance/compliance-mapping) — this is about classifying, triaging, and managing declared security incidents.
---
Table of Contents
- Overview
- Incident Triage Tool
- Incident Classification
- Severity Framework
- False Positive Filtering
- Forensic Evidence Collection
- Escalation Paths
- Regulatory Notification Obligations
- Workflows
- Anti-Patterns
- Cross-References
---
Overview
What This Skill Does
This skill provides the methodology and tooling for incident triage and response — classifying security events into typed incidents, scoring severity, filtering false positives, determining escalation paths, and initiating forensic evidence collection under chain-of-custody controls.
Distinction from Other Security Skills
| Skill | Focus | Approach |
|---|---|---|
| incident-response (this) | Active incidents | Reactive — classify, escalate, collect evidence |
| threat-detection | Pre-incident hunting | Proactive — find threats before alerts fire |
| cloud-security | Cloud posture assessment | Preventive — IAM, S3, network misconfiguration |
| red-team | Offensive simulation | Offensive — test detection and response capability |
Prerequisites
A security event must be ingested before triage. Events can come from SIEM alerts, EDR detections, threat intel feeds, or user reports. The triage tool accepts JSON event payloads; see the input schema below.
---
Incident Triage Tool
The incident_triage.py tool classifies events, checks false positives, scores severity, determines escalation, and performs forensic pre-analysis.
# Classify an event from JSON file
python3 scripts/incident_triage.py --input event.json --classify --json
# Classify with false positive filtering enabled
python3 scripts/incident_triage.py --input event.json --classify --false-positive-check --json
# Force a severity level for tabletop exercises
python3 scripts/incident_triage.py --input event.json --severity sev1 --json
# Read event from stdin
echo '{"event_type": "ransomware", "host": "prod-db-01", "raw_payload": {}}' | \
python3 scripts/incident_triage.py --classify --false-positive-check --jsonInput Event Schema
{
"event_type": "ransomware",
"host": "prod-db-01",
"user": "svc_backup",
"source_ip": "10.1.2.3",
"timestamp": "2024-01-15T14:32:00Z",
"raw_payload": {}
}Exit Codes
| Code | Meaning | Required Response |
|---|---|---|
| 0 | SEV3/SEV4 or clean | Standard ticket-based handling |
| 1 | SEV2 — elevated | 1-hour bridge call, async coordination |
| 2 | SEV1 — critical | Immediate 15-minute war room, all-hands |
---
Incident Classification
Security events are classified into 14 incident types. Classification drives default severity, MITRE technique mapping, and response SLA.
Incident Taxonomy
| Incident Type | Default Severity | MITRE Technique | Response SLA |
|---|---|---|---|
| ransomware | SEV1 | T1486 | 15 minutes |
| data_exfiltration | SEV1 | T1048 | 15 minutes |
| apt_intrusion | SEV1 | T1566 | 15 minutes |
| supply_chain_compromise | SEV1 | T1195 | 15 minutes |
| domain_controller_breach | SEV1 | T1078.002 | 15 minutes |
| credential_compromise | SEV2 | T1110 | 1 hour |
| lateral_movement | SEV2 | T1021 | 1 hour |
| malware_infection | SEV2 | T1204 | 1 hour |
| insider_threat | SEV2 | T1078 | 1 hour |
| cloud_account_compromise | SEV2 | T1078.004 | 1 hour |
| unauthorized_access | SEV3 | T1190 | 4 hours |
| policy_violation | SEV3 | N/A | 4 hours |
| phishing_attempt | SEV4 | T1566.001 | 24 hours |
| security_alert | SEV4 | N/A | 24 hours |
SEV Escalation Triggers
Any of the following automatically re-declare a higher severity:
| Trigger | New Severity |
|---|---|
| Ransomware note found | SEV1 |
| Active exfiltration confirmed | SEV1 |
| CloudTrail or SIEM disabled | SEV1 |
| Domain controller access confirmed | SEV1 |
| Second system compromised | SEV1 |
| Exfiltration volume exceeds 1 GB | SEV2 minimum |
| C-suite account accessed | SEV2 minimum |
---
Severity Framework
SEV Level Matrix
| Level | Name | Criteria | Skills Invoked | Escalation Path |
|---|---|---|---|---|
| SEV1 | Critical | Confirmed ransomware; active PII/PHI exfiltration (>10K records); domain controller breach; defense evasion (CloudTrail disabled); supply chain compromise | All skills (parallel) | SOC Lead → CISO → CEO → Board Chair |
| SEV2 | High | Confirmed unauthorized access to sensitive systems; credential compromise with elevated privileges; lateral movement confirmed; ransomware indicators without confirmed execution | triage + containment + forensics | SOC Lead → CISO |
| SEV3 | Medium | Suspected unauthorized access (unconfirmed); malware detected and contained; single account compromise (no priv escalation) | triage + containment | SOC Lead → Security Manager |
| SEV4 | Low | Security alert with no confirmed impact; informational indicator; policy violation with no data risk | triage only | L3 Analyst queue |
---
False Positive Filtering
The triage tool applies five filters before escalating to prevent false positive inflation.
False Positive Filter Types
| Filter | Description | Example Pattern |
|---|---|---|
| CI/CD agent activity | Known build/deploy agents flagged as anomalies | jenkins, github-actions, circleci, gitlab-runner |
| Test environment tagging | Assets tagged as non-production | test-, staging-, dev-, sandbox- |
| Scheduled job patterns | Expected batch processes triggering alerts | cron, scheduled_task, batch_job, backup_ |
| Whitelisted identities | Explicitly approved service accounts | svc_monitoring, svc_backup, datadog-agent |
| Scanner activity | Known security scanners and vulnerability tools | nessus, qualys, rapid7, aws_inspector |
A confirmed false positive suppresses escalation and logs the suppression reason for audit purposes. Recurring false positives from the same source should be tuned out at the detection layer, not filtered repeatedly at triage.
---
Forensic Evidence Collection
Evidence collection follows the DFRWS six-phase framework and the principle of volatile-first acquisition.
DFRWS Six Phases
| Phase | Activity | Priority |
|---|---|---|
| Identification | Identify what evidence exists and where | Immediate |
| Preservation | Prevent modification — write-block, snapshot, legal hold | Immediate |
| Collection | Acquire evidence in order of volatility | Immediate |
| Examination | Technical analysis of collected evidence | Within 2 hours |
| Analysis | Interpret findings in investigative context | Within 4 hours |
| Presentation | Produce findings report with chain of custody | Before incident closure |
Volatile Evidence — Collect First
1. Live memory (RAM dump) — lost on reboot 2. Running processes and open network connections (netstat, ps) 3. Logged-in users and active sessions 4. System uptime and current time (for timeline anchoring) 5. Environment variables and loaded kernel modules
Chain of Custody Requirements
Every evidence item must be recorded with:
- SHA-256 hash at acquisition time
- Acquisition timestamp in UTC with timezone offset
- Tool provenance (FTK Imager, Volatility, dd, AWS CloudTrail export)
- Investigator identity
- Transfer log (who had custody and when)
---
Escalation Paths
By Severity
| Severity | Immediate Contact | Bridge Call | External Notification |
|---|---|---|---|
| SEV1 | SOC Lead + CISO (15 min) | Immediate war room | Legal + PR standby; regulatory notification per deadline table |
| SEV2 | SOC Lead (30 min async) | 1-hour bridge | Legal notification if PII involved |
| SEV3 | Security Manager (4 hours) | Async only | None unless scope expands |
| SEV4 | L3 Analyst queue (24 hours) | None | None |
By Incident Type
| Incident Type | Primary Escalation | Secondary |
|---|---|---|
| Ransomware / APT | CISO + CEO | Board if data at risk |
| PII/PHI breach | Legal + CISO | Regulatory body (per deadline table) |
| Cloud account compromise | Cloud security team | CISO |
| Insider threat | HR + Legal + CISO | Law enforcement if criminal |
| Supply chain | CISO + Vendor management | Board |
---
Regulatory Notification Obligations
The notification clock starts at incident declaration, not at investigation completion.
| Framework | Incident Type | Deadline | Penalty |
|---|---|---|---|
| GDPR (EU 2016/679) | Personal data breach | 72 hours after discovery | Up to 4% global revenue |
| PCI-DSS v4.0 | Cardholder data breach | 24 hours to acquirer | Card brand fines |
| HIPAA (45 CFR 164) | PHI breach (>500 individuals) | 60 days after discovery | Up to $1.9M per violation category |
| NY DFS 23 NYCRR 500 | Cybersecurity event | 72 hours to DFS | Regulatory sanctions |
| SEC Rule (17 CFR 229.106) | Material cybersecurity incident | 4 business days after materiality determination | SEC enforcement |
| CCPA / CPRA | Breach of sensitive PI | Without unreasonable delay | AG enforcement; private right of action |
| NIS2 (EU 2022/2555) | Significant incident (essential services) | 24-hour early warning; 72-hour notification | National authority sanctions |
Operational rule: If scope is unclear at declaration, assume the most restrictive applicable deadline and confirm scope within the first response window.
Full deadline reference: references/regulatory-deadlines.md
---
Workflows
Workflow 1: Quick Triage (15 Minutes)
For single alert requiring classification before escalation decision:
# 1. Classify the event with false positive filtering
python3 scripts/incident_triage.py --input alert.json \
--classify --false-positive-check --json
# 2. Review severity, escalation_path, and false_positive_flag in output
# 3. If severity = sev1 or sev2, page SOC Lead immediately
# 4. If false_positive_flag = true, document and closeDecision: Exit code 2 = SEV1 war room now. Exit code 1 = SEV2 bridge call within 30 minutes.
Workflow 2: Full Incident Response (SEV1)
T+0 Detection arrives (SIEM alert, EDR, user report)
T+5 Classify with incident_triage.py --classify --false-positive-check
T+10 If SEV1: page CISO, open war room, start regulatory clock
T+15 Initiate forensic collection (volatile evidence first)
T+15 Containment assessment (parallel with forensics)
T+30 Human approval gate for any containment action
T+45 Execute approved containment
T+60 Assess containment effectiveness, brief Legal if PII/PHI scope
T+4h Final forensic evidence package, dwell time estimate
T+8h Eradication and recovery plan
T+72h Regulatory notification submission (if GDPR/NIS2 triggered)# Full classification with forensic context
python3 scripts/incident_triage.py --input incident.json \
--classify --false-positive-check --severity sev1 --json > incident_triage_output.json
# Forensic pre-analysis
python3 scripts/incident_triage.py --input incident.json --json | \
jq '.forensic_findings, .chain_of_custody_steps'Workflow 3: Tabletop Exercise Simulation
Simulate incidents at specific severity levels without real events:
# Simulate SEV1 ransomware incident
echo '{"event_type": "ransomware", "host": "prod-db-01", "user": "svc_backup"}' | \
python3 scripts/incident_triage.py --classify --severity sev1 --json
# Simulate SEV2 credential compromise
echo '{"event_type": "credential_compromise", "user": "admin_user", "source_ip": "203.0.113.5"}' | \
python3 scripts/incident_triage.py --classify --false-positive-check --json
# Verify escalation paths for all 14 incident types
for type in ransomware data_exfiltration credential_compromise lateral_movement; do
echo "{\"event_type\": \"$type\"}" | python3 scripts/incident_triage.py --classify --json
done---
Anti-Patterns
1. Starting the notification clock at investigation completion — Regulatory clocks (GDPR 72 hours, PCI 24 hours) start at discovery, not investigation completion. Declaring late exposes the organization to maximum penalties even if the incident itself was minor. 2. Containing before collecting volatile evidence — Rebooting or isolating a system destroys RAM, running processes, and active connections. Forensic collection of volatile evidence must happen in parallel with containment, never after. 3. Skipping false positive verification before escalation — Escalating every alert to SEV1 degrades SOC credibility and causes alert fatigue. Always run false positive filters before paging the CISO. 4. Undocumented incident command decisions — Every decision made during a SEV1, including decisions made under uncertainty, must be logged in the evidence chain with timestamp and rationale. Undocumented decisions cannot be defended in regulatory investigations. 5. Treating incident closure as investigation completion — Incidents are closed when eradication and recovery are complete, not when the investigation is done. The forensic report and regulatory submissions may continue after operational closure. 6. Single-source classification — Classifying an incident from a single data source (one SIEM alert) without corroborating evidence frequently leads to misclassification. Collect at least two independent signals before declaring SEV1. 7. Bypassing human approval gates for containment — Automated containment actions (network isolation, credential revocation) taken without human approval can cause production outages, destroy evidence, and create liability. Human approval is non-negotiable for all mutating containment actions.
---
Cross-References
| Skill | Relationship |
|---|---|
| threat-detection | Confirmed hunting findings escalate to incident-response for triage and classification |
| cloud-security | Cloud posture findings (IAM compromise, S3 exposure) may trigger incident classification |
| red-team | Red team findings validate detection coverage; confirmed gaps become hunting hypotheses |
| security-pen-testing | Pen test vulnerabilities exploited in the wild escalate to incident-response for active incident handling |
Regulatory Notification Deadlines
Reference table for incident notification deadlines under major regulatory frameworks. The notification clock starts at the moment an incident is declared, not at investigation completion.
Operational rule: If the scope of a breach is unclear at declaration time, assume the most restrictive applicable deadline and confirm scope within the first response window. Document the assumption and its resolution in the incident record.
---
Deadline Summary Table
| Framework | Jurisdiction | Incident Type | Notification Deadline | Recipient | Penalty for Non-Compliance |
|---|---|---|---|---|---|
| GDPR (EU 2016/679) | EU/EEA | Personal data breach | 72 hours after discovery | Supervisory Authority (DPA) | Up to 4% of global annual turnover or €20M |
| GDPR (EU 2016/679) | EU/EEA | Personal data breach affecting individual rights/freedoms | Without undue delay | Affected data subjects | Up to 4% of global annual turnover |
| PCI-DSS v4.0 | Global (card brands) | Cardholder data breach | 24 hours after confirmation | Acquiring bank and card brands | Fines per card brand schedule; potential card processing suspension |
| HIPAA (45 CFR §164.408) | United States | PHI breach (>500 individuals) | 60 calendar days after discovery | HHS Office for Civil Rights | $100–$50,000 per violation; up to $1.9M per violation category per year |
| HIPAA (45 CFR §164.406) | United States | PHI breach (>500 individuals in a state) | 60 days after discovery | Prominent media outlets in affected state | Same as above |
| HIPAA Small Breach | United States | PHI breach (<500 individuals) | Within 60 days of end of calendar year in which breach occurred | HHS (annual report) | Same as above |
| NY DFS 23 NYCRR 500.17 | New York State | Cybersecurity event affecting NY-regulated entity | 72 hours | NY DFS Superintendent | Regulatory sanctions, fines, license revocation |
| SEC Cybersecurity Rule (17 CFR §229.106) | United States (public companies) | Material cybersecurity incident | 4 business days after materiality determination | SEC Form 8-K filing (public disclosure) | SEC enforcement action; restatement risk |
| CCPA / CPRA | California, United States | Breach of sensitive personal information | Without unreasonable delay | CA Attorney General (if >500 CA residents affected) | Civil penalties up to $7,500 per intentional violation |
| NIS2 (EU 2022/2555) | EU/EEA (essential/important entities) | Significant incident | 24-hour early warning; 72-hour full notification | National CSIRT or competent authority | Up to €10M or 2% of global turnover |
| DORA (EU 2022/2554) | EU/EEA (financial sector) | Major ICT-related incident | Initial notification: 4 hours; intermediate: 72 hours; final: 1 month | Financial supervisory authority | National authority sanctions |
| SOX (for material incidents) | United States (public companies) | Financial system compromise creating material weakness | Immediate disclosure required | SEC, audit committee, auditors | Enforcement action; officer certification liability |
| Australia Privacy Act | Australia | Eligible data breach (serious harm likely) | 30 days after awareness | OAIC (Office of the Australian Information Commissioner) | Up to AUD 50M per serious contravention |
| PIPL (China) | China | Personal information breach | Immediately; notify individuals without delay | National Internet Information Office (CAC) | Up to ¥50M or 5% of prior year revenue |
---
GDPR — Detailed Requirements
Article 33 — Notification to Supervisory Authority
When: Any personal data breach where there is a risk to the rights and freedoms of individuals.
Exception: No notification required if the breach is unlikely to result in risk (e.g., the data was encrypted with a key that was not compromised, and the key cannot be recovered).
What to include: 1. Nature of the breach, including categories and approximate number of data subjects and records 2. Name and contact details of the Data Protection Officer 3. Likely consequences of the breach 4. Measures taken or proposed to address the breach, including mitigation
Staggered notification: If full information is not available within 72 hours, submit what is known and provide additional information in phases. Document why the information is being provided in phases.
Article 34 — Notification to Data Subjects
When: When a breach is likely to result in high risk to the rights and freedoms of individuals.
How: In clear, plain language. Direct communication to the affected individuals.
Exception: Notification to individuals not required if:
- The personal data was protected by appropriate technical measures (e.g., encryption)
- The controller has taken subsequent measures that ensure high risk no longer materializes
- It would involve disproportionate effort (use public communication instead)
---
PCI-DSS v4.0 — Detailed Requirements
Requirement 12.10.5
Report compromises of cardholder data to the applicable payment brands and acquiring bank immediately upon detection of a suspected compromise. Do not wait for internal investigation to complete.
Immediate actions required upon suspicion: 1. Contact acquiring bank within 24 hours of suspicion (even if not yet confirmed) 2. Preserve all logs and evidence — do not modify or delete 3. Implement containment without destroying forensic evidence 4. Engage a PCI Forensic Investigator (PFI) from the approved list
Card brand notification channels:
- Visa: Visa Fraud Control
- Mastercard: Mastercard Fraud Control
- American Express: AmEx Security
- Discover: Discover Security
---
HIPAA — Detailed Requirements
45 CFR §164.408 — Breach Notification to HHS
Notification form: HHS breach notification portal (https://www.hhs.gov/hipaa/for-professionals/breach-notification/)
Content required:
- Name of covered entity or business associate
- Nature of PHI involved (type of PHI, not specific records)
- Unauthorized persons who accessed or used the PHI
- Whether PHI was actually acquired or viewed
- Extent to which risk has been mitigated
Breach Risk Assessment (45 CFR §164.402)
HIPAA provides a risk assessment safe harbor. A breach is presumed unless the covered entity can demonstrate (low probability PHI was compromised) based on: 1. Nature and extent of PHI involved 2. Who accessed the information 3. Whether PHI was actually acquired or viewed 4. Extent to which risk has been mitigated
Document this risk assessment in writing and retain for 6 years.
---
Notification Clock Management
Starting the Clock
Document the exact timestamp when the incident was declared in the incident record. This is the official start of all regulatory clocks.
Parallel Tracking
Incidents often cross multiple frameworks simultaneously. Track all applicable clocks in parallel:
Incident declared: 2024-01-15T14:30:00Z
GDPR notification due: 2024-01-18T14:30:00Z (72 hours)
PCI notification due: 2024-01-16T14:30:00Z (24 hours)
HIPAA HHS notification: 2024-03-15T14:30:00Z (60 days)
NY DFS notification: 2024-01-18T14:30:00Z (72 hours)Notification Drafting
Prepare draft notifications in parallel with investigation. Do not wait until investigation is complete to begin drafting. All external regulatory communications must be reviewed by Legal and approved by CISO before transmission.
#!/usr/bin/env python3
"""
incident_triage.py — Incident Classification, Triage, and Escalation
Classifies security events into 14 incident types, applies false-positive
filters, scores severity (SEV1-SEV4), determines escalation path, and
performs forensic pre-analysis for confirmed incidents.
Usage:
echo '{"event_type": "ransomware", "raw_payload": {...}}' | python3 incident_triage.py
python3 incident_triage.py --input event.json --json
python3 incident_triage.py --classify --false-positive-check --input event.json --json
Exit codes:
0 SEV3/SEV4 or clean — standard handling
1 SEV2 — elevated response required
2 SEV1 — critical incident declared
"""
import argparse
import json
import sys
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple
# ---------------------------------------------------------------------------
# Constants — Forensic Pre-Analysis Base (reused from pre_analysis.py logic)
# ---------------------------------------------------------------------------
DWELL_CRITICAL = 720 # hours (30 days)
DWELL_HIGH = 168 # hours (7 days)
DWELL_MEDIUM = 24 # hours (1 day)
EVIDENCE_SOURCES = [
"siem_logs",
"edr_telemetry",
"network_pcap",
"dns_logs",
"proxy_logs",
"cloud_trail",
"authentication_logs",
"endpoint_filesystem",
"memory_dump",
"email_headers",
]
CHAIN_OF_CUSTODY_STEPS = [
"Identify and preserve volatile evidence (RAM, network connections)",
"Hash all collected artifacts (SHA-256) before analysis",
"Document collection timestamp and analyst identity",
"Transfer artifacts to isolated forensic workstation",
"Maintain write-blockers for disk images",
"Log every access to evidence with timestamps",
"Store originals in secure, access-controlled evidence vault",
"Maintain dual-custody chain for legal proceedings",
]
# ---------------------------------------------------------------------------
# Constants — Incident Taxonomy and Escalation
# ---------------------------------------------------------------------------
INCIDENT_TAXONOMY: Dict[str, Dict[str, Any]] = {
"ransomware": {
"default_severity": "sev1",
"mitre": "T1486",
"response_sla_minutes": 15,
},
"data_exfiltration": {
"default_severity": "sev1",
"mitre": "T1048",
"response_sla_minutes": 15,
},
"apt_intrusion": {
"default_severity": "sev1",
"mitre": "T1190",
"response_sla_minutes": 15,
},
"supply_chain_compromise": {
"default_severity": "sev1",
"mitre": "T1195",
"response_sla_minutes": 15,
},
"credential_compromise": {
"default_severity": "sev2",
"mitre": "T1078",
"response_sla_minutes": 60,
},
"lateral_movement": {
"default_severity": "sev2",
"mitre": "T1021",
"response_sla_minutes": 60,
},
"privilege_escalation": {
"default_severity": "sev2",
"mitre": "T1068",
"response_sla_minutes": 60,
},
"malware_detected": {
"default_severity": "sev2",
"mitre": "T1204",
"response_sla_minutes": 60,
},
"phishing": {
"default_severity": "sev3",
"mitre": "T1566",
"response_sla_minutes": 240,
},
"unauthorized_access": {
"default_severity": "sev3",
"mitre": "T1078",
"response_sla_minutes": 240,
},
"policy_violation": {
"default_severity": "sev4",
"mitre": "T1530",
"response_sla_minutes": 1440,
},
"vulnerability_discovered": {
"default_severity": "sev4",
"mitre": "T1190",
"response_sla_minutes": 1440,
},
"dos_attack": {
"default_severity": "sev3",
"mitre": "T1498",
"response_sla_minutes": 240,
},
"insider_threat": {
"default_severity": "sev2",
"mitre": "T1078.002",
"response_sla_minutes": 60,
},
}
FALSE_POSITIVE_INDICATORS = [
{
"name": "ci_cd_automation",
"description": "CI/CD pipeline service account activity",
"patterns": [
"jenkins", "github-actions", "gitlab-ci", "terraform",
"ansible", "circleci", "codepipeline",
],
},
{
"name": "test_environment",
"description": "Activity in test/dev/staging environment",
"patterns": [
"test", "dev", "staging", "sandbox", "qa", "nonprod", "non-prod",
],
},
{
"name": "scheduled_scanner",
"description": "Known security scanner or automated tool",
"patterns": [
"nessus", "qualys", "rapid7", "tenable", "crowdstrike",
"defender", "sentinel",
],
},
{
"name": "scheduled_batch_job",
"description": "Recurring batch process with expected behavior",
"patterns": [
"backup", "sync", "batch", "cron", "scheduled", "nightly", "weekly",
],
},
{
"name": "whitelisted_identity",
"description": "Identity in approved exception list",
"patterns": [
"svc-", "sa-", "system@", "automation@", "monitor@", "health-check",
],
},
]
ESCALATION_ROUTING: Dict[str, Dict[str, Any]] = {
"sev1": {
"escalate_to": "CISO + CEO + Board Chair (if data at risk)",
"bridge_call": True,
"war_room": True,
},
"sev2": {
"escalate_to": "SOC Lead + CISO",
"bridge_call": True,
"war_room": False,
},
"sev3": {
"escalate_to": "SOC Lead + Security Manager",
"bridge_call": False,
"war_room": False,
},
"sev4": {
"escalate_to": "L3 Analyst queue",
"bridge_call": False,
"war_room": False,
},
}
SEV_ESCALATION_TRIGGERS = [
{"indicator": "ransomware_note_found", "escalate_to": "sev1"},
{"indicator": "active_exfiltration_confirmed", "escalate_to": "sev1"},
{"indicator": "siem_disabled", "escalate_to": "sev1"},
{"indicator": "domain_controller_access", "escalate_to": "sev1"},
{"indicator": "second_system_compromised", "escalate_to": "sev1"},
]
# ---------------------------------------------------------------------------
# Forensic Pre-Analysis Functions (base pre_analysis.py logic)
# ---------------------------------------------------------------------------
def parse_forensic_fields(fact: dict) -> dict:
"""
Parse and normalise forensic-relevant fields from the raw event.
Returns a dict with keys: source_ip, destination_ip, user_account,
hostname, process_name, dwell_hours, iocs, raw_payload.
"""
raw = fact.get("raw_payload", {}) if isinstance(fact.get("raw_payload"), dict) else {}
def _pick(*keys: str, default: Any = None) -> Any:
"""Return first non-None value found across fact and raw_payload."""
for k in keys:
v = fact.get(k) or raw.get(k)
if v is not None:
return v
return default
source_ip = _pick("source_ip", "src_ip", "sourceIp", default="unknown")
destination_ip = _pick("destination_ip", "dst_ip", "dest_ip", "destinationIp", default="unknown")
user_account = _pick("user", "user_account", "username", "actor", "identity", default="unknown")
hostname = _pick("hostname", "host", "device", "computer_name", default="unknown")
process_name = _pick("process", "process_name", "executable", "image", default="unknown")
# Dwell time: accept hours directly or compute from timestamps
dwell_hours: float = 0.0
raw_dwell = _pick("dwell_hours", "dwell_time_hours", "dwell")
if raw_dwell is not None:
try:
dwell_hours = float(raw_dwell)
except (TypeError, ValueError):
dwell_hours = 0.0
else:
first_seen = _pick("first_seen", "first_observed", "initial_access_time")
last_seen = _pick("last_seen", "last_observed", "detection_time")
if first_seen and last_seen:
try:
fmt = "%Y-%m-%dT%H:%M:%SZ"
dt_first = datetime.strptime(str(first_seen), fmt)
dt_last = datetime.strptime(str(last_seen), fmt)
dwell_hours = max(0.0, (dt_last - dt_first).total_seconds() / 3600.0)
except (ValueError, TypeError):
dwell_hours = 0.0
iocs: List[str] = []
raw_iocs = _pick("iocs", "indicators", "indicators_of_compromise")
if isinstance(raw_iocs, list):
iocs = [str(i) for i in raw_iocs]
elif isinstance(raw_iocs, str):
iocs = [raw_iocs]
return {
"source_ip": source_ip,
"destination_ip": destination_ip,
"user_account": user_account,
"hostname": hostname,
"process_name": process_name,
"dwell_hours": dwell_hours,
"iocs": iocs,
"raw_payload": raw,
}
def assess_dwell_severity(dwell_hours: float) -> str:
"""
Map dwell time (hours) to a severity label.
Returns 'critical', 'high', 'medium', or 'low'.
"""
if dwell_hours >= DWELL_CRITICAL:
return "critical"
if dwell_hours >= DWELL_HIGH:
return "high"
if dwell_hours >= DWELL_MEDIUM:
return "medium"
return "low"
def build_ioc_summary(fields: dict) -> dict:
"""
Build a structured IOC summary from parsed forensic fields.
Returns a dict suitable for embedding in the triage output.
"""
iocs = fields.get("iocs", [])
dwell_hours = fields.get("dwell_hours", 0.0)
dwell_severity = assess_dwell_severity(dwell_hours)
# Classify IOCs by rough heuristic
ip_iocs = [i for i in iocs if _looks_like_ip(i)]
hash_iocs = [i for i in iocs if _looks_like_hash(i)]
domain_iocs = [i for i in iocs if not _looks_like_ip(i) and not _looks_like_hash(i)]
return {
"total_ioc_count": len(iocs),
"ip_indicators": ip_iocs,
"hash_indicators": hash_iocs,
"domain_url_indicators": domain_iocs,
"dwell_hours": round(dwell_hours, 2),
"dwell_severity": dwell_severity,
"evidence_sources_applicable": [
src for src in EVIDENCE_SOURCES
if _source_applicable(src, fields)
],
"chain_of_custody_steps": CHAIN_OF_CUSTODY_STEPS,
}
def _looks_like_ip(value: str) -> bool:
"""Heuristic: does the string look like an IPv4 address?"""
import re
return bool(re.match(r"^\d{1,3}(\.\d{1,3}){3}$", value.strip()))
def _looks_like_hash(value: str) -> bool:
"""Heuristic: does the string look like a hex hash (MD5/SHA1/SHA256)?"""
import re
return bool(re.match(r"^[0-9a-fA-F]{32,64}$", value.strip()))
def _source_applicable(source: str, fields: dict) -> bool:
"""Decide if an evidence source is relevant given parsed fields."""
mapping = {
"network_pcap": fields.get("source_ip") not in (None, "unknown"),
"edr_telemetry": fields.get("hostname") not in (None, "unknown"),
"authentication_logs": fields.get("user_account") not in (None, "unknown"),
"dns_logs": fields.get("destination_ip") not in (None, "unknown"),
"endpoint_filesystem": fields.get("process_name") not in (None, "unknown"),
"memory_dump": fields.get("process_name") not in (None, "unknown"),
}
return mapping.get(source, True)
# ---------------------------------------------------------------------------
# New Classification and Escalation Functions
# ---------------------------------------------------------------------------
def classify_incident(fact: dict) -> Tuple[str, float]:
"""
Classify incident type from event fields.
Performs keyword matching against INCIDENT_TAXONOMY keys and the
flattened string representation of raw_payload content.
Returns:
(incident_type, confidence) where confidence is 0.0–1.0.
Returns ("unknown", 0.0) when no match is found.
"""
# Build a single searchable string from the fact
searchable = _flatten_to_string(fact).lower()
scores: Dict[str, int] = {}
for incident_type in INCIDENT_TAXONOMY:
# The incident type slug itself is a keyword
slug_words = incident_type.replace("_", " ").split()
score = 0
for word in slug_words:
if word in searchable:
score += 2 # direct slug match carries more weight
# Additional keyword synonyms per type
synonyms = _get_synonyms(incident_type)
for syn in synonyms:
if syn in searchable:
score += 1
if score > 0:
scores[incident_type] = score
if not scores:
# Last resort: check explicit event_type field
event_type = str(fact.get("event_type", "")).lower().replace(" ", "_").replace("-", "_")
if event_type in INCIDENT_TAXONOMY:
return event_type, 0.6
return "unknown", 0.0
best_type = max(scores, key=lambda k: scores[k])
max_score = scores[best_type]
# Normalise confidence: cap at 1.0, scale by how much the best
# outscores alternatives
total_score = sum(scores.values()) or 1
raw_confidence = max_score / total_score
# Boost if event_type field matches
event_type = str(fact.get("event_type", "")).lower().replace(" ", "_").replace("-", "_")
if event_type == best_type:
raw_confidence = min(1.0, raw_confidence + 0.25)
confidence = round(min(1.0, raw_confidence + 0.1 * min(max_score, 5)), 2)
return best_type, confidence
def _flatten_to_string(obj: Any, depth: int = 0) -> str:
"""Recursively flatten any JSON-like object into a single string."""
if depth > 6:
return ""
if isinstance(obj, dict):
parts = []
for k, v in obj.items():
parts.append(str(k))
parts.append(_flatten_to_string(v, depth + 1))
return " ".join(parts)
if isinstance(obj, list):
return " ".join(_flatten_to_string(i, depth + 1) for i in obj)
return str(obj)
def _get_synonyms(incident_type: str) -> List[str]:
"""Return additional keyword synonyms for an incident type."""
synonyms_map: Dict[str, List[str]] = {
"ransomware": ["encrypt", "ransom", "locked", "decrypt", "wiper", "crypto"],
"data_exfiltration": ["exfil", "upload", "transfer", "leak", "dump", "steal", "exfiltrate"],
"apt_intrusion": ["apt", "nation-state", "targeted", "backdoor", "persistence", "c2", "c&c"],
"supply_chain_compromise": ["supply chain", "dependency", "package", "solarwinds", "xz", "npm"],
"credential_compromise": ["credential", "password", "brute force", "spray", "stuffing", "stolen"],
"lateral_movement": ["lateral", "pivot", "pass-the-hash", "wmi", "psexec", "rdp movement"],
"priv_escalation": ["privesc", "su_exec", "priv_change", "elevated_session", "priv_grant", "priv_abuse"],
"malware_detected": ["malware", "trojan", "virus", "worm", "keylogger", "spyware", "rat"],
"phishing": ["phish", "spear", "bec", "email", "lure", "credential harvest"],
"unauthorized_access": ["unauthorized", "unauthenticated", "brute", "login failed", "access denied"],
"policy_violation": ["policy", "dlp", "data loss", "violation", "compliance"],
"vulnerability_discovered": ["vulnerability", "cve", "exploit", "patch", "zero-day", "rce"],
"dos_attack": ["dos", "ddos", "flood", "amplification", "bandwidth", "exhaustion"],
"insider_threat": ["insider", "employee", "contractor", "abuse", "privilege misuse"],
}
return synonyms_map.get(incident_type, [])
def check_false_positives(fact: dict) -> List[str]:
"""
Check fact fields against FALSE_POSITIVE_INDICATORS pattern lists.
Returns a list of triggered false positive indicator names.
"""
searchable = _flatten_to_string(fact).lower()
triggered: List[str] = []
for indicator in FALSE_POSITIVE_INDICATORS:
for pattern in indicator["patterns"]:
if pattern.lower() in searchable:
triggered.append(indicator["name"])
break # one match per indicator is enough
return triggered
def get_escalation_path(incident_type: str, severity: str) -> dict:
"""
Return escalation routing for a given incident type and severity level.
Falls back to sev4 routing if severity is not recognised.
"""
sev_key = severity.lower()
routing = ESCALATION_ROUTING.get(sev_key, ESCALATION_ROUTING["sev4"]).copy()
# Augment with taxonomy SLA if available
taxonomy = INCIDENT_TAXONOMY.get(incident_type, {})
routing["incident_type"] = incident_type
routing["severity"] = sev_key
routing["response_sla_minutes"] = taxonomy.get("response_sla_minutes", 1440)
routing["mitre_technique"] = taxonomy.get("mitre", "N/A")
return routing
def check_sev_escalation_triggers(fact: dict) -> Optional[str]:
"""
Scan fact fields for any SEV escalation trigger indicators.
Returns the escalation target (e.g. 'sev1') if a trigger fires,
or None if no triggers are present.
"""
searchable = _flatten_to_string(fact).lower()
# Also inspect a flat list of explicit indicator flags
explicit_indicators: List[str] = []
if isinstance(fact.get("indicators"), list):
explicit_indicators = [str(i).lower() for i in fact["indicators"]]
if isinstance(fact.get("escalation_triggers"), list):
explicit_indicators += [str(i).lower() for i in fact["escalation_triggers"]]
for trigger in SEV_ESCALATION_TRIGGERS:
indicator_key = trigger["indicator"].replace("_", " ")
indicator_raw = trigger["indicator"].lower()
if (
indicator_key in searchable
or indicator_raw in searchable
or indicator_raw in explicit_indicators
):
return trigger["escalate_to"]
return None
# ---------------------------------------------------------------------------
# Severity Normalisation Helpers
# ---------------------------------------------------------------------------
_SEV_ORDER = {"sev1": 1, "sev2": 2, "sev3": 3, "sev4": 4}
def _sev_to_int(sev: str) -> int:
return _SEV_ORDER.get(sev.lower(), 4)
def _int_to_sev(n: int) -> str:
return {1: "sev1", 2: "sev2", 3: "sev3", 4: "sev4"}.get(n, "sev4")
def _escalate_sev(current: str, target: str) -> str:
"""Return the higher severity (lower SEV number)."""
return _int_to_sev(min(_sev_to_int(current), _sev_to_int(target)))
# ---------------------------------------------------------------------------
# Text Report
# ---------------------------------------------------------------------------
def _print_text_report(result: dict) -> None:
"""Print a human-readable triage report to stdout."""
sep = "=" * 70
print(sep)
print(" INCIDENT TRIAGE REPORT")
print(sep)
print(f" Timestamp : {result.get('timestamp_utc', 'N/A')}")
print(f" Incident Type : {result.get('incident_type', 'unknown').upper()}")
print(f" Severity : {result.get('severity', 'N/A').upper()}")
print(f" Confidence : {result.get('classification_confidence', 0.0):.0%}")
print(sep)
fp = result.get("false_positive_indicators", [])
if fp:
print(f"\n [!] FALSE POSITIVE FLAGS: {', '.join(fp)}")
print(" Review before escalating.")
esc_trigger = result.get("escalation_trigger_fired")
if esc_trigger:
print(f"\n [!] ESCALATION TRIGGER FIRED -> {esc_trigger.upper()}")
path = result.get("escalation_path", {})
print(f"\n Escalate To : {path.get('escalate_to', 'N/A')}")
print(f" Response SLA : {path.get('response_sla_minutes', 'N/A')} minutes")
print(f" Bridge Call : {'YES' if path.get('bridge_call') else 'no'}")
print(f" War Room : {'YES' if path.get('war_room') else 'no'}")
print(f" MITRE : {path.get('mitre_technique', 'N/A')}")
forensics = result.get("forensic_analysis", {})
if forensics:
print(f"\n Forensic Fields:")
print(f" Source IP : {forensics.get('source_ip', 'N/A')}")
print(f" User Account : {forensics.get('user_account', 'N/A')}")
print(f" Hostname : {forensics.get('hostname', 'N/A')}")
print(f" Process : {forensics.get('process_name', 'N/A')}")
print(f" Dwell (hrs) : {forensics.get('dwell_hours', 0.0)}")
print(f" Dwell Severity: {forensics.get('dwell_severity', 'N/A')}")
ioc_summary = result.get("ioc_summary", {})
if ioc_summary:
print(f"\n IOC Summary:")
print(f" Total IOCs : {ioc_summary.get('total_ioc_count', 0)}")
if ioc_summary.get("ip_indicators"):
print(f" IPs : {', '.join(ioc_summary['ip_indicators'])}")
if ioc_summary.get("hash_indicators"):
print(f" Hashes : {len(ioc_summary['hash_indicators'])} hash(es)")
print(f" Evidence Srcs : {', '.join(ioc_summary.get('evidence_sources_applicable', []))}")
print(f"\n Recommended Action: {result.get('recommended_action', 'N/A')}")
print(sep)
# ---------------------------------------------------------------------------
# Main Entry Point
# ---------------------------------------------------------------------------
# Embedded synthetic security event for --sample (no file/stdin needed).
SAMPLE_EVENT = {
"event_type": "ransomware",
"source_ip": "203.0.113.50",
"destination_ip": "10.0.4.21",
"user_account": "svc-backup",
"hostname": "fileserver-02",
"process_name": "encryptor.exe",
"first_seen": "2026-06-10T01:30:00Z",
"detected_at": "2026-06-10T09:30:00Z",
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Incident Classification, Triage, and Escalation",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
echo '{"event_type": "ransomware"}' | %(prog)s --json
%(prog)s --input event.json --classify --false-positive-check --json
%(prog)s --input event.json --severity sev1 --json
Exit codes:
0 SEV3/SEV4 or no confirmed incident
1 SEV2 — elevated response required
2 SEV1 — critical incident declared
""",
)
parser.add_argument(
"--input", "-i",
metavar="FILE",
help="JSON file path containing the security event (default: stdin)",
)
parser.add_argument(
"--json",
action="store_true",
help="Output results as JSON",
)
parser.add_argument(
"--classify",
action="store_true",
help="Run incident classification against INCIDENT_TAXONOMY",
)
parser.add_argument(
"--false-positive-check",
action="store_true",
dest="false_positive_check",
help="Run false positive filter checks",
)
parser.add_argument(
"--severity",
choices=["sev1", "sev2", "sev3", "sev4"],
help="Explicit severity override (skips taxonomy-derived severity)",
)
parser.add_argument(
"--sample",
action="store_true",
help="Triage an embedded synthetic ransomware event (no file/stdin needed; "
"note: exits 2 — the SEV1 exit-code signal is intentional)",
)
args = parser.parse_args()
# --- Load input ---
try:
if args.sample:
if args.input:
print("Warning: --sample specified; ignoring --input", file=sys.stderr)
raw_event = SAMPLE_EVENT
elif args.input:
with open(args.input, "r", encoding="utf-8") as fh:
raw_event = json.load(fh)
else:
raw_event = json.load(sys.stdin)
except json.JSONDecodeError as exc:
msg = {"error": f"Invalid JSON input: {exc}"}
if args.json:
print(json.dumps(msg, indent=2))
else:
print(f"Error: {msg['error']}", file=sys.stderr)
sys.exit(1)
except FileNotFoundError as exc:
msg = {"error": str(exc)}
if args.json:
print(json.dumps(msg, indent=2))
else:
print(f"Error: {msg['error']}", file=sys.stderr)
sys.exit(1)
# --- Forensic pre-analysis (base logic) ---
fields = parse_forensic_fields(raw_event)
ioc_summary = build_ioc_summary(fields)
forensic_analysis = {
"source_ip": fields["source_ip"],
"destination_ip": fields["destination_ip"],
"user_account": fields["user_account"],
"hostname": fields["hostname"],
"process_name": fields["process_name"],
"dwell_hours": fields["dwell_hours"],
"dwell_severity": assess_dwell_severity(fields["dwell_hours"]),
}
# --- Classification ---
incident_type = "unknown"
confidence = 0.0
if args.classify or not args.severity:
incident_type, confidence = classify_incident(raw_event)
# Override with explicit event_type if classify not run
if not args.classify:
et = str(raw_event.get("event_type", "")).lower().replace(" ", "_").replace("-", "_")
if et in INCIDENT_TAXONOMY:
incident_type = et
confidence = 0.75
# --- Determine base severity ---
if args.severity:
severity = args.severity.lower()
else:
taxonomy_entry = INCIDENT_TAXONOMY.get(incident_type, {})
severity = taxonomy_entry.get("default_severity", "sev4")
# Factor in dwell severity
dwell_sev_map = {"critical": "sev1", "high": "sev2", "medium": "sev3", "low": "sev4"}
dwell_derived = dwell_sev_map.get(forensic_analysis["dwell_severity"], "sev4")
severity = _escalate_sev(severity, dwell_derived)
# --- Escalation trigger check ---
escalation_trigger_fired: Optional[str] = None
trigger_result = check_sev_escalation_triggers(raw_event)
if trigger_result:
escalation_trigger_fired = trigger_result
severity = _escalate_sev(severity, trigger_result)
# --- False positive check ---
fp_indicators: List[str] = []
if args.false_positive_check:
fp_indicators = check_false_positives(raw_event)
# --- Escalation path ---
escalation_path = get_escalation_path(incident_type, severity)
# --- Recommended action ---
if fp_indicators:
recommended_action = (
f"Verify false positive flags before escalating: {', '.join(fp_indicators)}. "
"Confirm with asset owner and close or reclassify."
)
elif severity == "sev1":
recommended_action = (
"IMMEDIATE: Declare SEV1, open war room, page CISO and CEO. "
"Isolate affected systems, preserve evidence, activate IR playbook."
)
elif severity == "sev2":
recommended_action = (
"URGENT: Page SOC Lead and CISO. Open bridge call. "
"Contain impacted accounts/hosts and begin forensic collection."
)
elif severity == "sev3":
recommended_action = (
"Notify SOC Lead and Security Manager. "
"Investigate during business hours and document findings."
)
else:
recommended_action = (
"Queue for L3 Analyst review. "
"Document and track per standard operating procedure."
)
# --- Assemble output ---
result: Dict[str, Any] = {
"incident_type": incident_type,
"classification_confidence": confidence,
"severity": severity,
"false_positive_indicators": fp_indicators,
"escalation_trigger_fired": escalation_trigger_fired,
"escalation_path": escalation_path,
"forensic_analysis": forensic_analysis,
"ioc_summary": ioc_summary,
"recommended_action": recommended_action,
"taxonomy": INCIDENT_TAXONOMY.get(incident_type, {}),
"timestamp_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
}
# --- Output ---
if args.json:
print(json.dumps(result, indent=2))
else:
_print_text_report(result)
# --- Exit code ---
if severity == "sev1":
sys.exit(2)
elif severity == "sev2":
sys.exit(1)
else:
sys.exit(0)
if __name__ == "__main__":
main()
Related skills
How it compares
Pick incident-response over broad compliance suites when an incident is already declared and the urgent need is regulatory notification timing.
FAQ
When does the breach notification clock start in incident-response?
incident-response states the notification clock starts at incident declaration, not when investigation completes, so teams must act on deadlines from the first response window.
What if breach scope is unclear at declaration?
incident-response directs teams to assume the most restrictive applicable deadline, confirm scope within the first response window, and document the assumption in the incident record.
Is Incident Response safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.