Back to skill

Security audit

novel-forge

Security checks for vulnerabilities and agentic risk

Overview

Novel Forge is a coherent novel-writing workflow, but it asks for broad local configuration access and includes file-writing scripts that can overwrite arbitrary writable paths.

Review this skill before installing. Use it only in a constrained workspace, avoid running its scripts with elevated privileges, keep backups of novel project files, and do not pass arbitrary `--out`, `--out-dir`, or config paths. Prefer a sanitized model-inventory source over direct access to the OpenClaw configuration file.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/scaffold_project.py:51
Finding

Unrestricted and Destructive Project File Overwrites

Content
View full analysis

Vulnerability Details

File Location: scripts/scaffold_project.py, lines 51–57 and 127–143
Vulnerability Type: Unrestricted filesystem writes and symlink-following overwrite
Risk Level: Medium

Vulnerable Code

python
parser.add_argument("--out-dir", default="novel")
...
slug = slugify(args.title)
root = Path(args.out_dir).expanduser().resolve() / slug
chapters = root / "chapters"
state_dir = root / "state"
root.mkdir(parents=True, exist_ok=True)
chapters.mkdir(parents=True, exist_ok=True)
state_dir.mkdir(parents=True, exist_ok=True)
python
(root / "project.json").write_text(json.dumps(project, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
(root / "worldbuilding.md").write_text("# Worldbuilding\n\n", encoding="utf-8")
(root / "characters.md").write_text("# Characters\n\n", encoding="utf-8")
(root / "outline.md").write_text("# Outline\n\n", encoding="utf-8")
(root / "style.md").write_text("# Style\n\n", encoding="utf-8")
(root / "memory.md").write_text("# Memory\n\n", encoding="utf-8")
(state_dir / "current.json").write_text(json.dumps(current_state, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
(chapters / f"{chapter_number:04d}.md").write_text(f"# Chapter {chapter_number}\n\n", encoding="utf-8")
if args.resume_from:
    (root / "resume.md").write_text(
        "# Resume Notes\n\n"
        "## Recovery checkpoint\n"
        f"- resumeMode: {args.resume_mode}\n"
        f"- resumeFrom: {args.resume_from}\n"
        f"- latestChapterIndex: {args.latest_chapter_index}\n"
        f"- nextChapter: {chapter_number}\n"
        "- lastStableSentenceOrBeat: \n"
        "- knownCanonFacts: []\n"
        "- unknownsThatMustNotBeInvented: []\n"
        f"- partialDraftSource: {args.partial_draft}\n"
        "- nextAction: continue recovery\n"
        "- userConfirmationRequired: yes\n",
        encoding="utf-8",
    )

Technical Anal

...[truncated 2394 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require a configured workspace root and ensure the resolved project path remains beneath it:

    python
    workspace = Path(args.workspace).expanduser().resolve()
    root = (workspace / slug).resolve()
    if not root.is_relative_to(workspace):
        raise SystemExit("project path escapes the approved workspace")
    
  2. Refuse to scaffold into an existing non-empty directory unless the caller supplies an explicit --force or --overwrite option.

  3. Before writing, inspect every destination component and target with lstat() and reject symbolic links.

  4. Use exclusive file creation for new projects, such as mode "x", rather than unconditional truncation.

  5. For intentional updates, write to a temporary file in the same directory, flush and synchronize it, and atomically replace the destination.

  6. Back up existing project files before an explicitly authorized overwrite.

  7. Run the script under a minimally privileged account whose writable scope is limited to the novel workspace.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/build_context_pack.py:158
Finding

Arbitrary Output File Overwrite in Context Pack Builder

Content
View full analysis

Vulnerability Details

File Location: scripts/build_context_pack.py, lines 158 and 181–182
Vulnerability Type: Caller-controlled arbitrary file overwrite
Risk Level: Medium

Vulnerable Code

python
parser.add_argument("--out", help="Write JSON to this path instead of stdout")
python
if args.out:
    Path(args.out).write_text(text + "\n", encoding="utf-8")
else:
    print(text)

Technical Analysis

The --out argument is used directly as a filesystem path. No approved output root is enforced, and the script does not check whether the destination already exists or is a symbolic link.

Path.write_text() opens the destination for writing and truncates existing content. Therefore, any file writable by the executing account can be replaced with the generated JSON context pack. If an expected output path is located in a directory writable by another user, a symbolic link can redirect the write to another writable target.

The generated content is static project-state JSON rather than attacker-selected arbitrary bytes, but replacing a configuration or state file with that JSON can still cause data loss or denial of service.

Attack Path

  1. An attacker controls or influences the command-line arguments passed to build_context_pack.py.
  2. The attacker supplies an absolute or relative --out path identifying an existing file writable by the executing account.
  3. Alternatively, the attacker supplies a path to a pre-positioned symbolic link that resolves to such a file.
  4. The script constructs the context pack and calls write_text() without validating the destination.
  5. The target file is truncated and replaced with context-pack JSON.

Impact Assessment

The write capability is constrained by the operating-system permissions of the executing account. Within that boundary, exploitation can:

  • Destroy or corrupt existing project and state files.
  • Replace unrelated w ...[truncated 332 chars]
Remediation
View remediation

Remediation Suggestions

  1. Restrict output to the selected Novel Forge project directory or another explicitly approved root.

  2. Resolve the output path and verify that it remains beneath that root.

  3. Reject symbolic links in both the parent path and final destination.

  4. Refuse to overwrite an existing file unless the user supplies an explicit --force option.

  5. Use exclusive creation for new files:

    python
    with output_path.open("x", encoding="utf-8") as handle:
        handle.write(text + "\n")
    
  6. Use atomic temporary-file replacement when authorized updates are necessary.

  7. Log the resolved destination and obtain confirmation before replacing project state.

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/show_runtime_inventory.mjs:6
Finding

Mandatory Access to Privileged Runtime Configuration

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, line 20; scripts/show_runtime_inventory.mjs, lines 6–14
Vulnerability Type: Excessive access to potentially sensitive runtime configuration
Risk Level: Low

Vulnerable Code

From SKILL.md:

markdown
1. Read `/root/.openclaw/openclaw.json`.

From scripts/show_runtime_inventory.mjs:

javascript
function resolveConfigPath() {
  const arg = process.argv[2];
  if (arg) return path.resolve(arg);
  return '/root/.openclaw/openclaw.json';
}

function loadJson(filePath) {
  const raw = fs.readFileSync(filePath, 'utf8');
  return JSON.parse(raw);
}

Technical Analysis

The Skill instructs the agent to read /root/.openclaw/openclaw.json as part of its normal startup flow. Runtime configuration files can contain provider settings and potentially sensitive values beyond the model inventory needed by this Skill.

The inventory script reads and parses the entire JSON file even though it only needs a limited subset of model metadata. It also accepts any caller-provided path through process.argv[2], without limiting the path to an approved configuration location.

The reviewed implementation only emits:

  • The configuration path.
  • The primary model assigned to the main agent.
  • Provider names and model identifiers, names, and input metadata.

No credentials are explicitly printed, transmitted, or sent over a network. The issue is therefore an unnecessary expansion of the Skill’s read scope and a least-privilege violation, rather than confirmed credential exfiltration.

Attack Path

  1. The Skill is activated and follows its mandatory startup instruction.
  2. The inventory script opens the root OpenClaw configuration and loads its complete contents into process memory.
  3. Alternatively, a caller supplies another readable JSON file as the script’s first argument.
  4. The file is parsed without path, ownership, or schema ...[truncated 1072 chars]
Remediation
View remediation

Remediation Suggestions

  1. Replace direct access to /root/.openclaw/openclaw.json with a dedicated, sanitized runtime-inventory API or generated inventory file.
  2. Expose only the provider and model metadata required for role mapping.
  3. If a file must be used, restrict accepted paths to a configured allowlist and reject arbitrary command-line paths.
  4. Validate the file owner, permissions, type, and schema before parsing it.
  5. Run the inventory reader under a minimally privileged account.
  6. Avoid printing the absolute configuration path unless it is operationally necessary.
  7. Ensure credentials and provider secrets are structurally separated from model inventory metadata.
Vulnerability Patterns
  • 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description presents a full novel-production workflow controller with rich operational behavior: handling project bootstrap, continuation/resumption/repair, model inventory presentation, explicit user mapping of roles to models, persistence of that mapping, and active use of mapped roles to drive stage work via spawned sessions. The supplied code does something much narrower: it builds a JSON scaffold representing initial project/state data and writes it to stdout or a file. While some fields in the generated JSON align with the declared domain (project metadata, execution mode, model-role placeholders, recovery checkpoint, canon/planning/style/memory sections), these are only static placeholders. There is no implementation of the core declared behaviors such as prompting users, routing work, spawning agents, generating canon, drafting chapters, reviewing consistency, or using model mappings operationally. Therefore the code’s actual behavior is a small support utility for initializing state, not the described end-to-end workflow skill, which is a material mismatch in primary purpose and capabilities.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for a full novel-production workflow/orchestrator with interactive project management, drafting, review, recovery, and multi-session role/model coordination. The supplied code does none of that. It only discovers existing project folders by scanning a workspace/novel directory for project.json files, reads project/state metadata, and prints a summary JSON report. While project discovery could be a supporting utility inside a larger system, this code chunk by itself has a materially different primary purpose from the declared skill behavior and lacks the core declared capabilities.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents an end-to-end novel-production workflow with interactive control flow, model-role assignment requirements, multi-agent execution, stateful continuation/recovery, and substantive authoring/review capabilities. The supplied code only scaffolds a filesystem project structure and seeds initial JSON/Markdown placeholders plus optional resume metadata and a models map. While this scaffold behavior is consistent with one small part of the declared system (creating the project shell and persisting basic state), it does not implement the majority of the described functionality, especially the core orchestration and content-production behaviors. Therefore the description materially overstates what this code chunk actually does.

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 · SKILL.md (reported line 32)May include surrounding context.

md
10. Do not start canon until title, genre/audience, target length, taboo list, premise, execution mode, and role mapping are confirmed.
11. Do not fan out dependent canon stages blindly. Worldbuilding must be stable before character sheets; worldbuilding + characters must be stable before the full outline; the full outline must be stable before each 10-chapter batch outline; batch outline must be stable before writer; writer before reviewer; reviewer before orchestrator.

### Inventory display rule
- Always present the model list before asking for role mapping.
- Always present the role list before asking for role mapping.
- Always prefill a recommended mapping draft based on the current inventory.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
81% confidence
Finding

The skill instructs the agent to read environment/config files and to persist project state across multiple files, but the manifest does not declare any explicit tool scope or allowed-tools boundary. That creates unnecessary ambiguity around what file and environment access the skill expects, which can lead to over-broad access or accidental use of sensitive local resources during execution.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The manifest description says to use the skill when the user asks to 'generate canon or chapters, review for consistency, or maintain a long-running fiction project,' which is broad and lacks clear exclusion boundaries. These phrases could overlap with ordinary writing-assistance requests that are not meant to invoke this specific serialized-novel workflow.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest says that in multi-agent mode the skill must first surface available model inventory and the role list, then ask for an explicit role→model mapping before any canon work. But L022-L023 require persisting the project brief before any role or model discussion, and later sections repeat that ordering, creating an active contradiction in the documented operating contract.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The example '帮我检查这个小说技能是否适合继续写' is not a concrete, domain-unique command for starting the workflow and could match casual discussion about whether a skill is suitable. It does not clearly distinguish activation from ordinary conversation about writing tools.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The example user intents are written exclusively in Chinese, which signals that the skill is oriented toward a specific language/locale. The file does not state that users may interact in other languages or that Chinese is a required, justified constraint, so this creates a natural-language policy concern.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The runbook explicitly instructs the agent to read a global file at /root/.openclaw/openclaw.json to enumerate available models. Accessing a user- or host-level configuration file expands the skill's data access beyond project-local state and can expose unrelated secrets, configuration details, or environment metadata if that file contains more than just a harmless model list. In this skill's context, model selection can be handled by a trusted orchestration interface, so direct filesystem reads of global config are unnecessary and increase risk.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

L027-L029 state that agents_list is internal-only and should not be shown as inventory, while L176 later says to verify which agent IDs are spawnable and not use agents_list as a proxy for model inventory or spawn capability. These instructions create conflicting operational guidance about whether and how agents_list should inform execution decisions, increasing the chance of behavior that diverges from stated intent.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.