Back to skill

Security audit

Zoom Meetings

Security checks for vulnerabilities and agentic risk

Overview

This Zoom meeting skill mostly does what it says, but it can read meeting access details and delete meetings with weak scoping and no clear confirmation safeguards.

Install only if you are comfortable giving this skill access to your Zoom Server-to-Server OAuth credentials and allowing it to create, read, list, and delete Zoom meetings. Use least-privileged Zoom scopes, avoid broad admin credentials, verify meeting IDs before deletion, and review created meeting defaults because waiting room is disabled and join-before-host is enabled.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/zoom_api.py:275
Finding
Unvalidated identifiers are interpolated into authenticated Zoom API paths## Vulnerability Details **File Location**: `scripts/zoom_api.py:275-334` **Vulnerability Type**: Improper validation and encoding of URL path segments **Risk Level**: Medium ### Vulnerable Code ```python def get_meeting(self, meeting_id: str) -> dict: """ Retrieve meeting details. Args: meeting_id: Zoom meeting ID. Returns: Meeting details. """ response = self._request("GET", f"/meetings/{meeting_id}") return { "meeting_id": str(response.get("id", meeting_id)), "join_url": response.get("join_url", ""), "password": response.get("password", ""), "topic": response.get("topic", ""), "start_time": response.get("start_time", ""), "duration": response.get("duration", 0), "timezone": response.get("timezone", ""), "status": response.get("status", "") } def list_meetings(self, user_id: str = "me") -> list: """ List all meetings for a user. Args: user_id: User ID or 'me' for authenticated user. Returns: List of meeting summaries. """ response = self._request("GET", f"/users/{user_id}/meetings") meetings = response.get("meetings", []) return [ { "meeting_id": str(m.get("id", "")), "topic": m.get("topic", ""), "start_time": m.get("start_time", ""), "duration": m.get("duration", 0), "join_url": m.get("join_url", "") } for m in meetings ] def delete_meeting(self, meeting_id: str) -> dict: """ Delete a Zoom meeting. Args: meeting_id: Zoom meeting ID. Returns: Confirmation response. """ self._request("DELETE", f"/meetings/{meeting_id}") return { "success": True, "meeting_id": meeting ...[truncated 1991 chars]
Remediation
## Remediation Suggestions - Validate meeting IDs using Zoom's documented identifier format. If only numeric meeting IDs are supported, enforce a strict expression such as `^[0-9]+$`. - Restrict `user_id` to `me` unless arbitrary user access is explicitly required. Otherwise, validate it against Zoom's documented user identifier format. - Encode every variable URL path segment using `urllib.parse.quote(value, safe="")` after validation. - Reject values containing `/`, `\`, `?`, `#`, percent-encoded delimiters, control characters, or traversal components. - Construct endpoints through dedicated helper functions rather than general string interpolation. - Assign only the minimum Zoom OAuth scopes needed to create, read, list, and delete meetings. - Add tests covering traversal strings, encoded delimiters, query fragments, empty identifiers, oversized identifiers, and malformed Unicode input.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/zoom_api.py:239
Finding
Created meetings use insecure participant admission defaults## Vulnerability Details **File Location**: `scripts/zoom_api.py:239-244` **Vulnerability Type**: Insecure meeting access-control configuration **Risk Level**: Medium ### Vulnerable Code ```python payload = { "topic": topic, "type": 2, # Scheduled meeting "start_time": start_time, "duration": duration, "timezone": timezone, "settings": { "join_before_host": True, "mute_upon_entry": False, "waiting_room": False } } ``` The same insecure defaults are documented in `references/zoom_api_reference.md:44-49`: ```json "settings": { "join_before_host": true, "mute_upon_entry": false, "waiting_room": false } ``` ### Technical Analysis Every meeting created by the Skill permits participants to join before the host and disables the waiting room. These settings bypass host-controlled admission and are imposed automatically without requesting user confirmation. `mute_upon_entry` is also disabled, allowing participants to enter with active audio. While that setting alone is not an authorization failure, it increases the disruption risk created by the permissive admission configuration. These defaults are not necessary for the declared functionality of creating and managing Zoom meetings. More restrictive defaults would provide the same core functionality while preserving host control. ### Attack Path 1. A meeting is created using the Skill. 2. The meeting join URL, meeting ID, or password is disclosed, forwarded, guessed, or otherwise obtained by an unauthorized participant. 3. The participant connects before the host arrives. 4. Because `waiting_room` is disabled and `join_before_host` is enabled, the participant may enter without host approval. 5. Because participants are not muted on entry, the unauthorized participant may immediately disrupt the meeting or interact with other early attendees. ### Impact Assessment The issue ca ...[truncated 351 chars]
Remediation
## Remediation Suggestions - Use secure defaults: ```python "settings": { "join_before_host": False, "mute_upon_entry": True, "waiting_room": True } ``` - Expose these controls as explicit meeting-creation parameters when users need different behavior. - Require clear user confirmation before disabling the waiting room or allowing participants to join before the host. - Preserve Zoom account-level security policies rather than unconditionally overriding them with weaker settings. - Update `SKILL.md` and `references/zoom_api_reference.md` to document secure defaults and the risks of weakening participant admission controls. - Add tests verifying that newly created meetings use the secure configuration unless an authorized user explicitly requests otherwise.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (21)

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
Use this skill whenever the user mentions Zoom meetings, wants to schedule a meeting,
  needs meeting details, or asks to manage Zoom calls—even if they don't explicitly
  say "use the zoom-meeting skill." Supports both natural language requests and
  structured JSON commands. Always respond with human-readable output (no JSON).
---

