Is Grok Automation - Auto Grok on Grok.com safe?

Medium risk

Grok Automation is medium risk. Starting a Grok job, the extension asks its background for a remote config: a GET to configs.kylenguyen.me/config/grok-automation, falling back to onegreen.workers.dev. It supplies control selectors; without one, automation stops.

Truong Nguyenv2.6.5.0Chrome Web Store
45Risk
Who publishes it

Truong Nguyen - 4 other listings from the same operator, 1 of them carrying a finding

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

Publisher
Truong Nguyen
Registered address
Tu Hoàng, Nam Từ Liêm, Hà Nội 119000, VN
Registered contact
Truong Nguyen

Same store account

4 other listings published from this account, 186k+ users between them. 1 of them carries a finding.

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

Publishers can request a review.

Findings

Remote config controls Grok automation

Starting a Grok job, the extension asks its background for a remote config: a GET to configs.kylenguyen.me/config/grok-automation, falling back to onegreen.workers.dev.

It supplies control selectors; without one, automation stops.

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

You start a Grok automation job in the extension.

The job does not proceed with only the local extension code.

The extension did this

The extension requests a remote configuration and uses the returned selectors to drive the Grok page.

If the configuration is unavailable, the content script reports that it cannot connect to the server.

Captured request
GEThttps://configs.kylenguyen.me/config/grok-automation

Observed during dynamic analysis: the endpoint returned JSON with shareUrlTemplate, including an imagine-public.x.ai path ending in {uuid}.mp4, and selectors for imagineLink, fileInput, and imageUploading.

Headers
X-Client-Secret
<redacted>
Remote configuration fields used by the extension
  • Configuration host
    configs.kylenguyen.me

    This is where the extension asks for instructions before it runs your automation job.

  • Video URL pattern
    imagine-public.x.ai path ending in {uuid}.mp4

    This pattern tells the extension how generated video links should be recognized or formatted.

  • Imagine-page selector
    selectors.imagineLink

    This selector tells the extension which Grok page link to click when it redirects your job into the image or video flow.

  • Image upload selector
    selectors.fileInput

    This selector points the extension at the file input used to upload images into your Grok prompt.

  • Upload progress selector
    selectors.imageUploading

    This selector tells the extension how to wait while Grok is still processing an uploaded image.

The code that does this

The background module fetches remote configuration before automation runs

Readable version

Readable config fetcher

assets/remoteConfig-Bs4jYha-.js
function t(t, n) {  return t.version.split(",").map(t => t.trim()).filter(Boolean).includes(n.trim())}let n = null;async function e(t) {  const n = await fetch(`${t}/config/grok-automation`, {    method: "GET",    headers: {      "X-Client-Secret": "<redacted>"    }  });  if (!n.ok) throw new Error(`HTTP ${n.status}`);  const e = await n.json();  if (!e?.selectors) throw new Error("Invalid config shape");  return e}async function r() {  if (n) return n;  try {    return n = await e("https://configs.kylenguyen.me"), n  } catch (t) {}  try {    return n = await e("https://extension-config.onegreen.workers.dev"), n  } catch (t) {    return null  }}export {  r as g, t as i};
The code that does this

The content script gates automation on the remote config and consumes returned selectors

Readable version

Automation queue waits for GET_REMOTE_CONFIG

assets/index.ts-BVcYzrtz.js
async function z(e) {  const t = await new Promise(e => {    chrome.runtime.sendMessage({      type: "GET_REMOTE_CONFIG"    }, t => e(t ?? null))  });  if (!t) return e.status = "error", G(e), B.shift(), e.sendResponse({    success: !1,    error: "Cannot connect to server. Extension may be unauthorized.",    results: []  }), U = null, void V(1);  for (;;) {    if (e.isCancelling) {      e.status = "cancelled";      const t = e.results;      return B.shift(), G(e), e.sendResponse({        success: !1,        cancelled: !0,        results: t      }), U = null, void V(1)    }    if (e.pendingIndexes.length > 0) {      const n = [];      for (let r = 0; r < 1; r++) n.push($(e, t, W, G).catch(e => {}));      await Promise.all(n)    }    for (; W.some(t => t.id === e.id);) await F(200);    if (0 === e.pendingIndexes.length) {      e.status = "completed";      const t = e.results.every(e => e.success),        n = e.results;      return B.shift(), G(e), e.sendResponse({        success: t,        results: n      }), U = null, void V(1)    }  }}

Remote selectors drive page navigation and image upload

assets/index.ts-BVcYzrtz.js
I = async e => {  try {    b("Redirecting to imagine page...");    const t = window.location.href;    return !(!t.includes("/imagine") || t.includes("/imagine/") || t.includes("saved")) || (await m(e.selectors.imagineLink, "Imagine link", 1e4), !0)  } catch (t) {    return E("Error in redirectToImaginePage:", t), !1  }}k = async (e, t, n, r) => {  const o = n.selectors;  return await m(n.selectors.plusImageButton, "Upload image button"), await (async (e, t, n) => {    try {      if (!e.images || 0 === e.images.length) return C("No images provided in config"), !1;      if (t >= e.images.length || t < 0) return C(`Image index ${t} out of range. Total images: ${e.images.length}`), !1;      const r = e.images[t];      if (!r || !r.base64) return C("No image base64 provided"), !1;      let o = r.base64 || "";      o.includes(",") && (o = o.split(",")[1]);      const i = atob(o),        s = new Uint8Array(i.length);      for (let e = 0; e < i.length; e++) s[e] = i.charCodeAt(e);      const a = new Blob([s], {          type: "image/jpeg"        }),        u = new File([a], r.name || `image-${Date.now()}.jpg`, {          type: "image/jpeg"        });      b(`Creating file: ${u.name}, size: ${u.size} bytes`);      const c = n.selectors,        l = await h(c.fileInput, 1e4, 100, !1);      if (!l || 0 === l.length) return C("Could not find file input for image upload"), !1;      const d = l.first().get(0),        p = new DataTransfer;      return p.items.add(u), d ? (d.files = p.files, d.dispatchEvent(new Event("change", {        bubbles: !0      })), b("\u2705 Image file set and event triggered. Function will now complete."), !0) : (E("Could not get native input element to dispatch event."), !1)    } catch (r) {      return E("Error uploading image from base64:", r), !1    }  })(e, t, n), (!r || !r()) && (b("Waiting for image upload to complete..."), await p(3e3), await (async (e, t = 3e4, n = 200) => {    const r = Date.now();    for (; Date.now() - r < t;) {      const t = i(e);      if (!f(t)) return !0;      await p(n)    }    return !1  })(o.imageUploading, 3e4), !(await h(o.imageUploadFailed, 1e3)) || (C("Image upload failed"), await m(o.removeFailedUploadedImage, "Remove failed uploaded image"), !1))}
Remote hosts involved in this behavior
    • configs.kylenguyen.me

    Primary configuration host observed during dynamic analysis and present in the shipped config fetcher.

    • extension-config.onegreen.workers.dev

    Fallback configuration host present in the shipped config fetcher if the primary host fails.

    • imagine-public.x.ai

    Host pattern recorded in the captured response summary for generated video URLs.

Updated 30 September 2026kpeloeongamilgpjaibcdmldenfmdngp