Back to skill

Security audit

lobster-novel

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its novel-writing purpose, but it needs Review because it automatically loads an unsafe project cache that could run code from an untrusted novel project.

Install only if you trust the novel projects you will open. Do not run it on shared or downloaded project directories until the pickle RAG cache is replaced with a safe format or disabled, and review provider privacy terms before using API-backed writing on unpublished or sensitive manuscripts.

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

T09 · Insecure Skill Coding Practices

Error
Location
rag/novel_rag.py:254
Finding

Arbitrary Code Execution Through Unsafe Pickle Deserialization

Content
View full analysis
Optional[NovelRAGIndex]: """Load persisted index from disk.""" path = Path(project_dir) / RAG_CACHE_FILE if not path.exists(): return None try: data = pickle.loads(path.read_bytes()) idx = cls.__new__(cls) idx.project_dir = Path(project_dir) idx._docs = data["docs"] idx._vectorizer = data["vectorizer"] idx._tfidf_matrix = data["tfidf_matrix"] idx._story_state = None print(f"[NovelRAG] Index loaded from {path} ({len(idx._docs)} docs)") return idx except Exception as e: print(f"[NovelRAG] Failed to load cache: {e}") return None ``` The cache is automatically loaded by the context agent: ```python def _init_rag(self) -> None: """Lazy-init RAG index.""" if not self._use_rag or self._rag_index is not None: return idx = NovelRAGIndex.load(self.project_dir) if idx is None: idx = NovelRAGIndex(self.project_dir) idx.build() idx.save() self._rag_index = idx ``` ### Technical Analysis Python pickle data is executable serialization rather than a passive data format. During `pickle.loads`, specially constructed objects can invoke attacker-selected callables through methods such as `__reduce__`. Consequently, validating the resulting dictionary after deserialization cannot prevent code execution because the payload executes while it is being decoded. The cache file, `.rag_index.pkl`, resides inside the selected novel project directory. If a project originates from an untrusted archive, repository, shared directory, or collaborator, an attacker can include a malicious cache alongside otherw ...[truncated 1737 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
tools/novel-cli.py:174
Finding

Predictable Shared Temporary File Allows Symlink Overwrite and Data Exposure

Content
View full analysis
.json ``` The chapter number is predictable, no exclusive file-creation operation is used, and existing symbolic links are not rejected. `Path.write_text()` opens the destination for writing and follows a pre-existing symbolic link. On a multi-user system, another local process can create the predictable path before the victim runs the review. If it is a symlink to a file writable by the victim, the review operation can truncate and replace that target with JSON report content. The same predictable name also creates concurrency and confidentiality concerns. Separate Skill runs reviewing the same chapter can overwrite each other’s reports. The temporary file is not deleted after s ...[truncated 1630 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (165)

Tainted flow: 'req' from os.environ.get (line 124, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · batch_vol1.py (reported line 126)May include surrounding context.

python
try:
            req = urllib.request.Request(DEEPSEEK_URL, data=payload,
                headers={"Content-Type":"application/json","Authorization":f"Bearer {DEEPSEEK_KEY}"})
            with urllib.request.urlopen(req, timeout=300) as resp:
                return json.loads(resp.read().decode("utf-8"))["choices"][0]["message"]["content"]
        except Exception as e:
            logger.warning(f"Attempt {attempt+1} failed: {e}")

Tainted flow: 'req' from os.environ.get (line 29, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · core/batch_chapters.py (reported line 35)May include surrounding context.

python
"Content-Type": "application/json",
                    "Authorization": f"Bearer {KEY}",
                })
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                data = json.loads(resp.read().decode("utf-8"))
            msg = data.get("choices", [{}])[0].get("message", {})
            # Support both 'content' and 'reasoning' fields

Tainted flow: 'req' from os.environ.get (line 33, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · rebuild_greyhaven.py (reported line 36)May include surrounding context.

python
req = urllib.request.Request(
                DEEPSEEK_URL, data=payload,
                headers={"Content-Type": "application/json", "Authorization": f"Bearer {DEEPSEEK_KEY}"})
            with urllib.request.urlopen(req, timeout=300) as resp:
                data = json.loads(resp.read().decode("utf-8"))
            return data["choices"][0]["message"]["content"]
        except Exception as e:

Tainted flow: 'req' from os.environ.get (line 28, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · rebuild_selfworld.py (reported line 30)May include surrounding context.

python
try:
            req = urllib.request.Request(DEEPSEEK_URL, data=payload,
                headers={"Content-Type":"application/json","Authorization":f"Bearer {DEEPSEEK_KEY}"})
            with urllib.request.urlopen(req, timeout=300) as resp:
                data = json.loads(resp.read().decode("utf-8"))
            return data["choices"][0]["message"]["content"]
        except Exception as e:

Tainted flow: 'req' from os.environ.get (line 152, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · review/deai_writer.py (reported line 159)May include surrounding context.

python
"Authorization": f"Bearer {api_key}",
            })
        try:
            with urllib.request.urlopen(req, timeout=120) as resp:
                data = json.loads(resp.read().decode("utf-8"))
            rewritten = data["choices"][0]["message"]["content"]
            # Strip code blocks if present

Tainted flow: 'req' from os.environ.get (line 46, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/gen_ch02_ch07.py (reported line 50)May include surrounding context.

python
"Content-Type": "application/json",
        "Authorization": f"Bearer {DEEPSEEK_KEY}",
    })
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return json.loads(resp.read().decode("utf-8"))["choices"][0]["message"]["content"]

Tainted flow: 'req' from os.environ.get (line 54, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/generate_ch04_ch07.py (reported line 60)May include surrounding context.

python
"Content-Type": "application/json",
            "Authorization": f"Bearer {DEEPSEEK_KEY}",
        })
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        data = json.loads(resp.read().decode("utf-8"))
    return data["choices"][0]["message"]["content"]

