Is Guidde - Magically create video documentation safe?
Guidde is high risk. Guidde posts analytics events to app.guidde.com/tk/v1/track for extension and web-app actions, with account properties (user ID, email, name, org ID) plus event data that can include the current page URL.…
Who publishes itGuidde Inc - no other listings under this identity, 1 shared hostname
Guidde Inc - no other listings under this identity, 1 shared hostname
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 1 hostname
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
Guidde posts tracked user events and page URLs to its backend
Guidde posts analytics events to app.guidde.com/tk/v1/track for extension and web-app actions, with account properties (user ID, email, name, org ID) plus event data that can include the current page URL.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You use a Guidde extension feature or trigger an extension lifecycle event.
Examples in the source include opening recording UI, welcome-page actions, upload failures, and step-limit events.
Guidde sends an analytics event that can combine your account identity with page context.
The request goes to Guidde's own tracking endpoint under app.guidde.com.
Observed during dynamic analysis; the preserved evidence identified request-body keys but did not preserve a full body.
- User IDuser-id: 8f4c9e31-7b6a-4f4d-9c1d-0c2a7d6e5a19
Links the tracked event to your signed-in Guidde account.
- Email addressemail: alex.rivera@example.com
Connects the event to an address that can identify you directly.
- Namename: Alex Rivera
Adds your display name to the analytics record when the account has one.
- Organization IDorganization-id: org_6b2c91f4
Associates the event with your workspace or organization inside Guidde.
- Page URLhttps://app.guidde.com/signup?src=extension
Shows which page or Guidde screen was associated with the event.
- Extension metadataext-version: 81.0.0
Adds the extension ID, version, browser, and environment to help group your event.
The tracking helper builds the analytics payload and posts it to Guidde
Readable analytics properties and tracking request
background.jsconst aZ = () => ({ ...oZ(), ...sZ(), source: "chrome-extension", "ext-id": chrome.runtime.id, "ext-version": nZ(), environment: Li.firebase.projectId}),cZ = t => t ? { ...t.customProps || {}, "user-id": t.user.uid, email: t.user.email, name: t.user.displayName, "organization-id": t.roles?.o} : {},_g = async () => (await chrome?.storage?.local.get([oa]))[oa] || Li.firebase.authDomain,fa = async () => `https://${await _g()}`;let M0 = {}, L0 = {};const uZ = (t, e = {}) => ds("/tk/v1/track", "POST", { userProperties: M0, globalProperties: L0, eventData: { event: t, ...e, timeMs: new Date().getTime() } }), ln = (t, e = {}, n = null) => { const r = n ? { url: n.url } : {}, i = { ...e, ...r }; return uZ(t, i) }- app.guidde.com
Guidde backend endpoint that receives /tk/v1/track analytics posts from the extension.
Guidde recording uploads screenshots, URLs, and DOM events
Recording a Guidde flow makes the content script listen for interactions, build a step event with the page URL and DOM details, and ask the background to screenshot.
It uploads via a signed URL and posts the step to Guidde's addStep API.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You start a Guidde recording session on a page.
The recording path is tied to the quick-guide workflow rather than every browsing session.
The extension records step metadata, captures screenshots, and uploads the resulting step to Guidde.
The code path includes both a screenshot upload and an addStep API call.
- Page URLhttps://app.guidde.com/welcome
Shows the page where your recorded step happened.
- Tab titleWelcome to Guidde
Adds the visible page title to the recorded step.
- DOM node detailstagName: INPUT, type: text, name: email
Describes the clicked or typed-into page element, which can reveal form labels or element identifiers.
- Window dimensionsinnerWidth: 1440, innerHeight: 900
Adds screen context that helps reconstruct where the step occurred on your page.
- Screenshot referencequickguiddeScreenshots/uid_8f4c9e31/playbook_20260712/playbook_20260712_4d9f0c2a.png
Links the step to a captured image of the visible tab during recording.
The source builds this POST during recording; the preserved traffic evidence did not capture a full request body for this path.
The content script turns page interactions into recording events
Readable event collection and NEW_EVENT posting
iframeCS.jsconst nL = (t, e, n, r, i, s = []) => { let u; try { u = { innerHeight: top?.window?.innerHeight || window.innerHeight, innerWidth: top?.window.innerWidth || window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth } } catch { u = { innerHeight: window?.innerHeight, innerWidth: window?.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth } } let p = { x: 0, y: 0 }; r instanceof MouseEvent && (p = { x: r.clientX, y: r.clientY }); const g = (I, O) => { p.x = I.clientX + O.getBoundingClientRect()?.x, p.y = I.clientY + O.getBoundingClientRect()?.y }; let h = !1; if (self !== top) { const I = r?.view?.frameElement; if (I) h = !0, g(r, I); else { const O = document?.activeElement, x = r?.currentTarget?.defaultView?.frameElement; x ? (h = !0, ql = x, p.x = O?.getBoundingClientRect().x + x.getBoundingClientRect().x, p.y = O?.getBoundingClientRect().y + x.getBoundingClientRect().y) : ql && (h = !0, g(r, ql)) } } else h = !0; const b = s?.[0] instanceof Element ? s[0] : null, S = r?.target instanceof Element ? r.target : null; return { screenshot: null, id: S?.id, node: CI({ type: e, stepTextLimit: n, event: r, title: i, isFrameAccessible: h, clickedItem: S }), composedPathNode: CI({ type: e, stepTextLimit: n, event: r, title: i, isFrameAccessible: h, clickedItem: b }), devicePixelRatio: window.devicePixelRatio, url: window.location.href, timeStamp: t, type: e, windowDimensions: u, mousePosition: p }},Vl = ({ port: t, timeStamp: e, type: n, event: r, composedPath: i, title: s = ""}) => { jM(u => { const { useTabUrl: p, tabUrl: g } = u, h = SI(); t.postMessage({ action: "CAPTURE_NOW", uuid: h, url: p ? g : window.location.href, type: n }), t.postMessage({ action: "NEW_EVENT", uuid: h, url: p ? g : window.location.href, QGEvent: nL(e, n, u.stepTextLimit, r, s, i) }) })}Readable listeners for recording events
iframeCS.jsconst Cv = new lG("iframeCS"), dG = sC((t, e, n, r) => { Vl({ timeStamp: r, type: "input", port: t, event: e, composedPath: n }) }, 500), fG = 5e3, Lm = { timestamp: 0, target: null }, Vf = { mousedown: (t, e) => { e.composedPath().some(r => r instanceof HTMLElement ? r.id === F_ || r.id === Wl : !1) || (t.postMessage({ action: "COUNT_EVENT", type: "mousedown", url: window.location.href }), rL(Date.now(), t, e, e.composedPath(), !0)) }, click: t => { t.postMessage({ action: "COUNT_EVENT", type: "click", url: window.location.href }) }, keyup: (t, e) => { if (e.key === "Enter") { if (e.isComposing || e.target === Lm.target && Date.now() - Lm.timestamp < fG) return; Vl({ port: t, event: e, timeStamp: Date.now(), type: "freeText", title: "Go here", composedPath: e?.composedPath() }) } }, input: (t, e) => { e.composedPath().some(r => r instanceof HTMLElement ? r.id === F_ || r.id === Wl : !1) || (Lm.timestamp = Date.now(), Lm.target = e.target, dG(t, e, e.composedPath(), Date.now())) } }The background script captures screenshots and posts addStep
Readable upload helpers
background.jsconst Zme = (t, e) => fetch(t, { method: "PUT", body: typeof e == "string" ? Jme(e) : e}).then(n => { if (!n.ok) throw new Error(`Upload failed with status ${n.status}: ${n.statusText}`)}),ege = t => t.replace("storage.googleapis.com", `gstorage.${Li.firebase.authDomain}`),tge = (t, e = !0) => ds("/c/v1/get-upload-url", "POST", { filename: t}).then(n => ({ url: e ? ege(n.url) : n.url})),nge = (t, e) => new Promise((n, r) => { const i = "gs:///" + t; Ie.info("Getting signed URL", Oe.CreateQgFlow), tge(i).then(({ url: s }) => { Ie.info("Signed URL received, uploading file", Oe.CreateQgFlow), Zme(s, e).then(() => { Ie.info("File uploaded successfully", Oe.CreateQgFlow), n(i) }).catch(o => { Ie.error("File upload failed", Oe.CreateQgFlow, { error: o }), ln("file_upload_failed", { storagePath: t }), Bt(o), r(o) }) }).catch(s => { Bt(s), r(s) })}),rge = (t, e) => ds("/c/v1/ext/quickguidde/addStep", "POST", { playbookId: t, step: e}).then(n => (Ie.info("addStep POST request completed successfully", Oe.CreateQgFlow, { stepId: e.id}), n)).catch(n => { Ie.error("addStep POST request failed", Oe.CreateQgFlow, { stepId: e.id, error: n })})Readable addMetaData/addStep methods
background.jsasync addMetaData(e, n, r, i, s, o, c) { Ie.info("Adding metadata", Oe.CreateQgFlow, { stepWithoutScreenshot: e }); const [l, d] = await Promise.race([Promise.all([this.getStepElapsedTimeInRecording(e.timeStamp), this.getScreenDimensionFromTab(e.windowDimensions)]), new Promise(_ => setTimeout(() => _([0, null]), 2e3))]); d && (e.windowDimensions.innerHeight = d.innerHeight, e.windowDimensions.innerWidth = d.innerWidth), e.type === "input" ? (this.aggregatedStep && EG(e.node) !== EG(this.aggregatedStep.eventData.node) && this.processAggregatedStep(), this.aggregatedStep = { uuid: n, index: r, url: i, favicon: s, tabTitle: o, eventData: e, processed: !1, ...this.captureVideo ? { stepTimeInRecording: l, tabId: c } : {} }) : (this.processAggregatedStep(), this.data.addEventData({ uuid: n, index: r, url: i, favicon: s, tabTitle: o, eventData: e, processed: !1, ...this.captureVideo ? { stepTimeInRecording: l, tabId: c } : {} }), this.data.isEventReady(n) && this.addStep(this.data.getItem(n), n))}addStep(e, n) { if (Ie.info("Adding step", Oe.CreateQgFlow), !this.playbookId) throw new Error("Cannot add QG steps - video doesn't exist"); this.data.markItemAsProcessed(n); const r = { ...e.eventData, screenshot: e.imageUrl, id: e.uuid, favicon: e.favicon, documentTitle: e.tabTitle, stepIndex: this.steps.length, ...this.captureVideo ? { relativeRecordingTime: e.relativeRecordingTime, tabId: e.tabId } : {} }, i = rge(this.playbookId, r); this.steps.push(i), this.lastStepUrl = e.eventData.url}Readable screenshot capture method
background.jscaptureNow({ uuid: e, url: n, type: r, favicon: i, tabTitle: s}) { Ie.info("Requesting browser engine to capture image", Oe.CreateQgFlow, { eventType: r }); const o = this.imageFormat === "jpeg" || this.imageFormat === "png" ? this.imageFormat : "png", c = this.imageQuality >= 0 && this.imageQuality <= 100 ? this.imageQuality : 50; (this.imageFormat !== o || this.imageQuality !== c) && (Ie.warn("Invalid capture parameters detected and corrected", Oe.CreateQgFlow, { originalFormat: this.imageFormat, safeFormat: o, originalQuality: this.imageQuality, safeQuality: c }), Bt(new Error("Invalid captureVisibleTab parameters detected"))), this.data.addImageUrl(e, "loading", this.stepsIndex, n, i, s, !1, !1), chrome.tabs.captureVisibleTab({ format: o, quality: c }, l => { if (Ie.info("image captured", Oe.CreateQgFlow), chrome.runtime.lastError) { Ie.info("Runtime had error", Oe.CreateQgFlow, { error: chrome.runtime.lastError }), Bt(chrome.runtime.lastError), this.data.addImageUrl(e, null, this.stepsIndex, n, i, s, !0, !0), ln("step_dropped", { playbookType: "QG", uuid: e, url: n, playbookId: this.playbookId, lastError: JSON.stringify(chrome.runtime.lastError) }); return } r !== "input" && this.incrementRecPanelCounter(); const { uid: d } = Si()?.user, _ = `quickguiddeScreenshots/${d}/${this.playbookId}/${this.playbookId}_${e}.${o}`; Ie.info("Uploading image to storage", Oe.CreateQgFlow, { storageRef: _ }), nge(_, l).then(E => { this.data.addImageUrl(e, E, this.stepsIndex, n, i, s, !1, !1), setTimeout(() => { const S = this.data.getItem(e); Ie.info("Image uploaded. Attempting to add a step", Oe.CreateQgFlow), S && !S.processed && this.data.isEventReady(e) && this.addStep(S, e) }, 500) }).catch(E => { ln("screenshot_upload_failed", { playbookType: "QG", uuid: e, url: n, playbookId: this.playbookId, storagePath: _, error: E?.message || String(E) }), Bt(E, { context: "screenshot_upload_failed", uuid: e, playbookId: this.playbookId, storageRef: _ }), this.data.addImageUrl(e, null, this.stepsIndex, n, i, s, !0, !0) }) })}- app.guidde.com
Guidde API host that receives addStep and signed-upload-url requests.
- gstorage.app.guidde.com
Guidde storage hostname used for screenshot uploads through signed PUT URLs.