Is Notepal - 微信读书笔记同步助手 safe?

High risk

Notepal sends WeRead note and highlight contents to Mixpanel when sync errors occur.

When Notepal syncs WeRead notes to supported services, its error handling can include the note data in a Mixpanel event. That data includes highlight text, note text, book titles, authors, chapter titles, cover image URLs, source URLs, and creation times.

Randynamic LLCv1.1.2Chrome Web Store
72Risk
Who publishes it

Randynamic, LLC - 2 other listings from the same operator, none carrying a finding

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

Publisher
Randynamic LLC
Declared legal entity
Randynamic, LLC
Registered address
1111B S Governors Ave, Dover, DE 19904, US
Registered contact
Randynamic LLC

Same store account

2 other listings published from this account, 3k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

WeRead sync errors send note contents to Mixpanel

When a WeRead sync destination errors, the extension attaches note records to a Mixpanel Error event.

A POST to api.mixpanel.com/track carried properties.NoteDatas: highlight text, notes, book title, author, cover URL, chapter title.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You start syncing WeRead notes to Siyuan, Readwise, or Logseq.

The issue appears when that destination returns an error after the extension has prepared the note data.

The extension did this

The extension sends a Mixpanel error event with the prepared note records attached.

Those records are placed under the NoteDatas property before the tracker serializes the event.

Fields attached to the Mixpanel Error event
  • Your highlighted text
    highlightText: The margin notes changed how I read this chapter (illustrative)

    The highlighted passage can reveal what you selected while reading.

  • Your written note
    noteText: Follow up with the team about this idea (illustrative)

    A review note can contain your own commentary, names, or other personal context.

  • Book and author
    bookTitle: Atomic Habits; author: James Clear (illustrative)

    The book metadata connects the note to the title and author you were reading.

  • WeRead source link
    https://weread.qq.com/wrpage/book/share/3300024248 (illustrative)

    The share link points back to the WeRead book page associated with the note.

  • Creation time
    createTime: 1710950400 (illustrative)

    The timestamp places the note in time and can help reconstruct your reading activity.

Captured request
POSThttps://api.mixpanel.com/track?strict=1

Observed during dynamic analysis after a forced sync-destination error.

Headers
Content-Type
application/json
The code that does this

The shipped sync code passes note records into Mixpanel error telemetry

Readable version

Readable equivalent: create noteDatas from WeRead highlights and reviews

tabs/sync.4d579049.js
function WeReadHighlightToNoteDatas(bookmarkList) {  return bookmarkList.updated.map((highlight) => {    const chapterTitle = bookmarkList.chapters.find((chapter) => chapter.chapterUid === highlight.chapterUid)?.title;    return {      highlightText: highlight.markText,      bookTitle: bookmarkList.book.title,      author: bookmarkList.book.author,      chapterTitle,      imageUrl: bookmarkList.book.cover,      sourceUrl: `https://weread.qq.com/wrpage/book/share/${highlight.bookId}`,      createTime: highlight.createTime    };  });}function WeReadReviewToNoteDatas(reviewList) {  return reviewList.reviews    .map((item) => ({      highlightText: item.review.abstract,      bookTitle: item.review.book.title,      author: item.review.book.author,      imageUrl: item.review.book.cover,      noteText: item.review.content,      chapterTitle: item.review.chapterTitle,      sourceUrl: `https://weread.qq.com/wrpage/book/share/${item.review.bookId}`,      createTime: item.review.createTime    }))    .filter((noteData) => noteData.highlightText);}async function getWereadNotes(bookId) {  const highlights = mockLog ? mockLog.highlights : await getWeReadHighlights(bookId);  const notes = mockLog ? mockLog.notes : await getWeReadNotes(bookId);  if (localStorage.getItem(StorageKey.DebugMode) === "true") {    await getWeReadNotebooks();    navigator.userAgent;  }  const highlightNoteDatas = WeReadHighlightToNoteDatas(highlights);  const reviewNoteDatas = WeReadReviewToNoteDatas(notes);  return {    noteDatas: highlightNoteDatas.concat(reviewNoteDatas).sort((left, right) => (left.createTime || 0) - (right.createTime || 0)),    highlights,    notes  };}

Readable equivalent: destination error paths include NoteDatas

