T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/fast_claw_client.py:51
- Finding
- API Key File Is Stored Without Enforced Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fast_claw_client.py`, lines 51-68 **Vulnerability Type**: Insecure local credential storage **Risk Level**: High ### Vulnerable Code ```python def write_local_api_key(api_key: str) -> None: path = api_key_path() path.parent.mkdir(parents=True, exist_ok=True) path.write_text( json.dumps( { "api_key": api_key, "token": api_key, "service_url": service_url(), "saved_at": datetime.now(timezone.utc).isoformat(), }, indent=2, ) + "\n", encoding="utf-8", ) ``` ### Technical Analysis The client persists a reusable API key in plaintext but does not explicitly restrict the permissions of either the containing directory or the credential file. `Path.mkdir()` and `Path.write_text()` rely on the process's ambient `umask`. Under a permissive `umask`, the resulting file may be readable by other local users. If the destination file already exists with overly broad permissions, rewriting it does not correct those permissions. The implementation also does not verify file ownership or reject a symbolic-link destination before writing. Persisting a key is part of the Skill's declared functionality, but allowing ambient filesystem settings to determine access exceeds the minimum exposure required for that functionality. ### Attack Path 1. A user runs `purchase`, `wait`, or `set-api-key`. 2. The client saves the returned API key to `~/.fast-claw/api-key.json` or a path selected through `FAST_CLAW_API_KEY_PATH`. 3. The file is created or retained with permissions determined by the current `umask` or its previous mode. 4. Another local user or process with filesystem access reads the JSON file. 5. The attacker extracts the reusable `api_key` value. 6. The attacker uses the key to query the account or submit authenticated paid-service requests. A maliciously prepared dest ...[truncated 650 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Create the credential directory with mode `0700` and verify that it is owned by the current user. - Create the credential file with mode `0600`, using low-level flags such as `os.open(..., O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW, 0o600)` where supported. - For updates, write to a protected temporary file in the same directory, flush and synchronize it, set mode `0600`, and atomically replace the destination with `os.replace()`. - Explicitly call `os.chmod(path, 0o600)` when safely updating an existing regular file. - Reject symbolic links and non-regular files, and validate ownership before reading, writing, or deleting the credential file. - Consider using an operating-system credential store instead of a plaintext JSON file. - Avoid storing the same secret twice under both `api_key` and `token`; retain legacy compatibility during reads without duplicating the value during writes. ]]>
