Compare commits

..

No commits in common. "main" and "v0.1.14" have entirely different histories.

6 changed files with 44 additions and 142 deletions

View file

@ -12,14 +12,9 @@ permissions:
jobs:
lint-commits:
name: Lint commits
runs-on: docker
container:
image: docker.io/library/ubuntu:24.04
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Install git + grep
run: apt-get update && apt-get install -y --no-install-recommends git ca-certificates
- uses: actions/checkout@v4
with:
fetch-depth: 0
@ -50,37 +45,9 @@ jobs:
lint-scripts:
name: Lint shell scripts
runs-on: docker
container:
image: docker.io/library/ubuntu:24.04
runs-on: ubuntu-latest
steps:
- name: Install shellcheck
run: apt-get update && apt-get install -y --no-install-recommends shellcheck git ca-certificates
- uses: actions/checkout@v4
- name: Run ShellCheck
run: shellcheck scripts/*.sh
security-check:
name: Security patterns check
runs-on: docker
container:
image: docker.io/library/ubuntu:24.04
steps:
- name: Install grep + git
run: apt-get update && apt-get install -y --no-install-recommends git grep ca-certificates
- 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."

View file

@ -2,25 +2,6 @@
All notable changes to this project will be documented in this file.
## [0.1.17] - 2026-02-16
### Security
- Fix shell injection vulnerability in SKILL.md curl search commands
- Replace direct URL interpolation with `curl -G --data-urlencode` for all user-supplied query parameters
- Add explicit instruction to never interpolate user input directly into URL strings
- Update all curl examples (search, episode, common patterns, auth) to use safe parameter encoding
## [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

View file

@ -1,6 +1,6 @@
# torrentclaw-skill
**Version:** 0.1.16
**Version:** 0.1.13
**License:** MIT
**Homepage:** https://torrentclaw.com

View file

@ -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.17", "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.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"]}
---
# TorrentClaw
@ -31,24 +31,10 @@ The script outputs JSON with detected clients and OS info. Remember the result f
### Step 2: Search for content
Query the TorrentClaw API. Always include the `x-search-source: skill` header for analytics. The API key is **optional** — anonymous usage allows 30 req/min, which is enough for casual searches. Only include the `Authorization` header if `$TORRENTCLAW_API_KEY` is set.
**Important:** Always use `--data-urlencode` for user-supplied values to prevent shell injection. Never interpolate user input directly into the URL string.
Query the TorrentClaw API. Always include the `x-search-source: skill` header for analytics:
```bash
curl -s -G -H "x-search-source: skill" \
--data-urlencode "q=QUERY" \
-d "sort=seeders" -d "limit=5" \
"https://torrentclaw.com/api/v1/search"
```
If the user has configured an API key for higher rate limits:
```bash
curl -s -G -H "x-search-source: skill" -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \
--data-urlencode "q=QUERY" \
-d "sort=seeders" -d "limit=5" \
"https://torrentclaw.com/api/v1/search"
curl -s -H "x-search-source: skill" "https://torrentclaw.com/api/v1/search?q=QUERY&sort=seeders&limit=5"
```
**Useful filters** (append as query params):
@ -193,22 +179,19 @@ TorrentClaw supports smart episode filtering with multiple formats:
1. **In query text** (automatic parsing):
```bash
curl -s -G --data-urlencode "q=breaking bad S05E14" \
"https://torrentclaw.com/api/v1/search"
curl "https://torrentclaw.com/api/v1/search?q=breaking+bad+S05E14"
```
2. **With explicit parameters**:
```bash
curl -s -G --data-urlencode "q=breaking bad" \
-d "season=5" -d "episode=14" \
"https://torrentclaw.com/api/v1/search"
curl "https://torrentclaw.com/api/v1/search?q=breaking+bad&season=5&episode=14"
```
The API automatically detects episode patterns in queries and filters results accordingly.
## API Authentication
The API works without authentication (30 req/min anonymous tier). An API key is **only needed** if you require higher rate limits for heavy or automated usage.
TorrentClaw supports optional API key authentication for higher rate limits.
**Rate Limit Tiers:**
@ -221,12 +204,13 @@ The API works without authentication (30 req/min anonymous tier). An API key is
**Using an API key:**
Always use the `$TORRENTCLAW_API_KEY` environment variable via the `Authorization` header. Avoid passing the key as a query parameter — query strings may be logged in server access logs and HTTP referrer headers.
```bash
curl -s -G -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \
--data-urlencode "q=dune" \
"https://torrentclaw.com/api/v1/search"
# Via header (recommended)
curl -H "Authorization: Bearer tc_live_xxxxx" \
"https://torrentclaw.com/api/v1/search?q=dune"
# Via query parameter
curl "https://torrentclaw.com/api/v1/search?q=dune&api_key=tc_live_xxxxx"
```
**Rate limit headers in response:**
@ -264,38 +248,28 @@ Use `lang=es` filter.
**Search for a specific TV episode:**
```bash
curl -s -G --data-urlencode "q=entrevias S01E05" \
-d "locale=es" \
"https://torrentclaw.com/api/v1/search"
curl "https://torrentclaw.com/api/v1/search?q=entrevias+S01E05&locale=es"
```
**Search with API key for higher rate limits:**
```bash
curl -s -G -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \
--data-urlencode "q=dune" \
-d "quality=2160p" \
"https://torrentclaw.com/api/v1/search"
curl -H "Authorization: Bearer tc_live_xxxxx" \
"https://torrentclaw.com/api/v1/search?q=dune&quality=2160p"
```
**Find popular sci-fi movies:**
```bash
curl -s -G --data-urlencode "genre=Science Fiction" \
-d "type=movie" -d "sort=seeders" \
"https://torrentclaw.com/api/v1/search"
curl "https://torrentclaw.com/api/v1/search?genre=Science%20Fiction&type=movie&sort=seeders"
```
**Find Dolby Vision / HDR content:**
```bash
curl -s -G --data-urlencode "q=dune" \
-d "hdr=dolby_vision" -d "quality=2160p" \
"https://torrentclaw.com/api/v1/search"
curl "https://torrentclaw.com/api/v1/search?q=dune&hdr=dolby_vision&quality=2160p"
```
**Find Atmos audio torrents:**
```bash
curl -s -G --data-urlencode "q=oppenheimer" \
-d "audio=atmos" \
"https://torrentclaw.com/api/v1/search"
curl "https://torrentclaw.com/api/v1/search?q=oppenheimer&audio=atmos"
```
**Get cast info for a movie:**

View file

@ -44,13 +44,6 @@ 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
@ -83,14 +76,11 @@ 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
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]]}')
dir_param=",{\"dir\":\"$download_dir\"}"
fi
result=$(curl -sf http://localhost:6800/jsonrpc -d "$payload")
result=$(curl -sf http://localhost:6800/jsonrpc -d "{\"jsonrpc\":\"2.0\",\"id\":\"add\",\"method\":\"aria2.addUri\",\"params\":[[\"$magnet_url\"]$dir_param]}")
if echo "$result" | grep -q '"result"'; then
echo "Torrent added to aria2 successfully."
else

View file

@ -73,34 +73,24 @@ elif [ "$aria2_installed" = "true" ]; then
fi
# --- JSON Output ---
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: $a_installed,
path: (if $a_path == "" then null else $a_path end),
daemonRunning: $a_daemon
}
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
},
preferred: $preferred
}'
"aria2": {
"installed": $aria2_installed,
"path": $([ -n "$aria2_path" ] && echo "\"$aria2_path\"" || echo "null"),
"daemonRunning": $aria2_daemon
}
},
"preferred": "$preferred"
}
EOF