Is Tabrr Dashboard - New Tab with AI safe?

Medium risk

Tabrr is medium risk. Submitting a prompt in Tabrr's chat widget forwards chat messages to the background script, which posts them to account.tabrr.com/api/chat with the model, history, stream flag, and auth token. No live request was captured; sign-in required.

Tabrrv1.1.8Chrome Web Store
48Risk

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

Publishers can request a review.

Findings

AI chat prompts sent to Tabrr account API

Submitting a prompt in Tabrr's chat widget forwards chat messages to the background script, which posts them to account.tabrr.com/api/chat with the model, history, stream flag, and auth token.

No live request was captured; sign-in required.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You submit a question in the extension's AI chat widget.

The code path is gated by the chat interface and was not reached when the widget showed a sign-in prompt during testing.

The extension did this

The extension posts the chat payload to the Tabrr account chat API.

The background script includes your chat messages and the stored Tabrr account token in the request.

Fields prepared for the chat API request
  • Your chat prompt
    Please summarize this article for me. (illustrative)

    The text you type into the AI chat widget is included in the message list sent to the chat API.

  • Recent chat messages
    [{"role":"user","content":"Please summarize this article for me."}] (illustrative)

    The extension sends up to the last five non-loader chat messages, so earlier turns in the same chat can travel with your latest prompt.

  • Selected model
    gpt-4o-mini (illustrative)

    The request includes the AI model selected in the extension's chat settings.

  • Streaming choice
    true

    The request says whether the answer should stream back gradually or arrive as one response.

  • Tabrr account token
    Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.redacted.redacted (illustrative)

    The request carries a Tabrr account authorization token, linking the chat request to the signed-in account.

Captured request
POSThttps://account.tabrr.com/api/chat

No response was captured; the chat widget required a signed-in Tabrr account during testing.

Headers
Content-Type
application/json
Authorization
Bearer redacted-tabrr-account-token (illustrative)
The code that does this

The UI hands chat messages to the background script, which posts them to Tabrr

Readable version

New-tab chat submit and port handoff

deobfuscated/newtab.js
generateAnswer: function(t) {  var e = this;  if (!this.port) {    this.port = chrome.runtime.connect(chrome.runtime.id, {      name: "chat-ai"    });    var n = function(t) {      if ("chat-ai" === t.name && t.message) {        e.removeLoader();        t.done && (e.answering = !1);        var n, i = e.messages.findIndex((function(e) {          return e.id === t.id        }));        -1 === i ? e.messages.push(rr({          id: t.id        }, t.message)) : null != t && null !== (n = t.message) && void 0 !== n && n.content && (e.messages[i].content += t.message.content);        e.autoScroll && e.scrollToBottom()      }    };    this.port.onMessage.addListener(n);    this.port.onDisconnect.addListener((function() {      e.port.onMessage.removeListener(n);      e.port = null    }))  }  this.port.postMessage({    question: this.messages.filter((function(t) {      return "loader" !== t.role    })).slice(-5).map((function(t) {      return {        role: t.role,        content: t.content      }    }))  })},stopGeneration: function() {  this.port && (this.port.postMessage({    action: "stop"  }), this.removeLoader())},onSubmit: function(t) {  var e = this;  return mn()(vn.a.mark((function n() {    var i;    return vn.a.wrap((function(n) {      for (;;) switch (n.prev = n.next) {        case 0:          if (t && t.preventDefault(), e.autoScroll = !0, e.question && !e.answering && !e.error) {            n.next = 5;            break          }          return n.abrupt("return");        case 5:          i = e.question, e.selectedText && (i = e.question + " - " + e.selectedText, e.selectedText = ""), e.messages.push({            content: i,            id: Object(Ue.b)(),            role: "user"          }), e.messages.push({            content: "",            role: "loader"          }), e.answering = !0, e.generateAnswer(i), e.question = "", e.scrollToBottom();        case 14:        case "end":          return n.stop()      }    }), n)  })))()}

Background chat handler and API request builders

deobfuscated/background.js
function t() {  var e = this;  o()(this, t), chrome.runtime.onConnect.addListener((function(t) {    "chat-ai" === t.name && t.onMessage.addListener(function() {      var n = l()(p.a.mark((function n(r) {        var o;        return p.a.wrap((function(n) {          for (;;) switch (n.prev = n.next) {            case 0:              if ("stop" !== r.action) {                n.next = 4;                break              }              return e.stop(), n.abrupt("return");            case 4:              return n.prev = 4, n.next = 7, e.generateAnswer(r.question, t);            case 7:              n.next = 15;              break;            case 9:              if (n.prev = 9, n.t0 = n.catch(4), 20 === (o = n.t0).code) o = {                code: "user_abort",                message: o.message              };              else try {                o = JSON.parse(o)              } catch (t) {}              o && o.message && "user_abort" !== o.code && (o = o.message), t.postMessage({                error: o || !0,                name: "chat-ai"              });            case 15:            case "end":              return n.stop()          }        }), n, null, [          [4, 9]        ])      })));      return function(t) {        return n.apply(this, arguments)      }    }())  }))}var e;return a()(t, [{  key: "generateAnswer",  value: function(t, e) {    s.a.state.storage.user.aiChatConfig.stream ? this.stream(t, e) : this.noStream(t, e)  }}, {  key: "stream",  value: function(t, e) {    var n;    m = new AbortController, n = "string" == typeof t ? [{      role: "user",      content: t    }] : t, this.fetchStream("https://account.tabrr.com/api/chat", {      method: "POST",      signal: m.signal,      headers: {        "Content-Type": "application/json",        Authorization: "Bearer ".concat(s.a.state.storage.user.token)      },      body: JSON.stringify({        model: s.a.state.storage.user.aiChatConfig.model,        messages: n,        stream: !0      }),      id: Object(h.b)(),      onEvent: function(t) {        null == e || e.postMessage(g(g({}, t), {}, {          name: "chat-ai"        }))      }    })  }}, {  key: "noStream",  value: function(t, e) {    var n;    m = new AbortController, n = "string" == typeof t ? [{      role: "user",      content: t    }] : t, fetch("https://account.tabrr.com/api/chat", {      method: "POST",      signal: m.signal,      headers: {        "Content-Type": "application/json",        Authorization: "Bearer ".concat(s.a.state.storage.user.token)      },      body: JSON.stringify({        model: s.a.state.storage.user.aiChatConfig.model,        messages: n,        stream: !1      })    }).then((function(t) {      return t.json()    })).then((function(t) {      var n = Object(h.b)();      null == e || e.postMessage(g(g({}, t), {}, {        id: n,        error: !1,        name: "chat-ai"      }))    })).catch((function(t) {      null == e || e.postMessage(g(g({}, t), {}, {        error: !0,        name: "chat-ai"      }))    }))  }}, {  key: "stop",  value: function() {    m.abort()  }}]), t
Remote service that receives the chat request
    • account.tabrr.com

    Tabrr account API endpoint that receives the AI chat request payload and authorization header.

What it can do

Permissions this extension asks for, as declared in version 1.1.8. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on every site you visit

    <all_urls>

  • Store data in your browser

    storage

  • See the address and title of every tab you have open

    tabs

  • Run its own code inside the pages you visit

    scripting

  • Store an unlimited amount of data in your browser

    unlimitedStorage

  • Schedule its own background tasks

    alarms

  • Run searches with your default search engine

    search