T09 · Insecure Skill Coding Practices
- Location
scripts/vapi_calls.py:40- Finding
Public Webhook Accepts Events Without Authentication
- Content
View full analysis
Vulnerability Details
File Location:
scripts/vapi_calls.py, lines 40-115 and 137-150
Vulnerability Type: Unauthenticated public webhook
Risk Level: HighVulnerable Code
python class WebhookHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): pass # Silence HTTP logs def do_POST(self): global call_result, expected_call_id, last_activity_time # Keep connection alive last_activity_time = time.time() try: content_length = int(self.headers.get('Content-Length', 0)) body = json.loads(self.rfile.read(content_length)) message = body.get("message", {}) event_type = message.get("type") # Robust ID extraction call_id = message.get("call", {}).get("id") or body.get("call", {}).get("id") # Only process events for OUR call if call_id == expected_call_id: # 1. Watchdog: If call ended physically, set a short timeout for the report if event_type == "status-update" and message.get("status") == "ended": # If report doesn't arrive in 30s, force finish threading.Timer(POST_CALL_TIMEOUT, lambda: call_completed.set()).start() # 2. End of Call Report: The Goal elif event_type == "end-of-call-report": call_result = { "status": "completed", "call_id": call_id, "transcript": message.get("transcript", ""), "summary": message.get("summary", ""), "cost": message.get("cost", 0), "ended_reason": message.get("endedReason", ""), "duration": 0 } # Calculat ...[truncated 3646 chars]- Remediation
View remediation
Remediation Suggestions
- Implement Vapi's documented webhook-signature verification using the unmodified request body.
- Compare signatures in constant time and reject missing or invalid signatures with
401or403. - Validate signed timestamps and maintain replay protection for previously processed event identifiers.
- Accept requests only on the exact
/vapi-webhookpath and reject all other routes. - Validate event schemas and permit only expected event types and fields.
- Bind the listener to
127.0.0.1when a local tunnel forwards requests, rather than exposing the port directly on every interface. - Use a per-call, cryptographically random callback token as an additional defense, without treating it as a replacement for provider signature verification.
- Avoid silently swallowing verification and parsing failures; record security-relevant failures without logging secrets or sensitive payloads.
