Back to skill

Security audit

VAPI Calls

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to make Vapi phone calls as advertised, but it needs review because it can place autonomous calls, expose a local webhook publicly, and save call transcripts without strong safeguards.

Review before installing. Use this only for lawful, authorized calls where recipients have appropriate notice or consent, expect Vapi-related charges and data processing, secure or avoid the public webhook exposure, and treat saved transcripts as sensitive data that may need deletion, redaction, or restricted permissions.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Error
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: High

Vulnerable 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 401 or 403.
  • Validate signed timestamps and maintain replay protection for previously processed event identifiers.
  • Accept requests only on the exact /vapi-webhook path and reject all other routes.
  • Validate event schemas and permit only expected event types and fields.
  • Bind the listener to 127.0.0.1 when 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.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/vapi_calls.py:40
Finding

Unbounded Webhook Body Read Enables Resource Exhaustion and Timeout Manipulation

Content
View full analysis

Vulnerability Details

File Location: scripts/vapi_calls.py, lines 40-45
Vulnerability Type: Unbounded unauthenticated input processing
Risk Level: Medium

Vulnerable Code

python
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))

Technical Analysis

The handler trusts the client-provided Content-Length and reads that number of bytes without imposing a maximum body size. A client can therefore request a very large allocation or hold the single-threaded HTTPServer connection open while slowly transmitting the declared body.

In addition, last_activity_time is updated before the request is parsed, correlated to the active call, or authenticated. Any unauthenticated POST request can consequently reset the inactivity watchdog, including malformed and irrelevant requests.

Because HTTPServer handles requests serially, a slow or blocked body read can prevent legitimate webhook events from being processed during the call.

Attack Path

  1. A legitimate call exposes the webhook endpoint to the internet.
  2. An unauthenticated attacker connects and supplies an excessive Content-Length, or sends the body very slowly.
  3. The handler blocks while reading the body or consumes excessive memory.
  4. Legitimate provider callbacks are delayed or cannot be processed by the single-threaded server.
  5. Alternatively, the attacker repeatedly sends small invalid POST requests, resetting last_activity_time before validation and interfering with inactivity detection.

Impact Assessment

A remote unauthenticated attacker can degrade availability during an active call, delay or suppress legitimate call reports, manipulate watchdog timing, and potential ...[truncated 169 chars]

Remediation
View remediation

Remediation Suggestions

  • Reject missing, negative, malformed, or oversized Content-Length values before reading the request body.
  • Set a conservative maximum body size based on the largest legitimate Vapi event.
  • Configure connection and body-read timeouts.
  • Authenticate the webhook before treating it as activity associated with the current call.
  • Update last_activity_time only after signature verification, schema validation, and call-ID correlation succeed.
  • Use a production-capable HTTP server or reverse proxy with concurrency, request-size limits, rate limiting, and slow-client protections.
  • Return appropriate 400, 401, 403, or 413 responses instead of returning success for every exception.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/vapi_calls.py:217
Finding

Sensitive Call Transcripts Are Persisted Without Explicit Access Controls

Content
View full analysis

Vulnerability Details

File Location: scripts/vapi_calls.py, lines 68-76 and 217-222
Vulnerability Type: Insecure storage of sensitive call data
Risk Level: Medium

Vulnerable Code

python
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
}
python
# Save Log
try:
    log_dir = os.path.expanduser("~/.openclaw/workspace/logs/vapi-calls")
    os.makedirs(log_dir, exist_ok=True)
    fname = f"{log_dir}/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}_{result.get('call_id','unknown')}.json"
    with open(fname, 'w') as f: json.dump(result, f, indent=2)
    result["log_file"] = fname
except: pass

Technical Analysis

Completed call results include full transcripts and summaries, which may contain personal information, commercial information, credentials spoken during a call, or other confidential material. These fields are written automatically to persistent JSON files.

The code relies on the process's default umask and does not explicitly create the directory as mode 0700 or the log file as mode 0600. It also lacks encryption, redaction, retention limits, secure deletion, and an opt-in logging control. Depending on host configuration, other local users, services, backup systems, or workspace synchronization mechanisms may gain access to retained call content.

Attack Path

  1. A phone call completes and Vapi sends a transcript and summary in the end-of-call report.
  2. The Skill inserts those sensitive fields into call_result.
  3. The complete object is written under ~/.openclaw/workspace/logs/vapi-calls.
  4. A local user or process with access to the workspace, an overly broad backup service, or a synchronization mechanism reads the retained JSO ...[truncated 424 chars]
Remediation
View remediation

Remediation Suggestions

  • Make transcript and summary logging disabled by default and require explicit operator opt-in.
  • Store only the minimum metadata necessary for troubleshooting.
  • Redact phone numbers, credentials, payment data, and other sensitive content before persistence.
  • Create the log directory with mode 0700 and each log file with mode 0600, using APIs that apply secure permissions at creation time.
  • Consider encrypted storage with keys managed separately from the log directory.
  • Establish configurable retention limits and automatically delete expired records.
  • Document that Vapi processes call prompts, audio-derived transcripts, and summaries, and ensure appropriate notice and consent.
  • Avoid swallowing storage exceptions silently; report failures without exposing transcript content or credentials.

T08 · Insecure Dependencies

Note
Location
package.json:12
Finding

Unnecessary npm Dependency Expands the Supply-Chain Attack Surface

Content
View full analysis

Vulnerability Details

