Files
sofarr/CHANGELOG.md
Gronod 1bef14d590
All checks were successful
Build and Push Docker Image / build (push) Successful in 41s
Docs Check / Markdown lint (push) Successful in 48s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 57s
CI / Security audit (push) Successful in 1m23s
CI / Tests & coverage (push) Successful in 1m36s
Docs Check / Mermaid diagram parse check (push) Successful in 1m43s
feat(webhooks): security hardening, tests, full documentation audit & polish (Phase 6)
2026-05-19 17:11:45 +01:00

12 KiB
Raw Blame History

Changelog

All notable changes to this project will be documented in this file. Format follows Keep a Changelog. This project adheres to Semantic Versioning.


[1.4.0] - 2026-05-19

Added

Webhook Integration (Phases 15.1)

  • Webhook receiver endpointsPOST /api/webhook/sonarr and POST /api/webhook/radarr accept push events from Sonarr and Radarr with shared-secret validation (X-Sofarr-Webhook-Secret header). Dashboard updates in < 1 second after any grab, import, or failure.
  • Selective cache invalidation — each incoming event is classified into queue events (Grab, Download, DownloadFailed, ManualInteractionRequired) or history events (DownloadFolderImported, ImportFailed, EpisodeFileRenamed, MovieFileRenamed). Only the affected cache key is refreshed via a lightweight re-poll of that instance, rather than a full poll cycle.
  • SSE broadcast on webhook — after refreshing the cache, pollAllServices() is called as a fire-and-forget, triggering an immediate SSE push to every connected browser.
  • Notification management API proxyGET /api/sonarr/api/v3/notification and POST /api/sonarr/api/v3/notification (and Radarr equivalents) proxy the full *arr Notification API through sofarr with auth + CSRF enforcement.
  • One-click webhook setupPOST /api/sonarr/webhook/enable and POST /api/radarr/webhook/enable auto-configure the Sofarr webhook notification connection inside the respective *arr service. POST /api/sonarr/webhook/test and /radarr/webhook/test trigger a test event.
  • Webhooks Configuration UI — collapsible panel in the dashboard allowing users to enable, test, and view webhook status for each configured Sonarr/Radarr instance, with per-trigger type indicators showing which event types are active.
  • SOFARR_WEBHOOK_SECRET environment variable — required for webhook endpoints to accept requests. Generate with openssl rand -hex 32.
  • SOFARR_BASE_URL environment variable — public URL of sofarr, used by the one-click setup to tell Sonarr/Radarr where to POST events.

Smart Polling Optimization (Phase 5)

  • Webhook metrics trackingcache.js now maintains per-instance and global webhook metrics (lastWebhookTimestamp, eventsReceived, pollsSkipped) via getWebhookMetrics(), updateWebhookMetrics(), incrementPollsSkipped(), getGlobalWebhookMetrics().
  • Conditional poll skippingpoller.js calls shouldSkipInstancePolling() before each Sonarr/Radarr fetch. If all instances of a type have received a webhook event within the fallback timeout window, their queue/history API calls are skipped entirely; existing cached data has its TTL extended instead.
  • Webhook fallback — if no webhook events have been received globally for WEBHOOK_FALLBACK_TIMEOUT minutes (default: 10), the poller forces a full poll regardless of per-instance state. Logged as [Poller] Webhook fallback triggered.
  • Poll-skip logging — logs [Poller] Skipping sonarr/radarr polling for N instance(s) with active webhooks when polling is skipped.
  • WEBHOOK_FALLBACK_TIMEOUT environment variable — minutes before fallback polling (default: 10).
  • WEBHOOK_POLL_INTERVAL_MULTIPLIER environment variable — internal multiplier for TTL calculations when webhooks are active (default: 3).
  • Phase 5.1 metrics connectionwebhook.js calls cache.updateWebhookMetrics(instance.url) after every successfully validated event, activating the smart skip logic for that instance.

