Back to skill

Security audit

Prompt Cache

Security checks for vulnerabilities and agentic risk

Overview

The skill is a plausible local prompt cache, but it under-discloses plaintext retention of prompts, child names, and generated stories in a database.

Review this skill before installing in applications that handle private prompts, children's names, or confidential generated content. Use it only with a clearly scoped database, documented retained fields, access controls, encryption or equivalent protection where appropriate, and retention/deletion limits. The prompt_text field should either be removed or explicitly documented with user/operator consent.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

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. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/prompt_cache.py:28
Finding
Silent Suppression of All Cache Write Failures<![CDATA[ ## Vulnerability Details **File Location**: `scripts/prompt_cache.py:28-34` **Vulnerability Type**: Overly broad exception handling and failure suppression **Risk Level**: Low ### Vulnerable Code ```python try: 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)] ) except Exception: pass # Cache miss is fine, don't break the flow ``` ### Technical Analysis The function catches the base `Exception` class and discards every error without reporting it to the caller or emitting a diagnostic signal. This suppresses expected cache availability failures as well as unexpected conditions such as schema incompatibility, permission failures, serialization errors, database corruption, and storage exhaustion. The behavior is particularly relevant because the schema documented in `SKILL.md` does not include the `prompt_text` column required by this insert. An implementation created directly from the documented schema may therefore reject every cache write while the application receives no indication that caching is nonfunctional. Fail-open behavior can be appropriate for an optional cache, but complete suppression prevents monitoring and makes persistent failures indistinguishable from successful writes. ### Attack Path 1. The cache database becomes unwritable because of a schema mismatch, permission change, storage exhaustion, corruption, or deliberate disruption by an attacker who already has relevant database or filesystem access. 2. `db.execute()` raises an exception during each attempted cache write. 3. The broad exception handler silently discards the error. 4. Subsequent requests continue to miss the cache and invoke the underlying paid or resource-intensive generation service. 5. Operators receive no signal from this function that writes are failing, allow ...[truncated 744 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Align the documented database schema with the actual query before deployment. 2. Catch only expected database exceptions rather than the base `Exception` class. 3. Record a sanitized warning, metric, or tracing event without including raw prompts, child names, or story content. 4. Return an explicit success status so callers can monitor cache health while preserving fail-open application behavior. 5. Escalate unexpected failures instead of suppressing them. For example: ```python async def set_cached( prompt: str, child_name: str, language: str, story: dict, ) -> bool: h = hash_prompt(prompt, child_name, language) try: 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), ], ) return True except ExpectedDatabaseError as exc: logger.warning("Cache write failed: %s", type(exc).__name__) return False ``` Use the concrete exception type supplied by the database adapter in place of `ExpectedDatabaseError`. ]]>
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code does implement the core idea of normalized prompt hashing plus database-backed cache get/set behavior, so it is related to the declared purpose. However, the description overstates the capability and generality. This chunk only caches `story_json` records and is tailored to `prompt`, `child_name`, and `language`; it does not show generic caching for both LLM and TTS calls, nor any audio/result replay handling. It also does not implement fuzzy matching, despite the module docstring mentioning optional fuzzy matching. The claim about working with any database backend is not verifiable from this chunk because it depends on an abstract `database` module. Overall, the primary purpose is adjacent but materially narrower and more specialized than declared.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill encourages caching raw prompts and identifiers such as child_name and language, but does not warn that this can store sensitive user content and personal data at rest. In context, caching LLM/TTS inputs can retain secrets, private conversations, or children's identifiers longer than users expect, increasing privacy and compliance risk if the database is accessed, logged, backed up, or shared.

Intent-Code Divergence

Low
Confidence
38% confidence
Finding
The documentation explicitly states a specific normalization process before hashing, but the skill file provides no code here and points to a very small implementation file. This raises a possible intent-code divergence if the actual implementation hashes raw inputs or performs different normalization than documented.

Static analysis

No suspicious patterns detected.