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.
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]
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.
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.
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.
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.
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.