Is Video Downloader for Chrome - M3U8 Video Downloader | 4saved safe?
Video Downloader for Chrome - M3U8 Video Downloader | 4saved is high risk. Our dynamic analysis captured a startup GET to 4saved.com/domains.json?v=1, applied via updateDynamicRules.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote-injected declarativeNetRequest rules strip CSP and forge CORS headers
Our dynamic analysis captured a startup GET to 4saved.com/domains.json?v=1, applied via updateDynamicRules.
Rules strip CSP/X-Frame-Options, set Access-Control-Allow-Origin to *, and spoof Referer/Origin for CDNs, on every site.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You install the extension and open any browser session.
The service worker runs at startup and checks whether the remote config was fetched in the past 24 hours.
The extension fetches a rule list from 4saved.com and applies it to every website you visit.
chrome.declarativeNetRequest.updateDynamicRules replaces all existing dynamic rules with the operator-supplied set, which modifies response headers globally.
Returns a JSON array of declarativeNetRequest rule objects. Observed rules include: removeHeaders actions for content-security-policy and x-frame-options; setHeaders action for access-control-allow-origin: *; modifyHeaders actions spoofing Referer and Origin for CDN domains.
Rule fetch and application logic (background.js)
// background.js:3907-3912 — Remote rule fetchif (!remoteConfig.rulesFetchDate || remoteConfig.rulesFetchDate + 86400000 < Date.now()) { fetch('https://4saved.com/domains.json?v=1') .then(r => r.json()) .then(rules => { if (rules && rules.length) { store.commit('SET_RULES', rules); store.commit('SET_RULES_FETCH_DATE', Date.now()); } });}// background.js:7661-7678 — Application to browserclass DynamicRules { setRules() { chrome.declarativeNetRequest.getDynamicRules().then(existingRules => { chrome.declarativeNetRequest.updateDynamicRules({ addRules: store.state.storage.remoteConfig.rules, removeRuleIds: existingRules.map(r => r.id) }); }); }}// Triggered by Vuex mutation: store.subscribe(mutation => {// if (mutation.type === 'remoteConfig/SET_RULES') dynamicRules.setRules();// });- 4saved.com
Operator-controlled server serving the declarativeNetRequest rule array; same domain as the extension's name. Rules fetched every 24 hours and applied to all sites.
Our dynamic analysis observed rules that: remove the Content-Security-Policy and X-Frame-Options response headers from any site; set Access-Control-Allow-Origin to * on responses; and replace Referer and Origin headers on requests to CDN domains. Stripping CSP weakens the security of every site you visit during the session. Because these rules are fetched from a remote server, their content can change between browser sessions without any extension update.
Remote-controlled m3u8 domain whitelist broadens webRequest inspection scope
Our dynamic analysis captured a startup GET to 4saved.com/master.json.
Its 'master-m3u8-domains' array, stored as remoteConfig.rawMasterDomains, sets which responses get inspected for m3u8 content.
Observed: ['twimg.com', 'hls.ted.com'].
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You open any browser session with the extension installed.
The service worker runs at startup and fetches the operator-controlled domain list.
The extension downloads a domain list from 4saved.com and uses it to decide which of your network responses to inspect for video content.
Any domain added to the server-side list will subsequently trigger additional fetching and parsing of matching network responses, without a browser extension update.
Returns a JSON object with key 'master-m3u8-domains' containing an array of domain strings. Observed response: {"master-m3u8-domains": ["twimg.com", "hls.ted.com"]}.
Whitelist fetch and webRequest matching (background.js)
// background.js:3900-3905 — Whitelist fetchif (!remoteConfig.rawMasterDomainsFetchDate || remoteConfig.rawMasterDomainsFetchDate + 86400000 < Date.now()) { fetch('https://4saved.com/master.json') .then(r => r.json()) .then(data => { store.commit('SET_RAW_MASTER_DOMAINS', data && data['master-m3u8-domains'] ? data['master-m3u8-domains'] : []); store.commit('SET_RAW_MASTER_DOMAINS_FETCH_DATE', Date.now()); });}// background.js:7293 — webRequest matchingfor (let i = 0; i < remoteConfig.rawMasterDomains.length; i++) { if (request.url.includes(remoteConfig.rawMasterDomains[i])) { matchedRemoteDomain = true; }}// matchedRemoteDomain triggers additional fetch + m3u8 playlist parsing- 4saved.com
Operator-controlled server serving the m3u8 domain whitelist, same domain as the extension brand. Fetched every 24 hours; the list sets which responses get retrieved.
interceptor.js injected as page script patches XMLHttpRequest on all sites
On every page except YouTube and 4saved.com, the content script injects interceptor.js, a resource any site can load.
It patches XMLHttpRequest's open/send/setRequestHeader to log XHR URLs, read bodies, and parse responses for video URLs.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-506
- Source
- Dynamic sandbox
You navigate to any website (other than YouTube and 4saved.com).
The content script, which runs at document_start on all URLs, calls setInterceptor() unconditionally.
The extension injects interceptor.js as a page-level script that replaces the browser's XMLHttpRequest with a custom version that reads every request URL and response body.
The patched XHR logs each request and, for known video-hosting domains, parses the response body to extract video stream URLs.
Script injection and XHR monkey-patch (content.js + interceptor.js)
content.js — interceptor injection (content.js:5777-5780)
content.js// Injects interceptor.js into the page's own JavaScript contextconst script = document.createElement('script');script.src = chrome.runtime.getURL('interceptor.js');script.onload = function() { this.remove(); };(document.head || document.documentElement).appendChild(script);interceptor.js — XHR prototype monkey-patch (interceptor.js:405-435)
interceptor.js// Replaces the native XHR prototype methods on the pageconst proto = XMLHttpRequest.prototype;const origOpen = proto.open;const origSend = proto.send;const origSetHeader = proto.setRequestHeader;proto.open = function(method, url) { this._method = method; this._url = url; this._requestHeaders = {}; this._startTime = new Date().toISOString(); return origOpen.apply(this, arguments);};proto.setRequestHeader = function(key, value) { this._requestHeaders[key] = value; return origSetHeader.apply(this, arguments);};proto.send = function(body) { this.addEventListener('load', function() { if (this._url) { // Iterate registered origin handlers for (let i = 0; i < handlers.length; i++) { for (let j = 0; j < handlers[i].origins.length; j++) { let url = this._url; if (!url.startsWith('http')) url = document.location.origin + url; if ( (handlers[i].origins[j] instanceof RegExp && document.location.hostname.match(handlers[i].origins[j])) || document.location.hostname === handlers[i].origins[j] ) { // Pass full response text to the domain-specific handler handlers[i].onLoad(this.responseText, url); break; } } } } }); return origSend.apply(this, arguments);};- Request URLhttps://www.instagram.com/api/v1/media/3491827364/info/
The full URL of every XHR request made by the page is recorded in the patched prototype.
- Response body{"items":[{"video_versions":[{"url":"https://scontent-lhr8-1.cdninstagram.com/v/t50.2886-16/...","width":1080,"height":1920}]}]}
For requests from instagram.com, vimeo.com, x.com, twitter.com, and configured CDNs, the full response text is parsed for video stream URLs.
- Request headers{"Authorization": "Bearer IGQWRPa...", "X-CSRFToken": "abc123"}
All custom request headers set by the page before each XHR send are captured in this._requestHeaders.
- www.instagram.com
XHR responses parsed for video_versions JSON keys; extracted video CDN URLs dispatched to the content script.
- vimeo.com
XHR responses to /config paths parsed for progressive and HLS video URLs.
- x.com / twitter.com
XHR responses parsed for video_info.variants arrays to extract tweet video URLs.
- hls.enjoy24cdn.com / 928hd.tv / showhd9.com
XHR responses parsed for #EXTM3U m3u8 content to list available stream qualities.