Back to skill

Security audit

story-long-write

Security checks for vulnerabilities and agentic risk

Overview

This writing skill is coherent overall, but it needs Review because a bundled tracking script can delete Markdown files outside the project through a symlink and author-memory files are written with permissive local permissions.

Install only if you want an agent to manage a local novel project with many files, run bundled Python/Node checks, and keep workspace-local author preferences. Avoid running it on untrusted or shared projects until the symlink deletion issue is fixed, keep workspaces private if using author memory, and treat the plot-adaptation and viewpoint guidance as material to review rather than neutral writing rules.

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
scripts/tracking_commit.py:1068
Finding
Symlink-Following Cleanup Can Delete Files Outside the Project<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tracking_commit.py`, lines 1068-1073 **Vulnerability Type**: Unrestricted file deletion through a symlinked managed directory **Risk Level**: High ### Vulnerable Code ```python expected_character_files = { Path(relative).name for relative in views if relative.startswith("角色状态/") } character_dir = tracking / "角色状态" character_dir.mkdir(parents=True, exist_ok=True) for path in character_dir.glob("*.md"): if path.name not in expected_character_files: path.unlink() ``` ### Technical Analysis The `write_views()` cleanup routine assumes that `追踪/角色状态` is a real directory located beneath the selected project. It neither checks the directory with `lstat()` nor verifies that its resolved path remains inside the resolved project root. If a crafted project contains `追踪/角色状态` as a symbolic link to another directory, `mkdir(..., exist_ok=True)` accepts the existing linked directory. The subsequent `glob("*.md")` enumeration follows that link, and `path.unlink()` removes Markdown files from the linked external directory whenever their names are absent from `expected_character_files`. The deletion operation runs with the privileges of the user invoking the Skill. Exploitation does not require command injection or shell execution; it relies entirely on filesystem path resolution. ### Attack Path 1. An attacker prepares or modifies a story project. 2. The attacker replaces `追踪/角色状态` with a symbolic link to a directory containing files the victim can modify. 3. The victim opens the project and invokes a documented tracking initialization or commit workflow. 4. `write_views()` follows the symbolic link and enumerates `*.md` files in the external target directory. 5. Every enumerated file not matching an expected generated character-state filename is deleted. 6. The files are removed using the victim process's filesystem permissions. ### Impact Assessment An attacker can cause deletion of Markdo ...[truncated 455 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject symbolic links for every managed tracking directory: ```python character_dir = tracking / "角色状态" if character_dir.is_symlink(): raise TrackingError("character-state directory must not be a symbolic link") ``` 2. Resolve and enforce directory containment before reading, writing, or deleting: ```python tracking_real = tracking.resolve(strict=True) character_real = character_dir.resolve(strict=True) try: character_real.relative_to(tracking_real) except ValueError as exc: raise TrackingError("character-state directory escapes tracking root") from exc ``` 3. Use `os.lstat()` or `Path.lstat()` so validation examines the directory entry itself rather than following a link. 4. Securely create managed directories during initialization and record their expected identity. Refuse to proceed if an existing path is not a real directory owned or controlled by the invoking user. 5. Before deleting each stale file, verify that: - The file is a regular file and not a symbolic link. - Its resolved parent is the validated character-state directory. - Its name matches the exact generated-file naming convention. 6. Prefer an allowlisted manifest of files previously generated by the tool rather than deleting every unexpected `*.md` file. This avoids deleting unrelated user-created files even inside the legitimate managed directory. 7. Add regression tests using: - A symlinked `角色状态` directory. - Symlinked individual Markdown files. - Nested paths resolving outside the project. - Unrelated user-created Markdown files in the managed directory. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/author_memory_commit.py:136
Finding
Persistent Author-Memory Files Are Created with World-Readable Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/author_memory_commit.py`, lines 136-147 **Vulnerability Type**: Excessively permissive permissions on persistent user-profile data **Risk Level**: Medium ### Vulnerable Code ```python def atomic_write_text(path: Path, payload: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) mode = stat.S_IMODE(path.stat().st_mode) if path.exists() else 0o644 fd, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent) temporary = Path(temporary_name) try: with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle: handle.write(payload) handle.flush() os.fsync(handle.fileno()) os.chmod(temporary, mode) os.replace(temporary, path) ``` ### Technical Analysis `tempfile.mkstemp()` initially creates the temporary file with restrictive permissions. The code subsequently changes a newly created file to mode `0644` before atomically replacing the destination. The author-memory subsystem stores persistent preference assertions, exact evidence quotations, source references, and a change journal under `.story/作者记忆`. Mode `0644` allows all local users to read these files when the parent directories are traversable. The parent directories are created with default `mkdir()` permissions and are not explicitly restricted. For existing files, the code preserves their current mode. Consequently, previously permissive files remain permissive rather than being tightened during later writes. ### Attack Path 1. The victim uses the Skill's author-memory feature to save a preference or decision. 2. The input includes an exact quotation and may include a source reference. 3. `write_snapshot()` calls `atomic_write_text()` for the structured state and generated views. 4. New files are explicitly changed to mode `0644`. 5. On a multi-user host where the workspace path is traversable, another local account ...[truncated 600 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create new author-memory files with owner-only permissions: ```python mode = 0o600 ``` 2. Create the author-memory directory with owner-only access, such as mode `0700`, and verify its effective permissions after creation. 3. Do not preserve an existing mode when it is more permissive than the security policy. Normalize existing files to `0600` during each successful write: ```python existing_mode = stat.S_IMODE(path.stat().st_mode) if path.exists() else 0o600 mode = existing_mode & 0o600 ``` 4. Apply permissions using descriptor-based operations where possible, such as `os.fchmod(fd, 0o600)`, to reduce path replacement and race concerns. 5. Validate that `.story` and `作者记忆` are real directories rather than symbolic links before creating persistent files. 6. Document that exact user quotations and preference history are persisted, where they are stored, and how the user can inspect or remove them. 7. Add tests confirming: - New state and view files are mode `0600`. - Memory directories are mode `0700`. - Existing mode `0644` files are tightened on update. - The implementation behaves safely under restrictive and permissive process umasks. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (10)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill instructs the agent to read and write many project files, invoke scripts, and use shell-like tooling, but the manifest declares no permissions or capability boundaries. This creates a confused-deputy risk: users and hosts may treat the skill as low-privilege writing assistance while it actually performs filesystem mutations and command execution paths.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
The declared description presents a writing-assistance skill, but the body additionally manages persistent state, tracking files, lock files, quality-analysis pipelines, protocol validation, and prompt construction for subagents. That mismatch can mislead users and security policy engines about the true operational scope, increasing the chance that broader file and process behavior is approved without informed consent.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
This section explicitly instructs users to locate source works and adapt their plots, including a statement that modifying a source enough makes it 'original.' In a writing-assistance skill, that encourages derivative copying and can facilitate plagiarism or copyright infringement, especially because it operationalizes how to transform existing works rather than merely study genre conventions.

