Back to skill

Security audit

Zoho

Security checks for vulnerabilities and agentic risk

Overview

This Zoho skill is purpose-aligned, but it needs review because it combines broad business-data authority, local long-lived secrets, destructive examples, and third-party meeting transcription without enough safeguards.

Review this skill before installing. Use least-privilege Zoho scopes, keep secrets in a managed secret store instead of a skill-directory .env, require explicit approval for writes/deletes and meeting downloads, and only run the summarizer when everyone is authorized for recordings to be sent to Gemini. The package should also include the missing CLI wrapper or clearly declare and verify that dependency.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/standup-summarizer.sh:93
Finding
Zoho OAuth Token Sent to an Unvalidated Recording URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/standup-summarizer.sh`, lines 93 and 109-115 **Vulnerability Type**: Credential disclosure through an unvalidated remote URL **Risk Level**: High ### Vulnerable Code ```bash DOWNLOAD_URL=$(echo "$REC" | jq -r '.downloadUrl // .publicDownloadUrl') # Download recording echo " ⬇️ Downloading..." TOKEN=$(get_token) MP4_FILE="${TMP_DIR}/recording_${i}.mp4" HTTP_CODE=$(curl -s -w "%{http_code}" -o "$MP4_FILE" -L \ -H "Authorization: Zoho-oauthtoken ${TOKEN}" \ "$DOWNLOAD_URL") ``` ### Technical Analysis `DOWNLOAD_URL` is extracted from a remote Zoho API response and passed directly to `curl` without validating its scheme or hostname. The request includes a valid Zoho OAuth access token in the `Authorization` header. Although the URL normally points to a Zoho-controlled file service, the script does not enforce that security assumption. If the recording metadata is maliciously modified, a compromised API response is received, or an unexpected URL is returned, the initial request can be sent to an attacker-controlled server together with the OAuth token. The `-L` option also follows redirects. While curl versions commonly restrict forwarding authorization headers across different hosts, relying on client-version-specific redirect behavior is not an adequate security boundary. The initial unvalidated URL remains sufficient to expose the token. ### Attack Path 1. An attacker compromises or manipulates the recording metadata received by the script. 2. The `downloadUrl` or `publicDownloadUrl` field is changed to an attacker-controlled HTTPS endpoint. 3. The script extracts that URL without validation. 4. The script obtains a valid Zoho access token through `bin/zoho token`. 5. `curl` sends `Authorization: Zoho-oauthtoken <token>` to the attacker-controlled endpoint. 6. The attacker captures and reuses the token until it expires. ### Impact Assessment A successful exploit discloses a valid Zoho ...[truncated 533 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse every recording URL and require the `https` scheme. 2. Enforce an explicit allowlist of expected Zoho file-service hostnames for each supported region. 3. Reject URLs containing unexpected ports, user-information components, IP literals, or non-Zoho hostnames. 4. Do not attach the OAuth header to a URL unless its destination has passed validation. 5. Disable automatic redirects or validate each redirect destination before following it. 6. Prefer a fixed Zoho API endpoint that accepts a recording identifier instead of trusting a URL returned in metadata. 7. Reduce OAuth scopes to only those required for the requested operation and avoid requesting CRM or Projects write access for meeting transcription. 8. Revoke and rotate any token suspected of having been sent to an untrusted endpoint. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/standup-summarizer.sh:9
Finding
Predictable and Potentially Permissive Temporary Storage Exposes Meeting Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/standup-summarizer.sh`, lines 9, 41-45, 112-113, 136-137, 183-198, and 225-227 **Vulnerability Type**: Unsafe temporary directory and sensitive plaintext temporary files **Risk Level**: High ### Vulnerable Code ```bash TMP_DIR="/tmp/standup-$$" # ── Ensure dirs ────────────────────────────────────────────────────── mkdir -p "$TMP_DIR" "$(dirname "$PROCESSED_FILE")" [[ -f "$PROCESSED_FILE" ]] || echo '[]' > "$PROCESSED_FILE" cleanup() { rm -rf "$TMP_DIR"; } trap cleanup EXIT ``` Sensitive files are subsequently created in that predictable directory: ```bash MP4_FILE="${TMP_DIR}/recording_${i}.mp4" HTTP_CODE=$(curl -s -w "%{http_code}" -o "$MP4_FILE" -L \ -H "Authorization: Zoho-oauthtoken ${TOKEN}" \ "$DOWNLOAD_URL") WAV_FILE="${TMP_DIR}/audio_${i}.wav" ffmpeg -i "$MP4_FILE" -vn -acodec pcm_s16le -ar 16000 -ac 1 "$WAV_FILE" -y -loglevel error 2>&1 ``` ```bash B64_FILE="${TMP_DIR}/audio_${i}.b64" base64 -w0 "$WAV_FILE" > "$B64_FILE" PROMPT_TEXT="Transcribe this meeting recording. The speakers are Egyptian and speak in Egyptian Arabic mixed with English technical terms. Provide a faithful transcription preserving the language as spoken (Arabic parts in Arabic script, English parts in English). Include speaker changes where detectable. Do NOT summarize — provide the full transcription." GEMINI_BODY=$(jq -n --rawfile audio "$B64_FILE" --arg prompt "$PROMPT_TEXT" \ '{ "contents": [{ "parts": [ {"inline_data": {"mime_type": "audio/wav", "data": $audio}}, {"text": $prompt} ] }], "generationConfig": {"temperature": 0.1, "maxOutputTokens": 8192} }') rm -f "$B64_FILE" ``` ```bash # Save transcript TRANSCRIPT_FILE="${TMP_DIR}/transcript_${i}.txt" echo "$TRANSCRIPT" > "$TRANSCRIPT_FILE" ``` ### Technical Analysis The script constructs its temporary directory from its process ID rather than using an atomic secure temporary-directory API. Process IDs are pr ...[truncated 2231 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Set a restrictive creation mask at the beginning of the script: ```bash umask 077 ``` 2. Create the directory atomically with `mktemp`: ```bash TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/standup.XXXXXXXXXX")" ``` 3. Verify that the directory is owned by the current user and has mode `0700`. 4. Avoid `mkdir -p` for security-sensitive temporary paths. 5. Create temporary files atomically and refuse to follow symbolic links where supported. 6. Keep sensitive data in memory when practical and remove unnecessary transcript copies. 7. Install cleanup handlers for relevant signals in addition to normal exit. 8. Consider encrypted storage when temporary plaintext meeting content cannot be avoided. 9. Run the Skill under a dedicated unprivileged account to limit file-clobber consequences. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/standup-summarizer.sh:156
Finding
Gemini API Key Exposed in Request URLs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/standup-summarizer.sh`, lines 156-157, 202-203, and 216-217 **Vulnerability Type**: Secret placed in URL query parameters **Risk Level**: Medium ### Vulnerable Code ```bash UPLOAD_RESP=$(curl -s -X POST \ "https://generativelanguage.googleapis.com/upload/v1beta/files?key=${GEMINI_API_KEY}" \ -H "X-Goog-Upload-Command: start, upload, finalize" \ -H "X-Goog-Upload-Header-Content-Length: ${WAV_BYTES}" \ -H "X-Goog-Upload-Header-Content-Type: audio/wav" \ -H "Content-Type: audio/wav" \ --data-binary "@${WAV_FILE}") ``` ```bash GEMINI_RESP=$(curl -s -X POST \ "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}" \ -H "Content-Type: application/json" \ -d "@${GEMINI_BODY_FILE}") ``` ```bash GEMINI_RESP=$(curl -s -X POST \ "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}" \ -H "Content-Type: application/json" \ -d "@${GEMINI_BODY_FILE}") ``` ### Technical Analysis The Gemini API key is embedded in the query string of every upload and generation request. HTTPS protects the URL while in transit, but it does not prevent exposure through local process inspection, shell tracing, endpoint monitoring, proxy telemetry, HTTP access logs, error reports, or diagnostic tooling. Secrets in query parameters are more likely to be retained by operational systems than secrets supplied through dedicated authentication headers. Because the key is reused, one disclosure can enable unauthorized requests until the key is revoked or restricted. ### Attack Path 1. The victim runs the summarizer while another local user or monitoring system can observe process command lines or outbound request metadata. 2. The observer captures a URL containing `?key=<GEMINI_API_KEY>`. 3. The observer extracts the API key. 4. The key is used to make unauthorized Gemini API requests. 5. The unauth ...[truncated 504 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use Google’s supported API-key authentication header instead of a query parameter: ```bash -H "x-goog-api-key: ${GEMINI_API_KEY}" ``` 2. Remove `?key=${GEMINI_API_KEY}` from all Gemini URLs. 3. Restrict the key to only the required Generative Language API and permitted deployment environments. 4. Apply usage quotas and billing alerts. 5. Ensure shell tracing and verbose curl output are disabled around secret-bearing operations. 6. Review proxy and endpoint telemetry for prior URL exposure. 7. Rotate the key if it may already have appeared in logs or process-monitoring records. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/standup-summarizer.sh:153
Finding
Uploaded Meeting Audio Is Not Deleted from the Gemini File API<![CDATA[ ## Vulnerability Details **File Location**: `scripts/standup-summarizer.sh`, lines 153-180 and 264-266 **Vulnerability Type**: Excessive third-party retention of sensitive meeting data **Risk Level**: Medium ### Vulnerable Code ```bash if [[ "$WAV_BYTES" -gt 20000000 ]]; then # Large file: upload via File API first echo " 📤 Large file — uploading to Gemini File API..." UPLOAD_RESP=$(curl -s -X POST \ "https://generativelanguage.googleapis.com/upload/v1beta/files?key=${GEMINI_API_KEY}" \ -H "X-Goog-Upload-Command: start, upload, finalize" \ -H "X-Goog-Upload-Header-Content-Length: ${WAV_BYTES}" \ -H "X-Goog-Upload-Header-Content-Type: audio/wav" \ -H "Content-Type: audio/wav" \ --data-binary "@${WAV_FILE}") FILE_URI=$(echo "$UPLOAD_RESP" | jq -r '.file.uri // empty') if [[ -z "$FILE_URI" ]]; then echo " ❌ File upload failed: $(echo "$UPLOAD_RESP" | jq -r '.error.message // "unknown"')" continue fi echo " ✅ Uploaded: ${FILE_URI}" GEMINI_BODY=$(jq -n \ --arg uri "$FILE_URI" \ '{ "contents": [{ "parts": [ {"file_data": {"mime_type": "audio/wav", "file_uri": $uri}}, {"text": "Transcribe this meeting recording. The speakers are Egyptian and speak in Egyptian Arabic mixed with English technical terms. Provide a faithful transcription preserving the language as spoken (Arabic parts in Arabic script, English parts in English). Include speaker changes where detectable. Do NOT summarize — provide the full transcription."} ] }], "generationConfig": {"temperature": 0.1, "maxOutputTokens": 8192} }') ``` The only explicit cleanup removes local files: ```bash # Clean up large files immediately to save disk rm -f "$MP4_FILE" "$WAV_FILE" ``` ### Technical Analysis Audio larger than 20 MB is uploaded to the Gemini File API and referenced through `FILE_URI`. The script does not issue a corresponding remote deletion request after successful trans ...[truncated 1527 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Capture the remote file name or identifier returned by the upload API. 2. Add a deletion function that invokes the Gemini File API’s supported delete operation. 3. Call remote deletion immediately after transcription completes. 4. Register remote deletion in cleanup and error paths so it also runs after generation failures or interrupted processing. 5. Record deletion failures without printing secrets, and retry them through a bounded cleanup mechanism. 6. Document that meeting audio is transferred to Google and state the applicable retention behavior. 7. Require explicit authorization before sending sensitive recordings to a third-party model. 8. Prefer an existing Zoho-generated transcript when available to avoid uploading raw audio. 9. Minimize uploaded data, use the shortest necessary retention period, and apply project-level access controls. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (25)

