diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..176a458
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto
diff --git a/PRIVACY.md b/PRIVACY.md
new file mode 100644
index 0000000..8f4cb21
--- /dev/null
+++ b/PRIVACY.md
@@ -0,0 +1,88 @@
+# Privacy
+
+BiliStreamMonitor runs entirely inside your browser. There is no server behind
+it, no account, and no build step — the source in this repository is what runs.
+Everything below is a statement about code you can read here; file references
+point at the exact place.
+
+## What it reads
+
+- **One cookie: `DedeUserID` on `https://www.bilibili.com`.** `shared/api.js`
+ calls `chrome.cookies.get({ url: 'https://www.bilibili.com', name:
+ 'DedeUserID' })` and reads no other cookie anywhere in the extension. That
+ value is your own numeric Bilibili user id. It is used as the `target_id`
+ query parameter of the medal-wall request and for nothing else — it is not
+ written to storage and not sent to any host other than Bilibili.
+- **Your Bilibili session, implicitly.** The API calls in `shared/api.js` use
+ `fetch(url, { credentials: 'include' })`, so Chrome attaches the cookies it
+ already holds for `bilibili.com` — the same ones bilibili.com receives when
+ you browse it yourself. That is what makes "your follow list" mean *yours*.
+ Those cookies are sent by the browser to Bilibili and to no one else.
+
+## Which endpoints it calls
+
+All API calls go to `https://api.live.bilibili.com` (`API_BASE` in
+`shared/constants.js`); `shared/api.js` contains every call site:
+
+| Endpoint | When |
+|---|---|
+| `/xlive/web-ucenter/user/MedalWall` | every refresh cycle |
+| `/room/v1/Room/get_status_info_by_uids` | every cycle, for the uids currently in scope |
+| `/xlive/web-ucenter/user/following` | only when **All other follows** is ticked, or when you open the "everything live" view |
+| `/room/v1/Room/get_info` | hovering a live card, and adding a custom room |
+| `/live_user/v1/Master/info` | resolving a custom room's streamer name |
+
+Two other kinds of Bilibili traffic:
+
+- **Images.** Avatars and stream covers come from the `hdslb.com` CDN.
+ Notification icons are fetched with `credentials: 'omit'` and
+ `referrerPolicy: 'no-referrer'` (`background/notify.js`), and the popup's
+ `
` tags carry `referrerpolicy="no-referrer"` (`popup/cards.js`,
+ `popup/settings.js`) — so those image requests carry no cookies and no
+ referrer.
+- **The hover preview.** In live-player mode the popup embeds
+ `https://www.bilibili.com/blackboard/live/live-activity-player.html` in an
+ iframe (`popup/preview.js`), only while you are hovering a live card.
+
+`manifest.json` declares exactly two host permissions, `https://*.bilibili.com/*`
+and `https://*.hdslb.com/*`. No other host appears anywhere in the code.
+
+## Where the data goes
+
+Only to your own machine and to Bilibili.
+
+- **Local storage only.** Settings, marks, hidden list, custom rooms and the
+ last known stream states live in `chrome.storage.local` (`shared/storage.js`).
+ `chrome.storage.sync` is not used anywhere, so nothing is uploaded to your
+ Google account or copied to your other devices.
+- **Export is a plain file save.** The export button builds a JSON `Blob` and
+ triggers a normal browser download (`popup/settings.js`); the file goes
+ wherever you save it. It contains settings only — custom rooms, marks, hidden
+ list, alert scope, view mode, refresh interval, preview options, appearance —
+ because `exportConfig` copies a fixed `SETTINGS_KEYS` allowlist and
+ deliberately leaves runtime state out (`shared/storage.js`).
+- **The content script is narrow.** It runs on one URL pattern, the Bilibili
+ live-activity player page (`manifest.json` → `content_scripts.matches`), and
+ acts only on `postMessage` events whose `event.origin` is this extension's own
+ id, which it uses to sync mute and volume (`content_script.js`).
+
+## What is not collected
+
+- No analytics, telemetry, crash or usage reporting. There is no `sendBeacon`,
+ no `WebSocket`, no reporting endpoint in the codebase.
+- No third-party servers. The author receives nothing — there is nowhere for it
+ to be sent to.
+- No remote code. The icons in the popup are an inline SVG sprite rather than a
+ CDN stylesheet (`popup/popup.html`), and `manifest.json` declares no
+ `externally_connectable` and loads no remote script.
+
+## Revoking access
+
+- **Cut off site access:** `chrome://extensions` → BiliStreamMonitor →
+ **Details** → **Site access**. Without access to `bilibili.com` the extension
+ can no longer query anything.
+- **Remove it:** `chrome://extensions` → **Remove**. Chrome discards the
+ extension together with its `chrome.storage.local` area; a JSON file you
+ exported yourself stays where you saved it.
+- The `cookies` permission is granted at install time and Chrome offers no way
+ to revoke it on its own — uninstalling is how you take it back.
diff --git a/README.md b/README.md
index 0cd9c42..932e0e2 100644
--- a/README.md
+++ b/README.md
@@ -89,6 +89,9 @@ it on is the only thing that makes the extension page your whole follow list.
No ``, no analytics, no third-party requests. The content script runs
on exactly one URL pattern, to keep the preview player in sync.
+Which cookie is read, which endpoints are called and where the data stays is
+written out endpoint by endpoint in [PRIVACY.md](PRIVACY.md).
+
## Configuration
Everything lives behind the gear in the popup: refresh interval (30 s minimum,
diff --git a/README.zh-Hant.md b/README.zh-Hant.md
index 1a52ecd..87e28ce 100644
--- a/README.zh-Hant.md
+++ b/README.zh-Hant.md
@@ -69,6 +69,8 @@
沒有 ``,沒有任何分析工具,也不會對第三方發出請求。content script 只作用在唯一一個網址模式上,用來讓預覽播放器保持同步。
+讀了哪個 cookie、呼叫了哪些 endpoint、資料留在哪裡,逐條寫在 [PRIVACY.md](PRIVACY.md)(英文)。
+
## 設定
所有設定都收在 popup 裡的齒輪圖示後面:重新整理間隔(最短 30 秒、預設 60 秒)、popup 大小、頭像大小、間距、字型大小、亮/暗主題、預覽模式、隱藏清單,以及提醒範圍矩陣。