Report privately, not as a public issue:
- GitHub Security Advisories — https://github.com/GLinnik21/plx-native/security/advisories/new (preferred: it is private, it threads, and it produces a CVE if one is warranted)
- or e-mail support@plxnative.com with
PlxNative securityin the subject.
This is a one-person unpaid project, so the honest service level is: acknowledged within 7 days, an assessment within 30. If you have not heard back in a week, assume the mail was lost and open a public issue saying only "sent a security report on , no reply" — with no details.
Please give me a reasonable window to ship a fix before disclosing. There is no bounty; I will credit you in the release note unless you ask me not to.
The app, its packaging, and the host-side tools in tools/ and ci/. Concretely, the things worth
looking at:
- The
/tmptrigger surface./tmpis mode 1777 in webOS's production jail, so any co-resident process can create files there. Roughly fortyplxnative-*files change behaviour, and three are outright takeovers —plxnative-tokenbeats the signed-in session,plxnative-serversinjects a server and its token,plxnative-urlreplaces the stream. All of it is compiled out of a release build by dropping thedevtriggerscargo feature, andci/check-elf.shmeasures that on the shipped bytes rather than asserting it. A release binary that still carries any of it is a valid report, and a serious one. - The event log.
plxnative-events.log— likeplxnative-crash.logandplxnative-stderr.logbeside it — is created 0640 (owner read/write, the app's own group read, never world-readable), so that a Developer Mode user can fetch it with webOS Dev Manager on a television that is not rooted. Every event-log line goes througheventlog::scrub::scrub_localbefore the write. A line that reaches it carrying a credential, a Plex token, aplex.directhostname, a household name or anything about what is being watched is a valid report — see PRIVACY.md for the contract that is meant to hold. The panic text the Rust panic hook appends to the crash log goes through the same scrubber. All three files are opened withO_NOFOLLOWas a regular file this app owns with exactly one link, so a symlink or a hard link planted at their names in the shared/tmpis refused and never chmod'ed or truncated. Stated plainly, the residual exposure: another native app on the same television that runs in the app's group can read all three files, and the stderr log (whatever aborts and the television's own libraries print), the crash log's C-side records (a faulting address, registers, one memory-map line) and the C-side lines the shim and the Starfish seam write into the event log are not passed through that scrubber. A credential in any of them is a valid report too. - TLS. Certificate verification is on for every HTTPS request, with one bounded exception and
one fallback that relaxes nothing. The exception: when a Plex server's certificate fails only its
validity-date check (the television has no battery-backed clock) and a public key was remembered
for that exact host and port from an earlier fully verified connection, the request is repeated
with the chain-and-date check replaced by a pin on that key; the name check stays on. plex.tv,
telemetry and any host with no remembered key never take this path (
net.rs,net::keypin). The fallback: when a*.plex.directserver's certificate fails because the television's own trust store lacks its issuer (Let's Encrypt's 2025 roots on a 2020 firmware), the request is repeated once with the CA file set tole-roots.pem, the four public ISRG roots shipped in the package. Chain, dates and name are all still verified, against a different root set. It is offered only for a host name that is a*.plex.directname (a URL whose userinfo merely names one does not count), never for plex.tv or telemetry. A redirect is a request of its own: it is verified against the television's own store like any other, and the bundle is offered for it only if its target is itself a*.plex.directname that fails the same way. When the bundle verifies the chain and only the dates fail (a wrong clock too), the remembered key above is still tried. Stable builds refuse any PMS control or media URL that would carry a Plex token over plaintext HTTP, with one consented exception: a server that answers only unencrypted at a numeric private address on the television's own network, where the person answered "Connect without encryption?" for that server (plex::grant). The grant names that server and that exact origin, is never persisted, and is revoked on sign-in, sign-out and return to the foreground, at a profile switch whose roster does not install that server at that origin, whenever a fresh probe no longer reaches it, and at once when the person says no (a refusal is written again until it reaches the disk). A token sent over plaintext to any origin outside a live grant, or to a server other than the one it names, is in scope. Only an explicit developer-trigger build can otherwise allow the lab path, and it logs the exception without the URL. Anything that disables, downgrades or bypasses these rules is in scope — including a key-mode request that accepts a different key or a wrong name, or a bundled-roots request that accepts a certificate those roots do not verify or is offered for a host that is not a*.plex.directname; so is any path where a failure to set a security option results in a request going out anyway. - The session file.
<id>-auth.jsonholds one access token per server your account can reach. It is encrypted with the firmware's authenticated Key Manager wherecom.webos.service.keymanager3is available and permitted, with a 0600 plaintext compatibility fallback otherwise. The legacycom.palm.keymanagerAES-CFB interface is not used because it provides no authenticated-encryption operation. The file is always created 0600 throughopen(2)'s own mode argument. A downgrade of an existing encrypted file, a way to read it from another process, or a way to make the app write it somewhere world-readable is in scope. - The bundled FFmpeg. Built from unmodified FFmpeg 9.0 with demuxers, parsers and subtitle
decoders only — it is fed untrusted bytes from the network, so parser bugs reachable through
ff.rsare in scope. Report FFmpeg's own bugs upstream as well.
- Post-compromise access by an attacker who already has root on the television. Root is not an app prerequisite; a report whose only precondition is an already-rooted OS describes a platform compromise rather than an app sandbox escape.
- The webosbrew Homebrew Channel, webOS itself, LG's own libraries, or Plex Media Server. Report those to their maintainers.
- Missing hardening that costs nothing to an attacker who is already executing code in the app's jail, unless you can show a concrete consequence.
No account of its own, no server, no payment path, and no user-generated content. It signs in to your Plex account and talks to your servers.
It does have telemetry, and that hedge used to say it did not. A release binary carries a Sentry
DSN and a PostHog project key — both write-only ingest credentials, publishable by design, which
permit sending to a project and grant no read of anything in it. First run asks about crash reports
and product analytics separately. The first answer remains a draft; answering the second records
both choices, and only a Share answer enables that category and permits its POSTs to
ingest.de.sentry.io or eu.i.posthog.com. BACK navigates without recording a refusal. Later
changes live under Account → Settings → Privacy & data, where Done commits and BACK discards.
The Sentry
auth token is the real secret in this system: it can read and delete the project, it never
enters the binary, and it exists only as a GitHub Actions secret used by sentry-cli in the release
workflow.
In scope for a report, and worth naming since a "no telemetry endpoint" line told researchers not to
look here: the consent gate failing open, an identifier existing before product analytics is
explicitly enabled or surviving its withdrawal, anything that gets a runtime string past
diag::schema's no-owned-strings guarantee,
and the spool's file mode or its contents. PRIVACY.md is the full account of what
leaves the television.