Security Alert: Confirmed Malware
twDisplayVicinity
ID: anmfeeanmnmdkjlhojpodibignbcfgjm
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- https://memo.furyutei.workView Profile
- Help
- Help Center
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- No
- Mailbox exists
- Yes
- Website
- Visit
Display the vicinity of a particular tweet on Twitter.
On Twitter's official website (https://twitter.com/*), display tweets before and after a specific tweet in timeline format. It is useful when you want to know what the same person is tweeting about before and after a tweet. ========== - Retweeted - Introduced in blogs etc. - Hit with search engine You might not know what tweets were around before and after. If you can read the tweets before and after, you may be able to see an unexpected context you did not understand from a single tweet. When installing this extension, the "vicinify(neighbor)" link will be displayed when you see Twitter's timeline or single tweet. By clicking on this link, a separate tab / another window opens, you can see the timeline before and after that tweet. Also, if your tweets are retweeted, opening the "Notification" timeline (https://twitter.com/i/notifications) will also display the "vicinity" link in the notice of the retweet. By clicking, you can see what the person is tweeting about RT tweets.
Sensitive Domain Access
This extension has access to the following sensitive domains:
- *://*.twitter.com/*
Uses declarativeNetRequest to forcibly downgrade Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy on twitter.com responses to 'unsafe-none'. Weakening these browser-level isolation headers reduces cross-origin protections, allowing the extension's injected page-context scripts (and any popup it opens via window.open) to retain references to and manipulate the parent window across navigations.
const net_request_rules = [{ "id": 10, "priority": 1, "action": { "type": "modifyHeaders", "responseHeaders": [{ "header": "cross-origin-embedder-policy", "operation": "set", "value": "unsafe-none" }, { "header": "cross-origin-opener-policy", "operation": "set", "value": "unsafe-none" } ] }, "condition": { "urlFilter": "||twitter.com", "resourceTypes": [ "main_frame", "sub_frame", "xmlhttprequest" ] } },Monkey-patches XMLHttpRequest.prototype.open to silently rewrite both the destination URL (via url_filter) and the responseText / responseURL the calling page sees (via response_filter). Object.defineProperty is used to make the read-only response properties writable so the page reads attacker-controlled content. This is a classic in-page network-interception / response-tampering primitive.
window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) { var xhr = this, called_url = url, replaced_url = url, config = configs.slice() .reverse() .filter(config => config.reg_url.test(url || ''))[0], url_filter = config.url_filter, response_filter = config.response_filter; if (typeof url_filter == 'function') { arguments[1] = replaced_url = url_filter(url || ''); if (typeof response_filter != 'function') { response_filter = (original_responseText, replaced_url, called_url, original_responseURL) => original_responseText; } } xhr._called_url = called_url; xhr._replaced_url = replaced_url; if (async !== false) { if (typeof response_filter == 'function') { xhr.addEventListener('readystatechange', function(event) { if (xhr.readyState != 4) { return; } var original_responseURL = event.target.responseURL, original_responseText = event.target.responseText, filterd_responseText = response_filter(original_responseText, replaced_url, called_url, original_responseURL); Object.defineProperty(xhr, 'responseURL', { writable: true }); xhr.responseURL = called_url; xhr._original_responseURL = original_responseURL; Object.defineProperty(xhr, 'responseText', { writable: true }); xhr.responseText = filterd_responseText; xhr._original_responseText = original_responseText; }); } } return original_prototype_open.apply(xhr, arguments);};Hardcodes Twitter web-client bearer tokens and harvests the user's ct0 CSRF cookie directly from document.cookie to forge authenticated OAuth2Session calls against api.twitter.com with credentials:'include'. This impersonates the logged-in user from page context and is the standard pattern used to silently scrape a user's timeline using their session.
api1_bearer = 'AAAAAAAAAAAAAAAAAAAAAF7aAAAAAAAASCiRjWvh7R5wxaKkFp7MM%2BhYBqM%3DbQ0JPmjU9F6ZoMhDfI4uTNAaQuTDm2uO9x3WFVr2xBZ2nhjdP0', api2_bearer = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA', ... headers: { 'authorization': 'Bearer ' + ((api_user_timeline_url.indexOf('/2/') < 0) ? api1_bearer : api2_bearer), 'x-csrf-token': document.cookie.match(/ct0=(.*?)(?:;|$)/)[1], 'x-twitter-active-user': 'yes', 'x-twitter-auth-type': 'OAuth2Session', 'x-twitter-client-language': 'en', }, mode: 'cors', credentials: 'include',Provides generic helpers (inject_script_sync, inject_code, inject_code_sync) that lift a nonce off the host page's existing <script nonce> tag and use it to inject extension-controlled <script> elements (or arbitrary inline JS strings) into the page's main world, bypassing twitter.com's CSP. This nonce-stealing pattern lets the extension execute code in the page origin with full access to cookies, tokens, and authenticated APIs.
inject_script_sync: (script_url, nonce) => { if (!/^(https?:)?\/\//.test(script_url)) { try { script_url = chrome.runtime.getURL(script_url); } catch (error) { script_url = new URL(script_url, location.href) .href; } } let script = create_script(nonce); script.async = false; script.src = script_url; document.documentElement.insertBefore(script, document.documentElement.firstChild); script.remove(); }, ... inject_code_sync: (code, nonce) => { let script = create_script(nonce); script.async = false; script.textContent = code; document.documentElement.appendChild(script); script.remove(); },Same credential-forging pattern repeated in the main content script: reads ct0 from document.cookie and combines it with hardcoded Twitter web bearer tokens to build OAuth2Session-authenticated API request headers. The extension then issues authenticated calls to user_timeline / search / statuses/retweets endpoints on the user's behalf, scraping data from Twitter as the logged-in user.
api_get_csrf_token = () => { var csrf_token; try { csrf_token = document.cookie.match(/ct0=(.*?)(?:;|$)/)[1]; } catch (error) {} return csrf_token; }, create_api_header = (api_url) => { return { 'authorization': 'Bearer ' + (((api_url || '') .indexOf('/2/') < 0) ? API_AUTHORIZATION_BEARER : API2_AUTHORIZATION_BEARER), 'x-csrf-token': api_get_csrf_token(), 'x-twitter-active-user': 'yes', 'x-twitter-auth-type': 'OAuth2Session', 'x-twitter-client-language': LANGUAGE, }; },Strips the Cookie header from requests to api.twitter.com/oauth2/token (so the extension can mint anonymous app-only OAuth2 access tokens against the Twitter API without binding them to the user) and spoofs the User-Agent to an old Trident/Waterfox value on requests bearing a custom __twdv=legacy marker. Both rules are network-interception primitives used to evade Twitter's normal authentication/UA-based serving paths.
{ "id": 20, "priority": 2, "action": { "type": "modifyHeaders", "requestHeaders": [{ "header": "cookie", "operation": "remove" }] }, "condition": { "urlFilter": "||api.twitter.com/oauth2/token", "resourceTypes": [ "main_frame", "sub_frame", "xmlhttprequest" ] }}, { "id": 30, "priority": 2, "action": { "type": "modifyHeaders", "requestHeaders": [{ "header": "user-agent", "operation": "set", "value": "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) Waterfox/56.2" }] },The service worker exposes a generic FETCH_JSON message handler that performs an arbitrary fetch() on any URL/options provided by content scripts and returns the parsed JSON. Although callers in this extension only pass twitter.com URLs, the handler itself does no URL allowlisting, giving any compromised content script (or page able to message the extension) a CORB/CORS bypass primitive via the privileged background context.
case 'FETCH_JSON':log_debug('FETCH_JSON', message);fetch(message.url, message.options) .then(response => response.json()) .then((json) => { log_debug('FETCH_JSON => json', json); sendResponse({ json: json, }); }) .catch((error) => { log_error('FETCH_JSON => error', error); sendResponse({ error: error, }); });return true;Captured XHR request bodies and decoded JSON responses for every twitter.com API call are forwarded via window.postMessage to the content script for further processing. While the target origin is restricted to location.origin (twitter.com), this still exfiltrates the contents of authenticated API responses (timelines, user data, retweet info) out of the page into the extension, materially expanding what the extension sees beyond what its declared permissions imply.
write_data = (() => { var fetch_wrapper_container = document.querySelector('#' + container_dom_id); return (data, data_dom_id, reg_url_list) => { var url = data.url; if (!reg_url_list.some(reg_url_filter => reg_url_filter.test(url))) { return; } window.postMessage({ namespace: params.SCRIPT_NAME, message_id: message_id_map[data_dom_id], url: url, data: data, }, location.origin);Reaches into Twitter's React app private state (window.__INITIAL_STATE__.featureSwitch) using a MutationObserver and silently flips the user's server-delivered feature flags (responsive_web_graphql_profile_timeline) to force a different code path. Tampering with internal app feature switches behind the user's back is suspicious behavior and is also brittle/easily abused.
if (!window.__INITIAL_STATE__) { return;}observer.disconnect();const featureSwitch = window.__INITIAL_STATE__.featureSwitch;...try { if (DEBUG) console.debug('featureSwitch.defaultConfig.responsive_web_graphql_profile_timeline.value:', featureSwitch.defaultConfig.responsive_web_graphql_profile_timeline.value, '=>', false); featureSwitch.defaultConfig.responsive_web_graphql_profile_timeline.value = false;} catch (error) { console.error('[twDisplayVicinity]', error);}try { if (DEBUG) console.debug('featureSwitch.user.config.responsive_web_graphql_profile_timeline.value:', featureSwitch.user.config.responsive_web_graphql_profile_timeline.value, '=>', false); featureSwitch.user.config.responsive_web_graphql_profile_timeline.value = false;} catch (error) { console.error('[twDisplayVicinity]', error);}By severity
Versions scanned
Showing 1 of 22 scanned versions with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 0.3.3.0 | 9 |
Files with findings
6 distinct paths — top paths by unique finding count:
- scripts/background.js3
- scripts/fetch_wrapper.js2
- scripts/feature_switch.js1
- scripts/inject_script.js1
- scripts/intercept_xhr.js1
- scripts/main_react.js1
URLs
View the external URLs this extension communicates with to understand its network activity and data interactions.
Gain full insight into all external connections.
Upgrade for full visibility.
Gain full insight into all external connections.
Upgrade for full visibility.
Code Diff
Compare extension code between any two versions.
No comparable text files found between these versions.
Browse and explore files within this extension package
Gain full insight into all external connections.
Upgrade for full visibility.