Back to skill

Security audit

Mindgraph

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent knowledge-graph tool, but it tries to affect all Markdown work and contains an unbounded path-handling flaw in its mindskill commands.

Review before installing. This skill is not showing malicious exfiltration or destructive behavior, but it can influence ordinary Markdown edits, index broad workspace notes, and create persistent files. Only use it in workspaces where Obsidian-style wikilinks are desired, and avoid passing untrusted or path-like names to mindskill commands until path validation is added.

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

Error
Location
SKILL.md:3
Finding
Mandatory Session-Wide Behavior Override Through Always-On Skill Instructions<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:3`, `SKILL.md:15-24`, and `SKILL.md:124-127` **Vulnerability Type**: Skill instruction hijacking **Risk Level**: High ### Vulnerable Instructions ```markdown description: Obsidian-style [[wikilink]] knowledge graph and learnable MindSkills for OpenClaw workspaces. Use for ALL of these: (1) Any workspace file read/write — always use [[wikilinks]] for people, projects, tools, concepts. (2) Running learned processes like knockout-test, seo-validator, competitor-analysis. (3) Querying knowledge — "what do I know about X", "show connections to Y". (4) Learning new repeatable processes — "learn a new mindskill called Z". (5) Memory maintenance — finding orphans, dead links, unconnected files. This skill is always active — treat [[wikilinks]] as standard practice in every markdown file you write. ``` ```markdown ## Always-On Rules **Every time you write or edit a markdown file, use `[[wikilinks]]` for:** - People: `[[Alice]]`, `[[Bob]]` - Projects: `[[my-saas]]`, `[[landing-page]]` - Companies/tools: `[[Stripe]]`, `[[Vercel]]`, `[[GitHub]]` - Concepts/frameworks: `[[Knockout Test]]`, `[[B2B SaaS]]` - Other agents/models: `[[Claude Code]]`, `[[Sonnet]]` This is not optional. Links are how knowledge connects. No links = isolated notes = useless. ``` ```markdown When a user's request matches a learned mindskill, proactively suggest it: - "Want me to run the [[Knockout Test]] on that?" - "I have an [[SEO Validator]] mindskill — should I audit that?" - "This looks like a [[Competitor Analysis]] — want the full framework?" ``` ### Technical Analysis The Skill declares itself permanently active and directs the agent to alter every Markdown-writing operation, including operations unrelated to MindGraph. The phrases “Use for ALL,” “always active,” and “This is not optional” attempt to establish global behavioral rules merely by loading the Skill. A properly scoped Skill should affect behavior only when th ...[truncated 2006 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the global-scope language, including “Use for ALL,” “always active,” and “This is not optional.” 2. Apply wikilink formatting only when: - The user explicitly requests MindGraph or Obsidian-compatible output. - The target file is already managed as part of the MindGraph workspace. - The user has explicitly enabled a workspace-level wikilink preference. 3. Replace mandatory language with a scoped instruction, for example: ```markdown When the user explicitly requests MindGraph-compatible Markdown, use wikilinks for relevant entities. Do not modify unrelated Markdown files solely to add wikilinks. ``` 4. Require user confirmation before applying MindGraph conventions to an existing file that does not already use them. 5. Change proactive MindSkill promotion to an opt-in behavior and avoid suggestions when they are not directly relevant to the current task. 6. Clearly separate invocation guidance from session-wide agent policy so loading the Skill does not change unrelated behavior. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/mindgraph.py:449
Finding
Path Traversal in MindSkill Read and Creation Commands<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mindgraph.py:449-509` **Vulnerability Type**: Unvalidated path traversal **Risk Level**: High ### Vulnerable Code The `skill` command constructs a filesystem path directly from the CLI-controlled `name`: ```python def cmd_skill(name): """Show a mindskill's process.""" skill_dir = os.path.join(MINDSKILLS_DIR, name) process_file = os.path.join(skill_dir, 'PROCESS.md') if not os.path.exists(process_file): print(f"❌ MindSkill '{name}' not found") print(f" Available: {', '.join(os.listdir(MINDSKILLS_DIR)) if os.path.exists(MINDSKILLS_DIR) else 'none'}") return with open(process_file, 'r') as f: print(f.read()) ``` The `results` command uses the same unvalidated value to select and read a results directory: ```python def cmd_results(name): """List results for a mindskill.""" results_dir = os.path.join(MINDSKILLS_DIR, name, 'results') if not os.path.exists(results_dir): print(f"No results for '{name}' yet") return files = sorted([f for f in os.listdir(results_dir) if f.endswith('.md')]) if not files: print(f"No results for '{name}' yet") return print(f"📊 Results for [[{name}]] ({len(files)}):\n") for fname in files: filepath = os.path.join(results_dir, fname) with open(filepath, 'r') as f: content = f.read() # Extract verdict from frontmatter verdict = "" subject = fname.replace('.md', '') if content.startswith('---'): end = content.find('---', 3) if end != -1: fm = content[3:end] for line in fm.split('\n'): if line.startswith('verdict:'): verdict = line.split(':', 1)[1].strip() if line.startswith('subject:'): subject = line.split(':', 1)[1].strip() ...[truncated 4392 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict MindSkill names to a conservative slug format: ```python SKILL_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,63}$") def validate_skill_name(name): if not SKILL_NAME_RE.fullmatch(name): raise ValueError("Invalid MindSkill name") return name ``` 2. Resolve and verify every generated path before accessing the filesystem: ```python from pathlib import Path MINDSKILLS_PATH = Path(MINDSKILLS_DIR).resolve() def safe_skill_dir(name): validate_skill_name(name) candidate = (MINDSKILLS_PATH / name).resolve() if not candidate.is_relative_to(MINDSKILLS_PATH): raise ValueError("MindSkill path escapes the configured directory") return candidate ``` 3. Use the validated helper consistently in `cmd_skill`, `cmd_results`, and `cmd_learn`. 4. Explicitly reject absolute paths, path separators, `.` components, and `..` components even if canonical containment checks are also implemented. 5. Before opening result files, resolve each file and verify that it remains beneath the validated `results` directory. 6. Consider rejecting symbolic links or opening files with safe no-follow semantics where supported, particularly if the MindSkill directory can be modified by another user. 7. Create files with explicit UTF-8 encoding and restrictive permissions where sensitive workspace data may be involved. 8. Add automated tests covering: - `../` traversal. - Absolute POSIX paths. - Platform-specific path separators. - Symlink escapes. - Valid slug names. - Attempts to create content outside `MINDSKILLS_DIR`. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • 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
Findings (11)

