T09 · Insecure Skill Coding Practices
- Location
scripts/media_to_obsidian.py:48- Finding
Configurable MiniMax endpoint can receive confidential images and API credentials
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
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.
scripts/media_to_obsidian.py:48Configurable MiniMax endpoint can receive confidential images and API credentials
scripts/media_to_obsidian.py:54MiniMax API key is exposed through the curl process argument list
scripts/media_to_obsidian.py:187Unvalidated category path permits writes and recursive deletion outside the Obsidian vault
SKILL.md:18Third-party dependencies are installed without version or integrity controls
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.
"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"]:
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.
]}]
}
resp = requests.post("https://api.anthropic.com/v1/messages", headers=headers, json=payload, timeout=60)
data = resp.json()
if "content" in data:
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.
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.
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.
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.
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 calls execute external commands. Without careful input validation, this enables command injection.
"-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:
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
"-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:
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.
"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"]:
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.
"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"]:
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.
"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"]:
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.
]}]
}
resp = requests.post("https://api.anthropic.com/v1/messages", headers=headers, json=payload, timeout=60)
data = resp.json()
if "content" in data:
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.
]}]
}
resp = requests.post("https://api.anthropic.com/v1/messages", headers=headers, json=payload, timeout=60)
data = resp.json()
if "content" in data:
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.
]}]
}
resp = requests.post("https://api.anthropic.com/v1/messages", headers=headers, json=payload, timeout=60)
data = resp.json()
if "content" in data:
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# 使用 pdftoppm 转换
base_name = os.path.join(output_dir, "page")
subprocess.run([
"pdftoppm", "-png", "-r", "150", pdf_path, base_name
], capture_output=True)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
os.makedirs(output_dir, exist_ok=True)
# 使用 pandoc 转换为图片
subprocess.run([
"pandoc", ppt_path, "-t", "slide.html", "-o", "/dev/null"
], capture_output=True)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# 简单方案:转换为 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)
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.
# 清理临时目录
if os.path.exists(temp_dir):
subprocess.run(["rm", "-rf", temp_dir], capture_output=True)
print(f" -> 已保存: {output_md}")
return len(images)
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.
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.
No suspicious patterns detected.