Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build automated alerting for vulnerability remediation SLA breaches with severity-based timelines, escalation workflows, and compliance reporting dashboards.
.claude/skills/implementing-vulnerability-sla-breach-alerting/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-17 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-15 | ✗→✓ | ▲ Improved | — | — |
Vulnerability remediation SLAs define maximum timeframes for addressing security findings based on severity. This skill covers building an automated alerting system that tracks remediation timelines, detects SLA breaches, sends escalation notifications, and generates compliance reports. Industry-standard SLA targets are: Critical (24-48 hours), High (15-30 days), Medium (60 days), Low (90 days).
requests, pandas, jinja2, smtplib libraries| Severity | Remediation SLA | Grace Period | Escalation Level | |----------|----------------|--------------|-----------------| | Critical (CVSS 9.0-10.0) | 48 hours | 12 hours | VP Engineering + CISO | | High (CVSS 7.0-8.9) | 15 days | 5 days | Director of Engineering | | Medium (CVSS 4.0-6.9) | 60 days | 14 days | Team Lead | | Low (CVSS 0.1-3.9) | 90 days | 30 days | Asset Owner |
yaml# sla_policy.yaml sla_tiers: critical: cvss_min: 9.0 cvss_max: 10.0 remediation_days: 2 grace_period_days: 0.5 escalation_contacts: - ciso@company.com - vp-engineering@company.com pagerduty_severity: critical high: cvss_min: 7.0 cvss_max: 8.9 remediation_days: 15 grace_period_days: 5 escalation_contacts: - security-director@company.com pagerduty_severity: high medium: cvss_min: 4.0 cvss_max: 6.9 remediation_days: 60 grace_period_days: 14 escalation_contacts: - team-lead@company.com pagerduty_severity: warning low: cvss_min: 0.1 cvss_max: 3.9 remediation_days: 90 grace_period_days: 30 escalation_contacts: - asset-owner@company.com pagerduty_severity: info notification_channels: slack: webhook_url: "${SLACK_WEBHOOK_URL}" channel: "#vulnerability-alerts" email: smtp_host: smtp.company.com smtp_port: 587 from_address: vuln-alerts@company.com pagerduty: api_key: "${PAGERDUTY_API_KEY}" service_id: "${PAGERDUTY_SERVICE_ID}" alert_schedules: approaching_breach: percentage_elapsed: 80 frequency_hours: 24 at_breach: notification: immediate escalation: true post_breach: frequency_hours: 12 escalation_increase: true
sqlCREATE TABLE vulnerability_sla ( id SERIAL PRIMARY KEY, cve_id VARCHAR(20) NOT NULL, finding_id VARCHAR(100) NOT NULL, asset_hostname VARCHAR(255), severity VARCHAR(20) NOT NULL, cvss_score DECIMAL(3,1), discovered_at TIMESTAMP NOT NULL, sla_deadline TIMESTAMP NOT NULL, remediated_at TIMESTAMP, status VARCHAR(20) DEFAULT 'open', owner_email VARCHAR(255), escalation_level INTEGER DEFAULT 0, last_alert_sent TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_sla_status ON vulnerability_sla(status); CREATE INDEX idx_sla_deadline ON vulnerability_sla(sla_deadline); CREATE INDEX idx_sla_severity ON vulnerability_sla(severity);
pythonfrom datetime import datetime, timedelta, timezone import yaml def load_sla_policy(policy_path="sla_policy.yaml"): with open(policy_path, "r") as f: return yaml.safe_load(f) def get_sla_tier(cvss_score, policy): for tier_name, tier in policy["sla_tiers"].items(): if tier["cvss_min"] <= cvss_score <= tier["cvss_max"]: return tier_name, tier return "low", policy["sla_tiers"]["low"] def calculate_sla_deadline(discovered_at, cvss_score, policy): tier_name, tier = get_sla_tier(cvss_score, policy) deadline = discovered_at + timedelta(days=tier["remediation_days"]) return deadline, tier_name def check_sla_status(discovered_at, sla_deadline, remediated_at=None): now = datetime.now(timezone.utc) if remediated_at: if remediated_at <= sla_deadline: return "remediated_within_sla" return "remediated_breach" if now > sla_deadline: overdue_days = (now - sla_deadline).days return f"breached_{overdue_days}d_overdue" remaining = sla_deadline - now total_sla = sla_deadline - discovered_at pct_elapsed = ((total_sla - remaining) / total_sla) * 100 if pct_elapsed >= 80: return "approaching_breach" return "within_sla"
pythonimport requests import json import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart def send_slack_alert(webhook_url, vuln_data, sla_status): color = {"breached": "#FF0000", "approaching_breach": "#FFA500", "within_sla": "#36A64F"} status_color = color.get("breached" if "breached" in sla_status else sla_status, "#808080") payload = { "attachments": [{ "color": status_color, "title": f"Vulnerability SLA Alert: {vuln_data['cve_id']}", "fields": [ {"title": "Severity", "value": vuln_data["severity"], "short": True}, {"title": "CVSS", "value": str(vuln_data["cvss_score"]), "short": True}, {"title": "Asset", "value": vuln_data["asset_hostname"], "short": True}, {"title": "SLA Status", "value": sla_status, "short": True}, {"title": "Deadline", "value": vuln_data["sla_deadline"].strftime("%Y-%m-%d %H:%M UTC"), "short": True}, {"title": "Owner", "value": vuln_data.get("owner_email", "Unassigned"), "short": True}, ], }] } requests.post(webhook_url, json=payload, timeout=10) def send_pagerduty_alert(api_key, service_id, vuln_data, severity): payload = { "routing_key": api_key, "event_action": "trigger", "payload": { "summary": f"SLA Breach: {vuln_data['cve_id']} on {vuln_data['asset_hostname']}", "severity": severity, "source": vuln_data["asset_hostname"], "custom_details": { "cve_id": vuln_data["cve_id"], "cvss_score": vuln_data["cvss_score"], "sla_deadline": vuln_data["sla_deadline"].isoformat(), } } } requests.post( "https://events.pagerduty.com/v2/enqueue", json=payload, timeout=10 ) def send_email_alert(smtp_config, to_addresses, vuln_data, sla_status): msg = MIMEMultipart("alternative") msg["Subject"] = f"[SLA {sla_status.upper()}] {vuln_data['cve_id']} - {vuln_data['severity']}" msg["From"] = smtp_config["from_address"] msg["To"] = ", ".join(to_addresses) body = f""" Vulnerability SLA Alert CVE: {vuln_data['cve_id']} Severity: {vuln_data['severity']} (CVSS {vuln_data['cvss_score']}) Asset: {vuln_data['asset_hostname']} SLA Deadline: {vuln_data['sla_deadline'].strftime('%Y-%m-%d %H:%M UTC')} Status: {sla_status} Owner: {vuln_data.get('owner_email', 'Unassigned')} Please take immediate action to remediate this vulnerability. """ msg.attach(MIMEText(body, "plain")) with smtplib.SMTP(smtp_config["smtp_host"], smtp_config["smtp_port"]) as server: server.starttls() server.send_message(msg)
bash# Run SLA breach check every hour via cron echo "0 * * * * cd /opt/vuln-sla && python3 scripts/process.py --check-sla" | crontab - # Manual check python3 scripts/process.py --check-sla --policy sla_policy.yaml # Generate SLA compliance report python3 scripts/process.py --report --period monthly --output sla_report.html
pythondef calculate_sla_metrics(db_connection, period_start, period_end): metrics = { "total_findings": 0, "remediated_within_sla": 0, "sla_breach_count": 0, "mean_time_to_remediate": {}, "sla_compliance_rate": 0.0, "current_overdue": 0, } # Query findings in period grouped by severity query = """ SELECT severity, COUNT(*) as total, SUM(CASE WHEN remediated_at <= sla_deadline THEN 1 ELSE 0 END) as within_sla, AVG(EXTRACT(EPOCH FROM (COALESCE(remediated_at, NOW()) - discovered_at))/86400) as avg_days FROM vulnerability_sla WHERE discovered_at BETWEEN %s AND %s GROUP BY severity """ return metrics
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +57 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.