Back to skill

Security audit

Pexo Video Agent

Security checks for vulnerabilities and agentic risk

Overview

The skill broadly matches its video-generation purpose, but it needs review because it relays remote service text and links and sends media to server-supplied URLs without enough validation.

Review before installing if your media files may be sensitive or if you run agents on a network with private internal services. Use it only for files you intentionally choose, keep the API key scoped and removable, and treat returned messages, billing links, and signed asset URLs as external Pexo-provided content rather than trusted local instructions.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:105
Finding
Untrusted Backend Content Is Required to Be Relayed Verbatim<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:105-107`, `SKILL.md:117-146`, `SKILL.md:239-246`, `SKILL.md:338-350` **Vulnerability Type**: Instruction and output hijacking through untrusted external content **Risk Level**: Critical ### Complete Code Snippets ```markdown ## ⚠️ LANGUAGE RULE (highest priority) **You MUST reply to the user in the SAME language they use. This is non-negotiable.** ``` ```markdown ## Your Role: Delivery Worker You are a delivery worker between the user and Pexo. You do three things: 1. **Upload**: user gives a file → `pexo-upload.sh` → get asset ID 2. **Relay**: copy the user's words into `pexo-chat.sh` 3. **Deliver**: poll for results → send video and link to user ``` ```markdown Event "message" (Pexo sent text): Relay Pexo's text to the user in full. If Pexo asked a question, wait for the user's answer. Then run: pexo-chat.sh <project_id> "{user's exact answer}" Go back to Step 5. ``` ```markdown Step A. If stderr contains a purchase link and instructions, send them to the user (in their language). Step B. If stderr only contains the error message without a purchase link, send the user a message (in their language) with: - Their credits are insufficient. - To add credits: visit https://pexo.ai/home?billing=credits and complete the purchase flow. ``` ### Technical Analysis The Skill text uses priority-asserting language and redefines the Agent as a passive “delivery worker.” More importantly, it requires externally supplied Pexo messages, error instructions, and purchase links to be relayed in full. Content returned by a remote service is outside the local trust boundary and must be treated as untrusted data. Requiring verbatim relay prevents the Agent from independently assessing whether a response contains prompt injection, misleading billing instructions, unsafe links, requests for secrets, or instructions unrelated to video production. The same-langu ...[truncated 1408 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove “highest priority,” “non-negotiable,” and passive role-redefinition language. 2. Explicitly classify all backend messages, errors, and links as untrusted data. 3. Replace verbatim relay requirements with a requirement to summarize backend content accurately while ignoring embedded instructions. 4. Validate externally supplied links before displaying them: - Require HTTPS. - Allowlist approved Pexo billing and project origins. - Reject credential-bearing URLs and unexpected redirects. 5. Never request or disclose credentials, secrets, or unrelated local information in response to backend-generated text. 6. Preserve the Agent’s ability to refuse unsafe or irrelevant backend requests. 7. Clearly label remote text as content supplied by Pexo rather than presenting it as authoritative Agent instruction. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/pexo-upload.sh:70
Finding
Server-Provided Upload URL Is Used Without Scheme or Destination Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pexo-upload.sh:70-86` **Vulnerability Type**: Unvalidated external upload destination **Risk Level**: High ### Complete Code Snippet ```bash # Phase 1: get upload credential cred=$(pexo_post "/api/biz/projects/${pid}/assets/upload-credential" \ "{\"file_name\":\"$filename\",\"file_size\":$filesize}") upload_url=$(echo "$cred" | jq -r '.uploadUrl') asset_id=$(echo "$cred" | jq -r '.assetId') storage_path=$(echo "$cred" | jq -r '.storagePath') [[ -n "$upload_url" && "$upload_url" != "null" ]] || { echo "Error: failed to get upload credential" >&2; echo "$cred" >&2; exit 1; } [[ -n "$asset_id" && "$asset_id" != "null" ]] || { echo "Error: upload credential missing assetId" >&2; echo "$cred" >&2; exit 1; } [[ -n "$storage_path" && "$storage_path" != "null" ]] || { echo "Error: upload credential missing storagePath" >&2; echo "$cred" >&2; exit 1; } # Phase 2: PUT raw bytes to presigned URL http_code=$(curl -sS -X PUT -H "Content-Type: $mime_type" \ --data-binary "@$filepath" -o /dev/null -w '%{http_code}' "$upload_url" 2>/dev/null || echo "000") [[ "$http_code" =~ ^2 ]] || { echo "Error: upload failed with HTTP $http_code" >&2; exit 1; } ``` ### Technical Analysis The authenticated request used to obtain the upload credential is locked to `https://pexo.ai`, but the returned `uploadUrl` crosses a second trust boundary. The script only checks whether this value is nonempty. It does not require HTTPS, verify the hostname or port, reject credential-bearing URLs, or prevent loopback, link-local, and private-network destinations. The script then sends the complete contents of the selected local file to that URL using an HTTP PUT request. Presigned object-storage URLs are necessary for the declared upload workflow, but destination validation remains necessary because the API response itself may be compromised or malformed. The Pexo API key is not attached to this secondary upload request. The expos ...[truncated 1254 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse `uploadUrl` with a dedicated URL parser before invoking `curl`. 2. Require the `https` scheme and reject: - Plain HTTP. - Embedded credentials. - Unexpected ports. - Empty or malformed hostnames. 3. Maintain an allowlist of documented Pexo object-storage domains. 4. Resolve the hostname and reject loopback, link-local, multicast, and private-network addresses for every resolved address. 5. Use `curl --proto '=https'` and disable unsupported protocols. 6. Apply explicit connection and overall request timeouts to upload requests. 7. Document the approved object-storage domains and the fact that file bytes are transferred directly to them. 8. Fail closed if URL validation or DNS classification cannot be completed safely. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pexo-asset-get.sh:84
Finding
Unvalidated Download URL and Redirect Chain Permit Arbitrary Network Requests<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pexo-asset-get.sh:84-113` **Vulnerability Type**: Server-side request forgery from unvalidated download metadata **Risk Level**: Medium ### Complete Code Snippet ```bash download=$(pexo_get "/api/biz/projects/${pid}/assets/${aid}/download-url?remove_watermark=${remove_watermark}") download_url=$(echo "$download" | jq -r '.url // empty') with_watermark_result=$(echo "$download" | jq -c 'if has("withWatermark") then .withWatermark else null end') if [[ -z "$download_url" ]]; then echo "$asset" | jq --argjson withWatermark "$with_watermark_result" '. + {url:null, localPath:null, withWatermark:$withWatermark}' exit 0 fi tmp_dir=$(pexo_tmp_dir) file_name=$(echo "$asset" | jq -r '.fileName // .assetName // empty') [[ -n "$file_name" && "$file_name" != "null" ]] || file_name="${aid}.bin" safe_name=$(printf '%s' "$file_name" | sed 's#[/[:space:]]#_#g') variant="clean" if [[ "$with_watermark" == true ]]; then variant="watermarked" fi local_path="${tmp_dir}/${aid}-${variant}-${safe_name}" part_path="${local_path}.part.$$" err_file=$(mktemp) http_code="" curl_status=0 http_code=$(curl -sS -L \ --connect-timeout "$_PEXO_CONNECT_TIMEOUT" \ --max-time "$_PEXO_REQUEST_TIMEOUT" \ -o "$part_path" \ -w '%{http_code}' \ "$download_url" 2>"$err_file") || curl_status=$? ``` ### Technical Analysis The download URL is supplied by the remote API and passed directly to `curl`. The `-L` option follows redirects, but neither the initial URL nor redirect targets are validated. Consequently, a compromised or malformed backend response can make the client issue GET requests to arbitrary destinations, including HTTP endpoints, loopback services, private-network systems, or cloud metadata endpoints. Curl may also switch protocols unless restricted explicitly. The response is stored locally rather than returned to Pexo, so this is primarily a blind SSRF and arbitrary-download primitive. Subsequent Agent beh ...[truncated 1240 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS for the initial URL and all redirect destinations. 2. Allowlist documented Pexo object-storage hostnames. 3. Reject embedded credentials, unexpected ports, malformed hosts, and IP-literal bypasses. 4. Resolve each destination and reject loopback, private, link-local, multicast, and reserved address ranges. 5. Avoid `curl -L` where possible. If redirects are required, validate every redirect target before following it. 6. Restrict protocols with `curl --proto '=https' --proto-redir '=https'`. 7. Apply response-size limits appropriate to supported asset types. 8. Validate the downloaded content type and expected size against trusted asset metadata before accepting the file. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pexo-asset-get.sh:91
Finding
Unvalidated Asset Identifier Is Embedded in the Local Download Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pexo-asset-get.sh:91-103` **Vulnerability Type**: Path traversal and unintended file placement **Risk Level**: Medium ### Complete Code Snippet ```bash tmp_dir=$(pexo_tmp_dir) file_name=$(echo "$asset" | jq -r '.fileName // .assetName // empty') [[ -n "$file_name" && "$file_name" != "null" ]] || file_name="${aid}.bin" safe_name=$(printf '%s' "$file_name" | sed 's#[/[:space:]]#_#g') variant="clean" if [[ "$with_watermark" == true ]]; then variant="watermarked" fi local_path="${tmp_dir}/${aid}-${variant}-${safe_name}" part_path="${local_path}.part.$$" ``` ### Technical Analysis The remote filename receives limited sanitization, but the caller-controlled `aid` value is inserted directly into the local destination path. The script does not validate `aid` against the documented asset identifier formats and does not canonicalize the final path to ensure it remains below `tmp_dir`. An identifier containing slash and traversal components can alter the destination path. Although the fixed suffix constrains the exact target filename and successful exploitation requires the relevant parent directories to exist, the script can still place downloaded content outside the intended `~/.pexo/tmp` directory. The same identifier is first used in API paths, so exploitation also depends on the constructed API request returning valid asset and download metadata. That dependency reduces exploitability but does not replace local input validation. ### Attack Path 1. An attacker influences the asset identifier passed to `pexo-asset-get.sh`. 2. The identifier contains path separators or traversal components. 3. The corresponding API request returns usable asset metadata and a download URL. 4. The script concatenates the unvalidated identifier into `local_path`. 5. The downloaded response is written to `part_path` and moved to the resulting destination. 6. If path resolution escapes `tmp_dir` and required parent dire ...[truncated 525 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `aid` before any API or filesystem use. Accept only the documented identifier formats, for example: - `a_` followed by the permitted identifier alphabet and length. - The documented fixed-length uppercase identifier format. 2. Reject slashes, backslashes, dots used as traversal components, control characters, and empty identifiers. 3. Generate local cache filenames from a locally computed safe token rather than raw remote or caller-controlled values. 4. Canonicalize both `tmp_dir` and the destination parent, then verify the destination remains beneath the canonical temporary directory. 5. Open destination files with safe creation semantics and avoid overwriting existing files unless explicitly intended. 6. Apply robust filename normalization to every remote field used in local paths. ]]>
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
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
This variant includes a materially security-relevant mismatch: the skill claims authenticated requests are locked to https://pexo.ai, yet the workflow explicitly instructs use of outbound presigned asset URLs for upload/download. If users or operators rely on the stronger claim, they may underestimate external data transfer paths and fail to review or constrain non-pexo.ai endpoints handling user assets.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This variant includes a materially security-relevant mismatch: the skill claims authenticated requests are locked to https://pexo.ai, yet the workflow explicitly instructs use of outbound presigned asset URLs for upload/download. If users or operators rely on the stronger claim, they may underestimate external data transfer paths and fail to review or constrain non-pexo.ai endpoints handling user assets.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This variant includes a materially security-relevant mismatch: the skill claims authenticated requests are locked to https://pexo.ai, yet the workflow explicitly instructs use of outbound presigned asset URLs for upload/download. If users or operators rely on the stronger claim, they may underestimate external data transfer paths and fail to review or constrain non-pexo.ai endpoints handling user assets.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This variant includes a materially security-relevant mismatch: the skill claims authenticated requests are locked to https://pexo.ai, yet the workflow explicitly instructs use of outbound presigned asset URLs for upload/download. If users or operators rely on the stronger claim, they may underestimate external data transfer paths and fail to review or constrain non-pexo.ai endpoints handling user assets.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
This variant includes a materially security-relevant mismatch: the skill claims authenticated requests are locked to https://pexo.ai, yet the workflow explicitly instructs use of outbound presigned asset URLs for upload/download. If users or operators rely on the stronger claim, they may underestimate external data transfer paths and fail to review or constrain non-pexo.ai endpoints handling user assets.

