Back to skill

Security audit

Obsidian Inbox Pipeline

Security checks for vulnerabilities and agentic risk

Overview

This Obsidian automation skill is not clearly malicious, but it needs review because it can run other local scripts, write/read vault files, and send content externally with weak scoping controls.

Review before installing, especially if you plan to run it from cron. Use a dedicated low-privilege account, restrict OBSIDIAN_VAULT_PATH and OBSIDIAN_INBOX_DIR to the intended vault, avoid untrusted radar_type values, secure or disable Telegram/Feishu credentials unless you want external sending, and replace predictable /tmp files with private temporary directories.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/daily_pipeline.sh:25
Finding

Unvalidated radar type permits path traversal and execution of unintended Python scripts

Content
View full analysis
}" RADAR_NAME="${2:?}" CATEGORY="${3:?}" EMOJI="${4:?}" SOURCE="${5:?}" TODAY="$(date '+%Y-%m-%d')" TMP="/tmp/radar_${RADAR_TYPE}.md" # ── Step 1:生成日报 ────────────────────────────────── RADAR_MAIN="$SKILL_DIR/../${RADAR_TYPE}/main.py" if [[ -f "$RADAR_MAIN" ]]; then python3 "$RADAR_MAIN" > "$TMP" else echo "⚠️ 未找到 $RADAR_MAIN,跳过生成步骤" exit 0 fi # ── Step 2:写入 Obsidian inbox ────────────────────── # 模板:用 capture.py 写入(结构化版本) # 如 radar 提供了独立格式脚本,优先用其写入结构化 Obsidian 文件 FORMAT_SCRIPT="$SKILL_DIR/../${RADAR_TYPE}/format_push.py" if [[ -f "$FORMAT_SCRIPT" ]]; then python3 "$FORMAT_SCRIPT" \ "$RADAR_TYPE" "$RADAR_NAME" "$SOURCE" "$CATEGORY" "$EMOJI" "$TODAY" \ > /dev/null 2>&1 OBSIDIAN_PATH="$(python3 -c " import sys for line in open('$TMP'): if line.startswith('OBSIDIAN|'): print(line.split('|',1)[1].strip()) ")" ``` ### Technical Analysis `RADAR_TYPE` is taken directly from the first command-line argument without an allowlist, canonicalization, or containment check. It is then used to construct the paths of `main.py` and `format_push.py`, both of which are executed by the pipeline. Because values such as `../` are not rejected, the resolved script path can escape the expected sibling skill directory. Shell quoting prevents ordinary shell metacharacter expansion at these execution sites, but it does not prevent filesystem traversal. The same value also controls `TMP`, which is embedded directly inside Python source passed to `python3 -c`. A value containing a single quote and valid Python syntax could modify the generated Python program. Reaching that code requires the earlier generated paths to satisfy the file che ...[truncated 1502 chars]
Remediation
View remediation
&2 exit 1 fi ``` 2. Define a trusted skill root and canonicalize every executable path with `realpath`. 3. Verify that each resolved path is a descendant of the trusted skill root before execution. 4. Consider using an explicit allowlist mapping radar identifiers to approved script paths rather than constructing executable paths from user input. 5. Never interpolate shell-controlled values into Python source. Pass the temporary path as an argument: ```bash python3 - "$TMP" <<'PY' import sys with open(sys.argv[1], encoding="utf-8") as file: for line in file: if line.startswith("OBSIDIAN|"): print(line.split("|", 1)[1].strip()) PY ``` 6. Run scheduled pipelines under a dedicated least-privileged account with access only to the required skill directories and vault paths. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/daily_pipeline.sh:31
Finding

Predictable shared temporary files enable symlink overwrite and stale-content attacks

Content
View full analysis
"$TMP" ``` ```bash if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -n "${TELEGRAM_CHAT_ID:-}" ]]; then PUSH_FILE="/tmp/radar_push_${RADAR_TYPE}.txt" python3 - "$PUSH_FILE" << 'PYEOF' import sys, json, urllib.request, urllib.parse push_file = sys.argv[1] with open(push_file) as f: text = f.read() ``` ```bash # read push file push_file = f"/tmp/radar_push_{radar_type}.txt" with open(push_file) as f: content = f.read() ``` ```bash echo "✅ $RADAR_NAME 流水线完成" rm -f "$TMP" ``` ### Technical Analysis The pipeline uses deterministic names in the globally shared `/tmp` directory. It does not create a private temporary directory, atomically create files, verify ownership, reject symbolic links, or apply restrictive permissions. The shell redirection `> "$TMP"` follows symbolic links. A local attacker who can write to `/tmp` can pre-create the predictable path as a symlink to another file writable by the pipeline account. When the scheduled pipeline runs, Python output is redirected through that symlink and truncates or overwrites the target. The notification file is also predictable and is opened without validating its type, owner, or origin. The fallback capture branch does not create `PUSH_FILE`, so a stale or attacker-created file can be consumed. Temporary files may also contain report content and are not protected with a restrictive `umask`. The current notification blocks contain a separate functional defect: both reference `os.environ` without importing `os`. Consequently, the inspected version fails before succ ...[truncated 1480 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
capture.py:113
Finding

