Is Pastel safe?

High risk

Pastel sends page cookies and Pastel HTTP credentials with new annotation payloads.

When a user creates feedback in a Pastel canvas, the injected feedback script includes the page URL, document cookies, captured page content, and any Pastel HTTP credentials in the new annotation payload sent to the Pastel app. While a Pastel canvas tab is registered, the extension also removes CSP, CORP, COEP, and X-Frame-Options headers from HTTPS subframes through a session rule that is not scoped to that tab.

haneefv1.0.7Chrome Web Store
74Risk
Who publishes it

Pastel Labs Inc - no other listings under this identity, 2 shared hostnames

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
haneef
Declared legal entity
Pastel Labs Inc
Registered address
21 Nelson St, Unit 329, Toronto, ON M5V 3H9, CA
Registered contact
Pastel

Shared hosts - 2 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.

renderer.gist.build
Also called by 3 other listings, including Cluing - AI Knowledge Base, Pin Toolbox - Pinterest Board Downloader
code.gist.build
Also called by 4 other listings, including Cluing - AI Knowledge Base, Pin Toolbox - Pinterest Board Downloader

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

Publishers can request a review.

Findings

Pastel feedback annotations include cookies and credentials

Creating feedback in a Pastel canvas builds an annotation from the page: URL, readable cookies, and any window._PastelHTTPCredentials value, sent to the parent app.

Can also include page HTML/canvas if told the extension isn't present.

Severity
High unwanted
Type
Unexpected
CWE
CWE-522
Source
Dynamic sandbox
What actually happens
You did this

You click to create feedback while a Pastel canvas is in feedback mode.

The click has to be a real page click; the shipped code checks for non-zero screen coordinates before creating the annotation.

The extension did this

The extension-created annotation includes the page URL, readable cookies, and Pastel HTTP credentials when present.

The feedback bundle sends that annotation to the parent Pastel app window as `annotation.new`.

Fields placed into the annotation object
  • Current page URL
    https://example.org/account/settings?tab=billing (illustrative)

    This shows exactly which embedded page you were viewing when you created the feedback item.

  • Readable page cookies
    session_id=abc123; workspace=team-42 (illustrative)

    Cookies can identify your browser session for the embedded site when those cookies are readable by page JavaScript.

  • Pastel HTTP credentials
    {"username":"u","password":"p"}

    Credentials can authenticate access to a protected site or resource used inside the Pastel canvas.

  • Page context
    <!DOCTYPE html><html><body><main>Review draft</main></body></html> (illustrative)

    The code can include page HTML and canvas captures in the annotation path when the app does not mark the extension as present.

Captured request
POSThttps://app.usepastel.com/link

No HTTP request body was recorded in the verification evidence; the observed handoff is an `annotation.new` postMessage to this Pastel app page context.

The code that does this

The injected feedback path builds and forwards the annotation

Readable version

Readable equivalent: service worker injection path

background.b78a8f7f.js
async function registerSubFrame(frame) {  if (!frame.url.startsWith("http://") && !frame.url.startsWith("https://")) {    return;  }  if (!this.mapOfPastelCanvasTabs[frame.tabId] || frame.parentFrameId !== 0) {    return;  }  if (    frame.parentFrameId === 0 && (      frame.url.startsWith("https://renderer.gist.build") ||      frame.url.startsWith("https://code.gist.build") ||      frame.url.startsWith("https://canny.io") ||      frame.url.startsWith("https://td.doubleclick.net") ||      frame.url.startsWith("https://js.stripe.com")    ) ||    frame.parentFrameId > 0 && !this.mapOfPastelCanvasFrames[frame.parentFrameId]  ) {    return;  }  const isPastelProxyUrl =    frame.url.includes("proxy.usepastel.com/_domains/") ||    frame.url.includes("proxy.usepastel.test/_domains/");  this.mapOfPastelCanvasFrames[frame.frameId] = {    tabId: frame.tabId,    frameId: frame.frameId,    parentFrameId: frame.parentFrameId,    url: frame.url  };  await chrome.scripting.executeScript({    target: { tabId: frame.tabId, frameIds: [frame.frameId] },    files: [canvasContentBundleFile],    injectImmediately: true  });  if (!isPastelProxyUrl) {    await chrome.scripting.executeScript({      target: { tabId: frame.tabId, frameIds: [frame.frameId] },      world: "MAIN",      files: [feedbackScriptBundleFile],      injectImmediately: true    });  }}

Readable equivalent: annotation payload fields

feedback-script-bundle.cc342ef1.js
function getAnnotationPayload() {  const location = getLocation();  return {    comment: this.data.comment,    targetSelector: this.targetSelector,    fallbackTargetSelector: this.fallbackTargetSelector,    targetOffsetX: this.offset.XPercentage,    targetOffsetY: this.offset.YPercentage,    path: this.path,    fullUrl: location.href,    userName: R.user ? R.user.name || R.user.username : this.data.name || "",    avatarUrl: R.user ? R.user.avatarUrl : null,    UserId: R.user ? R.user.id : null,    metadata: this.metadata,    html: this.pastelExtensionExists ? null : captureHtml(),    canvasContents: this.pastelExtensionExists      ? null      : Array.from(querySelectorAll("canvas"))          .map(function(canvas) {            try {              return {                selector: getUniqueSelector(t()(canvas)),                imageData: canvas.toDataURL()              };            } catch (error) {              return false;            }          })          .filter(Boolean),    cookies: document.cookie,    recordedCSSChanges: this.recordedCSSChanges,    credentials: window._PastelHTTPCredentials ? window._PastelHTTPCredentials : null  };}

