Back to skill

Security audit

Reading Tracker

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent reading tracker, but its script can write note files outside the intended reading folder when given a crafted book title.

Review this skill before installing. It keeps local reading notes, quotes, reflections, progress, and reminder data under the user's reading workspace, and it may surface that information in scheduled reviews. Do not use untrusted or unusual book titles until the filename/path handling is fixed, because crafted titles can cause writes outside the intended notes folder.

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/reading_cli.py:63
Finding

Path Traversal and Arbitrary File Overwrite Through Unsanitized Book Titles

Content
View full analysis

Vulnerability Details

File Location: scripts/reading_cli.py, lines 63–85; related reuse at lines 134–148 and 208–219
Vulnerability Type: Path traversal and arbitrary file write
Risk Level: High

Vulnerable Code

python
month = datetime.now().strftime("%Y-%m")
file_name = f"{title}-{month}.md"
file_path = BOOKS_DIR / file_name

# Create note file
content = f"""# 《{title}》

## Metadata
- Author: {author or "(待填写)"}
- Started: {today}
- Finished:
- Progress: 0%
- Rating:

## Key Outputs
- One-sentence summary:
- Mindset shift:
- Action plan:

## Quotes and Thoughts

## Review Log
"""
file_path.write_text(content, encoding="utf-8")

The generated path is subsequently stored and trusted during quote and completion operations:

python
file_path = Path(book["filePath"])
if file_path.exists():
    content = file_path.read_text(encoding="utf-8")
    # ...
    file_path.write_text(content, encoding="utf-8")

Technical Analysis

The title value originates from a command-line argument and is embedded directly into file_name without validation or canonicalization. Path components such as ../, platform-specific path separators, and absolute path prefixes are therefore interpreted by pathlib as filesystem navigation rather than as literal title characters.

Joining BOOKS_DIR with such an attacker-controlled path does not ensure that the result remains inside the intended books directory. A crafted title can cause write_text() to create or overwrite a file elsewhere in the filesystem, provided the process user can write to the target location. The automatically appended -YYYY-MM.md suffix limits the exact target filename but does not prevent escape from BOOKS_DIR.

The resulting path is persisted in library.json. Later calls to add_quote() and save_finish_answers() reconstruct a Path from this stored value and modify the referenced file withou ...[truncated 1775 chars]

Remediation
View remediation

Remediation Suggestions

  1. Use trusted identifiers for filenames. Generate note filenames from the existing UUID-based book_id rather than from the user-supplied title:

    python
    file_name = f"{book_id}-{month}.md"
    

    Preserve the original title only inside the note content and library metadata.

  2. Apply strict title or slug validation. If human-readable filenames are required, convert titles to a restricted slug containing only an explicitly permitted character set. Reject path separators, absolute paths, . and .. components, null bytes, and control characters.

  3. Enforce containment after canonicalization. Resolve both the base directory and candidate path, then verify that the candidate remains beneath BOOKS_DIR before any read or write:

    python
    base = BOOKS_DIR.resolve()
    candidate = (base / file_name).resolve()
    
    if candidate.parent != base:
        raise ValueError("Invalid book title or note path")
    

    If nested directories are intentionally supported, use candidate.is_relative_to(base) on supported Python versions instead of requiring the immediate parent to equal base.

  4. Revalidate persisted paths. Before using book["filePath"] in add_quote(), save_finish_answers(), or report generation, canonicalize it and enforce the same containment rule. Prefer storing a trusted book ID or relative filename rather than an absolute path.

  5. Prevent unintended overwrites. Create new note files using exclusive creation semantics where appropriate, and define explicit collision handling rather than silently replacing an existing file.

  6. Add regression tests. Test absolute paths, repeated ../ traversal, Windows separators and drive prefixes, symbolic-link escape scenarios, control characters, and ordinary Unicode book titles.

Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (10)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill clearly describes creating and updating files under workspace/reading, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an authorization ambiguity where file read/write behavior may be broader than users expect, increasing the risk of unintended file access or modification if the runtime grants default filesystem capabilities.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The trigger phrase "复习" is generic and likely to appear in normal conversation unrelated to this skill. An overly broad trigger can activate the skill unexpectedly, causing unintended reads/writes, report generation, or reminder-related actions from ordinary user text.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The workflow states that the skill creates note files, saves quotes and reflections, and updates book-tracking data automatically, but it does not clearly warn users that these writes happen in workspace storage. This undermines informed consent and can lead to accidental persistence of personal reading habits, notes, and reflections that may be more sensitive than the user realizes.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This markdown file presents all user-facing prompts exclusively in Chinese, which can amount to a language/locale policy violation when no opt-in, alternative language, or justification is provided. The content does not indicate that the skill is intended only for Chinese-speaking users or that the user selected this language.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The module description explicitly presents the tool as a Chinese-language system, and all user-facing prompts/help text in the file are only in Chinese. For a general-purpose reading tracker, this is a natural-language locale constraint without user opt-in or justification, which matches the policy-violation criteria.

Content

No source excerpt is available for this finding.

Tainted flow: 'content' from pathlib.Path.read_text (line 147, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/reading_cli.py (reported line 148)May include surrounding context.

python
"""
        # 插入到"书摘与思考"章节后
        content = content.replace("## 书摘与思考\n", f"## 书摘与思考\n{entry}")
        file_path.write_text(content, encoding="utf-8")

    # 设置复习提醒
    schedule = load_schedule()

Tainted flow: 'content' from pathlib.Path.read_text (line 218, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/reading_cli.py (reported line 219)May include surrounding context.

python
if rating:
                    stars = "⭐" * rating
                    content = content.replace("- 评分:\n", f"- 评分:{stars}\n")
                file_path.write_text(content, encoding="utf-8")

            # 设置长期复习提醒
            schedule = load_schedule()

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
71% confidence
Finding

The primary description and trigger phrases are presented in Chinese, which may steer activation and usage toward a single language by default. Although English translations are included elsewhere, the file does not explicitly state that users may choose their preferred interaction language.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The cron-based review flow uses stored reading data to generate reminder-like prompts, but the skill does not clearly disclose this ongoing background behavior. Even if the data is local, scheduled resurfacing of personal reading history can surprise users and expose private interests or annotations in shared environments.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The template uses mandatory language ('必须' / 'must') for the required questions and is primarily structured around Chinese with English translations, which can be interpreted as enforcing a particular language/formatting expectation. There is no explicit statement that users may choose their preferred language for responses.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.