fix(security): eliminate shell injection and add input validation
Replace unsafe string interpolation in aria2 RPC JSON construction with jq --arg for proper escaping. Add magnet URL format validation to reject arbitrary input. Refactor detect-client.sh JSON output to use jq. Add CI security check to prevent regression. Resolves VirusTotal "Suspicious" classification caused by the shell injection vulnerability in add-torrent.sh.
This commit is contained in:
parent
5d409c4a66
commit
d3d6c702ed
6 changed files with 73 additions and 25 deletions
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
|
|
@ -51,3 +51,21 @@ jobs:
|
|||
|
||||
- name: Run ShellCheck
|
||||
run: shellcheck scripts/*.sh
|
||||
|
||||
security-check:
|
||||
name: Security patterns check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for unsafe string interpolation in curl payloads
|
||||
run: |
|
||||
# Flag inline JSON in double quotes (allows shell interpolation).
|
||||
# Safe patterns: curl -d '{}' (single quotes) or curl -d "$var" (pre-built payload).
|
||||
if grep -rPn 'curl.*-d\s*"[{]' scripts/*.sh; then
|
||||
echo ""
|
||||
echo "ERROR: Found curl -d with inline JSON in double quotes."
|
||||
echo "Use jq --arg to build JSON safely and pass via variable."
|
||||
exit 1
|
||||
fi
|
||||
echo "No unsafe curl patterns found."
|
||||
|
|
|
|||
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -2,6 +2,16 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.1.16] - 2026-02-14
|
||||
|
||||
### Security
|
||||
|
||||
- Fix shell injection vulnerability in aria2 RPC JSON construction (add-torrent.sh)
|
||||
- Add magnet URL format validation before passing to torrent clients
|
||||
- Replace shell string interpolation with `jq --arg` for safe JSON construction
|
||||
- Refactor detect-client.sh JSON output to use `jq` instead of heredoc interpolation
|
||||
- Add CI security pattern check to prevent unsafe curl payload regression
|
||||
|
||||
## [0.1.13] - 2026-02-13
|
||||
|
||||
### Features
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# torrentclaw-skill
|
||||
|
||||
**Version:** 0.1.13
|
||||
**Version:** 0.1.16
|
||||
**License:** MIT
|
||||
**Homepage:** https://torrentclaw.com
|
||||
|
||||
|
|
|
|||
2
SKILL.md
2
SKILL.md
|
|
@ -2,7 +2,7 @@
|
|||
name: torrentclaw
|
||||
description: Search and download torrents via TorrentClaw. Use when the user asks to find, search, or download movies, TV shows, or torrents. Detects local torrent clients (Transmission, aria2) and adds magnets directly, or offers magnet link copy and .torrent file download. Supports filtering by type (movie/show), genre, year, quality (480p-2160p), rating, language, and season/episode (S01E05, 1x05). Features API key authentication with tiered rate limits, AI-verified matching, and quality scoring (0-100). Returns titles with posters, ratings, and torrents with magnet links and quality scores.
|
||||
license: MIT
|
||||
metadata: {"version": "0.1.13", "repository": "https://github.com/torrentclaw/torrentclaw-skill", "homepage": "https://torrentclaw.com", "openclaw": {"emoji": "🎬", "os": ["darwin", "linux", "win32"], "requires": {"bins": ["curl", "bash", "jq"], "env": ["TORRENTCLAW_API_KEY"]}, "primaryEnv": "TORRENTCLAW_API_KEY"}, "tags": ["torrent", "movies", "tv-shows", "download", "media", "entertainment", "magnet", "transmission", "aria2", "search", "4k", "hdr"]}
|
||||
metadata: {"version": "0.1.16", "repository": "https://github.com/torrentclaw/torrentclaw-skill", "homepage": "https://torrentclaw.com", "openclaw": {"emoji": "🎬", "os": ["darwin", "linux", "win32"], "requires": {"bins": ["curl", "bash", "jq"], "env": ["TORRENTCLAW_API_KEY"]}, "primaryEnv": "TORRENTCLAW_API_KEY"}, "tags": ["torrent", "movies", "tv-shows", "download", "media", "entertainment", "magnet", "transmission", "aria2", "search", "4k", "hdr"]}
|
||||
---
|
||||
|
||||
# TorrentClaw
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ if [ -z "$magnet_url" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# --- Validate magnet URL format ---
|
||||
if [[ ! "$magnet_url" =~ ^magnet:\?xt=urn:btih:[a-fA-F0-9]{40} ]] && \
|
||||
[[ ! "$magnet_url" =~ ^magnet:\?xt=urn:btih:[a-zA-Z2-7]{32} ]]; then
|
||||
echo "Error: Invalid magnet URL format. Expected: magnet:?xt=urn:btih:<hash>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Auto-detect client if not specified ---
|
||||
if [ -z "$client" ]; then
|
||||
if command -v transmission-remote >/dev/null 2>&1; then
|
||||
|
|
@ -76,11 +83,14 @@ case "$client" in
|
|||
# Check if aria2 RPC is running
|
||||
if curl -sf http://localhost:6800/jsonrpc -d '{"jsonrpc":"2.0","id":"test","method":"aria2.getVersion"}' >/dev/null 2>&1; then
|
||||
echo "Adding to aria2 via RPC..."
|
||||
dir_param=""
|
||||
if [ -n "$download_dir" ]; then
|
||||
dir_param=",{\"dir\":\"$download_dir\"}"
|
||||
payload=$(jq -n --arg url "$magnet_url" --arg dir "$download_dir" \
|
||||
'{"jsonrpc":"2.0","id":"add","method":"aria2.addUri","params":[[$url],{"dir":$dir}]}')
|
||||
else
|
||||
payload=$(jq -n --arg url "$magnet_url" \
|
||||
'{"jsonrpc":"2.0","id":"add","method":"aria2.addUri","params":[[$url]]}')
|
||||
fi
|
||||
result=$(curl -sf http://localhost:6800/jsonrpc -d "{\"jsonrpc\":\"2.0\",\"id\":\"add\",\"method\":\"aria2.addUri\",\"params\":[[\"$magnet_url\"]$dir_param]}")
|
||||
result=$(curl -sf http://localhost:6800/jsonrpc -d "$payload")
|
||||
if echo "$result" | grep -q '"result"'; then
|
||||
echo "Torrent added to aria2 successfully."
|
||||
else
|
||||
|
|
|
|||
|
|
@ -73,24 +73,34 @@ elif [ "$aria2_installed" = "true" ]; then
|
|||
fi
|
||||
|
||||
# --- JSON Output ---
|
||||
cat <<EOF
|
||||
{
|
||||
"os": "$os_name",
|
||||
"distro": "$distro",
|
||||
"clients": {
|
||||
"transmission": {
|
||||
"installed": $transmission_installed,
|
||||
"path": $([ -n "$transmission_path" ] && echo "\"$transmission_path\"" || echo "null"),
|
||||
"variant": "$transmission_variant",
|
||||
"remoteAvailable": $transmission_remote_available,
|
||||
"daemonRunning": $transmission_daemon
|
||||
jq -n \
|
||||
--arg os "$os_name" \
|
||||
--arg distro "$distro" \
|
||||
--argjson t_installed "$transmission_installed" \
|
||||
--arg t_path "${transmission_path:-}" \
|
||||
--arg t_variant "$transmission_variant" \
|
||||
--argjson t_remote "$transmission_remote_available" \
|
||||
--argjson t_daemon "$transmission_daemon" \
|
||||
--argjson a_installed "$aria2_installed" \
|
||||
--arg a_path "${aria2_path:-}" \
|
||||
--argjson a_daemon "$aria2_daemon" \
|
||||
--arg preferred "$preferred" \
|
||||
'{
|
||||
os: $os,
|
||||
distro: $distro,
|
||||
clients: {
|
||||
transmission: {
|
||||
installed: $t_installed,
|
||||
path: (if $t_path == "" then null else $t_path end),
|
||||
variant: $t_variant,
|
||||
remoteAvailable: $t_remote,
|
||||
daemonRunning: $t_daemon
|
||||
},
|
||||
"aria2": {
|
||||
"installed": $aria2_installed,
|
||||
"path": $([ -n "$aria2_path" ] && echo "\"$aria2_path\"" || echo "null"),
|
||||
"daemonRunning": $aria2_daemon
|
||||
aria2: {
|
||||
installed: $a_installed,
|
||||
path: (if $a_path == "" then null else $a_path end),
|
||||
daemonRunning: $a_daemon
|
||||
}
|
||||
},
|
||||
"preferred": "$preferred"
|
||||
}
|
||||
EOF
|
||||
preferred: $preferred
|
||||
}'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue