Is Primus safe?
Primus is high risk. Starting a Primus attestation makes the extension register request listeners on the tab, record matched headers, cookies, bodies, and place them into proof params sent to the offscreen engine. An earlier test saw only padolabs.org traffic.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Attestation flow copies headers and cookies
Starting a Primus attestation makes the extension register request listeners on the tab, record matched headers, cookies, bodies, and place them into proof params sent to the offscreen engine.
An earlier test saw only padolabs.org traffic.
You start a Primus attestation for a data-source site.
The extension opens or uses a tab for that site as part of the proof flow.
The extension records matched requests from that tab and prepares them as proof inputs.
Headers, cookies, and POST bodies are copied when the active template says the request must be captured.
| Field | Value | Why it matters | |
|---|---|---|---|
Session cookies | Cookie: luma.auth-session-key=sess_7f4b3d2a91c845bb; csrf=bd6d2f3c1a994d1e (illustrative) | This can identify your signed-in session for the site used in the attestation. | |
Authorization header | Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAxIn0.Ji6Q0l3YbQ4L9wVmzK3qYsB5cR7n2qH8pW1dAeT0sYg (illustrative) | This can carry a bearer token or similar value that proves your account is signed in. | |
Request body | POST body: {"accountType":"SPOT","timestamp":1720771200000} (illustrative) | This can include the submitted parameters for the account or API request being proved. | |
Matched request URL | https://api.binance.com/api/v3/account?timestamp=1720771200000 (illustrative) | This shows which account or API endpoint the proof flow matched during your attestation. | |
Anti-CSRF header | csrf-token: ajax:734920156421 (illustrative) | This can link the copied request to the signed-in browser session for that site. |
The shipped service worker captures headers and bodies before building proof parameters
a.onBeforeSendHeadersFn = d, a.onBeforeRequestFn = f, chrome.webRequest.onBeforeSendHeaders.addListener(d, {
urls: ["<all_urls>"],
types: ["xmlhttprequest", "main_frame"]
}, ["requestHeaders", "extraHeaders"]), chrome.webRequest.onBeforeRequest.addListener(f, {
urls: ["<all_urls>"],
types: ["xmlhttprequest", "main_frame"]
}, ["requestBody"])console.log("captured request", f, JSON.stringify(e));
const a = {
headers: s,
method: r,
url: f,
requestId: n,
templateRequestUrl: p,
type: e.type
};
i && (a.queryString = i), c(n, a), await lc()if (r?.raw?.[0]?.bytes) {
const a = new Uint8Array(r.raw[0].bytes),
t = (new TextDecoder).decode(a);
c(n, {
...e,
body: JSON.parse(t)
})
}
r?.formData && c(n, {
...e,
body: r.formData,
isFormData: !0
})Object.assign(p, {
reqType: "web",
host: S,
schemaType: b,
requests: m,
responses: T,
uiTemplate: x,
templateId: u,
calculations: s,
PADOSERVERURL: X,
padoExtensionVersion: e
}), await ka(p, t), qe(p, t), Je(p, t), c.formatAlgorithmParams = p, console.log("formatAlgorithmParams", p, t)const a = Object.assign({
isUserClick: "true"
}, s.formatAlgorithmParams),
c = await D(),
t = {
...a,
clientType: c?.clientType || ""
};
await _({
activeRequestAttestation: JSON.stringify(a)
}), console.log("pageDecode-algorithmParams", a), chrome.runtime.sendMessage({
type: "algorithm",
method: "getAttestation",
params: t
})_call(e, t = {}, i) {
const s = {
method: e,
version: i || CLIENT_VERSION,
params: t
},
n = JSON.stringify(s);
return Module.cwrap("callAlgorithm", "string", ["string"])(n)
}
getAttestation(e) {
const t = getEffectiveVersion(e?.clientType);
if (console.log("getAttestation AlgorithmInited=", this.initialized), !this.initialized) return JSON.stringify({
content: null,
retcode: "2",
retdesc: "Algorithm not initialized"
});
const i = this._call("getAttestation", e, t);
return console.log("getAttestation typeof res", typeof i, "res", i), i
}- api.padolabs.org
Production API base used for identity, event reporting, logs, and runtime configuration.
- api2.padolabs.org
Production WebSocket host hardcoded for algorithm-proxy and algoproxy endpoints.
Primus replays signed-in exchange API requests
Starting a Primus proof flow lets the extension retain headers from the data-source tab and replay matching exchange API requests with credentials included.
The confirmed path covers private Binance-style endpoints; no body was recorded.
You start a Primus proof flow that uses a signed-in exchange account.
The start handler marks the flow as a user click and sends the proof parameters to the attestation worker.
The extension can replay a matching private exchange API request with retained headers and browser credentials included.
The replayed response is used to validate account data before the proof is generated.
| Field | Value | Why it matters | |
|---|---|---|---|
Exchange session cookies | Cookie: Binance session cookies redacted | Keeps the replayed request tied to your signed-in exchange session. | |
Captured request headers | User-Agent: Mozilla/5.0; Accept: application/json | Preserves the same browser request context that the exchange page used. | |
Private account endpoint | https://www.binance.com/bapi/asset/v3/private/asset-service/asset/get-user-asset | Points the replay at an account API that returns data about your exchange profile or assets. | |
Proof-flow context | activeRequestAttestation with isUserClick true | Links the replayed exchange response to the proof request that is being generated. |
| Cookie | redacted active exchange session cookies |
Replay helper keeps headers and browser credentials
function ge({
url: e,
method: a,
body: c,
header: t,
isFormData: d
}) {
return {
url: e,
method: a,
body: c,
header: t || {},
isFormData: d
}
}
function ye({
url: e,
method: a,
body: c,
header: t,
isFormData: d
}) {
const f = {
method: "GET",
headers: {
"Content-Type": "application/json"
},
credentials: "include",
...{
method: a,
body: c,
headers: t
}
};
["POST", "PUT", "PATCH"].includes(f.method.toUpperCase()) && f.body && (i(f.body) || (f.body = JSON.stringify(f.body)));
const b = "application/x-www-form-urlencoded";
if (f.body && t["content-type"]?.includes(b) || t["Content-Type"]?.includes(b) && d) {
let e = i(f.body) ? JSON.parse(f.body) : f.body;
f.body = (r = e, Object.keys(r).map((e => encodeURIComponent(e) + "=" + encodeURIComponent(r[e]))).join("&"))
}
var r;
return {
finalOptions: f,
url: e
}
}
async function ve(e, a) {
const c = await fetch(a, e);
if (!c.ok) throw new Error(`HTTP error! status: ${c.status}`);
const t = c.headers.get("Content-Type") || "";
let d;
return d = t.includes("application/json") || t.includes("+json") ? await c.json() : t.includes("text/") ? await c.text() : t.includes("application/octet-stream") ? await c.blob() : await c.text(), {
data: d,
contentType: t,
finalUrl: c.url
}
}const a = await Se({
...f[e],
header: f[e].headers,
url: I,
body: E
});
a && (w = a.data, A = a.contentType, T = a.finalUrl)The extension stores matching request headers before replay
const {
requests: d
} = t, {
url: f,
requestHeaders: b,
method: r,
requestId: n
} = e;
let i = "";
const s = b.reduce(((e, a) => (e[a.name] = a.value, e)), {});
let p = "";
if (d.some((e => {
if (!1 === e.needCapture) return !1;
if (e.queryParams?.[0]) {
const a = f.split("?")[0];
if (e.url === a) {
const a = x(f, e.queryParams);
a && (i = a)
}
}
const a = u({
requestUrl: f,
requiredUrl: e.url,
urlType: e.urlType,
queryParams: e.queryParams
});
return a && (p = e.url), a
}))) {
console.log("captured request", f, JSON.stringify(e));
const a = {
headers: s,
method: r,
url: f,
requestId: n,
templateRequestUrl: p,
type: e.type
};
i && (a.queryString = i), c(n, a), await lc()
}A user-started flow launches proof generation
if ("start" === n) {
if (s.startHandled) return void r({
ok: !0
});
s.startHandled = !0, s.phase = xe.ATTESTING, Oc(), chrome.alarms.create(bc, {
periodInMinutes: .4
});
const {
userInfo: e
} = await w(["userInfo"]);
e || Kc().catch((e => {
console.log("ensureExtensionUserIdentity non-blocking error", e)
}));
const a = Object.assign({
isUserClick: "true"
}, s.formatAlgorithmParams),
c = await D(),
t = {
...a,
clientType: c?.clientType || ""
};
await _({
activeRequestAttestation: JSON.stringify(a)
}), console.log("pageDecode-algorithmParams", a), chrome.runtime.sendMessage({
type: "algorithm",
method: "getAttestation",
params: t
})
}- www.binance.com
Private account and asset endpoints used as replay targets during exchange proof validation.
- www.okx.com
Private profile endpoint named in verification evidence for the same authenticated replay pattern.