Back to skill

Security audit

Kids AI Magazine

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent magazine-making purpose, but it needs Review because its generated HTML workflow is unsafe for public sharing and includes misleading hardcoded output.

Review generated files before serving or sharing them, avoid including private child or family information, do not use the public tunnel for sensitive content, and treat story JSON/source fields as untrusted until the builder escapes text and validates links. Also verify or remove the hardcoded publisher attribution before distributing output.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (4)

T01 · Skill Instruction Hijacking

Note
Location
assets/template.html:356
Finding
Undisclosed Third-Party Attribution Injected into Generated Publications<![CDATA[ ## Vulnerability Details **File Location**: `assets/template.html`, lines 356-360 **Vulnerability Type**: Persistent output manipulation and misleading attribution **Risk Level**: Low ### Vulnerable Code ```html <div class="footer"> <p>🌈 小世界 · 学前亲子版</p> <p>北京智源人工智能研究院(BAAI) · 出品</p> <p>适合3-6岁 · 建议亲子共读</p> <p style="margin-top: 8px;">❤️ 爱你的不是电脑,是爸爸妈妈 ❤️</p> </div> ``` ### Technical Analysis The bundled template unconditionally states that the generated publication was produced by the Beijing Academy of Artificial Intelligence (`BAAI`). This attribution is not declared as a required feature in the Skill metadata or workflow, and the builder provides no publisher configuration that would allow a user to remove or replace it. Because `scripts/build_magazine.py` uses the template as the basis of every generated magazine, the attribution persists in the final output regardless of who created the publication. This represents stable, undisclosed manipulation of user-visible output for third-party branding. There is no evidence that this markup changes agent safety constraints, executes code, or grants system access. Its security significance is limited to integrity, provenance, and misleading attribution. ### Attack Path 1. A user invokes the Skill to generate a children's AI magazine. 2. The documented workflow directs the user to use `assets/template.html`. 3. The builder incorporates the template into the generated HTML. 4. The hardcoded footer remains in the final publication. 5. Readers are presented with an unsupported claim that BAAI produced the content. ### Impact Assessment The issue does not provide operating-system privileges or access to confidential data. Its scope is the integrity and provenance of every publication generated from the default template. Potential consequences include: - False or unauthorized organizational attribution. - Misrepresentation of authorship or endorsement. - Reputational or legal risk when the gen ...[truncated 103 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the hardcoded organizational attribution from the default template. - Add an explicit publisher placeholder, such as `{{PUBLISHER}}`, and leave it empty by default. - Require the user to opt in before adding third-party attribution or endorsement claims. - Document all default branding in `SKILL.md`. - Add a release check that rejects templates containing undeclared publisher or endorsement statements. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/build_magazine.py:12
Finding
Stored HTML and JavaScript Injection Through Unsanitized Story Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/build_magazine.py`, lines 12-56 and 87-89 **Vulnerability Type**: Stored HTML injection and cross-site scripting **Risk Level**: High ### Vulnerable Code ```python def build_story_card(story, index): """Generate HTML for a single story card.""" icon = story.get("icon", "🤖") title = story.get("title", f"故事{index}") paragraphs = story.get("paragraphs", []) dialogue = story.get("dialogue", []) parent_note = story.get("parent_note", "") source_url = story.get("source_url", "") source_name = story.get("source_name", "") audio_file = f"story{index}.mp3" html = f'''<div class="story-card" data-icon="{icon}"> <h3>{icon} 故事{_cn_num(index)}:{title}</h3> <div class="audio-box"> <span class="play-icon">🔊</span> <div style="flex:1"> <div class="audio-label">📖 听故事(点击播放)</div> <audio controls preload="none" style="width:100%; margin-top:4px;"> <source src="{audio_file}" type="audio/mpeg"> </audio> </div> </div> ''' for p in paragraphs: html += f' <p>{p}</p>\n' if dialogue: html += ' <div class="talk">\n' for d in dialogue: role = d.get("role", "child") avatar = d.get("avatar", "👶" if role == "child" else "👩") text = d.get("text", "") direction = "right" if role == "parent" else "" html += f' <div class="line {direction}">\n' html += f' <div class="avatar">{avatar}</div>\n' html += f' <div class="bubble">{text}</div>\n' html += f' </div>\n' html += ' </div>\n' if parent_note: source_html = "" if source_url and source_name: source_html = f'<br>📰 <a href="{source_url}" style="color:var(--pink)">新闻来源:{source_name}</a>' html += f''' <div class="for-parent"> <div class="label">👨‍👩‍👧 给爸爸妈妈的话:</div> {parent_note}{source_html} ...[truncated 3165 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Escape all plain-text values with `html.escape(value, quote=True)` before interpolation. - Apply escaping according to output context; HTML body text, attributes, and URLs require different validation. - Treat paragraphs and dialogue as plain text unless formatted HTML is explicitly required. - If limited formatting is required, sanitize it with a maintained HTML sanitizer and a strict allowlist, for example: - Allowed tags: `strong`, `em`, `br`. - No event-handler attributes. - No `style`, `script`, `iframe`, `object`, `embed`, or SVG content. - Parse `source_url` with `urllib.parse.urlparse` and permit only absolute `https` URLs. - Reject control characters, malformed URLs, credentials in URLs, and dangerous schemes such as `javascript:`, `data:`, and `file:`. - Add `rel="noopener noreferrer"` and, where appropriate, `target="_blank"` to external links. - Escape `issue` and `date` before template substitution. - Prefer a template engine with automatic HTML escaping rather than manual string concatenation. - Add regression tests containing script tags, event handlers, quote-breaking attributes, and dangerous URL schemes. - Deploy generated pages with a restrictive Content Security Policy as defense in depth, while not relying on CSP as the primary fix. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:32
Finding
Unpinned Third-Party Text-to-Speech Dependency<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 32 **Vulnerability Type**: Unpinned package installation and supply-chain exposure **Risk Level**: Medium ### Vulnerable Code ```bash pip3 install edge-tts ``` ### Technical Analysis The installation instruction retrieves the latest available `edge-tts` release and its transitive dependency graph from the configured Python package index. No exact version, integrity hash, lock file, or reviewed dependency set is specified. The audit did not find evidence that `edge-tts` is itself malicious. The risk arises because the effective code installed by this instruction can change after the Skill has been reviewed. A future compromised release, compromised dependency, package-index account takeover, or unsafe index configuration could introduce arbitrary installation-time or runtime behavior. The lack of version pinning also prevents reproducible builds and makes it difficult to determine which package code was executed in a given Skill run. ### Attack Path 1. A user follows the prerequisite instruction from `SKILL.md`. 2. `pip` resolves the latest package and transitive dependency versions from the configured index. 3. A package maintainer account, release, dependency, or package source is compromised, or the user is configured to use an untrusted index. 4. The compromised package is downloaded and installed. 5. Malicious package code executes during installation, import, or invocation through `python -m edge_tts`. ### Impact Assessment Python packages execute with the privileges of the user who installs or invokes them. A compromised dependency could therefore potentially: - Read or modify files accessible to the current user. - Access environment variables and user-level credentials. - Make arbitrary network requests. - Execute subprocesses. - Alter generated output. - Establish additional persistence if the current account has sufficient permissions. The exact scope depends on the priv ...[truncated 158 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin `edge-tts` to a reviewed exact version. - Maintain dependencies in a lock file or hashed requirements file. - Use hash verification, for example with `pip install --require-hashes`. - Pin and review transitive dependencies rather than only the top-level package. - Install dependencies inside an isolated virtual environment with minimal filesystem and credential access. - Use a trusted package index and disable unexpected supplemental indexes. - Periodically scan pinned dependencies for known vulnerabilities and review updates before changing versions. - Record the resolved dependency versions in generated build metadata to support reproducibility and incident response. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:59
Finding
Unauthenticated Public Exposure of the Output Directory Through a Cloudflare Tunnel<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 59-64 **Vulnerability Type**: Public file exposure without authentication or access control **Risk Level**: Medium ### Vulnerable Code ```bash # Local preview python3 -m http.server 8899 -d ./output # Public sharing (install once: brew install cloudflared) cloudflared tunnel --url http://localhost:8899 ``` ### Technical Analysis The documented sharing workflow serves the entire `./output` directory with Python's basic HTTP server and then exposes that server through a public Cloudflare tunnel. No authentication, authorization, file allowlist, or warning about sensitive files is provided. Python's `http.server` is a development server, not a hardened publication system. Every accessible file under the selected directory can be requested by anyone who obtains or discovers the tunnel URL. The tunnel command does not add application-level authentication. The command itself does not escalate local operating-system privileges. The access-control failure occurs because locally selected content is intentionally bridged to an externally reachable endpoint without safeguards. ### Attack Path 1. The output directory contains the generated magazine and one or more unintended files, such as draft content, metadata, backups, source JSON, or copied private media. 2. The user runs the documented `http.server` command. 3. The user starts the documented Cloudflare tunnel. 4. Cloudflare assigns a public endpoint that forwards requests to the local HTTP server. 5. An external party obtains the URL through sharing, logs, browser history, messaging, or accidental disclosure. 6. The external party requests unintended files under the served directory. 7. The HTTP server returns those files without requiring authentication. ### Impact Assessment The exposed scope consists of files readable by the HTTP server under `./output`. The tunnel does not by itself provide shell access or arbitrary access to files ...[truncated 598 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Clearly warn users that the tunnel creates a public endpoint. - Copy only intended publication artifacts into a new, clean staging directory before serving. - Verify the staging directory contents and reject hidden files, backups, source JSON, and unrelated artifacts. - Bind local-only previews explicitly to the loopback interface: ```bash python3 -m http.server 8899 --bind 127.0.0.1 -d ./output ``` - Do not create a public tunnel automatically or as part of the default workflow. - Require explicit user confirmation immediately before public exposure. - Use an authenticated sharing mechanism, such as Cloudflare Access or another access-controlled hosting service. - Apply expiration and revocation controls to shared endpoints. - Prefer static hosting configured to publish only an explicit file allowlist. - Document that Python's `http.server` is intended only for temporary development preview and should not be treated as a production server. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Content
html += f'      <div class="bubble">{text}</div>\n'
            html += f'    </div>\n'
        html += '  </div>\n'

    if parent_note:
        source_html = ""
        if source_url and source_name:
            source_html = f'<br>📰 <a href="{source_url}" style="color:var(--pink)">新闻来源:{source_name}</a>'
        html += f'''  <div class="for-parent">
    <div class="label">👨‍👩‍👧 给爸爸妈妈的话:</div>
    {parent_note}{source_html}
  </div>\n'''

    html += '</div>\n'
    return html

def _cn_num(n):
    nums = {1: "一", 2: "二", 3: "三", 4: "四", 5: "五", 6: "六", 7: "七", 8: "八", 9: "九", 10: "十"}
    return nums.get(n, str(n))

def main():
    parser = argparse.ArgumentParser(description="Build kids AI magazine HTML")
    parser.add_argument("--stories", required=True, help="Path to stories JSON")
    parser.add_argument("--template", required=True, help="Path to HTML template")
    parser.add_argument("--output", default="./outp
Confidence
95% confidence
Finding
The script interpolates untrusted story fields such as text, title, parent_note, source_name, source_url, and icon directly into HTML attributes and element bodies without escaping or URL validation. If the stories JSON comes from an LLM, external feed, or user input, an attacker can inject HTML/JavaScript into the generated magazine, leading to stored XSS when the HTML is opened in a browser; in a child-focused skill, this is especially concerning because malicious content could target parents or children viewing the page.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill instructs the agent to read templates/references, write output files, and run shell commands, but it does not declare any tool scope or permission boundaries. This can lead to overbroad tool access at runtime, making accidental file access, file overwrite, or command execution more likely than necessary for the task.

Vague Triggers

Medium
Confidence
85% confidence
Finding
The trigger list is broad and includes generic terms like kids magazine, children AI story, and several Chinese preschool/parent-reading phrases, which may cause the skill to activate in contexts the user did not intend. Unintended activation can route unrelated requests into shell/file-writing workflows and produce surprising or privacy-impacting behavior.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The instruction requires "Pure Chinese narration text, no English words," forcing a specific language choice as part of the skill behavior. The file does not indicate that this is optional or user-selectable, which is a language policy concern.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The public sharing step exposes a locally served site through a tunnel without any warning about making generated content externally reachable. If the magazine contains sensitive prompts, source material, local paths, or child-related information, the user may unintentionally publish it to the internet.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
This JSON content presents all story titles, narration, dialogue, and parent notes exclusively in Chinese. Under the policy, forcing a specific language without user opt-in or documented regional justification is a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The script embeds Chinese text directly into generated HTML, including default titles and UI labels such as "故事", "听故事", and parent-facing text. This enforces a specific language/locale in the skill output without offering user opt-in or documenting that the tool is intentionally limited to a Chinese-only audience.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The file description and usage example hard-code a Chinese TTS voice (zh-CN-XiaoxiaoNeural), and the same locale-specific default is used in the code. This can violate language/locale policy because the skill imposes a specific language/locale unless the user explicitly overrides it, rather than offering a neutral default or opt-in choice.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--text", text,
        "--write-media", output_path
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"Error generating {output_path}: {result.stderr}")
        return False
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
90% confidence
Finding
The document declares `lang="zh-CN"`, which hard-codes a specific language/locale for all users. Under the policy, locale constraints should either be optional, user-selectable, or clearly justified as region-specific.

Static analysis

No suspicious patterns detected.