Back to skill

Security audit

Zoom Manager

Security checks for vulnerabilities and agentic risk

Overview

This Zoom skill does what it claims, but it asks for broad account-level permissions and performs sensitive meeting actions without enough safeguards.

Review this before installing on a real Zoom account. Prefer a dedicated least-privilege Zoom OAuth app, remove unused user and recording read scopes, avoid the legacy config.json scripts, rotate any secret stored in files, and be careful with delete/update commands and default cloud recording behavior.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:27
Finding

Unnecessary Zoom API scopes violate least-privilege requirements

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:27-35
Vulnerability Type: Excessive OAuth permissions
Risk Level: Medium

Vulnerable configuration:

text
2. **Scopes Required**: In the Zoom App Marketplace (Scopes tab), add the following:
   - **Meetings**:
     - `meeting:read:admin` / `meeting:read:master` (View meetings)
     - `meeting:write:admin` / `meeting:write:master` (Create and update meetings)
     - `meeting:delete:admin` / `meeting:delete:master` (Delete meetings)
   - **Users**: `user:read:admin`
   - **Recordings**: `recording:read:admin`

Technical Analysis

The Skill instructs users to grant user:read:admin and recording:read:admin, but none of the audited scripts calls the Zoom Users API or Recordings API. The implemented operations only authenticate and create, list, retrieve, update, or delete meetings.

Enabling auto_recording: "cloud" when creating a meeting does not require permission to read existing recordings. Consequently, the user-read and recording-read scopes exceed the minimum privileges needed for the declared and implemented functionality.

The account-level Server-to-Server OAuth credentials can obtain tokens carrying all scopes assigned to the Zoom application. Any compromise of those credentials or tokens would therefore expose capabilities beyond meeting management.

