Back to skill

Security audit

Deeptutor

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says: it reads EPUB books and saves study notes to Obsidian, but users should be aware it creates and updates local note files automatically.

Install only if you are comfortable with the skill reading local EPUB files and creating or updating Markdown files in /data/workspace/obsidian-vault/Books. Review saves before running on untrusted EPUBs, keep the vault protected from untrusted writers, and avoid treating instructions found inside book text as commands for the agent.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Warning
Location
scripts/epub_chapter_reader.py:30
Finding

Untrusted EPUB Content Is Passed to the Agent Without Prompt-Injection Isolation

Content
View full analysis

Vulnerability Details

File Location: scripts/epub_chapter_reader.py:30-32, 65; SKILL.md:146-157
Vulnerability Type: Indirect prompt injection through attacker-controlled document content
Risk Level: Medium

Vulnerable Code

python
html = item.get_content()
soup = BeautifulSoup(html, 'html.parser')
text = soup.get_text('\n', strip=True)
python
return f"# {ch['title']}\n\n{text}"

The skill then directs the agent to consume the extracted content:

markdown
When an EPUB exists, always read from it directly — never from search or memory.
markdown
Read the full text before writing. For short chapters, use what's there. For long chapters, 14000 chars is enough to work with.

Technical Analysis

An EPUB is a container of attacker-controlled HTML documents. The reader converts each document into plain text and emits it directly for use by the AI agent. It does not remove hidden HTML elements such as script, style, or elements concealed with CSS before calling get_text(). It also does not mark the extracted text as an untrusted data boundary.

The skill instructions require the agent to read this output but do not instruct it to disregard operational commands found inside the book. Consequently, text embedded in an EPUB may be interpreted as agent instructions rather than quoted source material.

This is an indirect prompt-injection weakness. Exploitation depends on the agent following the malicious document text and on the tools or context available during that session.

Attack Path

  1. An attacker prepares an EPUB containing instruction-like text in a chapter document, potentially inside visually hidden HTML.
  2. The victim provides that EPUB to DeepTutor.
  3. item.get_content() reads the attacker-controlled HTML.
  4. BeautifulSoup.get_text() includes the instruction-like content in the extracted chapter text.
  5. get_chapter() emits that content without an explicit trust-boundary marker.
  6. The agen ...[truncated 835 chars]
Remediation
View remediation

Remediation Suggestions

  1. Treat all EPUB content as untrusted data and state this explicitly in SKILL.md.
  2. Instruct the agent never to follow commands, policies, tool requests, destination changes, or requests for secrets contained in extracted book text.
  3. Place extracted text inside clear begin/end delimiters and label it as quoted source material.
  4. Remove non-content and hidden elements before extracting text, including script, style, noscript, and elements hidden through common attributes or CSS.
  5. Avoid presenting EPUB-derived headings or text in a form that resembles privileged system or skill instructions.
  6. Require explicit user confirmation before any tool operation requested or suggested by document content.
  7. Apply host-level tool allowlists and least-privilege controls so document text cannot independently authorize file, network, or command operations.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/save_chapter_deepdive.py:32
Finding

Obsidian File Writes Can Follow Pre-Existing Symbolic Links

Content
View full analysis

Vulnerability Details

File Location: scripts/save_chapter_deepdive.py:32-37, 62-83
Vulnerability Type: Symlink-following arbitrary file overwrite
Risk Level: Low

Vulnerable Code

python
def save_deepdive(book: str, chapter: int, title: str, content: str) -> Path:
    book_dir = VAULT / slugify(book)
    book_dir.mkdir(parents=True, exist_ok=True)

    filename = f"chapter-{chapter}-{slugify(title)}.md"
    filepath = book_dir / filename
    filepath.write_text(content, encoding='utf-8')
    print(f"Saved: {filepath}")

The index and all discovered chapter files are also overwritten without checking for symbolic links:

python
index_path = book_dir / 'index.md'
index_path.write_text('\n'.join(lines) + '\n', encoding='utf-8')
print(f"Updated index: {index_path}")

# Add prev/next nav links to each chapter file
for i, ch_path in enumerate(chapters):
    text = ch_path.read_text(encoding='utf-8')

    # Remove old nav block if exists
    text = re.sub(r'\n---\n\n## 章节导航\n.*$', '', text, flags=re.DOTALL)

    nav = ['\n---\n\n## 章节导航\n']
    if i > 0:
        prev = chapters[i - 1]
        nav.append(f'← [[{prev.stem}|上一章]]')
    if i < len(chapters) - 1:
        nxt = chapters[i + 1]
        nav.append(f'→ [[{nxt.stem}|下一章]]')
    nav.append(f'📚 [[index|返回目录]]')

    text += '\n'.join(nav) + '\n'
    ch_path.write_text(text, encoding='utf-8')

