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`. ]]>
