Back to skill

Security audit

Zalo Multi Send

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it can immediately use stored Zalo credentials to send local or downloaded files and has weak safeguards around recipients, profiles, and URL fetching.

Review each recipient, credential profile, and attachment path before use. Avoid sending sensitive local files, avoid untrusted URLs, and prefer adding confirmation, URL allowlisting, HTTPS-only fetching, size/time limits, and recipient controls before installing in an agent that may act automatically.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/send.mjs:52
Finding
Unrestricted Remote URL Fetching Enables SSRF and Resource Exhaustion## Vulnerability Details **File Location**: `scripts/send.mjs`, lines 52–58 **Vulnerability Type**: Server-Side Request Forgery (SSRF) and unbounded resource consumption **Risk Level**: Medium ### Vulnerable Code ```js async function loadFile(source) { if (/^https?:\/\//.test(source)) { const res = await fetch(source); if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${source}`); const buf = Buffer.from(await res.arrayBuffer()); const name = basename(new URL(source).pathname) || "file.bin"; return { buf, name }; } ``` ### Technical Analysis The `--files` argument accepts arbitrary HTTP and HTTPS URLs. The script passes each URL directly to `fetch()` without validating its destination hostname, resolved IP address, port, or URL scheme beyond the initial regular expression. Consequently, an authorized caller able to invoke the Skill can direct the host to request loopback, link-local, private-network, cloud metadata, or other otherwise inaccessible endpoints. Redirects are followed by `fetch()` by default, but redirect destinations are not revalidated. Although the documentation advertises HTTPS URLs, the implementation also permits plaintext HTTP. The response is read in full with `res.arrayBuffer()` and then copied into a `Buffer`. No request timeout, response-size ceiling, attachment-count limit, or aggregate-memory limit is applied. A large or slow response can therefore consume substantial memory and execution resources. ### Attack Path 1. An attacker or untrusted caller supplies a URL through `--files`, such as a loopback/private-network endpoint or a public URL that redirects to one. 2. `loadFile()` passes that URL directly to `fetch()`. 3. The process accesses the target using the host's network position and follows redirects without destination validation. 4. The complete response is loaded into process memory. 5. The response is packaged as an attachment and sent to ...[truncated 931 chars]
Remediation
## Remediation Suggestions - Permit only HTTPS URLs unless plaintext HTTP is explicitly required. - Parse URLs with the standard `URL` API and reject embedded credentials, unexpected ports, malformed hostnames, and unsupported schemes. - Resolve hostnames before connecting and reject loopback, link-local, private, multicast, reserved, and cloud-metadata address ranges for both IPv4 and IPv6. - Disable automatic redirects or manually process them, applying the same validation to every redirect destination. - Add an `AbortController`-based connection and total-request timeout. - Validate `Content-Length` when available and enforce a streaming byte limit regardless of whether the header is present. - Enforce per-file, attachment-count, and aggregate-download size limits. - Prefer an explicit allowlist of trusted download domains where operationally practical. - Avoid holding every attachment in memory concurrently; use bounded concurrency and streaming or temporary files with secure permissions where supported. - Ensure destination authorization is checked before fetching remote content so unauthorized recipients cannot be used as exfiltration channels.
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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (10)

Ae1

High
Category
analysis-evasion
Content
Send multiple attachments in one Zalo message via `scripts/send.mjs`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Send multiple attachments in one Zalo message via `scripts/send.mjs`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Send multiple attachments in one Zalo message via `scripts/send.mjs`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Send multiple attachments in one Zalo message via `scripts/send.mjs`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Send multiple attachments in one Zalo message via `scripts/send.mjs`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
## Notes

- Credentials are read from `~/.openclaw/credentials/zalouser/credentials.json` (managed by openclaw, no re-auth needed)
- ZCA_PATH is hardcoded to the openclaw-bundled zca-js. If openclaw is updated/reinstalled, the path may need updating in `scripts/send.mjs`
- Local files must be readable by the current user
- Zalo may have undocumented limits on number of attachments per message (tested up to ~10 without issues)
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
}

function resolveCredentialsPath(profile) {
  const name = !profile || profile === "default" ? "credentials.json" : `credentials-${encodeURIComponent(profile.trim().toLowerCase())}.json`;
  return join(os.homedir(), ".openclaw/credentials/zalouser", name);
}
Confidence
93% confidence
Finding
The script directly locates and reads Zalo session credentials from the user's home directory, then uses them to authenticate and send messages. In an agent-skill context, this is sensitive credential access because the skill can act as the user without additional approval, and the optional profile argument broadens access to multiple stored accounts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill explicitly supports sending local files and remote URLs through Zalo, which implies outbound network activity, yet it declares no tool scope or permissions boundary. Without an explicit scope declaration, an agent may invoke network-capable behavior without clear policy review or user visibility, increasing the chance of unintended data transmission.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The description says the skill can send multiple local files or URL-sourced files to a Zalo contact or group, but it does not warn that this transmits potentially sensitive local content or remote-fetched content to external recipients. That omission creates a meaningful risk of accidental exfiltration, especially if an agent selects files automatically or if a user does not realize local paths will be uploaded.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The notes disclose that the skill relies on stored Zalo account credentials and can send without re-authentication, but they do not warn users that actions are performed under an already-authenticated identity. This can lead to users or higher-level agents underestimating the sensitivity of invoking the skill, since messages may be sent immediately as the configured account.

Static analysis

No suspicious patterns detected.