Is Video Downloader Social safe?

Medium risk

Video Downloader is medium risk. The shipped service worker captures the outgoing header array from X.com API requests and stores it as twitter_token locally. A later function rebuilds those headers and sends a GET to X.com's timeline API using the saved auth headers.

Downloader Webv0.0.9Chrome Web Store
45Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

X.com authentication headers are saved and reused

MediumunwantedCWE-522Dynamic sandbox

The shipped service worker captures the outgoing header array from X.com API requests and stores it as twitter_token locally.

What actually happens

You did this

You browse X.com while signed in.

The listener is scoped to X.com API URLs under /i/api/.

The extension did this

The extension saves the outgoing request headers and later uses them in its own X.com API request.

Those headers can include authorization, CSRF, cookie, and other session-related fields sent by the browser.

Header fields stored under twitter_token

  • Authorization header
    Authorization: Bearer <redacted> (illustrative)

    This value can make an API request act like it came from your signed-in X.com session.

  • CSRF token
    x-csrf-token: 8f7b3c2e91a046d4b5c6a7e8f9012345 (illustrative)

    This token is part of the browser session context that X.com expects on authenticated requests.

  • X.com cookies
    auth_token=<redacted>; ct0=<redacted> (illustrative)

    Cookies can identify the signed-in browser session used for the API request.

  • Other request headers
    x-twitter-active-user: yes (illustrative)

    The stored array preserves the rest of the browser request context that was sent with the X.com API call.

What's stored on your device

The extension keeps a reusable copy of session headers in its own storage, not just letting the browser send them with the original request.

Location
chrome.storage.local key 'twitter_token'
Contents
{  "twitter_token": [    {      "name": "Authorization",      "value": "Bearer <redacted>"    },    {      "name": "x-csrf-token",      "value": "8f7b3c2e91a046d4b5c6a7e8f9012345"    },    {      "name": "Cookie",      "value": "auth_token=<redacted>; ct0=<redacted>"    }  ]}

Captured request

GEThttps://api.x.com/2/timeline/conversation/${t}.json?include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&skip_status=1&cards_platform=Web-12&include_cards=1&include_composer_source=true&include_ext_alt_text=true&include_reply_count=1&tweet_mode=extended&include_entities=true&include_user_entities=true&include_ext_media_color=true&include_ext_media_availability=true&send_error_codes=true&simple_quoted_tweets=true&count=20&ext=mediaStats%252ChighlightedLabel%252CcameraMoment
Headers
Cookie
auth_token=<redacted>; ct0=<redacted>
x-csrf-token
<redacted>
Authorization
Bearer <redacted>

The code that does this

The worker stores X.com API headers and reuses them in a later fetch

Readable version

Readable header capture

deobfuscated/js/worker.js
chrome.webRequest.onBeforeSendHeaders.addListener((e => {  e.requestHeaders && chrome.storage.local.set({    twitter_token: e.requestHeaders  })}), {  urls: ["*://*.x.com/i/api/*"]}, ["requestHeaders"])

Readable header replay

deobfuscated/js/worker.js
async fetchDataFromTwitId(t, a, s) {  const o = `https://api.x.com/2/timeline/conversation/${t}.json?include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&skip_status=1&cards_platform=Web-12&include_cards=1&include_composer_source=true&include_ext_alt_text=true&include_reply_count=1&tweet_mode=extended&include_entities=true&include_user_entities=true&include_ext_media_color=true&include_ext_media_availability=true&send_error_codes=true&simple_quoted_tweets=true&count=20&ext=mediaStats%252ChighlightedLabel%252CcameraMoment`,    n = (await chrome.storage.local.get("twitter_token")).twitter_token.reduce(((e, {      name: t,      value: a    }) => ({      ...e,      [t]: a    })));  fetch(o, {    method: "GET",    headers: n  }).then((e => e.json())).then((s => {    if (s.errors) return;    const o = s.globalObjects.tweets[t].extended_entities.media[0].video_info.variants,      n = {        fileName: `Video by ${s.globalObjects.users[s.globalObjects.tweets[t].user_id_str].name} (${t})`,        url: o.filter((e => "video/mp4" === e.content_type))[0],        ext: ".mp4"      };    e.addVideoLinks([n], a.tab.id, a.tab.url)  }))}

Destination used by the replay function

    • api.x.com

    Receives the extension's conversation timeline GET request with the headers reconstructed from twitter_token.

Updated 30 September 2026jbmbplbpgcpooepakloahbjjcpfoegji