From 5d409c4a66bf299f32d3aa8086dd92426f1c4bec Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Sat, 14 Feb 2026 10:02:54 +0100 Subject: [PATCH 1/4] docs(SKILL.md): clarify API key is optional and use env var consistently --- SKILL.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/SKILL.md b/SKILL.md index e382483..2fe7258 100644 --- a/SKILL.md +++ b/SKILL.md @@ -31,12 +31,19 @@ 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: +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: ```bash curl -s -H "x-search-source: skill" "https://torrentclaw.com/api/v1/search?q=QUERY&sort=seeders&limit=5" ``` +If the user has configured an API key for higher rate limits: + +```bash +curl -s -H "x-search-source: skill" -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \ + "https://torrentclaw.com/api/v1/search?q=QUERY&sort=seeders&limit=5" +``` + **Useful filters** (append as query params): - `type=movie` or `type=show` - `quality=1080p` (also: 720p, 2160p, 480p) @@ -191,7 +198,7 @@ The API automatically detects episode patterns in queries and filters results ac ## API Authentication -TorrentClaw supports optional API key authentication for higher rate limits. +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. **Rate Limit Tiers:** @@ -204,13 +211,11 @@ TorrentClaw supports optional API key authentication for higher rate limits. **Using an API key:** -```bash -# Via header (recommended) -curl -H "Authorization: Bearer tc_live_xxxxx" \ - "https://torrentclaw.com/api/v1/search?q=dune" +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. -# Via query parameter -curl "https://torrentclaw.com/api/v1/search?q=dune&api_key=tc_live_xxxxx" +```bash +curl -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \ + "https://torrentclaw.com/api/v1/search?q=dune" ``` **Rate limit headers in response:** @@ -253,7 +258,7 @@ curl "https://torrentclaw.com/api/v1/search?q=entrevias+S01E05&locale=es" **Search with API key for higher rate limits:** ```bash -curl -H "Authorization: Bearer tc_live_xxxxx" \ +curl -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \ "https://torrentclaw.com/api/v1/search?q=dune&quality=2160p" ``` From d3d6c702ed9b1e68e11cbcdcb64b42e97c404a90 Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Sun, 15 Feb 2026 10:46:34 +0100 Subject: [PATCH 2/4] 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. --- .github/workflows/ci.yml | 18 +++++++++++++++ CHANGELOG.md | 10 ++++++++ README.md | 2 +- SKILL.md | 2 +- scripts/add-torrent.sh | 16 ++++++++++--- scripts/detect-client.sh | 50 ++++++++++++++++++++++++---------------- 6 files changed, 73 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c4e9e1..7822970 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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." diff --git a/CHANGELOG.md b/CHANGELOG.md index 151d870..b9ec3ee 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/README.md b/README.md index dd2c8e1..ffef74e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # torrentclaw-skill -**Version:** 0.1.13 +**Version:** 0.1.16 **License:** MIT **Homepage:** https://torrentclaw.com diff --git a/SKILL.md b/SKILL.md index 2fe7258..afd6ce5 100644 --- a/SKILL.md +++ b/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 diff --git a/scripts/add-torrent.sh b/scripts/add-torrent.sh index 3d45764..84dad87 100755 --- a/scripts/add-torrent.sh +++ b/scripts/add-torrent.sh @@ -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:" >&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 diff --git a/scripts/detect-client.sh b/scripts/detect-client.sh index 0bf2af6..1780b7a 100755 --- a/scripts/detect-client.sh +++ b/scripts/detect-client.sh @@ -73,24 +73,34 @@ elif [ "$aria2_installed" = "true" ]; then fi # --- JSON Output --- -cat < Date: Mon, 16 Feb 2026 11:44:34 +0100 Subject: [PATCH 3/4] fix(security): use --data-urlencode in SKILL.md curl commands --- CHANGELOG.md | 9 +++++++++ SKILL.md | 51 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9ec3ee..71a8ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ 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 diff --git a/SKILL.md b/SKILL.md index afd6ce5..2710958 100644 --- a/SKILL.md +++ b/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.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"]} +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"]} --- # TorrentClaw @@ -31,17 +31,24 @@ 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: +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. ```bash -curl -s -H "x-search-source: skill" "https://torrentclaw.com/api/v1/search?q=QUERY&sort=seeders&limit=5" +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 -H "x-search-source: skill" -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \ - "https://torrentclaw.com/api/v1/search?q=QUERY&sort=seeders&limit=5" +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" ``` **Useful filters** (append as query params): @@ -186,12 +193,15 @@ TorrentClaw supports smart episode filtering with multiple formats: 1. **In query text** (automatic parsing): ```bash -curl "https://torrentclaw.com/api/v1/search?q=breaking+bad+S05E14" +curl -s -G --data-urlencode "q=breaking bad S05E14" \ + "https://torrentclaw.com/api/v1/search" ``` 2. **With explicit parameters**: ```bash -curl "https://torrentclaw.com/api/v1/search?q=breaking+bad&season=5&episode=14" +curl -s -G --data-urlencode "q=breaking bad" \ + -d "season=5" -d "episode=14" \ + "https://torrentclaw.com/api/v1/search" ``` The API automatically detects episode patterns in queries and filters results accordingly. @@ -214,8 +224,9 @@ The API works without authentication (30 req/min anonymous tier). An API key is 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 -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \ - "https://torrentclaw.com/api/v1/search?q=dune" +curl -s -G -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \ + --data-urlencode "q=dune" \ + "https://torrentclaw.com/api/v1/search" ``` **Rate limit headers in response:** @@ -253,28 +264,38 @@ Use `lang=es` filter. **Search for a specific TV episode:** ```bash -curl "https://torrentclaw.com/api/v1/search?q=entrevias+S01E05&locale=es" +curl -s -G --data-urlencode "q=entrevias S01E05" \ + -d "locale=es" \ + "https://torrentclaw.com/api/v1/search" ``` **Search with API key for higher rate limits:** ```bash -curl -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \ - "https://torrentclaw.com/api/v1/search?q=dune&quality=2160p" +curl -s -G -H "Authorization: Bearer $TORRENTCLAW_API_KEY" \ + --data-urlencode "q=dune" \ + -d "quality=2160p" \ + "https://torrentclaw.com/api/v1/search" ``` **Find popular sci-fi movies:** ```bash -curl "https://torrentclaw.com/api/v1/search?genre=Science%20Fiction&type=movie&sort=seeders" +curl -s -G --data-urlencode "genre=Science Fiction" \ + -d "type=movie" -d "sort=seeders" \ + "https://torrentclaw.com/api/v1/search" ``` **Find Dolby Vision / HDR content:** ```bash -curl "https://torrentclaw.com/api/v1/search?q=dune&hdr=dolby_vision&quality=2160p" +curl -s -G --data-urlencode "q=dune" \ + -d "hdr=dolby_vision" -d "quality=2160p" \ + "https://torrentclaw.com/api/v1/search" ``` **Find Atmos audio torrents:** ```bash -curl "https://torrentclaw.com/api/v1/search?q=oppenheimer&audio=atmos" +curl -s -G --data-urlencode "q=oppenheimer" \ + -d "audio=atmos" \ + "https://torrentclaw.com/api/v1/search" ``` **Get cast info for a movie:** From d0a935a8bc6f7fa9f84a554da1e35791ed32f1eb Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Wed, 27 May 2026 15:45:46 +0200 Subject: [PATCH 4/4] ci: port workflows from .github/ to .forgejo/ (Forgejo Actions) GitHub torrentclaw org is shadow-banned; CI is hosted at git.torrentclaw.com now. Move workflows into the runner's natively-watched .forgejo/workflows/ tree and adapt steps to run in the available 'docker'-labeled Forgejo runner without GitHub-only tooling (gh CLI, third-party marketplace actions). - Use container: image to ship the toolchain (no actions/setup-* needed). - Drop GitHub-only marketplace actions in favour of upstream installers invoked over curl/apt. - Where a workflow created a GitHub Release (release.yml), substitute the step with a curl call against the Forgejo Releases API (POST /repos///releases). --- {.github => .forgejo}/workflows/ci.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) rename {.github => .forgejo}/workflows/ci.yml (76%) diff --git a/.github/workflows/ci.yml b/.forgejo/workflows/ci.yml similarity index 76% rename from .github/workflows/ci.yml rename to .forgejo/workflows/ci.yml index 7822970..dde3e99 100644 --- a/.github/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -12,9 +12,14 @@ permissions: jobs: lint-commits: name: Lint commits - runs-on: ubuntu-latest + runs-on: docker + container: + image: docker.io/library/ubuntu:24.04 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 @@ -45,8 +50,13 @@ jobs: lint-scripts: name: Lint shell scripts - runs-on: ubuntu-latest + runs-on: docker + container: + image: docker.io/library/ubuntu:24.04 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 @@ -54,8 +64,13 @@ jobs: security-check: name: Security patterns check - runs-on: ubuntu-latest + 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