# Zoom Meeting Skill
Confidence
88% confidence
Finding
The instruction to 'Always respond' constrains agent behavior and can discourage safe refusal, escalation, or confirmation patterns when a request is risky or ambiguous. In a skill capable of deletion and data retrieval, anti-refusal language makes unsafe execution more likely.

Credential Access

High
Category
Privilege Escalation
Content
}
```

The skill automatically obtains and refreshes access tokens.

## Supported Actions
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
- Create: `POST /users/me/meetings`
- Get: `GET /meetings/{meetingId}`
- List: `GET /users/me/meetings`
- Delete: `DELETE /meetings/{meetingId}`

## Defaults
Confidence
86% confidence
Finding
The skill exposes a direct destructive endpoint parameterized by user-supplied `meetingId` without documented safeguards such as ownership checks, confirmation, or ambiguity handling. This creates a realistic path for accidental or manipulated deletion of meetings through prompt injection or mistaken intent resolution.

Credential Access

High
Category
Privilege Escalation
Content
### Token Flow

1. Read credentials from file
2. Request access token from `https://zoom.us/oauth/token`
3. Use token in `Authorization: Bearer <token>` header
4. Token expires after 3600 seconds (1 hour)
5. Automatically refresh when expired
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### Delete Meeting

**Endpoint:** `DELETE /meetings/{meetingId}`

**Response:** `204 No Content`
Confidence
88% confidence
Finding
The documented delete endpoint accepts a meeting ID parameter with no described validation, ownership checks, confirmation step, or guardrails against arbitrary target selection. In an agent-integrated skill, this creates a realistic path for parameter abuse where a user or prompt injection causes deletion of the wrong meeting or bulk destructive actions.

Credential Access

High
Category
Privilege Escalation
Content
def _get_access_token(self) -> str:
        """
        Obtain or refresh access token using Server-to-Server OAuth.
        
        Returns:
            Access token string.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def _get_access_token(self) -> str:
        """
        Obtain or refresh access token using Server-to-Server OAuth.
        
        Returns:
            Access token string.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
Obtain or refresh access token using Server-to-Server OAuth.
        
        Returns:
            Access token string.
        """
        # Check if we have a valid cached token
        if self._access_token and self._token_expires_at:
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if response.status_code != 200:
                raise ZoomAPIError(
                    "Failed to obtain access token",
                    status_code=response.status_code,
                    details={"response": response.text}
                )
Confidence
82% confidence
Finding
On token acquisition failure, the code stores response.text inside the exception details and later prints those details to stdout. Error bodies from OAuth/token endpoints can contain sensitive operational information and, depending on provider behavior or intermediaries, may include credential-related data that should not be surfaced to end users or logs.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill performs sensitive operations requiring file reads and outbound network access, but it does not declare any tool scope or permission boundary. This increases the chance an agent invokes the skill with broader-than-necessary capabilities and makes review and enforcement of least privilege harder.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The activation criteria are broad enough to trigger on ordinary Zoom-related conversation, including requests that may not actually intend to call an API or expose meeting data. Over-broad routing can cause unintended access to meeting details or accidental state-changing operations.

Session Persistence

Medium
Category
Rogue Agent
Content
## When to Use

Use this skill when the user:
- Wants to create or schedule a Zoom meeting
- Requests meeting details (join URL, password, etc.)
- Wants to see all upcoming meetings
- Needs to cancel/delete a meeting
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
Using `Asia/Almaty` as a silent default timezone can create meetings at unintended times for users in other regions. While not a direct security exploit, it can cause operational harm, missed meetings, and social-engineering opportunities from mis-scheduled events.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill includes destructive deletion behavior but does not require a warning or confirmation step before deleting a meeting. A mistaken parse, ambiguous request, or malicious prompt chaining could lead to irreversible cancellation of meetings.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
Documenting a global locale-specific timezone default without user choice reinforces unsafe scheduling behavior across all invocations. This can lead to systematically incorrect meeting times and unexpected disclosure of availability patterns.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The reference documents a destructive delete operation without any caution, confirmation guidance, or mention of irreversibility. In an agent skill that may act on natural-language requests, this increases the chance of accidental or overly broad meeting deletion triggered by ambiguous user input.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The skill hard-codes a default timezone of Asia/Aqtobe and does not require or strongly encourage user-specified timezone selection. In a scheduling skill, this can cause meetings to be created at unintended times, potentially leading to missed meetings, privacy issues, or business disruption.

External Transmission

Medium
Category
Data Exfiltration
Content
class ZoomClient:
    """Zoom API client using Server-to-Server OAuth."""
    
    BASE_URL = "https://api.zoom.us/v2"
    TOKEN_URL = "https://zoom.us/oauth/token"
    CREDENTIAL_PATH = Path.home() / ".openclaw" / "credentials" / "zoom.json"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The skill accepts a caller-controlled user_id for list_meetings and forwards it directly to /users/{user_id}/meetings, enabling enumeration of meetings for arbitrary Zoom users if the OAuth app has sufficient scope. This exceeds the manifest's implied 'my meetings' behavior and can expose other users' meeting metadata and join URLs within the authorized account.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The delete path performs an irreversible meeting deletion as soon as a meeting_id is supplied, with no confirmation, dry-run, or safeguard against accidental or induced destructive actions. In an agent context, this increases the risk that a malformed request, prompt injection, or user misunderstanding could cancel legitimate meetings without adequate friction.

Context-Inappropriate Capability

Low
Confidence
81% confidence
Finding
The manifest describes creating, retrieving, listing, and deleting Zoom meetings via the Zoom REST API, but does not mention local filesystem access for secret retrieval. While authentication is necessary, directly reading a credential file from ~/.openclaw/credentials/zoom.json is an additional capability beyond the stated user-facing purpose.

Static analysis

No suspicious patterns detected.