Castreader Openclaw Skill
WarnAudited by ClawScan on May 10, 2026.
Overview
The skill matches its stated text-to-speech purpose, but it fetches arbitrary URLs with risky browser settings and sends extracted text to a plaintext remote API.
Review before installing. Avoid using this skill on private documents or internal URLs unless the API endpoint is HTTPS and trusted. Prefer running it in a sandboxed environment, and verify the skill has been updated to validate URLs, safely write summary files, and use per-request temporary filenames.
Findings (7)
Artifact-based informational review of SKILL.md, metadata, install specs, static scan signals, and capability signals. ClawScan does not execute the skill or run runtime probes.
A malicious or mistaken URL could make the agent fetch local/internal resources or expose the host to higher-risk browser content.
The helper accepts a raw URL and loads it in Chromium without visible scheme, localhost, private-network, or file URL restrictions, while also disabling Chromium sandboxing.
const url = process.argv[2]; ... await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }); ... args: ['--no-sandbox', '--disable-setuid-sandbox']Restrict accepted URLs to public http/https targets, block localhost/private IP/file schemes, and avoid disabling the browser sandbox unless absolutely necessary.
If a crafted page or summary contains shell metacharacters, the agent could run unintended commands when generating summary audio.
The instructions place page-derived summary text directly inside a shell command. Double quotes do not prevent all shell expansion, and malformed text can break the command.
echo "<summary text>" > /tmp/castreader-summary.txt node scripts/generate-text.js /tmp/castreader-summary.txt <language>
Write the summary using a safe file-write API or pass it via stdin; do not interpolate untrusted text into shell commands.
Article or document text, including potentially private content, could be visible or modifiable on the network while sent for TTS generation.
The script sends the text being converted to speech to a default remote API over unencrypted HTTP.
const API_URL = process.env.CASTREADER_API_URL || 'http://api.castreader.ai:8123'; ... input: remaining ... fetch(`${API_URL}/api/captioned_speech_partly`, { method: 'POST', headers, body: JSON.stringify(body) })Use HTTPS by default, clearly disclose what text is sent to the provider, and avoid using the skill on private documents unless transport and retention are acceptable.
If you set CASTREADER_API_KEY, the skill will send it to the configured TTS endpoint even though this credential is not declared in the metadata.
The code supports sending an optional API key, but the registry metadata declares no credentials or environment variables.
const API_KEY = process.env.CASTREADER_API_KEY || ''; ... if (API_KEY) headers['Authorization'] = `Bearer ${API_KEY}`;Declare CASTREADER_API_KEY in metadata if supported, document when it is used, and send it only over HTTPS.
Concurrent or stale runs could overwrite each other, causing the wrong summary audio to be sent to a user.
The summary workflow uses fixed shared /tmp filenames for generated text and audio.
echo "<summary text>" > /tmp/castreader-summary.txt ... "filePath":"/tmp/castreader-summary.mp3"
Use a unique per-request temporary directory or randomized filename, verify the generated file before sending, and clean up after delivery.
Dependency installation is expected for Puppeteer, but silent installation can hide warnings or failures.
The skill tells users/agents to install Node dependencies at setup time while suppressing install output.
cd <skill-directory> && npm install --silent 2>/dev/null
Declare the install step in the package metadata, avoid suppressing errors, and keep dependencies pinned with the provided lockfile.
If you use this helper, browser state can persist between runs and the browser may keep running until manually stopped.
The optional browser read-aloud helper uses a persistent Chrome profile and intentionally keeps the browser process open.
const CHROME_PROFILE = process.env.CHROME_PROFILE || path.resolve(__dirname, '../.chrome-profile'); ... Browser will remain open. Press Ctrl+C to exit. ... await new Promise(() => {});Document this behavior clearly, prefer an ephemeral profile for one-off reads, and close the browser automatically when practical.
