mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
fix(security): complete Electron hardening and review follow-ups
* fix(security): harden Electron IPC against MITM, SSRF, path and injection risks S1 TLS: validate certs by default on playlist/EPG fetches (opt-out via IPTVNATOR_ALLOW_INSECURE_TLS); new util/secure-https.ts. S2: write-file IPC restricted to save-dialog-authorized paths. S3: XTREAM_PROBE_URL guarded by assertRemoteUrlAllowed + maxRedirects:0; new events/url-safety.ts (+19 tests). S4: EPG titles rendered via interpolation, not [innerHTML]. S5: downloads reveal/play limited to recorded download paths. S6: Stalker cmd encoded (slash-preserving) to block query injection. EPG-worker and Stalker fetches reject file://-style/credentialed URLs; LAN/self-hosted targets remain allowed. * perf(player): lazy-load web video players via @defer Wrap Video.js/HTML5/ArtPlayer in @defer (on immediate) so video.js, hls.js, artplayer and mpegts.js split into a deferred chunk loaded on first playback instead of eagerly on the player route. Embedded MPV (native) stays eager. Spec uses DeferBlockBehavior.Playthrough. * fix(player): remove leaked HTML video listeners on destroy volumechange used a mismatched removeEventListener reference, while loadedmetadata and timeupdate were never removed at all. Bind all three to stable handler fields used for both add and remove, and add a teardown regression test asserting each listener is detached on destroy. * refactor(dashboard): extract pure navigation helpers from DashboardDataService Move the 8 stateless link/navigation-state/type-kind helpers into a new dashboard-navigation.util.ts so the routing logic is independently testable and the 1260-line god-service shrinks. DashboardDataService keeps the public methods as thin delegators (facade) so the public API and the single consumer (workspace-dashboard-rails) are unchanged. First slice of the DashboardDataService decomposition; verified by the existing service spec (33/33) and the app typecheck. * fix(review): address PR feedback (IPv6 link-local, write-path cap, @defer placeholder) - url-safety: broaden IPv6 link-local detection to the full fe80::/10 range (fe80:: through febf::), not just the fe80:: prefix (+ regression tests). - playlist.events: cap authorizedWritePaths (evict oldest past 32) so a save dialog opened without a following write cannot accumulate entries until restart. - web-player-view: add a @placeholder to each @defer (on immediate) player block to avoid the one-frame blank/layout-shift before the chunk resolves. * fix(security): close Electron network and download gaps * test(downloads): cover cancellation and restart cleanup * fix(downloads): address Greptile review gaps * test(security): reproduce remaining Greptile findings * fix(security): close remaining Greptile findings * test(downloads): reproduce early database queue stall * fix(downloads): release queue after setup failures * test(downloads): reproduce completion queue stall * fix(downloads): release queue after completion failures
This commit is contained in:
1 parent
7775553a6b
commit
2c032cd3c8
44 files changed
+3646
-1124
No files matched your search
@@ -4,17 +4,25 @@ The download manager is a desktop-only feature that layers a curated queue, prog
|
||||
|
||||
## Backend responsibilities
|
||||
|
||||
- **Queue control (apps/electron-backend/src/app/events/downloads.events.ts)**
|
||||
`DownloadTask` mirrors the shared `DownloadItem` table plus transient cancel/progress helpers. `enqueueDownload()` resolves a unique file path, persists a `queued` row in `downloads`, pushes the task onto `downloadQueue`, and triggers `processQueue()`. `processQueue()` keeps one active download, updates the row to `downloading`, and calls `startDownload()`.
|
||||
- **electron-dl integration**
|
||||
`startDownload()` now calls `electron-dl`’s `download()` helper. Headers (user agent, referer, origin) are attached, and the `onStarted`, `onProgress`, `onCompleted`, and `onCancel` callbacks translate the helper’s payload into Drizzle updates. The handler throttles progress broadcast, saves `filePath`/`fileName` from `electron-dl`, and marks failures/cancellations cleanly. Errors and cancellations delete partial files.
|
||||
- **Queue control (`apps/electron-backend/src/app/events/database/download-runtime.ts`)**
|
||||
`DownloadTask` mirrors the shared `DownloadItem` table plus transient cancel/progress helpers. Request validation and row creation live in `download-requests.ts`, while `downloads.events.ts` stays focused on IPC registration. `enqueueDownload()` pushes the task onto `downloadQueue` and triggers `processQueue()`. `processQueue()` keeps one active download, updates the row to `downloading`, and calls `startDownload()`.
|
||||
- **electron-dl integration**
|
||||
`startDownload()` calls `electron-dl`'s `download()` helper. Headers (user agent, referer, origin) are attached, and the `onStarted`, `onProgress`, `onCompleted`, and `onCancel` callbacks translate the helper's payload into Drizzle updates. A cancellation requested before `onStarted` is remembered and applied as soon as Electron supplies the `DownloadItem`, so the request cannot be lost in the startup race.
|
||||
- **Destination collision policy**
|
||||
Existing destination files are never overwritten. Before starting Electron's
|
||||
download, the backend atomically reserves a free numbered filename with an
|
||||
exclusive filesystem create. Electron may overwrite that empty reservation,
|
||||
but cannot overwrite a file that existed before the reservation. The selected
|
||||
`filePath` and `fileName` are persisted before transfer begins. Errors,
|
||||
cancellations, and startup recovery remove that exact partial path and clear
|
||||
it from the row; completed downloads replace it with Electron's final values.
|
||||
- **IPC surface**
|
||||
The backend exposes `DOWNLOADS_*` handlers for list retrieval, start/cancel/retry/remove operations, folder selection/reveal, and the `DOWNLOADS_UPDATE_EVENT` emitter that the renderer listens to in order to refresh its signal store.
|
||||
|
||||
## Renderer architecture
|
||||
|
||||
- **Downloads service** (`apps/web/src/app/services/downloads.service.ts`)
|
||||
Signals back the current download list while `hasDownloads` and `isAvailable` gates UI rendering. Before each download the service resolves a download folder (stored in `SettingsStore` or fetched via `downloadsGetDefaultFolder`) and calls `downloadsStart`. The backend extracts the file extension from the URL or falls back to `mp4`. `onDownloadsUpdate` updates the signal, while helper methods `retryDownload`, `removeDownload`, `cancelDownload`, and `playDownload` talk to the corresponding IPC commands so retries reuse existing rows and completed items can open the recorded path.
|
||||
- **Downloads service** (`libs/services/src/lib/downloads.service.ts`)
|
||||
Signals back the current download list while `hasDownloads` and `isAvailable` gates UI rendering. Before each download the service asks the main process for the authorized folder and calls `downloadsStart`. The backend extracts the file extension from the URL or falls back to `mp4`. `onDownloadsUpdate` updates the signal, while helper methods `retryDownload`, `removeDownload`, `cancelDownload`, and `playDownload` talk to the corresponding IPC commands so retries reuse existing rows and completed items can open the recorded path.
|
||||
- **Downloads view** (`libs/portal/downloads/feature`)
|
||||
A standalone page exposes the queue, desktop-only messaging, folder picker, and action buttons. `downloads.component.html` now wraps the list inside a scrollable panel (`downloads__list-wrapper`) so long queues stay reachable, and `downloads.component.scss` drives a bold two-tone aesthetic inspired by the frontend-design mandate—gradient cards, floating avatars, and theme-aware variables triggered via `body.dark-theme`.
|
||||
Failed/canceled cards now show retry/delete controls, queued/downloading cards show a cancel icon, and completed cards render inline play/open buttons with `mat-icon` cues. The header also shows the resolved download folder and a `CHANGE FOLDER` action.
|
||||
@@ -33,9 +41,12 @@ The download manager is a desktop-only feature that layers a curated queue, prog
|
||||
|
||||
## Queuing, persistence, and UX notes
|
||||
|
||||
- Every download row writes to the shared `downloads` table with statuses (`queued`, `downloading`, `completed`, `failed`, `canceled`) plus metadata such as `bytesDownloaded`, `totalBytes`, `errorMessage`, and Xtream identifiers. Stale downloads reset to `failed` on startup.
|
||||
- Queue cancellation removes the task or calls `downloadItem.cancel()` if the item is active; retries reuse the same database entry, preventing duplicate rows.
|
||||
- Folder selection first checks stored preferences, falls back to the OS default downloads path, and finally prompts the user to pick a folder. The downloads service persists the chosen path via `SettingsStore`.
|
||||
- Every download row writes to the shared `downloads` table with statuses (`queued`, `downloading`, `completed`, `failed`, `canceled`) plus metadata such as `bytesDownloaded`, `totalBytes`, `errorMessage`, and Xtream identifiers. On startup, `download-recovery.ts` deletes persisted partial reservations before stale queued/downloading rows become `failed`.
|
||||
- Queue cancellation removes a queued task or records an active cancellation request and calls `downloadItem.cancel()` when the item is available; retries reuse the same database entry, preventing duplicate rows.
|
||||
- The OS downloads path is always authorized. A custom folder becomes
|
||||
authorized only after native folder selection, and the main process persists
|
||||
that selection under Electron `userData`. Renderer settings may display the
|
||||
path, but they are not trusted as authorization.
|
||||
- The new UI leverages CSS variables for theme-specific backgrounds/borders, ensures `.downloads__list` can scroll inside its panel, and brings consistent badge/typography treatments to each card.
|
||||
|
||||
Keeping the backend queue, IPC handlers, shared schema, and renderer signals synchronized minimizes drift between platform rules and the UI. Future work might cover download list filters, cancel-all actions, or integration with upcoming playback analytics.
|
||||
@@ -103,3 +103,49 @@ Rules:
|
||||
When changing this flow, keep stale header cleanup covered. Switching from a
|
||||
channel or playlist with custom headers to one without custom headers must clear
|
||||
the previous override.
|
||||
|
||||
## Main-Process Remote Requests
|
||||
|
||||
Renderer-triggered HTTP requests must pass through the URL policy in
|
||||
`apps/electron-backend/src/app/events/url-safety.ts`. The policy rejects
|
||||
non-HTTP(S) URLs and embedded credentials, and strict callers also reject
|
||||
loopback, private, reserved, and DNS-resolved private addresses. IPv4-mapped
|
||||
IPv6 literals are decoded before classification, including hexadecimal forms
|
||||
such as `::ffff:7f00:1`, so alternate IPv6 spelling cannot bypass IPv4 rules.
|
||||
|
||||
Remote request callers must use the validated Axios redirect helper so every
|
||||
redirect target is checked before the main process follows it. Under the strict
|
||||
policy, the helper pins the socket lookup to the IP addresses that passed
|
||||
validation while retaining the original hostname for TLS SNI, certificate
|
||||
validation, and virtual hosting. This prevents DNS rebinding between validation
|
||||
and connection. Callers with custom TLS policy provide a typed agent factory;
|
||||
the validated request layer supplies the pinned lookup instead of copying
|
||||
private Node `Agent.options` state. Cross-origin redirects must not forward `Authorization`,
|
||||
`Cookie`, `Proxy-Authorization`, Axios `params`, or request bodies.
|
||||
|
||||
EPG URLs are strict by default because an M3U playlist can supply them through
|
||||
`url-tvg`. Operators who intentionally use a LAN-hosted EPG source can opt in
|
||||
for that run with `IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS=1`. Directly configured
|
||||
Xtream, Stalker, and playlist providers retain private-network support, but
|
||||
still require HTTP(S), reject embedded credentials, and validate redirects.
|
||||
|
||||
Remote playlist TLS certificates are validated by default. The
|
||||
`IPTVNATOR_ALLOW_INSECURE_TLS=1` escape hatch is only for explicitly trusted
|
||||
providers with invalid or self-signed certificates.
|
||||
|
||||
## Filesystem Capabilities
|
||||
|
||||
Renderer IPC payloads are not filesystem authorization.
|
||||
|
||||
- `write-file` accepts only a path returned to the same renderer by the native
|
||||
save dialog. The capability is single-use and is consumed before the write,
|
||||
including when the filesystem operation fails.
|
||||
- Download folders are owned by the Electron main process. The OS downloads
|
||||
directory is always allowed; a custom directory is accepted only after the
|
||||
native folder dialog selects it.
|
||||
- The selected download directory is persisted under Electron `userData` and
|
||||
returned by `DOWNLOADS_GET_DEFAULT_FOLDER`, so renderer-managed settings
|
||||
cannot substitute an arbitrary host path.
|
||||
- Downloads do not overwrite an existing destination file.
|
||||
- Reveal and playback handlers accept only file paths recorded in IPTVnator's
|
||||
downloads database.
|
||||
Reference in new issue
Block a user