Back to skill

Security audit

Psd Automator

Security checks for vulnerabilities and agentic risk

Overview

This PSD automation skill is mostly purpose-aligned, but needs review because its dry-run can still write or copy files and its local index can persist a broad inventory of design files.

Review before installing on a workstation with sensitive design assets. Use explicit --root values for indexing, avoid the Desktop/Documents default, review or delete ~/.openclaw index and audit files, do not rely on dry-run as zero-write behavior, set styleLockSoftRetry to false when style integrity matters, and constrain task output paths and sender allowlists.

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
scripts/run-task.js:367
Finding
Dry-run mode performs unintended filesystem writes<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run-task.js:219-229`, `scripts/run-task.js:367-390` **Vulnerability Type**: Dry-run safety violation and unsafe file creation **Risk Level**: Medium ### Vulnerable Code ```js function resolveWorkingPsdPath(task, sourcePath) { const sourceMode = task.workflow?.sourceMode || "inplace"; if (sourceMode !== "copy_then_edit") { return { sourceMode, workingPath: sourcePath, copiedFrom: undefined }; } const copyToDir = path.resolve(expandHome(task.workflow?.copyToDir || resolveDesktopDir())); const fileName = path.basename(sourcePath); const workingPath = path.join(copyToDir, fileName); ensureParentDir(workingPath); fs.copyFileSync(sourcePath, workingPath); return { sourceMode, workingPath, copiedFrom: sourcePath }; } ``` The filesystem-mutating function is invoked before the dry-run condition is evaluated: ```js const platform = os.platform(); const dryRun = Boolean(args.dryRun || (normalizedTask.options && normalizedTask.options.dryRun)); const workingInfo = resolveWorkingPsdPath(normalizedTask, resolved.path); const pathBridge = platform === "darwin" ? prepareMacPathBridgeIfNeeded(normalizedTask, workingInfo.workingPath) : { executionPath: workingInfo.workingPath, syncBack: () => {} }; const edits = normalizedTask.input.edits || []; const plannedExports = Array.isArray(normalizedTask.output?.exports) ? normalizedTask.output.exports : []; const matchImagePath = normalizedTask.options?.matchImagePath ? path.resolve(expandHome(normalizedTask.options.matchImagePath)) : ""; const enableBundleZip = normalizedTask.options?.bundleZip === true; const plannedPngPaths = plannedExports .filter((item) => item.format === "png") .map((item) => item.mode === "layer_sets" ? resolvePngOutputDir(item, workingInfo.workingPath) : resolvePngOutputPath(item, workingInfo.workingPath), ); if (dryRun) { ``` ### Technical Analysis A dry-run is expected to calcula ...[truncated 2170 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Evaluate dry-run mode immediately after task validation and path resolution, before invoking any function that creates directories, copies files, writes logs, or prepares temporary files. 2. Split path planning from path execution: - Add a pure function that calculates the intended working and bridge paths. - Only create directories and copy files in the real execution branch. 3. Do not call `resolveWorkingPsdPath()` or `prepareMacPathBridgeIfNeeded()` during dry-run unless they support an explicit non-mutating mode. 4. Before copying during actual execution, detect an existing destination and require an explicit overwrite option or generate a collision-resistant filename. 5. Add automated tests that snapshot the relevant filesystem before and after dry-run and assert that no files or directories are created, modified, or overwritten. 6. Consider whether writing the audit log during dry-run is intended. If strict zero-mutation semantics are required, return the preview without appending to a persistent log or explicitly document that exception. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run-task.js:500
Finding
Style-lock integrity control is silently disabled after a mismatch<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run-task.js:450-453`, `scripts/run-task.js:500-527` **Vulnerability Type**: Fail-open integrity-control bypass **Risk Level**: Medium ### Vulnerable Code The relaxed retry is enabled by default whenever style lock itself is enabled: ```js const styleLockEnabled = normalizedTask.options?.styleLock !== false; const styleLockSoftRetryEnabled = styleLockEnabled && normalizedTask.options?.styleLockSoftRetry !== false; ``` A style mismatch then causes the operation to be repeated with style locking disabled: ```js if (!result || result.status !== 0) { const message = `${result?.stderr || ""}\n${result?.stdout || ""}`; if (styleLockSoftRetryEnabled && message.includes("E_STYLE_MISMATCH")) { const relaxedResult = executeWithRetry({ maxRetries, retryEnabled, execute: () => { if (platform === "darwin") { return runMacModify( pathBridge.executionPath, edit.layerName, edit.newText, pathBridge.executionPath, timeoutMs, false, ); } return runWinModify( workingInfo.workingPath, edit.layerName, edit.newText, workingInfo.workingPath, timeoutMs, false, ); }, }); if (relaxedResult && relaxedResult.status === 0) { styleLockFallbackUsed = true; result = relaxedResult; } } } ``` ### Technical Analysis The style-lock mechanism verifies that changing text does not unexpectedly alter the Photoshop text layer's font or size. This is an output-integrity safeguard and is described by the Skill documentation as part of its safety baseline. However, `styleLockSoftRetryEnabled` defaults to `true` because it is disabled only when the caller explicitly supplies `styleLockSoftRetry: false`. When an edit fails with `E_STYLE_MISMATCH`, the runner automatically repeats the edit with the ...[truncated 1754 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Change the default to fail closed: ```js const styleLockSoftRetryEnabled = styleLockEnabled && normalizedTask.options?.styleLockSoftRetry === true; ``` 2. Return `E_STYLE_MISMATCH` by default and require explicit requester or operator approval before retrying without style lock. 3. If relaxed execution is intentionally permitted: - Use a non-success status such as `warning` or `requires-confirmation`. - Include the original and resulting font and size values. - Prevent automatic export or delivery until approval is received. 4. Separate strict and relaxed execution into distinct task modes so disabling an integrity control is visible in task configuration and audit logs. 5. Update the documentation to state precisely when style lock may be disabled and ensure it does not claim unconditional style preservation. 6. Add tests confirming that a mismatch fails the task unless the caller explicitly opts into a relaxed retry. ]]>
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The supplied code chunk does not implement the declared Photoshop PSD text-replacement behavior. It only provides a helper function for expanding home-directory shortcuts in file paths. While such a utility could support file handling in a larger system, this chunk by itself has a materially different and much narrower purpose than the declared description. Therefore, the description does not accurately represent what this code chunk actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The code chunk does not implement the declared end-to-end PSD automation behavior. Instead, it handles task file loading and task schema validation/normalization. That by itself could be a supporting component, but it also exposes capabilities beyond the description: notably image placement and text deletion in addition to text replacement. More importantly, many prominent declared features—dry-run safety, style-lock checks, rollback, local PSD index cache, and Photoshop/dialog behavior—are not reflected in this code. Since the description is specifically about a Photoshop PSD text-replacement skill, while this chunk is a generic task parser/normalizer with broader edit types, the description does not accurately represent what this code actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared purpose centers on automating text replacement inside PSD files via Photoshop workflows. This code chunk does not interact with PSDs, Photoshop, text layers, chat channels, rollback logic, or safety/style checks. Its actual purpose is unrelated image analysis: loading raster images with sharp, comparing them visually, hashing image pixel content in directories, and identifying changed image files. While filesystem access could be a supporting detail in some skills, here the primary behavior is materially different from the declared Photoshop text-replacement functionality, so this is a clear mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The code substantially includes the declared PSD text-replacement workflow features: Mac/Windows execution, dry-run mode, style-lock checks, backup creation (a form of rollback support), and local PSD path resolution via an index. However, it also implements several materially broader capabilities not disclosed in the description. Most notably, it can export PNGs and layer/artboard image sets, compare exported images using visual similarity against a reference image, package exports into ZIP files, and place external images into PSDs on Mac. These are not just internal implementation details; they are user-facing capabilities that extend the skill beyond 'PSD text replacement.' Therefore the description does not accurately represent the full behavior of the code.

Ae1

High
Category
analysis-evasion
Content
4. Subagent runs `run-task.js` locally.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Session Persistence

Medium
Category
Rogue Agent
Content
## Build and Refresh PSD Index

Create or refresh local cache:

```bash
node skills/psd-automator/scripts/build-index.js \
Confidence
84% confidence
Finding
The skill instructs users to build and refresh a local PSD index stored under a persistent path such as ~/.openclaw/psd-index.json. Persistent indexing of project roots can expose sensitive file names, directory structures, and asset locations across sessions or to other local processes/users if permissions and retention are not tightly controlled.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The script defaults to recursively scanning the user's Desktop and Documents folders when no explicit root is provided, which collects metadata about PSD/PSB files unrelated to the immediate task. In this skill context, that behavior expands access far beyond a specific requested file and can silently inventory sensitive design assets and associated sidecar text/layer metadata.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script performs broad recursive indexing and persists the results to ~/.openclaw/psd-index.json without any upfront notice, confirmation, or data minimization. Because the index includes file paths, project names, modification times, sizes, and sidecar-derived layer/text metadata, it creates a durable local inventory of potentially sensitive design content that users may not expect.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
This code creates backups and later copies, writes, and overwrites PSD, PNG, and ZIP files, but the script only emits structured results after execution and does not provide any user-facing warning or confirmation before modifying files. For a code file, safety-relevant file writes should have at least some visible disclosure in code comments, prompts, or logging unless clearly documented elsewhere in markdown, which is not present in this file.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The skill accepts an external reference image path and performs visual similarity analysis against generated outputs, which is not necessary for basic text replacement. This broadens the skill into processing arbitrary local images and can expose additional sensitive files or metadata through side-channel selection logic, especially when file paths are supplied from chat-driven tasks.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The script goes beyond simple PSD text replacement by supporting image placement, PNG export, and ZIP bundling. Those capabilities materially expand data access and modification scope, which increases the chance of unintended file writes, exfiltration via packaged outputs, or use outside the declared skill purpose; the risk is amplified because paths and export targets are task-controlled.

Natural-Language Policy Violations

Low
Confidence
77% confidence
Finding
The file contains prescriptive natural-language response requirements specifically for DingTalk, and the only natural-language dispatch example is in Chinese. While the marker format itself is language-neutral, the skill does not offer any user opt-in or alternative locale behavior for this chat-channel-specific response pattern.

Intent-Code Divergence

Low
Confidence
72% confidence
Finding
The inline comment and returned error make clear that `place_image` is unsupported on Windows, while the broader skill positioning emphasizes Mac and Windows automation. This creates an intent/documentation gap for at least one supported operation, since the code contradicts an implied uniformly cross-platform capability.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/run-task.js:153