Is Voila – AI Assistant, Copilot and AI Writer safe?
Voila is high risk. Code analysis: in Outlook web mail, Voila reads the open message's body, subject, sender, recipients, cc, and bcc, turns them into reply context, and posts it to https://chat.getvoila.ai/api/v1/prompts/chat.…
Who publishes itInitial condition s.r.o. - 2 other listings from the same operator, none carrying a finding
Initial condition s.r.o. - 2 other listings from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
2 other listings published from this account, 310k+ users between them, none of them carrying a finding.
Shared hosts - 12 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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Outlook email content sent to Voila chat API
Code analysis: in Outlook web mail, Voila reads the open message's body, subject, sender, recipients, cc, and bcc, turns them into reply context, and posts it to https://chat.getvoila.ai/api/v1/prompts/chat.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You use Voila while viewing or replying to an Outlook web mail message.
The Outlook integration inserts a Respond with Voila button next to the compose send button.
The extension reads Outlook message fields and sends them as chat context to Voila.
The context string includes who the message is from, who you are replying to, and the message text.
- Message bodyMaya Patel wrote: Please send the revised partner terms before the finance review on Thursday.
This can expose the text of the Outlook conversation you are asking Voila to answer.
- SubjectPartner renewal terms
This reveals what the email conversation is about even without the full body.
- SenderMaya Patel <maya.patel@example.com>
This identifies the person or mailbox that sent the message.
- RecipientsJordan Lee <jordan.lee@example.com>
This identifies who is included in the conversation.
- Cc and bccCc: legal@example.com; Bcc: finance@example.com
This can expose additional participants, including recipients not visible in the main To line.
Earlier dynamic analysis did not exercise the Outlook-specific path, so no request body or response body was recorded for this mail flow.
- Content-Type
- application/json
Outlook reads message fields and sends them into the Voila sidebar flow
Outlook DOM extraction and reply context
js/outlook-extension.jsfunction o() { var t, e, n, r, o, i, c, a, u, f, s, d, l, v; return { message: (null === (t = document.querySelector('[aria-label="Message body"]')) || void 0 === t || null === (e = t.textContent) || void 0 === e ? void 0 : e.trim()) || (null === (n = document.getElementById("docking_InitVisiblePart_0")) || void 0 === n || null === (r = n.querySelector("#docking_DockingTriggerPart_0")) || void 0 === r || null === (o = r.textContent) || void 0 === o ? void 0 : o.trim()), subject: null === (i = document.querySelector('[data-app-section="MailReadCompose"] [role="heading"]')) || void 0 === i ? void 0 : i.textContent, from: null === (c = document.querySelector('[id^="fromContainer"]')) || void 0 === c || null === (a = c.parentElement) || void 0 === a || null === (u = a.querySelector("span")) || void 0 === u ? void 0 : u.textContent, to: (null === (f = document.querySelector(".EditorClass")) || void 0 === f ? void 0 : f.textContent) || (null === (s = document.getElementById("docking_InitVisiblePart_0")) || void 0 === s || null === (d = s.querySelector("ul li span")) || void 0 === d ? void 0 : d.textContent), cc: null === (l = document.querySelector('[aria-label="Cc"]')) || void 0 === l ? void 0 : l.textContent, bcc: null === (v = document.querySelector('[aria-label="Bcc"]')) || void 0 === v ? void 0 : v.textContent }}function i() { var t = o(), e = void 0 === t.message; if (e) window.postMessage({ type: "voila-message", context: "", email: { draft: !0 } }); else { var n = "I'm ".concat(t.from, ". I'm replying to " ).concat(t.to, ". " ).concat(t.to, " wrote: " ).concat(t.message); window.postMessage({ type: "voila-message", context: n, email: { to: t.to, subject: t.subject, draft: !1 } }) }}Sidebar POST to Voila chat API
js/standalone.jsne = { chat: n.chat, email: null === Z || void 0 === Z ? void 0 : Z.email, model: null === (c = n.selectedAiModel) || void 0 === c ? void 0 : c.id, auth_token: null === Z || void 0 === Z ? void 0 : Z.auth_token, client_date: (new Date).toString(), language: null === (u = n.selectedLanguage) || void 0 === u ? void 0 : u.code, intent: null === (h = n.intent) || void 0 === h ? void 0 : h.intent, temperature: null === (p = n.metadata) || void 0 === p || null === (f = p.action) || void 0 === f ? void 0 : f.temperature, tone: null === (g = n.tone) || void 0 === g ? void 0 : g.value, context: (n.context || "").trim(), attachments: Y, persona: N, conversation: n.conversation, user_uuid: ee, version: null === te || void 0 === te ? void 0 : te.version}, n.metadata = {}, oe = "", Object(l["a"])(x["b"] + "/api/v1/prompts/chat", { method: "POST", body: JSON.stringify(ne), signal: n.abortController.signal, openWhenHidden: !0, priority: "high", headers: { "Content-Type": "application/json" }})- chat.getvoila.ai
Receives the Voila chat request containing Outlook message context.
Gmail email content sent to Voila chat API
When Voila runs inside Gmail, it reads the email's body, subject, sender, and recipient.
The sidebar builds a chat request with that data, the account email, an auth token, and a user UUID, posted to chat.getvoila.ai/api/v1/prompts/chat.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You use Voila while viewing or replying to a Gmail message.
The Gmail integration adds a reply control and wires the current message into that action.
The extension reads the email and sends it as chat context to Voila.
The request also includes Voila account identifiers from extension storage.
- Email bodyPrevious email chain: From: Alice Morgan <alice@example.com> Date: Mon, 13 Jul 2026 09:12:00 -0700 To: Jordan Lee <jordan@example.com> Subject: Renewal terms Please review the renewal language before Friday.
This can expose the text of the Gmail conversation you are asking Voila to answer.
- Email metadataTo: Alice Morgan; Subject: Renewal terms; draft: false
This identifies who is involved in the conversation and what the message is about.
- Voila account emailjordan@example.com
This links the Gmail context to the Voila account using the extension.
- Auth tokenauth_token=<redacted>
This is account authorization data sent in the same JSON request as the email context.
- User UUID4d1d8e62-26d4-4b73-9cf1-55f5b2a76c37
This gives the service a stable identifier for the browser profile using the extension.
Earlier dynamic analysis did not exercise the Gmail-specific path, so no request body or response body was recorded for this mail flow.
- Content-Type
- application/json
Gmail reads the open message and posts it into the Voila sidebar flow
Gmail message extraction and sidebar handoff
js/gmail-extension.jsfunction S(t, e) { var n = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : null, r = { type: "voila-message", context: t, email: e }; n && (r.prompt = n), window.postMessage(r)}function P(t, e, n) { r = t; var o = "", i = {}; if ("compose" === e) i = { draft: !0 }; else { var c, a, u, f = n.new.get.email_id(), s = n.new.get.email_data(f); i = { to: (null === s || void 0 === s || null === (c = s.from) || void 0 === c ? void 0 : c.name) || (null === s || void 0 === s || null === (a = s.from) || void 0 === a ? void 0 : a.address), subject: null === s || void 0 === s ? void 0 : s.subject, draft: 0 === ((null === (u = t.recipients()) || void 0 === u ? void 0 : u.to) || []).length }, o = I(t, s) } S(o, i)}function w(t) { try { if (!o) return; var e = document.querySelectorAll(".gA.gt"), n = e[e.length - 1]; if (!n) return; var i = n.querySelector(".ams.bkI, .ams.bkH"); i || (i = document.querySelectorAll('[aria-label="Reply"]')[0]), i || (i = n.querySelectorAll("button[aria-label]")[0]), i || (i = n.querySelectorAll(".amn button")[0]), i && i.click(), S(o.context, o.email, t), setTimeout((function() { r = window.gmail.dom.composes()[0], m() }), 100) } catch (c) {}}Sidebar POST to Voila chat API
js/standalone.jsQ = o.sent, Z = Q.user, ee = Q.uuid, te = e.runtime.getManifest(), ne = { chat: n.chat, email: null === Z || void 0 === Z ? void 0 : Z.email, model: null === (c = n.selectedAiModel) || void 0 === c ? void 0 : c.id, auth_token: null === Z || void 0 === Z ? void 0 : Z.auth_token, client_date: (new Date).toString(), language: null === (u = n.selectedLanguage) || void 0 === u ? void 0 : u.code, intent: null === (h = n.intent) || void 0 === h ? void 0 : h.intent, temperature: null === (p = n.metadata) || void 0 === p || null === (f = p.action) || void 0 === f ? void 0 : f.temperature, tone: null === (g = n.tone) || void 0 === g ? void 0 : g.value, context: (n.context || "").trim(), attachments: Y, persona: N, conversation: n.conversation, user_uuid: ee, version: null === te || void 0 === te ? void 0 : te.version}, n.metadata = {}, oe = "", Object(l["a"])(x["b"] + "/api/v1/prompts/chat", { method: "POST", body: JSON.stringify(ne), signal: n.abortController.signal, openWhenHidden: !0, priority: "high", headers: { "Content-Type": "application/json" }})- chat.getvoila.ai
Receives the Voila chat request containing the Gmail context and account identifiers.
Page text sent to Voila when context is enabled
With "Use current page as context" on, the extension extracts the page's text and adds it to the chat request sent to chat.getvoila.ai.
Code review confirmed this; traffic replay didn't trigger the feature, so no body was captured.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You enable Use current page as context for a prompt.
The button appears in the Voila prompt footer when page context is available.
The extension reads the active page and adds the extracted text to the chat request.
The implementation calls the page-text extractor and later serializes the context field into the POST body.
- Readable page textExample Article Title This article explains the quarterly rollout plan and internal milestones.
The main text from the page you are viewing can be included with your prompt.
- Page contexthttps://intranet.example.com/quarterly-rollout-plan
The context ties your prompt to the page where you used the feature.
- Voila accountemployee@example.com
The chat request can include account data that links the prompt to your Voila session.
- Content-Type
- application/json
The page-context toggle reads the document, and the chat POST includes context
Page-context toggle calls the extractor
deobfuscated/js/standalone.jstogglePageAsContext: function() { var e = this; return Object(s["a"])(regeneratorRuntime.mark((function t() { return regeneratorRuntime.wrap((function(t) { while (1) switch (t.prev = t.next) { case 0: if (e.currentPageAsContext = !e.currentPageAsContext, !e.currentPageAsContext) { t.next = 9; break } return t.t0 = e, t.next = 5, Object(T["a"])(document, e.prompt); case 5: t.t1 = t.sent, t.t0.$emit.call(t.t0, "onContextChange", t.t1), t.next = 10; break; case 9: e.$emit("onContextChange", ""); case 10: e.focusElement("prompt"); case 11: case "end": return t.stop() } }), t) })))()}Chat request body includes the context property
deobfuscated/js/standalone.jsne = { chat: n.chat, email: null === Z || void 0 === Z ? void 0 : Z.email, model: null === (c = n.selectedAiModel) || void 0 === c ? void 0 : c.id, auth_token: null === Z || void 0 === Z ? void 0 : Z.auth_token, client_date: (new Date).toString(), language: null === (u = n.selectedLanguage) || void 0 === u ? void 0 : u.code, intent: null === (h = n.intent) || void 0 === h ? void 0 : h.intent, temperature: null === (p = n.metadata) || void 0 === p || null === (f = p.action) || void 0 === f ? void 0 : f.temperature, tone: null === (g = n.tone) || void 0 === g ? void 0 : g.value, context: (n.context || "").trim(), attachments: Y, persona: N, conversation: n.conversation, user_uuid: ee, version: null === te || void 0 === te ? void 0 : te.version}, n.metadata = {}, oe = "", Object(l["a"])(x["b"] + "/api/v1/prompts/chat", { method: "POST", body: JSON.stringify(ne), signal: n.abortController.signal, openWhenHidden: !0, priority: "high", headers: { "Content-Type": "application/json" }})Extractor removes scripts/styles and chooses readable text
deobfuscated/js/chunk-common.jsfunction a(t, e) { if (null !== e && void 0 !== e && e.cssSelector && "" !== (null === e || void 0 === e ? void 0 : e.cssSelector)) return null === t || void 0 === t ? void 0 : t.querySelectorAll(null === e || void 0 === e ? void 0 : e.cssSelector); window.location.hostname; var n = window.location.href; return n.startsWith("https://app.frontapp.com/inboxes") ? null === t || void 0 === t ? void 0 : t.querySelector('[data-testid="conversation-timeline"]') : void 0}function u(t) { return t instanceof NodeList ? Array.from(t).map(u).join("------") : (null === t || void 0 === t ? void 0 : t.innerText) || (null === t || void 0 === t ? void 0 : t.textContent)}function c(t) { try { if (Object(o["isProbablyReaderable"])(t, { minScore: 70 })) { var e = new o["Readability"](t).parse(); return e.textContent } } catch (n) {}}function s(t, e, n) { return l.apply(this, arguments)}function l() { return l = Object(r["a"])(regeneratorRuntime.mark((function t(e, n, r) { var o, s, l, p, d; return regeneratorRuntime.wrap((function(t) { while (1) switch (t.prev = t.next) { case 0: return o = e.cloneNode(!0), o.querySelectorAll("style, script").forEach((function(t) { return t.remove() })), s = u(a(o, r)), l = c(o), p = u(null === o || void 0 === o ? void 0 : o.querySelector("main")), d = u(null === o || void 0 === o ? void 0 : o.querySelector("body")), t.abrupt("return", Object(i["a"])(s || l || p || d)); case 7: case "end": return t.stop() } }), t) }))), l.apply(this, arguments)}- chat.getvoila.ai
Receives the chat request containing the context field.
- www.getvoila.ai
Configured as the main Voila API host in the bundled API constants.
+1 more finding not shown