Tainted flow: 'req' from os.environ.get (line 61, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/generate_volume2_chapters.py (reported line 67)May include surrounding context.

python
"Content-Type": "application/json",
            "Authorization": f"Bearer {DEEPSEEK_KEY}",
        })
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        data = json.loads(resp.read().decode("utf-8"))
    return data["choices"][0]["message"]["content"]

Tainted flow: 'req' from os.environ.get (line 60, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · tools/serial_writer.py (reported line 66)May include surrounding context.

python
"Content-Type": "application/json",
                    "Authorization": f"Bearer {key}",
                })
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                data = json.loads(resp.read().decode("utf-8"))
            content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
            if content:

Tainted flow: 'req' from os.environ.get (line 27, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · write_ch1.py (reported line 30)May include surrounding context.

python
req = urllib.request.Request(
                DEEPSEEK_URL, data=payload,
                headers={"Content-Type": "application/json", "Authorization": f"Bearer {DEEPSEEK_KEY}"})
            with urllib.request.urlopen(req, timeout=300) as resp:
                data = json.loads(resp.read().decode("utf-8"))
            return data["choices"][0]["message"]["content"]
        except Exception as e:

Tainted flow: 'req' from os.environ.get (line 29, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · write_ch2.py (reported line 31)May include surrounding context.

python
try:
            req = urllib.request.Request(DEEPSEEK_URL, data=payload,
                headers={"Content-Type":"application/json","Authorization":f"Bearer {DEEPSEEK_KEY}"})
            with urllib.request.urlopen(req, timeout=300) as resp:
                return json.loads(resp.read().decode("utf-8"))["choices"][0]["message"]["content"]
        except Exception as e:
            logger.warning(f"Attempt {attempt+1} failed: {e}")

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 · core/scene_items.py (reported line 158)May include surrounding context.

python
rules["economy"] = "poor_fishing_village"
        if any(k in setting_text for k in ["帝国", "首都", "贸易"]):
            rules["economy"] = "urban_trading"
        return rules

    # ── Phase 1: LLM generates scene items ────────────────────

Natural-Language Policy Violations

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The preset library contains styles explicitly centered on sexual content, including detailed narrative rules for erotic scenes, but provides no safety boundaries, eligibility checks, or restrictions on when such presets may be activated and injected into model prompts. In context, this is more dangerous because get_prompt_injection() converts these presets into reusable generation instructions, making unsafe content generation a built-in feature rather than incidental data.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · write_ch1.py (reported line 118)May include surrounding context.

python
print(f"\n...(共{char_count}字)")

if __name__ == "__main__":
    # If DEEPSEEK_API_KEY not in env, try loading from .env
    if not DEEPSEEK_KEY:
        env_file = Path(".env")
        if env_file.exists():

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · write_ch1.py (reported line 120)May include surrounding context.

python
if __name__ == "__main__":
    # If DEEPSEEK_API_KEY not in env, try loading from .env
    if not DEEPSEEK_KEY:
        env_file = Path(".env")
        if env_file.exists():
            for line in env_file.read_text().splitlines():
                if line.startswith("DEEPSEEK_API_KEY="):

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

Line L005 describes the skill as a pipeline for writing long-form Chinese fiction, presenting a fixed language/content locale rather than an optional setting. Because the README does not offer a language choice or explain a justified region-specific constraint, this appears to force a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Line L113 states that the write command auto-writes the next chapter via the SenseNova API, which implies sending user-provided novel content or context to a third-party service. The README explains the feature and API key requirement but does not warn users that their manuscript data may be transmitted externally.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The README says the tool is "Designed for long-form Chinese fiction," which sets a language-specific constraint in natural language without presenting it as an optional mode or explicit user-selected setting. Under the policy, forcing a specific language without user opt-in is a locale/language policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill documents a write command that uses the SenseNova API but does not clearly warn users that their chapter content and project context will be transmitted to an external service. This can lead to unintended disclosure of unpublished manuscripts, sensitive notes, or proprietary material because users may assume the tool operates locally.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The embedded natural-language rules and style defaults are written exclusively in Chinese, and later prompt construction also uses Chinese section headers and chapter labels. This imposes a specific language/locale on the skill's behavior without offering the user a choice or documenting a justified region-specific constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The writing prompt is assembled entirely with Chinese headings such as '大纲', '活跃角色', and '创作纪律', which enforces a single language for user-facing behavior. Under the policy, a skill should not force a locale unless it offers a choice or clearly documents a justified locale restriction.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The extractor is hard-coded around Chinese-only markers and keywords such as dialogue, action, emotion, and world-building terms, which means the skill effectively assumes Chinese input. There is no natural-language indication that this locale restriction is optional, configurable, or justified as a region-specific tool, so it appears to enforce a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The class advertises itself as only extracting structured data, but it also contains a method that saves commits and integrates them into persistent story state. That mismatch can mislead reviewers, orchestrators, or users into granting the component broader trust than intended, increasing the chance of unintended state modification in automated workflows.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The agent writes a commit to disk, mutates the in-memory story state, and then persists that updated state without any visible authorization, confirmation, or documented boundary separating analysis from side effects. In a skill presented as a data-extraction component, this creates an integrity risk: simply invoking the skill can permanently alter project data, which is dangerous if the caller expects read-only behavior or if extracted data is attacker-controlled.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This Python skill embeds Chinese-only dimension names, keyword lists, status strings, and user-facing messages throughout the file. That enforces a specific language/locale in the skill’s behavior and output without any visible opt-in, configurability, or documented regional limitation.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
agents/reviewer_agent.py:60