Back to skill

Security audit

NovelForge

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent novel-writing skill, but it needs Review because some local file tools are under-scoped and can modify, read, or delete outside the intended project boundary.

Install only in a dedicated, trusted novel workspace. Do not run it on projects or engine folders from untrusted sources until the author enforces project-root path confinement, rejects symlinked project folders, and restricts the patch script to hash-checked chapter files. Expect Chinese-first interaction and persistent .novelforge project state.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
novelforge_engine/settlement.py:143
Finding
Project Directory Symlinks Permit Filesystem Access Outside the Project Root<![CDATA[ ## Vulnerability Details **File Location**: `novelforge_engine/settlement.py:143-152, 189-225`; `novelforge_engine/wiki.py:67-114, 125-126, 244-245` **Vulnerability Type**: Insufficient symlink validation and project-root path confinement **Risk Level**: High ### Vulnerable Code ```python # novelforge_engine/settlement.py:143-152 wiki_file = root / folder / (fact["id"] + ".md") if not wiki_file.is_file(): wiki_file = root / "wiki" / folder / (fact["id"] + ".md") if wiki_file.is_file(): try: meta, _ = read_frontmatter(wiki_file) if meta.get("_source_chapter") == chapter: wiki_file.unlink() except (OSError, ValueError): pass ``` ```python # novelforge_engine/settlement.py:189-225 for fact in canonical["facts"]: folder = _FOLDER_BY_TYPE[fact["type"]] folder_path = root / folder folder_path.mkdir(parents=True, exist_ok=True) wiki_path = folder_path / (fact["id"] + ".md") existing_meta = {} existing_body = "" if wiki_path.is_file(): try: existing_meta, existing_body = read_frontmatter(wiki_path) except Exception: existing_meta, existing_body = {}, "" elif (root / "wiki" / folder / (fact["id"] + ".md")).is_file(): try: existing_meta, existing_body = read_frontmatter( root / "wiki" / folder / (fact["id"] + ".md") ) except Exception: existing_meta, existing_body = {}, "" meta = dict(existing_meta) meta.update(fact) meta["_source_chapter"] = chapter meta["_source_body_hash"] = canonical["source_hash"] body = existing_body if not body.strip(): name = meta.get("name", meta.get("id", "Untitled")) lines = [f"# {name}\n"] for key in sorted(meta): if key in ( "id", "name", "type", "_source_chapter", "_source_body_hash", "p ...[truncated 3830 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject symbolic links for the project root and every managed directory before any operation: - `.novelforge` - `wiki` - `characters` - `locations` - `items` - `factions` - `hooks` - `relations` - `timeline` - `entities` - `blueprints` - `chapters` 2. Resolve every candidate path and verify confinement immediately before reading, writing, replacing, or deleting: ```python def require_project_path(root, candidate): root = Path(root).resolve(strict=True) candidate = Path(candidate) if candidate.is_symlink(): raise ValueError("symbolic links are not permitted") resolved_parent = candidate.parent.resolve(strict=True) resolved_parent.relative_to(root) return resolved_parent / candidate.name ``` 3. Check every intermediate path component, not only the final file. Reject the operation if any component is a symlink. 4. For deletion, verify that the resolved target remains beneath the resolved project root and is a regular file before calling `unlink()`. 5. Where supported, use directory file descriptors and no-follow flags such as `O_NOFOLLOW` to reduce time-of-check/time-of-use races. 6. Do not recursively scan a directory until its resolved location has been confirmed to be inside the project root. 7. Add tests covering: - A symlinked standard Wiki directory. - A symlinked legacy `wiki` directory. - Settlement writes through a symlink. - Stale-settlement deletion through a symlink. - Wiki scanning through a symlink. - Symlink replacement between validation and mutation. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/apply_patch.py:52
Finding
Patch Utility Can Replace Arbitrary Accessible UTF-8 Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apply_patch.py:52-60, 89-100` **Vulnerability Type**: Missing target authorization and optional integrity precondition **Risk Level**: Medium ### Vulnerable Code ```python # scripts/apply_patch.py:52-60 def apply_patch(file_path, patch_text, expected_sha256=None): """Apply every block or leave the original file byte-for-byte unchanged.""" if not os.path.exists(file_path): print("error: target file does not exist: {}".format(file_path)) return False try: with open(file_path, "rb") as handle: original_bytes = handle.read() original_text = original_bytes.decode("utf-8") ``` ```python # scripts/apply_patch.py:89-100 updated_bytes = "".join(lines).encode("utf-8") directory = os.path.dirname(os.path.abspath(file_path)) or "." descriptor, temp_path = tempfile.mkstemp(prefix=".patch-", dir=directory) try: with os.fdopen(descriptor, "wb") as handle: handle.write(updated_bytes) handle.flush() os.fsync(handle.fileno()) os.replace(temp_path, file_path) except OSError as exc: try: os.unlink(temp_path) ``` The source-hash condition is optional: ```python if expected_sha256 and _sha256(original_bytes) != expected_sha256: print("error: source SHA-256 does not match") return False ``` ### Technical Analysis The Skill documentation presents `scripts/apply_patch.py` as a narrow chapter-repair utility. The implementation, however, accepts any existing path supplied on the command line. It does not require the target to: - Belong to an initialized NovelForge project. - Resolve beneath the project root. - Be located in an approved `chapters` directory. - Have a chapter `.md` or `.txt` extension. - Be a regular non-symlink file. - Be accompanied by a SHA-256 precondition. The chapter invalidation helper does not enforce authorization. It simply returns successfully for paths that do not look like chapter ...[truncated 1609 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require the caller to provide an initialized NovelForge project root explicitly. 2. Resolve the target and enforce that it is directly inside an approved chapter directory under that root, such as: ```python root = Path(project_root).resolve(strict=True) target = Path(file_path).resolve(strict=True) allowed = { (root / "chapters").resolve(strict=True), (root / "wiki" / "chapters").resolve(strict=True), } if target.parent not in allowed: raise ValueError("patch target must be a project chapter") ``` 3. Require the target suffix to be `.md` or `.txt`, and require its stem to pass the project's chapter-ID validation. 4. Reject the project root, target parent, and target if any is a symbolic link. 5. Make `expected_sha256` mandatory rather than optional. Reject missing, malformed, or mismatched hashes before creating a temporary file. 6. Confirm that the target is a regular file and remains beneath the resolved project root immediately before replacement. 7. Perform chapter invalidation as part of the same required workflow. Do not allow successful patching when the target cannot be associated with an initialized project chapter. 8. Add negative tests proving that the utility rejects: - Absolute paths outside the project. - Relative paths using traversal. - Files in unrelated project directories. - Non-chapter files. - Symlink targets and symlinked chapter directories. - Calls without a valid source hash. ]]>
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (47)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A file patch facility that can target arbitrary paths, combined with integrity/hash logic and atomic writes, is a real security-sensitive capability in an agent skill. Even if intended for chapter editing, such machinery can be misused to overwrite unrelated local files, modify configuration, or tamper with code unless strict path boundaries are enforced.

Vague Triggers

High
Confidence
97% confidence
Finding
The trigger scope is extremely broad, covering many common authoring phrases and workflow requests, which increases the chance of accidental or overbroad activation. In a skill that can read, write, patch, audit, and invoke shell-based scripts, overly permissive natural-language activation materially raises the risk of unintended state-changing operations.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
该文件以中文明确描述整个技能及其使用方式,但未说明是否支持其他语言,也没有给出用户可选择语言/locale 的提示。按规则,若技能在自然语言层面强制特定语言且无用户 opt-in,可视为语言/locale 政策风险。

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares it requires local binaries and describes filesystem-changing workflows, but it does not declare any explicit tool/permission scope. In an agent environment, missing tool scoping can allow broader-than-necessary file and shell access, increasing the chance of unintended modification or abuse if the skill is triggered in the wrong context.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file’s description and all operating instructions are written exclusively in Chinese, indicating the skill is intended to operate in a specific language/locale. The file does not offer a user language choice or explain a justified region-specific constraint, which matches the language/locale policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The manifest description is written as a Chinese-only role definition, and the body of the skill continues entirely in Chinese, indicating a fixed language/locale expectation. Because the file does not offer opt-in language selection or explain that the skill is intentionally region-specific, this is a natural-language policy concern under the locale-choice rule.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This manifest file contains user-facing natural-language prompt content exclusively in Chinese across multiple model entries. Because the file provides no indication that the skill is region-specific or that users can choose language/locale, it appears to impose a specific language without user opt-in.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The entire skill guidance is written only in Chinese, with no indication that users may opt into another language or locale. Under the language/locale policy, forcing a specific language without user choice is a natural-language policy violation unless clearly documented as region-specific or optional.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
Multiple eval prompts are written as direct Chinese-language instructions, which effectively impose a specific language/locale on skill behavior. The file does not indicate that Chinese is optional, user-selected, or required for a documented region-specific purpose.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
This JSON file defines the skill's trigger evaluation examples entirely in Chinese, which signals an implicit language constraint for activation and expected usage. Because there is no accompanying indication that other languages are supported or that Chinese is an opt-in requirement, this can violate the policy against forcing a specific language without user choice.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
Mandating Simplified Chinese for all human interaction without user opt-in can override user preferences and make the skill activate or continue in a language mode the user did not request. This is primarily a safety/usability control issue rather than direct code execution, but it increases the chance of user confusion, consent mismatch, and incorrect handling in multilingual environments.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The skill advertises very broad natural-language triggers such as ‘继续’, ‘恢复’, ‘写小说’, and other common phrases, which can cause the agent to invoke this workflow in contexts the user did not clearly intend. Because invocation leads to filesystem-oriented commands and pipeline advancement behavior, accidental activation could redirect the assistant away from the user’s actual task or cause unintended project-state changes.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This code embeds Chinese-language prompt text in default model cards and repeatedly generates Chinese-language markdown content and research summaries. The file provides no user choice of language and no documented reason that the engine must be Chinese-only, which conflicts with the policy against forcing a specific language without opt-in.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
This is a code file, so SQP-3 applies to natural-language policy violations found in docstrings. The description explicitly says the skill provides 'standard Chinese webnovel genre taxonomy', which signals a forced language/locale orientation without any user choice or documented region-specific justification.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
This code embeds multiple user-facing artifact descriptions entirely in Chinese, such as project/profile and blueprint instructions. That creates an implicit language policy constraint without offering a user language choice or documenting a justified locale restriction.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code deletes wiki files via unlink() when cleaning up obsolete facts, but there is no confirmation prompt, logging/print statement, or nearby comment/docstring that clearly discloses this deletion behavior to the user. This is a safety-relevant destructive operation because it irreversibly removes files derived from prior settlement state.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_apply_patch.py:11

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_text_lint.py:10