Technical Analysis

The script constructs predictable paths under a fixed vault directory and uses Path.write_text() directly. It does not verify whether the book directory, destination chapter, index, or discovered chapter files are symbolic links. It also does not verify that their fully resolved paths remain beneath VAULT.

Normal path slugification prevents straightforward ../ traversal through the book and title arguments, but it does not protect against pre-existing filesystem links. Python follows symbolic links during these reads and writes.

Exploitation requires the att ...[truncated 1456 chars]

Remediation
View remediation

Remediation Suggestions

  1. Resolve VAULT and every destination path, then verify with Path.is_relative_to() that the resolved destination remains beneath the resolved vault.
  2. Inspect each path component with lstat() and reject symbolic links, including the book directory, chapter destination, index, and files returned by glob().
  3. Open destination files using low-level flags that reject symlinks where supported, such as O_NOFOLLOW.
  4. Write to a safely created temporary file in the verified destination directory, flush it, and atomically replace the intended regular file.
  5. Refuse to process files that are not regular files.
  6. Restrict vault permissions so untrusted users and processes cannot create or replace directory entries used by the skill.
  7. Consider making the vault location configurable while still enforcing a canonical allowed root.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents an educational assistant that helps users deeply read texts, provides close-reading/strategy guidance, and auto-saves to Obsidian. The supplied code does none of that. It only reads local EPUB files, extracts chapter-like document sections, lists them, and prints chapter contents or JSON. While chapter extraction could be a supporting component for a reading tutor, this chunk’s actual primary behavior is a standalone EPUB extraction tool, and the key declared features—personalization, tutoring, strategy guidance, and Obsidian autosave—are absent. Therefore the description does not accurately represent this code chunk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The README repeatedly promises automatic saving into an Obsidian vault but does not clearly warn that invoking the skill will modify local files. In an agent or subagent setting, implicit file writes can surprise users, overwrite notes, or cause unintended persistence of extracted book content and model-generated text in a personal knowledge base.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill instructs the agent to read from arbitrary EPUB paths and write notes into an Obsidian vault, but it does not declare any explicit tool scope or permissions boundaries. In an agent environment, undeclared file read/write capability increases the chance of overbroad filesystem access, unexpected writes, or use in contexts where the user did not realize the skill would touch local files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill repeatedly advertises that it 'auto-saves to Obsidian' and gives write commands, but it does not present a prominent warning or explicit consent step before file creation/modification. Automatic persistence to a local vault can expose sensitive reading activity, overwrite notes, or create unwanted files without the user's informed approval.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The instruction 'Write in Chinese unless instructed otherwise' imposes a language default without explicitly obtaining the user's language preference. This is a natural-language policy issue because it forces a locale/language behavior rather than offering a choice or requiring opt-in.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The invocation guidance is very broad ('use this skill when you want...') and does not define clear boundaries for when the agent should or should not activate the skill. In an agentic environment, ambiguous activation language can cause overuse or unintended invocation on user inputs that only loosely match, increasing the chance of unnecessary data access or downstream side effects like file writes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This example explicitly instructs automatic saving to Obsidian without warning the user that the skill may write to local notes or modify stored data. Silent or implicit writes are risky because users may not expect persistence, may expose sensitive reading material, or may overwrite or clutter existing notes.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script injects fixed Chinese text into chapter files for the navigation section, including the heading and link labels. This enforces a specific language in generated user-facing content without opt-in or explanation, which matches the locale-policy violation criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The README's primary invocation examples are written in Chinese and present them as the expected way to call the skill, but the document does not explicitly state that the skill is multilingual or that Chinese is optional. This can be read as imposing a language/locale convention without user opt-in or justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

This invocation example again normalizes saving output to Obsidian as a default side effect without surfacing any warning or confirmation step. Even if the write is low-risk in many cases, it still creates an integrity and privacy issue because user data is being persisted or modified without an explicit checkpoint.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The quiz-mode example bundles content generation with automatic saving, which encourages the agent to perform a write operation as part of a routine workflow without explicit permission. Bundling side effects into common prompts makes accidental data modification more likely and reduces user awareness of persistence behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.