Vague Triggers

High
Confidence
97% confidence
Finding
Mandating wikilinks as 'standard practice in every markdown file' is an ambiguous global instruction with no directory, project, or task boundaries. This can silently alter unrelated documentation, introduce formatting changes where they are inappropriate, and cause the agent to prioritize skill-specific conventions over the user's immediate intent or repository norms.

Vague Triggers

Medium
Confidence
94% confidence
Finding
Mandating wikilinks as 'standard practice in every markdown file' is an ambiguous global instruction with no directory, project, or task boundaries. This can silently alter unrelated documentation, introduce formatting changes where they are inappropriate, and cause the agent to prioritize skill-specific conventions over the user's immediate intent or repository norms.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill instructs the agent to save result files under the workspace and rebuild an index, but does not prominently warn that these steps perform writes and may modify repository state. In practice, this can lead to non-obvious persistence, accidental file creation, and workspace-wide index churn without explicit user awareness or consent.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The listed triggers include generic phrases such as "Should I build X?", "Validate this idea", and "Is this worth building?" that could easily occur in ordinary conversation outside a deliberate invocation context. The file does not provide narrowing constraints, exclusions, or negative examples to clarify when the skill should and should not activate.

Vague Triggers

Medium
Confidence
95% confidence
Finding
Line L20 says the process is encoded and 'just say "knockout test this" and it executes.' For a markdown file, this is an invocation description, and the phrase is broad enough to be ambiguous without constraints, scope, or negative examples. The document does not clarify when this trigger should or should not activate.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script writes a graph index to `mindgraph.json` in the workspace, and this behavior is not accompanied by any confirmation prompt or explicit warning before the write occurs. Although the operation is central to the `index` command, the file write itself can modify user workspace state and should be clearly disclosed at the point of action or in surrounding documentation/comments.

Tainted flow: 'GRAPH_FILE' from os.environ.get (line 16, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
'linkCount': sum(len(n['outLinks']) for n in nodes.values())
    }
    
    with open(GRAPH_FILE, 'w') as f:
        json.dump(graph, f, indent=2)
    
    file_nodes = sum(1 for n in nodes.values() if n['file'])
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.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The top-level docstring describes the script as an Obsidian-style wikilink graph that indexes, queries, and traverses markdown links. Starting at L407, the code adds a separate capability for creating, listing, and reading 'mindskills' and their results, including writing new PROCESS.md files, which is beyond the stated graph-focused behavior.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Creating directories and files without confirmation becomes security-relevant here because the destination path is derived from unvalidated user input. In the context of cmd_learn, an attacker-controlled or mistaken name can cause unintended filesystem writes outside the intended skill area, making the file-creation behavior materially dangerous.

Tainted flow: 'process_file' from os.environ.get (line 508, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
os.makedirs(os.path.join(skill_dir, 'results'), exist_ok=True)
    
    process_file = os.path.join(skill_dir, 'PROCESS.md')
    with open(process_file, 'w') as f:
        f.write(f"""# [[{name.replace('-', ' ').title()}]]

<!-- PURPOSE: What this process does and when to use it -->
Confidence
98% confidence
Finding
The learn command uses the unvalidated user-supplied name to construct skill_dir/process_file and then writes PROCESS.md. Because name is passed directly into os.path.join without sanitization, path traversal values such as '../../somewhere' can escape the intended mindskills directory and create or overwrite arbitrary files accessible to the current user.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The docstring says 'Find links pointing to nothing (no file, no other references),' which implies zero references. The actual filter requires `len(n.get('inLinks', [])) == 1`, meaning the node has one reference, so the documentation actively misstates the behavior of the command.

Static analysis

No suspicious patterns detected.