T09 · Insecure Skill Coding Practices
- Location
scripts/article2ebook.py:73- Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill is mostly aligned with sending documents to reMarkable, but it needs review because it uses persistent cloud credentials, unrestricted web fetching, and an unpinned system-wide helper install.
Review before installing. Use this only with a trusted, pinned rmapi build, avoid running it with elevated privileges, treat ~/.rmapi as sensitive account access, and avoid sending private URLs or documents unless you are comfortable with them being fetched locally and uploaded to reMarkable Cloud.
scripts/article2ebook.py:73Unrestricted URL Fetching Enables Server-Side Request Forgery
scripts/article2ebook.py:307Predictable Shared Temporary Files Permit Symlink-Based File Overwrite
SKILL.md:12Unpinned Upstream Source Is Built and Installed as a System-Wide Tool
The code matches only one narrow portion of the description: converting a web article into EPUB or PDF. The declared purpose emphasizes reMarkable-specific delivery and file management via the reMarkable Cloud, but this code contains no authentication, no cloud access, no reMarkable API calls, no device/file listing, and no upload logic. Its actual primary behavior is local document generation from a URL. That is a material description-to-behavior mismatch.
The skill documents capabilities that require shell execution, network access, and file writes, but it does not declare any tool scope or permission boundaries. This makes the operational surface implicit rather than explicit, increasing the chance the agent can invoke the skill in contexts where users did not clearly consent to filesystem, network, or command execution behavior.
The invocation guidance is broad enough that the skill may trigger on ordinary mentions of reMarkable or document/article handling without strong user intent. In a skill with network access, shell usage, and cloud-side effects, overbroad triggering increases the risk of unintended uploads, web fetches, or file-management actions.
The documentation omits an important privacy and security warning: article URLs are fetched from external websites and uploaded content is transmitted to reMarkable cloud services, while auth state is cached locally. Users may unknowingly expose sensitive URLs, document contents, or metadata to third parties if these transfers are not clearly disclosed.
The skill notes that rmapi caches authentication tokens at ~/.rmapi, which introduces session persistence beyond a single interaction. Persistent cloud credentials on disk can be reused by later processes or users on the same system if permissions are weak, enabling unauthorized access to the user's reMarkable account and cloud files.
{baseDir}/scripts/remarkable.sh upload --file /path/to/book.epub --dir /
### Create a folder
```bash
{baseDir}/scripts/remarkable.sh mkdir --path /NewFolder
The generated XHTML sets both xml:lang and lang to "en", forcing English metadata regardless of the source article or user preference. This is a natural-language locale policy issue because the skill does not offer opt-in or explain why English is required.
The navigation document also forces xml:lang and lang to "en" for all outputs. This creates the same locale policy problem across generated EPUB components and may mislabel non-English content.
The OPF metadata sets dc:language to "en" unconditionally, which forces an English locale even for content in other languages. There is no user opt-in or documented regional constraint that would justify this fixed language declaration.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
epub_data = build_epub(title, body_content, images, author='Web Article')
with open(output_path, 'wb') as f:
f.write(epub_data)
return output_path
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
epub_data = build_epub(title, body_content, images, author='Web Article')
with open(output_path, 'wb') as f:
f.write(epub_data)
return output_path
The manifest describes sending files/articles to reMarkable and converting web articles to ebooks, but this helper script also shells out to the local cupsfilter executable to render PDFs. Spawning a subprocess is a materially broader capability than the stated cloud/file-management purpose and is not justified by the manifest text.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
with open(tmp_html, 'w', encoding='utf-8') as f:
f.write(clean_html)
try:
result = subprocess.run(['cupsfilter', tmp_html], capture_output=True, timeout=30)
if result.returncode == 0 and result.stdout:
with open(output_path, 'wb') as f:
f.write(result.stdout)
The sanitizer claims to keep only safe tags, but the allowlist includes active or styling-related elements such as style, link, form, input, object, embed, video, audio, and button. In generated EPUB/HTML/PDF workflows, preserving such markup can permit external resource loading, malformed rendering behavior, or content that executes or triggers dangerous parser behavior in downstream readers and converters.
No suspicious patterns detected.