T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/quote.py:65
- Finding
- Authentication Token Cached Without Explicit Access Controls or Symlink Protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/quote.py:65-77` **Vulnerability Type**: Insecure credential storage and unsafe file creation **Risk Level**: Medium ### Vulnerable Code ```python def save_token_to_cache(token): """Save token to cache with expiration timestamp.""" CACHE_DIR.mkdir(parents=True, exist_ok=True) expires_at = datetime.now(timezone.utc) + timedelta(hours=TOKEN_TTL_HOURS) data = { "token": token, "expires_at": expires_at.isoformat() } with open(TOKEN_CACHE_FILE, "w") as f: json.dump(data, f, indent=2) ``` ### Technical Analysis The Skill stores a reusable Quodd authentication token at the predictable path `~/.openclaw/credentials/quodd-token.json`. Neither the credentials directory nor the token file is assigned an explicit restrictive permission mode. Consequently, effective access permissions depend on the process umask and any pre-existing permissions on `~/.openclaw/credentials`. Under a permissive configuration, other local users or processes may be able to read the cached token. The ordinary `open(..., "w")` operation also follows symbolic links. The implementation does not verify that the destination is a regular file owned by the current user. If an attacker can modify the credentials directory, the attacker may replace `quodd-token.json` with a symbolic link before the Skill writes the token. ### Attack Path A viable local token-disclosure path is: 1. The Skill runs in an environment with a permissive umask or a credentials directory accessible to another local account. 2. The user invokes the Skill, causing a Quodd token to be acquired and cached. 3. The cache file is created without an explicitly enforced `0600` mode. 4. A local attacker reads the token from `~/.openclaw/credentials/quodd-token.json`. 5. The attacker reuses the token against the Quodd quote service until it expires or is revoked. A symlink-based path requires stronger local access: 1 ...[truncated 1179 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create and validate the credentials directory with owner-only permissions: ```python CACHE_DIR.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(CACHE_DIR, 0o700) ``` 2. Reject cache paths that are symbolic links or are not regular files owned by the current user. 3. Write the token to a securely created temporary file in the same directory using mode `0600`, flush and synchronize it, and then atomically replace the destination. 4. Ensure the final cache file remains owner-readable and owner-writable only: ```python fd = os.open(temp_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) ``` 5. When loading the cache, validate file ownership, file type, and permission bits before reading the token. 6. Consider using an operating-system credential store instead of a plaintext JSON file. If file caching remains necessary, document that the token is sensitive and should never be copied, logged, or shared. ]]>