Ae1

High
Category
analysis-evasion
Content
Resolve `SKILL_ROOT` to the directory containing this `SKILL.md`. Script names
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Context Leakage

High
Category
Data Exfiltration
Content
- `400`: the file was rejected — possible reasons: file exceeds the size limit, file format is not supported, or the file content does not match its extension. Convert or compress the file and re-upload from scratch using `pexo-upload.sh`.
- `401`: auth failure — see Auth and Proxy Errors above.
- `404`: the file record was not found. The upload session may have been cleaned up. Re-upload from scratch using `pexo-upload.sh`.
- `412`: the upload session has already expired or been completed. Re-upload from scratch using `pexo-upload.sh`.
- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai.
Confidence
85% confidence
Finding
Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Context Leakage

High
Category
Data Exfiltration
Content
- `400`: the file was rejected — possible reasons: file exceeds the size limit, file format is not supported, or the file content does not match its extension. Convert or compress the file and re-upload from scratch using `pexo-upload.sh`.
- `401`: auth failure — see Auth and Proxy Errors above.
- `404`: the file record was not found. The upload session may have been cleaned up. Re-upload from scratch using `pexo-upload.sh`.
- `412`: the upload session has already expired or been completed. Re-upload from scratch using `pexo-upload.sh`.
- `500`: an unexpected server error occurred. Retry in a moment; if the problem persists, contact support at pexo.ai.
Confidence
85% confidence
Finding
Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill explicitly requires shell and outbound HTTPS access but does not declare a restrictive tool scope such as allowed-tools or permissions. In an agent environment, this increases the attack surface because broad shell/network capability can be abused by prompt injection, implementation drift, or future script changes to perform unintended actions without policy-level containment.

