T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/feishu-contacts.py:124
- Finding
- Organization-Wide Contact Data Is Cached Without Secure File Controls<![CDATA[ ## Vulnerability Details **File Location**: `scripts/feishu-contacts.py`, lines 7 and 105–139 **Vulnerability Type**: Insecure storage of sensitive contact data and unsafe file creation **Risk Level**: Medium ### Vulnerable Code ```python CACHE_FILE = os.path.expanduser("~/.openclaw/.feishu-contacts-cache.json") ``` ```python for u in resp.get("data", {}).get("items", []): oid = u.get("open_id") if oid: dept_users[dept_id].append(oid) if oid not in all_users: name = u.get("name", "") py_full, py_init = to_pinyin(name) all_users[oid] = { "name": name, "open_id": oid, "email": u.get("email", ""), "en_name": u.get("en_name", ""), "pinyin": py_full, "pinyin_initials": py_init, "departments": [] } all_users[oid]["departments"].append(dept_id) # 3. Save cache cache = { "synced_at": time.strftime("%Y-%m-%d %H:%M:%S"), "users": list(all_users.values()), "departments": list(all_depts.values()), "dept_users": dept_users } with open(CACHE_FILE, "w") as f: json.dump(cache, f, ensure_ascii=False, indent=2) ``` ```python def load_cache(): if not os.path.exists(CACHE_FILE): print("No cache found. Run 'sync' first.", file=sys.stderr); sys.exit(1) with open(CACHE_FILE) as f: return json.load(f) ``` ### Technical Analysis The synchronization command retrieves and persistently stores names, email addresses, Feishu Open IDs, English names, department identifiers, and department-membership mappings for the organization. The cache is created through `open(CACHE_FILE, "w")`, so its effective permissions depend on the process umask. The code does not explicitly enforce owner-only permissions such as `0600`. The implementation also performs no ownership, regular-file, or symbolic-link validation before opening the cache. Python's normal file opening behavior follows sy ...[truncated 1958 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Ensure `~/.openclaw` exists, is owned by the current user, and has mode `0700`. 2. Create the cache with owner-only mode `0600`, independent of the process umask. 3. Reject symbolic links and non-regular files. On supported platforms, use `os.open` with `O_NOFOLLOW`, `O_CREAT`, `O_EXCL`, and an explicit `0o600` mode. 4. Write to a securely created temporary file in the same directory, flush and `fsync` it, and then use `os.replace` for atomic publication. 5. Verify the ownership and permissions of an existing cache before reading it. 6. Minimize cached fields. If local search does not require email addresses, omit them and retrieve them only through the live `get` command. 7. Consider encrypting the cache at rest when the threat model includes local filesystem access. 8. Add retention controls and provide a command to securely remove stale cached directory information. ]]>
