Skip to content

feat(tracker-adapters): TorrentLeech hit-and-run + count fix - #2

Merged
patrickdundas merged 3 commits into
mainfrom
feat/torrentleech
Jul 29, 2026
Merged

feat(tracker-adapters): TorrentLeech hit-and-run + count fix#2
patrickdundas merged 3 commits into
mainfrom
feat/torrentleech

Conversation

@patrickdundas

Copy link
Copy Markdown
Owner

Implements the reason this fork exists, using a real logged-in TL header capture.

Hit and run

TL exposes it in the header menu, linking to /profile/<user>/hnr:

<div title="Hit and Run" class="div-menu-item">
  <span class="link" onclick="...'/profile/evergreen99/hnr'"><i class="fa fa-ban"></i>  0</span>
</div>

hitAndRuns stays null when that element is absent rather than defaulting to 0 — a missing counter must not render as "no hit and runs", which would hide the exact condition the field exists to surface.

Bug found in the process

The menu has two shapes, and the existing parser only handled one:

<div title="Uploaded (Seeding)">  <i/> <span>2.39 TB</span> (30) </div>
<div title="Hit and Run">         <i/>  0                        </div>

Seeding/leeching lead with the transfer size and carry the torrent count in trailing parens. Matching "the first number in the element" therefore returned 2 (from "2.39 TB") and 244 (from "244.44 GB") instead of 30 and 9 — so seeding and leeching counts are wrong for every TL user on upstream jordanlambrecht#175. Fixed by matching the parenthesised count for those and the bare number for Hit and Run.

Tests

Upstream had no .div-menu-item fixture, which is why the count bug went unnoticed. Added one taken from real markup, plus regression coverage for both shapes and the null-vs-zero distinction. 17 pass locally; tsc --noEmit and biome check clean.

One unrelated pre-existing failure in chart-transforms.test.ts reproduces identically on unmodified upstream/main — it is timezone-dependent and should pass in CI's UTC.

Still stubbed

requiredRatio and warned remain null — neither appears in the header capture. warned feeds the warned notification event, so it is worth a follow-up if TL surfaces it elsewhere.

🤖 Generated with Claude Code

…hing counts

Populate `hitAndRuns` from the header menu item titled "Hit and Run", which
links to /profile/<user>/hnr. This is the field the fork exists for - TL has
no API and upstream PR jordanlambrecht#175 stubs it null.

Also fixes a real bug in the existing count parsing. The menu has two shapes:

  <div title="Uploaded (Seeding)">  <i/> <span>2.39 TB</span> (30) </div>
  <div title="Hit and Run">         <i/>  0                        </div>

Seeding/leeching lead with the transfer SIZE and put the torrent count in
trailing parens, so the previous "first number in the element" match returned
2 (from "2.39 TB") and 244 (from "244.44 GB") instead of 30 and 9. Seeding and
leeching counts have been wrong for every TL user. Now matches the
parenthesised count for those, and the bare number for Hit and Run.

`hitAndRuns` stays null when the counter is absent rather than defaulting to
0: a missing element must not render as "no hit and runs", which would hide
the exact condition the field exists to surface.

Adds a menu fixture to the tests (upstream had none, which is why the count
bug went unnoticed) plus regression coverage for both shapes and the
null-vs-zero distinction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Knip Code Analysis

Found 8 total issues

Category Count
Unused Dependencies 1
Unused Dev Dependencies 3
Unused Exports 4
View details

Run pnpm knip locally to see the full report.

Use pnpm knip:filter pattern to filter results by file path.


Use /** @public */ JSDoc tags to mark intentionally exported symbols.

@github-actions

Copy link
Copy Markdown

🚫 Security audit failed

Critical Failures

Check File Details
External fetch calls have timeouts src/lib/adapters/btn.ts:69 fetch() call without a timeout signal — add signal: AbortSignal.timeout(ms)

Warnings

Check File Details
No swallowed errors in catch blocks src/lib/adapters/iptorrents.ts:108 catch block swallows error without logging or re-throwing
No swallowed errors in catch blocks src/lib/adapters/iptorrents.ts:114 catch block swallows error without logging or re-throwing
No swallowed errors in catch blocks src/lib/adapters/iptorrents.ts:175 catch block swallows error without logging or re-throwing
No swallowed errors in catch blocks src/lib/adapters/iptorrents.ts:181 catch block swallows error without logging or re-throwing