Session Persistence

Medium
Category
Rogue Agent
Content
```bash
umask 077
mkdir -p ~/.pexo
read -rsp "Pexo API key: " pexo_api_key
printf '\n'
{
Confidence
91% confidence
Finding
The setup flow persists the PEXO_API_KEY in ~/.pexo/config, creating session-spanning credential storage on disk. Even with umask 077 and chmod 600, persistent plaintext secrets increase the blast radius of local compromise, accidental backup leakage, or unintended reuse across projects/sessions in agent environments.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The markdown explicitly states that replies MUST be in the same language as the user and calls this non-negotiable. This is a natural-language locale policy constraint that removes user choice and is not framed as optional or justified as a region-specific requirement.

Session Persistence

Medium
Category
Rogue Agent
Content
## Quick Start

### 1. Create config file

```bash
umask 077
Confidence
90% confidence
Finding
The setup instructions persist the Pexo API key in `~/.pexo/config`, creating a long-lived local secret on disk. Even with `umask 077` and `chmod 600`, the key remains exposed to compromise from account takeover, local malware, backups, shell access by the same user, or accidental inclusion in copied home-directory data; in this skill context, that could enable unauthorized authenticated API use and billing-impacting actions.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
printf '%s=%s\n' PEXO_API_KEY "$pexo_api_key"
} > ~/.pexo/config
unset pexo_api_key
chmod 600 ~/.pexo/config
```

Get your API key at: https://pexo.ai
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
printf '%s=%s\n' PEXO_API_KEY "$pexo_api_key"
} > ~/.pexo/config
unset pexo_api_key
chmod 600 ~/.pexo/config
```