Credential Access

High
Category
Privilege Escalation
Content
```bash
zoho help          # Show all commands
zoho token         # Print current access token (auto-refreshes)
```

## Authentication Setup
Confidence
97% confidence
Finding
A command that prints the current access token is highly risky in an agent context because tokens can be exposed in terminal output, logs, chat transcripts, screenshots, or copied accidentally. Since the token enables authenticated API access, disclosure can immediately permit unauthorized reads or writes within the Zoho tenant until expiry.

External Script Fetching

High
Category
Supply Chain
Content
### Step 3: Exchange Code for Refresh Token

Run this curl command (replace placeholders):

```bash
curl -X POST "https://accounts.zoho.com/oauth/v2/token" \
Confidence
60% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Credential Access

High
Category
Privilege Escalation
Content
}
```

Save the **refresh_token** — this is your long-lived credential. The access token expires in 1 hour, but the CLI auto-refreshes it using the refresh token.

### Step 4: Find Your Org IDs
Confidence
88% confidence
Finding
This line explains that the refresh token is a long-lived credential and that the access token auto-refreshes, which is accurate documentation rather than active credential theft. However, the context does normalize handling a high-value persistent credential without sufficient safeguards, which contributes to operational risk when combined with local .env storage and shell-based workflows.

Credential Access