Intent-Code Divergence

Medium
Confidence
93% confidence
Finding
This line labels `设定/角色/{角色名}.md` as '角色状态', which conflicts with the rest of the workflow that explicitly distinguishes static character definitions from dynamic tracked state. That inconsistency can cause the agent to read stale static profiles as current state, leading to continuity corruption, wrong updates, or invalid tracking transactions that may overwrite derived state with incorrect data.

Description-Behavior Mismatch

Medium
Confidence
84% confidence
Finding
This writing-assistance skill includes commit/checkpoint functionality via a tracking module, expanding it from content assistance into repository/state-mutating behavior. In an agent context, that increases the chance of unintended persistence, unauthorized project modifications, or abuse of the skill to alter tracked state beyond the user's writing request.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The skill executes external Node.js helper programs as part of normal operation, which is a materially broader capability than simple writing assistance. In an agent environment, spawning external runtimes increases attack surface through helper-script compromise, PATH manipulation, dependency hijacking, or unsafe behavior inside those scripts, especially when processing user-selected project files.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger list includes broad everyday phrases such as '续写', '继续写', and '写大纲', which are common in normal conversation and can invoke a skill that performs file reads, writes, and script-driven workflow changes. In a high-action skill with persistent project mutations, accidental activation can lead to unintended modifications or execution of complex workflows without clear user intent.

Natural-Language Policy Violations

Medium
Confidence
87% confidence
Finding
The file presents gendered romance-writing logic as prescriptive guidance tied to 'male' versus 'female' audience segments, with language such as 'must' and 'cannot be mixed,' rather than as optional or contextualized advice. In a writing-assistant skill, this can steer outputs toward stereotyped, exclusionary, or biased content without checking the user's goals, making the assistant more likely to generate low-safety or misaligned responses for users who do not fit those assumptions.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The file instructs the agent to align historical人物评判 with a specific contemporary political authority's consensus, especially singling out '教员', without user choice or contextual limitation. In a writing-assistant skill, this creates viewpoint steering and ideological bias that can shape outputs as if they were neutral guidance, potentially suppressing user intent and producing politically slanted content.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The document explicitly instructs the agent to create and write many files under a user-specified working directory, but it does not require a clear confirmation or warning before modifying the local workspace. In an agent setting, this can lead to unexpected filesystem changes, accidental overwrites, or creation of large project trees when the user did not realize the skill would perform persistent writes rather than just provide text in chat.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/storyctl.py:22

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/tracking_commit.py:37