Back to skill

Security audit

Agent Profile Images

Security checks for vulnerabilities and agentic risk

Overview

The avatar feature is mostly coherent, but the package includes broad OpenClaw control-plane source snapshots with unrelated admin, secrets, update, and agent-management code that should be reviewed before installation.

Install only after reviewing the referenced source snapshots as a broad OpenClaw gateway/UI change, not as a narrow avatar-only patch. Confirm the installer or maintainer applies only the avatar-related diffs, disclose OpenAI data sharing to users, and harden avatar upload validation before using this in a shared or exposed gateway.

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
references/src-gateway-server-methods-agents-ts.txt:440
Finding
Avatar uploads trust client-controlled file type metadata without validating image content<![CDATA[ ## Vulnerability Details **File Location**: `references/src-gateway-server-methods-agents-ts.txt:440-464`, with the upload handler at lines `649-675` **Vulnerability Type**: Unrestricted or insufficiently validated file upload **Risk Level**: Medium ### Vulnerable Code ```ts function avatarExtensionForUpload(filename: string, contentType?: string | null): string | null { const lower = filename.toLowerCase(); if (lower.endsWith(".png") || contentType === "image/png") return ".png"; if (lower.endsWith(".jpg") || lower.endsWith(".jpeg") || contentType === "image/jpeg") { return ".jpg"; } if (lower.endsWith(".webp") || contentType === "image/webp") return ".webp"; if (lower.endsWith(".gif") || contentType === "image/gif") return ".gif"; return null; } async function writeAgentAvatarFile(params: { cfg: ReturnType<typeof loadConfig>; agentId: string; bytes: Buffer; extension: string; }): Promise<{ avatar: string; avatarUrl: string }> { const workspace = resolveAgentWorkspaceDir(params.cfg, params.agentId); const avatarDir = path.join(workspace, "avatars"); await fs.mkdir(avatarDir, { recursive: true }); const filename = `profile${params.extension}`; const absolutePath = path.join(avatarDir, filename); await fs.writeFile(absolutePath, params.bytes); const identityPath = path.join(workspace, DEFAULT_IDENTITY_FILENAME); await fs.appendFile(identityPath, `\n- Avatar: avatars/${filename}\n`, "utf-8"); return { avatar: `avatars/${filename}`, avatarUrl: `/avatar/${params.agentId}`, }; } ``` The corresponding upload handler derives the extension entirely from request metadata: ```ts const decoded = decodeAvatarUploadData(String(params.data ?? "")); if (!decoded || decoded.bytes.length === 0) { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid avatar image data")); return; } if (decoded.bytes.length > 2 * 1024 * 1024) { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQ ...[truncated 2946 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate decoded content using trusted image parsing rather than relying on filename or MIME metadata. 2. Check format-specific magic bytes before invoking an image decoder, but do not treat signature checks alone as sufficient. 3. Fully decode and re-encode accepted images into a canonical server-selected format, such as PNG or WEBP. This removes trailing payloads, metadata, and most polyglot structures. 4. Reject files with inconsistent filename, declared MIME type, detected format, or decoding results. 5. Enforce maximum pixel dimensions, frame counts, decompressed size, and animation duration to prevent image decompression bombs. 6. Serve avatar files with a fixed validated `Content-Type`, `X-Content-Type-Options: nosniff`, and a restrictive Content Security Policy where applicable. 7. Add tests for fake extensions, fake MIME types, malformed files, polyglot files, oversized dimensions, animated-image abuse, and valid supported formats. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
references/src-gateway-server-methods-agents-ts.txt:424
Finding
Avatar size enforcement occurs only after unbounded base64 input is decoded<![CDATA[ ## Vulnerability Details **File Location**: `references/src-gateway-server-methods-agents-ts.txt:424-438` and `665-669`; schema definition at `references/src-gateway-protocol-schema-agents-models-skills-ts.txt:87-95` **Vulnerability Type**: Resource exhaustion through decode-before-limit processing **Risk Level**: Low ### Vulnerable Code The request schema permits a non-empty string without a maximum length: ```ts export const AgentsAvatarUploadParamsSchema = Type.Object( { agentId: NonEmptyString, filename: NonEmptyString, contentType: NonEmptyString, data: NonEmptyString, }, { additionalProperties: false }, ); ``` The complete input is trimmed, copied during whitespace removal, and decoded before its size is checked: ```ts function decodeAvatarUploadData(value: string): { bytes: Buffer; contentType: string | null } | null { const trimmed = value.trim(); const dataUrlMatch = /^data:([^;,]+)?(?:;base64)?,([A-Za-z0-9+/=\s]+)$/i.exec(trimmed); if (dataUrlMatch) { const mime = dataUrlMatch[1]?.trim() || null; try { return { bytes: Buffer.from(dataUrlMatch[2]!.replace(/\s+/g, ""), "base64"), contentType: mime, }; } catch { return null; } } try { return { bytes: Buffer.from(trimmed.replace(/\s+/g, ""), "base64"), contentType: null, }; } catch { return null; } } ``` The decoded-size limit is applied afterward: ```ts const decoded = decodeAvatarUploadData(String(params.data ?? "")); if (!decoded || decoded.bytes.length === 0) { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid avatar image data")); return; } if (decoded.bytes.length > 2 * 1024 * 1024) { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "avatar image exceeds 2MB limit")); return; } ``` ### Technical Analysis The intended 2 MB restriction applies only to the resulting `Buffer`. Before reaching that check, the gateway must receive ...[truncated 2165 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a strict `maxLength` to the `data` field in `AgentsAvatarUploadParamsSchema`. For a 2 MB decoded limit, account for base64 expansion, the data-URL prefix, and only a small permitted amount of formatting overhead. 2. Reject encoded strings that exceed the calculated maximum before calling `trim()`, regular expressions, `replace()`, or `Buffer.from()`. 3. Enforce equivalent or smaller request-size limits at the HTTP and WebSocket transport layers. 4. Avoid permitting arbitrary whitespace in base64 input unless required. If whitespace is supported, validate its quantity without first creating a complete normalized copy. 5. Consider a streaming upload and decoding mechanism with a hard byte counter if larger uploads are introduced later. 6. Add per-connection rate limits and concurrency limits for upload and generation RPCs. 7. Add tests confirming that oversized encoded input is rejected before base64 decoding or large buffer allocation. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (22)

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The schema for this skill exposes capabilities far beyond profile-image handling, including agent creation, update, deletion, file listing/get/set, skill installation/update, and tool catalog access. In a skill whose stated purpose is avatar upload/generation, this mismatch creates an unnecessary expansion of the attack surface and enables privilege abuse if the skill is invoked or granted broadly.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The file implements agent creation, update, deletion, and workspace file read/write operations even though the skill is presented as profile image management. In context, this is dangerous because users or reviewers may grant trust appropriate for avatar features while unknowingly exposing powerful filesystem and lifecycle controls over agents.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
This skill is described as handling agent profile images, but the file also exposes a broad AI wizard that uses model credentials, accepts arbitrary user descriptions, and generates agent configuration content. That scope expansion materially increases attack surface and data flow beyond the declared purpose, making it easier to smuggle sensitive capabilities into a seemingly low-risk skill.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
The wizard code resolves provider API keys and sends non-image prompts to external LLM APIs, which is unrelated to profile-image handling. Access to stored model credentials in an unexpectedly broad skill raises the risk of unauthorized external data transmission and hidden use of privileged authentication material.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The manifest describes a narrowly scoped skill for uploading, generating, previewing, and persisting agent profile images. This file registers a much wider set of operations including config changes, secrets management, node/device pairing, cron control, session deletion, approvals, updates, browser requests, and general agent/chat methods, which materially exceed avatar-related behavior.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
Methods for listing, writing, deleting, migrating, and resolving secrets are not a direct or obvious requirement for uploading or generating profile images. Exposing secret-handling functionality in a profile image skill grants access to sensitive capabilities unrelated to the manifest's stated purpose.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
Node pairing, device token management, remote node invocation, and cron administration are infrastructure-control functions unrelated to avatar upload/generation workflows. These capabilities are not justified by the manifest's narrow UI-oriented image-management purpose.

Self-Modification

High
Category
Rogue Agent
Content
? ` Use model ${state.mergeModel} for conflict resolution (set SAFE_MERGE_MODEL=${state.mergeModel}).`
              : "";
            void state.handleSendChat(
              `Run a safe upstream merge update now. Follow the safe-merge-update skill at ~/.openclaw/workspace/skills/safe-merge-update/SKILL.md — run all 4 phases (preflight, AI merge, validate, commit). Report progress as you go.${modelNote}`,
            );
            setTimeout(() => {
              state.updateInProgress = false;
Confidence
95% confidence
Finding
The UI sends a chat instruction that explicitly tells the agent to follow a local skill for upstream merge/update operations, including AI-assisted merge and commit phases. This creates a self-modification pathway where an LLM-driven agent can alter local code/configuration based on natural-language prompting, increasing the risk of prompt injection, unsafe code changes, or persistence of malicious modifications if an attacker can influence the conversation or the referenced skill.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly advertises AI avatar generation through OpenAI Images but does not warn users that prompts and possibly uploaded or generated content may be sent to a third-party provider. This can lead to unintended disclosure of personal, sensitive, or proprietary information if users include identifying details in generation prompts or source images.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
Avatar generation sends agent-derived fields such as name, emoji, theme, and optional instructions to OpenAI. In a profile-images skill this is contextually relevant, but without explicit disclosure/consent and clear indication of what metadata leaves the system, it creates a real privacy and policy risk through external transmission.

External Transmission

Medium
Category
Data Exfiltration
Content
.filter(Boolean)
    .join(" ");

  const response = await fetch("https://api.openai.com/v1/images/generations", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
Confidence
84% confidence
Finding
The hardcoded OpenAI endpoint confirms that avatar generation depends on a third-party service. While not inherently malicious, it is a genuine data egress point and should be treated as such, especially because the skill also contains broader-than-advertised functionality.

External Transmission

Medium
Category
Data Exfiltration
Content
.filter(Boolean)
    .join(" ");

  const response = await fetch("https://api.openai.com/v1/images/generations", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
Confidence
84% confidence
Finding
The hardcoded OpenAI endpoint confirms that avatar generation depends on a third-party service. While not inherently malicious, it is a genuine data egress point and should be treated as such, especially because the skill also contains broader-than-advertised functionality.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The agents.delete handler can remove an agent's workspace, agent directory, and session transcripts by moving them to trash when deleteFiles is true, but this path contains no confirmation prompt or user-facing disclosure about those filesystem effects. For a destructive operation affecting user data, the code only performs the action and returns success, which fits the missing-warning criterion for code files.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The wizard endpoint transmits free-form user descriptions to Anthropic or OpenAI, which may include sensitive operational or personal data. Because this capability is outside the declared profile-image scope, the hidden external sharing is more dangerous and likely to violate user expectations and trust boundaries.

External Transmission

Medium
Category
Data Exfiltration
Content
const headers: Record<string, string> = { "Content-Type": "application/json" };

      if (provider === "anthropic") {
        url = "https://api.anthropic.com/v1/messages";
        headers["x-api-key"] = auth.apiKey;
        headers["anthropic-version"] = "2023-06-01";
        body = {
Confidence
95% confidence
Finding
This outbound call to Anthropic is part of the unrelated wizard functionality, sending user-supplied descriptions and using resolved API credentials. In a profile-images skill, that hidden network capability significantly increases risk because it enables external transmission of arbitrary text under unexpectedly broad trust.

External Transmission

Medium
Category
Data Exfiltration
Content
};
      } else {
        // OpenAI-compatible
        url = "https://api.openai.com/v1/chat/completions";
        headers["Authorization"] = `Bearer ${auth.apiKey}`;
        body = {
          model: modelStr.replace(`${provider}/`, ""),
Confidence
95% confidence
Finding
This OpenAI chat completions call supports the non-image wizard feature and sends arbitrary user descriptions to an external provider using stored credentials. Because it is outside the stated purpose of the skill, it represents undeclared external exfiltration potential and hidden privilege use.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The presence of a generic "browser.request" method suggests arbitrary browser-mediated network access. A profile image skill may reasonably upload or generate images, but a generic request capability is broader than necessary and is not explicitly justified by the manifest description.

Skill Enumeration

Medium
Category
Agent Snooping
Content
? ` Use model ${state.mergeModel} for conflict resolution (set SAFE_MERGE_MODEL=${state.mergeModel}).`
              : "";
            void state.handleSendChat(
              `Run a safe upstream merge update now. Follow the safe-merge-update skill at ~/.openclaw/workspace/skills/safe-merge-update/SKILL.md — run all 4 phases (preflight, AI merge, validate, commit). Report progress as you go.${modelNote}`,
            );
            setTimeout(() => {
              state.updateInProgress = false;
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code issues an `agents.delete` request, which is a destructive operation, but the user-facing confirmation at L1893-L1896 only asks for confirmation and does not explain consequences such as agent removal side effects or data loss. In this file there is no accompanying comment, docstring, or disclosure describing what deletion entails beyond a generic failure message.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The code directly requests `gateway.restart`, which can interrupt service and active sessions, but this file provides no warning, confirmation, or explanatory comment around that action. Because restart affects system availability, users should be explicitly informed before it occurs.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This separate restart path also calls `gateway.restart` without any visible confirmation, warning text, or inline explanation in the file. The action is safety-relevant because it can disrupt running operations and connectivity.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The default Discord voice configuration sets `transcribeLanguage` to `"en"`, which imposes a specific language choice in natural-language-facing behavior. In this file there is no accompanying comment or user-facing explanation indicating that the language is optional, user-selectable, or required for a documented region-specific purpose.

Static analysis

No suspicious patterns detected.