Is Pixiv Downloader safe?
Pixiv Downloader sends download details and Google account identifiers to a Google Apps Script endpoint.
When a Pixiv download completes, the extension posts a log that includes your Google email and user ID, account status, and download count. It also sends details about the downloaded work, such as the page URL, title, Pixiv ID, type, view and bookmark counts, upload date, and tags, and stores failed logs locally for later retry.
Who publishes itga_i_sa_i - no other listings under this identity
ga_i_sa_i - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Pixiv downloads send account and work metadata to Apps Script
After a Pixiv PDF/EPUB download completes, Pixiv Downloader builds a record with account identifiers and work metadata, posted to a hardcoded Apps Script URL.
On failure it falls back to a beacon and stores the record in localStorage.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You finish a Pixiv PDF or EPUB download using the extension.
The code path is reached after the generated file is downloaded.
The extension posts your account identifiers and the downloaded work's metadata to a Google Apps Script URL.
If that send fails, the same record is stored locally and can be retried.
- Download timestamp2026/07/14 12:04:33.123 (illustrative)
Ties the record to the exact time a download completed.
- Your extension accountreader@example.com, userId 38492017 (illustrative)
Connects the download event to your account identity when the extension has it.
- Your plan and countersfree, downloadCount 12 (illustrative)
Shows whether your account is marked as paid or free and how many downloads were counted.
- Pixiv page URLhttps://www.pixiv.net/artworks/123456789 (illustrative)
Identifies the exact Pixiv artwork or novel page associated with the download.
- Pixiv work detailsSummer Illustration, id 123456789, illust2 (illustrative)
Names what you downloaded and adds Pixiv's metadata for that work.
- Popularity metricsviewCount 84521, bookmarkCount 3910 (illustrative)
Adds view and bookmark counts for the downloaded work.
- Pixiv tagsillustration, original, landscape (illustrative)
Carries the Pixiv tag list associated with the work you downloaded.
The source sends the request with mode no-cors and does not read a response.
- Content-Type
- application/json
If the first send fails, the extension keeps the same account-and-work record in your browser so it can be sent later.
- Location
- localStorage key 'failedGoogleScriptLogs'
{ "data": [ "date string", "success flag", "", "user email", "user id", "vip or free", "download count", "account created timestamp", "account canceled timestamp", "", "Pixiv upload date", "Pixiv page URL", "Pixiv title", "Pixiv work id", "Pixiv work type", "view count", "bookmark count", "comma-joined Pixiv tags" ], "artTitle": "Summer Illustration (illustrative)", "timestamp": "2026-07-14T12:04:33.123Z (illustrative)"}Shipped code that gathers metadata, logs completion, and posts the record
Readable equivalent of the Pixiv metadata fetch
assets/main.ts-bd71c890.jsconst getPixivMetadata = async (url) => { const match = url.match(/https:\/\/www\.pixiv\.net\/(artworks|novel)(?:\/show\.php\?id=|\/)(\d+)/); if (!match) return null; const workType = match[1] === "artworks" ? "illust" : "novel"; const workId = match[2]; const responseText = await (await fetch(`https://www.pixiv.net/ajax/${workType}/${workId}?lang=ja`)).text(); const parsed = new DOMParser().parseFromString(responseText, "text/html"); const body = JSON.parse(parsed.body.innerText).body; return { pageUrl: url, ArtType: workType, artTitle: body.illustTitle ?? body.title, artId: body.illustId ?? body.id, uploadDate: new Date(body.uploadDate), ArtistName: body.userName, ArtistId: body.userId, seriesTitle: body.seriesNavData?.title ?? "", seriesOrder: body.seriesNavData?.order != null ? String(body.seriesNavData.order) : "", id: body.id, illustType: body.illustType, rowUrl: body.urls?.original ?? "", pageCount: body.pageCount, language: body.language, content: body.content, coverUrl: body.coverUrl, imgUrl: Object.values(body.textEmbeddedImages || {}).map((image) => image?.urls?.["1200x1200"]), viewCount: body.viewCount, bookmarkCount: body.bookmarkCount, tags: body.tags.tags };};Readable equivalent of the completion logger
assets/main.ts-bd71c890.jsclass DownloadLogger { constructor(startMessage = "") { this.discord = new SpreadsheetReporter(); const match = startMessage.match(/▼downloadCount\.value\s*:\s*(\d+)/); this.downloadCount = match ? parseInt(match[1], 10) : null; } async logComplete(meta, user) { await this.discord.stack("▼Complete Jobs!"); await this.discord.post_spreadsheet(meta, user, this.downloadCount); } async logError(error, meta, user) { await this.discord.error(error, { meta: meta || {}, user, downloadCount: this.downloadCount }); }}Readable equivalent of the Apps Script reporter
assets/main.ts-bd71c890.jspost_spreadsheet = async (meta, user, downloadCount, success = true) => { const endpoint = "https://script.google.com/macros/s/AKfycbyBn0ud1Ij37SkHkCvXeyX4Cqne8py5fjtC4M44eUkU-yTG7qh-AuOmZmNQbG2veoWY0g/exec"; const account = user || { email: "unknown", userId: "unknown", isVIP: false, created: null, canceled: null }; const record = [ await this.date(), success, "", account.email, account.userId, account.isVIP ? "vip" : "free", downloadCount, account.created, account.canceled, "", meta.uploadDate, meta.pageUrl, meta.artTitle, meta.id, meta.illustType ? meta.ArtType + meta.illustType : meta.ArtType, meta.viewCount, meta.bookmarkCount, meta.tags.map((tag) => tag.tag).join(", ") ]; try { await fetch(endpoint, { method: "POST", mode: "no-cors", headers: { "Content-Type": "application/json" }, body: JSON.stringify(record) }); return true; } catch { if (navigator.sendBeacon) { try { const blob = new Blob([JSON.stringify(record)], { type: "application/json" }); if (navigator.sendBeacon(endpoint, blob)) return true; } catch (error) { console.error("Beaconエラー:", error); } } try { const failed = JSON.parse(localStorage.getItem("failedGoogleScriptLogs") || "[]"); failed.push({ timestamp: new Date().toISOString(), data: record, artTitle: meta.artTitle }); if (failed.length > 100) failed.shift(); localStorage.setItem("failedGoogleScriptLogs", JSON.stringify(failed)); } catch { console.error("Beaconエラー:"); } return true; }};Readable equivalent of the injected button handler
assets/main.ts-bd71c890.jsdownloadButton.addEventListener("click", async (event) => { event.preventDefault(); event.stopPropagation(); const fullUrl = href.startsWith("http") ? href : `https://www.pixiv.net${href}`; if (downloadQueue) downloadQueue.orderWork(fullUrl);});- www.pixiv.net
The content script fetches Pixiv's own artwork or novel metadata from /ajax/{type}/{id}?lang=ja.
- script.google.com
Receives the account-and-work record through a hardcoded Google Apps Script endpoint.
Where it sends data
Destinations our analysis observed Pixiv Downloader contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- script.google.com/macros/s/AKfycbyBn0ud1Ij37SkHkCvXeyX4Cqne8py5fjtC4M44eUkU-yTG7qh-AuOmZmNQbG2veoWY0g/exec
Pixiv Downloader sends data to script.google.com/macros/s/AKfycbyBn0ud1Ij37SkHkCvXeyX4Cqne8py5fjtC4M44eUkU-yTG7qh-AuOmZmNQbG2veoWY0g/exec. No other extension we have analysed sends data here.