High
Category
Privilege Escalation
Content
**CRM/Projects Org ID:**
```bash
# After setting up .env with client_id, client_secret, refresh_token:
zoho raw GET /crm/v7/org | jq '.org[0].id'
```
Confidence
92% confidence
Finding
The skill explicitly instructs users to configure a .env containing client credentials and a long-lived refresh token. In agent environments with shell/file access, colocating secrets in the skill directory materially increases the risk of unintended disclosure through logs, command output, workspace sharing, or other tools reading local files.

Credential Access

High
Category
Privilege Escalation
Content
**Meeting Org ID:**
Log into [Zoho Meeting](https://meeting.zoho.com) → Admin Settings → look for the Organization ID in the URL or settings page. It's different from the CRM org ID.

### Step 5: Configure .env

Create `.env` in the skill directory:
Confidence
90% confidence
Finding
The .env setup centralizes multiple sensitive values, including client secret, refresh token, and organization identifiers, in a predictable local file. While org IDs alone are not secret, bundling them with reusable credentials makes compromise easier and increases the blast radius if the workspace is exposed.

Credential Access

High
Category
Privilege Escalation
Content
TMP_DIR="/tmp/standup-$$"

# ── Load env ──────────────────────────────────────────────────────────
source "${ZOHO_SKILL_DIR}/.env"
GEMINI_API_KEY="${GEMINI_API_KEY:-}"
ZOHO_MEETING_ORG_ID="${ZOHO_MEETING_ORG_ID:-853106938}"
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
cleanup() { rm -rf "$TMP_DIR"; }
trap cleanup EXIT

# ── Get Zoho access token ────────────────────────────────────────────
get_token() {
  "${ZOHO_SKILL_DIR}/bin/zoho" token
}
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README promotes automated downloading of meeting recordings and sending them into a transcription pipeline, but it does not warn about consent, recording policies, retention, or sharing sensitive meeting content with a third-party model provider. In a workplace context, this can expose confidential discussions or regulated personal data and may lead users to deploy the workflow in violation of internal policy or applicable privacy law.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill documents extensive shell-based capabilities but declares no explicit tool scope or permission boundaries. In an agent environment, that increases the chance the skill is invoked with broader execution authority than intended, enabling credential handling, API calls, downloads, and file operations without clear restrictions.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The trigger description is very broad and includes generic terms like projects, tasks, meetings, and recordings, which are common in ordinary conversations. That can cause accidental invocation of a powerful integration skill in contexts where the user did not intend Zoho access, increasing the risk of unnecessary data exposure or unintended actions.

External Transmission

Medium
Category
Data Exfiltration
Content
### Step 3: Exchange Code for Refresh Token

Run this curl command (replace placeholders):

```bash
curl -X POST "https://accounts.zoho.com/oauth/v2/token" \
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation includes a direct delete command for CRM records with no warning, safety notes, or confirmation guidance. In an agent-assisted workflow, that normalizes destructive behavior and can lead to irreversible record deletion from a production CRM through user error or prompt ambiguity.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The recording download and summarization pipeline processes potentially sensitive meeting audio, transcripts, and summaries, and explicitly suggests sending content to external AI services, but it provides no privacy, consent, retention, or access-control guidance. This creates a realistic path for confidential business discussions or personal data to be exported and retained outside Zoho without informed approval.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This markdown file documents endpoints for listing and downloading recordings, including transcript and summary download URLs, which can expose sensitive meeting content. Under the markdown-specific warning criterion, the description lacks any caution about privacy, consent, or careful handling of user data affected by these operations.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The skill description suggests Zoho interaction, but this script also downloads recordings, extracts audio locally, and performs full transcription. While not inherently malicious, that broadens capability into surveillance-style content processing and increases privacy risk, especially because full transcripts are produced and returned to the caller.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The script sends downloaded Zoho meeting audio and derived transcript content to Google's Gemini APIs, expanding data flow beyond Zoho into a third-party processor. This is dangerous because meeting recordings commonly contain sensitive business discussions, customer data, and credentials, and the transfer occurs automatically without any consent gate, data minimization, or processor-validation controls.

External Transmission

Medium
Category
Data Exfiltration
Content
if [[ "$WAV_BYTES" -gt 20000000 ]]; then
    # Large file: upload via File API first
    echo "   📤 Large file — uploading to Gemini File API..."
    UPLOAD_RESP=$(curl -s -X POST \
      "https://generativelanguage.googleapis.com/upload/v1beta/files?key=${GEMINI_API_KEY}" \
      -H "X-Goog-Upload-Command: start, upload, finalize" \
      -H "X-Goog-Upload-Header-Content-Length: ${WAV_BYTES}" \
Confidence
98% confidence
Finding
This request uploads extracted meeting audio to Google's File API, constituting direct external exfiltration of potentially sensitive meeting content. Because the transfer is automatic and the skill context is Zoho-focused, the behavior is more dangerous: users are less likely to expect raw recordings to leave the primary platform.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
Meeting recordings are uploaded or embedded into requests sent to external Gemini endpoints without any explicit warning, confirmation, or approval flow. This creates a real confidentiality risk because users may reasonably expect a Zoho skill to keep meeting data within Zoho or at least warn before sharing audio with a third party.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The prompt explicitly instructs Gemini that the speakers are Egyptian and speak in Egyptian Arabic mixed with English technical terms, effectively imposing a locale/language assumption. This can violate language/locale policy when the skill does not give users a choice or document that this is a region-specific tool.

External Transmission

Medium
Category
Data Exfiltration
Content
GEMINI_BODY_FILE="${TMP_DIR}/gemini_body_${i}.json"
  echo "$GEMINI_BODY" > "$GEMINI_BODY_FILE"
  GEMINI_RESP=$(curl -s -X POST \
    "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}" \
    -H "Content-Type: application/json" \
    -d "@${GEMINI_BODY_FILE}")
Confidence
96% confidence
Finding
The script sends transcription requests containing meeting audio or file references to Gemini's generateContent API. This exposes sensitive communications to a third-party model service and may also place data into provider-side processing pipelines without user awareness.

External Transmission

Medium
Category
Data Exfiltration
Content
echo "   🔄 Retrying with gemini-2.5-flash..."
    GEMINI_BODY_FILE="${TMP_DIR}/gemini_body_${i}.json"
    echo "$GEMINI_BODY" > "$GEMINI_BODY_FILE"
    GEMINI_RESP=$(curl -s -X POST \
      "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}" \
      -H "Content-Type: application/json" \
      -d "@${GEMINI_BODY_FILE}")
Confidence
95% confidence
Finding
The fallback retry to a second Gemini model repeats third-party transmission of the same sensitive meeting content, increasing exposure surface and data-sharing events. Retrying against multiple models without consent or minimization amplifies confidentiality and compliance risk.

Natural-Language Policy Violations

Low
Confidence
71% confidence
Finding
The README explicitly highlights handling of an Arabic and English language mix, which introduces a language-specific behavior in the skill description. There is no indication here that language handling is user-configurable or opt-in, which may conflict with organizational language/locale choice expectations.

Missing User Warnings

Low
Confidence
91% confidence
Finding
This markdown file documents irreversible deletion operations for projects and tasks, but provides no warning about data loss, confirmation expectations, or recovery limitations. Under the markdown-specific warning criterion, descriptions of behaviors that can affect user data should include a caution when destructive actions are exposed.

Missing User Warnings

Low
Confidence
77% confidence
Finding
The script sources a .env file and reads GEMINI_API_KEY, which is sensitive credential material. There is no user-facing warning, confirmation, or descriptive comment indicating that the skill accesses local secrets, which can be relevant for safety review of code that handles credentials.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
When no date is provided, the script computes the target date and time range in the Africa/Cairo timezone. This is a locale-specific assumption that may lead to unintended behavior for users in other regions and is not presented as an explicit user choice.

Static analysis

No suspicious patterns detected.