Back to skill

Security audit

Multimedia To Obsidian

Security checks for vulnerabilities and agentic risk

Overview

This skill has a plausible Obsidian import purpose, but it uploads document images to external AI services and has unsafe path and cleanup handling that users should review before installing.

Review this skill carefully before installing. Use it only on documents you are comfortable sending to the selected AI provider, avoid confidential or regulated material unless you have approval, keep MINIMAX_API_HOST at a trusted HTTPS provider, and do not pass category values containing slashes, absolute paths, or .. traversal. Prefer running it in an isolated environment with a dedicated test Obsidian vault and limited-scope API keys.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/media_to_obsidian.py:48
Finding
Configurable MiniMax endpoint can receive confidential images and API credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/media_to_obsidian.py:48-64` **Related Documentation**: `SKILL.md:24-26` **Vulnerability Type**: Unrestricted transmission of sensitive data to a configurable network endpoint **Risk Level**: High ### Vulnerable Code ```python def understand_minimax(image_data, media_type, prompt): """MiniMax VLM""" API_KEY = os.environ.get("MINIMAX_API_KEY", "") API_HOST = os.environ.get("MINIMAX_API_HOST", "https://api.minimaxi.com") if not API_KEY: return "[错误] 请设置 MINIMAX_API_KEY" image_url = f"data:{media_type};base64,{image_data}" payload = {"prompt": prompt, "image_url": image_url} cmd = [ "curl", "-s", "--max-time", "30", f"{API_HOST}/v1/coding_plan/vlm", "-H", f"Authorization: Bearer {API_KEY}", "-H", "Content-Type: application/json", "-d", json.dumps(payload) ] ``` The documentation explicitly encourages users to configure the host: ```bash export MINIMAX_API_KEY="your-key" export MINIMAX_API_HOST="https://api.minimaxi.com" ``` ### Technical Analysis The script Base64-encodes complete extracted images and submits them to the URL derived from `MINIMAX_API_HOST`. Base64 is transport encoding and provides no confidentiality. Although sending images to a multimodal provider is necessary for the declared functionality, allowing the destination host to be changed without validation exceeds the minimum network privilege needed to communicate with the official provider. The code does not: - Require the HTTPS scheme. - Restrict the hostname to an approved MiniMax domain. - Reject URLs containing embedded credentials or unexpected path components. - Warn the user when a non-default destination is selected. - Establish an explicit policy for redirects. Consequently, control over the process environment is sufficient to redirect both document contents and the MiniMax bearer credential to an arbitrary endpoint. ### ...[truncated 1212 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove arbitrary host configurability if only the official MiniMax service is supported. 2. If custom hosts are required, parse the value with a URL parser and enforce: - HTTPS only. - An explicit allowlist of exact hostnames. - No embedded username or password. - No unexpected ports unless specifically approved. - No cross-origin redirects. 3. Display the resolved destination before uploading data and require explicit user consent for any non-default provider. 4. Document that source images are uploaded to a third party and may contain sensitive information. 5. Add a local-only or redaction workflow for confidential documents. 6. Separate endpoint configuration from ambient environment variables where possible, using an explicit command-line option or trusted configuration file with restrictive permissions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/media_to_obsidian.py:54
Finding
MiniMax API key is exposed through the curl process argument list<![CDATA[ ## Vulnerability Details **File Location**: `scripts/media_to_obsidian.py:54-64` **Vulnerability Type**: Secret exposure through subprocess arguments **Risk Level**: Medium ### Vulnerable Code ```python image_url = f"data:{media_type};base64,{image_data}" payload = {"prompt": prompt, "image_url": image_url} cmd = [ "curl", "-s", "--max-time", "30", f"{API_HOST}/v1/coding_plan/vlm", "-H", f"Authorization: Bearer {API_KEY}", "-H", "Content-Type: application/json", "-d", json.dumps(payload) ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=35) ``` ### Technical Analysis The MiniMax API key is interpolated into the `curl` command's argument vector as an HTTP authorization header. On systems where process arguments are visible to other users, administrative monitoring agents, crash collectors, or diagnostic utilities, the bearer token can be captured while `curl` is running. The payload itself is also passed through the command-line argument vector using `-d`. This may expose the Base64-encoded image to process-inspection tooling and can create operating-system argument-length failures for large images. The primary credential risk is the authorization header. The OpenAI and Anthropic implementations use an in-process HTTP client and therefore do not expose their authorization headers through a child process argument list. ### Attack Path 1. The victim invokes the Skill with the MiniMax model. 2. The script spawns `curl` for each extracted image. 3. The child process argument vector contains `Authorization: Bearer <API_KEY>`. 4. During the request, a local user or monitoring process with sufficient process-inspection access reads the command line. 5. The observer extracts the bearer token and uses it to make unauthorized MiniMax API requests. ### Impact Assessment Successful exploitation discloses the MiniMax API credential. The attacker can exercise the per ...[truncated 334 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the `curl` subprocess with an in-process HTTP client such as `requests` or `urllib.request`. 2. Supply the API key only through the HTTP client's header interface. 3. Never place secrets in command-line arguments, URLs, logs, exception messages, or generated Markdown. 4. Send the JSON body directly through the HTTP client rather than through `curl -d`. 5. Configure explicit connection and read timeouts and disable unsafe cross-host authorization forwarding. 6. Add tests confirming that API keys do not appear in spawned process arguments or diagnostic output. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/media_to_obsidian.py:187
Finding
Unvalidated category path permits writes and recursive deletion outside the Obsidian vault<![CDATA[ ## Vulnerability Details **File Location**: `scripts/media_to_obsidian.py:187-193, 236-238, 250-254` **Vulnerability Type**: Path traversal and unsafe recursive deletion **Risk Level**: High ### Vulnerable Code ```python def process_single_file(file_path, output_dir, category, model): """处理单个文件""" filename = os.path.basename(file_path) name_without_ext = os.path.splitext(filename)[0] # 创建输出目录 if category: target_dir = os.path.join(output_dir, category) else: target_dir = os.path.join(output_dir, "导入") os.makedirs(target_dir, exist_ok=True) output_md = os.path.join(target_dir, f"{name_without_ext}.md") temp_dir = os.path.join(target_dir, f"{name_without_ext}_temp") ``` The derived path is later used for recursive deletion: ```python # 清理临时目录 if os.path.exists(temp_dir): subprocess.run(["rm", "-rf", temp_dir], capture_output=True) ``` The category is taken directly from command-line input: ```python parser.add_argument("--category", default="", help="分类目录名") args = parser.parse_args() source = args.source output = args.output file_format = args.format model = args.model category = args.category ``` ### Technical Analysis `category` is joined to `output_dir` without canonicalization or containment validation. Python's `os.path.join()` does not enforce sandboxing: - An absolute `category` can discard the intended output prefix. - A category containing `../` can traverse outside the vault. - Existing symbolic links in the output path can redirect operations elsewhere. The escaped path controls the Markdown output location and the temporary directory location. After processing, the script passes the derived temporary path to `rm -rf`. Although `subprocess.run()` uses an argument list and therefore avoids shell metacharacter injection, it does not prevent deletion of an unintended filesystem path. ### Attack Path 1. An attacker infl ...[truncated 1458 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject absolute category paths and categories containing path separators or traversal components. 2. Resolve and validate the target path before creating or writing files: ```python from pathlib import Path vault = Path(output_dir).resolve() category_name = category or "导入" if Path(category_name).is_absolute() or Path(category_name).name != category_name: raise ValueError("Category must be a single directory name") target = (vault / category_name).resolve() if target.parent != vault: raise ValueError("Category escapes the Obsidian vault") ``` 3. Resolve every deletion target and verify it remains strictly beneath the validated vault before deletion. 4. Replace the external `rm -rf` command with `shutil.rmtree()` after performing containment and symlink checks. 5. Use a safely generated temporary directory, such as `tempfile.TemporaryDirectory(dir=validated_target)`, rather than a predictable source-derived name. 6. Refuse to clean up a path that is a symbolic link or that resolves outside the approved temporary root. 7. Add regression tests covering absolute paths, `../`, nested traversal, symlink escapes, and unusual source basenames. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:18
Finding
Third-party dependencies are installed without version or integrity controls<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:18-21` **Related Runtime Imports**: `scripts/media_to_obsidian.py:74,102` **Vulnerability Type**: Unpinned and incomplete dependency specification **Risk Level**: Medium ### Vulnerable Code ```bash brew install pandoc poppler pip install python-pptx pillow ``` The script additionally performs undeclared runtime imports: ```python import requests ``` This import appears in both the OpenAI and Anthropic provider implementations. ### Technical Analysis The installation instructions request mutable latest versions of `python-pptx`, `pillow`, `pandoc`, and `poppler` without version constraints, lockfiles, or integrity hashes. As a result, two installations performed at different times may execute materially different dependency code. No evidence was found that the listed package names are typosquatted or intentionally malicious. The risk arises from the absence of reproducibility and integrity controls: a compromised upstream release, dependency account, package index, or incompatible future version would be accepted automatically. The `requests` package is required for OpenAI and Anthropic operation but is not declared in the installation instructions. Users may install it manually from an unreviewed source or encounter runtime failure. ### Attack Path 1. A user follows the documented installation commands. 2. The package managers resolve the newest package versions available at installation time. 3. If an upstream package or transitive dependency has been compromised, the unpinned installation accepts the affected version without a project-specific integrity check. 4. Package installation hooks or subsequently imported dependency code execute under the installing or invoking user's account. 5. For the undeclared `requests` dependency, a user may respond to the missing-module error by installing a package using ad hoc instructions from an untrusted source. ### Impact Assessment A com ...[truncated 508 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Provide a reviewed dependency manifest containing exact versions for all Python dependencies, including `requests`. 2. Generate and distribute hashes for Python artifacts and require hash verification during installation. 3. Use a lockfile or constraints file to pin transitive dependencies as well as direct dependencies. 4. Recommend installation inside an isolated virtual environment. 5. Document minimum supported and reviewed versions of external tools such as `pandoc`, `poppler`, and `soffice`. 6. Periodically update pinned versions through a controlled review process that includes vulnerability scanning and functional testing. 7. Avoid suggesting ad hoc installation commands in response to missing modules; maintain one authoritative dependency specification. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

Tainted flow: 'headers' from os.environ.get (line 101, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
"max_tokens": 1000
    }
    
    resp = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload, timeout=60)
    data = resp.json()
    
    if "choices" in data and data["choices"]:
Confidence
97% confidence
Finding
This function sends base64-encoded image content and user document-derived data to OpenAI's external API, creating a clear confidentiality and data-governance risk. In a knowledge-base import tool, users may process sensitive training materials, notes, or internal documents, so silent off-device transmission is materially dangerous if not explicitly consented to and controlled.

Tainted flow: 'headers' from os.environ.get (line 101, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
]}]
    }
    
    resp = requests.post("https://api.anthropic.com/v1/messages", headers=headers, json=payload, timeout=60)
    data = resp.json()
    
    if "content" in data:
Confidence
97% confidence
Finding
This transmits image contents to Anthropic's external API, with the same confidentiality and compliance concerns as the OpenAI path. Because the skill is framed as an Obsidian import utility rather than a cloud-upload tool, the hidden transfer of potentially sensitive document content increases risk in context.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises and operationally requires shell execution, filesystem read/write, environment variable access, and outbound network calls, but it does not declare any tool scope or permission boundaries. This is dangerous because users and execution frameworks cannot accurately assess or constrain the skill’s privileges, increasing the chance of overbroad access to local files, secrets, and external services during import operations.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill states that it will use multimodal models to understand document and image content, but it does not clearly warn users that file contents may be transmitted to third-party AI providers such as MiniMax, OpenAI, or Anthropic. This is dangerous because users may process confidential training materials, notes, or images without informed consent, causing unintended data disclosure to external processors.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill's core behavior includes uploading extracted document/image content to third-party model APIs, but this is not plainly conveyed by the stated purpose of merely importing content into Obsidian. That mismatch can mislead users into exposing confidential material without understanding that cloud services will receive it.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The code transmits image/document content to external model APIs without any obvious user-facing warning, confirmation, or privacy notice. In this context, imported media may contain proprietary, regulated, or personal information, making undisclosed transmission a significant security and trust issue.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The default prompt hard-codes Chinese instructions for the model output, which imposes a specific language without any user opt-in or alternative locale selection. This is a natural-language policy issue because the skill behavior is language-constrained by default rather than user-selectable.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"-d", json.dumps(payload)
    ]
    
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=35)
    data = json.loads(result.stdout)
    
    if data.get("base_resp", {}).get("status_code") == 0:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'cmd' from os.environ.get (line 52, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
"-d", json.dumps(payload)
    ]
    
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=35)
    data = json.loads(result.stdout)
    
    if data.get("base_resp", {}).get("status_code") == 0:
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.

External Transmission

Medium
Category
Data Exfiltration
Content
"max_tokens": 1000
    }
    
    resp = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload, timeout=60)
    data = resp.json()
    
    if "choices" in data and data["choices"]:
Confidence
94% confidence
Finding
The hardcoded OpenAI endpoint confirms outbound transfer to a third-party service. In combination with arbitrary multimedia ingestion, this can leak sensitive images and document content outside the local environment.

External Transmission

Medium
Category
Data Exfiltration
Content
"max_tokens": 1000
    }
    
    resp = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload, timeout=60)
    data = resp.json()
    
    if "choices" in data and data["choices"]:
Confidence
94% confidence
Finding
The hardcoded OpenAI endpoint confirms outbound transfer to a third-party service. In combination with arbitrary multimedia ingestion, this can leak sensitive images and document content outside the local environment.

External Transmission

Medium
Category
Data Exfiltration
Content
"max_tokens": 1000
    }
    
    resp = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload, timeout=60)
    data = resp.json()
    
    if "choices" in data and data["choices"]:
Confidence
94% confidence
Finding
The hardcoded OpenAI endpoint confirms outbound transfer to a third-party service. In combination with arbitrary multimedia ingestion, this can leak sensitive images and document content outside the local environment.

External Transmission

Medium
Category
Data Exfiltration
Content
]}]
    }
    
    resp = requests.post("https://api.anthropic.com/v1/messages", headers=headers, json=payload, timeout=60)
    data = resp.json()
    
    if "content" in data:
Confidence
94% confidence
Finding
The hardcoded Anthropic endpoint likewise confirms third-party transfer of processed media. For a knowledge import tool, such hidden egress is a real security concern, especially in enterprise or regulated environments.

External Transmission

Medium
Category
Data Exfiltration
Content
]}]
    }
    
    resp = requests.post("https://api.anthropic.com/v1/messages", headers=headers, json=payload, timeout=60)
    data = resp.json()
    
    if "content" in data:
Confidence
94% confidence
Finding
The hardcoded Anthropic endpoint likewise confirms third-party transfer of processed media. For a knowledge import tool, such hidden egress is a real security concern, especially in enterprise or regulated environments.

External Transmission

Medium
Category
Data Exfiltration
Content
]}]
    }
    
    resp = requests.post("https://api.anthropic.com/v1/messages", headers=headers, json=payload, timeout=60)
    data = resp.json()
    
    if "content" in data:
Confidence
94% confidence
Finding
The hardcoded Anthropic endpoint likewise confirms third-party transfer of processed media. For a knowledge import tool, such hidden egress is a real security concern, especially in enterprise or regulated environments.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def extract_images_from_docx(docx_path, output_dir):
    """从 DOCX 提取图片"""
    os.makedirs(output_dir, exist_ok=True)
    subprocess.run([
        "pandoc", docx_path, "-t", "markdown",
        "--extract-media", output_dir, "-o", "/dev/null"
    ], capture_output=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 使用 pdftoppm 转换
    base_name = os.path.join(output_dir, "page")
    subprocess.run([
        "pdftoppm", "-png", "-r", "150", pdf_path, base_name
    ], capture_output=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
os.makedirs(output_dir, exist_ok=True)
    
    # 使用 pandoc 转换为图片
    subprocess.run([
        "pandoc", ppt_path, "-t", "slide.html", "-o", "/dev/null"
    ], capture_output=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 简单方案:转换为 PDF 再提取
    pdf_path = os.path.join(output_dir, "temp.pdf")
    subprocess.run([
        "soffice", "--headless", "--convert-to", "pdf", ppt_path,
        "--outdir", output_dir
    ], capture_output=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 清理临时目录
    if os.path.exists(temp_dir):
        subprocess.run(["rm", "-rf", temp_dir], capture_output=True)
    
    print(f"  -> 已保存: {output_md}")
    return len(images)
Confidence
81% confidence
Finding
The code recursively deletes a path derived from user-controlled filenames and category/output selections using rm -rf. Even without shell injection, unsafe path construction can cause unintended deletion if temp_dir resolves outside the intended workspace via symlinks, path confusion, or crafted names in a hostile filesystem context.

Missing User Warnings

Low
Confidence
87% confidence
Finding
The script reads API keys from environment variables for multiple providers, which is access to sensitive credentials under this rule. While the code uses the variables as intended, there is no user-facing notice in comments, CLI output, or documentation in this file explaining that credentials are consumed.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
The function docstring and inline comment state that PPT pages are extracted as images, and another comment says pandoc is used to convert to images. In reality, the pandoc call writes slide.html to /dev/null, the python-pptx branch does nothing, and the fallback checks for a temp.pdf path that likely does not match LibreOffice's output naming, so the implementation may return no images at all.

Static analysis

No suspicious patterns detected.