Is Tabrr Dashboard - New Tab with AI safe?
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.
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
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 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.
- Your chat promptPlease 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 modelgpt-4o-mini (illustrative)
The request includes the AI model selected in the extension's chat settings.
- Streaming choicetrue
The request says whether the answer should stream back gradually or arrive as one response.
- Tabrr account tokenBearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.redacted.redacted (illustrative)
The request carries a Tabrr account authorization token, linking the chat request to the signed-in account.
No response was captured; the chat widget required a signed-in Tabrr account during testing.
- Content-Type
- application/json
- Authorization
- Bearer redacted-tabrr-account-token (illustrative)
The UI hands chat messages to the background script, which posts them to Tabrr
New-tab chat submit and port handoff
deobfuscated/newtab.jsgenerateAnswer: 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.jsfunction 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- 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