tabs/sync.4d579049.js
async function syncSiyuan(noteDatas) {  const notebookId = await storage.get(StorageKey.SiyuanNotebookId);  if (!notebookId) throw Error("Set the Siyuan notebook id first");  const template = (await storage.get(StorageKey.SiyuanTemplate)) || defaultSiyuanTemplate;  const syncPathTemplate = (await storage.get(StorageKey.SiyuanSyncPath)) || "/books/{{bookTitle}}";  const groupedByChapter = groupBy(noteDatas, "chapterTitle");  const bookTitle = noteDatas[0].bookTitle;  const path = await liquid.parseAndRender(syncPathTemplate, { bookTitle });  try {    const markdown = await liquid.parseAndRender(template, { notes: groupedByChapter, ...noteDatas[0] });    const response = await postSiyuan("/api/filetree/createDocWithMd", { notebook: notebookId, path, markdown });    const body = await response.json();    if (response.ok) {      trackEvent([{ event: EventType.SyncSiyuan, properties: { Highlights_count: noteDatas.length } }]);      await storage.set(StorageKey.ExportCount, ((await storage.get(StorageKey.ExportCount)) || 0) + noteDatas.length);      return { success: true, url: `siyuan://blocks/${body.data}` };    }    throw Error(body.msg);  } catch (error) {    trackEvent([{ event: EventType.Error, properties: { Type: EventType.SyncSiyuan, Error: error, NoteDatas: noteDatas } }]);    throw Error("Sync failed");  }}async function syncReadwise(noteDatas) {  const highlights = noteDatas.map(noteDataToReadwiseHighlight).filter((highlight) => highlight.text && highlight.text.length > 0);  const token = await storage.get(StorageKey.ReadwiseToken);  if (!token) throw Error("Readwise Token not set");  const valid = await validateReadwiseToken(token);  if (!valid) throw Error("Readwise Token invalid");  const response = await fetch("https://readwise.io/api/v2/highlights", {    method: "POST",    body: JSON.stringify({ highlights }),    headers: new Headers({ "Content-Type": "application/json", Authorization: `Token ${token}` })  });  const body = await response.json();  if (response.status < 200 || response.status >= 300) {    trackEvent([{ event: EventType.Error, properties: { Type: "HTTP", Status: response.status, Response: body, NoteDatas: highlights } }]);  } else {    await storage.set(StorageKey.ExportCount, ((await storage.get(StorageKey.ExportCount)) || 0) + highlights.length);    trackEvent([{ event: EventType.SyncReadwise, properties: { Highlights_count: highlights.length } }]);  }  return { status: response.status, data: body };}async function syncLogseq(noteDatas) {  const connected = await checkLogseqConnect();  if (!connected) throw Error("Logseq connection failed; check the token and API server");  const groupedByChapter = groupBy(noteDatas, "chapterTitle");  const bookTitle = noteDatas[0].bookTitle;  const pageTemplate = await storage.get(StorageKey.LogseqSyncPageName);  const blockTemplate = (await storage.get(StorageKey.LogseqNoteBlockTemplate)) || defaultLogseqNoteBlockTemplate;  const pageName = await liquid.parseAndRender(pageTemplate, { bookTitle });  try {    const pageResponse = await callLogseq("logseq.Editor.createPage", [pageName, {}, {}]);    const { uuid } = await pageResponse.json();    const blocks = Object.keys(groupedByChapter).map((chapterTitle) => ({      content: `${chapterTitle}`,      children: groupedByChapter[chapterTitle].map((noteData) => ({        content: liquid.parseAndRenderSync(blockTemplate, {          chapterTitle,          bookTitle,          highlightText: noteData.highlightText,          noteText: noteData.noteText,          author: noteData.author        })      }))    }));    await callLogseq("logseq.Editor.insertBatchBlock", [uuid, blocks]);    await storage.set(StorageKey.ExportCount, ((await storage.get(StorageKey.ExportCount)) || 0) + noteDatas.length);    trackEvent([{ event: EventType.SyncLogseq, properties: { Highlights_count: noteDatas.length } }]);    return { url: `logseq://graph/logseq?page=${pageName}` };  } catch (error) {    trackEvent([{ event: EventType.Error, properties: { Type: EventType.SyncLogseq, Error: error, NoteDatas: noteDatas } }]);    throw error;  }}

Readable equivalent: trackEvent posts the complete event array to Mixpanel

tabs/sync.4d579049.js
async function trackEvent(events) {  const debugId = await storage.get(StorageKey.DebugId);  events.forEach((event) => {    event.properties ||= {};    event.properties.Version = packageJson.version;    event.properties.time = Number(new Date());    event.properties.token = "77cf4f0a97fe05780b95175c9eac4765";    event.properties.distinct_id = debugId;  });  const response = await fetch("https://api.mixpanel.com/track?strict=1", {    method: "POST",    headers: new Headers({ "Content-Type": "application/json" }),    body: JSON.stringify(events)  });  return response;}
Hosts involved in the confirmed path
    • weread.qq.com

    Source host for notebook, highlight, and review data loaded with the user's WeRead session.

    • api.mixpanel.com

    Mixpanel tracking endpoint that receives the Error event containing NoteDatas.

Where it sends data

Destinations our analysis observed Notepal - 微信读书笔记同步助手 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.

  • api.mixpanel.comwidely used

    Notepal - 微信读书笔记同步助手 sends data to api.mixpanel.com. A widely used service: 35 other extensions we have analysed send data here.

Updated 30 September 2026fdiajgjolaldofnimijejnjocmpceakl