Back to skill

Security audit

rpg-travel: Game Pilgrimage · RPG 旅行:游戏圣地巡礼

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent travel-planning purpose, but its generator can write files outside the intended folder and can persist unsafe HTML from travel data into generated maps.

Review before installing. Use only with travel data you are comfortable sending through FlyAI, confirm any remembered personal details before searches, and avoid opening generated HTML from untrusted or externally influenced data until filename sanitization, HTML escaping, and URL allowlisting are fixed.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_map.py:51
Finding
Path Traversal Through Unsanitized Output Filenames<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_map.py:51-64` **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code ```python out_dir = Path(args.output_dir) if args.output_dir else OUTPUT_DIR out_dir.mkdir(parents=True, exist_ok=True) if not args.html_only: taskbook = generate_text_taskbook(data) taskbook_file = ( out_dir / f"{data.game_name}-任务书-{data.date_range or '待定'}.txt" ) taskbook_file.write_text(taskbook, encoding="utf-8") print(f"✅ 任务书已生成:{taskbook_file}") if not args.text_only: html = generate_html(data) html_file = ( out_dir / f"{data.game_name}-冒险地图-{data.date_range or '待定'}.html" ) html_file.write_text(html, encoding="utf-8") ``` ### Technical Analysis The generated filenames include `data.game_name` and `data.date_range`, both of which originate from supplied JSON. `TripData.validate()` checks whether the game name is present but does not reject path separators, `..` traversal components, absolute paths, control characters, or platform-specific path syntax. `pathlib.Path` does not automatically constrain the resulting path to `out_dir`. A value containing traversal components can therefore cause `write_text()` to resolve outside the intended output directory. This contradicts the documented claim that generated files are restricted to the current working directory. No final resolved-path containment check is performed before writing. ### Attack Path 1. An attacker influences the user prompt, collected trip data, or JSON supplied through `--stdin` or `--data`. 2. The attacker supplies a crafted `game_name` or `date_range` containing traversal components, such as `../../target`. 3. `TripData` accepts the value because validation only checks that `game_name` is nonempty. 4. `generate_map.py` directly incorporates the value into the destination path. 5. `write_tex ...[truncated 914 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Convert all user-controlled filename components to strict safe slugs containing only an allowlisted character set. 2. Explicitly reject `/`, `\`, `..`, null bytes, control characters, drive prefixes, and absolute-path syntax. 3. Resolve the final destination and enforce containment beneath the output directory: ```python import re from pathlib import Path def safe_filename_component(value: str) -> str: value = re.sub(r"[^A-Za-z0-9._-]+", "_", value).strip("._") if not value: raise ValueError("Invalid filename component") return value[:100] base = out_dir.resolve() game = safe_filename_component(data.game_name) date = safe_filename_component(data.date_range or "undated") destination = (base / f"{game}-adventure-map-{date}.html").resolve() if base not in destination.parents: raise ValueError("Output path escapes the configured directory") ``` 4. Consider exclusive file creation or explicit overwrite confirmation to prevent accidental replacement of existing files. 5. Add tests for Unix traversal, Windows separators and drive paths, absolute paths, Unicode separators, empty sanitized values, and excessively long names. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/node_builder.py:8
Finding
Persistent HTML and JavaScript Injection in Generated Adventure Maps<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/node_builder.py:8-65` - `scripts/node_builder.py:77-123` - `scripts/html_generator.py:41-89` - `scripts/html_generator.py:183-228` - `scripts/html_generator.py:401-420` **Vulnerability Type**: Stored HTML injection and local cross-site scripting **Risk Level**: High **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code User-controlled narrative fields are directly inserted into HTML: ```python def _plot_section(event: dict) -> str: plot_summary = event.get("plot_summary", "") dialogues = event.get("dialogues", []) related_locs = event.get("related_locations", []) if not plot_summary and not dialogues and not related_locs: return "" html_parts = [ ' <div class="node-plot">', ' <div class="node-plot-title">📖 剧情概要</div>', ] if plot_summary: html_parts.append( f' <div class="node-plot-summary">{plot_summary}</div>' ) for d in dialogues: speaker = d.get("speaker", "") text = d.get("text", "") html_parts.append(f' <div class="node-dialogue">') html_parts.append( f' <div class="node-dialogue-speaker">🗣️ {speaker}</div>' ) html_parts.append( f' <div class="node-dialogue-text">"{text}"</div>' ) html_parts.append(f" </div>") if related_locs: loc_tags = "".join( f'<span class="node-related-loc">📍 {loc}</span>' for loc in related_locs ) html_parts.append( f' <div class="node-related-locations">{loc_tags}</div>' ) html_parts.append(" </div>") return "\n".join(html_parts) ``` Image URLs and other fields are inserted into HTML attributes without escaping: ```python pic = event.get("pic_url", event.get("picUrl", "")) game_pic = event.get("game_pic_url", ...[truncated 4156 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every value inserted into HTML text or attribute contexts: ```python from html import escape safe_text = escape(str(value), quote=True) ``` 2. Do not place untrusted values inside inline event handlers. Remove inline `onclick` attributes and register handlers from static JavaScript using `addEventListener`. 3. Store URLs in validated `data-*` attributes or bind them programmatically rather than embedding them inside JavaScript source. 4. Construct dynamic page elements with DOM APIs and assign untrusted strings through `textContent`. 5. When embedding JSON in a script element, escape at least `<`, `>`, `&`, Unicode line separators, and any `</script>` sequence. Prefer a non-executable JSON element: ```html <script id="locations-data" type="application/json">...</script> ``` The serialized JSON must still replace `<` with `\u003c` before insertion. 6. Validate and normalize all URLs independently of HTML escaping. 7. Add a restrictive Content Security Policy. Ideally, disallow inline scripts and limit image and navigation destinations to approved HTTPS hosts. 8. Add regression tests covering quote characters, angle brackets, `</script>`, event-handler payloads, malformed URLs, and Unicode encoding edge cases. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/models.py:294
Finding
Unvalidated External URLs Permit Phishing, Unsafe Schemes, and Undisclosed Tracking Hosts<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/models.py:294-309` - `scripts/html_generator.py:183-228` - `scripts/node_builder.py:46-65` - `scripts/node_builder.py:104-113` **Vulnerability Type**: Improper URL validation and unsafe external resource embedding **Risk Level**: Medium **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code Supplied purchase URLs are accepted without scheme or hostname validation: ```python def build_fliggy_link(item_type: str, item: dict) -> str: item_id = item.get("itemId") or item.get("item_id") if item_id: return f"https://market.fliggy.com/item.htm?itemId={item_id}" jump_url = ( item.get("jumpUrl") or item.get("jump_url") or item.get("detailUrl") or item.get("detail_url") ) if jump_url: return jump_url ``` The returned value is rendered as a trusted purchase link: ```python items.append(f""" <div class="buy-item"> <span class="buy-item-label">✈️ 去程航班</span> <a href="{link}" target="_blank" rel="noopener" class="buy-btn">飞猪购买</a> <button class="copy-btn" onclick="copyLink('{link}', this)">📋 复制</button> <span class="buy-reason">💡 {reason}</span> </div>""") ``` External image URLs are likewise embedded without host restrictions: ```python pic = event.get("pic_url", event.get("picUrl", "")) game_pic = event.get("game_pic_url", event.get("gamePicUrl", "")) game_img_src = game_pic if game_pic else pic real_img_src = pic game_img = ( f' <div class="node-img" onclick="openLightbox(this)">\n' f' <img src="{game_img_src}" alt="游戏中" onerror="this.style.display=\'none\'" />\n' f' <div class="node-img-label">🎮 游戏中</div>\n' f" </div>" ) ``` ### Technical Analysis The project documentation describes purchase links as Fliggy links and lists a limited set of third-party image hosts. The implementation does not enforce ei ...[truncated 2294 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse URLs with `urllib.parse.urlsplit` and reject malformed values. 2. Permit only the `https` scheme. 3. Enforce an exact hostname allowlist for purchase links, such as approved Fliggy domains. 4. Maintain a separate, documented allowlist for image hosts. Do not treat purchase-link and image-source policies as interchangeable. 5. Reject URL credentials, control characters, ambiguous hostnames, nonstandard ports unless required, and schemes including `javascript`, `data`, `file`, and `vbscript`. 6. Encode item IDs and query parameters with `urllib.parse.urlencode` rather than direct string interpolation. 7. Escape validated URLs for their final HTML context. 8. For privacy-sensitive operation, download images locally after enforcing response size, MIME type, redirect, and timeout limits. Generated HTML should then reference local copies. 9. Warn the user before opening or navigating to any non-allowlisted external resource. 10. Add tests for deceptive subdomains, user-info hostname tricks, mixed-case schemes, percent encoding, protocol-relative URLs, redirects, and internationalized domain names. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (40)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
A second description-behavior mismatch is the undeclared use of public image or game endpoints for background and screenshot retrieval. Even if the destinations are public, hidden outbound access changes the skill's trust boundary and can leak user-selected game/travel context or violate deployment expectations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding
A second description-behavior mismatch is the undeclared use of public image or game endpoints for background and screenshot retrieval. Even if the destinations are public, hidden outbound access changes the skill's trust boundary and can leak user-selected game/travel context or violate deployment expectations.

Vague Triggers

High
Confidence
97% confidence
Finding
The activation rule is overly broad: a bare game name or a vague travel-intent phrase can trigger the skill. In context, this is risky because the skill can then start collecting personal itinerary information, consult Memory, and query external services with configured credentials even when the user may have only been chatting generally about a game.

Hidden Instructions

High
Category
Prompt Injection
Content
</head>
<body>

  <!-- 全屏背景图 -->
  <div class="bg-image" style="background-image: url('[BG_IMAGE_URL]'); filter: brightness(0.6) saturate(0.8);"></div>

  <!-- 内容层 -->
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
<!-- BUDGET_WARNING -->
  </div>

  <!-- 底部预算总览 -->
  <div class="budget-section">
    <div class="budget-card">
      <div class="budget-title">💰 冒险经费总览</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
<!-- Toast -->
  <div class="toast" id="toast"></div>

  <!-- Lightbox -->
  <div class="lightbox-overlay" id="lightbox" onclick="closeLightbox()">
    <button class="lightbox-close" onclick="closeLightbox()">✕</button>
    <img id="lightbox-img" src="" alt="放大图片" />
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
1. 读取本文件的完整 HTML 模板
2. 根据用户选择的风格,替换 `:root` 中的 CSS 变量
3. 替换所有 `[占位符]`
4. 为每个行程节点生成 HTML 卡片,替换 `<!-- NODES_START -->` 和 `<!-- NODES_END -->` 之间的内容
5. 替换 `[LORE_TEXT]`、`[GAME_IMAGE_URL]`、`[REALITY_IMAGE_URL]` 等背景故事内容
6. 替换预算区域的 `[FLIGHT_TOTAL]`、`[HOTEL_TOTAL]` 等
7. 输出完整 HTML 文件
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
    </div>

    <!-- 剧情概要 + 台词 -->
    <div class="node-plot">
      <div class="node-plot-title">📖 剧情概要</div>
      <div class="node-plot-summary">[剧情概要:描述游戏中与此地相关的关键剧情段落,2-3句话]</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
    </div>

    <!-- 剧情概要 + 台词 -->
    <div class="node-plot">
      <div class="node-plot-title">📖 剧情概要</div>
      <div class="node-plot-summary">[剧情概要:描述游戏中与此地相关的关键剧情段落,2-3句话]</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
    <div class="node-time">[入住日期] · [地址]</div>

    <!-- 剧情概要 + 台词 -->
    <div class="node-plot">
      <div class="node-plot-title">📖 剧情概要</div>
      <div class="node-plot-summary">[剧情概要:描述游戏中与此存档点/休息地相关的关键剧情]</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
</div>
    <div class="node-time">[入住日期] · [地址]</div>

    <!-- 剧情概要 + 台词 -->
    <div class="node-plot">
      <div class="node-plot-title">📖 剧情概要</div>
      <div class="node-plot-summary">[剧情概要:描述游戏中与此存档点/休息地相关的关键剧情]</div>
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger examples are broad enough to match ordinary travel or gaming conversation, which can cause the skill to activate outside clear user intent. In an agent environment with tool access and external data queries, accidental invocation can lead to unnecessary network calls, confusing behavior, or unintended processing of user travel preferences.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill directs the agent to use network access, read reference files, and write output files, but it does not declare an explicit tool scope or allowed-tools boundary. Without least-privilege constraints, an agent may invoke broader capabilities than users expect, increasing the chance of unintended file access, outbound requests, or data exposure during execution.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill asks for departure city, budget, and travel preferences and then instructs use of external travel-query tooling, but it does not warn users that this data may be sent to third-party services using configured credentials. Missing disclosure creates privacy and compliance risk because users cannot meaningfully consent to external processing of their itinerary data.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger rules allow activation from very broad inputs like a game name or generic travel-related phrases. Over-broad invocation can cause the skill to capture unrelated conversations, prompt for personal itinerary details, or initiate external lookups when the user did not clearly intend to use this skill.

Vague Triggers

Medium
Confidence
93% confidence
Finding
Several descriptive triggers are ambiguous enough to match ordinary conversation and are not tightly scoped to this skill. While not directly enabling code execution, this increases accidental activation risk and can lead to unnecessary collection or transmission of user context.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill relies on configured FlyAI credentials and real flight/hotel/POI queries, but it does not clearly tell the user that their departure city and itinerary inputs will be sent to external services. This undermines informed consent and creates privacy risk around travel-related personal data.

Natural-Language Policy Violations

Medium
Confidence
80% confidence
Finding
该技能说明、交互问题、示例输出和流程均默认并强制为中文,没有说明可根据用户偏好切换语言。若组织要求避免未经用户选择而固定语言,这种设计可能构成自然语言层面的 locale 策略违规。

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger phrase list contains broad conversational phrases without exclusion rules or contextual checks. This increases accidental invocation risk and can cause unintended collection of travel details or external lookups tied to the user's interests and location.

Ssd 3

Medium
Confidence
97% confidence
Finding
The instruction to reuse previously stored user information from memory without fresh confirmation is a direct data-handling flaw. In this skill, the risk is amplified because the reused data may be combined with external travel-service lookups, turning stale or previously disclosed personal information into a new outbound disclosure.

Natural-Language Policy Violations

Medium
Confidence
79% confidence
Finding
The title and instructions are entirely in Chinese and line L03 uses absolute language ('都必须') to require inclusion of Fliggy purchase links, but the file provides no indication that Chinese is optional or limited to a China-specific audience. Under the policy, forcing a specific language or locale without user opt-in can be a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
该 markdown 文档全文以中文编写,并通过中文标题、参数说明和注意事项默认要求使用中文语境,但未说明这是区域限定文档,也未提供其他语言或用户选择语言的说明。根据规则,若技能强制特定语言而没有用户 opt-in 或明确、合理的地域性说明,属于自然语言政策风险。

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The entire reference is written in Chinese and provides Chinese-specific search terms such as '取景地' and '圣地巡礼' without indicating that language selection is optional. Under the policy, forcing a specific language or locale without user opt-in is a natural-language policy violation unless clearly justified as region-specific.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The title and all user-facing template text are in Chinese, which implies the skill may force a specific language/locale. The policy requires either offering language choice or clearly documenting a justified locale restriction, neither of which appears in this file.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The HTML template hard-codes `lang="zh-CN"`, which imposes a specific language/locale on generated output. The surrounding markdown also presents the skill entirely in Chinese and does not indicate that this locale is optional or limited to a China-specific use case.

Static analysis

No suspicious patterns detected.