Back to skill

Security audit

Fast Image

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but its send helper can execute shell-interpreted user input and has staging-path issues that make it unsafe without review.

Install only after the maintainer removes shell: true or otherwise prevents shell interpretation, validates channel and target values, uses a unique home-directory-based staging directory, and pins dependencies. Treat this as a Review item rather than proven malware: the core image-send behavior is disclosed, but the implementation has high-impact safety bugs.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
send_image.mjs:67
Finding
Shell Command Injection Through Caller-Controlled CLI Arguments<![CDATA[ ## Vulnerability Details **File Location**: `send_image.mjs`, lines 67-74 **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```js const args = ['message', 'send', '--media', imagePath, '--channel', channel, '--target', target]; if (message) args.push('--message', message); const proc = spawn('openclaw', args, { stdio: 'inherit', shell: true }); ``` ### Technical Analysis The `channel`, `target`, and `message` values originate directly from command-line arguments. The media path is also derived from a caller-supplied source path. These values are passed to `child_process.spawn()` with `shell: true`. Enabling `shell` causes the command and its arguments to be interpreted through a system shell. Shell metacharacters embedded in an argument may consequently be treated as command syntax rather than as literal data. Supplying arguments as an array does not provide the expected direct-process safety when shell execution is explicitly enabled. The program does not require shell functionality because `openclaw` can be executed directly with the argument array. ### Attack Path 1. An attacker obtains the ability to invoke the Skill or influence one of its documented parameters. 2. The attacker supplies shell syntax in `channel`, `target`, `message`, or a caller-controlled filename. 3. `main()` passes the value to `sendImage()`. 4. `sendImage()` inserts the value into the `args` array without validation. 5. `spawn()` starts `openclaw` with `shell: true`. 6. The operating-system shell interprets applicable metacharacters and may execute an additional attacker-selected command. The exact metacharacters and payload form depend on the operating system and configured shell. ### Impact Assessment Successful exploitation permits arbitrary command execution with the same operating-system identity and privileges as the Skill process. This may allow an attacker to: - Read or modify files accessible to the Agent. - Acces ...[truncated 465 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Disable shell interpretation and execute the binary directly: ```js const proc = spawn('openclaw', args, { stdio: 'inherit', shell: false }); ``` Additional hardening should include: 1. Validate `channel` against an explicit allowlist of supported channel identifiers. 2. Validate `target` according to the selected channel's documented identifier format. 3. Treat `message` exclusively as data and impose a reasonable length limit. 4. Resolve and validate the media path before passing it to the CLI. 5. Consider using an absolute, trusted path to the `openclaw` executable to reduce executable-path ambiguity. 6. Add tests containing spaces, quotes, command separators, substitutions, and redirection characters to verify that all values remain literal arguments. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
send_image.mjs:19
Finding
Predictable and Incorrectly Resolved Media Staging Path<![CDATA[ ## Vulnerability Details **File Location**: `send_image.mjs`, lines 19-54 and 83-87 **Vulnerability Type**: Unsafe temporary file handling and predictable path collision **Risk Level**: Medium ### Vulnerable Code ```js const TMP_DIR = "~/.openclaw/media/browser/"; const SIZE_THRESHOLD = 10 * 1024 * 1024; // 10MB async function ensureTmpDir() { await fs.mkdir(TMP_DIR, { recursive: true }); } async function getFileSize(filePath) { const stats = await fs.stat(filePath); return stats.size; } async function copyOrCompress(sourcePath) { const fileSize = await getFileSize(sourcePath); const fileName = path.basename(sourcePath); let targetName; if (fileSize >= SIZE_THRESHOLD) { const stem = path.parse(fileName).name; targetName = `${stem}_compressed.jpg`; } else { targetName = fileName; } const targetPath = path.join(TMP_DIR, targetName); if (fileSize < SIZE_THRESHOLD) { await fs.copyFile(sourcePath, targetPath); } else { try { const sharp = (await import('sharp')).default; await sharp(sourcePath) .jpeg({ quality: 80, progressive: true }) .toFile(targetPath); } catch (err) { console.error('FAIL: 压缩图片失败'); process.exit(1); } } return targetPath; } ``` The resulting path is later deleted without confirming that it still refers to the file created by the current invocation: ```js async function cleanup(imagePath) { try { await fs.unlink(imagePath); } catch {} } ``` ### Technical Analysis Node.js filesystem APIs do not perform shell-style tilde expansion. Therefore, the literal path `"~/.openclaw/media/browser/"` is not reliably resolved to the current user's home directory. It is treated as a relative path beginning with a directory named `~`, making the actual location dependent on the process working directory. The destination filename is deterministically derived from `path.basename(sourcePath)`. Multiple invocations using the same basename t ...[truncated 2537 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Resolve the intended home directory explicitly and use a unique private staging directory for every invocation: ```js const MEDIA_ROOT = path.join( os.homedir(), '.openclaw', 'media', 'browser' ); await fs.mkdir(MEDIA_ROOT, { recursive: true, mode: 0o700 }); const invocationDir = await fs.mkdtemp(path.join(MEDIA_ROOT, 'fast-image-')); ``` Further hardening should include: 1. Generate a cryptographically unpredictable destination filename instead of reusing the source basename. 2. Create destination files atomically with exclusive semantics where the processing API permits it. 3. Use `fs.lstat()` and reject symbolic links or other unexpected filesystem object types. 4. Ensure the staging directory is owned by the expected user and is not writable by untrusted users. 5. Retain only a sanitized extension rather than an attacker-controlled filename. 6. Put cleanup in a `finally` block and remove the unique invocation directory recursively after the send operation completes. 7. Before cleanup, avoid operating on paths outside the freshly created staging directory. 8. Add concurrent-execution tests using identical input basenames. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:49
Finding
Unpinned Third-Party Dependency Installation<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 49-52 **Vulnerability Type**: Mutable and non-reproducible dependency installation **Risk Level**: Medium ### Vulnerable Code ```md ## Dependencies - Node.js - sharp: `npm install sharp` - openclaw CLI ``` ### Technical Analysis The documented installation command retrieves the currently resolved `sharp` release without pinning an exact audited version. The project also contains no lockfile or integrity metadata in the audited directory. As a result, two users following the same instructions at different times may install different dependency artifacts. npm packages may execute lifecycle scripts during installation, and the dependency is imported into the Skill process at runtime: ```js const sharp = (await import('sharp')).default; ``` This weakens supply-chain reproducibility and causes the reviewed package behavior to depend on future registry state. This finding does not establish that the legitimate `sharp` package is malicious; the risk arises from installing a mutable, unpinned dependency without recorded integrity guarantees. ### Attack Path 1. A user follows the documented `npm install sharp` instruction. 2. npm resolves the dependency version and transitive dependency graph available at installation time. 3. If a future release, registry artifact, maintainer account, or transitive dependency is compromised, npm downloads the affected artifact. 4. Applicable package lifecycle scripts may execute during installation. 5. The Skill later imports the installed module while processing a large image. 6. Compromised dependency code executes with the privileges of the installing user or running Agent process. This path depends on an upstream or registry compromise; no such compromise was identified in the audited files. ### Impact Assessment If the resolved dependency or one of its transitive dependencies is compromised, malicious code could potentially: - Execute during pac ...[truncated 458 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `sharp` to a specific audited version rather than installing the latest matching registry release: ```sh npm install --save-exact sharp@<audited-version> ``` 2. Add a `package.json` and committed lockfile generated by a supported package manager. 3. Preserve and verify registry integrity hashes in the lockfile. 4. Use `npm ci` for reproducible installations. 5. Configure a trusted registry explicitly in deployment environments. 6. Review dependency provenance, release signatures, lifecycle scripts, and transitive dependencies before upgrading. 7. Use automated dependency scanning and establish a controlled update process. 8. Avoid recommending global or privileged installation unless it is strictly required. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Vague Triggers

Medium
Confidence
96% confidence
Finding
The trigger keywords are broad terms like "image," "media," "photo," and "send," which are common in normal conversation and can cause the skill to activate in unintended contexts. Because this skill copies local files, may compress them, and sends them to an external channel, accidental invocation could lead to unintended disclosure of local images.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill documentation describes copying local files, transmitting media to a channel, and deleting temporary files, but it does not clearly warn the user that local data will be accessed and sent externally. In a file-handling skill, missing disclosure increases the risk of users invoking it without understanding the privacy and data-handling consequences, which can result in accidental exfiltration or loss of temporary artifacts needed for audit or recovery.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
Natural-language strings in the header comments and runtime error messages are written only in Chinese, with no opt-in or indication that the skill is intended solely for Chinese-speaking users. This can violate language/locale policy when a skill imposes a specific language without user choice or justified regional scope.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This code writes a user-supplied image into a local media directory and may create a compressed derivative, which affects user data on disk. While there are terse comments and success/failure logs, there is no clear user-facing disclosure at the point of execution that the file will be copied into ~/.openclaw/media/browser/ and then deleted for non-qqbot channels.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The script invokes `openclaw` via `spawn(..., { shell: true })` while passing user-controlled values such as `channel`, `target`, `message`, and the derived image path. Using a shell here creates command-injection risk if any argument contains shell metacharacters, turning a simple image-send utility into arbitrary command execution.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script invokes an external command to send the image and optional message to a specified channel/target, which is a network-affecting operation involving user data. Although the file header says it sends a local image to a channel, the execution path lacks a clear runtime warning or confirmation that content will be transmitted to an external destination.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
send_image.mjs:75