Get your API key at: https://pexo.ai
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
- `403`: the download link has expired. Re-run `pexo-asset-get.sh` to get a fresh link.
- `000`: network request failed before receiving a response. Check network connectivity and retry.
- local filesystem write failure: the temp directory (`~/.pexo/tmp/`) is not writable or the disk is full. Free up space or set `PEXO_TMP_DIR` to a writable path.

Notes:
Confidence
73% confidence
Finding
The documentation confirms that downloaded assets are written to a persistent local temp directory under the user's home path and may be redirected via an environment variable. In a skill that handles generated media and local file access, this increases risk of residual sensitive assets remaining on disk, being exposed to other local processes/users, or being redirected to an unsafe location if `PEXO_TMP_DIR` is manipulated.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
pexo_tmp_dir() {
  local tmp_dir="${PEXO_TMP_DIR:-$HOME/.pexo/tmp}"
  mkdir -p "$tmp_dir"
  chmod 700 "$tmp_dir"
  printf '%s\n' "$tmp_dir"
}
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

External Transmission

Medium
Category
Data Exfiltration
Content
err_file=$(mktemp)

  if [[ -n "$body" ]]; then
    http_code=$(curl -sS \
      --connect-timeout "$_PEXO_CONNECT_TIMEOUT" \
      --max-time "$_PEXO_REQUEST_TIMEOUT" \
      -X "$method" \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
err_file=$(mktemp)

  if [[ -n "$body" ]]; then
    http_code=$(curl -sS \
      --connect-timeout "$_PEXO_CONNECT_TIMEOUT" \
      --max-time "$_PEXO_REQUEST_TIMEOUT" \
      -X "$method" \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
set +o pipefail
  if [[ -n "$body" ]]; then
    curl -sS -N \
      --connect-timeout "$_PEXO_CONNECT_TIMEOUT" \
      --max-time "$timeout" \
      -X POST \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
set +o pipefail
  if [[ -n "$body" ]]; then
    curl -sS -N \
      --connect-timeout "$_PEXO_CONNECT_TIMEOUT" \
      --max-time "$timeout" \
      -X POST \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
echo "$WARN Could not determine config file permissions"
  elif [[ "${config_mode: -2}" != "00" ]]; then
    echo "$FAIL Config file is accessible by group or other users (mode $config_mode)"
    echo "  Fix with: chmod 600 $config_path"
    errors=$((errors + 1))
  else
    echo "$PASS Config file permissions are owner-only (mode $config_mode)"
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

External Transmission

Medium
Category
Data Exfiltration
Content
[[ -n "$storage_path" && "$storage_path" != "null" ]] || { echo "Error: upload credential missing storagePath" >&2; echo "$cred" >&2; exit 1; }

# Phase 2: PUT raw bytes to presigned URL
http_code=$(curl -sS -X PUT -H "Content-Type: $mime_type" \
  --data-binary "@$filepath" -o /dev/null -w '%{http_code}' "$upload_url" 2>/dev/null || echo "000")

[[ "$http_code" =~ ^2 ]] || { echo "Error: upload failed with HTTP $http_code" >&2; exit 1; }
Confidence
70% 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
88% confidence
Finding
The script performs a network transfer of the specified local file to a remote presigned URL using curl, but there is no confirmation prompt or explicit user-facing warning at the point of transmission. The usage text describes upload steps, but the execution path itself provides no visible disclosure when data leaves the local system.

Static analysis

No suspicious patterns detected.