Unvalidated inbox directory can escape the configured Obsidian vault

Content
View full analysis
list: inbox_path = os.path.join(vault, inbox_dir) pattern = os.path.join(inbox_path, "*.md") files = glob.glob(pattern) items = [] for path in sorted(files, key=os.path.getmtime, reverse=True): try: with open(path, encoding='utf-8') as f: content = f.read() ``` ### Technical Analysis Calling `lstrip("/")` only removes leading slash characters. It does not remove or reject parent-directory components such as `..`, nor does it prevent escape through symbolic links. For example, an `OBSIDIAN_INBOX_DIR` value of `../../target` causes `os.path.join(vault, inbox_dir)` to produce a path that resolves outside the configured vault. No canonical path comparison is performed before directories are created, notes are written, or Markdown files are read. This violates the Skill's declared vault-oriented access boundary. Although controlling an environment variable usually requires configuration-level access, environment values may be inherited ...[truncated 1424 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (26)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

该代码块的核心功能是把命令行提供的内容格式化为 Markdown 并写入 Obsidian inbox。它不包含从任意来源自动抓取内容的逻辑,也没有 Telegram、飞书或定时任务相关实现。虽然“结构化写入 Obsidian inbox”这一点与声明部分一致,但声明强调的是完整的自动采集与推送流水线,而代码实际只覆盖了其中末端的写入步骤,因此描述明显高于代码实际能力,构成能力层面的不匹配。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

描述强调的是“自动采集+入库+推送+定时”的完整自动化流水线,而该代码块并不执行采集、转换、写入、推送或调度。它只读取本地 Obsidian Vault 的索引文件和可选正文搜索结果,生成查询报告,主用途是检索知识库而非沉淀知识内容。虽然同属 Obsidian 场景,但核心功能与声明明显不符,且代码具备一个描述中未提到的主要能力:搜索知识库。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

声明描述的是“采集+入库+推送+定时”的完整知识沉淀流水线,但该代码片段的实际作用是一个 review 工具:扫描 Obsidian inbox 下已有 .md 文件,提取元数据与摘要,按类型汇总,输出整理建议,并可写入 review 报告文件。它不连接任何外部数据源,不抓取内容,不执行推送,也没有定时触发逻辑。因此其主要用途与声明存在实质性不一致。虽然两者都与 Obsidian/inbox 相关,但代码行为明显偏向‘整理回顾’而非‘自动采集写入流水线’。

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

整体上,这段代码的核心目的与声明大体接近:它确实是在做“生成日报→写入 Obsidian→可选消息推送→可选索引重建”的自动化流水线,因此不是完全无关的实现。但声明存在明显夸大或不准确之处。首先,代码并没有展示对“任意来源”或 RSS/文章等多种外部来源的通用采集能力,它只是执行某个相邻目录下的 radar 技能 main.py,数据采集能力依赖外部技能,当前代码本身只是调度器。其次,声明提到支持 cron 定时执行,但代码没有包含任何 cron 配置或触发器,只能被外部定时器调用。最后,声明说支持 Telegram / 飞书推送,但在该代码片段里推送内容来自 /tmp/radar_push_.txt,而该文件并未在脚本中生成,说明至少此片段内推送链路不完整。综合判断:描述与实际行为部分一致,但存在实质性能力夸大与实现不完整,应判为 mismatch。

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

clawhub install obsidian-inbox-pipeline

复制环境变量模板

cp references/.env.example .env

编辑 .env,填入真实路径和凭证

text

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

clawhub install obsidian-inbox-pipeline

复制环境变量模板

cp references/.env.example .env

编辑 .env,填入真实路径和凭证

text

Credential Access

High
Category
Privilege Escalation
Confidence
77% confidence
Finding

The cron example uses source /path/to/.env inline before invoking shell scripts. In practice, this pattern can expose secrets to process listings, inherited environments, debugging output, or overly broad shell contexts, especially if the downstream scripts call other tools or log failures verbosely.

Content

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

bash
# 三合一每日自动运行(配合 cron)
0 7 * * * \
  source /path/to/.env && \
  bash /path/to/obsidian-inbox-pipeline/scripts/daily_pipeline.sh \
    "ai-radar" "AI 资讯雷达" "AI" "🤖" "AI 资讯" && \
  bash /path/to/obsidian-inbox-pipeline/scripts/daily_pipeline.sh \

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill documents capabilities that require access to environment variables, local files, shell execution, and potentially networked notification channels, but it does not declare any explicit tool scope or permission boundaries. This increases the risk of overbroad execution in hosts that rely on manifest-declared permissions, making unintended file access, command execution, or secret exposure harder to constrain or audit.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest description and main documentation are written entirely in Chinese, which implicitly constrains the skill to a specific language/locale. The file does not offer an opt-in language choice or explain that the skill is intended only for a Chinese-language audience or region-specific use case.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The module docstring, usage instructions, argument help text, and status messaging are all written in Chinese, which imposes a specific language/locale on users. The file does not indicate that Chinese is optional, configurable, or limited to a documented region-specific use case.

Content

No source excerpt is available for this finding.

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

Medium
Category
Data Flow
Confidence
91% confidence
Finding

The file write path is derived from environment-controlled values via get_vault() and OBSIDIAN_INBOX_DIR, then written with open() without constraining the final path to an approved base directory. If an attacker can influence those environment variables, the skill can be abused to overwrite arbitrary files accessible to the process, which is especially relevant in an automation pipeline that runs unattended.

Content

Scanner excerpt · capture.py (reported line 93)May include surrounding context.

python
def write_note(path: str, content: str) -> str:
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, 'w', encoding='utf-8') as f:
        f.write(content)
    return path

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · config.py (reported line 15)May include surrounding context.

python
# fallback: 尝试 obsidian-cli
    import subprocess
    try:
        result = subprocess.run(
            ["obsidian-cli", "print-default", "--path-only"],
            capture_output=True, text=True, timeout=5
        )

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This file contains natural-language descriptions, help text context, and output strings that assume Chinese as the required language. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · query.py (reported line 93)May include surrounding context.

python
def search_content(query: str, vault: str, limit: int = 10) -> List[Dict]:
    """用 obsidian-cli 搜索正文内容"""
    try:
        result = subprocess.run(
            ["obsidian-cli", "search-content", query, "--path", vault],
            capture_output=True, text=True, timeout=15
        )

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The argparse description and help text are presented only in Chinese, which forces a single locale for interaction. The policy allows locale constraints only when users can opt in or when the restriction is clearly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The natural-language descriptions, CLI help text, and generated report content are all hard-coded in Chinese, indicating a fixed locale experience. There is no visible option for users to select another language or any justification that this skill is intended only for a Chinese-language context.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · capture.py (reported line 32)May include surrounding context.

python
return path
        import subprocess
        try:
            r = subprocess.run(["obsidian-cli", "print-default", "--path-only"],
                              capture_output=True, text=True, timeout=5)
            if r.returncode == 0:
                return r.stdout.strip()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · query.py (reported line 34)May include surrounding context.

python
return path
        import subprocess
        try:
            r = subprocess.run(["obsidian-cli", "print-default", "--path-only"],
                              capture_output=True, text=True, timeout=5)
            if r.returncode == 0:
                return r.stdout.strip()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · review.py (reported line 31)May include surrounding context.

python
return path
        import subprocess
        try:
            r = subprocess.run(["obsidian-cli", "print-default", "--path-only"],
                              capture_output=True, text=True, timeout=5)
            if r.returncode == 0:
                return r.stdout.strip()

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script constructs a Python entrypoint path from the user-supplied radar_type and executes it, enabling invocation of arbitrary sibling skill code rather than a fixed trusted program. In an agent-skill ecosystem where neighboring skills may be untrusted or less reviewed, this broadens execution scope and can lead to arbitrary code execution, data access, or unintended side effects.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This block sends locally generated report content to Telegram using bot credentials whenever environment variables are present, with no confirmation, scoping controls, or output sanitization policy beyond Markdown escaping. That can leak sensitive vault-derived or upstream-ingested content to a third party if the pipeline processes confidential material or is run in a shared environment.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
79% confidence
Finding

The script makes an external network request to Telegram's API to transmit report text off-host. In isolation, network use is not inherently malicious, but in this skill context it materially changes the data-flow trust boundary by exporting locally assembled content and therefore constitutes a real exfiltration surface.

Content

Scanner excerpt · scripts/daily_pipeline.sh (reported line 92)May include surrounding context.

sh
}).encode()

req = urllib.request.Request(
    f"https://api.telegram.org/bot{token}/sendMessage",
    data=data,
    headers={"Content-Type": "application/json"}
)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code authenticates with Feishu using app credentials and sends report text externally without an explicit consent or review step. In a knowledge-ingestion pipeline, the content may include sensitive internal notes or scraped material, so silent transmission increases data leakage risk and expands the impact of any upstream compromise.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This code reads sensitive bot credentials from environment variables, including TELEGRAM_BOT_TOKEN and FEISHU_APP_SECRET. Although the functions have brief internal docstrings, there is no user-facing warning, logging, or confirmation indicating that credential material is being accessed.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code performs a file write into the user's Obsidian vault by creating a review directory and saving a markdown report. Although it prints the destination after writing, the safety-relevant behavior is not disclosed before execution in the top-level docstring or gated by a confirmation prompt, so users may not realize a file will be created unless they inspect the code or use --dry-run.

Content

No source excerpt is available for this finding.