Attack Path

  1. A user follows SKILL.md and grants all documented scopes to the Server-to-Server OAuth application.
  2. An attacker obtains the Zoom client ID and client secret, or captures a valid OAuth bearer token, through an unrelated host, process, log, backup, or credential compromise.
  3. The attacker requests an account-level access token from Zoom if only the client credentials were obtained.
  4. The attacker uses the unnecessarily granted user:read:admin or recording:read:admin scope to call Zoom APIs that the Skill itself never uses.
  5. The attacker accesses user o ...[truncated 479 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove user:read:admin and recording:read:admin from the documented scope requirements and from existing Zoom application configurations.
  2. Grant only the narrowest meeting read, write, and delete scopes required for the commands that users intend to enable.
  3. Do not instruct users to grant both admin and master variants indiscriminately; document which variant applies to each supported Zoom account type.
  4. Separate read-only and mutating deployments where practical. A deployment that only lists or retrieves meetings should not receive write or delete permission.
  5. Revoke existing tokens and rotate the client secret after reducing scopes so previously issued credentials cannot retain broader access.
  6. Add a permissions-to-command matrix to SKILL.md and periodically verify it against the actual API endpoints used by the code.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/create_meeting.js:5
Finding

Legacy scripts require Zoom credentials to be stored in plaintext configuration files

Content
View full analysis

Vulnerability Details

File Locations:

  • scripts/create_meeting.js:5-9
  • scripts/create_meeting.py:7-15
  • scripts/delete_meeting.js:6-17
  • scripts/delete_meeting.py:7-18
  • scripts/get_meeting_info.js:6-10
  • scripts/get_meeting_info.py:12-20
  • scripts/list_meetings.js:6-10
  • scripts/list_meetings.py:11-18

Vulnerability Type: Plaintext sensitive credential storage
Risk Level: Low

Vulnerable code segments:

scripts/create_meeting.js:5-9

javascript
// Load config
async function loadConfig() {
  const configPath = path.join(__dirname, '..', 'config.json');
  const data = await fs.readFile(configPath, 'utf8');
  return JSON.parse(data);
}

scripts/create_meeting.py:7-15

python
# Load config
config_path = os.path.join(os.path.dirname(__file__), '..', 'config.json')
with open(config_path, 'r') as f:
    config = json.load(f)

ACCOUNT_ID = config['account_id']
CLIENT_ID = config['client_id']
CLIENT_SECRET = config['client_secret']
USER_ID = config['user_id']

scripts/delete_meeting.js:6-17

javascript
// Load config
const configPath = path.join(__dirname, '..', 'config.json');
let config;

async function loadConfig() {
    try {
        const data = await fs.readFile(configPath, 'utf8');
        config = JSON.parse(data);
    } catch (err) {
        console.error('Error loading config:', err.message);
        process.exit(1);
    }
}

scripts/delete_meeting.py:7-18

python
# Загрузка конфигурации
try:
    with open('config.json', 'r') as f:
        config = json.load(f)
except FileNotFoundError:
    print("Error: config.json not found. Please create it with your Zoom API credentials.")
    sys.exit(1)

ACCOUNT_ID = config['account_id']
CLIENT_ID = config['client_id']
CLIENT_SECRET = config['client_secret']
USER_ID = config['user_id']

scripts/get_meeting_info.js:6-10

javascri
...[truncated 3677 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the unused duplicate JavaScript and Python scripts if scripts/zoom-cli.js is the supported interface.
  2. If the scripts must remain, migrate every script to environment variables or an operating-system-backed secret manager.
  3. Centralize credential loading in one maintained module to prevent configuration behavior from diverging between commands.
  4. Validate that all required variables exist without printing their values:
    • ZOOM_CLIENT_ID
    • ZOOM_CLIENT_SECRET
    • ZOOM_ACCOUNT_ID
    • Optional ZOOM_USER_ID
  5. Add config.json to .gitignore and secret-scanning rules as defense in depth, even after removing file-based loading.
  6. Document credential rotation procedures and advise users to rotate any secret previously stored in a repository or shared workspace.
  7. If file-based storage is unavoidable, keep the file outside the project directory, require owner-only permissions, reject insecure permissions at runtime, and never resolve the credential file relative to the current working directory.
  8. Ensure errors never include authorization headers, access tokens, client secrets, or complete sensitive response bodies.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (43)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The code’s behavior is narrowly focused on creating a Zoom meeting through the Zoom OAuth/API flow. That aligns partially with the declared Zoom-management purpose, but the declared description materially overstates the implemented capabilities in this supplied chunk by claiming create, list, delete, and update support. No unrelated or suspicious extra capability is present beyond reading config and calling Zoom APIs, but the actual behavior does not fully match the declared breadth of functionality.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description says the skill manages Zoom meetings/events by creating, listing, deleting, and updating them. However, this code chunk only performs a read operation: it fetches information for a specific Zoom meeting. While using Zoom OAuth is consistent with the declared resource and authentication method, the primary behavior in this code is materially different from the listed management actions. There is no implementation here for create, list, delete, or update operations, and the undeclared capability is viewing/getting meeting details.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/create_meeting.js (reported line 12)May include surrounding context.

js
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/create_meeting.js (reported line 30)May include surrounding context.

js
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/delete_meeting.js (reported line 42)May include surrounding context.

js
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/delete_meeting.py (reported line 33)May include surrounding context.

python
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/get_meeting_info.js (reported line 13)May include surrounding context.

js
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/get_meeting_info.js (reported line 31)May include surrounding context.

js
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/get_meeting_info.py (reported line 23)May include surrounding context.

python
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/list_meetings.js (reported line 30)May include surrounding context.

js
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/zoom-cli.js (reported line 46)May include surrounding context.

js
return JSON.parse(data);
}

// Get OAuth access token
async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({

Missing User Warnings

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

This script performs an irreversible destructive action against the Zoom API based solely on a command-line meeting ID, with no confirmation prompt, dry-run mode, or validation step. In an agent or automation context, that increases the risk of accidental or unauthorized deletion of meetings, causing service disruption or loss of scheduling data.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill advertises executable Node.js commands and requires secrets plus network access, but it does not declare any explicit tool scope such as allowed tools or permissions. In an agent environment, missing capability declarations weakens least-privilege controls and can allow broader-than-expected access to environment variables, files, and outbound network operations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Advertising automatic cloud recording without any privacy, consent, retention, or legal-use warning can lead users to enable recording in contexts where participants have not consented or local law requires notice. In a meeting-management skill, this is especially sensitive because it automates a surveillance-affecting feature at scale.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill documents a destructive delete command with no warning, confirmation guidance, or mention of irreversible effects. In an automation context, this increases the risk of accidental or scripted deletion of meetings, disrupting operations and causing loss of scheduling data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script unconditionally enables Zoom cloud recording for every created meeting without any user confirmation, notice, or policy check. This can capture sensitive meeting content by default, creating privacy, compliance, and data retention risks, especially if users do not expect recording to occur.

Content

No source excerpt is available for this finding.

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/create_meeting.py (reported line 50)May include surrounding context.

python
"auto_recording": "cloud"
        }
    }
    response = requests.post(url, headers=headers, json=payload)
    response.raise_for_status()
    return response.json()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code reads Zoom API credentials and account identifiers from a local config.json file, but the only message shown is when the file is missing. There is no comment, docstring, or user-facing disclosure warning that the script consumes sensitive credentials, which falls under missing warnings for code files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sends OAuth client credentials from config.json to Zoom's token endpoint, but there is no confirmation prompt or user-facing disclosure around this sensitive network operation. While the script's purpose is to retrieve meeting information, it does not visibly warn the user that stored credentials will be transmitted to obtain an access token.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script sends a user-supplied meeting ID to Zoom and retrieves meeting details over the network, but there is no visible disclosure that meeting-related data will be transmitted to an external service. This is a privacy-relevant operation and lacks an explicit warning in the file.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The script prints the full Zoom meeting object directly to stdout, which may include sensitive metadata such as join URLs, passwords, host details, settings, and scheduling information. In agent or shared-console environments, stdout is often logged or exposed to other components, creating unnecessary data leakage risk.

Content

No source excerpt is available for this finding.

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/create_meeting.js (reported line 14)May include surrounding context.

js
}

async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({
    grant_type: 'account_credentials',
    account_id: config.account_id

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/create_meeting.js (reported line 40)May include surrounding context.

js
}

async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({
    grant_type: 'account_credentials',
    account_id: config.account_id

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/create_meeting.py (reported line 18)May include surrounding context.

python
}

async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({
    grant_type: 'account_credentials',
    account_id: config.account_id

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/create_meeting.py (reported line 29)May include surrounding context.

python
}

async function getAccessToken(config) {
  const url = 'https://api.zoom.us/oauth/token';
  const params = new URLSearchParams({
    grant_type: 'account_credentials',
    account_id: config.account_id

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/zoom-cli.js:10

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/create_meeting.py:28

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/delete_meeting.py:37