T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/main.py:16
- Finding
- Persistent Data File Uses Inherited Permissions and Follows Symbolic Links<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py`, lines 16–29 **Vulnerability Type**: Insecure persistent file handling **Risk Level**: Medium ```python def __init__(self, data_file="~/.openclaw/equipment_log.json"): self.data_file = Path(data_file).expanduser() self.data_file.parent.mkdir(parents=True, exist_ok=True) self.equipment = self._load() def _load(self): if self.data_file.exists(): with open(self.data_file) as f: return json.load(f) return {} def _save(self): with open(self.data_file, 'w') as f: json.dump(self.equipment, f, indent=2) ``` The related documentation in `SKILL.md`, lines 43 and 54, states that output is saved to and restricted to the workspace, while the implementation persistently stores records under the user's home directory at `~/.openclaw/equipment_log.json`. ### Technical Analysis The parent directory and data file are created without explicit restrictive permissions. Their resulting permissions therefore depend on the process umask. In an environment with a permissive umask or unsuitable preexisting permissions, equipment names and locations may become accessible to other local accounts. The calls to `open()` also follow symbolic links. The implementation does not verify whether `~/.openclaw`, `equipment_log.json`, or any relevant path component is a symbolic link, nor does it validate ownership and file type before reading or truncating the destination. Opening the file with mode `w` truncates the resolved target before writing JSON. The default path is fixed rather than supplied through the command-line interface, which limits remote exploitability. Exploitation requires local access sufficient to prepare or modify the destination path. In addition, initialization loads an existing destination as JSON before `_save()` is reached, so a symlink overwrite attack requires the linked target to initially contain valid JSON compatible with the expected o ...[truncated 1563 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store data in a documented, application-controlled location. If workspace-only storage is intended, resolve the destination beneath the workspace and reject paths escaping that boundary. 2. Create the application directory with mode `0700` and verify that it is a real directory owned by the current user. 3. Create data files with mode `0600` rather than relying on the process umask. 4. Reject symbolic links and non-regular files. On supported platforms, use `os.open()` with `O_NOFOLLOW`, `O_CREAT`, and suitable exclusive or ownership checks. 5. Save atomically by writing to a securely created temporary file in the same trusted directory, flushing and synchronizing it, setting mode `0600`, and replacing the destination with `os.replace()`. 6. Before reading or replacing an existing file, use `lstat()` or descriptor-based checks to confirm its type, owner, and expected permissions. Prefer descriptor-relative operations to reduce time-of-check/time-of-use races. 7. Update `SKILL.md` to disclose the actual persistent storage location and the nature of the stored data, or change the implementation so that its behavior matches the workspace-restriction claim. ]]>
