T09 · Insecure Skill Coding Practices
- Location
- scripts/config.py:44
- Finding
- API Key Stored in a Plaintext Dotenv File Without Input Sanitization or Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/config.py:44-65` **Vulnerability Type**: Plaintext credential storage and dotenv injection **Risk Level**: Medium ### Vulnerable Code ```python def save_api_key_to_env(api_key: str) -> bool: try: env_path = Path(".env") lines = [] if env_path.exists(): lines = env_path.read_text(encoding="utf-8").splitlines() found = False new_lines = [] for line in lines: if line.startswith("XBY_APIKEY="): new_lines.append(f"XBY_APIKEY={api_key}") found = True else: new_lines.append(line) if not found: new_lines.append(f"XBY_APIKEY={api_key}") env_path.write_text("\n".join(new_lines) + "\n", encoding="utf-8") os.environ["XBY_APIKEY"] = api_key return True except Exception as e: print(f"Failed to save API key: {e}") return False def set_api_key(api_key: str) -> bool: if not api_key or not api_key.strip(): return False api_key = api_key.strip() if not save_api_key_to_env(api_key): return False ``` ### Technical Analysis The application persists the API key directly in a plaintext `.env` file. It does not explicitly apply restrictive file permissions such as mode `0600`. When the file is created, its effective permissions therefore depend on the process umask. In an environment with a permissive umask, another local user or process may be able to read the credential. The API key is also interpolated directly into dotenv file contents without rejecting embedded carriage-return or newline characters. Calling `strip()` removes only leading and trailing whitespace; it does not remove newline characters within the value. A value such as `legitimate-key\nADDITIONAL_SETTING=attacker-value` can consequently introduce an additional persistent dotenv entry. The file update is not atomic. An inte ...[truncated 1952 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Prefer a platform credential manager, secret-management service, or session-scoped environment variable instead of writing the API key to the project directory. 2. If dotenv persistence is required, reject carriage returns and newlines before writing: ```python if "\r" in api_key or "\n" in api_key: raise ValueError("API key contains invalid characters") ``` 3. Create the secret file with restrictive permissions and verify them after replacement: ```python os.chmod(env_path, 0o600) ``` 4. Update the file atomically by creating a mode-`0600` temporary file in the same directory, flushing and synchronizing it, and replacing `.env` with `os.replace()`. 5. Avoid constructing dotenv records through unrestricted string interpolation. Use a serializer that safely quotes values, while still rejecting line separators. 6. Ensure `.env` is excluded from version control, build artifacts, logs, backups, and diagnostic bundles. 7. Avoid retaining the credential in both a plaintext file and `os.environ` unless both storage locations are operationally required. 8. Document the local threat model and warn users that persistent storage places the key on disk. ]]>
