# ACR and backend telemetry components in webOS 24
## Subject
An extracted root filesystem, with all mounts, taken from a rooted LG television.
| | |
| --- | --- |
| Model | `55NANO82T6B.BEUSLJP` — NANO82 series, 55", LCD/NanoCell |
| Model year | 2024 (`manufactureYear: 2024`, platform code `W24P`, product code `webOSTV 24`) |
| Panel / tuner | UD (2160p) at 60 Hz, CEDS interface; DVB (`sysType: DVB`), tuner present |
| SoC / memory | `K8LPN2` (machine `k8lpn2`), 1.5 GB DDR |
| OS | webOS TV 24; starfish release `Rockhopper release 10.2.2-5901 (ponytail-paparoa)`; platform version `10.2.2` |
| Firmware | `33.22.86`; bootloader `9.00.89/9.00.89` |
| Service country | NL / NLD; UI locale `nl-NL`, formats and NLP `en-GB`; language group `EU` |
| SDP region | EIC (LG's Europe/rest-of-world region), environment `Production` |
| Channel map | `NL-webOS-6.4.0-MR`, version `202609030035` |
| Serial | `50[REDACTED 10 digits]` (full value in `var-18/preferences/configd_db.json`, key `tv.model`) |
The update server advertises `33.31.61` as the current official NL build for this model
(`mnt-6/lg/cmn_data/sdp/sdx/detailconfig.json`, `nsu` block), so the image is one or more
releases behind the shipping firmware. Developer mode is not enabled
(`var-18/luna/preferences/devmode_enabled` is empty).
Snapshot age: the newest file written by the TV itself is
`mnt-6/lg/cmn_data/acr/data/power_off_info`, timestamped 2026-09-04 16:48 UTC, which is the last
power-off. Analysis was performed 2026-09-07. Runtime state described below is therefore the
state at that last power-off, not at first boot.
Sources for the table: `etc/starfish-release`, `var-18/preferences/configd_db.json`
(`tv.model`, `tv.nyx`, `tv.hw`), `mnt-6/lg/cmn_data/var/webos-profile/device_selection.json`,
`var-18/luna/preferences/localeInfo`, `mnt-6/lg/cmn_data/pbs/pbs_config.json`.
## Scope and method
Static inspection only. Paths below are relative to the directory containing this file.
`usr-2/` is the mount holding `/usr`, `mnt-6/` holds `/mnt/lg`, `var-18/` holds `/var`, and
`etc/` holds `/etc`. Where a component's on-device path matters it is given in absolute form as
the binary itself refers to it.
The approach was: locate services and packages by name, read their Luna manifests and startup
activities, extract strings from the ELF binaries, and read the runtime state files the
components leave in `/mnt/lg/cmn_data`. Nothing was executed and no traffic was captured. Claims
about what a binary sends are drawn from format strings, URL fragments and symbol names, so
field lists are indicative rather than an observed wire format. Where plaintext source was
available (QML, JavaScript, Python, shell) that is stated, and those parts are exact.
Identifiers belonging to this device or to third parties are redacted in place, marked
`[REDACTED …]`, with enough structure left to show the shape of the value. Three occur: the
BSSIDs of two neighbouring access points in §1.5, the device identifier in §6.1, and the ad
server session token in §2.1. The device serial in the table above is partially masked. Nothing
else in this report is specific to one unit.
---
## 1. Automatic Content Recognition
### 1.1 The service
`usr-2/palm/services/com.webos.service.acr/services.json` states the purpose directly:
```json
{
"id": "com.webos.service.acr",
"description": "Automatic Content Recognition",
"services": [ { "name": "com.webos.service.acr" } ]
}
```
The package manifest (`usr-2/palm/packages/com.webos.service.acr/packageinfo.json`) titles it
"ACR Service", version 0.0.1.
Files that make it up:
| Path | Size | Role |
| --- | ---: | --- |
| `usr-2/sbin/acr2` | 558 684 | The daemon. Capture plumbing and Luna API. |
| `usr-2/lib/libalphonsosolution.so.1.0.0` | 87 472 | Adapter between `acr2` and the vendor SDK. |
| `usr-2/lib/libas.so.3.0.93` | 2 565 924 | Alphonso ACR SDK. Fingerprinting and network I/O. |
| `usr-2/lib/libalphonsoadoverlay.so.1.0.0` | 50 608 | Same adapter pattern for the ad-overlay service. |
| `usr-2/lib/libvtcapture.so.1.0.0` | — | Video/screen capture (`screen-capture-webos` 1.0.0-56). |
| `usr-2/share/acr/acr.a.2.1.4.db.zero.mp3` | 295 184 | Shipped fingerprint database, despite the `.mp3` extension. |
| `usr-2/palm/applications/com.webos.app.acrcomponent` | — | Hidden overlay WebView. |
| `usr-2/palm/applications/com.webos.app.acroverlay` | — | Same, bound to live TV. |
| `usr-2/palm/applications/com.webos.app.acrhdmi{1..4}` | — | Same, one per HDMI input. |
| `ROOT/opt/webos/tests/acr2/gtest_acr2` | — | Unit tests, left in the image. |
| `etc/pmlog.d/acr.conf` | — | Log context configuration. |
`acr2` links `libasound.so.2` and `libvtcapture.so.1` alongside `libluna-service2`,
`libcrypto.so.3` and `libjsoncpp`.
### 1.2 What it captures
Symbol and string extraction from `acr2` shows two capture paths running in their own threads.
Audio: classes `capture::AudioCapture` and `capture::AlsaAudioWrapper`, wrapping ALSA
(`PcmOpen`, `PcmHwParamsSetRateNear`, `PcmHwParamsSetChannels`, `PcmReadi`), with a recovery
loop that logs `[AudioCapture] Audio Capture Recovery count[%d]`. This is a tap on the TV's
own audio mixdown, not a microphone.
Video: classes `capture::VideoCapture`, `capture::ImageConverter`, `capture::CaptureBuff`,
`capture::CaptureDelegate`, plus a string `DaiFastCapture`. `libvtcapture` is the LG
`screen-capture-webos` library, whose API strings (`create vt capture handle`,
`process vt capture called by %s`, `vtHistogram`) describe frame grabs off the video pipeline.
DAI most plausibly stands for dynamic ad insertion, which would make the fast-capture path an
ad-break detector, but I did not confirm that.
Configuration keys present in the binary include `capture_method`, `whitelist_method`,
`colorInversion`, `acr_config_format_ver`, `firmware_version`, `webos_version`.
### 1.3 Vendor engine and loading
`acr2` contains a `core::SolutionLoader` that resolves a vendor engine at runtime from
`/mnt/lg/cmn_data/acr/lib/`, passing it `WebOSVersion()` and `FirmwareVersion()`. That directory
is empty on this device, so the preinstalled `libalphonsosolution.so` and `libas.so` are what is
in use. `libalphonsosolution.so` exports `CreateSolution` and implements
`solution::alphonso::AlphonsoSolution` and `AlphonsoClient`.
`libas.so.3.0.93` retains its build path, which names the vendor and the LG port:
```
/mnt/alpha/acr-sdk/sdk-build/git-lg-webos-24/acr-sdk/common/audio/AudioHdlr.cpp
/mnt/alpha/acr-sdk/sdk-build/git-lg-webos-24/acr-sdk/common/as-client/ASClientOptout.cpp
/mnt/alpha/acr-sdk/sdk-build/git-lg-webos-24/acr-sdk/common/utils/UtilsReadWifiProperties.cpp
```
The audio handler is a state machine with states for `PreBuffering`, `Running`, `Sleeping`,
`RunningPO`, `SleepingPO` and `InitedLivetvExpress`, plus `SplitACR.cpp` and constants
`ACR_TYPE_SPLIT`, `ACR_SHIFT_TYPE_93`, `ACR_SHIFT_TYPE_186`. "PO" appears to mean power-off,
given `AudioHdlrPOSleepTimerCB` and `AudioHdlrPOPrimeTimeTimerCB` — a scheduled, prime-time-
weighted wake pattern. Source types are enumerated as
`ASAPI_SOURCE_LOCAL_AUDIO_TYPE_{PLATFORM,EXTERNAL,MIC}`, so the SDK has a microphone input mode
compiled in; nothing in `acr2` selects it, and `acr2` only opens ALSA capture on the platform
mixdown.
On a match, `AlphonsoClient` logs the identifiers it received:
```
[AlphonsoClient] ContentResult : tms program id : %s, tms station id : %s
[AlphonsoClient] AlphonsoMatchCB: json_info: %s
```
TMS IDs are Gracenote's content and station identifiers, which is what ties a fingerprint back
to a named programme and channel.
### 1.4 Where fingerprints go
Hostnames in `libas.so.3.0.93`:
- `prov-lg.alphonso.tv` (`ASAPI_PROV_SERVER`) — provisioning; the ACR server address is then
supplied dynamically (`ASAPI_ACR_SERVER`, `AS_ACR_SERVER_UPDATE`).
- `eulacheck.alphonso.tv` — consent check.
API paths in the same binary:
```
/audio/fingerprint /user /user/location
/user/lookups /user/timed-lookups /user/appsource
/recommendation /recommendation/info /bl/config
/device/config /device/clock /countryList
/latestVersion /log/tar /sdk_debug/stats
recommendation/v1/ott recommendation/v1/ott_feedback
```
Query parameters seen as fragments: `alp_uid=`, `device_id=`, `api_key=`, `zipcode=`,
`ipaddr=`, `app_version_code=`, `app_version_name=`, and log strings printing
`longitude:` and `zipcode:` next to each other. `/user/appsource` alongside
`ASAPI_SOURCE_LOCATION_{LOCAL,REMOTE}` suggests the SDK also reports which input or app the
audio came from.
### 1.5 Wi-Fi scanning for location
`libas.so.3.0.93` contains `UtilsReadWifiProperties.cpp`, a dedicated
`ASUtilsReadWifiPropertiesThread`, a `startScan()` and `processUpdateWifiScanInfo()`, a
comparator `compareBySignalStrength`, and these strings:
```
Wifi Scanning Start
Wifi Scanning Stop
Wifi Scanning disabled
Recvd enable_wifi_scan:
LocationClient() with lat/long and wifiScanOutput.
Empty wifiScanJsonOutput:
```
`acr2` has the matching `interface::Wifi` class with `OnFindNetworks` and writes the result to
disk. The file is present on this device:
`mnt-6/lg/cmn_data/acr/data/wifi_network_info`
```json
[{"mac_address":"[REDACTED MAC]","signal_strength":"-57"},
{"mac_address":"[REDACTED MAC]","signal_strength":"-63"}]
```
Those are neighbouring access points, not the TV's own radio. A BSSID list with signal strengths
is the standard input to a Wi-Fi geolocation lookup, which resolves to a street-level position
without GPS and without the user entering an address. The scan is enabled server-side
(`Recvd enable_wifi_scan:`), so it is not governed by a setting in the TV's menus.
### 1.6 Runtime state and current status
`mnt-6/lg/cmn_data/acr/data/`:
| File | Contents |
| --- | --- |
| `optin` | `optin` |
| `first_optout` | `first_optout` |
| `eula_allowed` | `allowed` |
| `sdp_server` | `Production` |
| `service_country` | `NL` |
| `power_off_info` | `{"time_stamp":1788540517}` |
| `wifi_network_info` | see above |
`var-18/luna/preferences/option` contains `"livePlus": "on"`. Live Plus is the menu name for
this feature. ACR is therefore enabled on this device, and the opt-in is recorded both in the
Luna settings store and in the ACR service's own state directory.
Factory defaults in `etc/palm/defaultSettings.json` are the opposite: `acrAllowed`, `acrOnAllowed`,
`acrAdAllowed` and `acrGdprAllowed` are all `false`. The enabled state came from setup or from
the EULA acceptance flow, not from the shipped defaults.
Vendor logging is disabled. `mnt-6/lg/cmn_data/acr/alphonso/log4cplus.properties`:
```
log4cplus.rootLogger=ERROR, R
log4cplus.appender.R=log4cplus::NullAppender
```
Everything at ERROR and below is routed to a null appender. A debug variant exists at
`usr-2/share/acr/log4cplus-debug.properties`, gated on `/var/luna/preferences/alphonso_debug`.
### 1.7 Startup
`etc/palm/activities/com.webos.service.acr/activity-com.webos.service.acr.start.json` registers
`luna://com.webos.service.acr/startAcr` with `{"reason":"normal"}`, triggered on
`com.webos.bootManager/getBootStatus` reaching `rest-boot-done`. So the service starts on every
boot, not on demand.
`acr2` also registers activities that restart it when the advertising identifier changes
(`"name": "ccpa changed"`, watching `com.webos.service.admanager/getAdid` for `IFA` changes) and
on network connect, EULA acceptance, store-mode change, power on and pre-power-off. Its own log
messages enumerate these: "ACR service as dynamic for connection network", "for eula allowed",
"for live plus menu", "for power on", "for prepare power off", "for store mode".
`usr-2/share/luna-service2/client-permissions.d/com.webos.service.acr.perm.json` grants the
service, and its sync and async client handles, `["all"]` — unrestricted access to the Luna bus.
### 1.8 The overlay apps
`com.webos.app.acrcomponent`, `com.webos.app.acroverlay` and `com.webos.app.acrhdmi1` through
`acrhdmi4` are near-identical hidden web apps, all titled "LivePlus", all placed on a window
layer named `acrLayer` owned by live TV or by the external-input app, with `"visible": false`
and `"class": {"hidden": true}`.
`index.html` sets a zero-size input region and `KeyMaskNone`, so the app is present but takes no
input, then loads `present_frm.html` into an iframe. `present_frm.html` is four lines of logic:
```js
var params = JSON.parse(window.PalmSystem.launchParams);
if (params.contentTarget != undefined) {
window.location = params.contentTarget;
}
```
The container navigates to whatever URL the service passes in `contentTarget`, and re-navigates
on `webOSRelaunch`. The rendered content is entirely server-controlled. The per-HDMI variants
mean overlays can be drawn over an external device's picture, not only over the tuner.
---
## 2. Advertising services
### 2.1 admanager
`usr-2/sbin/admanager` (293 428 bytes), service `com.webos.service.admanager`, described in its
manifest as "Advertisement Service Manager".
Endpoint host in the binary: `info.lgsmartad.com`. Paths:
```
/rest/json/v1.0/req
/rest/json/v1.0/baseinfo
/rest/json/v1.0/appinfo
/rest/xml/v1.0/api/DNSInfo
/rest/xml/v1.0/api/TNC?TNC=
```
The live cookie jar `mnt-6/lg/cmn_data/admanager/cookie/ad_cookie` shows the regional host this
device actually talks to:
```
#HttpOnly_.nl.ad.lgsmartad.com TRUE /rest FALSE 0 JSESSIONID [REDACTED 32 hex chars]
```
The class `CIFAdService` owns the advertising identifier. Methods include `getAdRequestInfo`,
`requestCampaignInfo`, `requestContextIndex`, `requestBaseAsset`, `sendAssetMsg`, `assetClicked`,
`reqNormalLog`, `startDownloadUrl`, `createSDKContext` and `launchCMPApp`. Request parameters
`&ifa=` and `&ifa_type=` are built inline, and the IFA is fetched from the Security Manager
(`Failed to get IFA from Security Manager`). Reset is handled by `MSG_AD_RESET_IFA`, which emits
the telemetry event `NL_ADMAN_IFA_RST`.
`mnt-6/lg/cmn_data/admanager/cache/` holds around two dozen downloaded creatives, named after
their source URLs on `ngfts.lge.com` under `biz_code=CMS&func_code=CMS_ASSET`.
Startup: `etc/palm/activities/com.webos.service.admanager/` triggers `admanager/start` when
`com.webos.service.connectionmanager/getstatus` reports `isInternetConnectionAvailable: true`.
`etc/init/admanager.conf` pre-creates the cache directory at boot.
### 2.2 adoverlay
`usr-2/sbin/adoverlay-service` (452 176 bytes), service `com.webos.service.adoverlay`, "Ad
Overlay Service". It follows the ACR design exactly: a vendor engine directory at
`/mnt/lg/cmn_data/adoverlay/lib/`, an Alphonso adapter (`libalphonsoadoverlay.so`), its own
`acr.a.2.1.4.db`-equivalent fingerprint DB at `usr-2/share/adoverlay/adoverlay.a.2.1.4.db.zero.mp3`,
and a state directory `mnt-6/lg/cmn_data/adoverlay/data/` containing `eula_allowed: allowed`,
`sdp_server: Production`, `service_country: NL`.
It carries the check `[ACR] ACR Solution is not Alphonso.`, confirming it is content-recognition
driven. It also reads `/mnt/lg/cmn_data/shopping/stored_data.json` and requests HTTP headers via
`getHttpHeaderForServiceRequest`, i.e. it authenticates to LG's SDP like the other services.
Front ends: `com.webos.app.adoverlay`, `com.webos.app.adoverlayex`, and
`com.webos.app.adhdmi{1..4}` — the same per-input pattern as the ACR overlays.
### 2.3 videoads
`usr-2/palm/applications/com.webos.app.videoads/app.js` is unminified and readable. It is a
screensaver-type app (`"defaultWindowType": "screenSaver"`, `"requiredPermissions": ["all"]`)
that builds its URL from a regional code:
```js
const config = { baseHost: "videoads.lgtvcommon.com", isNextSDP: true, isProduction: true, ricCode: "" };
const buildURL = () => {
const env = config.isProduction ? "" : "dev-";
const region = config.ricCode.toLowerCase() || "aic";
return `https://${env}${region}.${config.baseHost}/`;
};
```
The region map sends KOR/AUS/JPN/IND/SGP/NZL/TWN to KIC, the Americas to AIC, and everything
else, including NL, to EIC. So this device loads `https://eic.videoads.lgtvcommon.com/`.
Production vs. staging is decided by inspecting the `HOST` header returned by
`com.webos.service.sdx/getHttpHeaderForServiceRequest`.
### 2.4 Other ad-related paths
- `mnt-6/lg/cmn_data/adlogservice/data/`
- `usr-2/palm/license/XAD`
- `etc/pmlog.d/admanager.conf`, `etc/pmlog.d/adoverlay-service.conf`
- `ROOT/opt/webos/tests/admanager/gtest_admanager`
---
## 3. Viewing-history reporting
### 3.1 PersonalDataLogger
`usr-2/palm/applications/com.webos.app.inputcommon/qml/Interfaces/ChannelInfoInterfaces/PersonalDataLogger.qml`
is plaintext QML and is the most legible telemetry sender in the image. It fires on channel
change and POSTs through the SDX transport to the SDP service `ibis_stat_secure` at
`sdp/livetvwatch.json`.
Tuner body, built verbatim in `sendPersonalLogTv`:
```
chan_name, chan_code, prev_chan_code, device_src_idx, dtv_standard_type,
accept_flag, zipcode, timezone, user_id, channel_change
```
Set-top-box body, from `sendPersonalLogStb`, adds `content_id` and `from_where`. The
`from_where` values are declared at the top of the file:
```qml
readonly property int fromWhereAcr: 10
readonly property int fromWhereScd: 11
readonly property int fromWhereUei: 12
```
`checkSendLogStb` implements the priority ACR > UEI > SCD. This is the joining point between the
two halves of this report: ACR identifies what is playing on a connected set-top box, and the
result is reported to LG as a channel-viewing record with the same schema used for the internal
tuner.
Consent is checked per source before sending — `watchFlag` for RF, `ipWatchFlag` for IP
channels, `stbAcrWatchFlag`, `stbScdWatchFlag`, `stbUeiWatchFlag` for the STB paths — and the
server can independently switch each off in its reply via `activation_flag`, `ip_activation_flag`,
`stb_acr_activation_flag` and so on.
Sends are deliberately jittered:
```qml
requestTimer.interval = Math.floor((Math.random() * 10000) + 1) + minTime; // minTime = 10000
```
10 to 20 seconds after the channel change. A fixed delay would make the report trivially
correlatable with the user action; the randomisation removes that.
### 3.2 pbs
`usr-2/sbin/pbs` supplies the identity and the consent flags that `PersonalDataLogger` reads via
`getAuthenticatedData`. Its `UniqueID` class chains `getMacAddress()`, `encryptMacAddr()` and
`convertSHA512()`. The `user_id` in every viewing record is therefore a SHA-512 derived from the
TV's MAC address: stable for the life of the device, resettable only in the sense that
`UniqueID::reset()` exists.
`pbs` also handles `X-Device-Eula` headers, `isZipcodeMandatory()`, and talks to `ibis_secure`.
`mnt-6/lg/cmn_data/pbs/pbs_config.json` on this device:
```json
{ "watchFlag": true, "ipWatchFlag": false, "stbAcrWatchFlag": false,
"stbScdWatchFlag": false, "stbUeiWatchFlag": false,
"channelplus": true, "generalTerms": true, "pbsAuth": true,
"country": "NLD", "channelMapId": "NL-webOS-6.4.0-MR" }
```
Tuner channel-change reporting is on. The STB detection paths, including the ACR one, are off
server-side even though the ACR service itself is running and opted in.
---
## 4. The general analytics pipeline
Events are emitted as key/value log lines with a `NL_` prefix, collected by `rdxd`, and uploaded
by `uploadd` to LG's RDX server. Grepping the binaries, libraries, apps and configs yields
**584 distinct `NL_*` event codes**.
### 4.1 Emission
Producers call `PmLogCtl logkv`. `etc/palm/service-logger/rules/extInput.py` shows the pattern
end to end — it queries `com.webos.service.eim/getAllInputStatus`, walks every connected device,
and emits an inventory:
```python
logData['dev_list'] = devList
log = json.dumps(logData, ensure_ascii = False)
cmd = ['PmLogCtl', 'logkv', 'EIM', 'info', 'NL_EIM_INPUTLIST', log]
subprocess.run(cmd)
```
Each record carries the device `name`, `type`, `label` (from `labelName`, `serviceName` or
`brandName` of the URCU/SIMPLINK data) and `id`. `rule.json` schedules this monthly.
Sibling collectors in the same directory: `getGameDeviceInfo.py` (triggered on picture-mode or
aspect-ratio change while an HDMI input is active), `getScreenInfo.py`, `getFirstUseTVInfo.py`,
`getFirstUseAppInfo.py`, `getBatteryCyInfo.py`. The scheduler is `usr-2/sbin/service-logger`,
configured by `etc/palm/service-logger-conf.json` and backed by a MojoDB kind
`com.webos.service.logger.activity:1`.
### 4.2 Collection
`usr-2/sbin/rdxd`, configured by `etc/rdxd.conf` with `AutoUpload=true`. Report types are
registered in `etc/rdxd.d/`:
```json
{ "type":"analytic", "handlerURL":"com.palm.uploadd/uploadAnalyticsReport" }
{ "type":"crash", "handlerURL":"com.palm.uploadd/uploadCrashReport" }
```
It respects two gates, both visible as strings in the binary: "EULA has not accepted yet,
analytics reports will not be created" and "Devmode is enabled, analytics reports will not be
generated".
Crash reports are bundled with context produced by `usr-2/share/rdxd/make_sysinfo.sh`, which is
a plain shell script. It captures `/proc/cpuinfo`, the eMMC `manfid`/`oemid`/`serial`,
`/proc/mounts`, `/etc/version`, then `uptime`, `df -h`, `ps aux`, `date -R`,
`ls -l /var/luna/preferences` and `ifconfig`, then queries the bus for attached storage devices,
all input statuses, the last ten power on/off events, connection status and the running app list.
### 4.3 Upload
`usr-2/sbin/uploadd`, configured by `etc/uploadd.conf`:
```ini
[server=rdx]
AnalyticsLogURL=log/normal
CrashLogURL=log/crash
[upload]
MaxUploadBytes=2097152
```
It will not send unless the EULA is accepted, an SDX server is available, and SDP/SDX
authentication has succeeded — each is a distinct refusal string in the binary. Failed uploads
retry five times with a minimum hour between attempts. Sent files move to
`/var/spool/uploadd/uploaded`. First-use marker: `mnt-6/lg/cmn_data/uploadd/initial_date.txt`
contains `202004`.
The destination is the `rdx_secure` SDP service, `rdx2.lgsdp.com`, with a separate
developer-log route to `rdl.lgtvcommon.com`.
### 4.4 Event coverage
A sample of the 584 codes, chosen to show breadth rather than to be exhaustive:
```
NL_ACR_OFF_REASON NL_ADOVERLAY_OFF_REASON NL_ADMAN_REQ / _IMP / _CLK
NL_ADMAN_IFA_RST NL_AD_CLICK NL_AD_SWITCH_MANUAL
NL_CHANNEL_CHANGE NL_CHANNELS_IN NL_BROADCAST_CHANNEL_COUNT
NL_BROADCAST_WATCH_CHANNELSOURCE / _RESOLUTION / _SLINGTV
NL_APP_LAUNCH NL_APP_INSTALLED NL_APP_REMOVED
NL_LAUNCH_APP NL_CLOSE_APP NL_INPUT_APPHISTORY
NL_ACTIVATE_VOICE NL_ACTIVATE_SEARCH NL_CHATBOT_VOICE_RESULT
NL_FARVOICE_ACTIVATION / _TRIGGER / _ALEXA / _DANGBEI
NL_EIM_INPUTLIST NL_EULA_CHANGED NL_ACCESSIBILITY_CHANGED
NL_DVR_WATCHNRECORD NL_GAME_OPTIMIZER_CHANGE NL_CAMERA_APP_INFO
```
Some codes double as on-disk cache filenames, e.g. `mnt-6/lg/cmn_data/NL_VIDEO_ALL_DIM_ASPECT_RATIO_HISTORY_CACHE`.
---
## 5. SDX: the shared transport
`usr-2/sbin/sdx` (792 484 bytes), service `com.webos.service.sdx`, is the HTTPS client every
other component posts through. Callers pass a logical `serviceName` plus a path and method, and
`sdx` resolves the hostname, attaches authentication headers and performs the request. Its two
public helpers, seen throughout the apps, are `send` and `getHttpHeaderForServiceRequest`.
The routing table ships at `usr-2/palm/sdx/server_addr_version.conf` and is updated in place at
`mnt-6/lg/cmn_data/sdp/sdx/server_addr_version.conf` (37 entries on this device). In both files
the LG-operated hosts appear as the literal `nextlgsdp.com`, prefixed at runtime by region.
Entries relevant here:
| serviceName | domain | base path |
| --- | --- | --- |
| `sdp_logging` | `nextlgsdp.com` | `logging/10.0.0/` |
| `ibis_secure` | `ibs.nextlgsdp.com` | `ibs/10.1.0/` |
| `ibis_stat_secure` | `ibsstat.nextlgsdp.com` | `ibs/10.0.0/` |
| `rdx_secure` | `rdx2.nextlgsdp.com` | `rdx/9.0.0/` |
| `rdxdev_secure` | `rdl.lgtvcommon.com` | `devlog/nextsdp/v1.0/` |
| `cdpbeacon_secure` | `cdpbeacon.lgtvcommon.com` | `api/v2/beacon/` |
| `nudge_secure`, `nudge_log_secure` | `nudge.lgtvcommon.com` | `nudge/nextsdp/` |
| `recommend_secure`, `tlamp_secure` | `recommend.lgtvcommon.com` | `recommend/…`, `tlamp/…` |
| `cpv_secure` | `pnv.lgtvcommon.com` | `rest/csi/` |
| `homeprv_secure` | `homeprv.lgtvcommon.com` | `nextsdp/homeprv/` |
| `service_setting_secure` | `wiseconfig.lgtvcommon.com` | `nextsdp/smartConfig/` |
| `cpauth_secure` | `cpauth.lgtvcommon.com` | `auth/nextsdp/v1.0/` |
`sdx` also exposes `getRdxServer` and `getRdxServerUrl` to the bus, which is how `uploadd`
discovers where to post.
Because everything funnels through one daemon, the per-component hostnames above are not
individually resolvable from a network capture without also reading this table.
---
## 6. Behavioural profiling on-device
### 6.1 user-context-manager and user-intent-manager
`usr-2/sbin/user-context-manager` and `usr-2/sbin/user-intent-manager` maintain ranked usage
profiles in MojoDB. Their `QueryProcessor` builds `RankInfo` lists by genre, keyword, intent
type and channel (`makeRankInfoGenreList`, `makeRankInfoKeywordList`,
`makeRankInfoIntentTypeList`, `makeRankInfoYChannelList`), and expose `getHistory`, `getRank`,
`getSubRank` and `getCategorizedRank` on the bus.
The method name worth flagging is `DBHandler::findHistoryInfoForServer` in
`user-context-manager` — a distinct query path from `findHistoryInfo`, i.e. history shaped for
transmission rather than for local use. There is also a monthly summary feature
(`displayMonthlyReport`) whose user-facing strings are in the binary: "Last month, you watched TV
for an average of about {hour} hours per day and used {app name} the most."
`var-18/preferences/com.webos.service.usercontextmanager` currently holds
`1672531310NL250[REDACTED 10 digits]com.webos.app.home1756457284` — a timestamp, the device serial, the
most-used app, and a second timestamp.
### 6.2 nudge
`usr-2/sbin/nudge` reads those profiles (`GetUCMHistory`, `GetUCMChannelRank`) and keeps its own
kind `com.webos.service.nudge.history:1`. Rules are JSON in `mnt-6/lg/cmn_data/nudge/`, one per
prompt: `nu_ch_watch_tv_n_times.json`, `nu_AI_channel_zapping.json`, `nu_view_sport.json`,
`nu_input_game.json` and about a dozen more. Each carries a `provideCondition` describing the
behaviour that triggers it, for example:
```json
"queryInfo": { "duration": 14, "comment": "query channel history from before 14 day" },
"zapping": { "zappingHistoryCount": 8, "zappingHistoryRatio": 20,
"comment": "trigger it after changing the channel in case watching the channel 8 times and 20% ratio" }
```
and a `policyCondition` with `selectRate`, `closeCount`, `timeoutCount` and `suspendCount` —
the prompt suppresses itself if the user keeps dismissing it. One nudge's own text describes the
data basis: "You can receive AI generated recommendations of features, apps, and contents based
on your TV usage history."
The server-side switch `nudge_log_transfer` is `on` (see §7).
### 6.3 Other collectors
- `usr-2/sbin/contentminer` (1 018 220 bytes, mode 0700) — `ContentMinerLogger` with mining,
parsing and action engines, `OnMiningInfoChanged_Full`, `OnMiningInfoChanged_One`, and
`Callback_GetHttpHeaderForSvcReq`, so it is SDP-connected. Preferences directory exists at
`var-18/preferences/com.webos.service.contentminer`.
- `usr-2/sbin/color-info-miner`, `usr-2/sbin/cbox`, `usr-2/sbin/homelaunchpoints`,
`usr-2/sbin/sdp-server-notice`.
- `usr-2/sbin/systemprofile` (mode 0700) — local performance profiling into `/var/webos-profile/`;
no network paths found.
Checked and found not to be telemetry: `tvdataexchanger` (TV-to-TV settings transfer over USB),
`remotelogger` and `snaplog` (local log dumps), `pacrunner` (a proxy auto-config runner, matched
only on the substring "acr").
---
## 7. Server-controlled feature switches
`mnt-6/lg/cmn_data/sdp/sdx/detailconfig.json` is the cached response from
`wiseconfig.lgtvcommon.com` (`service_setting_secure`, path `nextsdp/smartConfig/getConfig`). It
holds 19 configuration blocks, including a `services_info.itemList` of on/off switches that the
server sets. Relevant entries as currently cached:
| Switch | State |
| --- | --- |
| `nudge_service` | on |
| `nudge_server_request` | on |
| `nudge_log_transfer` | on |
| `search_recommendation` | on |
| `voiceid` | on |
| `product_register` | on |
| `thetake_acr` | off |
| `contextual_masa` | off |
| `music_discovery` | off |
| `lgshop_overlay` | off |
`thetake_acr` refers to a second, separate ACR integration (TheTake), disabled for this region.
The same file carries two advertising-identifier allowlists — third-party apps permitted to read
the device IFA:
```
AdIdWhiteList: amazon
NewAdIdWhiteList: amazon, com.disney.disneyplus-prod, com.twin.app.gamingportal,
com.wbd.hbomax, youtube.leanback.v4
```
---
## 8. Consent state as recorded on the device
`mnt-6/lg/cmn_data/sdp/sdx/eula.json`:
```json
{ "deviceCountryCode": "NL", "deviceDateTime": "1752508517",
"eulaChangeReason": "changed", "synchronized": true,
"statusList": [
{ "managementTypeCode": "S_VNG", "versionId": "20231109136_NL", "status": "A" },
{ "managementTypeCode": "S_MKT", "versionId": "20240715263_NL", "status": "A" },
{ "managementTypeCode": "S_SVC", "versionId": "20240716023_NL", "status": "A" },
{ "managementTypeCode": "S_TAG", "versionId": "20250627461_NL", "status": "A" },
{ "managementTypeCode": "S_ADG", "versionId": "20250627462_NL", "status": "A" } ] }
```
All five accepted. `S_SVC` is the service terms and `S_MKT` marketing; `S_ADG` and `S_TAG` are
advertising and, by position and by the `acrGdprAllowed`/`acrAdAllowed` setting names, targeted
advertising. I did not find a file in the image that defines these three-letter codes, so treat
the expansions of `S_TAG` and `S_VNG` as inference.
`mnt-6/lg/cmn_data/sdp/sdx/maketingAllowedDate.json` schedules a re-consent toast via
ActivityManager for `2027-01-07 13:36:03`, two years after the recorded acceptance date of
2025-04-07, described in the file as "Create toast within 2 years that reminds maketingAllowed"
(the misspelling is LG's).
Corresponding Luna settings:
| Key | File | Value |
| --- | --- | --- |
| `livePlus` | `var-18/luna/preferences/option` | `on` |
| `watchedListCollection` | `var-18/luna/preferences/option` | `on` |
| `contentRecommendation` | `var-18/luna/preferences/other` | `on` |
| `aiNudge` | `var-18/luna/preferences/general` | `on` |
| `adCookie` | `var-18/luna/preferences/general` | `on` |
| `screenSaverAd` | `var-18/luna/preferences/general` | `on` |
| `customizedAd` | `var-18/luna/preferences/general` | `off` |
| `doNotSellMyPersonalInformation` | `var-18/luna/preferences/general` | `off` |
| `personalRecommend` | `var-18/luna/preferences/general` | `off`, `changedByUser: false` |
`personalRecommend` being off with `changedByUser: false` means that one is a default, not a
user choice.
---
## 9. Vendors
Searched for the known ACR and audience-measurement SDKs. Results:
- **Alphonso** — present and in use. Sole ACR vendor on this device.
- **TheTake** — referenced as a switch (`thetake_acr`), off; no binary found.
- **Gracenote** — 771 string hits, all in EPG, sports and metadata contexts
(`com.webos.app.sportsteamsettings`). Also the origin of the TMS program and station IDs that
Alphonso returns on a match.
- **Samba TV, Inscape, ACRCloud, Shazam** — no SDK, no endpoints.
- **Nielsen, Comscore, Conviva, Kantar** — no SDK. The handful of substring hits are in spelling
dictionaries, a Python stdlib file, third-party licence texts and one user-guide HTML page.
- **Google Analytics, DoubleClick, Firebase/Crashlytics, Sentry, Branch, Amplitude, Mixpanel,
Segment, Moat, IAS, FreeWheel, SpotX, PubMatic, Criteo, Innovid, Adobe** — no hits.
All outbound analytics on this device goes to LG-operated infrastructure
(`*.lgsdp.com`, `*.lgtvcommon.com`, `*.lgsmartad.com`, `*.lgappstv.com`, `ngfts.lge.com`) or to
Alphonso (`*.alphonso.tv`).
---
## 10. Summary of live status on this device
Enabled and running: ACR (`livePlus: on`, `optin`, Alphonso engine loaded), ad manager with an
active `nl.ad.lgsmartad.com` session, ad overlay service, screensaver video ads, tuner
channel-change reporting to `ibsstat`, the `NL_*` analytics pipeline through rdxd/uploadd, nudge
with log transfer on, and the usage-ranking services.
Enabled but currently unused: the STB detection reporting paths, including the ACR one
(`stbAcrWatchFlag: false`) — the ACR engine is running and matching, but its results are not
being forwarded as set-top-box viewing records.
Disabled: `customizedAd`, `personalRecommend`, `thetake_acr`, and the wider set of `off` switches
in `detailconfig.json`.
The Wi-Fi neighbour scan in §1.5 is the item with no corresponding user-facing control. It is
enabled by a server flag inside the Alphonso SDK, its output sits in the ACR service's state
directory, and it is sent to `/user/location` alongside the fingerprint stream.
---
## Appendix: quick file index
```
ACR
usr-2/sbin/acr2
usr-2/lib/libalphonsosolution.so.1.0.0
usr-2/lib/libas.so.3.0.93
usr-2/lib/libvtcapture.so.1.0.0
usr-2/share/acr/
usr-2/palm/services/com.webos.service.acr/services.json
usr-2/palm/applications/com.webos.app.acr{component,overlay,hdmi1..4}/
etc/palm/activities/com.webos.service.acr/
mnt-6/lg/cmn_data/acr/{data,alphonso,lib}/
Advertising
usr-2/sbin/admanager
usr-2/sbin/adoverlay-service
usr-2/lib/libalphonsoadoverlay.so.1.0.0
usr-2/palm/applications/com.webos.app.{adoverlay,adoverlayex,adhdmi1..4,videoads}/
etc/init/admanager.conf
etc/palm/activities/com.webos.service.ad{manager,overlay}/
mnt-6/lg/cmn_data/{admanager,adoverlay,adlogservice}/
Viewing history
usr-2/palm/applications/com.webos.app.inputcommon/qml/Interfaces/ChannelInfoInterfaces/PersonalDataLogger.qml
usr-2/sbin/pbs
mnt-6/lg/cmn_data/pbs/pbs_config.json
Analytics pipeline
usr-2/sbin/{rdxd,rdx_reporter,uploadd,service-logger}
etc/{rdxd.conf,uploadd.conf}
etc/rdxd.d/
etc/palm/service-logger/rules/
usr-2/share/rdxd/, usr-2/share/uploadd/
mnt-6/lg/cmn_data/uploadd/
Transport and config
usr-2/sbin/sdx
usr-2/palm/sdx/server_addr_version.conf
mnt-6/lg/cmn_data/sdp/sdx/{server_addr_version.conf,detailconfig.json,eula.json,maketingAllowedDate.json}
Profiling
usr-2/sbin/{user-context-manager,user-intent-manager,nudge,contentminer}
mnt-6/lg/cmn_data/nudge/
var-18/preferences/com.webos.service.{usercontextmanager,userintentmanager,contentminer}/
Settings
var-18/luna/preferences/{general,option,other,eula}
etc/palm/defaultSettings.json
```