Is Site Palette safe?
Site Palette is high risk. Creating a palette captures the page as a stitched JPEG and posts it to api.palette.site/palettes with the page URL, a hostname title, color data, and your account Bearer token. A passive session didn't exercise this; no live body captured.…
Who publishes itSliday LTD - no other listings under this identity, 4 shared hostnames
Sliday LTD - no other listings under this identity, 4 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 4 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
Full-page screenshots uploaded when creating palettes
Creating a palette captures the page as a stitched JPEG and posts it to api.palette.site/palettes with the page URL, a hostname title, color data, and your account Bearer token.
A passive session didn't exercise this; no live body captured.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You create a color palette from the page you are viewing.
The create button starts page measurement, color extraction, and screenshot capture for the active tab.
The extension packages the page URL, a full-page JPEG screenshot, and color data for upload.
The upload path uses the stored Site Palette account token and posts to api.palette.site.
- Page URLhttps://intranet.example.com/projects/q3-roadmap?view=board (illustrative)
Shows which page you used to create the palette, including path and query details present in the address bar.
- Page titleintranet.example.com (illustrative)
Adds context about the captured page by deriving a title from the page hostname.
- Full-page imagedata:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD (illustrative)
Can include visible text, images, account names, dashboards, and other information shown on the page when the palette is created.
- Generated colors{"hex":"#1f6feb","name":"Dodger Blue"} (illustrative)
Records the colors extracted from the page and links the palette result to the captured page.
- Site Palette tokenAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 (illustrative)
Associates the upload with the signed-in Site Palette account used by the extension.
No live response was captured; passive dynamic analysis did not exercise the login and create-palette flow.
- Content-Type
- application/json
- Authorization
- Bearer value from chrome.storage.local['palette-token']
The shipped and readable code paths behind the upload
Popup create button starts capture for the active tab
popup.jsfunction v(t) { chrome.runtime.sendMessage({ command: "get-tab", tabId: t.id, tabUrl: t.url }), chrome.tabs.sendMessage(t.id, { msg: "getPageDetails" }, (function(t) { g.classList.contains("container_hide") || g.classList.add("container_hide"), f.classList.contains("container_hide") && f.classList.remove("container_hide") }))}a.addEventListener("click", (async function(t) { const e = await L; chrome.scripting.executeScript({ target: { tabId: e.id }, func: i.collectVisibleColors, args: [{ maxColors: 10, textWeight: .3, samples: 1600 }] }, (t => { if (t && t[0]) { const e = t[0].result; chrome.runtime.sendMessage({ command: "viewport-colors-analyzed", data: { palette: e.palette } }) } chrome.tabs.sendMessage(e.id, { command: "are_you_there_content_script?" }, (function(t) { "yes" !== (t = t || {}).status ? chrome.scripting.executeScript({ target: { tabId: e.id }, files: ["scroller.js", "getPalette.js", "sitePalette.js"] }, (() => { v(e) })) : "yes" === t.status && v(e), chrome.runtime.lastError && console.log("err", chrome.runtime.lastError) })) }))}))Service worker captures viewport JPEGs
background.jscapturePage: function(e, t) { var o = this; setTimeout((function() { chrome.tabs.captureVisibleTab(null, { format: "jpeg" }, (function(s) { if (void 0 !== s) if (t) { o.resetPage(); var a, r = o.getNextViewTabUrl(); chrome.tabs.onUpdated.addListener((function t(n, i) { n == a && "complete" == i.status && (chrome.tabs.onUpdated.removeListener(t), chrome.tabs.sendMessage(o.tabId, { msg: "setScreenshotUrl", viewTabUrl: r, dataURI: s, tabUrl: o.tabURL, position: e, size: o.size, colors: o.cssColors })) })), chrome.tabs.create({ url: r }, (function(e) { a = e.id })) } else chrome.tabs.sendMessage(o.tabId, { msg: "getCapturePage", dataURI: s, position: e, size: o.size }), o.scrollTo(e + o.scrollBy); else console.log("Error", s), chrome.tabs.sendMessage(o.tabId, { msg: "showError", error: "Error", originalParams: o.originalParams }) })) }), 550)}Canvas code emits the final JPEG data URL
getPalette.js(() => { let e = document.createElement("canvas"), a = e.getContext("2d"); chrome.runtime.onMessage.addListener(((i, t, o) => { if ("setScreenshotUrl" === i.msg)(n = new Image).onload = function() { a.drawImage(n, 0, i.position * window.devicePixelRatio), chrome.runtime.sendMessage({ msg: "screenshotCanvasReady", canvasURL: e.toDataURL("image/jpeg", .4), tabUrl: i.tabUrl, viewTabUrl: i.viewTabUrl, colors: i.colors }) }, n.src = i.dataURI; else if ("getCapturePage" === i.msg) { var n; (n = new Image).onload = function() { a.drawImage(n, 0, i.position * window.devicePixelRatio) }, n.src = i.dataURI } else "initializeCanvas" === i.msg ? (e.width = i.size.width * window.devicePixelRatio, e.height = i.size.height * window.devicePixelRatio) : "are_you_there_content_script?" === i.command && o({ status: "yes" }) }))})();Finish builds and posts the palette payload
sitePalette.jsbuildPostColors: function() { var e = { cube: [], thief: [], vibrant: [], css: [] }; for (var t in this.data) for (var n in this.data[t].colors) { var r = { hex: this.data[t].colors[n].hex, name: this.data[t].colors[n].name }; "vibrant" == t && (r.vibrant_name = this.data[t].colors[n].vibrant_text), e[t].push(r) } return e}finish: function() { var e = (new Date).getTime(), t = "Done in " + this.toSeconds(e - this.timestamp).toString() + " sec"; this.debug && $("#profiler-done").html(t).show(); var n = this, r = n.buildPostColors(), o = { title: new URL(n.url).hostname.replace(/^www\./, ""), url: n.url, image: n.imagesrc, colors: r }; document.getElementById("palette-title").textContent = `/ ${o.title}`, chrome.storage.local.get(["palette-token"]).then((e => { Be(`${n.host}/palettes`, { headers: { Authorization: "Bearer " + e["palette-token"], "Content-Type": "application/json" }, method: "POST", data: JSON.stringify(o) }).then((e => { e.data.message ? window.close() : (n.resultUrl = "https://palette.site/palette?id=" + e.data.palette._id, $(".js-get-share-link").removeClass("is-loading")) })).catch((e => { console.log("Something went wrong. Try again.") })) }))}- api.palette.site
Receives the palette POST containing the page URL, derived title, full-page JPEG data URL, generated colors, and Site Palette account authorization header.
Current tab URL sent when creating a palette
Clicking Site Palette's create button sends the active tab URL and hostname to api.palette.site/palettes.
Verification confirmed the popup-background-content-script path carrying the URL; no body recorded since creation wasn't triggered.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You click Site Palette's create button on the page you are viewing.
The popup only enables this flow for HTTP or HTTPS tabs.
The extension includes that page's URL and hostname in the palette record it sends to Palette's API.
The evidence traces the URL from the popup message through the background worker into the final POST body.
- Current page URLhttps://docs.example.com/design/colors?project=brand-refresh
This identifies the exact page open when you create the palette, including any path or query string in the address.
- Site hostnamedocs.example.com
This records which site the palette came from, even when the full page path is not shown elsewhere.
- Palette colors#1f2937, #f9fafb, #2563eb
This describes the colors the extension extracted from the page and links the palette to the page URL sent with it.
No response body was recorded in the stored evidence.
- Content-Type
- application/json
- Authorization
- Bearer <redacted>
The active tab URL is collected before the palette POST is built
popup.js sends command get-tab with tabUrl
popup.jsfunction v(t) { chrome.runtime.sendMessage({ command: "get-tab", tabId: t.id, tabUrl: t.url }), chrome.tabs.sendMessage(t.id, { msg: "getPageDetails" }, (function(t) { g.classList.contains("container_hide") || g.classList.add("container_hide"), f.classList.contains("container_hide") && f.classList.remove("container_hide") }))}a.addEventListener("click", (async function(t) { const e = await L; chrome.scripting.executeScript({ target: { tabId: e.id }, func: i.collectVisibleColors, args: [{ maxColors: 10, textWeight: .3, samples: 1600 }] }, (t => { if (t && t[0]) { const e = t[0].result; chrome.runtime.sendMessage({ command: "viewport-colors-analyzed", data: { palette: e.palette } }) } chrome.tabs.sendMessage(e.id, { command: "are_you_there_content_script?" }, (function(t) { "yes" !== (t = t || {}).status ? chrome.scripting.executeScript({ target: { tabId: e.id }, files: ["scroller.js", "getPalette.js", "sitePalette.js"] }, (() => { v(e) })) : "yes" === t.status && v(e), chrome.runtime.lastError && console.log("err", chrome.runtime.lastError) })) }))}))sitePalette.js finish builds and posts the palette object
sitePalette.jsfinish: function() { var e = (new Date).getTime(), t = "Done in " + this.toSeconds(e - this.timestamp).toString() + " sec"; this.debug && $("#profiler-done").html(t).show(); var n = this, r = n.buildPostColors(), o = { title: new URL(n.url).hostname.replace(/^www\./, ""), url: n.url, image: n.imagesrc, colors: r }; document.getElementById("palette-title").textContent = `/ ${o.title}`, chrome.storage.local.get(["palette-token"]).then((e => { Be(`${n.host}/palettes`, { headers: { Authorization: "Bearer " + e["palette-token"], "Content-Type": "application/json" }, method: "POST", data: JSON.stringify(o) }).then((e => { e.data.message ? window.close() : (n.resultUrl = "https://palette.site/palette?id=" + e.data.palette._id, $(".js-get-share-link").removeClass("is-loading")) })).catch((e => { console.log("Something went wrong. Try again.") })) }))}- api.palette.site
Receives palette creation requests that include the active tab URL, hostname-derived title, page image data, colors, and a Bearer token.