Is HolyTrick safe?

High risk

HolyTrick is high risk. When you ask HolyTrick to change a page, its content script sends sanitized page HTML and your prompt to api.holytrick.com/getTrick. A // EXEC marker in the response makes the background script eval the rest via Runtime.evaluate.

75Risk
Who publishes it

1 other listing from the same operator, none carrying a finding

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

Same store account

1 other listing published from this account, 1k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

HolyTrick Runs Server-Returned Code in Active Tabs

When you ask HolyTrick to change a page, its content script sends sanitized page HTML and your prompt to api.holytrick.com/getTrick.

A // EXEC marker in the response makes the background script eval the rest via Runtime.evaluate.

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

You submit a command in HolyTrick while a webpage is open.

The action can start from the extension UI or a saved script action.

The extension did this

The extension sends page content to HolyTrick and can run marked server-returned code in that tab.

The shipped background script checks the response for // EXEC and passes that response text into Chrome debugger Runtime.evaluate.

Fields the shipped code serializes into the getTrick request
  • Your HolyTrick command
    Hide the recommendation section on the right side of this page (illustrative)

    This is the instruction you typed for the page change.

  • Current page HTML
    <form><input name="email" value="alex@example.com"><button>Continue</button></form> (illustrative)

    This can include visible text, form values, element names, and the page structure from the site you are viewing.

  • Selected element DOM
    {"Dom":"<button id="checkout">Pay now</button>","selector":"#checkout"} (illustrative)

    If you select page elements, their DOM and selector are sent with the request.

  • Extension metadata
    {"version":"0.1.4","apiKey":""} (illustrative)

    The request includes metadata that helps the service handle this extension version.

Captured request
POSThttps://api.holytrick.com/getTrick

Dynamic analysis confirmed this endpoint path and confirmed that a response containing // EXEC is passed to Runtime.evaluate.

Headers
Content-Type
application/json
The code that does this

Shipped code path from page snapshot to debugger evaluation

Readable version

Readable equivalent: sanitize the active page HTML

