Back to skill

Security audit

siyuan-export

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent SiYuan-to-Word export tool, but it uses a private SiYuan token with raw SQL queries built from user-controlled arguments, creating a real review concern for unintended note access.

Install only if you trust the agents and prompts that will invoke it, because crafted search text or document IDs could cause broader SiYuan SQL queries than intended. Prefer using exact trusted document IDs/paths, keep the SiYuan token limited if possible, and review or patch SQL handling before using it on sensitive notebooks.

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/siyuan_export.py:124
Finding

SQL Injection in Authenticated SiYuan Document Queries

Content
View full analysis
list[dict]: """按文档名搜索""" stmt = f"SELECT id, content, hpath, created, updated FROM blocks WHERE content LIKE '%{keyword}%' AND type = 'd' ORDER BY updated DESC LIMIT 20" result = api_call("/api/query/sql", {"stmt": stmt}, config) if result.get("code") == 0: return result.get("data", []) return [] def get_child_docs(doc_id: str, config: dict) -> list[dict]: """获取子文档列表""" doc_info = api_call("/api/query/sql", {"stmt": f"SELECT hpath FROM blocks WHERE id = '{doc_id}' AND type = 'd'"}, config) if doc_info.get("code") != 0 or not doc_info.get("data"): return [] parent_hpath = doc_info["data"][0]["hpath"] escaped_hpath = parent_hpath.replace("'", "''") result = api_call("/api/query/sql", {"stmt": f"SELECT id, content FROM blocks WHERE hpath LIKE '{escaped_hpath}/%' AND type = 'd' ORDER BY hpath"}, config) ``` ### Technical Analysis The `keyword` and `doc_id` values originate from the `--search` and `--doc-id` command-line arguments. They are inserted directly into SQL statements using Python formatted strings and then submitted to the authenticated SiYuan `/api/query/sql` endpoint. No SQL escaping, parameter binding, or strict identifier validation is applied to either input: - `keyword` is placed inside a `LIKE '%...%'` expression. - `doc_id` is placed inside a quoted equality expression. - Although `parent_hpath` is escaped before the final query, it is obtained using the already-injectable `doc_id` query. The later escaping therefore does not eliminate the initial injection point. An attacker who can influence the arguments used to invoke the Skill can close the SQL string literal and modify the query structure. Depending on restr ...[truncated 2254 chars]
Remediation
View remediation
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill clearly requires environment variables, local configuration files, and network access to a local SiYuan API, but it does not declare any explicit tool scope or permissions. That creates a trust and review gap: an agent may invoke file, env, or network capabilities that users did not expect, increasing the risk of unintended token exposure or local service access.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest description and main documentation force a specific language/locale for users, which matches the policy category for language or locale violations. There is no indication that the user can choose another language or that the Chinese-only presentation is region-specific or otherwise justified.

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 docstring, usage instructions, and later user-facing CLI text are presented only in Chinese, which effectively forces a specific language for interaction. The policy requires flagging language or locale constraints unless the skill offers user choice or clearly documents a justified regional limitation.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The docstring states that searching by document name will automatically execute when there is one match, but in the actual search flow a single match with child documents may still stop and return a scope-selection prompt instead of exporting. This is an intent/documentation mismatch because the documented behavior is more automatic than the code really is.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The documentation explicitly says that when multiple matches occur, the tool returns a document-list JSON response without waiting for input. In practice, the code emits formatted console text and a selection prompt message, not JSON, before exiting.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.install_untrusted_source

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
config.example.json:2