* 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
7.3 KiB
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: truenodeIntegration: falsesandbox: truewebSecurity: truepreload: 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:
ElectronBridgeApiin@iptvnator/shared/interfacesapps/electron-backend/src/app/api/main.preload.ts- the owning
ipcMain.handle(...)event module - 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:
setWindowOpenHandlerdenies every new window.http:andhttps:targets are opened in the operating system browser throughshell.openExternal.will-navigateallows only the trusted renderer URL. Development mode allowshttp://localhost:4200,http://127.0.0.1:4200, andhttp://[::1]:4200.will-redirectapplies 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
userAgentandrefererclear all active overrides - empty channel-level
userAgentandrefererwith ascopeUrlclear 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, andOriginnames 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-fileaccepts 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
userDataand returned byDOWNLOADS_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.