Back to skill

Security audit

Joplin API(中文)

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Joplin note helper, but its SSH remote mode can turn crafted note arguments into command execution on the configured remote host.

Install only if you plan to use local Joplin access or you fully trust and control any SSH target. Avoid SSH remote mode until the scripts validate IDs and fields and stop interpolating URLs and tokens into remote shell commands. Review write and delete actions carefully, especially permanent deletion.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/joplin.py:56
Finding

Remote Shell Command Injection Through Unescaped Joplin Request URL in Python Client

Content
View full analysis
' ... ``` The URL includes API paths assembled from command-line arguments. These include note IDs, folder IDs, requested field list ...[truncated 2576 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/joplin.sh:63
Finding

Remote Shell Command Injection Through Unescaped Joplin Request URL in Bash Client

Content
View full analysis
' ``` Although local expansion preserves the value as part of one argument to `ssh`, the SSH server subsequently passes that argument to the remote account's shell. An apostrophe in `url` can therefore close the remote single-quoted assignment and expose following shell syntax for execution. The URL includes API paths created from arguments such as note IDs, folder IDs, field lists, and limits. These values are not strictly validated before being placed into the URL. The API token is also appended without shell-safe quoting. Encoding the request body with Base64 does not protect these URL components. The Base64 behavior is functionally justified as a mechanism for transferring JSON through the documented SSH mode. No undeclared recipient or covert output ...[truncated 1610 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (32)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · references/api-docs.md (reported line 105)May include surrounding context.

md
Custom ID: supply 32-char hex string as `id` property.

### Delete Note (DELETE /notes/:id)

Default: move to trash. Add `?permanent=1` for permanent deletion.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · references/api-docs.md (reported line 109)May include surrounding context.

md
Default: move to trash. Add `?permanent=1` for permanent deletion.

### DELETE /notes/:id/revisions

Delete all revisions for a note.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · references/api-docs.md (reported line 145)May include surrounding context.

md
**Note:** When no `fields` parameter is specified, the API may return a tree structure with `children` key. When `fields` is specified, it returns a flat paginated list.

### DELETE /folders/:id

Default: move to trash. Add `?permanent=1` for permanent deletion.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · references/api-docs.md (reported line 217)May include surrounding context.

md
Add tag to a note. Note data must contain at least an `id` property.

### DELETE /tags/:id/notes/:note_id

Remove tag from note.

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 · scripts/joplin.py (reported line 17)May include surrounding context.

python
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

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 · scripts/joplin.sh (reported line 8)May include surrounding context.

sh
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

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 24)May include surrounding context.

md
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Error: $ENV_FILE not found. Copy .env.example to .env and set api_token." >&2

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 25)May include surrounding context.

md
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Error: $ENV_FILE not found. Copy .env.example to .env and set api_token." >&2

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 192)May include surrounding context.

md
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Error: $ENV_FILE not found. Copy .env.example to .env and set api_token." >&2

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 · scripts/joplin.py (reported line 19)May include surrounding context.

python
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Error: $ENV_FILE not found. Copy .env.example to .env and set api_token." >&2

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 · scripts/joplin.py (reported line 24)May include surrounding context.

python
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Error: $ENV_FILE not found. Copy .env.example to .env and set api_token." >&2

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 · scripts/joplin.sh (reported line 10)May include surrounding context.

sh
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Error: $ENV_FILE not found. Copy .env.example to .env and set api_token." >&2

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 · scripts/joplin.sh (reported line 13)May include surrounding context.

sh
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Error: $ENV_FILE not found. Copy .env.example to .env and set api_token." >&2

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 · scripts/joplin.sh (reported line 21)May include surrounding context.

sh
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_DIR/../.env"

# ── Load .env ──────────────────────────────────────────────

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Error: $ENV_FILE not found. Copy .env.example to .env and set api_token." >&2

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill invokes shell commands, reads local files, and performs network access to a local or SSH-forwarded Joplin API, yet it declares no explicit tool scope or allowed-tools boundary. Without a least-privilege declaration, an agent runtime may grant broader capabilities than necessary, increasing the chance of unintended file access, command execution, or network use beyond the documented Joplin workflow.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

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