Security Hardening (Phase 6)

  • Dedicated webhook rate limiter — 60 requests per minute per IP on /api/webhook/*, stricter than the global 300/15 min API limiter. Bypassed in tests via SKIP_RATE_LIMIT=1.
  • Strict input validationvalidatePayload() rejects: non-object bodies, missing/non-string/overlong eventType, unrecognised event type values (allowlist of 18 known *arr event types), non-string instanceName. Returns 400 with a descriptive message.
  • Replay protectionisReplay() tracks recently-seen (eventType, instanceName, date) tuples in a Map with a 5-minute TTL. Duplicate events within the window return 200 { received: true, duplicate: true } without triggering a cache refresh or SSE broadcast.
  • 35 new webhook integration tests — cover secret validation (missing/wrong/unconfigured), payload validation (all invalid cases), replay protection, happy-path acceptance of all relevant event types, metrics increment, and secret-never-leaks assertions.

Documentation (Phase 6)

  • README.md — updated architecture overview diagram, added Webhooks section with quick-setup guide, added PDCA/PALDRA/webhook architecture table, added Webhooks & Smart Polling env var section, added webhook API endpoints, updated test count.
  • CHANGELOG.md — this entry.
  • SECURITY.md — added webhook threat model rows, webhook-specific hardening checklist, and rate-limit table entry for webhook endpoints.
  • ARCHITECTURE.md (root) — new concise top-level architecture reference describing all pluggable layers and the full webhook + polling optimization flow.
  • .env.sample — added WEBHOOK_FALLBACK_TIMEOUT and WEBHOOK_POLL_INTERVAL_MULTIPLIER with explanatory comments; updated NOTES section.

Changed

  • poller.jspollAllServices() now conditionally skips Sonarr/Radarr fetches when webhooks are active; extends TTL of existing cache entries instead of overwriting with empty data.
  • cache.js — exports four new webhook metrics helpers alongside the existing MemoryCache singleton.
  • webhook.js — imported express-rate-limit; added validatePayload(), isReplay(), VALID_EVENT_TYPES allowlist, recentEvents Map, and per-route webhookLimiter middleware.

[1.3.0] - 2026-05-17

Added

  • History tab — new "Recently Completed" tab showing imported and failed downloads from Sonarr/Radarr history for the last N days (configurable via the days input, persisted in localStorage). Auto-refreshes every 5 minutes.
  • History deduplication — when a failed download has subsequently been imported successfully, only the successful record is shown. If the most recent record for an item is a failure but the episode/movie is already on disk (upgrade attempt), the record is flagged as availableForUpgrade.
  • "Upgrade available" badge — failed history cards where the content is already on disk display an amber badge to indicate this is a failed upgrade rather than a missing item.
  • "Hide upgrade failures" toggle — checkbox in the history tab to filter out failed records that are already available on disk. State persists in localStorage. Tooltip explains the behaviour and matches the episode/multi-episode tooltip style.
  • Blocklist & Search button — admin-only button on download cards with an "Import Pending" caution. Removes the download from the client with blocklist=true (preventing re-grab of the same release) then immediately triggers an EpisodeSearch/MoviesSearch command in Sonarr/Radarr. Shows a confirmation dialog, loading/success/error states. Kicks a background poll on success.
  • POST /api/dashboard/blocklist-search — new admin-only endpoint backing the above button. Accepts arrQueueId, arrType, arrInstanceUrl, arrInstanceKey, arrContentId, arrContentType.
  • Title link home navigation — the sofarr logo/title in the header now navigates to the default view (Active Downloads tab, close status panel, reset "Show all users" toggle) without a page reload.
  • Version footer link — the version string in the dashboard footer links to the source repository.

Changed

  • History records are now deduplicated server-side before being sent to the client — only the most relevant record per content item per instance is returned.
  • Import-issue badge tooltip now uses the themed var(--surface) / var(--text-primary) / var(--border) CSS variables, matching the episode and toggle tooltip style.
  • Poller now stores _instanceKey on Sonarr/Radarr queue records in the cache, enabling the backend to look up API credentials for blocklist operations without an additional configuration lookup.
  • Blocklist & Search button — now available on all admin downloads (not just those with import issues), and also available to non-admin users when: import issues are present, OR (for qBittorrent torrents only) the download is more than 1 hour old AND has less than 100% availability.
  • Download object — added canBlocklist boolean field to indicate whether the current user can blocklist a given download.
  • qBittorrent torrent data — added addedOn timestamp field to enable age-based blocklist eligibility checks.

[1.2.2] - 2026-05-17

Changed

  • Header logo — uses the higher-resolution 192px favicon source rendered at 56px for better visual balance alongside the title text.

[1.2.1] - 2026-05-17

Added

  • Version footer — the dashboard footer now displays the running app version (e.g. sofarr v1.2.1), fetched from the /health endpoint on page load.

[1.2.0] - 2025-05-17

Security

  • Docker secrets support — all sensitive environment variables (COOKIE_SECRET, EMBY_API_KEY, SABNZBD_API_KEY, SONARR_API_KEY, RADARR_API_KEY, QBITTORRENT_PASSWORD) now support the standard _FILE variant for loading values from mounted secret files (e.g. COOKIE_SECRET_FILE=/run/secrets/cookie_secret).
  • Weak secret warning — server now warns at startup if COOKIE_SECRET is shorter than 32 characters.
  • EMBY_URL validation — validates the Emby URL scheme at startup and warns on misconfiguration.
  • Improved error sanitizationsanitizeError() now also redacts hostnames from full request URLs that may appear in axios error messages.
  • Graceful shutdownSIGTERM and SIGINT handlers now stop the background poller and drain open HTTP connections before exiting. Prevents data loss and zombie processes on docker stop.

Compliance

  • MIT LICENSE file added to project root.
  • Copyright headers added to key server source files (index.js, poller.js, config.js, sanitizeError.js, loadSecrets.js).
  • security.txt (/.well-known/security.txt) added for responsible disclosure.

Configuration

  • URL validation added to config.js — all configured service instance URLs are validated for scheme (http/https) and well-formedness at startup; malformed URLs emit a warning instead of crashing.

Docker / Deployment

  • docker-compose.yaml updated with commented Option B (Docker secrets _FILE pattern) alongside the existing plain-env Option A.
  • .dockerignore updated — tests/, coverage/, vitest.config.js, CHANGELOG.md, SECURITY.md, LICENSE, .markdownlint.json excluded from the production image.

CI

  • docs-check workflow added — separate Gitea Actions workflow that lints all Markdown files and validates Mermaid diagram syntax on every push that touches .md files. Both jobs use continue-on-error: true so documentation issues never block a release.
  • Mermaid diagrams in docs/ARCHITECTURE.md fixed — replaced invalid \n in stateDiagram transition labels, Unicode arrows/dashes, and double-spaces in flowchart edge definitions.

[1.1.2] - 2025-05-15

Changed

  • Server startup message now includes the current version (sofarr v1.1.2).

[1.1.1] - 2025-05-14

Fixed

  • Docker/TrueNAS SCALE healthcheck: dynamic HTTP/HTTPS selection based on TLS_ENABLED environment variable. Prevents containers from being stuck in "starting" state when TLS_ENABLED=false.

[1.1.0] - 2025-05-13

Added

  • Episode display — TV show download cards now show episode information (S01E01 format with title). Multi-episode packs show a "Multiple episodes" badge with a tooltip listing all episodes.
  • Episode tooltip — solid background colour (theme-dependent) for readability.
  • Sonarr queue and history API requests now include includeEpisode=true.

[1.0.0] - 2025-05-01

Added

  • Initial release.
  • SABnzbd queue and history integration.
  • qBittorrent torrent integration.
  • Sonarr and Radarr queue/history matching with user tag filtering.
  • Emby/Jellyfin authentication.
  • Server-Sent Events (SSE) real-time dashboard.
  • Per-request CSP nonce, CSRF double-submit, HSTS, Permissions-Policy.
  • Background polling with configurable interval and on-demand fallback.
  • Docker multi-stage build, non-root user, read-only filesystem.
  • TLS support with bundled snakeoil certificate.