Back to skill

Security audit

Story Master/通过图谱和管道方式创建剧本

Security checks for vulnerabilities and agentic risk

Overview

This story-generation skill is mostly purpose-aligned, but its local graph storage can read, write, or delete JSON files outside its intended data folder if given a crafted pipeline ID.

Review this skill before installing if it will run with access to important local JSON files. Its persistence features fit the story-pipeline purpose, but the graph manager should validate pipeline IDs, enforce storage-directory containment, and add explicit confirmation for deletion before it is trusted in a broader workspace.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
  • 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/graph_manager.py:41
Finding

Path Traversal in Local Graph Storage

Content
View full analysis

Vulnerability Details

File Location: scripts/graph_manager.py:41-43, 47-60, 70-81, 270-284
Vulnerability Type: Path traversal leading to unauthorized file access, modification, or deletion
Risk Level: High

Vulnerable Code

python
def _get_graph_path(self, pipeline_id: str) -> str:
    """获取图谱文件路径"""
    return os.path.join(self.storage_dir, f"{pipeline_id}.json")

The resulting path is used directly by file operations:

python
def _load_graph(self, pipeline_id: str) -> Dict:
    """加载图谱数据"""
    # 先检查缓存
    if pipeline_id in self.cache:
        return self.cache[pipeline_id]
    
    # 从文件加载
    graph_path = self._get_graph_path(pipeline_id)
    if os.path.exists(graph_path):
        try:
            with open(graph_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
                self.cache[pipeline_id] = data
                return data
        except (json.JSONDecodeError, Exception):
            pass
python
def _save_graph(self, pipeline_id: str, data: Dict):
    """保存图谱数据"""
    graph_path = self._get_graph_path(pipeline_id)
    
    # 更新缓存
    self.cache[pipeline_id] = data
    
    # 保存到文件
    with open(graph_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
python
def delete_graph(self, pipeline_id: str) -> Dict:
    """
    删除图谱
    
    Args:
        pipeline_id: 管道ID
        
    Returns:
        删除结果
    """
    # 清除缓存
    if pipeline_id in self.cache:
        del self.cache[pipeline_id]
    
    # 删除文件
    graph_path = self._get_graph_path(pipeline_id)
    if os.path.exists(graph_path):
        os.remove(graph_path)
        return {"success": True, "message": "图谱已删除"}
    
    return {"success": False, "error": "图谱不存在"}

Technical Analysis

pipeline_id is concatenated into a filesystem path without vali ...[truncated 1561 chars]

Remediation
View remediation

Remediation Suggestions

  • Restrict pipeline_id to an explicit allowlist pattern, such as ^[A-Za-z0-9_-]+$.
  • Reject absolute paths, path separators, . segments, and .. segments.
  • Resolve both the storage directory and destination path to canonical paths before every operation.
  • Verify that the resolved destination is a child of the resolved storage directory using Path.is_relative_to() or an equivalent safe containment test.
  • Use an internal server-generated identifier rather than accepting arbitrary identifiers at the storage boundary.
  • Open files using safer flags where appropriate, and consider preventing symbolic-link traversal.
  • Apply the same validation to read, write, cache, and delete operations.

Example hardening:

python
import re
from pathlib import Path

def _get_graph_path(self, pipeline_id: str) -> str:
    if not re.fullmatch(r"[A-Za-z0-9_-]+", pipeline_id):
        raise ValueError("Invalid pipeline identifier")

    base = Path(self.storage_dir).resolve()
    target = (base / f"{pipeline_id}.json").resolve()

    if not target.is_relative_to(base):
        raise ValueError("Graph path escapes the storage directory")

    return str(target)

T01 · Skill Instruction Hijacking

Warning
Location
scripts/ai_reviewer.py:106
Finding

Indirect Prompt Injection Through Story and Graph Content

Content
View full analysis

Vulnerability Details

File Location: scripts/ai_reviewer.py:106-124; scripts/episode_generator.py:180-188, 264-272; scripts/pipeline.py:214-225
Vulnerability Type: Untrusted content embedded directly into executable LLM prompts
Risk Level: Medium

Vulnerable Code

The review prompt directly interpolates submitted episode content, previous content, and graph data:

python
prompt = f"""# 剧集质量审核任务

## 审核对象
- 剧集序号:第 {episode_number} 集
- 内容:{episode_content}

"""
        
if prev_content:
    prompt += f"""## 上一集内容
{prev_content}

"""
        
if graph_data:
    prompt += f"""## 图谱数据
{json.dumps(graph_data, ensure_ascii=False, indent=2)}

"""

Generation prompts use persisted content in the same way:

python
prompt = f"""# 第{episode_number}集生成任务

## 上下文信息

### 上一集内容
{prev_content}

### 图谱数据
{json.dumps(graph_data, ensure_ascii=False, indent=2)}

{hooks_info}

The pipeline forwards the submitted content into that prompt:

python
# 获取上一集内容(如果有)
prev_content = None
if episode > 1:
    graph_result = self.graph_manager.query_graph(pipeline_id, episode - 1)
    prev_content = graph_result.get("content", "")

# 创建审核提示词
review_prompt = self.ai_reviewer.create_review_prompt(
    episode_content=content,
    prev_content=prev_content,
    graph_data=None,
    episode_number=episode
)

Technical Analysis

Story content and graph records are treated as both data and instructions because they are interpolated into the same plain-text prompt that defines the LLM task. The prompts do not establish a strong trust boundary or explicitly instruct the model to disregard commands contained inside the supplied content.

An attacker can place instruction-like text in an episode, previous episode, hook, or graph field. When that data is later used to construct a review or generation prompt, the LLM may follow those embedded instructions in ...[truncated 1349 chars]

Remediation
View remediation

Remediation Suggestions

  • Clearly label episode and graph content as untrusted data.
  • Add explicit instructions that commands, policies, or output requests found inside supplied story content must never be followed.
  • Place untrusted data in a separate message or structured field instead of concatenating it into the primary instruction text.
  • Use schema-constrained output for review results.
  • Validate all review output locally rather than relying on the model's Boolean decision.
  • Minimize the graph fields supplied to the model and exclude content that is not required for the task.
  • Consider detecting common instruction-injection patterns, while treating filtering only as defense in depth.
  • Preserve provenance for persisted fields so that untrusted content does not silently become trusted context in later prompts.

A hardened prompt should state that the delimited content is evidence to evaluate, not a source of instructions, and that any directives inside it must be ignored.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ai_reviewer.py:174
Finding

AI Review Quality Gate Trusts an Attacker-Controlled Passed Flag

Content
View full analysis

Vulnerability Details

File Location: scripts/ai_reviewer.py:55, 174-190; scripts/pipeline.py:257-280
Vulnerability Type: Workflow authorization bypass caused by missing server-side validation
Risk Level: Medium

Vulnerable Code

A passing threshold is declared:

python
# 通过阈值
PASS_THRESHOLD = 7.0

However, the parser independently trusts the supplied passed and score values:

python
try:
    # 尝试提取JSON
    json_str = response
    if "```json" in response:
        json_str = response.split("```json")[1].split("```")[0]
    elif "```" in response:
        json_str = response.split("```")[1].split("```")[0]
    
    data = json.loads(json_str.strip())
    
    return ReviewResult(
        passed=data.get("passed", False),
        score=data.get("score", 0),
        checks=data.get("checks", {}),
        suggestions=data.get("suggestions", []),
        summary=data.get("summary", "")
    )

The pipeline advances solely according to the parsed Boolean:

python
# 解析审核结果
review = self.ai_reviewer.parse_review_result(ai_review_result)

if not review.passed:
    # AI审核未通过,可以无限重试
    state.retry_count += 1
    state.status = "ai_retry_needed"
    self._save_states()
    
    return {
        "success": False,
        "passed": False,
        "retry_count": state.retry_count,
        "review": {
            "score": review.score,
            "checks": review.checks,
            "suggestions": review.suggestions
        },
        "message": f"AI审核未通过(得分{review.score}),需要重新生成(无次数限制)"
    }

# AI审核通过,进入等待人工确认状态
state.status = "waiting_user_confirm"

Technical Analysis

PASS_THRESHOLD is never applied when determining whether a review passes. The code trusts the passed property from the caller-supplied JSON independently of score. It also does not validate that the score is numeric, lies between zero and te ...[truncated 1445 chars]

Remediation
View remediation

Remediation Suggestions

  • Ignore the model-supplied passed field and compute the decision locally.
  • Require score to be an integer or floating-point value in the inclusive range from zero to ten.
  • Require all expected review dimensions and validate each dimension's score.
  • Recalculate the weighted aggregate score locally from the individual checks.
  • Reject review objects whose aggregate score conflicts with their individual scores.
  • Use a strict JSON schema and reject unknown, missing, or incorrectly typed fields.
  • Enforce the expected pipeline state and episode number before accepting a review.
  • Where identity boundaries exist, ensure only the trusted review component can submit review results.

Example:

python
score = data.get("score")
if not isinstance(score, (int, float)) or isinstance(score, bool):
    raise ValueError("Review score must be numeric")
if not 0 <= score <= 10:
    raise ValueError("Review score is outside the valid range")

passed = score >= self.PASS_THRESHOLD

return ReviewResult(
    passed=passed,
    score=float(score),
    checks=validated_checks,
    suggestions=validated_suggestions,
    summary=validated_summary
)
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

声明描述的是一个较完整的“剧情生成管道流”系统,包含连续多剧集生成、图谱管理、AI质检加人工确认双控,以及角色/场景/钩子关系的自动管理。实际代码主要是一个提示词生成模块:根据主题、上一集内容、图谱数据和开放钩子拼接出给模型使用的生成提示;另外提供一个非常简化的内容解析函数。代码没有看到任何质检逻辑、人工审核接口、工作流编排、状态持久化、图谱更新维护或关系管理算法。因此,代码只覆盖了“剧集生成模块”的一部分,且明显少于声明中的关键能力,属于描述与实际行为不符。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Yes, this is a mismatch. The description claims a substantial narrative-generation pipeline with multiple concrete capabilities, but the provided code chunk contains no executable logic implementing any of them. This is a material difference in primary purpose and behavior: the actual code does nothing, while the declared description describes a feature-rich system. There are no undeclared capabilities present, but the declared functionality is unsupported by the code shown.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

声明描述的是一个较完整的“剧情生成管道流技能”,核心能力应包括生成流程、跨剧集连续生成、质检和人工审核控制,以及图谱关系自动管理。但给出的代码块只是 graph_manager.py,一个图谱存储/查询组件。它使用本地 JSON 文件保存 episodes、characters、hooks、scenes,并提供查询、删除、缓存等管理功能。这与“图谱管理”部分基本一致,也对人物、场景、钩子做了部分记录和状态维护;但它没有实现任何内容生成逻辑,没有 AI 质检,也没有人工确认工作流。另一个细节是虽然返回中包含 relationships 字段,代码并未真正维护关系数据,因此“自动管理关联关系”也只得到有限支持。综上,代码行为仅覆盖声明中的一个子模块,不能准确代表完整声明,属于描述与实际行为不匹配。

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/ai_reviewer.py (reported line 162)May include surrounding context.

python
[描述本集的情绪起伏]
```
"""
        return prompt
    
    def generate_next_episode(
        self,

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/episode_generator.py (reported line 148)May include surrounding context.

python
[描述本集的情绪起伏]
```
"""
        return prompt
    
    def generate_next_episode(
        self,

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/episode_generator.py (reported line 234)May include surrounding context.

python
[描述本集的情绪起伏]
```
"""
        return prompt
    
    def generate_next_episode(
        self,

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/episode_generator.py (reported line 322)May include surrounding context.

python
[描述本集的情绪起伏]
```
"""
        return prompt
    
    def generate_next_episode(
        self,

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
78% confidence
Finding

The skill declares file read/write capabilities implicitly through its documented state persistence (data/pipeline_state.json) but does not declare any tool scope or permissions boundaries. This is dangerous because an agent or host may grant broader filesystem access than users expect, increasing the risk of unintended file access or state tampering if the implementation follows the documented behavior.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The title and description present the skill entirely in Chinese and describe its functionality as if Chinese is the required operating language, with no indication that users may choose another language. Under the policy, forcing a specific language without opt-in is a natural-language locale violation unless clearly justified as region-specific.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code defines the module description, review dimensions, prompts, and formatted output entirely in Chinese, which imposes a specific language on users. The policy for this audit flags language/locale constraints when they are forced without explicit user opt-in or documented justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This code file contains natural-language docstrings and generated prompt content in Chinese, which effectively forces a specific language/locale for users of the skill. The policy allows language constraints only when the skill offers opt-in choice or clearly documents a justified region-specific limitation, neither of which is present here.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The manifest describes a story-generation pipeline with graph management, continuity handling, and review controls, but this file also exposes a destructive delete operation that removes stored graph files. Persistent deletion is not mentioned in the skill description and is not an obvious requirement of generating episodes or managing associations, making this capability broader than the stated purpose.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The delete_graph method removes a local JSON file via os.remove, which is a destructive operation. Although the docstring says '删除图谱', there is no confirmation prompt, explicit warning comment, or user-facing disclosure at the point of deletion about irreversible data loss.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code file contains natural-language module documentation and return messages only in Chinese, with no indication that users may choose another language. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The markdown template is entirely written in Chinese and presents fixed Chinese section headings and content, which implies the skill output is constrained to a specific language. The policy allows locale constraints only when the user is given a choice or the constraint is clearly documented and justified, neither of which appears here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

This JSON file contains natural-language fields such as theme, style, comments, summary, and generated episode content entirely in Chinese. Under the policy rule, forcing a specific language without any visible user opt-in or documented locale justification can be a language/locale policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

Natural-language strings and documentation throughout the file are written exclusively in Chinese, with no indication that the user can opt into another language. Under the stated policy, forcing a specific language without user choice can be a locale-policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.