Is Guidde - Magically create video documentation safe?

High risk

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.…

Guidde Chrome Storev88.0.0Chrome Web Store
75Risk
Who publishes it

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.

Publisher
Guidde Chrome Store
Declared legal entity
Guidde Inc
Registered address
2031 Lyon Ave, Belmont, CA 94002-1636, US
Registered contact
Yoav

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.

api.stigg.io
Also called by 4 other listings, including Wordtune, Wordtune: AI Writing, Paraphrasing & Grammar Tool

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

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

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.

The extension did this

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.

02EvidenceNETWORK CAPTURE
Captured request
POSThttps://app.guidde.com/tk/v1/track
Observed during dynamic analysis; the preserved evidence identified request-body keys but did not preserve a full body.
03EvidenceFIELD TABLE
Fields the analytics helper places in the tracking request
FieldValueWhy it matters
User ID
user-id: 8f4c9e31-7b6a-4f4d-9c1d-0c2a7d6e5a19Links the tracked event to your signed-in Guidde account.
Email address
email: alex.rivera@example.comConnects the event to an address that can identify you directly.
Name
name: Alex RiveraAdds your display name to the analytics record when the account has one.
Organization ID
organization-id: org_6b2c91f4Associates the event with your workspace or organization inside Guidde.
Page URL
https://app.guidde.com/signup?src=extensionShows which page or Guidde screen was associated with the event.
Extension metadata
ext-version: 81.0.0Adds the extension ID, version, browser, and environment to help group your event.
04EvidenceCODE COMPARE
The code that does this

The tracking helper builds the analytics payload and posts it to Guidde

What it actually does
Readable analytics properties and tracking requestbackground.js
const 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)
  }
05EvidenceTHIRD PARTY LIST
Destination receiving these events
  • app.guidde.com

    Guidde backend endpoint that receives /tk/v1/track analytics posts from the extension.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-200
SourceAI SANDBOX

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.

01EvidenceCAUSE EFFECT
What actually happens
You did this

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 did this

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.

02EvidenceFIELD TABLE
Fields collected for a recorded step
FieldValueWhy it matters
Page URL
https://app.guidde.com/welcomeShows the page where your recorded step happened.
Tab title
Welcome to GuiddeAdds the visible page title to the recorded step.
DOM node details
tagName: INPUT, type: text, name: emailDescribes the clicked or typed-into page element, which can reveal form labels or element identifiers.
Window dimensions
innerWidth: 1440, innerHeight: 900Adds screen context that helps reconstruct where the step occurred on your page.
Screenshot reference
quickguiddeScreenshots/uid_8f4c9e31/playbook_20260712/playbook_20260712_4d9f0c2a.pngLinks the step to a captured image of the visible tab during recording.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://app.guidde.com/c/v1/ext/quickguidde/addStep
The source builds this POST during recording; the preserved traffic evidence did not capture a full request body for this path.
04EvidenceCODE COMPARE
The code that does this

The content script turns page interactions into recording events

What it actually does
Readable event collection and NEW_EVENT postingiframeCS.js
const 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 eventsiframeCS.js
const 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()))
    }
  }
05EvidenceCODE COMPARE
The code that does this

The background script captures screenshots and posts addStep

What it actually does
Readable upload helpersbackground.js
const 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 methodsbackground.js
async 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 methodbackground.js
captureNow({
  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)
    })
  })
}
06EvidenceTHIRD PARTY LIST
Destinations used by the recording flow
  • 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.

Updated 30 September 2026oacmmmjedhheaijfjidilonpngccnhdl