T09 · Insecure Skill Coding Practices
Error
- Location
- helper.py:118
- Finding
- SenseAudio API keys are persisted in plaintext session-state files<![CDATA[ ## Vulnerability Details **File Location**: `helper.py:118-123` and `helper.py:343-349` **Vulnerability Type**: Plaintext credential storage **Risk Level**: High ### Vulnerable Code ```python def _save_state(session_id: str, state: dict[str, Any]) -> None: _state_file(session_id).write_text( json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8", ) ``` Caller-provided API keys are copied into the state configuration: ```python @staticmethod def _merge_after_sales_payload(cfg: dict[str, Any], payload: dict[str, Any]) -> None: for key in ("api_key", "refund_policy", "shipping_fee_by", "audio_output_path"): value = str(payload.get(key, "")).strip() if value: cfg[key] = value ``` Equivalent storage occurs for sales-mode configuration through `_merge_sales_payload`. Repository artifacts confirm that the resulting state schema contains plaintext credentials: ```json "sales": { "api_key": "k", "voice_id": "male_0018_a" } ``` This pattern appears in: - `.session_state/persist-fix.json:13` - `.session_state/persist-fix-2.json:13` - `.session_state/persist-fix-3.json:13` ### Technical Analysis The Skill accepts a SenseAudio API key through its runtime payload, stores it in the session configuration, and serializes the complete configuration directly to a JSON file. There is no credential redaction, encryption, secret-store integration, expiry mechanism, or explicit owner-only file permission enforcement. Although the API key is legitimately required to authenticate TTS requests, persisting it in session state is not necessary for the declared functionality. The key can remain in process memory or be retrieved from the documented `SENSEAUDIO_API_KEY` environment variable when a request is made. The generated files inherit permissions from the process umask. In a shared or incorrectly configured environment, other local users or processes may therefore be able to read the credential ...[truncated 1231 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove `api_key` from both `AFTER_SALES_DEFAULTS` and `SALES_DEFAULTS`. 2. Never merge credentials into objects that are passed to `_save_state`. 3. Resolve credentials only at request time from: - `SENSEAUDIO_API_KEY`; - a host-provided secret reference; or - an operating-system or cloud secret manager. 4. If per-session credentials must be supported, keep them in a short-lived in-memory credential cache and clear them when the session ends. 5. Add a serialization allowlist containing only non-sensitive fields rather than serializing the complete runtime state. 6. Create the state directory and files with owner-only permissions, such as directory mode `0700` and file mode `0600`, as defense in depth. 7. Add `.session_state/` to source-control and packaging exclusions. 8. Delete existing state artifacts and rotate any genuine credentials that may previously have been written there. 9. Add automated tests asserting that serialized state never contains fields named `api_key`, `token`, `secret`, or `password`. ]]>