Passed (36/38)

  • ✅ Auth enforcement on protected routes (per-handler)
  • ✅ No dangerous functions (eval, innerHTML, etc.)
  • ✅ No hardcoded secrets in source
  • ✅ Security headers in next.config.ts
  • ✅ Cookie security (httpOnly, sameSite, secure)
  • ✅ No sensitive fields in API responses
  • ✅ No .env files committed to repo
  • ✅ No raw SQL in API routes
  • ✅ No fetch/redirect with unvalidated URLs in routes
  • ✅ Timing-safe comparison for secret values
  • ✅ No raw SQL migration files (schema-first only)
  • ✅ Docker container runs as non-root user
  • ✅ Public routes match proxy allowlist
  • ✅ File delete operations have path traversal defense
  • ✅ Password hashing uses Argon2 (not SHA-256/bcrypt)
  • ✅ Encrypted columns written via encrypt()
  • ✅ TOTP 2FA flow integrity
  • ✅ Emergency lockdown flow integrity
  • ✅ Scrub & delete (nuke) flow integrity
  • ✅ Backup restore flow integrity
  • ✅ Login flow integrity
  • ✅ Auth result checked before proceeding
  • ✅ Backup password inputs bounded before key derivation
  • ✅ Webhook delivery fetch uses redirect: "error"
  • ✅ SESSION_SECRET minimum-length guard in auth/crypto modules
  • ✅ Notification URL validators include SSRF protection
  • ✅ Dockerfile does not COPY sensitive files
  • ✅ No secret env vars in client components
  • ✅ Adapter Cookie headers guard against injection
  • ✅ Adapter files do not log credential values
  • ✅ No console.log in API routes
  • ✅ No TODO/FIXME in security-critical files
  • ✅ JSON.parse wrapped in try-catch
  • ✅ Request body size validation on upload routes
  • ✅ BigInt fields use string serialization
  • ✅ No raw error messages in API responses

Summary: 36/38 checks passed
1 critical failure(s)
— 1 warning(s)

See scripts/security-audit.ts for check definitions and SECURITY.md for the full security architecture.

patrickdundas and others added 2 commits July 28, 2026 22:23
Upstream's release.yml cannot publish from a fork. Three blockers, none of
them cosmetic:

- "Log in to Docker Hub" uses DOCKERHUB_* secrets that do not exist here.
  docker/login-action fails on empty credentials, aborting the job BEFORE the
  build/push step ran, so no image was ever produced.
- The build tagged docker.io/jordyjordy/tracker-tracker, upstream's Docker Hub
  namespace, which this fork cannot push to.
- "Sync README to Docker Hub" has the same missing secrets and target.

Removed all three. GHCR only; IMAGE_NAME already resolves to our repo path.

Also makes Trivy non-blocking (exit-code 0) with if: always() on the SARIF
upload. The image is pushed BEFORE the scan runs, so failing hard there never
prevented a vulnerable image shipping - it only skipped the SARIF upload and
the GitHub Release, leaving the run permanently red and the findings invisible
in the Security tab. The current findings (undici, vite) come from upstream's
lockfile.

Documents all of it in FORK.md, since .github/ is now the main conflict
surface on upstream syncs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Trivy failed the build on undici and vite CVEs inherited from upstream's
lockfile. Both are dev-only, reaching the image because the schema-deps
Dockerfile stage runs a full pnpm install (devDependencies included) and the
runner copies that node_modules in for drizzle-kit.

undici: pinned to ^7.28.0 via pnpm.overrides, resolving to 7.29.0. Kept inside
the 7.x line deliberately - an unbounded >= range pulled 8.9.0, a major jump
for a jsdom dependency with no upside.

vite: could not be moved. vitest 4.1.4 holds it at 7.3.2; 7.3.6 exists but
neither overrides nor --force re-resolves it. Documented in .trivyignore
instead, which is honest here rather than evasive: the CVE is a server.fs.deny
bypass via Windows alternate paths in vite's DEV SERVER, and this image is
Linux serving Next.js standalone with vite never started. Exposure is nil.
That file already carries the same class of suppression for esbuild.

The root cause - devDependencies shipping in the production image - is
recorded in FORK.md as follow-up. Not attempted here because schema-sync runs
at container startup, so getting it wrong breaks deploys rather than builds.

Adapter tests: 248 pass with the bumped undici.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@patrickdundas
patrickdundas merged commit e80ef42 into main Jul 29, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant