Is Pastel safe?
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.
Who publishes itPastel Labs Inc - no other listings under this identity, 2 shared hostnames
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.
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.
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
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-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`.
- Current page URLhttps://example.org/account/settings?tab=billing (illustrative)
This shows exactly which embedded page you were viewing when you created the feedback item.
- Readable page cookiessession_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.
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 injected feedback path builds and forwards the annotation
Readable equivalent: service worker injection path
background.b78a8f7f.jsasync 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.jsfunction 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.jsfunction 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.jsfunction send(type, data = null) { if (this.parentWindow !== window) { this.parentWindow.postMessage(new Message(type, data), "*"); }}- 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
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.