id: github-dependabot-sla-escalation-sentinel
namespace: company.security
description: Audit open GitHub Dependabot vulnerability alerts against SLA
deadlines and escalate overdue CVEs to Slack.
triggers:
- id: daily_sla_audit
type: io.kestra.plugin.core.trigger.Schedule
description: Run daily SLA audit for repository security alerts.
cron: "0 9 * * 1-5"
- id: on_demand_webhook
type: io.kestra.plugin.core.trigger.Webhook
description: Trigger an on-demand audit from SecOps pipelines or GitHub webhooks.
key: dependabot-sla-check
inputs:
- id: repository
type: STRING
displayName: GitHub Repository
defaults: kestra-io/kestra
description: Target repository in owner/repo format.
- id: critical_sla_days
type: INT
displayName: Critical Severity SLA (Days)
defaults: 7
description: Maximum allowed days to resolve Critical severity vulnerabilities.
- id: high_sla_days
type: INT
displayName: High Severity SLA (Days)
defaults: 14
description: Maximum allowed days to resolve High severity vulnerabilities.
- id: medium_sla_days
type: INT
displayName: Medium Severity SLA (Days)
defaults: 30
description: Maximum allowed days to resolve Medium severity vulnerabilities.
- id: low_sla_days
type: INT
displayName: Low Severity SLA (Days)
defaults: 60
description: Maximum allowed days to resolve Low severity vulnerabilities.
tasks:
- id: fetch_dependabot_alerts
type: io.kestra.plugin.core.http.Request
description: Retrieve open Dependabot vulnerability alerts from the GitHub REST API.
uri: "https://api.github.com/repos/{{ inputs.repository
}}/dependabot/alerts?state=open&per_page=100&direction=desc&sort=created"
method: GET
headers:
Accept: "application/vnd.github+json"
User-Agent: "Kestra-Dependabot-SLA-Sentinel"
Authorization: "Bearer {{ secret('GITHUB_TOKEN') }}"
- id: audit_sla_breaches
type: io.kestra.plugin.scripts.python.Script
description: Evaluate open alerts against defined remediation SLA thresholds.
containerImage: python:3.11-slim
inputFiles:
alerts.json: "{{ outputs.fetch_dependabot_alerts.body | toJson }}"
script: |
import json
from datetime import datetime, timezone
with open("alerts.json", "r") as f:
raw = f.read().strip()
data = json.loads(raw) if raw else []
alerts = data if isinstance(data, list) else []
now = datetime.now(timezone.utc)
sla_limits = {
"critical": int("{{ inputs.critical_sla_days }}"),
"high": int("{{ inputs.high_sla_days }}"),
"medium": int("{{ inputs.medium_sla_days }}"),
"low": int("{{ inputs.low_sla_days }}")
}
total_open = len(alerts)
breached_alerts = []
compliant_alerts = []
by_severity = {"critical": 0, "high": 0, "medium": 0, "low": 0}
for item in alerts:
advisory = item.get("security_advisory", {})
severity = advisory.get("severity", "low").lower()
if severity not in sla_limits:
severity = "low"
by_severity[severity] = by_severity.get(severity, 0) + 1
created_str = item.get("created_at")
age_days = 0
if created_str:
dt = datetime.fromisoformat(created_str.replace("Z", "+00:00"))
age_days = (now - dt).days
allowed_days = sla_limits.get(severity, 60)
is_breached = age_days > allowed_days
record = {
"number": item.get("number"),
"package": item.get("dependency", {}).get("package", {}).get("name", "unknown"),
"severity": severity,
"cve_id": advisory.get("cve_id") or advisory.get("ghsa_id", "N/A"),
"summary": advisory.get("summary", ""),
"age_days": age_days,
"sla_days": allowed_days,
"overdue_by": age_days - allowed_days if is_breached else 0,
"html_url": item.get("html_url", "")
}
if is_breached:
breached_alerts.append(record)
else:
compliant_alerts.append(record)
breached_alerts.sort(key=lambda x: x["overdue_by"], reverse=True)
breach_count = len(breached_alerts)
has_breaches = breach_count > 0
alert_lines = []
for a in breached_alerts[:10]:
alert_lines.append(
f"• [{a['severity'].upper()}] {a['package']} ({a['cve_id']}): {a['age_days']}d old (SLA: {a['sla_days']}d, {a['overdue_by']}d overdue) - <{a['html_url']}|View Alert>"
)
if breach_count > 10:
alert_lines.append(f"• ...and {breach_count - 10} more overdue alert(s).")
breach_details_text = "\n".join(alert_lines) if alert_lines else "No SLA breaches."
summary = {
"total_open": total_open,
"breach_count": breach_count,
"has_breaches": has_breaches,
"by_severity": by_severity,
"breach_details_text": breach_details_text
}
print(f"Audited {total_open} alerts: {breach_count} breached SLA thresholds.")
print("::" + json.dumps({"outputs": summary}) + "::")
- id: evaluate_sla_breaches
type: io.kestra.plugin.core.flow.If
description: Escalate to Slack if any open alerts exceed remediation SLA limits.
condition: "{{ outputs.audit_sla_breaches.vars.has_breaches == true }}"
then:
- id: alert_security_slack
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Dispatch urgent remediation alert to Slack security channel.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"text": ":rotating_light: *Dependabot Vulnerability SLA Breach Alert*\n*Repository:* {{ inputs.repository }}\n*Total Open Alerts:* {{ outputs.audit_sla_breaches.vars.total_open }}\n*SLA Breaches:* {{ outputs.audit_sla_breaches.vars.breach_count }}\n*Severity Breakdown:* Critical: {{ outputs.audit_sla_breaches.vars.by_severity.critical }}, High: {{ outputs.audit_sla_breaches.vars.by_severity.high }}, Medium: {{ outputs.audit_sla_breaches.vars.by_severity.medium }}, Low: {{ outputs.audit_sla_breaches.vars.by_severity.low }}\n\n*Overdue Alerts (Immediate Action Required):*\n{{ outputs.audit_sla_breaches.vars.breach_details_text }}"
}
else:
- id: log_sla_compliant
type: io.kestra.plugin.core.log.Log
description: Log compliance status when all open alerts are within SLA window.
message: "Dependabot SLA audit passed for {{ inputs.repository }}. Total open:
{{ outputs.audit_sla_breaches.vars.total_open }}, all within
remediation SLA."
errors:
- id: alert_audit_failure
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Notify Slack when the GitHub API or audit fails so security gaps
are not hidden.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"text": ":warning: *Dependabot SLA Sentinel Audit Failed*\n*Repository:* {{ inputs.repository }}\n*Execution:* {{ execution.id }}\nCheck GITHUB_TOKEN scope ('security_events' or 'repo') and API rate limits."
}
outputs:
- id: audit_summary
type: JSON
description: Summary of Dependabot open alerts, SLA breaches, and severity metrics.
value: "{{ outputs.audit_sla_breaches.vars | toJson }}"