md
---
name: joplin-api
description: 通过 Joplin Data API(Clipper Server)查询和管理笔记、笔记本、标签。支持本地直连和 SSH 远程两种模式。包含读写删全部操作,写/删需用户确认。触发词:"查 Joplin"、"Joplin 笔记"、"joplin search"、"create note in joplin" 等。
version: 1.4.1
metadata:
  openclaw:

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger description includes broad phrases like managing notes and examples such as 'create note in joplin', but it does not narrowly constrain when the skill should activate or what safety checks must precede sensitive operations. Over-broad activation increases the risk that an agent invokes a capability-bearing skill in contexts the user did not intend, especially since the skill supports create, update, delete, shell, and remote network behaviors.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
### API Token 安全
- Token 通过 URL 查询参数传递(`?token=xxx`),这是 Joplin Clipper API 的设计
- 注意:URL 可能被记录在 shell 历史、SSH 日志、代理日志中
- 建议:`.env` 文件权限设为 `600`(`chmod 600 .env`),并通过 `.gitignore` 排除

### 删除操作警告
- `delete <id>` 默认**软删除**(移至回收站,可在 Joplin 中恢复)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This markdown file describes deleting notes, permanently deleting them with ?permanent=1, and deleting all note revisions, but it does not include any warning about irreversibility or potential data loss. Under the markdown-specific warning rule, destructive behaviors that can affect user data should be accompanied by a clear caution.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The documentation states that folders can be deleted and permanently deleted via ?permanent=1, but it provides no caution about the impact on user data or the irreversible nature of permanent deletion. Markdown skill documentation should warn users about behaviors affecting stored content or system integrity.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/api-docs.md (reported line 176)May include surrounding context.

Uses multipart/form-data:

bash
curl -F 'data=@/path/to/file.jpg' \
     -F 'props={"title":"my resource title"}' \
     "http://localhost:41184/resources?token=$JOPLIN_TOKEN"

External Transmission

Medium
Category
Data Exfiltration
Confidence
92% confidence
Finding

This skill transmits Joplin note data, including note bodies and API tokens in URLs, over HTTP to a local or SSH-forwarded service and via remote curl execution. In the skill context, the tool is explicitly designed to read, create, update, and delete notes, so external transmission is expected; however, it still expands the attack surface because sensitive note content may be exposed through command arguments, process inspection, logs, or an insecure base_url if reconfigured away from localhost.

Content

Scanner excerpt · scripts/joplin.py (reported line 67)May include surrounding context.

python
# Inside single quotes, $ is literal — no backslash needed
            remote_cmd = (
                "JOP_URL='" + url + "' JOP_BODY='" + b64 + "' JOP_METHOD=" + method + " "
                "bash -c 'curl -s -X $JOP_METHOD "
                "-H \"Content-Type: application/json\" "
                "-d \"$(echo \"$JOP_BODY\" | base64 -d)\" "
                "\"$JOP_URL\"'"

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
89% confidence
Finding

The code builds a remote shell command string and passes it to ssh for execution on the remote host. Although the author comments that no raw user input reaches the shell, the URL contains path/query components derived from command arguments such as note IDs, fields, folder IDs, and limits, and these values are embedded into shell-quoted environment assignments without robust escaping. A crafted value containing shell metacharacters or quotes could break out on the remote side and lead to command injection.

Content

Scanner excerpt · scripts/joplin.py (reported line 83)May include surrounding context.

python
# JOP_URL/JOP_BODY/JOP_METHOD are env-var indirection to bypass zsh globbing.
        # No raw user input reaches the shell — method is whitelisted (GET/POST/PUT/DELETE),
        # body is base64-encoded, URL is built by build_url() above.
        result = subprocess.run(
            ["ssh", HOST, remote_cmd],
            capture_output=True, text=True, check=True
        )

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 · scripts/joplin.py (reported line 112)May include surrounding context.

python
def cmd_ping():
    if HOST:
        try:
            result = subprocess.run(
                ["ssh", HOST, "curl -s http://127.0.0.1:41184/ping"],
                capture_output=True, text=True, check=True
            )

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

In remote mode, note content and the API token are transmitted over SSH to another host and then sent to the remote Joplin Clipper endpoint. This is intentional functionality, but it still expands the trust boundary: sensitive note contents and credentials leave the local machine and may be exposed on the remote host through process arguments, shell history, logging, or compromise of that host.

Content

Scanner excerpt · scripts/joplin.sh (reported line 74)May include surrounding context.

sh
local b64
      b64=$(printf '%s' "$data" | base64 -w 0)
      # Use env vars + bash -c to avoid zsh globbing on remote macOS
      ssh "$HOST" "JOP_URL='${url}' JOP_BODY='${b64}' JOP_METHOD=${method} bash -c 'curl -s -X \$JOP_METHOD -H \"Content-Type: application/json\" -d \"\$(echo \"\$JOP_BODY\" | base64 -d)\" \"\$JOP_URL\"'"
    else
      # Use env var to avoid zsh globbing on remote macOS
      ssh "$HOST" "JOP_URL='${url}' JOP_METHOD=${method} bash -c 'curl -s -X \$JOP_METHOD \"\$JOP_URL\"'"