Is Netflix Dual subtitle for learning languages safe?
Netflix Dual subtitle for learning languages loads Netflix page code from Google Cloud Storage when a remote manifest enables it.
The extension injects code into Netflix pages and checks a Google Cloud Storage manifest that can make it load a remote replacement script for that page logic. It also stores the Google sign-in JWT in Netflix localStorage with a bundled AES passphrase and later sends the token to its license API.
Who publishes itniko - 2 other listings from the same operator, 1 of them carrying a finding
niko - 2 other listings from the same operator, 1 of them carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
2 other listings published from this account, 26k+ users between them. 1 of them carries a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote Script Can Replace Netflix Page Integration
When Netflix loads, the extension injects its bundled page integration and requests a GCS manifest.
If it returns an enabled flag, the page loads a replacement insert script from GCS with the same context as the integration.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You open a Netflix page while the extension is active.
The extension checks a hosted update manifest and can load a hosted replacement for its Netflix page script.
The hosted script is added to the page only when the manifest response contains an enabled update flag.
- Update manifesthttps://storage.googleapis.com/niko-extension/netflix-dual-subtitle-for-learning-languages/mv3/manifest.json
This remote file decides whether the page keeps the packaged script or loads hosted code instead.
- Update flag{"update":true}
When this flag is enabled, the page loads code from the hosted script URL.
- Hosted scripthttps://storage.googleapis.com/niko-extension/netflix-dual-subtitle-for-learning-languages/mv3/insert.min.js
This is the replacement script URL that the page loads when the remote flag is enabled.
- Injected script IDdualSubtitleForLearningLanguages-secondary-subtitle-wrapperGoogleCloudInsertMinJs
This page element marks the hosted script that the extension adds to Netflix.
- Netflix page URLhttps://www.netflix.com/watch/81234567
The hosted page script runs in the Netflix page you are using, including the page address and page content available there.
The packaged content script injects the Netflix page integration
Readable equivalent of the content script injection path
content.min.jsconst logger = require(7434);const appName = require(8489).APP_NAME;function injectHeadScript() { if (!document.querySelector("head")) return false; const scriptId = `${appName}InsertJSToHead`; if (document.getElementById(scriptId)) return false; const file = "insert_to_head.min.js"; const script = document.createElement("script"); script.onload = function () { logger.log(`Injected: ${file} Epoch time: ${Date.now()}`); }; script.async = false; script.setAttribute("id", scriptId); script.setAttribute("type", "text/javascript"); script.setAttribute("src", chrome.runtime.getURL(file)); document.head.appendChild(script); return true;}const observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { mutation.addedNodes.forEach(function (node) { if (node.nodeName.toLowerCase() === "head" || node.nodeName.toLowerCase() === "body") { if (injectHeadScript()) observer.disconnect(); } }); });});observer.observe(document, { childList: true, subtree: true });if (injectHeadScript()) observer.disconnect();window.addEventListener("DOMContentLoaded", function () { ["insert.min.js"].forEach(function (file) { const script = document.createElement("script"); script.onload = function () { logger.log(`Injected: ${file} Epoch time: ${Date.now()}`); }; script.async = false; script.setAttribute("type", "text/javascript"); script.setAttribute("src", chrome.runtime.getURL(file)); document.head.appendChild(script); });});Readable manifest scope
manifest.json"host_permissions": [ "https://www.netflix.com/*"],"content_scripts": [ { "matches": [ "https://www.netflix.com/*" ], "js": [ "content.min.js" ], "run_at": "document_start" }],"web_accessible_resources": [ { "resources": [ "insert.min.js", "insert_to_head.min.js" ], "matches": [ "https://www.netflix.com/*" ] }]The injected script gates hosted code on a remote manifest flag
Readable equivalent of the remote update branch
insert.min.jsconst googleCloudScriptId = `${secondarySubtitleWrapperId}GoogleCloudInsertMinJs`;new Promise(function (resolve) { if (document.getElementById(googleCloudScriptId)) { logger.log("Emergency update already exists"); resolve(false); return; } new Promise(function (resolveManifest, rejectManifest) { const xhr = new XMLHttpRequest(); xhr.responseType = "json"; xhr.open( "GET", "https://storage.googleapis.com/niko-extension/netflix-dual-subtitle-for-learning-languages/mv3/manifest.json", true ); xhr.timeout = 15000; xhr.onload = function () { if (xhr.status === 200) { resolveManifest(xhr.response); } else { logger.error(`xhr manifest error: ${xhr.statusText} status: ${xhr.status}`); rejectManifest(); } }; xhr.onerror = function () { logger.error(`xhr manifest error: ${xhr.statusText}`); rejectManifest(); }; xhr.ontimeout = function () { logger.error(`xhr manifest timeout: ${xhr.statusText}`); rejectManifest(); }; xhr.send(null); }) .then(function (manifest) { if (manifest.update) { logger.log("Emergency update is enabled"); resolve(true); } else { logger.log("Emergency update is not enabled"); resolve(false); } }) .catch(function () { resolve(false); });}).then(function (enabled) { if (enabled) { new Promise(function (resolve, reject) { const url = "https://storage.googleapis.com/niko-extension/netflix-dual-subtitle-for-learning-languages/mv3/insert.min.js"; const script = document.createElement("script"); script.id = googleCloudScriptId; script.onload = function () { logger.log(`Injected: ${url} Epoch time: ${Date.now()}`); resolve(); }; script.onerror = function () { logger.error("Emergency update failed to load"); reject(); }; script.setAttribute("type", "text/javascript"); script.setAttribute("src", url); (document.head || document.documentElement).appendChild(script); }).catch(function () { startPackagedIntegration(); }); } else { startPackagedIntegration(); }});- storage.googleapis.com
Google Cloud Storage host for the update manifest and the hosted replacement insert script.
Where it sends data
Destinations our analysis observed NetflixDualSubtitle contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- storage.googleapis.com
NetflixDualSubtitle sends data to storage.googleapis.com. 7 other extensions we have analysed send data here.
- niko-pay.appspot.com
NetflixDualSubtitle sends data to niko-pay.appspot.com. No other extension we have analysed sends data here.