T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/ifind_token_store.py:17
- Finding
- Credential File Is Created Without Fail-Closed Permission Enforcement<![CDATA[ ## Vulnerability Details **File Location**: `scripts/ifind_token_store.py:17-21, 55-56` **Vulnerability Type**: Unsafe sensitive-file creation and silently ignored permission errors **Risk Level**: Medium ### Vulnerable Code ```python def _chmod_owner_only(path: Path) -> None: try: path.chmod(stat.S_IRUSR | stat.S_IWUSR) except OSError: pass ``` ```python STORE_PATH.write_text( json.dumps(payload, ensure_ascii=False, indent=2), encoding='utf-8' ) _chmod_owner_only(STORE_PATH) ``` ### Technical Analysis The long-lived iFinD refresh token is first written using `Path.write_text()`. Its initial permissions therefore depend on the process umask. Mode `0600` is applied only after the complete token has already been written. This creates a time-of-check/time-of-protection interval during which the credential file may have broader permissions than intended. More importantly, `_chmod_owner_only()` suppresses every `OSError`, so the operation can report successful token storage even if permission hardening fails. The containing credential directory is also created without explicitly verifying or enforcing owner-only permissions. The credential path is legitimate and necessary for the skill's declared functionality, but the implementation does not reliably enforce the documented owner-only storage policy. ### Attack Path 1. The user invokes the token-storage command. 2. `credentials.json` is created with permissions determined by the current umask. 3. A local process or another account with directory access reads the file before permission hardening, or permission hardening fails. 4. The exception is silently ignored and the script still reports that the token was stored successfully. 5. The observer uses the exposed refresh token to request an iFinD access token and perform API calls under the victim's account. Exploitation requires local access and sufficient filesystem traversal permissions; no remote exploitation pat ...[truncated 397 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Create and verify the credential directory with mode `0700`. - Create a temporary file atomically with mode `0600`, for example with `os.open()` using `O_CREAT | O_EXCL` and an explicit mode. - Write and flush the token, then atomically replace the destination with `os.replace()`. - Verify the resulting file's ownership, type, and mode after replacement. - Refuse to report success if permission enforcement or verification fails. - Avoid following symbolic links and reject a credential path that is not a regular file owned by the current user. - Consider using the operating system's credential manager or keyring instead of a plaintext JSON file. ]]>
