T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/geekbi_auth.py:59
- Finding
- Bearer Tokens Are Persisted in Multiple Plaintext Locations and a Cross-Skill Namespace<![CDATA[ ## Vulnerability Details **File Location**: `scripts/geekbi_auth.py:59-75`, `scripts/geekbi_auth.py:376-388`, `scripts/geekbi_auth.py:631-648` **Vulnerability Type**: Plaintext credential persistence and excessive credential replication **Risk Level**: Medium ### Vulnerable Code ```python def _user_config_state_path(): return _absolute_path( user_config_path("GeekBI", appauthor=False, ensure_exists=True) / "temu-research-skill" / AUTH_FILE_NAME ) def _skill_state_path(): return _absolute_path(Path(__file__).parent.parent / AUTH_STATE_DIR / AUTH_FILE_NAME) def _workspace_state_path(): return _absolute_path(Path(os.getcwd()) / AUTH_STATE_DIR / AUTH_FILE_NAME) ``` The same state is written to every usable store: ```python def _write_state_files(stores, payload): normalized = _normalize_state(payload) errors = [] written = 0 for store in stores: try: _write_state_file(store, normalized) written += 1 except OSError as error: errors.append(f"{store.kind}: {_storage_probe_reason(error)}") if written == 0: reason = ";".join(errors) or "登录状态目录不可用" raise OSError(reason) ``` The persisted payload includes the bearer token itself: ```python token_data = response["data"] access_token = token_data.get("accessToken") if not isinstance(access_token, str) or not access_token: raise ValueError("登录令牌响应缺少 accessToken") expires_in = int(token_data.get("expiresIn", 0)) def save_token(latest): latest_server = latest["servers"].get(server_key) if not isinstance(latest_server, dict): return False, False latest_pending = latest_server.get("pending") if not isinstance(latest_pending, dict): return False, False if latest_pending.get("deviceCode") != pending.get("deviceCode"): return False, False _remove_access_token(latest_server) latest_server["accessToken"] = access_token latest_ser ...[truncated 2634 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store authentication state in exactly one per-user configuration location by default. 2. Correct the namespace from `temu-research-skill` to a unique Ozon-specific identifier such as `ozon-keyword-search-skill`. 3. Do not mirror bearer tokens into the installed Skill directory or current working directory. 4. Use an operating-system credential service, such as Keychain, Credential Manager, or Secret Service, to protect the token at rest. 5. If file storage is unavoidable: - Retain only short-lived tokens. - Enforce owner-only ACLs on every supported operating system. - Refuse storage when secure permissions cannot be established. - Store non-sensitive metadata separately from credentials. 6. Provide migration logic that securely removes legacy copies from all three old locations. 7. Ensure logout and expiration cleanup remove every legacy credential copy. 8. Restrict server-side tokens to the minimum Ozon API scopes and short lifetimes. ]]>
