T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/commands/export.py:63
- Finding
- Arbitrary Local-File Overwrite Through the Export Command## Vulnerability Details **File Location**: `scripts/commands/export.py:63-72` **Vulnerability Type**: Unrestricted file write and unsafe overwrite **Risk Level**: High ### Vulnerable Code ```python # Write to file if output_path not provided, use default if not output_path: output_path = os.path.join(base, f"export_{customer_id}{ext}") else: output_path = os.path.expanduser(output_path) try: Path(output_path).write_text(exported_content, encoding="utf-8") except Exception as e: ``` ### Technical Analysis The user-controlled `output_path` is expanded and passed directly to `Path.write_text()`. The implementation does not: - Restrict the destination to the customer's journal or export directory. - Canonicalize the path and verify its parent directory. - Reject symbolic links. - Check whether the destination already exists. - Require confirmation before truncating an existing file. `Path.write_text()` opens an existing destination for truncating writes. It also follows symbolic links. Consequently, an invocation can replace any file writable by the account running the Skill. This behavior contradicts the documented security model in `SKILL.md`, which states that all I/O is constrained to `~/.openclaw/customers/{customer_id}/`. ### Attack Path 1. Ensure the selected customer has at least one journal entry so that export reaches the file-writing branch. 2. Invoke the export command with `--output-path` set to an existing file writable by the Skill process. 3. Alternatively, select a path that is a symbolic link to another writable file. 4. The command calls `Path(output_path).write_text(...)`. 5. The destination file is truncated and replaced with the generated journal export. ### Impact Assessment The attacker can overwrite files with the permissions of the account running the Skill. The scope includes user documents, application configuration, shell configuration, local state fil ...[truncated 197 chars]
- Remediation
- ## Remediation Suggestions - Store all exports in a dedicated directory beneath the sanitized customer directory. - Resolve both the allowed export directory and requested destination with `Path.resolve()`, then verify that the destination is a descendant of the allowed directory. - Reject destinations whose path or existing components are symbolic links. - Use exclusive creation by default, such as mode `"x"`, to prevent silent replacement. - Require a separate explicit overwrite flag when replacing an existing export. - Write through a same-directory temporary file, flush and `fsync()` it, and use `os.replace()` only after all validation succeeds. - Do not disclose unrestricted host filesystem paths as a supported export feature unless such access is explicitly required and authorized.
