Back to skill

Security audit

Claw Xiaoai

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent as a character selfie generator, but it quietly saves recent user request details locally and exposes broad file-write behavior that should be reviewed before install.

Install only if you are comfortable with a persona skill that sends image prompts to ModelScope, saves continuity state under ~/.openclaw, and may generate images from broad conversational triggers; prefer clearing the state file, using narrowly scoped output paths, and avoiding private details in selfie requests.

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/build-claw-xiaoai-prompt.mjs:6
Finding
Raw User Requests Persisted in Plaintext Without Minimum-Permission Controls<![CDATA[ ## Vulnerability Details **File Location**: `scripts/build-claw-xiaoai-prompt.mjs`, lines 6, 20–21, 99–101, and 119 **Vulnerability Type**: Unnecessary plaintext retention of potentially sensitive user input **Risk Level**: Medium ### Vulnerable Code ```js const STATE_PATH = resolve(process.env.HOME || '/root', '.openclaw', 'claw-xiaoai-state.json'); ``` ```js function loadState(){ try{ return existsSync(STATE_PATH)? JSON.parse(readFileSync(STATE_PATH,'utf8')):{};}catch{return{};}} function saveState(state){ mkdirSync(dirname(STATE_PATH),{recursive:true}); writeFileSync(STATE_PATH, JSON.stringify(state,null,2)+'\n','utf8'); } ``` ```js const nextState={ scene, mode, slot:slotInfo.slot, lastRequest:request, updatedAt:new Date().toISOString(), outfitTop, outfitBottom, outfitColor, pose, cameraAngle }; return { prompt, mode, state: nextState, slotInfo, preset }; ``` ```js if(save) saveState(result.state); ``` ### Technical Analysis The prompt builder saves continuity state by default in `~/.openclaw/claw-xiaoai-state.json`. The persisted object contains `lastRequest: request`, which is the complete, unmodified selfie request supplied by the user. Persisting the raw request exceeds the minimum data needed for the Skill's continuity functionality. Subsequent continuity decisions use derived properties such as `scene`, `mode`, `outfitTop`, `outfitBottom`, `pose`, and `cameraAngle`; the code does not need the previous `lastRequest` to provide that behavior. The file and its parent directory are created without explicit restrictive modes. Their effective permissions therefore depend on the process umask. In an environment with permissive defaults, other local users or processes may be able to read the retained request. The default persistence and retention behavior is also not disclosed in `SKILL.md`. This is not agent-memory poisoning because the stored request is not executed as a future instruction. It is an insecure data-retention and local conf ...[truncated 1117 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `lastRequest` from the persisted state because it is not required by the continuity logic: ```js const nextState = { scene, mode, slot: slotInfo.slot, updatedAt: new Date().toISOString(), outfitTop, outfitBottom, outfitColor, pose, cameraAngle }; ``` 2. Make persistence opt-in rather than enabled by default. For example, replace the default `save=true` behavior with an explicit `--save` option. 3. If persistence is necessary, create the directory with mode `0700` and the file with mode `0600`: ```js mkdirSync(dirname(STATE_PATH), { recursive: true, mode: 0o700 }); writeFileSync( STATE_PATH, JSON.stringify(state, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 } ); ``` 4. Avoid retaining raw user text. Persist only the minimum derived fields required for continuity. 5. Add an expiration or cleanup policy for state data. 6. Document the persistence location, retained fields, default behavior, and deletion procedure in `SKILL.md`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate-selfie.mjs:14
Finding
Unrestricted Output Path Allows Overwriting Arbitrary User-Writable Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate-selfie.mjs`, lines 14–23 and 63–71 **Vulnerability Type**: Unrestricted file write and overwrite **Risk Level**: Medium ### Vulnerable Code ```js function parseArgs(argv) { const out = { json: false, retry: 1 }; for (let i = 0; i < argv.length; i++) { const a = argv[i]; if (a === '--json') out.json = true; else if (a === '--prompt-stdin') out.promptStdin = true; else if (a === '--prompt') out.prompt = argv[++i]; else if (a === '--out') out.out = argv[++i]; else if (a === '--retry') out.retry = Number(argv[++i] || 1); } return out; } ``` ```js const outPath = resolve(args.out || './claw-xiaoai-selfie.jpg'); let err; for (let attempt = 1; attempt <= Math.max(1, args.retry); attempt++) { const prompt = attempt === 1 ? args.prompt : `${RETRY_PREFIX}, ${args.prompt}`; try { const { taskId, imageUrl, last } = await generate(prompt, runtime); const buf = await fetchBuffer(imageUrl, runtime.timeoutMs); mkdirSync(dirname(outPath), { recursive: true }); writeFileSync(outPath, buf); ``` ### Technical Analysis The `--out` argument accepts an unrestricted path. The script resolves the supplied value to an absolute path, creates missing parent directories, and writes the downloaded response with `writeFileSync()`. By default, `writeFileSync()` opens an existing file with truncation semantics. Consequently, any existing file writable by the Skill process can be replaced with the downloaded image bytes. The script does not: - constrain output to an approved image directory; - verify that the target has an expected image extension; - prevent replacement of an existing file; - reject symbolic-link targets; - confirm that the resolved path remains under an intended output root. The documented workflow normally supplies a fixed image path, but the script itself does not enforce that boundary. Exploitation therefore requires an attacker or unsafe integ ...[truncated 1674 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a dedicated output root, such as an application-owned image directory, and reject any resolved path outside it: ```js const OUTPUT_ROOT = resolve( process.env.HOME || process.cwd(), '.openclaw', 'generated-images' ); const requestedName = args.out || 'claw-xiaoai-selfie.jpg'; const outPath = resolve(OUTPUT_ROOT, requestedName); const relative = path.relative(OUTPUT_ROOT, outPath); if (relative.startsWith('..') || path.isAbsolute(relative)) { fail('Output path must remain inside the generated-images directory.'); } ``` 2. Accept a filename rather than an unrestricted absolute path when possible. 3. Permit only expected image suffixes such as `.jpg`, `.jpeg`, or `.png`. 4. Use exclusive creation, such as the `wx` flag, unless the caller explicitly authorizes overwriting: ```js writeFileSync(outPath, buf, { flag: 'wx', mode: 0o600 }); ``` 5. Check path components and the final destination for symbolic links before writing. For stronger protection, create a new randomized file in a trusted directory rather than accepting an existing destination. 6. Create output directories with restrictive permissions. 7. If replacement is a legitimate feature, require a separate explicit `--overwrite` option and verify that the destination is still inside the approved output root. 8. Ensure host integrations never construct `--out` directly from untrusted conversation text. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

Ae1

High
Category
analysis-evasion
Content
printf '%s' "<prompt>" | node scripts/generate-selfie.mjs --prompt-stdin --out /tmp/claw-xiaoai-selfie.jpg
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
printf '%s' "<prompt>" | node scripts/generate-selfie.mjs --prompt-stdin --out /tmp/claw-xiaoai-selfie.jpg
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
printf '%s' "<prompt>" | node scripts/generate-selfie.mjs --prompt-stdin --out /tmp/claw-xiaoai-selfie.jpg
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
printf '%s' "<prompt>" | node scripts/generate-selfie.mjs --prompt-stdin --out /tmp/claw-xiaoai-selfie.jpg
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
const sceneTag=detectSceneTag(text);
  const mappedScene=sceneTag ? PROMPT_SCENE_BY_TAG[sceneTag] : undefined;
  if(mappedScene) return mappedScene;
  if(slotInfo.slot === 'weekend-day' && isWeekend) return PROMPT_SCENE_BY_TAG[resolveWeekendDaySceneTag(hour, text)];
  if(hasRelativeInstruction(text)) return state.scene || slotInfo.scene;
  return slotInfo.scene;
}
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Natural-Language Policy Violations

High
Confidence
95% confidence
Finding
The retry prompt hard-codes a protected/sensitive demographic attribute ('East Asian ethnicity') and gender constraints into image-generation instructions. This is dangerous because it bakes demographic profiling and identity coercion into outputs without user awareness, increasing the risk of biased, non-consensual, or discriminatory content generation, especially in a persona-based companion skill.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill documents access to environment variables, local config, and a network-backed image generation flow, but it does not declare an explicit tool/permission scope. That creates an authorization transparency gap: a host or reviewer cannot easily tell what capabilities the skill expects, making overbroad execution or accidental secret exposure more likely.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger mapping uses broad conversational phrases like 'What are you doing?' and 'Where are you?' to initiate photo/selfie behavior, which can cause the agent to generate and send images when the user may only want a text response. In a companion persona that simulates current activity, location, outfit, or mood, this increases the risk of unintended media generation and deceptive or privacy-sensitive behavior.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The instruction to "Prefer clean English prompt blocks for persona definitions" imposes a language preference as a policy choice in the skill's natural-language guidance. The file does not offer the user a language choice or explain why English is required for a region-specific or technical reason.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The examples prescribe output captions entirely in Chinese, while the file presents them as the target caption style without any opt-in or alternative language handling. This creates a natural-language locale policy concern because the skill appears to enforce a specific language rather than letting the user choose.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The prompt directs the persona to respond primarily in Chinese regardless of the user's language preference, which can override user intent and reduce transparency or safe use in multilingual contexts. While not a direct security exploit, hard-coded language behavior can degrade user control and make safety-critical guidance less accessible if the user expects another language.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The selfie trigger list is intentionally broad and maps ordinary phrases like 'what are you doing' or 'where are you' to image-generation behavior. In a live agent, this can cause unexpected modality switching and implicit consent issues, where normal conversational prompts are interpreted as requests for personal-looking images or location/context disclosures.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The code loads an API key-backed runtime and makes authenticated requests to a third-party image generation service. For a skill described only as an energetic intern companion, this networked media-generation capability is not an obvious or declared requirement.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The manifest description presents the skill as a character-style companion, not as an image-generation utility. This script submits prompts to a remote image generation API, polls task status, downloads the generated image, and writes it locally, which is a materially different operational scope from a conversational companion description.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The script creates directories and writes the generated image to the filesystem, but provides no prior warning, confirmation, or explanatory comment/docstring about this file write behavior. Under the code-file criteria, file writes should have some visible disclosure unless clearly documented elsewhere in the skill description, which is not present in this file.

Missing User Warnings

Low
Confidence
91% confidence
Finding
The script persists conversational and inferred state to a file under the user's home directory without any user-facing notice, consent prompt, or protective controls. Because the saved state includes prior requests, scene, pose, clothing, and timestamps, it creates a privacy risk and unintended retention of potentially sensitive user interaction data on disk.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The script explicitly uses `Intl.DateTimeFormat('en-GB', ...)`, which imposes a specific language/locale choice in behavior. Under the policy, locale constraints should be user-selectable or clearly justified as region-specific; neither is indicated here.

Static analysis

No suspicious patterns detected.