feat(taosnet): client passkey + web-seed + torrent_url wiring, DHT off - #1728
Conversation
Closed-swarm client plumbing in torrent_downloader.py: inject the node's account-bound passkey into the private tracker announce, add BEP-19 web seeds, implement torrent_url metadata fetch, and disable DHT session-wide (taOSnet is a private authenticated mesh). New pure helpers in taosnet/torrent_client.py (announce/scrape/metadata URLs). Passkey acquisition + 401 re-announce stay with the caller (DownloadManager, has account context) and land next.
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
📝 WalkthroughWalkthroughThis PR adds a new ChangestaOSnet torrent client integration
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant TorrentDownloader
participant httpx
participant libtorrent
Caller->>TorrentDownloader: download(task_id, magnet_or_torrent, dest, passkey, web_seeds)
alt torrent_url source
TorrentDownloader->>httpx: GET .torrent file
httpx-->>TorrentDownloader: torrent bytes
TorrentDownloader->>libtorrent: bdecode + torrent_info
else magnet source
TorrentDownloader->>libtorrent: parse_magnet_uri
end
TorrentDownloader->>TorrentDownloader: _build_params(passkey, web_seeds)
TorrentDownloader->>libtorrent: add_torrent(params)
TorrentDownloader-->>Caller: TorrentTask
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| "enable_dht": True, | ||
| # taOSnet is a closed, authenticated swarm: no DHT. Private torrents | ||
| # (BEP-27) also disable it per-torrent, but keep it off session-wide. | ||
| "enable_dht": False, |
There was a problem hiding this comment.
WARNING: Disabling DHT session-wide breaks existing public/trackerless torrent downloads
This contradicts the PR's claim that "Existing callers unaffected." Public magnets without embedded trackers rely on DHT to discover peers; with enable_dht: False they will hit TorrentTimeout and fall back to HTTP on every download. The PR comment notes "Private torrents (BEP-27) also disable it per-torrent" — libtorrent already honours that per-torrent via the private flag, so the coarse session-wide disable is unnecessary and harms non-taOSnet traffic. Prefer keeping DHT on at the session level (or use a dedicated session for the taOSnet swarm), and rely on per-torrent private handling. Note should_use_torrent's docstring still promises "downloads via magnet always work when libtorrent is installed," which is no longer true for trackerless magnets.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| resp = httpx.get(url, timeout=30.0, follow_redirects=True) | ||
| resp.raise_for_status() | ||
| ti = lt.torrent_info(lt.bdecode(resp.content)) | ||
| params = lt.add_torrent_params() |
There was a problem hiding this comment.
WARNING: torrent_url is fetched with no host/scheme validation and follow_redirects=True (SSRF risk)
url originates from the manifest (torrent_url) and is fetched server-side. A malicious or compromised catalog entry could point it at internal services (e.g. http://169.254.169.254/..., http://localhost:...); the redirect-following makes it trivial to pivot to internal endpoints. Validate that the scheme is http/https and that the host is not loopback/link-local/private, and constrain or disable redirects. There is also no cap on response size, so a large response is a memory-exhaustion vector before lt.bdecode runs.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| ti = lt.torrent_info(lt.bdecode(resp.content)) | ||
| params = lt.add_torrent_params() | ||
| params.ti = ti | ||
| return params |
There was a problem hiding this comment.
SUGGESTION: Wrap bencode decode / torrent parse in TorrentError
lt.bdecode(resp.content) and lt.torrent_info(...) raise libtorrent-specific exceptions (and lt.bdecode raises on malformed input). An attacker-controlled or corrupted .torrent will surface as an unhandled lt_error instead of the project's TorrentError, making callers' except TorrentError fallbacks dead code for this path. Catch and re-raise as TorrentError.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| params.save_path = str(save_dir) | ||
| if passkey: | ||
| from tinyagentos.taosnet.torrent_client import announce_url | ||
|
|
There was a problem hiding this comment.
SUGGESTION: announce_url can raise a raw ValueError that escapes download()
if passkey: is truthy for a whitespace-only string, so announce_url(" ") raises ValueError, which propagates out of _build_params/download uncaught. Since every other failure mode here raises TorrentError, wrap the URL construction (or pre-validate the passkey) so callers receive a consistent error type.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
Code Review SummaryStatus: 4 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (4 files)
Fix these issues in Kilo Cloud Reviewed by hy3-20260706:free · Input: 72.6K · Output: 12.7K · Cached: 109.3K |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
tinyagentos/taosnet/torrent_client.py (1)
41-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueType hints declare
strbutNoneis silently accepted.The
orshort-circuit meansNonenever hits.strip(), so it's handled gracefully today, but the signature (value: str) and tests passingNoneare inconsistent with the declared type. ConsiderOptional[str]ifNoneis an expected input, or drop the None test case if it isn't.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tinyagentos/taosnet/torrent_client.py` around lines 41 - 44, The _segment helper currently accepts None in practice but is annotated as taking only str, so align the contract in _segment to match intended usage. If None should be supported, update the value parameter type to Optional[str] and keep the existing validation; if not, remove the None-based test case and ensure callers only pass strings. Use the _segment function and its related tests in torrent_client.py to make the signature and test expectations consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tinyagentos/torrent_downloader.py`:
- Around line 187-198: Wrap failures from _params_from_torrent_url in
TorrentError so download() has a consistent torrent-only failure contract. Catch
httpx exceptions around httpx.get and resp.raise_for_status in
_params_from_torrent_url, then rethrow them as TorrentError with the underlying
details preserved. Keep the existing flow in torrent_downloader.py so callers
relying on TorrentError/TorrentTimeout can still fall back to HTTP.
- Around line 187-198: The _params_from_torrent_url method currently fetches any
http(s) torrent_url, so add a taOS origin allowlist check before calling
httpx.get() and reject non-taOS URLs up front. Use the _params_from_torrent_url
symbol to locate the fetch path, and ensure the validation blocks both direct
URLs and redirect-based bypasses while preserving the existing
torrent_info/add_torrent_params flow for allowed taOS origins only.
---
Nitpick comments:
In `@tinyagentos/taosnet/torrent_client.py`:
- Around line 41-44: The _segment helper currently accepts None in practice but
is annotated as taking only str, so align the contract in _segment to match
intended usage. If None should be supported, update the value parameter type to
Optional[str] and keep the existing validation; if not, remove the None-based
test case and ensure callers only pass strings. Use the _segment function and
its related tests in torrent_client.py to make the signature and test
expectations consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 41845cf6-8fc6-42f3-8fed-afc504aab2d9
📒 Files selected for processing (4)
tests/taosnet/test_torrent_client.pytests/taosnet/test_torrent_downloader_taosnet.pytinyagentos/taosnet/torrent_client.pytinyagentos/torrent_downloader.py
| def _params_from_torrent_url(self, url: str): | ||
| """Fetch a .torrent over HTTP (the public taOSnet metadata path) and | ||
| build add_torrent_params from it. Used when a manifest supplies a | ||
| ``torrent_url`` instead of a magnet.""" | ||
| import httpx | ||
|
|
||
| resp = httpx.get(url, timeout=30.0, follow_redirects=True) | ||
| resp.raise_for_status() | ||
| ti = lt.torrent_info(lt.bdecode(resp.content)) | ||
| params = lt.add_torrent_params() | ||
| params.ti = ti | ||
| return params |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Wrap httpx errors into TorrentError for a consistent failure contract.
download()'s docstring says callers should catch torrent-specific exceptions and fall back to HTTP; httpx.get/raise_for_status() failures (connect errors, timeouts, non-2xx) propagate as raw httpx exceptions here, not TorrentError, so a caller only catching TorrentError/TorrentTimeout would miss them and skip the fallback path.
🛡️ Suggested fix
def _params_from_torrent_url(self, url: str):
import httpx
- resp = httpx.get(url, timeout=30.0, follow_redirects=True)
- resp.raise_for_status()
+ try:
+ resp = httpx.get(url, timeout=30.0, follow_redirects=True)
+ resp.raise_for_status()
+ except httpx.HTTPError as e:
+ raise TorrentError(f"failed to fetch torrent_url: {e}") from e
ti = lt.torrent_info(lt.bdecode(resp.content))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _params_from_torrent_url(self, url: str): | |
| """Fetch a .torrent over HTTP (the public taOSnet metadata path) and | |
| build add_torrent_params from it. Used when a manifest supplies a | |
| ``torrent_url`` instead of a magnet.""" | |
| import httpx | |
| resp = httpx.get(url, timeout=30.0, follow_redirects=True) | |
| resp.raise_for_status() | |
| ti = lt.torrent_info(lt.bdecode(resp.content)) | |
| params = lt.add_torrent_params() | |
| params.ti = ti | |
| return params | |
| def _params_from_torrent_url(self, url: str): | |
| """Fetch a .torrent over HTTP (the public taOSnet metadata path) and | |
| build add_torrent_params from it. Used when a manifest supplies a | |
| ``torrent_url`` instead of a magnet.""" | |
| import httpx | |
| try: | |
| resp = httpx.get(url, timeout=30.0, follow_redirects=True) | |
| resp.raise_for_status() | |
| except httpx.HTTPError as e: | |
| raise TorrentError(f"failed to fetch torrent_url: {e}") from e | |
| ti = lt.torrent_info(lt.bdecode(resp.content)) | |
| params = lt.add_torrent_params() | |
| params.ti = ti | |
| return params |
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 192-192: Request-controlled URL passed to httpx; validate against an allowlist to prevent SSRF.
Context: httpx.get(url, timeout=30.0, follow_redirects=True)
Note: [CWE-918] Server-Side Request Forgery (SSRF).
(avoid-ssrf)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tinyagentos/torrent_downloader.py` around lines 187 - 198, Wrap failures from
_params_from_torrent_url in TorrentError so download() has a consistent
torrent-only failure contract. Catch httpx exceptions around httpx.get and
resp.raise_for_status in _params_from_torrent_url, then rethrow them as
TorrentError with the underlying details preserved. Keep the existing flow in
torrent_downloader.py so callers relying on TorrentError/TorrentTimeout can
still fall back to HTTP.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Files mentioning torrent_url ==\n'
rg -n "torrent_url|_params_from_torrent_url|torrent_url must|allowlist|allow-list|trusted taOSnet|taosnet" .
printf '\n== Candidate file outline ==\n'
ast-grep outline tinyagentos/torrent_downloader.py --view expanded || true
printf '\n== Surrounding lines in tinyagentos/torrent_downloader.py ==\n'
nl -ba tinyagentos/torrent_downloader.py | sed -n '1,280p'Repository: jaylfc/taOS
Length of output: 14207
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== tinyagentos/torrent_downloader.py (relevant section) ==\n'
sed -n '89,250p' tinyagentos/torrent_downloader.py
printf '\n== tinyagentos/taosnet/torrent_client.py ==\n'
sed -n '1,220p' tinyagentos/taosnet/torrent_client.py
printf '\n== tests/test_torrent_downloader.py ==\n'
sed -n '1,180p' tests/test_torrent_downloader.py
printf '\n== tests/taosnet/test_torrent_downloader_taosnet.py ==\n'
sed -n '1,220p' tests/taosnet/test_torrent_downloader_taosnet.py
printf '\n== manifest-related search ==\n'
rg -n "torrent_url|magnet|info_hash|web_seeds|taosnet" tinyagentos docs tests | sed -n '1,220p'Repository: jaylfc/taOS
Length of output: 28455
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== tinyagentos/torrent_downloader.py top section ==\n'
sed -n '1,140p' tinyagentos/torrent_downloader.py
printf '\n== Search for torrent_url validation / parsing ==\n'
rg -n "urlparse|netloc|hostname|scheme|torrent_url.*https|torrent_url.*allow|allowlist.*torrent_url|manifest.*torrent_url" tinyagentos tests docs
printf '\n== docs/design/model-torrent-mesh.md excerpts ==\n'
sed -n '90,180p' docs/design/model-torrent-mesh.md
printf '\n== relevant manifest tests / validators ==\n'
rg -n "manifest|variant|torrent_url|magnet|info_hash" tests tinyagentos | sed -n '1,220p'Repository: jaylfc/taOS
Length of output: 50369
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Search for torrent_base / manifest generation ==\n'
rg -n "torrent_base|announce_url\(|web_seed_url\(|torrent_url\s*=|info_hash\s*=|model torrent|manifest generator|generated catalog|origin restrictions|taOS-hosted" tinyagentos docs tests scripts
printf '\n== tinyagentos/taosnet/__init__.py ==\n'
sed -n '1,120p' tinyagentos/taosnet/__init__.py
printf '\n== nearby manifest/design lines ==\n'
sed -n '1,120p' docs/design/model-torrent-mesh.mdRepository: jaylfc/taOS
Length of output: 7496
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== code mentions of torrent_base ==\n'
rg -n "torrent_base" tinyagentos tests scripts docs
printf '\n== code mentions of torrent_url assignment/building ==\n'
rg -n "torrent_url\s*=|web_seeds\s*=|announce_url\(|web_seed_url\(" tinyagentos tests scripts
printf '\n== likely manifest-related files ==\n'
fd -a "manifest" tinyagentos scripts tests docs | sed -n '1,200p'Repository: jaylfc/taOS
Length of output: 2007
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== scripts/audit-manifests.py relevant section ==\n'
sed -n '1,260p' scripts/audit-manifests.py
printf '\n== search inside audit-manifests for torrent fields ==\n'
rg -n "torrent_url|web_seeds|info_hash|torrent_base|taos|https|origin|license" scripts/audit-manifests.pyRepository: jaylfc/taOS
Length of output: 8624
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== references to audit-manifests.py ==\n'
rg -n "audit-manifests\.py|audit_manifest\(|audit manifests|manifest audit" .github scripts README.md docs tinyagentos tests
printf '\n== workflow files ==\n'
fd -a ".github/workflows" .github || true
fd -a "*.yml" .github/workflows 2>/dev/null || true
fd -a "*.yaml" .github/workflows 2>/dev/null || trueRepository: jaylfc/taOS
Length of output: 3111
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== audit-manifests in workflows ==\n'
rg -n "audit-manifests|manifest|torrent_url|taosnet|provenance" .github/workflows
printf '\n== release workflow excerpt ==\n'
sed -n '1,260p' .github/workflows/release.yml
printf '\n== tests workflow excerpt ==\n'
sed -n '1,260p' .github/workflows/tests.ymlRepository: jaylfc/taOS
Length of output: 1108
Restrict torrent_url to taOS origins before fetching
This still trusts any http(s) URL and follows redirects. The manifest audit only enforces the taOS prefix for generated catalog files, so a bad manifest can still reach this fetch. Enforce the taOS allowlist here before httpx.get().
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 192-192: Request-controlled URL passed to httpx; validate against an allowlist to prevent SSRF.
Context: httpx.get(url, timeout=30.0, follow_redirects=True)
Note: [CWE-918] Server-Side Request Forgery (SSRF).
(avoid-ssrf)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tinyagentos/torrent_downloader.py` around lines 187 - 198, The
_params_from_torrent_url method currently fetches any http(s) torrent_url, so
add a taOS origin allowlist check before calling httpx.get() and reject non-taOS
URLs up front. Use the _params_from_torrent_url symbol to locate the fetch path,
and ensure the validation blocks both direct URLs and redirect-based bypasses
while preserving the existing torrent_info/add_torrent_params flow for allowed
taOS origins only.
Source: Linters/SAST tools
Next taOSnet client slice, built against the now-live taos.my contract (docs/taosnet.md).
torrent_downloader.py:
download()gains optionalpasskey+web_seeds; a new_build_paramsinjects the account-bound passkey into the private tracker announce (https://tracker.taos.my/<passkey>/announce), adds BEP-19 web seeds, and implementstorrent_urlmetadata fetch (was a NotImplemented raise). DHT is now disabled session-wide (taOSnet is a closed, authenticated mesh; private torrents disable it per-torrent too). Existing callers unaffected (new args are optional + trailing).taosnet/torrent_client.py (new): pure announce/scrape/metadata URL builders (passkey percent-encoded as a single path segment).
Boundary: passkey acquisition (GET /api/taosnet/passkey) and 401 re-announce stay with the DownloadManager (it has the account session); this slice is the passkey-agnostic wiring it will call. That integration lands next.
Tests: 8 pure URL tests (CI) + 6 libtorrent tests that skip where libtorrent is absent (so CI stays green) and were verified on the Pi against real libtorrent 2.0.13 (14/14 pass) in an isolated venv, not the production install.
Summary by CodeRabbit
New Features
.torrentURLs, not just magnet links.Bug Fixes