T09 · Insecure Skill Coding Practices
- Location
- redditrank_tui/config.py:29
- Finding
- API Key Persisted Without Explicit Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `redditrank_tui/config.py:29-39`; `setup.sh:190-195` **Vulnerability Type**: Plaintext credential storage with permissions inherited from the process umask **Risk Level**: Medium ### Vulnerable Code `redditrank_tui/config.py:29-39`: ```python def save_api_key(key: str): """Save API key to config file.""" CONFIG_DIR.mkdir(parents=True, exist_ok=True) data = {} if CONFIG_FILE.exists(): try: data = json.loads(CONFIG_FILE.read_text()) except Exception: pass data["api_key"] = key CONFIG_FILE.write_text(json.dumps(data, indent=2)) ``` `setup.sh:190-195`: ```bash mkdir -p "$RR_DIR" if $HAS_JQ; then echo "{\"api_key\": \"$API_KEY\"}" | jq . > "$RR_CONFIG" else echo "{\"api_key\": \"$API_KEY\"}" > "$RR_CONFIG" fi ``` The setup script also exposes the complete credential in terminal output at `setup.sh:185` and `setup.sh:200`: ```bash echo -e " Key: ${CYAN}$API_KEY${NC}" ... echo -e " ${DIM}export REDDITRANK_API_KEY=$API_KEY${NC}" ``` ### Technical Analysis The application stores the RedditRank API key as plaintext in `~/.redditrank/config.json`. Neither the Python implementation nor the shell setup script explicitly creates the configuration directory with mode `0700` or the credential file with mode `0600`. Consequently, access permissions depend on the user's current umask and any pre-existing directory or file permissions. Under a permissive umask or an incorrectly permissioned existing configuration path, another local user may be able to read the API key. Printing the complete key to the terminal additionally exposes it to terminal recording, screenshots, copied logs, or surrounding automation that captures standard output. ### Attack Path 1. A user runs `setup.sh` or completes TUI onboarding, causing the API key to be written to `~/.redditrank/config.json`. 2. The process runs under a permissive umask, or the configuration direct ...[truncated 925 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create the configuration directory with owner-only permissions: ```python CONFIG_DIR.mkdir(mode=0o700, parents=True, exist_ok=True) os.chmod(CONFIG_DIR, 0o700) ``` 2. Write the credential atomically to a temporary file opened with mode `0600`, then replace the destination: ```python import os import tempfile CONFIG_DIR.mkdir(mode=0o700, parents=True, exist_ok=True) os.chmod(CONFIG_DIR, 0o700) fd, temp_path = tempfile.mkstemp(dir=CONFIG_DIR, prefix=".config-", text=True) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w") as handle: json.dump(data, handle, indent=2) handle.flush() os.fsync(handle.fileno()) os.replace(temp_path, CONFIG_FILE) os.chmod(CONFIG_FILE, 0o600) finally: if os.path.exists(temp_path): os.unlink(temp_path) ``` 3. Harden the shell implementation before writing: ```bash umask 077 install -d -m 700 "$RR_DIR" printf '%s\n' "{\"api_key\": \"$API_KEY\"}" > "$RR_CONFIG" chmod 600 "$RR_CONFIG" ``` 4. Do not display the complete API key after creation. Show only a masked value and the configuration path. 5. Prefer an operating-system credential store or keyring where available. 6. On startup, detect and warn about configuration files that are readable or writable by group or other users. ]]>
