Back to skill

Security audit

Story generation pipeline skill

Security checks for vulnerabilities and agentic risk

Overview

This story-writing skill is not malicious, but it needs review because its local graph storage can be tricked into accessing JSON files outside its intended folder.

Review before installing in any shared or automated environment. The main issue to fix is strict validation and path containment for pipeline_id before any graph read, write, or delete. Also consider deriving AI review pass/fail locally from validated scores and documenting that output and prompts are Chinese-first. Avoid exposing GraphManager methods to untrusted callers or granting broad filesystem access until those issues are fixed.

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 Graph File Operations

Content
View full analysis

Vulnerability Details

File Location: scripts/graph_manager.py:41-43, with vulnerable file operations at scripts/graph_manager.py:52-56, scripts/graph_manager.py:74-80, and scripts/graph_manager.py:280-283
Vulnerability Type: Path traversal allowing files outside the graph storage directory to be read, overwritten, or deleted
Risk Level: High

Vulnerable Code

python
def _get_graph_path(self, pipeline_id: str) -> str:
    """Get the graph file path."""
    return os.path.join(self.storage_dir, f"{pipeline_id}.json")

The generated path is then used by read, write, and deletion operations without validation:

python
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)
python
def _save_graph(self, pipeline_id: str, data: Dict):
    """Save graph data."""
    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
graph_path = self._get_graph_path(pipeline_id)
if os.path.exists(graph_path):
    os.remove(graph_path)
    return {"success": True, "message": "Graph deleted"}

Technical Analysis

pipeline_id is directly concatenated into a filesystem path. There is no allowlist validation, separator rejection, canonical-path comparison, or verification that the resolved file remains under self.storage_dir.

A value containing traversal components, such as ../../target, produces a path equivalent to:

text
data/graphs/../../target.json

Operating-system path resolution can consequently escape data/graphs. The vulnerable path is consumed by _load_graph(), _save_graph(), and delete_graph(), exposing read, write, and deletion primitives.

The normal ...[truncated 1869 chars]

Remediation
View remediation

Remediation Suggestions

  1. Apply strict allowlist validation to every externally supplied pipeline identifier. For example, accept only identifiers matching ^pipeline_[A-Za-z0-9_-]+$.

  2. Explicitly reject absolute paths, path separators, null bytes, . components, and .. components.

  3. Resolve both the storage root and candidate path canonically, then verify containment before every operation:

    python
    from pathlib import Path
    import re
    
    PIPELINE_ID_PATTERN = re.compile(r"^pipeline_[A-Za-z0-9_-]+$")
    
    def _get_graph_path(self, pipeline_id: str) -> Path:
        if not PIPELINE_ID_PATTERN.fullmatch(pipeline_id):
            raise ValueError("Invalid pipeline identifier")
    
        root = Path(self.storage_dir).resolve()
        candidate = (root / f"{pipeline_id}.json").resolve()
    
        if candidate.parent != root:
            raise ValueError("Graph path escapes the storage directory")
    
        return candidate
    
  4. Perform the containment check centrally so reads, writes, and deletions cannot accidentally use different validation rules.

  5. Run the process with a dedicated, minimally privileged account whose filesystem access is restricted to application data.

  6. Consider removing or separately authorizing delete_graph() when graph identifiers can originate from untrusted callers.

  7. Add tests covering relative traversal, absolute paths, mixed separators, symbolic links, encoded separators, and malformed identifiers.

T01 · Skill Instruction Hijacking

Warning
Location
scripts/episode_generator.py:87
Finding

Prompt Injection Through Untrusted Story and Graph Content

Content
View full analysis

Vulnerability Details

File Location: scripts/episode_generator.py:87-94, with additional injection sinks at scripts/episode_generator.py:178-190, scripts/episode_generator.py:262-274, and scripts/ai_reviewer.py:106-126
Vulnerability Type: Untrusted content embedded directly into privileged LLM prompts
Risk Level: Medium

Vulnerable Code

The first-episode prompt directly interpolates user-controlled theme and style values:

python
prompt = f"""# First Episode Generation Task

## Basic Settings
- Story theme: {theme}
- Total episodes: {target_episodes}
- Style: {style}

## First Episode Requirements

Subsequent generation prompts directly include previously generated content and stored graph data:

python
prompt = f"""# Episode {episode_number} Generation Task

## Context

### Previous Episode Content
{prev_content}

### Graph Data
{json.dumps(graph_data, ensure_ascii=False, indent=2)}

{hooks_info}

## Episode {episode_number} Requirements

The review prompt likewise places generated episode content into the same instruction string as the review policy:

python
prompt = f"""# Episode Quality Review Task

## Review Target
- Episode number: Episode {episode_number}
- Content: {episode_content}

"""

if prev_content:
    prompt += f"""## Previous Episode Content
{prev_content}

"""

if graph_data:
    prompt += f"""## Graph Data
{json.dumps(graph_data, ensure_ascii=False, indent=2)}

"""

prompt += """## Review Requirements

Technical Analysis

The prompt builders do not maintain a clear trust boundary between control instructions and untrusted data. Themes, styles, generated episodes, previous episode content, and graph records are placed directly into the same textual prompt that defines generation or review behavior.

An attacker can place instruction-like text in one of these fields, such as a demand to ignore later review ...[truncated 2070 chars]

Remediation
View remediation

Remediation Suggestions

  1. Separate trusted instructions from untrusted content using structured message roles where supported. Keep policy and task requirements in system or developer messages and place story content in user-data fields.
  2. Wrap all untrusted content in explicit delimiters and state that text inside those delimiters is data that must not be followed as instructions.
  3. Prefer structured payloads over free-form concatenation. For example, pass a JSON object with dedicated fields for theme, style, previous content, and graph data.
  4. Validate output independently rather than relying on the model to obey prompt instructions.
  5. Sanitize or flag instruction-like phrases in persisted story and graph content before reusing them in later prompts.
  6. Limit the amount of historical raw content reintroduced into each prompt. Use validated summaries where possible.
  7. Add adversarial tests containing phrases such as “ignore previous instructions,” forged Markdown headings, fake review schemas, and nested code fences.
  8. Ensure that any surrounding agent runtime does not grant tool privileges based solely on content generated from these prompts.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ai_reviewer.py:174
Finding

AI Review Threshold Bypass Through Trusted Model-Supplied Boolean

Content
View full analysis

Vulnerability Details

File Location: scripts/ai_reviewer.py:174-184, with the bypass consumed at scripts/pipeline.py:253-286
Vulnerability Type: Fail-open business-logic validation of an untrusted AI review result
Risk Level: Medium

Vulnerable Code

The parser accepts the model-provided passed field independently of the score:

python
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 then treats that Boolean as authoritative:

python
review = self.ai_reviewer.parse_review_result(ai_review_result)

if not review.passed:
    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 failed with score {review.score}; regeneration is required"
    }

state.status = "waiting_user_confirm"
state.retry_count = 0
state.ai_review = {
    "score": review.score,
    "checks": review.checks,
    "suggestions": review.suggestions,
    "summary": review.summary
}

Technical Analysis

The documented policy defines a passing review as a score of at least 7.0, and AIReviewer.PASS_THRESHOLD is set to 7.0. However, parse_review_result() does not derive the decision from this threshold. Instead, it trusts the untrusted JSON Boolean returned by the model.

Consequently, internally inconsistent input such as the following is accepted as a passing review:

json
{
  "passed": true,
  "score": 0,
  "checks": {},
  "suggest
...[truncated 1644 chars]
Remediation
View remediation

Remediation Suggestions

  1. Ignore the model-supplied passed Boolean and derive the decision locally:

    python
    raw_score = data.get("score")
    
    if not isinstance(raw_score, (int, float)) or isinstance(raw_score, bool):
        raise ValueError("Review score must be numeric")
    
    score = float(raw_score)
    if not 0.0 <= score <= 10.0:
        raise ValueError("Review score is outside the permitted range")
    
    passed = score >= self.PASS_THRESHOLD
    
  2. Require every configured review dimension and validate that each component score is numeric and between 0 and 10.

  3. Recalculate the weighted overall score from component scores instead of trusting the supplied aggregate.

  4. Reject any response in which the supplied Boolean conflicts with the locally calculated decision.

  5. Treat schema-validation failures as review failures and retain a clear error reason.

  6. Authorize access to process_ai_review() so untrusted callers cannot directly submit forged review results.

  7. Add unit tests for low-score pass claims, high-score failure claims, strings in numeric fields, Boolean scores, missing dimensions, out-of-range values, non-finite numbers, and inconsistent weighted totals.

Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The mismatch between broad claims and narrower actual behavior increases the chance of misuse, especially because the skill also references local storage and remote APIs. Users may assume robust story-generation, approval, and relationship-management logic exists when the system may only store data and emit prompts, causing accidental disclosure, improper automation, or policy bypass through misplaced trust.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The mismatch between broad claims and narrower actual behavior increases the chance of misuse, especially because the skill also references local storage and remote APIs. Users may assume robust story-generation, approval, and relationship-management logic exists when the system may only store data and emit prompts, causing accidental disclosure, improper automation, or policy bypass through misplaced trust.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The mismatch between broad claims and narrower actual behavior increases the chance of misuse, especially because the skill also references local storage and remote APIs. Users may assume robust story-generation, approval, and relationship-management logic exists when the system may only store data and emit prompts, causing accidental disclosure, improper automation, or policy bypass through misplaced trust.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The mismatch between broad claims and narrower actual behavior increases the chance of misuse, especially because the skill also references local storage and remote APIs. Users may assume robust story-generation, approval, and relationship-management logic exists when the system may only store data and emit prompts, causing accidental disclosure, improper automation, or policy bypass through misplaced trust.

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
89% confidence
Finding

The skill describes file-based state persistence and local storage operations, but the manifest does not declare any tool scope or allowed-tools. This creates an authorization/expectation gap: a reviewer or runtime may not understand that the skill needs filesystem access, and if tools are later granted broadly, the skill could read or write local data without explicit least-privilege constraints.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This JSON stores the skill's theme, style, review comments, summaries, and generated episode content entirely in Chinese, indicating a language-specific behavior. Because the file provides no natural-language note that the skill is China-specific or that users can choose the language, this can indicate a locale policy violation under the language/opt-in rule.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code embeds user-facing descriptions and formatted review output entirely in Chinese, which imposes a specific language on users. The file does not provide opt-in, localization support, or justification that the skill is intended only for a Chinese-language environment.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code file contains natural-language instructions and documentation exclusively in Chinese, starting with the module docstring and continuing throughout the file. Under the policy, forcing a specific language without opt-in or a documented region-specific justification is a locale-policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code file contains module documentation and multiple returned user-facing messages entirely in Chinese, indicating an implicit language restriction. Under the policy, forcing a specific language without user opt-in or documented justification 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
93% confidence
Finding

This code file uses Chinese-only natural-language documentation and returns Chinese-only status/error messages throughout the API, starting with the module docstring. Under the policy rule, forcing a specific language without user opt-in is a locale/language policy violation because no alternate language or configuration path is offered.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The example preview switches from English narration to Chinese text, suggesting the skill may generate multilingual output by default. The document does not state that users can choose the output language or that mixed-language output is optional, which can violate language/locale expectations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

This code file contains natural-language text that mixes English and Chinese in the module description, but does not state whether the skill is intended to operate bilingually or whether users can choose their preferred language. Under the policy rule for language/locale constraints, presenting user-facing language without opt-in can be a policy concern when no choice is offered.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.