Is KanFan - Manga Translator safe?
KanFan is high risk. When you click KanFan's "Allow and retry" prompt, the extension asks Chrome for cookie access, stores the origin, and later reads cookies for image URLs. DA confirmed a GET to api.kanfan.site/test with the cookie header present.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Cookie headers added to image fetches after approval
When you click KanFan's "Allow and retry" prompt, the extension asks Chrome for cookie access, stores the origin, and later reads cookies for image URLs.
DA confirmed a GET to api.kanfan.site/test with the cookie header present.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You approve the cookie-assist prompt for a site.
The content script sends the current page URL to the background service worker.
The extension reads cookies for matching image URLs and adds them to retry requests.
The approval is saved per origin and reused until revoked.
- Cookie request headersessionid=4f8c0f7a9d2e11ef; cf_clearance=R4k9QvM2h7 (illustrative)
This can carry signed-in session state for the site whose image is being fetched.
- Approved site originhttps://www.pixiv.net
This lets the extension remember which site you approved for future image retries.
- Image referrerhttps://www.pixiv.net/
This tells the image host which site context the retry is meant to resemble.
Cookie-assisted GET observed after approval; no request body was recorded.
The click prompt asks for cookie assist and saves approval state
Content-script Allow button sends requestCookieAssist
content.jsC.addEventListener("click", async () => { C.disabled = !0; const t = await new Promise(e => { try { chrome.runtime.sendMessage({ action: i.REQUEST_COOKIE_ASSIST, pageUrl: location.href }, t => e(t)) } catch (t) { e({ ok: !1, error: t?.message || String(t) }) } }); if (C.disabled = !1, t?.ok) return T.style.display = "none", void("function" == typeof e && e(t)); _.textContent = (0, s.i18nMessage)("msgCookieAssistDenied", "Cookie access was not enabled."), C.style.display = "none" })Background handler requests cookies permission and stores the origin
background.jsasync function(e, t, a) { try { const n = m(e, t); if (!n) return void a({ ok: !1, error: "invalid_origin" }); if (!await S() && !await async function() { try { return !!chrome?.permissions?.request && await chrome.permissions.request({ permissions: ["cookies"] }) } catch { return !1 } }()) { const e = await p(); return e.includes(n) || await A([...e, n]), void a({ ok: !1, origin: n, error: "permission_denied" }) } const r = await _(); r.includes(n) || await w([...r, n]); const o = await p(); o.includes(n) && await A(o.filter(e => e !== n)), a({ ok: !0, origin: n, allowed: !0, hasCookiePermission: !0 }) } catch (e) { a({ ok: !1, error: e?.message || String(e) }) } }The image retry path reads cookies and sets the Cookie header
Readable fetchImageResponse implementation
background.jst.fetchImageResponse = async function(e, t) { const o = function(e) { const t = new URL(e).hostname; return a.find(e => t === e.urlFilter || t.endsWith(e.urlFilter)) }(e), l = o?.referer; let u = null; try { t && (u = new URL(t).href) } catch {} if (!u) try { const e = await chrome.storage.local.get(["last_active_url"]); e?.last_active_url && (u = new URL(e.last_active_url).href) } catch {} const d = new URL(e).origin + "/", h = l || u || d, f = new URL(e).hostname; let g = h; if (!l) try { "same-origin" !== i(f, new URL(h).hostname) && (g = new URL(h).origin + "/") } catch {} let m = null; try { const e = new URL(h), t = e.hostname.replace(/^www\./, ""); m = `${e.protocol}//${t}` } catch {} const _ = await async function(e, t) { try { const a = await chrome.storage.local.get([n]), r = Array.isArray(a?.[n]) ? a[n] : [], o = c(t), s = c(e); return !!(o && r.includes(o) || s && r.includes(s)) } catch { return !1 } }(e, t), p = !_ && await async function(e, t) { try { const a = await chrome.storage.local.get([r]), n = Array.isArray(a?.[r]) ? a[r] : [], o = c(t), s = c(e); return !!(o && n.includes(o) || s && n.includes(s)) } catch { return !1 } }(e, t), w = !!_ && await async function() { try { return !!chrome?.permissions?.contains && await chrome.permissions.contains({ permissions: ["cookies"] }) } catch { return !1 } }(), A = _ && w ? await async function(e, t) { try { if (!chrome?.cookies?.getAll) return ""; const a = await chrome.cookies.getAll({ url: e }); let n = []; if (t) try { n = await chrome.cookies.getAll({ url: e, partitionKey: { topLevelSite: t } }) } catch {} const r = new Map; for (const e of a) r.set(e.name, e); for (const e of n) r.set(e.name, e); return r.size ? Array.from(r.values()).map(e => `${e.name}=${e.value}`).join("; ") : "" } catch { return "" } }(e, m): "", S = await async function({ referer: e, domain: t, cookieHeader: a }, n) { const r = Math.floor(2e4 * Math.random()) + 1e3; let o = "cross-site"; try { o = i(t, new URL(e).hostname) } catch {} const s = [{ header: "referer", operation: "set", value: e }, { header: "sec-fetch-site", operation: "set", value: o }, { header: "sec-fetch-mode", operation: "set", value: "no-cors" }, { header: "sec-fetch-dest", operation: "set", value: "image" }]; a && s.push({ header: "cookie", operation: "set", value: a }); const c = { id: r, priority: 1, action: { type: "modifyHeaders", requestHeaders: s }, condition: { urlFilter: `||${t}`, resourceTypes: ["xmlhttprequest", "image", "other"] } }; try { return await chrome.declarativeNetRequest.updateDynamicRules({ addRules: [c], removeRuleIds: [r] }), await n() } catch (e) { throw console.error(`[DNR] Rule add failed (ID:${r}):`, e), e } finally { try { await chrome.declarativeNetRequest.updateDynamicRules({ addRules: [], removeRuleIds: [r] }) } catch (e) { console.warn("[DNR] Rule cleanup failed:", e) } } }({ referer: g, domain: f, cookieHeader: A }, () => fetch(e, { method: "GET", credentials: "omit", cache: "no-cache", headers: { Accept: "image/avif,image/webp,image/apng,image/*,*/*;q=0.8" } })); if (!(S?.ok || A || p || _ && w)) throw new s; return S };- api.kanfan.site
Observed endpoint for the confirmed cookie-assisted GET request during verification.
- pximg.net
Hardcoded image-host rule that uses https://www.pixiv.net/ as the referrer context.
- hamreus.com
Hardcoded image-host rule that uses https://www.manhuagui.com/ as the referrer context.
- klimv1.xyz
Hardcoded image-host rule that uses https://klmanga.com/ as the referrer context.