github voc0der/ytdl-material v1.1.0
ytdl-material v1.1.0

9 hours ago

🚀 ytdl-material 1.1.0

Permissions were enforced by hiding buttons, so every route behind them was reachable by any account that could log in. All 110 API routes are now guarded. Also adds per-user API tokens, makes RSS feeds private, and fixes scheduled tasks running in the wrong timezone.

This release contains security fixes. Read the Upgrading section before you pull.

✨ Features

  • Added per-user API tokens. Triple-dot menu → Your Profile: create a named token, see it once, revoke it later. Authorization: Bearer, stored only as a SHA-256 hash, max 10 per account. A token acts as its owner across the whole API, so revoking it is how you take access back — the list shows last-used so you know what is still live. #389 #390
  • RSS feeds are private and revocable. Feed URLs carried a plain user uid, which is not a secret and cannot be withdrawn. Generate an RSS token instead; it resolves only on the feed route, so a leaked URL is not a credential for anything else. #389 #390
  • /api/stream and /api/thumbnail/{uid} are documented. The spec described a way to list media and no way to fetch it. #393

🔒 Security

Every route knew who was calling; almost none checked whether they were allowed. Per SECURITY.md this runs internal-facing, so these are not "stranger on the internet" problems — but on multi-user installs every one was reachable by any account that could log in, including deliberately narrow ones. All #389 unless noted.

  • A hard-coded token bypassed the API gate on every install. 4241b401-7236-493e-92b5-b72696b9d853, committed in both trees, checked first and unconditionally. Removed — along with the Public API key, which identified nobody and so authorized nothing.
  • Any account could perform any administrative action. setConfig, restartServer, transferDB, deleteUser, changeRolePermissions. Now a deny-by-default guard table across all 110 routes, failing closed.
  • Any logged-in account could take over any other, including an admin's. /api/auth/changePassword read user_uid from the request body and checked nothing beyond a valid session. Changing someone else's password is now admin-only; changing your own requires your current password.
  • Unlimited password attempts. The rate limiter matched the full URL, so ?x=/api/get on any request skipped it.
  • A share link reached the whole library. Paths matched by substring, and a shared playlist authorized its owner's entire library rather than its own files.
  • Custom download arguments reached yt-dlp as options. The denylist fell to abbreviation: --update-to= passes as --upda, and it replaces the yt-dlp binary. Now allowlisted, URL last after --.
  • /api/config served the whole config to anonymous callers — connection strings, LDAP credentials, OIDC secret, bot tokens, webhook secrets. Now tiered by role.
  • Stored records and paths were trusted. user_uid, sharingEnabled and path were reassignable through the edit routes, and /api/thumbnail/:path served any account's thumbnails.
  • LDAP uids could escape the users folder. A directory naming an account ../.. steered writes outside it. #383
  • Also: the webhook verifies its secret and chat id, password hashes no longer leave the process, and cookie upload is admin-gated before the file hits disk.

🛠️ Fixes

  • Scheduled tasks run in the timezone they were scheduled in. The browser always sent it; the scheduler discarded it. UTC container, Tokyo user, nine-hour miss. #386
  • Byte-range streaming works for non-browser clients. Tail reads and malformed ranges returned 500; a range past EOF hung the connection. Deleting a file mid-playback crashed the server. #393
  • Container downloads and server backups work again, broken since the archiver v8 bump. #389
  • 32-bit ARM was running ffmpeg 5.1.1. The architecture case could never match. Now 7.0.2. c0b19106
  • LDAP usernames containing * can log in. The filter was escaped twice. #384
  • Also: adding a user from settings, category playlists showing other people's files, sidecar paths escaping a media root with a dot in its name, FFPROBE_PATH ignored. #375 #389

📦 Dependencies

  • Three packages decommissioned upstream, replaced rather than bumped: passport-ldapauth/ldapjs → ldapts, node-schedule/cron-parsercroner, fluent-ffmpeg → direct ffmpeg calls. Config preserved, behaviour preserved. Dropped shortid, removed eleven overrides that constrained nothing, refreshed both lockfiles. #368 #374 #375 #376 #377 #381 #384 #385 #386 #394
  • Frontend tests moved karma → vitest and now run in CI. Added an LDAP test harness and the first coverage of that path. Coverage badge is a reproducible 59.7%. #378 #380 #387 b7e2eac3

⚠️ Upgrading

  • RSS feed URLs stop working on multi-user installs. Generate a feed token and send it as Authorization: Bearer.
  • The Public API key is removed, not deprecated. ytdl_use_api_key and ytdl_api_key are deleted from your config on startup, and ?apiKey= is ignored rather than rejected — so scripts fail silently. Use a token from Your Profile.
  • /api/thumbnail/ takes a uid. Regenerate API clients; the apiKey scheme is gone.
  • Scheduled tasks move if your browser timezone differs from the container's — to the time the interface always showed.
  • The webhook needs a secret header. Self-heals on restart. If you proxy it, forward X-Telegram-Bot-Api-Secret-Token.
  • Media outside the configured roots is refused. Check imported libraries, moved folders and changed mounts; refusals are logged.
  • Custom args outside the allowlist are discarded for subscriptions and queued downloads. The global Downloader.custom_args is exempt.
  • Non-admin roles lose two things: saving settings is admin-only, and filemanager now governs file edits — including favourites.
  • LDAP is stricter. An ambiguous searchFilter now refuses the login instead of binding arbitrarily.

Full Changelog: v1.0.10...v1.1.0

Contributors

Don't miss a new ytdl-material release

NewReleases is sending notifications on new releases.