Back to skill

Security audit

story-engine-for-creator

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent story-writing skill, but its optional LLM provider can send API keys and unpublished writing to any configured endpoint without enforcing HTTPS or trusted destinations.

Use the offline/default mode for sensitive manuscripts. If you enable OpenAIProvider, configure only a trusted HTTPS endpoint, use a limited-scope API key, and assume story text, outlines, character data, world rules, and audit context may be transmitted to that provider. Be careful with generate_novel output paths because it writes the report file directly.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/story_engine.py:76
Finding
API Credentials and Narrative Content Can Be Sent to an Untrusted or Cleartext Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `scripts/story_engine.py`, lines 76–111 **Vulnerability Type**: Unvalidated remote endpoint and potentially insecure transmission of sensitive information **Risk Level**: High ### Vulnerable Code ```python def __init__( self, api_key: str, model: str = "gpt-4o", base_url: str = "https://api.openai.com/v1", timeout: int = 120, ): self.api_key = api_key self.model = model self.base_url = base_url.rstrip("/") self.timeout = timeout def generate(self, prompt: str, **kwargs) -> str: import json import urllib.request import urllib.error temperature = kwargs.get("temperature", 0.7) max_tokens = kwargs.get("max_tokens", 4096) body = json.dumps({ "model": self.model, "messages": [{"role": "user", "content": prompt}], "temperature": temperature, "max_tokens": max_tokens, }).encode("utf-8") req = urllib.request.Request( f"{self.base_url}/chat/completions", data=body, headers={ "Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}", }, method="POST", ) try: with urllib.request.urlopen(req, timeout=self.timeout) as resp: ``` ### Technical Analysis `OpenAIProvider` accepts a caller-controlled `base_url` and uses it without validating its scheme or destination. The implementation therefore permits cleartext `http://` endpoints and arbitrary remote hosts. Every request places the API key in the `Authorization` header and the generated prompt in the HTTP request body. Depending on the operation, prompts may contain unpublished chapter text, character profiles, world rules, plot details, causal chains, and audit context. Relevant remote-call paths include chapter generation and rewriting, presentation repair, causal diagnosis, prose review, and world-consistency review. The network behavior is optional rather than c ...[truncated 1890 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Require secure transport** - Parse `base_url` with `urllib.parse.urlparse`. - Reject URLs whose scheme is not `https`. - If local development endpoints must be supported, permit cleartext HTTP only for explicitly approved loopback addresses and only through a clearly named development option. 2. **Restrict endpoint destinations** - Default to a documented allowlist of trusted provider hosts. - Require explicit user approval before allowing a custom host. - Reject embedded credentials, malformed hosts, unexpected ports, and ambiguous URL forms. 3. **Prevent unsafe redirects** - Ensure requests carrying `Authorization` cannot be redirected to a different or less secure origin. - Either disable redirects for authenticated requests or validate every redirect target before forwarding credentials. 4. **Minimize transmitted data** - Send only the narrative fields required for the selected operation. - Avoid transmitting entire character databases, chapters, or world state when a smaller excerpt is sufficient. - Add optional redaction for secrets, personal data, and confidential metadata. 5. **Improve disclosure and consent** - Clearly document which methods invoke the provider and what categories of data each method transmits. - Display or log the destination host before the first remote request. - Require confirmation when a custom remote provider is first attached. 6. **Limit credential exposure** - Use provider credentials scoped to the minimum required models, permissions, quotas, and billing limits. - Avoid retaining API keys longer than necessary. - Never include the key in error messages, logs, serialized engine state, or generated reports. 7. **Add security tests** - Verify rejection of `http://` URLs, unapproved hosts, malformed URLs, and cross-origin redirects. - Verify that credentials are sent only to the approved HTTPS origin. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The documented behavior is broader and partly inconsistent with the declared purpose, including generation, style recognition, and optional LLM-backed processing, while some claimed audit/security properties are not substantiated in the provided skill file. This mismatch can mislead users and platform policy engines about what the skill actually does, which undermines safe routing, review, and consent.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises capabilities that imply file access and optional network transmission, but it does not declare an explicit tool/permission scope. That creates a governance gap: hosts or users may invoke the skill without understanding that local content may be read/written or sent to an external API when `OpenAIProvider` is attached.

Vague Triggers

Medium
Confidence
87% confidence
Finding
The trigger scenarios are broad enough to match ordinary creative-writing requests, which can cause over-triggering and unintended invocation. In a skill with audit, generation, and optional external-provider behavior, broad activation increases the chance of processing user content without clear intent or least-privilege scoping.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The usage example shows configuring an OpenAI-compatible endpoint but does not place a clear privacy/data-transmission warning adjacent to that example. Users may paste sensitive manuscript, character, or unpublished IP content into a path that transmits data off-device without informed consent.

External Transmission

Medium
Category
Data Exfiltration
Content
# v2.3: attach a real LLM (any OpenAI-compatible API, zero external deps)
from scripts.story_engine import OpenAIProvider
engine.set_llm_provider(OpenAIProvider(api_key="sk-...", model="gpt-4o",
                                       base_url="https://api.openai.com/v1"))
# v2.3: single-chapter real-time gatekeeping (skips global recomputation)
quick = engine.audit_text("Chapter content", diff_only=True)
# v2.3: foreshadow + spacetime + hash-chain fields
Confidence
90% confidence
Finding
The skill documentation includes an external API endpoint and demonstrates transmitting content to it via an LLM provider. In context, the data being processed may include unpublished creative work or other sensitive text, so undocumented or weakly signposted external transmission presents a real confidentiality risk.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
This line states `output_language="zh"` as the constructor default, which imposes a specific language setting in the documentation. Because no user opt-in or language-choice guidance is provided here, it creates a natural-language locale policy concern.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The description repeatedly frames the skill as deterministic, but the code includes network-backed LLM generation with non-zero temperatures and uses LLMs for content generation, repair, deep causal diagnosis, prose checks, and world consistency checks. Those behaviors introduce probabilistic outputs that materially weaken the claimed deterministic nature of the tool.

External Transmission

Medium
Category
Data Exfiltration
Content
Args:
        api_key:   API 密钥。
        model:     模型名称,如 "gpt-4o"、"deepseek-chat"。
        base_url:  API 基础地址,默认 "https://api.openai.com/v1"。
        timeout:   请求超时(秒)。
    """
Confidence
96% confidence
Finding
The code is explicitly designed to send content to an external API endpoint, defaulting to api.openai.com, and later generation paths include story text, outlines, character data, and audit context in prompts. In this skill's context, that means potentially sensitive or proprietary creative content may leave the local environment without any built-in disclosure or restriction, making the transmission materially security-relevant.

External Transmission

Medium
Category
Data Exfiltration
Content
self,
        api_key: str,
        model: str = "gpt-4o",
        base_url: str = "https://api.openai.com/v1",
        timeout: int = 120,
    ):
        self.api_key = api_key
Confidence
96% confidence
Finding
The default constructor sets a live external base URL, which lowers the barrier to accidental outbound transmission once a provider is instantiated. Because this engine can process full manuscripts and audit metadata, the skill context makes silent default egress more dangerous than a generic demo utility.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
User-provided story text and prompts are transmitted to an externally configurable OpenAI-compatible endpoint with no consent, warning, redaction, or policy gate in this code path. In a creative-writing tool, users may submit unpublished manuscripts, sensitive notes, or proprietary content, so silent transmission creates a real confidentiality and data-governance risk.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The engine defaults output_language to 'zh', and multiple prompt branches instruct the model to produce Chinese text by default. This creates a language policy concern because the skill imposes a specific language without user opt-in or automatic language choice.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest emphasizes deterministic plot architecture, causal reasoning, audits, and report generation. In this code, the engine goes beyond auditing into creative content generation and full-text rewriting, including chapter rendering, transition writing, and prose rewriting through LLM prompts. That is a broader behavior than a deterministic audit/tooling scope suggests.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
obj: Any = self.global_state
        for part in parts[:-1]:
            if hasattr(obj, part):
                obj = getattr(obj, part)
            elif isinstance(obj, dict):
                obj = obj.setdefault(part, {})
            elif isinstance(obj, list) and part.lstrip("-").isdigit():
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Missing User Warnings

Low
Confidence
88% confidence
Finding
generate_novel writes generated report output to output_path using open(..., 'w'), which can overwrite an existing file. Although the parameter name suggests output behavior, this code path has no visible confirmation, warning, or logging that a file write will occur.

Intent-Code Divergence

Low
Confidence
86% confidence
Finding
The audit_text docstring describes diff-only mode as checking only the current text and skipping accumulated global-state reanalysis. However, the diff-only path still builds a report using self.foreshadow_ledger.reconcile(), which reflects prior accumulated ledger state rather than only the current input, so the documentation overstates isolation of the incremental mode.

Static analysis

No suspicious patterns detected.