T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/prompt_cache.py:27
- Finding
- Plaintext Retention of Prompts and Child-Associated Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/prompt_cache.py:27-31` **Vulnerability Type**: Plaintext storage of potentially sensitive data **Risk Level**: Medium ### Vulnerable Code ```python await db.execute( "INSERT OR REPLACE INTO prompt_cache (prompt_hash, prompt_text, child_name, language, story_json) VALUES (?, ?, ?, ?, ?)", [h, prompt, child_name.lower(), language, json.dumps(story, ensure_ascii=False)] ) ``` ### Technical Analysis The cache persists the original prompt in the `prompt_text` column even though a SHA-256-derived value is already generated for cache lookup. It also stores the child name, language, and complete generated story as plaintext database values. Storing the raw prompt is not necessary for the documented deduplication function. The schema presented in `SKILL.md` does not disclose a `prompt_text` column, making this additional retention behavior unclear to integrators. No retention limit, automatic expiration, redaction, encryption requirement, or access-control requirement is defined. Prompts and generated stories may contain personal, confidential, or child-associated information. The hash does not protect those values because their original plaintext representations are stored alongside it. ### Attack Path 1. A user submits a prompt and child name to an application using this cache. 2. The application generates a story and invokes `set_cached()`. 3. The function writes the original prompt, normalized child name, language, and serialized story to the database. 4. An attacker or unauthorized local/database user who later obtains read access to the cache queries the `prompt_cache` table. 5. The attacker recovers the plaintext prompt, child-associated identifier, and generated content without needing to reverse the hash. This issue does not independently grant database access; exploitation requires existing local or database read access. It increases the sensitivity and consequences of any such acce ...[truncated 472 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove `prompt_text` from the cache insert unless retaining the original prompt is an explicit and documented requirement: ```python await db.execute( "INSERT OR REPLACE INTO prompt_cache " "(prompt_hash, child_name, language, story_json) VALUES (?, ?, ?, ?)", [h, child_name.lower(), language, json.dumps(story, ensure_ascii=False)] ) ``` 2. Minimize or pseudonymize child-associated identifiers where possible. Consider including a non-reversible tenant-scoped identifier in the cache key instead of a name. 3. Document every retained field and obtain appropriate user or operator consent for sensitive deployments. 4. Define expiration and deletion controls, such as a `expires_at` column and scheduled removal of stale records. 5. Restrict database file and account permissions according to least privilege. 6. Require encryption at rest for deployments that cache personal or confidential content. 7. Avoid logging raw prompts, names, or stories while handling database failures. ]]>
