T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/joplin.py:56
- Finding
- Remote Shell Command Injection Through Unescaped Joplin Request URL in Python Client<![CDATA[ ## Vulnerability Details **File Location**: `scripts/joplin.py`, lines 56-86 **Vulnerability Type**: Remote shell command injection **Risk Level**: High ### Vulnerable Code ```python def api_req(method, path, data=None): if HOST: # Remote Joplin via SSH url = build_url("http://127.0.0.1:41184", path) if data: # Base64 encode body to avoid shell quote swallowing b64 = base64.b64encode(data.encode("utf-8")).decode("ascii") # Use env vars + bash -c to avoid zsh globbing on remote macOS # 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\"'" ) else: # Use env var to avoid zsh globbing on remote macOS remote_cmd = ( "JOP_URL='" + url + "' JOP_METHOD=" + method + " " "bash -c 'curl -s -X $JOP_METHOD " "\"$JOP_URL\"'" ) # SECURITY: remote_cmd is constructed from hardcoded curl invocations. # 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 ) ``` ### Technical Analysis In remote mode, the script concatenates `url` into a shell command as a single-quoted environment assignment: ```text JOP_URL='<URL>' ... ``` The URL includes API paths assembled from command-line arguments. These include note IDs, folder IDs, requested field list ...[truncated 2576 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Eliminate dynamic remote-shell command construction.** Send request metadata and content through SSH standard input to a fixed, audited remote helper rather than concatenating them into a command string. 2. **Use strict identifier validation.** Validate note and folder IDs against the format expected by Joplin, such as `^[0-9a-fA-F]{32}$`, before constructing a request. 3. **Allowlist fields.** Parse requested fields and accept only documented Joplin field names. 4. **Validate numeric arguments.** Require limits to be integers within the Joplin API range, normally 1 through 100. 5. **Encode components separately.** Apply URL encoding to each path segment and query value rather than encoding or quoting a completed URL. 6. **Avoid tokens in command text.** Transfer the token through standard input or another protected channel so it is not embedded in the remote shell command or exposed through process inspection and logging. 7. **If shell use is unavoidable, apply a proven POSIX shell-quoting routine** to every dynamic value, including the URL, token, host-derived values, method, and body. Validation should still be applied as defense in depth. 8. **Add regression tests** using apostrophes, command separators, substitutions, newlines, and malformed IDs to confirm that no input can alter the remote command structure. ]]>