File Location: package.json, lines 12-17
Vulnerability Type: Unnecessary and ambiguously named dependency
Risk Level: Low

Vulnerable Code

json
"scripts": {
  "postinstall": "chmod +x scripts/vapi_calls.py"
},
"dependencies": {
  "requests": "^2.31.0"
},

The Skill metadata separately declares the Python dependency:

yaml
requires:
  env:
    - VAPI_API_KEY
    - VAPI_ASSISTANT_ID
    - VAPI_PHONE_NUMBER_ID
    - WEBHOOK_BASE_URL
  bins:
    - python3
  pip:
    - requests

Technical Analysis

The implementation is Python and imports the Python requests package. No JavaScript source uses an npm dependency. Nevertheless, package.json installs an npm package named requests, which is a separate package from Python's requests.

This duplicates an identifier across package ecosystems and installs code unnecessary for the declared runtime behavior. The version range is not accompanied by a lockfile in the audited project, so dependency resolution is not fully reproducible. Although no malicious dependency behavior was established during this audit, the unnecessary package increases supply-chain exposure beyond the minimum privileges and components needed by the Skill.

Attack Path

  1. An operator installs the project through npm.
  2. npm resolves requests and its dependency graph using the declared version range.
  3. Unnecessary third-party JavaScript code is added to the environment even though the Skill executes only Python.
  4. A compromise of that package, a transitive dependency, or the package-resolution process would affect installers despite providing no required functionality to the Skill.

Impact Assessment

No direct compromise is demonstrated by the reviewed source. The risk is an avoidable expansion of the trusted computing base and dependency-confusion potential between the npm and Python packages. The eventua ...[truncated 145 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove the npm requests dependency because no JavaScript code requires it.
  • Keep the Python dependency in a dedicated Python dependency manifest.
  • Pin Python dependencies to reviewed versions and use a lockfile or hash-verified requirements file.
  • Generate and retain dependency integrity metadata for reproducible installation.
  • Regularly scan direct and transitive dependencies for known vulnerabilities.
  • Retain only dependencies necessary for the Skill's declared telephony functionality.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (12)

Tainted flow: 'ASSISTANT_ID' from os.environ.get (line 19, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/vapi_calls.py (reported line 177)May include surrounding context.

python
overrides["endCallMessage"] = end_message

    try:
        response = requests.post(
            "https://api.vapi.ai/call",
            headers={"Authorization": f"Bearer {VAPI_API_KEY}"},
            json={

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill declares access to sensitive capabilities via env vars, package installation, network use, and implied file write/server behavior, but does not constrain tool scope with explicit permissions or allowed-tools. In an agent ecosystem, this broad undeclared capability surface can let the skill operate with more authority than a reviewer or runtime policy expects, increasing the chance of misuse or unintended data exposure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill enables autonomous outbound phone calls for persuasion, sales, bookings, reminders, and notifications without warning about recipient consent, call recording/privacy, telecom charges, or legal restrictions. Because this is a voice-calling skill aimed at real third parties, omission of these safeguards increases the risk of harassment, unlawful robocalling, privacy violations, and financial cost.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs users to expose a local webhook port to the public internet via Cloudflare Tunnel or ngrok, but does not describe authentication, request validation, replay protection, or general exposure risks. Making the host reachable from the internet can allow unauthorized requests, data leakage, denial of service, or abuse of the temporary server if the webhook endpoint is not strongly hardened.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The package description advertises autonomous AI phone calls without any warning about consent, privacy, recording, or transmission of call-related data. In a telephony skill, lack of disclosure increases the risk of unsafe deployment for spam, unauthorized outreach, or mishandling of personal data in a real-world channel.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/vapi_calls.py (reported line 177)May include surrounding context.

python
overrides["endCallMessage"] = end_message

    try:
        response = requests.post(
            "https://api.vapi.ai/call",
            headers={"Authorization": f"Bearer {VAPI_API_KEY}"},
            json={

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/vapi_calls.py (reported line 177)May include surrounding context.

python
overrides["endCallMessage"] = end_message

    try:
        response = requests.post(
            "https://api.vapi.ai/call",
            headers={"Authorization": f"Bearer {VAPI_API_KEY}"},
            json={

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/vapi_calls.py (reported line 178)May include surrounding context.

python
try:
        response = requests.post(
            "https://api.vapi.ai/call",
            headers={"Authorization": f"Bearer {VAPI_API_KEY}"},
            json={
                "assistantId": ASSISTANT_ID,

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill stores full call results under the local workspace logs directory, and those results may include transcripts, summaries, phone metadata, and call identifiers. Persisting this data exceeds the minimum needed to place calls and creates a privacy exposure if the workspace is shared, backed up, or later accessed by other tools or users.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code writes potentially sensitive conversation transcripts and summaries to disk without any notice, consent, or visibility to the user. Phone calls can contain personal, financial, health, or business information, so silent persistence materially increases privacy and compliance risk.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 12)May include surrounding context.

json
"postinstall": "chmod +x scripts/vapi_calls.py"
  },
  "dependencies": {
    "requests": "^2.31.0"
  },
  "skill": {
    "entry": "SKILL.md",

Vague Triggers

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This is a manifest file, so vague-trigger checks apply. The skill metadata identifies the entrypoint and required environment variables, but it does not describe any activation phrases, allowed contexts, or exclusion conditions, making invocation scope unclear from the manifest alone.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.