Back to skill

Security audit

Word Jumble

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent word-puzzle generator, with some renderer hardening issues but no evidence of hidden persistence, exfiltration, or purpose-mismatched authority.

Safe to install for generating your own puzzles. Do not use it to render puzzle JSON or image filenames from untrusted sources until the HTML escaping and validator checks are fixed, and remember that image prompts are sent to the configured external image generation provider.

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

Error
Location
scripts/render_puzzle.py:42
Finding
Arbitrary HTML and JavaScript Injection During Puzzle Rendering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/render_puzzle.py:42-47`; injection sinks in `assets/puzzle-template.html:145-151` **Vulnerability Type**: Unescaped data embedded into executable HTML and an HTML attribute **Risk Level**: High ### Vulnerable Code `scripts/render_puzzle.py:42-47`: ```python def build_html(template_path: str, puzzle: dict, cartoon_filename: str) -> str: with open(template_path) as f: html = f.read() html = html.replace("__PUZZLE_JSON__", json.dumps(puzzle)) html = html.replace("__CARTOON_IMAGE__", cartoon_filename) return html ``` `assets/puzzle-template.html:145-151`: ```html <div class="layout"> <div class="puzzle-col" id="puzzle-area"></div> <div class="cartoon-col" id="cartoon-col"> <img src="__CARTOON_IMAGE__" alt="Puzzle hint illustration" /> </div> </div> <script> const puzzle = __PUZZLE_JSON__; ``` ### Technical Analysis `build_html()` performs direct string substitution into two distinct HTML parsing contexts: 1. Puzzle JSON is inserted directly into an executable `<script>` element. 2. The image filename is inserted directly into a double-quoted HTML attribute. `json.dumps()` produces valid JSON but does not make the result safe for insertion into an HTML script element. In particular, an attacker-controlled string containing `</script>` can terminate the surrounding script because HTML parsing recognizes the closing tag even when it appears inside a JavaScript string. The remainder of the value can then introduce a new script or arbitrary HTML. Likewise, `os.path.basename()` used by the caller removes directory components but does not remove quotation marks or HTML metacharacters. A crafted image filename can close the `src` attribute and inject additional attributes or elements. The application subsequently serves the generated file over localhost and directs a browser to load it. Therefore, injected active content executes in the rendering browser's security ...[truncated 2094 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not place untrusted serialized data directly into an executable script element. Prefer writing the puzzle to a separate JSON file and retrieving it as data: ```javascript const puzzle = await fetch('./puzzle.json').then(response => response.json()); ``` 2. If inline JSON is required, place it in a non-executable element: ```html <script id="puzzle-data" type="application/json">...</script> ``` Before insertion, escape characters significant to HTML parsing, including at minimum: - `<` as `\u003c` - `>` as `\u003e` - `&` as `\u0026` - U+2028 and U+2029 where relevant Parse the element's text as JSON rather than evaluating it as JavaScript. 3. Do not interpolate the image filename into markup. Create the image element with a fixed template and assign the filename through a DOM property after validating it, or apply context-specific HTML attribute escaping. 4. Restrict accepted image filenames to a conservative allowlist, such as: ```text ^[A-Za-z0-9._-]+$ ``` Reject quotes, angle brackets, control characters, URL schemes, and path separators. 5. Add a restrictive Content Security Policy that blocks inline and remote scripts. For example, move legitimate JavaScript to a static file and use: ```text Content-Security-Policy: default-src 'none'; script-src 'self'; img-src 'self'; style-src 'self' ``` This should be treated as defense in depth rather than a substitute for correct encoding. 6. Add regression tests using clue values containing `</script>`, quotes, angle brackets, ampersands, and Unicode separators, as well as image filenames containing quotes and markup. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/validate_puzzle.py:25
Finding
Invalid Circled Positions Can Bypass Validation or Crash the Validator<![CDATA[ ## Vulnerability Details **File Location**: `scripts/validate_puzzle.py:25-34` **Vulnerability Type**: Improper input validation and unsafe array indexing **Risk Level**: Medium ### Vulnerable Code ```python # 2. circled positions extract the right letters circled = word.get("circled", []) clue_letters = word.get("clue_letters", []) extracted = [unscrambled[pos - 1] for pos in circled if pos - 1 < len(unscrambled)] if extracted != clue_letters: errors.append( f"{tag}: clue_letters {clue_letters} don't match " f"letters at circled positions {circled} → {extracted}" ) all_circled.extend(extracted) ``` ### Technical Analysis The documented format requires `circled` positions to be one-indexed. The validator only checks whether: ```python pos - 1 < len(unscrambled) ``` It does not verify that: - `pos` is an integer. - `pos` is at least `1`. - `pos` is within the complete range `1 <= pos <= len(unscrambled)`. Python permits negative list indexing. Consequently, a position of `0` becomes index `-1` and extracts the final character, even though zero is invalid under the documented one-indexed format. Other negative positions can similarly read characters from the end of the word and may pass validation if `clue_letters` and the final solution are constructed to match those unintended characters. A sufficiently negative position produces an `IndexError` because the conditional only enforces an upper bound. Non-integer values can also cause an unhandled `TypeError`. Oversized positive positions are silently omitted rather than explicitly reported as invalid, which weakens the validator's guarantee that all supplied positions conform to the schema. ### Attack Path A validation-bypass path is: 1. An attacker creates puzzle JSON containing `circled: [0]`. 2. The expression `pos - 1` evaluates to `-1`. 3. Python extracts the last character of `unscrambled`. 4. The attacker sets `clue_letters` and the final solution to match tha ...[truncated 1240 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate every position before attempting extraction: ```python circled = word.get("circled", []) clue_letters = word.get("clue_letters", []) invalid_positions = [ pos for pos in circled if not isinstance(pos, int) or isinstance(pos, bool) or pos < 1 or pos > len(unscrambled) ] if invalid_positions: errors.append( f"{tag}: invalid circled positions {invalid_positions}; " f"positions must be integers from 1 to {len(unscrambled)}" ) extracted = [] else: extracted = [unscrambled[pos - 1] for pos in circled] ``` Additional hardening should include: 1. Validate that `scrambled`, `unscrambled`, `circled`, `clue_letters`, and `solution` have the expected list types. 2. Validate that letter arrays contain strings of the expected length. 3. Decide whether duplicate circled positions are valid; reject them if each position is intended to be unique. 4. Report oversized positions explicitly rather than silently omitting them. 5. Catch malformed top-level JSON structures and return controlled validation errors. 6. Add tests for zero, negative, oversized, boolean, floating-point, string, duplicate, and empty position values. ]]>
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • 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
Findings (4)

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: word-jumble
description: Generate a Word Jumble puzzle — scrambled words with circled letters that spell out a final idiom, plus a cartoon illustration hint and a printable puzzle image. Use when asked to generate a word jumble, create a daily puzzle, or schedule a jumble. Output is a puzzle JSON file, a cartoon image, and a printable 900×900px screenshot — posting/scheduling is the caller's responsibility. Note: uses the platform image_generate tool to create cartoon illustrations, which sends prompts to an external image generation API (whichever provider is configured).
---

# Word Jumble
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: word-jumble
description: Generate a Word Jumble puzzle — scrambled words with circled letters that spell out a final idiom, plus a cartoon illustration hint and a printable puzzle image. Use when asked to generate a word jumble, create a daily puzzle, or schedule a jumble. Output is a puzzle JSON file, a cartoon image, and a printable 900x900px screenshot — posting/scheduling is the caller's responsibility. Note: uses the platform image_generate tool to create cartoon illustrations, which sends prompts to an external image generation API (whichever provider is configured).
---

# Word Jumble
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The code contradicts its own comment: instead of ROT13-encoding or otherwise hiding the answer, it appends the plaintext solution directly into the DOM via `textContent`. In a puzzle skill, this defeats the integrity of the game and allows any user or embedding page with DOM access to trivially read the answer, making answer disclosure immediate rather than requiring puzzle solving.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The document sets `lang="en"`, which hard-codes English as the page language. Under the policy, forcing a specific language or locale without user opt-in or clear justification is a natural-language policy concern.

Static analysis

No suspicious patterns detected.