Back to skill

Security audit

Zettelkasten note tool

Security checks for vulnerabilities and agentic risk

Overview

This is a local note-taking skill, but its bundled CLI can run unintended shell commands through editor settings, so it should be reviewed before installation.

Install only if you are comfortable with a local CLI that creates and scans a markdown vault. Avoid using it until the editor launch is fixed to avoid shell parsing, or always run note-creation commands with --no-edit and a trusted ZK_VAULT path. Review any proposed ~/.bashrc or ~/.zshrc change before applying it.

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/zk.py:164
Finding

Shell Command Injection in Editor Launcher

Content
View full analysis

Vulnerability Details

File Location: scripts/zk.py, lines 164–166
Vulnerability Type: OS command injection through shell interpolation
Risk Level: High

Vulnerable Code

python
def _open_in_editor(path: Path):
    editor = os.environ.get("EDITOR", "nano")
    os.system(f'{editor} "{path}"')

Technical Analysis

The _open_in_editor function constructs a shell command by directly interpolating the EDITOR environment variable and the note path into a string passed to os.system(). Because os.system() invokes a command shell, shell metacharacters in either value may be interpreted as executable syntax.

The EDITOR value is entirely environment-controlled. The note path is also influenced by the ZK_VAULT environment variable or the --vault command-line option. Wrapping the path in double quotes is insufficient because a malicious path can contain embedded quotation marks, command substitutions, or other shell syntax.

The vulnerable function is reached by the new, promote, and moc commands unless the user supplies --no-edit.

Attack Path

  1. An attacker gains the ability to influence the process environment, a wrapper script, shell configuration, automation configuration, or command-line arguments.
  2. The attacker places shell syntax in EDITOR, ZK_VAULT, or --vault. For example, a malicious EDITOR value could append an additional shell command.
  3. The user invokes zk new, zk promote, or zk moc without --no-edit.
  4. The command creates or selects a note and calls _open_in_editor().
  5. _open_in_editor() interpolates the attacker-influenced value into a shell command.
  6. os.system() passes the resulting command to the shell, which executes the injected syntax with the privileges of the user running the CLI.

Impact Assessment

Successful exploitation permits arbitrary local command execution with the current user's privileges. The attacker could read or modify files accessible to that us ...[truncated 310 chars]

Remediation
View remediation

Remediation Suggestions

Avoid invoking a shell. Parse the configured editor command into an argument vector and execute it directly:

python
import shlex
import subprocess

def _open_in_editor(path: Path):
    editor = os.environ.get("EDITOR", "nano")
    command = shlex.split(editor)
    if not command:
        raise ValueError("EDITOR must specify an executable")
    subprocess.run([*command, str(path)], check=False)

Additional hardening measures:

  1. Validate that EDITOR resolves to an expected executable before launching it.
  2. Where flexible editor arguments are unnecessary, use a strict allowlist or require EDITOR to contain only an executable path.
  3. Resolve and validate the vault path before use, particularly when it originates from ZK_VAULT or --vault.
  4. Avoid shell=True, os.system(), and all string-built shell commands for paths or configuration values.
  5. Add regression tests using editor values and vault paths containing spaces, quotes, semicolons, command substitutions, and other shell metacharacters. Verify that these values are passed as literal arguments and never executed as shell syntax.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (9)

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

The code executes a shell command built from the EDITOR environment variable using os.system(), which allows arbitrary shell metacharacters and command chaining. Because EDITOR is fully attacker-controlled in the process environment, a crafted value such as 'vim; malicious_command' would execute unintended commands whenever a note is opened.

Content

Scanner excerpt · scripts/zk.py (reported line 168)May include surrounding context.

python
def _open_in_editor(path: Path):
    editor = os.environ.get("EDITOR", "nano")
    os.system(f'{editor} "{path}"')

# ── Commands ─────────────────────────────────────────────────────────────────

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill instructs delivery and use of a Python CLI that reads environment variables, writes files into a vault, and is installed/run via shell commands, but it declares no explicit tool scope or permissions boundaries. That increases the chance an agent could invoke broader capabilities than the user expects, especially for file writes and shell actions.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 3)May include surrounding context.

md
---
name: znote
description: Create, manage, and operate a Zettelkasten slip-box note system using the zk.py CLI script. Use this skill whenever a user wants to build or use a Zettelkasten note system, manage atomic notes, create fleeting/literature/permanent notes, link notes together, find orphaned notes, generate Maps of Content, or search/browse a note vault. Trigger on phrases like "zettelkasten", "slip box notes", "atomic notes", "permanent notes", "znote", "zk notes", "note vault", "create a note", "link my notes", "find orphaned notes", "map of content", or "manage my notes with zk".
---

# znote: Zettelkasten CLI Note Manager

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases are broad enough to match ordinary note-taking requests like 'create a note' or 'link my notes,' which can cause the skill to activate unexpectedly. That can lead to unintended file creation, vault manipulation, or shell-oriented setup guidance in contexts where the user did not ask for this specific local CLI workflow.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The setup workflow tells the user to copy an executable into ~/.local/bin, initialize a vault, and optionally persist environment configuration, but it does not clearly warn that these steps create and modify local files and may alter shell startup configuration. Users may consent to note help without realizing the skill steers them into persistent local system changes.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
96% confidence
Finding

The instruction to add ZK_VAULT to ~/.bashrc or ~/.zshrc introduces persistence by modifying shell startup files. While common for CLI setup, persistence changes are security-relevant because they survive the session and can normalize future execution context changes without a strong warning or explicit approval.

Content

Scanner excerpt · SKILL.md (reported line 38)May include surrounding context.

bash
chmod +x zk.py
cp zk.py ~/.local/bin/zk        # optional: make globally available
export ZK_VAULT=~/Zettelkasten  # add to ~/.bashrc or ~/.zshrc

Step 3: Initialise vault

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill is described as a note-management tool, but it also launches an arbitrary external program through the shell. In an agent-skill setting this expands the capability boundary from file manipulation to arbitrary process execution, which is materially more dangerous because opening a note can trigger any command configured via EDITOR.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The shell invocation directly incorporates the EDITOR environment variable without validation or escaping, creating a shell command injection risk. Even if the path argument is quoted, the editor portion remains unquoted and shell-parsed, so an attacker can inject additional commands, redirections, or subshells.

Content

No source excerpt is available for this finding.

Tainted flow: 'editor' from os.environ.get (line 167, credential/environment) → os.system (code execution)

Medium
Category
Data Flow
Confidence
98% confidence
Finding

There is a real tainted-data flow from os.environ.get('EDITOR') into os.system(), resulting in command execution under the current user's privileges. Environment variables are not trustworthy input in agent/tooling contexts, especially when a calling wrapper, plugin host, or remote orchestrator can influence them.

Content

Scanner excerpt · scripts/zk.py (reported line 168)May include surrounding context.

python
def _open_in_editor(path: Path):
    editor = os.environ.get("EDITOR", "nano")
    os.system(f'{editor} "{path}"')

# ── Commands ─────────────────────────────────────────────────────────────────

Static analysis

No suspicious patterns detected.