T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/brave_search.py:38
- Finding
- Plaintext Persistence and Partial Logging of Brave API Credentials## Vulnerability Details **File Location**: `scripts/brave_search.py:38`, `scripts/brave_search.py:60`, `scripts/brave_search.py:100-113`, and `scripts/brave_search.py:171` **Vulnerability Type**: Plaintext sensitive-data storage and credential disclosure through logs **Risk Level**: Medium ### Vulnerable Code ```python def save_state(state): STATE_FILE.write_text(json.dumps(state, indent=2)) ``` ```python key_state = state["keys"].get(key, {}) ``` ```python state["keys"].setdefault(key, {})["last_success"] = time.time() state["keys"][key]["requests"] = state["keys"][key].get("requests", 0) + 1 save_state(state) ``` ```python if e.code in (429, 403): state["keys"].setdefault(key, {})["blocked_until"] = time.time() + 60 save_state(state) print(f"[brave-rotator] Key #{idx} rate limited, rotating...", file=sys.stderr) key, idx = pick_key(keys, state) ``` ```python print(f"[brave-rotator] Used key #{used_idx} ({used_key[:8]}...)", file=sys.stderr) ``` ### Technical Analysis The state structure uses the complete Brave API key as a dictionary key under `state["keys"]`. When `save_state()` serializes the structure, full credentials are written in plaintext to `~/.brave_key_state.json` or to the path specified through `BRAVE_KEY_STATE_FILE`. The file is created with `Path.write_text()` without explicitly enforcing restrictive permissions, validating file ownership, rejecting symbolic links, or performing an atomic secure write. Consequently, credential confidentiality depends on the process umask, existing file permissions, and the safety of the configured path. The state file may also retain credentials after they have been removed from `BRAVE_API_KEYS`, because stale entries are not purged. The documentation compounds the issue by describing the file as containing request counts and timestamps without disclosing that complete API keys are used as JSON property names. In addit ...[truncated 2154 chars]
- Remediation
- ## Remediation Suggestions 1. **Do not use complete credentials as state identifiers.** Derive a non-reversible identifier using HMAC-SHA-256 with a locally protected random secret. A plain unkeyed short hash is less desirable because it may permit correlation or guessing when key formats have limited entropy. 2. **Remove credential prefixes from logs.** Log only the numeric key index or an independently generated non-secret label. 3. **Enforce restrictive state-file permissions.** Create the file with mode `0600`, verify that it is owned by the expected user, and reject files with unsafe ownership or permissions. 4. **Use secure atomic writes.** Write to a securely created temporary file in the same directory, apply restrictive permissions, flush it, and atomically replace the state file. 5. **Defend against symbolic-link attacks.** Reject symlinks and validate the configured state path before reading or writing it. 6. **Remove stale state records.** Retain state only for identifiers corresponding to the currently configured keys. 7. **Migrate existing installations.** Delete or securely replace state files that contain plaintext credentials and rotate any keys that may have been exposed. 8. **Correct the documentation.** Clearly describe the state data, required file protections, retention behavior, and the security implications of selecting a custom `BRAVE_KEY_STATE_FILE`.