Readable equivalent: click-to-annotation path

feedback-script-bundle.cc342ef1.js
function start() {  const app = this;  app.mode = "view";  app.deviceType = "desktop";  function isRealClick(event) {    return event.screenX && event.screenX !== 0 && event.screenY && event.screenY !== 0;  }  function clickInterceptor(event) {    const target = t()(event.target || event.srcElement);    if (app.mode === "view") {      return;    }    if (app.isReadOnly) {      l.send("annotation.deselected", {});      return;    }    if (app.newAnnotation && app.newAnnotation.isEditable) {      const isEditableTarget =        target.hasClass("pastel-editable-element") ||        target.closest(".pastel-editable-element").length > 0;      if (!isEditableTarget || event.type !== "click") {        event.preventDefault();        event.stopImmediatePropagation();        event.stopPropagation();      }      return;    }    const selectableTarget =      target.hasClass("pastel-selectable-element") ||      target.closest(".pastel-selectable-element").length > 0;    if (selectableTarget) {      if (event.type === "click") {        event.preventDefault();        event.stopImmediatePropagation();        event.stopPropagation();      }      return;    }    if (!isRealClick(event)) {      return;    }    event.preventDefault();    event.stopImmediatePropagation();    event.stopPropagation();    if (event.type !== "click") {      return;    }    const canCreateAnnotation = !app.closeAnnotations();    l.send("annotation.deselected", {});    if (!app.user && canCreateAnnotation) {      l.send("annotation.create.with.no.user");      return;    }    if (canCreateAnnotation) {      if (app.pastelExtensionExists) {        window.postMessage({ type: "screenshot.trigger", isPastelMessage: true, data: null }, "*");      }      app.newAnnotation = new L(target, event, app.deviceType, app.recordedCSSChanges, app.pastelExtensionExists);      app.newAnnotation.render();      l.send("annotation.new", app.newAnnotation.get());    }  }  document.addEventListener("click", clickInterceptor, true);  document.addEventListener("mousedown", clickInterceptor, true);  document.addEventListener("mouseup", clickInterceptor, true);  document.addEventListener("mouseover", function(event) {    let target = t()(event.target || event.srcElement);    if (app.mode !== "view" && !app.isReadOnly && !(app.newAnnotation && app.newAnnotation.isEditable)) {      const svg = target.closest("svg");      if (svg.length > 0) {        target = svg;      }      t()(".pastel-hover").removeClass("pastel-hover");      if (!target.hasClass("pastel-selectable-element") && target.closest(".pastel-selectable-element").length === 0) {        target.addClass("pastel-hover");      }    }    if (app.newAnnotation) {      event.preventDefault();      event.stopImmediatePropagation();      event.stopPropagation();    }  }, true);  document.addEventListener("mouseout", function(event) {    const relatedTarget = event.relatedTarget || event.toElement;    if (!relatedTarget || relatedTarget.nodeName === "HTML") {      t()(".pastel-hover").removeClass("pastel-hover");    }    if (app.newAnnotation) {      event.preventDefault();      event.stopImmediatePropagation();      event.stopPropagation();    }  }, true);  if (window.PointerEvent) {    document.addEventListener("pointerleave", stopWhenNewAnnotationExists, true);    document.addEventListener("pointerup", stopWhenNewAnnotationExists, true);    document.addEventListener("pointerout", stopWhenNewAnnotationExists, true);  }  window.addEventListener("resize", stopWhenNewAnnotationExists);  function stopWhenNewAnnotationExists(event) {    if (app.newAnnotation) {      event.preventDefault();      event.stopImmediatePropagation();      event.stopPropagation();    }  }  app.watchDOMChanges();  app.watchCSSChanges();  app.watchAnnotationElementPositions();  app.watchKeyEvents();  app.watchNewWindowOpens();  t()(function() {    t()("body").addClass("pastel-body");    if (/iPad|iPhone|iPod/.test(navigator.userAgent) && window.MSStream === undefined) {      t()("html, body").addClass("pastel-ios");    }    app.connectToParentWindow();  });}

Readable equivalent: message transport

feedback-script-bundle.cc342ef1.js
function send(type, data = null) {  if (this.parentWindow !== window) {    this.parentWindow.postMessage(new Message(type, data), "*");  }}
Recipient host named by the claim and code path
    • app.usepastel.com

    Pastel web app page that receives the `annotation.new` message from the injected feedback script in the embedded page frame.

    • usepastel.com

    Pastel canvas origin registered by the service worker before it injects the feedback bundle into matching canvas subframes.

What it can do

Permissions this extension asks for, as declared in version 1.0.7. 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>

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

    tabs

  • See every page you navigate to, as you navigate to it

    webNavigation

  • Run its own code inside the pages you visit

    scripting

declarativeNetRequestWithHostAccess

Where it sends data

Destinations our analysis observed Pastel contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • app.usepastel.com

    Pastel sends data to app.usepastel.com. No other extension we have analysed sends data here.

  • usepastel.com

    Pastel sends data to usepastel.com. No other extension we have analysed sends data here.

Updated 30 September 2026pogpefpnemelncamdkcigobcdilekfpk