content-scripts/ui.js
function sanitizePageHtml(html) {  const config = {    ALLOWED_TAGS: ["div", "p", "span", "a", "img", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "table", "tr", "td", "th", "thead", "tbody", "input", "button", "form", "label", "select", "option", "textarea", "br", "hr", "strong", "em", "code", "pre", "blockquote"],    ALLOWED_ATTR: ["id", "class", "name", "value", "type", "placeholder", "for", "alt", "title"],    ALLOW_DATA_ATTR: false,    ALLOW_UNKNOWN_PROTOCOLS: false,    ALLOW_ARIA_ATTR: false,    FORBID_TAGS: ["style", "script"],    FORBID_ATTR: ["style", "href", "src"],    KEEP_CONTENT: true  };  DOMPurify.addHook("uponSanitizeElement", (node, data) => {    if (node instanceof HTMLElement) {      const style = node.getAttribute("style");      if (style && style.toLowerCase().includes("display:none")) {        node.parentNode?.removeChild(node);        return;      }      if (node.hasAttribute("hidden")) {        node.parentNode?.removeChild(node);        return;      }      if (data.tagName === "textarea" && node.textContent) {        node.textContent = node.textContent.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");        node.textContent = node.textContent.replace(/style\s*=\s*["'][^"']*["']/gi, "");      }    }  });  function normalizeWhitespace(value) {    let normalized = value.replace(/\s+/g, " ");    normalized = normalized.trim();    normalized = normalized.replace(/\s*([,.!?:;])\s*/g, "$1 ").trim();    normalized = normalized.replace(/([,.!?:;])([a-zA-Z0-9])/g, "$1 $2");    return normalized;  }  const sanitized = DOMPurify.sanitize(html, config);  return normalizeWhitespace(sanitized);}

Readable equivalent: send the prompt and page snapshot to the background

content-scripts/ui.js
const sendFeature = async () => {  if (!promptText) return;  setLoading(buildLoadingState());  closeElementPicker();  const sanitizedHtml = sanitizePageHtml(document.body.innerHTML);  setCurrentHtml(sanitizedHtml);  await browser.runtime.sendMessage({    action: "ht_send_feature",    data: [      ...messages.map(message => ({ role: message.role, content: message.code || message.text })),      { role: "user", content: promptText }    ],    html: sanitizedHtml,    extra: {      selectedElements: selectedElements.map(element => ({ Dom: element.Dom, selector: element.selector }))    }  });  setProgressText("");  setPromptText("");  setRunning(true);  setSelectedElements([]);  setMessages([...messages, { role: "user", text: promptText, status: "running" }]);};

Readable equivalent: POST to getTrick and collect streamed content

background.js
async function sendToHolyTrick(userCommand, html, extra, onProgress, onFinish, onError) {  try {    const account = await getAccount();    const token = account ? await getAuthToken() : "";    const response = await fetch("https://api.holytrick.com/getTrick", {      method: "POST",      body: JSON.stringify({ user_command: userCommand, html, extra }),      headers: {        "Content-Type": "application/json",        Authorization: account ? `Bearer ${token}` : ""      }    });    if (!response.ok) {      onError?.((await response.text()) || "unknown error");      return;    }    const reader = response.body?.getReader();    const decoder = new TextDecoder("utf-8");    let buffer = "";    let output = "";    for (;;) {      const { done, value } = await reader.read();      if (done) break;      buffer += decoder.decode(value, { stream: true });      const lines = buffer.split("\n");      buffer = lines.pop() || "";      for (const line of lines) {        if (line.startsWith("data: ")) {          const data = line.slice(6);          if (data === "[DONE]") continue;          const chunk = JSON.parse(data).choices[0]?.delta?.content || "";          if (chunk) {            output += chunk;            onProgress?.(output);          }        }      }    }    onFinish?.(output);  } catch (error) {    console.error("Error sending data:", error);    onError?.(error);  }}

Readable equivalent: evaluate returned code through Chrome debugger

background.js
async function evaluateInTab(tabId, remoteCode) {  await browser.debugger.attach({ tabId }, "1.3");  if (Array.isArray(remoteCode)) {    const results = [];    for (const code of remoteCode) {      const result = await browser.debugger.sendCommand(        { tabId },        "Runtime.evaluate",        { expression: helperPrefix + code, returnByValue: true }      );      track("EXEC_CODE");      results.push(result);    }    await browser.debugger.detach({ tabId });    return results;  }  const result = await browser.debugger.sendCommand(    { tabId },    "Runtime.evaluate",    { expression: helperPrefix + remoteCode, returnByValue: true }  );  await browser.debugger.detach({ tabId });  track("EXEC_CODE");  return result;}

Readable equivalent: handle a marked server response

background.js
browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => {  if (message.action === "ht_send_feature" && sender.tab) {    storage.getItem("local:apiKey").then(apiKey => {      sendToHolyTrick(        message.data,        message.html,        { apiKey: apiKey || cachedApiKey || "", version: extensionVersion, ...message.extra },        progress => browser.tabs.sendMessage(sender.tab.id, { action: "ht_send_feature_progress", finished: false, data: progress }),        async responseText => {          track("SEND_FEATURE");          await browser.tabs.sendMessage(sender.tab.id, { action: "ht_send_feature_progress", finished: true, data: responseText });          try {            if (responseText?.includes("// EXEC")) {              const remoteCode = responseText.substring(responseText.indexOf("// EXEC"));              const evaluation = await evaluateInTab(sender.tab.id, remoteCode);              await browser.tabs.sendMessage(sender.tab.id, {                action: "ht_send_feature_finished",                data: evaluation?.result?.value,                code: remoteCode              });              if (evaluation?.result?.value?.success) {                track(`EXEC_CODE_${evaluation.result.value.success ? "SUCCESS" : "FAILED"}`);              }            } else {              await browser.tabs.sendMessage(sender.tab.id, {                action: "ht_send_feature_finished",                data: { success: false, message: responseText.replace("REJECTED: ", "") }              });            }          } catch (error) {            track("EXEC_CODE_FAILED", { error: typeof error === "object" ? JSON.stringify(error) : error });            await browser.tabs.sendMessage(sender.tab.id, {              action: "ht_send_feature_finished",              data: { success: false, message: typeof error === "object" ? JSON.stringify(error) : error }            });          }        },        error => browser.tabs.sendMessage(sender.tab.id, { action: "ht_send_feature_finished", data: { message: error, success: false } })      );      sendResponse({ action: "hello" });    });    return true;  }});
External service contacted by this flow
    • api.holytrick.com

    Receives the getTrick POST containing the user command, sanitized page HTML, and extra metadata; its streamed response controls whether the debugger evaluation path is used.

What it can do

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

  • Store data in your browser

    storage

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

    tabs

  • Attach to pages with the browser's debugger, which can read and rewrite anything on them

    debugger

  • Sign you in with your Google account

    identity

Updated 30 September 2026chdpdcmianoeafncndadkpmklicedlkl