Back to skill

Security audit

Obsidian To Expertpack

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it advertises, but its converter automatically copies the hidden Obsidian configuration directory in a way that can include active plugins or follow symlinks to files outside the vault.

Install only if you will convert vaults you trust and can inspect. Before running it, do a dry run, review the vault's .obsidian folder, avoid converting untrusted shared vaults, and do not share or index the output until you confirm it does not contain plugin data, workspace state, secrets, or symlink-copied files from outside the vault. Be deliberate before adding the converted pack to global OpenClaw memory search, especially for personal or work notes.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/convert.py:406
Finding
Vault-Boundary Bypass Through Unrestricted .obsidian Copy<![CDATA[ ## Vulnerability Details **File Location**: `scripts/convert.py:406-412` **Vulnerability Type**: Symlink traversal and unsafe copying of active Obsidian content **Risk Level**: High ```python # Copy .obsidian config directory as-is (not processed as markdown content above). # This gives the output pack immediate Obsidian compatibility — open the output # folder in Obsidian and it inherits all plugins, themes, and Dataview settings. obsidian_src = vault_path / '.obsidian' if obsidian_src.exists(): shutil.copytree(obsidian_src, output_path / '.obsidian') print('Copied .obsidian config.') ``` ### Technical Analysis The converter recursively copies the source vault's entire `.obsidian` directory using `shutil.copytree()` without inspecting its entries or enforcing a source-directory boundary. By default, `shutil.copytree()` follows symbolic links rather than preserving them. A malicious or untrusted vault can therefore contain a symbolic link under `.obsidian` that points to a file or directory outside the vault. During conversion, the linked content is read with the converter process's privileges and copied into the generated ExpertPack. The operation also copies active Obsidian components, including potential `.obsidian/plugins/*/main.js` files, plugin data, themes, snippets, and workspace state. This exceeds the documented description of copying configuration. If the generated vault is subsequently opened under conditions where inherited community plugins are trusted and enabled, attacker-supplied plugin code may execute in the user's Obsidian environment. No automatic network transmission is present in the reviewed project. The direct vulnerability is unauthorized local-file inclusion in the output; possible plugin execution is a secondary consequence requiring the user to open and trust the generated vault. ### Attack Path 1. An attacker prepares or modifies an Obsidian vault sup ...[truncated 1707 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not copy `.obsidian/` by default. Add an explicit opt-in option such as `--copy-obsidian-config`. 2. Copy only a documented allowlist of inert configuration files instead of recursively copying the directory. 3. Exclude active or sensitive content, including: - `.obsidian/plugins/` - Plugin data directories - `.obsidian/themes/` - `.obsidian/snippets/` - Workspace and session-state files 4. Reject symbolic links at every level. Use `os.lstat()` or `Path.is_symlink()` before processing each entry. 5. Resolve every candidate source path and verify that it remains beneath the expected `.obsidian` root: ```python obsidian_root = (vault_path / '.obsidian').resolve(strict=True) def is_within_root(candidate): resolved = candidate.resolve(strict=True) try: resolved.relative_to(obsidian_root) return True except ValueError: return False ``` 6. Perform the boundary check immediately before opening each source file to reduce time-of-check/time-of-use exposure. 7. Copy regular files individually with size limits and reject device files, sockets, FIFOs, and other non-regular filesystem entries. 8. Warn users that opening an untrusted converted vault in Obsidian can activate inherited plugins or other active configuration. 9. Add automated tests covering file symlinks, directory symlinks, a symlinked `.obsidian` root, nested links, broken links, and links targeting locations outside the vault. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • 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 (6)

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill instructs the agent to read from an arbitrary Obsidian vault and write a converted copy, but it declares no explicit tool scope or path restrictions. That creates a capability/authorization gap where an agent may access broader filesystem locations than the user intended, especially because the example uses unrestricted absolute paths and home-directory output.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The manifest description declares triggers such as 'convert obsidian', 'make my vault ai ready', and 'obsidian rag', which are broad enough to match a variety of normal user requests without clearly delimiting when this specific skill should or should not activate. The file does not provide exclusion conditions or negative examples to narrow invocation scope.

Session Persistence

Medium
Category
Rogue Agent
Content
1. **Lead summaries** — add a 1-3 sentence blockquote at the top of the 5-10 most important files
2. **Glossary** — populate `glossary.md` with domain-specific terms (this is Tier 1 — always loaded)
3. **Propositions** — create `propositions/` with atomic factual statements extracted from high-EK files
4. **EK triage** — identify low-EK files (general knowledge) and compress or remove them
5. **File size** — split files >3KB on `##` header boundaries
Confidence
88% confidence
Finding
The skill explicitly encourages creating persistent derived knowledge artifacts such as glossary entries and a propositions directory, and then configuring OpenClaw to load the converted pack into memory search for future sessions. If the source vault contains sensitive personal, proprietary, or regulated content, this increases the risk of long-lived exposure, unintended retrieval in later sessions, and propagation of data the user may not realize is being retained.

Session Persistence

Medium
Category
Rogue Agent
Content
The script merges both into frontmatter `tags:`. Review the merged tags after conversion — Obsidian `#nested/tags` are converted to `nested-tags` (slash → hyphen).

### Dataview
Dataview query blocks and inline expressions are stripped entirely. They are computed views, not knowledge. If a Dataview block surfaces important aggregated information, manually write that as static content before converting.

### Daily Notes
Daily note files (typically in `daily/` or `journal/`) are assigned type `journal` and prefix `meta-`. These tend to be low-EK but high personal-knowledge for person packs. Keep in Tier 2 (searchable), not Tier 1.
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The script copies the source vault's entire .obsidian directory into the generated pack, which can transfer plugin settings, workspace state, community-plugin configuration, and other active client behavior metadata unrelated to content conversion. In the context of an agent-ready packaging tool, this expands scope from content transformation to behavior/configuration propagation, increasing the chance that unsafe or privacy-sensitive Obsidian settings ride along into a new environment.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
Preserving plugin and theme configuration is not necessary to make notes RAG-ready or convert them into an ExpertPack, yet it can carry forward enabled community plugins and local UI/workspace behavior into the output vault. That makes the generated artifact do more than advertised and can expose users to risky plugin execution paths or leakage of local configuration details when the output is later opened in Obsidian.

Static analysis

No suspicious patterns detected.