mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
* 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
152 lines
7.3 KiB
Markdown
152 lines
7.3 KiB
Markdown
# Electron Security Contract
|
|
|
|
This document records the Electron runtime security contract for the desktop app.
|
|
|
|
## BrowserWindow Defaults
|
|
|
|
The main window is created in `apps/electron-backend/src/app/app.ts` with an
|
|
explicit hardened `webPreferences` object:
|
|
|
|
- `contextIsolation: true`
|
|
- `nodeIntegration: false`
|
|
- `sandbox: true`
|
|
- `webSecurity: true`
|
|
- `preload: apps/electron-backend/src/app/api/main.preload.ts`
|
|
|
|
Renderer code must use the preload bridge exposed as `window.electron`.
|
|
Do not re-enable direct Node.js access from Angular code. New desktop-only APIs
|
|
should be added to the preload bridge and backed by an `ipcMain.handle(...)`
|
|
owner in the Electron backend.
|
|
|
|
## Preload API Type Contract
|
|
|
|
The canonical renderer bridge type is
|
|
`libs/shared/interfaces/src/lib/electron-api.interface.ts`.
|
|
|
|
Keep these surfaces in sync when adding or changing a preload method:
|
|
|
|
1. `ElectronBridgeApi` in `@iptvnator/shared/interfaces`
|
|
2. `apps/electron-backend/src/app/api/main.preload.ts`
|
|
3. the owning `ipcMain.handle(...)` event module
|
|
4. renderer capability checks or runtime bridge services that consume the method
|
|
|
|
`global.d.ts` and `apps/web/src/typings.d.ts` should reference
|
|
`ElectronBridgeApi` instead of redeclaring `window.electron` method lists.
|
|
`main.preload.ts` is typed as `ElectronBridgeApi`, so missing or extra preload
|
|
methods fail typecheck instead of silently drifting from renderer typings.
|
|
|
|
## Navigation And External URLs
|
|
|
|
The main window owns three navigation gates:
|
|
|
|
- `setWindowOpenHandler` denies every new window. `http:` and `https:` targets
|
|
are opened in the operating system browser through `shell.openExternal`.
|
|
- `will-navigate` allows only the trusted renderer URL. Development mode allows
|
|
`http://localhost:4200`, `http://127.0.0.1:4200`, and `http://[::1]:4200`.
|
|
- `will-redirect` applies the same allow/deny rules so server-side redirects
|
|
cannot move the app window to an untrusted origin.
|
|
|
|
Packaged mode allows only the app's resolved `index.html` renderer file, not
|
|
arbitrary `file:` URLs. External web navigations are denied in the app window
|
|
and opened in the operating system browser.
|
|
|
|
Do not add broad protocol allow-lists for renderer navigation. If a new
|
|
desktop-only flow needs to open a URL outside IPTVnator, route it through the
|
|
default browser unless the app window is deliberately meant to host that URL.
|
|
|
|
## Content Security Policy
|
|
|
|
The Angular shell defines a baseline CSP in `apps/web/src/index.html`.
|
|
|
|
The policy keeps the application self-hosted for scripts, blocks object and
|
|
frame embedding, limits forms to the app origin, and allows IPTV playback
|
|
sources through `media-src` and `connect-src` for `http:`, `https:`, `blob:`,
|
|
and `data:`. The policy keeps `script-src` self-hosted and currently keeps
|
|
`unsafe-inline` for existing inline styles.
|
|
|
|
Angular production builds must not rely on inline event handlers for stylesheet
|
|
activation. Keep `web:build:production` and `web:build:pwa` configured without
|
|
critical CSS stylesheet deferral (`optimization.styles.inlineCritical: false`)
|
|
unless the CSP is intentionally changed and runtime-validated in both Electron
|
|
and the self-hosted PWA.
|
|
|
|
Before tightening either value, validate both Electron development startup and
|
|
the PWA/electron build configurations. Playback-heavy changes should also check
|
|
that HLS, MPEG-TS, thumbnails, and local file playback are still allowed by the
|
|
policy.
|
|
|
|
## Scoped Request Header Overrides
|
|
|
|
Inline playback can request temporary `User-Agent`, `Referer`, and `Origin`
|
|
header overrides through `window.electron.setUserAgent(userAgent, referer,
|
|
scopeUrl)`.
|
|
|
|
The Electron backend handles that IPC in `apps/electron-backend/src/app/events/shared.events.ts`
|
|
and delegates to `apps/electron-backend/src/app/services/request-header-overrides.service.ts`.
|
|
The service registers one `session.defaultSession.webRequest.onBeforeSendHeaders`
|
|
listener and updates layered in-memory overrides instead of stacking a new
|
|
listener for every channel change.
|
|
|
|
Rules:
|
|
|
|
- empty playlist-level `userAgent` and `referer` clear all active overrides
|
|
- empty channel-level `userAgent` and `referer` with a `scopeUrl` clear only
|
|
the scoped channel override, preserving playlist-level defaults
|
|
- channel playback should pass the stream URL as `scopeUrl`
|
|
- scoped overrides apply only to the active stream origin and referer origin
|
|
- playlist-level user agents and referrers may call the bridge without a
|
|
`scopeUrl`; that is intentionally broader because playlist settings apply to
|
|
the whole M3U playlist
|
|
- header names are replaced case-insensitively before canonical `User-Agent`,
|
|
`Referer`, and `Origin` names are written
|
|
|
|
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.
|