<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>模組七：資安與隱私 on Tarragon</title><link>https://tarrragon.github.io/blog/monitoring/07-security-privacy/</link><description>Recent content in 模組七：資安與隱私 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/index.xml" rel="self" type="application/rss+xml"/><item><title>SDK Redaction API 設計</title><link>https://tarrragon.github.io/blog/monitoring/07-security-privacy/sdk-redaction-api/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/07-security-privacy/sdk-redaction-api/</guid><description>&lt;p>Redaction 是在事件資料離開 client 之前，把敏感欄位的值替換成遮罩或移除。本章聚焦 redaction 的策略面 — 哪些資訊需要保護、保護的判斷依據和適用範圍。SDK 的 API 實作細節（初始化方式、helper 函式設計、和 flush 管線的整合）見 &lt;a href="https://tarrragon.github.io/blog/monitoring/03-sdk-design/redaction-helper/" data-link-title="SDK redaction helper" data-link-desc="在事件離開 SDK 前移除敏感資訊 — 預設 redaction rule 處理常見 pattern，自訂 rule 處理業務特定的 secret">SDK redaction helper&lt;/a>。Redaction 在 SDK 端執行的設計原則是「敏感資料不離開 client」— 一旦資料送到 collector，即使 collector 有 access control，資料已經在網路上傳輸過，多了一層洩漏面。&lt;/p>
&lt;h2 id="預設-redaction-rule">預設 Redaction Rule&lt;/h2>
&lt;p>SDK 內建的 redaction rule 覆蓋最常見的敏感欄位模式。開發者不需要設定就能獲得基本保護。&lt;/p>
&lt;h3 id="欄位名稱比對">欄位名稱比對&lt;/h3>
&lt;p>以下欄位名稱（不分大小寫）的值自動替換為 &lt;code>[REDACTED]&lt;/code>：&lt;/p>
&lt;ul>
&lt;li>&lt;code>password&lt;/code>、&lt;code>passwd&lt;/code>、&lt;code>secret&lt;/code>、&lt;code>token&lt;/code>、&lt;code>api_key&lt;/code>、&lt;code>apiKey&lt;/code>&lt;/li>
&lt;li>&lt;code>authorization&lt;/code>、&lt;code>auth&lt;/code>、&lt;code>credential&lt;/code>&lt;/li>
&lt;li>&lt;code>ssn&lt;/code>、&lt;code>social_security&lt;/code>&lt;/li>
&lt;li>&lt;code>credit_card&lt;/code>、&lt;code>card_number&lt;/code>、&lt;code>cvv&lt;/code>、&lt;code>cvc&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>欄位名稱比對用 substring match — &lt;code>user_password&lt;/code> 包含 &lt;code>password&lt;/code> 會被 redact，&lt;code>password_reset_token&lt;/code> 包含 &lt;code>password&lt;/code> 和 &lt;code>token&lt;/code> 也會。&lt;/p>
&lt;h3 id="值格式比對">值格式比對&lt;/h3>
&lt;p>以下格式的值無論欄位名稱為何都自動替換：&lt;/p>
&lt;ul>
&lt;li>Email 地址格式（&lt;code>user@domain.com&lt;/code> → &lt;code>u***@domain.com&lt;/code>）&lt;/li>
&lt;li>信用卡號碼格式（連續 13-19 位數字 → 保留末四碼）&lt;/li>
&lt;li>Bearer token 格式（&lt;code>Bearer xxx&lt;/code> → &lt;code>Bearer [REDACTED]&lt;/code>）&lt;/li>
&lt;/ul>
&lt;p>值格式比對用正則表達式。正則的效能影響在大量事件時需要注意 — 預設 rule 的正則保持簡單，避免 catastrophic backtracking。&lt;/p>
&lt;h2 id="自訂-pattern">自訂 Pattern&lt;/h2>
&lt;p>應用可能有自己的 secret 格式，預設 rule 覆蓋不到。SDK 提供 API 讓開發者註冊自訂 redaction pattern。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">monitor.addRedactionRule(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> name: &amp;#39;internal-api-key&amp;#39;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> pattern: RegExp(r&amp;#39;sk_live_[a-zA-Z0-9]{24}&amp;#39;),
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> replacement: &amp;#39;[REDACTED:api-key]&amp;#39;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">monitor.addRedactionRule(
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> name: &amp;#39;database-url&amp;#39;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> fieldNames: [&amp;#39;database_url&amp;#39;, &amp;#39;db_url&amp;#39;, &amp;#39;connection_string&amp;#39;],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> replacement: &amp;#39;[REDACTED:db-url]&amp;#39;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>自訂 pattern 的設計考量：&lt;/p>
&lt;p>&lt;strong>Pattern 在 init 時註冊&lt;/strong>。Redaction rule 在 SDK 初始化時設定，之後所有事件都通過這些 rule。不支援動態修改 — 避免「中途加 rule 導致之前的事件沒被 redact」的困惑。&lt;/p>
&lt;p>&lt;strong>Pattern 順序無關&lt;/strong>。所有 rule 獨立執行，不依賴順序。一個欄位可以匹配多個 rule，以第一個匹配的 replacement 為準。&lt;/p>
&lt;p>&lt;strong>Replacement 可以保留部分資訊&lt;/strong>。&lt;code>[REDACTED]&lt;/code> 完全遮蔽，&lt;code>[REDACTED:api-key]&lt;/code> 保留類型資訊，&lt;code>u***@domain.com&lt;/code> 保留結構。保留類型資訊對 debug 有幫助 — 看到 &lt;code>[REDACTED:api-key]&lt;/code> 至少知道這裡原本有一個 API key。&lt;/p>
&lt;h2 id="redaction-的適用範圍">Redaction 的適用範圍&lt;/h2>
&lt;p>Redaction 應用在 SDK 送出事件前的最後一步 — 在序列化（JSON encode）之前。適用範圍包括：&lt;/p>
&lt;ul>
&lt;li>Event 的 data 欄位（自由欄位，開發者可能放入任何內容）&lt;/li>
&lt;li>Error 的 stack trace（檔案路徑可能包含使用者名稱或部署路徑）&lt;/li>
&lt;li>Error 的 message（例外訊息可能包含 query string 或參數值）&lt;/li>
&lt;li>Lifecycle 的 metadata（連線 URL 可能包含認證資訊）&lt;/li>
&lt;/ul>
&lt;p>Redaction 不應用在 SDK 的內部欄位（timestamp、event type、session ID）— 這些是 SDK 自己產生的，不包含使用者資料。&lt;/p></description><content:encoded><![CDATA[<p>Redaction 是在事件資料離開 client 之前，把敏感欄位的值替換成遮罩或移除。本章聚焦 redaction 的策略面 — 哪些資訊需要保護、保護的判斷依據和適用範圍。SDK 的 API 實作細節（初始化方式、helper 函式設計、和 flush 管線的整合）見 <a href="/blog/monitoring/03-sdk-design/redaction-helper/" data-link-title="SDK redaction helper" data-link-desc="在事件離開 SDK 前移除敏感資訊 — 預設 redaction rule 處理常見 pattern，自訂 rule 處理業務特定的 secret">SDK redaction helper</a>。Redaction 在 SDK 端執行的設計原則是「敏感資料不離開 client」— 一旦資料送到 collector，即使 collector 有 access control，資料已經在網路上傳輸過，多了一層洩漏面。</p>
<h2 id="預設-redaction-rule">預設 Redaction Rule</h2>
<p>SDK 內建的 redaction rule 覆蓋最常見的敏感欄位模式。開發者不需要設定就能獲得基本保護。</p>
<h3 id="欄位名稱比對">欄位名稱比對</h3>
<p>以下欄位名稱（不分大小寫）的值自動替換為 <code>[REDACTED]</code>：</p>
<ul>
<li><code>password</code>、<code>passwd</code>、<code>secret</code>、<code>token</code>、<code>api_key</code>、<code>apiKey</code></li>
<li><code>authorization</code>、<code>auth</code>、<code>credential</code></li>
<li><code>ssn</code>、<code>social_security</code></li>
<li><code>credit_card</code>、<code>card_number</code>、<code>cvv</code>、<code>cvc</code></li>
</ul>
<p>欄位名稱比對用 substring match — <code>user_password</code> 包含 <code>password</code> 會被 redact，<code>password_reset_token</code> 包含 <code>password</code> 和 <code>token</code> 也會。</p>
<h3 id="值格式比對">值格式比對</h3>
<p>以下格式的值無論欄位名稱為何都自動替換：</p>
<ul>
<li>Email 地址格式（<code>user@domain.com</code> → <code>u***@domain.com</code>）</li>
<li>信用卡號碼格式（連續 13-19 位數字 → 保留末四碼）</li>
<li>Bearer token 格式（<code>Bearer xxx</code> → <code>Bearer [REDACTED]</code>）</li>
</ul>
<p>值格式比對用正則表達式。正則的效能影響在大量事件時需要注意 — 預設 rule 的正則保持簡單，避免 catastrophic backtracking。</p>
<h2 id="自訂-pattern">自訂 Pattern</h2>
<p>應用可能有自己的 secret 格式，預設 rule 覆蓋不到。SDK 提供 API 讓開發者註冊自訂 redaction pattern。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">monitor.addRedactionRule(
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  name: &#39;internal-api-key&#39;,
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  pattern: RegExp(r&#39;sk_live_[a-zA-Z0-9]{24}&#39;),
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  replacement: &#39;[REDACTED:api-key]&#39;,
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">monitor.addRedactionRule(
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  name: &#39;database-url&#39;,
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  fieldNames: [&#39;database_url&#39;, &#39;db_url&#39;, &#39;connection_string&#39;],
</span></span><span class="line"><span class="ln">10</span><span class="cl">  replacement: &#39;[REDACTED:db-url]&#39;,
</span></span><span class="line"><span class="ln">11</span><span class="cl">)</span></span></code></pre></div><p>自訂 pattern 的設計考量：</p>
<p><strong>Pattern 在 init 時註冊</strong>。Redaction rule 在 SDK 初始化時設定，之後所有事件都通過這些 rule。不支援動態修改 — 避免「中途加 rule 導致之前的事件沒被 redact」的困惑。</p>
<p><strong>Pattern 順序無關</strong>。所有 rule 獨立執行，不依賴順序。一個欄位可以匹配多個 rule，以第一個匹配的 replacement 為準。</p>
<p><strong>Replacement 可以保留部分資訊</strong>。<code>[REDACTED]</code> 完全遮蔽，<code>[REDACTED:api-key]</code> 保留類型資訊，<code>u***@domain.com</code> 保留結構。保留類型資訊對 debug 有幫助 — 看到 <code>[REDACTED:api-key]</code> 至少知道這裡原本有一個 API key。</p>
<h2 id="redaction-的適用範圍">Redaction 的適用範圍</h2>
<p>Redaction 應用在 SDK 送出事件前的最後一步 — 在序列化（JSON encode）之前。適用範圍包括：</p>
<ul>
<li>Event 的 data 欄位（自由欄位，開發者可能放入任何內容）</li>
<li>Error 的 stack trace（檔案路徑可能包含使用者名稱或部署路徑）</li>
<li>Error 的 message（例外訊息可能包含 query string 或參數值）</li>
<li>Lifecycle 的 metadata（連線 URL 可能包含認證資訊）</li>
</ul>
<p>Redaction 不應用在 SDK 的內部欄位（timestamp、event type、session ID）— 這些是 SDK 自己產生的，不包含使用者資料。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>資料離開 client 後的保護 → <a href="/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全</a></li>
<li>去識別化策略 → <a href="/blog/monitoring/07-security-privacy/anonymization-strategy/" data-link-title="去識別化策略" data-link-desc="IP 截斷 / user agent 簡化 / stack trace 路徑清理 / session UUID — 四種去識別化技術的適用場景和實作方式">去識別化策略</a></li>
<li>IME 個人化學習的 secret 洩漏風險 → <a href="/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">ux-design 模組三 IME 安全 checklist</a></li>
</ul>
]]></content:encoded></item><item><title>Transport 安全</title><link>https://tarrragon.github.io/blog/monitoring/07-security-privacy/transport-security/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/07-security-privacy/transport-security/</guid><description>&lt;p>Transport 安全保護監控資料在從 SDK 傳送到 collector 的過程中不被竊聽或篡改。即使 SDK 端做了 redaction，傳輸中的資料仍然包含使用者行為、系統狀態、error 訊息等有價值的資訊 — 這些資訊在未加密的傳輸中可以被同網段的任何人攔截。&lt;/p>
&lt;h2 id="同區網也要加密的理由">同區網也要加密的理由&lt;/h2>
&lt;p>自用工具的 SDK 和 collector 通常在同一台機器或同一個區域網路（LAN / Tailscale tailnet）。常見的假設是「同區網不需要加密，因為只有我自己在用」。&lt;/p>
&lt;p>這個假設在以下情境不成立：&lt;/p>
&lt;p>&lt;strong>共用網路&lt;/strong>：咖啡廳、共享辦公室、飯店 WiFi — 同一個 AP 下的其他裝置可以用 ARP spoofing 或 WiFi sniffing 攔截未加密的 HTTP 流量。&lt;/p>
&lt;p>&lt;strong>未來的網路拓撲變更&lt;/strong>：目前在同一台機器上的 SDK 和 collector，可能之後拆到不同的機器或不同的網路段。如果一開始就用 HTTPS，拓撲變更不需要額外的安全調整。&lt;/p>
&lt;p>&lt;strong>養成正確習慣&lt;/strong>：在自用工具上用 HTTP 是因為「反正只有我」，但相同的開發者在商業專案中可能延續這個習慣。從自用工具開始就用 HTTPS，讓加密傳輸成為預設行為。&lt;/p>
&lt;h2 id="https-設定">HTTPS 設定&lt;/h2>
&lt;h3 id="自簽憑證">自簽憑證&lt;/h3>
&lt;p>自用工具和內部服務用自簽憑證（self-signed certificate）就足夠。不需要購買 CA 憑證 — 自簽憑證提供加密（防竊聽）和完整性（防篡改），只是不提供身份驗證（client 無法確認 server 是不是「官方的」）。在自用場景中 server 就是自己架的，身份驗證不是問題。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days &lt;span class="m">365&lt;/span> -nodes&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Go collector 使用自簽憑證：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServeTLS&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;:8443&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;cert.pem&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;key.pem&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">handler&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SDK 端需要信任自簽憑證。開發期可以在 HTTP client 設定 &lt;code>badCertificateCallback&lt;/code> 接受自簽憑證；production 應該把自簽憑證加入系統的信任清單。&lt;/p>
&lt;h3 id="lets-encrypt">Let&amp;rsquo;s Encrypt&lt;/h3>
&lt;p>如果 collector 有公開的 domain name，用 Let&amp;rsquo;s Encrypt 取得免費的 CA 憑證。自動續期、不需要手動管理。適合部署在 VPS 或雲端的 collector。&lt;/p>
&lt;h2 id="basic-auth">Basic Auth&lt;/h2>
&lt;p>HTTPS 保護傳輸層（防竊聽），basic auth 保護 endpoint 層（防未授權存取）。兩者互補，缺一不可 — basic auth 在 HTTP 上傳送的是 base64 編碼的帳密，沒有 HTTPS 的加密保護等於明文傳送。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">Authorization: Basic base64(username:password)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SDK 在每個 HTTP POST request 的 header 中帶上 basic auth。Collector 端驗證帳密，不匹配則回傳 401。&lt;/p>
&lt;p>Basic auth 的帳密管理：&lt;/p>
&lt;ul>
&lt;li>帳密存在 SDK 的設定檔或環境變數中，不硬編碼在程式碼裡&lt;/li>
&lt;li>Collector 端的帳密用 bcrypt hash 儲存，不存明文&lt;/li>
&lt;li>定期輪替帳密（自用工具半年到一年一次即可）&lt;/li>
&lt;/ul>
&lt;h2 id="api-key-替代方案">API Key 替代方案&lt;/h2>
&lt;p>如果不需要 username/password 的雙因素，單一 API key 更簡單。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">X-API-Key: sk_monitor_abc123...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>API key 的管理比 basic auth 簡單（一個字串而非帳密對），但安全性略低（只有一個 factor）。自用工具場景下 API key 通常足夠。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>SDK 端的 redaction → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/sdk-redaction-api/" data-link-title="SDK Redaction API 設計" data-link-desc="預設 redaction rule 過濾已知敏感欄位、自訂 pattern 擴展應用特有的 secret 格式 — redaction 在 SDK 端執行，敏感資料不離開 client">SDK Redaction API 設計&lt;/a>&lt;/li>
&lt;li>Collector 端的 access control → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control 實作&lt;/a>&lt;/li>
&lt;li>Server-side 的 secret management → &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">backend 07 資安&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Transport 安全保護監控資料在從 SDK 傳送到 collector 的過程中不被竊聽或篡改。即使 SDK 端做了 redaction，傳輸中的資料仍然包含使用者行為、系統狀態、error 訊息等有價值的資訊 — 這些資訊在未加密的傳輸中可以被同網段的任何人攔截。</p>
<h2 id="同區網也要加密的理由">同區網也要加密的理由</h2>
<p>自用工具的 SDK 和 collector 通常在同一台機器或同一個區域網路（LAN / Tailscale tailnet）。常見的假設是「同區網不需要加密，因為只有我自己在用」。</p>
<p>這個假設在以下情境不成立：</p>
<p><strong>共用網路</strong>：咖啡廳、共享辦公室、飯店 WiFi — 同一個 AP 下的其他裝置可以用 ARP spoofing 或 WiFi sniffing 攔截未加密的 HTTP 流量。</p>
<p><strong>未來的網路拓撲變更</strong>：目前在同一台機器上的 SDK 和 collector，可能之後拆到不同的機器或不同的網路段。如果一開始就用 HTTPS，拓撲變更不需要額外的安全調整。</p>
<p><strong>養成正確習慣</strong>：在自用工具上用 HTTP 是因為「反正只有我」，但相同的開發者在商業專案中可能延續這個習慣。從自用工具開始就用 HTTPS，讓加密傳輸成為預設行為。</p>
<h2 id="https-設定">HTTPS 設定</h2>
<h3 id="自簽憑證">自簽憑證</h3>
<p>自用工具和內部服務用自簽憑證（self-signed certificate）就足夠。不需要購買 CA 憑證 — 自簽憑證提供加密（防竊聽）和完整性（防篡改），只是不提供身份驗證（client 無法確認 server 是不是「官方的」）。在自用場景中 server 就是自己架的，身份驗證不是問題。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days <span class="m">365</span> -nodes</span></span></code></pre></div><p>Go collector 使用自簽憑證：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServeTLS</span><span class="p">(</span><span class="s">&#34;:8443&#34;</span><span class="p">,</span> <span class="s">&#34;cert.pem&#34;</span><span class="p">,</span> <span class="s">&#34;key.pem&#34;</span><span class="p">,</span> <span class="nx">handler</span><span class="p">)</span></span></span></code></pre></div><p>SDK 端需要信任自簽憑證。開發期可以在 HTTP client 設定 <code>badCertificateCallback</code> 接受自簽憑證；production 應該把自簽憑證加入系統的信任清單。</p>
<h3 id="lets-encrypt">Let&rsquo;s Encrypt</h3>
<p>如果 collector 有公開的 domain name，用 Let&rsquo;s Encrypt 取得免費的 CA 憑證。自動續期、不需要手動管理。適合部署在 VPS 或雲端的 collector。</p>
<h2 id="basic-auth">Basic Auth</h2>
<p>HTTPS 保護傳輸層（防竊聽），basic auth 保護 endpoint 層（防未授權存取）。兩者互補，缺一不可 — basic auth 在 HTTP 上傳送的是 base64 編碼的帳密，沒有 HTTPS 的加密保護等於明文傳送。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Authorization: Basic base64(username:password)</span></span></code></pre></div><p>SDK 在每個 HTTP POST request 的 header 中帶上 basic auth。Collector 端驗證帳密，不匹配則回傳 401。</p>
<p>Basic auth 的帳密管理：</p>
<ul>
<li>帳密存在 SDK 的設定檔或環境變數中，不硬編碼在程式碼裡</li>
<li>Collector 端的帳密用 bcrypt hash 儲存，不存明文</li>
<li>定期輪替帳密（自用工具半年到一年一次即可）</li>
</ul>
<h2 id="api-key-替代方案">API Key 替代方案</h2>
<p>如果不需要 username/password 的雙因素，單一 API key 更簡單。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">X-API-Key: sk_monitor_abc123...</span></span></code></pre></div><p>API key 的管理比 basic auth 簡單（一個字串而非帳密對），但安全性略低（只有一個 factor）。自用工具場景下 API key 通常足夠。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>SDK 端的 redaction → <a href="/blog/monitoring/07-security-privacy/sdk-redaction-api/" data-link-title="SDK Redaction API 設計" data-link-desc="預設 redaction rule 過濾已知敏感欄位、自訂 pattern 擴展應用特有的 secret 格式 — redaction 在 SDK 端執行，敏感資料不離開 client">SDK Redaction API 設計</a></li>
<li>Collector 端的 access control → <a href="/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control 實作</a></li>
<li>Server-side 的 secret management → <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">backend 07 資安</a></li>
</ul>
]]></content:encoded></item><item><title>Collector Access Control 實作</title><link>https://tarrragon.github.io/blog/monitoring/07-security-privacy/collector-access-control/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/07-security-privacy/collector-access-control/</guid><description>&lt;p>Collector access control 管理「誰可以對 collector 做什麼操作」。三層控制各自回答不同的問題：認證回答「來源是誰」，授權回答「這個來源被允許做什麼」，access log 回答「誰在什麼時候實際做了什麼」。&lt;/p>
&lt;h2 id="認證來源是誰">認證：來源是誰&lt;/h2>
&lt;p>認證驗證送出資料的 client 是否合法。未認證的 request 應該被拒絕，避免任意來源向 collector 寫入資料。&lt;/p>
&lt;h3 id="api-key-認證">API Key 認證&lt;/h3>
&lt;p>每個合法的 SDK client 有一個 API key。Collector 檢查 request header 中的 API key 是否在合法清單中。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">authMiddleware&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">next&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Handler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Handler&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandlerFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="nx">key&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;X-API-Key&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nf">isValidKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;unauthorized&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusUnauthorized&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="nx">next&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ServeHTTP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>自用工具場景下，一個 API key 對應一個 client 通常就足夠。多個 client（例如同一個 app 的 iOS 和 Android 版本）可以用同一個 key，或每個平台一個 key 以便在 access log 中區分來源。&lt;/p>
&lt;h3 id="mtlsmutual-tls">mTLS（Mutual TLS）&lt;/h3>
&lt;p>Client 和 server 互相驗證對方的憑證。安全性比 API key 高 — 攻擊者即使取得 API key，沒有 client 憑證也無法連線。&lt;/p>
&lt;p>mTLS 的設定成本較高（每個 client 需要產生和管理憑證），適合對安全性要求較高的環境。自用工具通常不需要 mTLS。&lt;/p>
&lt;h2 id="授權允許做什麼">授權：允許做什麼&lt;/h2>
&lt;p>授權控制已認證的 client 可以執行哪些操作。Collector 的操作通常分為兩類：寫入事件和查詢事件。&lt;/p>
&lt;h3 id="角色分離">角色分離&lt;/h3>
&lt;p>最簡單的授權模型是兩個角色：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Writer&lt;/strong>：只能寫入事件（POST /events）。SDK client 使用這個角色。&lt;/li>
&lt;li>&lt;strong>Reader&lt;/strong>：只能查詢事件（GET /events、GET /query）。開發者的 CLI 工具使用這個角色。&lt;/li>
&lt;/ul>
&lt;p>角色分離的價值在於限制洩漏的影響範圍。如果 SDK 的 API key 被洩漏，攻擊者只能寫入（產生垃圾事件），不能讀取（看到歷史事件中的敏感資訊）。&lt;/p>
&lt;h3 id="寫入限制">寫入限制&lt;/h3>
&lt;p>即使認證通過、角色正確，collector 也可以對寫入加上限制：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Rate limit&lt;/strong>：每個 API key 每分鐘最多 N 個 request。防止 client 端 bug 導致事件風暴。&lt;/li>
&lt;li>&lt;strong>Payload size limit&lt;/strong>：每個事件最大 M KB。防止異常大的 event data 消耗儲存。&lt;/li>
&lt;li>&lt;strong>Schema validation&lt;/strong>：事件必須符合定義的 JSON schema。格式不正確的事件拒絕存入。&lt;/li>
&lt;/ul>
&lt;h2 id="access-log誰做了什麼">Access Log：誰做了什麼&lt;/h2>
&lt;p>Access log 記錄每個到達 collector 的 request — 來源 IP、API key（或 key 的 hash）、操作類型、時間戳、response status。&lt;/p>
&lt;p>Access log 的用途：&lt;/p>
&lt;p>&lt;strong>安全審計&lt;/strong>：發現異常行為 — 未知 IP 的大量寫入、非工作時間的讀取、連續的認證失敗。&lt;/p>
&lt;p>&lt;strong>問題排查&lt;/strong>：SDK 說事件送出成功但 collector 沒有收到 — access log 可以確認 request 是否到達、response 是什麼。&lt;/p>
&lt;p>&lt;strong>用量統計&lt;/strong>：每個 client 送了多少事件、佔多少儲存。&lt;/p>
&lt;p>Access log 本身也是監控資料，但和業務事件分開儲存。Access log 存在 collector 本機的 log 檔中，用系統的 logrotate 管理輪替。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">2026-06-19T10:30:00Z POST /events key=sk_mon_ab...cd ip=192.168.1.50 status=200 size=1234
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">2026-06-19T10:30:01Z POST /events key=INVALID ip=10.0.0.99 status=401 size=0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">2026-06-19T10:31:00Z GET /query key=sk_read_ef...gh ip=192.168.1.1 status=200 size=8901&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>SDK 端的 redaction → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/sdk-redaction-api/" data-link-title="SDK Redaction API 設計" data-link-desc="預設 redaction rule 過濾已知敏感欄位、自訂 pattern 擴展應用特有的 secret 格式 — redaction 在 SDK 端執行，敏感資料不離開 client">SDK Redaction API 設計&lt;/a>&lt;/li>
&lt;li>Transport 層的加密 → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全&lt;/a>&lt;/li>
&lt;li>資料儲存後的去識別化 → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/anonymization-strategy/" data-link-title="去識別化策略" data-link-desc="IP 截斷 / user agent 簡化 / stack trace 路徑清理 / session UUID — 四種去識別化技術的適用場景和實作方式">去識別化策略&lt;/a>&lt;/li>
&lt;li>Client-side credential 暴露的根本限制 → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/client-sdk-authentication/" data-link-title="Client-side SDK 認證的根本限制" data-link-desc="嵌在 client 端的 credential 必然可被提取 — 認清 architecture 天花板後的多層緩解策略，從 origin 驗證到 device attestation">Client-side SDK 認證&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Collector access control 管理「誰可以對 collector 做什麼操作」。三層控制各自回答不同的問題：認證回答「來源是誰」，授權回答「這個來源被允許做什麼」，access log 回答「誰在什麼時候實際做了什麼」。</p>
<h2 id="認證來源是誰">認證：來源是誰</h2>
<p>認證驗證送出資料的 client 是否合法。未認證的 request 應該被拒絕，避免任意來源向 collector 寫入資料。</p>
<h3 id="api-key-認證">API Key 認證</h3>
<p>每個合法的 SDK client 有一個 API key。Collector 檢查 request header 中的 API key 是否在合法清單中。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="nf">authMiddleware</span><span class="p">(</span><span class="nx">next</span> <span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="p">)</span> <span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">return</span> <span class="nx">http</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="nx">key</span> <span class="o">:=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;X-API-Key&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="k">if</span> <span class="p">!</span><span class="nf">isValidKey</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            <span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="s">&#34;unauthorized&#34;</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusUnauthorized</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="nx">next</span><span class="p">.</span><span class="nf">ServeHTTP</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">r</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>自用工具場景下，一個 API key 對應一個 client 通常就足夠。多個 client（例如同一個 app 的 iOS 和 Android 版本）可以用同一個 key，或每個平台一個 key 以便在 access log 中區分來源。</p>
<h3 id="mtlsmutual-tls">mTLS（Mutual TLS）</h3>
<p>Client 和 server 互相驗證對方的憑證。安全性比 API key 高 — 攻擊者即使取得 API key，沒有 client 憑證也無法連線。</p>
<p>mTLS 的設定成本較高（每個 client 需要產生和管理憑證），適合對安全性要求較高的環境。自用工具通常不需要 mTLS。</p>
<h2 id="授權允許做什麼">授權：允許做什麼</h2>
<p>授權控制已認證的 client 可以執行哪些操作。Collector 的操作通常分為兩類：寫入事件和查詢事件。</p>
<h3 id="角色分離">角色分離</h3>
<p>最簡單的授權模型是兩個角色：</p>
<ul>
<li><strong>Writer</strong>：只能寫入事件（POST /events）。SDK client 使用這個角色。</li>
<li><strong>Reader</strong>：只能查詢事件（GET /events、GET /query）。開發者的 CLI 工具使用這個角色。</li>
</ul>
<p>角色分離的價值在於限制洩漏的影響範圍。如果 SDK 的 API key 被洩漏，攻擊者只能寫入（產生垃圾事件），不能讀取（看到歷史事件中的敏感資訊）。</p>
<h3 id="寫入限制">寫入限制</h3>
<p>即使認證通過、角色正確，collector 也可以對寫入加上限制：</p>
<ul>
<li><strong>Rate limit</strong>：每個 API key 每分鐘最多 N 個 request。防止 client 端 bug 導致事件風暴。</li>
<li><strong>Payload size limit</strong>：每個事件最大 M KB。防止異常大的 event data 消耗儲存。</li>
<li><strong>Schema validation</strong>：事件必須符合定義的 JSON schema。格式不正確的事件拒絕存入。</li>
</ul>
<h2 id="access-log誰做了什麼">Access Log：誰做了什麼</h2>
<p>Access log 記錄每個到達 collector 的 request — 來源 IP、API key（或 key 的 hash）、操作類型、時間戳、response status。</p>
<p>Access log 的用途：</p>
<p><strong>安全審計</strong>：發現異常行為 — 未知 IP 的大量寫入、非工作時間的讀取、連續的認證失敗。</p>
<p><strong>問題排查</strong>：SDK 說事件送出成功但 collector 沒有收到 — access log 可以確認 request 是否到達、response 是什麼。</p>
<p><strong>用量統計</strong>：每個 client 送了多少事件、佔多少儲存。</p>
<p>Access log 本身也是監控資料，但和業務事件分開儲存。Access log 存在 collector 本機的 log 檔中，用系統的 logrotate 管理輪替。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">2026-06-19T10:30:00Z POST /events key=sk_mon_ab...cd ip=192.168.1.50 status=200 size=1234
</span></span><span class="line"><span class="ln">2</span><span class="cl">2026-06-19T10:30:01Z POST /events key=INVALID ip=10.0.0.99 status=401 size=0
</span></span><span class="line"><span class="ln">3</span><span class="cl">2026-06-19T10:31:00Z GET /query key=sk_read_ef...gh ip=192.168.1.1 status=200 size=8901</span></span></code></pre></div><h2 id="下一步路由">下一步路由</h2>
<ul>
<li>SDK 端的 redaction → <a href="/blog/monitoring/07-security-privacy/sdk-redaction-api/" data-link-title="SDK Redaction API 設計" data-link-desc="預設 redaction rule 過濾已知敏感欄位、自訂 pattern 擴展應用特有的 secret 格式 — redaction 在 SDK 端執行，敏感資料不離開 client">SDK Redaction API 設計</a></li>
<li>Transport 層的加密 → <a href="/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全</a></li>
<li>資料儲存後的去識別化 → <a href="/blog/monitoring/07-security-privacy/anonymization-strategy/" data-link-title="去識別化策略" data-link-desc="IP 截斷 / user agent 簡化 / stack trace 路徑清理 / session UUID — 四種去識別化技術的適用場景和實作方式">去識別化策略</a></li>
<li>Client-side credential 暴露的根本限制 → <a href="/blog/monitoring/07-security-privacy/client-sdk-authentication/" data-link-title="Client-side SDK 認證的根本限制" data-link-desc="嵌在 client 端的 credential 必然可被提取 — 認清 architecture 天花板後的多層緩解策略，從 origin 驗證到 device attestation">Client-side SDK 認證</a></li>
</ul>
]]></content:encoded></item><item><title>去識別化策略</title><link>https://tarrragon.github.io/blog/monitoring/07-security-privacy/anonymization-strategy/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/07-security-privacy/anonymization-strategy/</guid><description>&lt;p>去識別化是把監控資料中可以關聯到特定個人的欄位，轉換成無法回溯到個人但仍保留分析價值的形式。去識別化和 &lt;a href="https://tarrragon.github.io/blog/monitoring/knowledge-cards/redaction/" data-link-title="Redaction" data-link-desc="說明在事件資料離開 client 之前把敏感欄位的值替換成遮罩或移除的機制">redaction&lt;/a> 的差別在於：redaction 完全移除資訊（&lt;code>[REDACTED]&lt;/code>），去識別化保留結構化的資訊但移除可識別性。&lt;/p>
&lt;h2 id="ip-截斷">IP 截斷&lt;/h2>
&lt;p>IP 位址是最常見的個人識別欄位。完整的 IPv4 位址（&lt;code>192.168.1.50&lt;/code>）可以定位到特定的網路和裝置；截斷後的 IP（&lt;code>192.168.1.0&lt;/code>）保留網段資訊但無法定位到特定裝置。&lt;/p>
&lt;h3 id="截斷策略">截斷策略&lt;/h3>
&lt;p>&lt;strong>IPv4 末八位清零&lt;/strong>：&lt;code>192.168.1.50&lt;/code> → &lt;code>192.168.1.0&lt;/code>。保留 /24 網段資訊，足以判斷「使用者在哪個網段」但無法定位到特定裝置。Google Analytics 採用這個策略。&lt;/p>
&lt;p>&lt;strong>IPv4 末十六位清零&lt;/strong>：&lt;code>192.168.1.50&lt;/code> → &lt;code>192.168.0.0&lt;/code>。更強的去識別化，但地理定位精度降低到城市級。&lt;/p>
&lt;p>&lt;strong>IPv6&lt;/strong>：截斷更多位元。IPv6 的後 80 位通常包含 MAC 位址衍生的 interface ID — 截斷到 /48 前綴保留 ISP 資訊，移除裝置識別。&lt;/p>
&lt;h3 id="實作位置">實作位置&lt;/h3>
&lt;p>IP 截斷應在 collector 收到事件後、寫入儲存前執行。SDK 端不做 IP 截斷 — SDK 通常不知道自己的外部 IP（知道的是 NAT 後的內部 IP），外部 IP 是 collector 從 HTTP request 的 source IP 取得的。&lt;/p>
&lt;h2 id="user-agent-簡化">User Agent 簡化&lt;/h2>
&lt;p>User agent 字串包含瀏覽器版本、OS 版本、裝置型號 — 組合起來可能形成唯一的 fingerprint。簡化 user agent 保留有用的分類資訊（「iOS 17 上的 Safari」），移除可用於 fingerprinting 的細節（「iPhone 15 Pro Max, Build/22A3354」）。&lt;/p>
&lt;h3 id="簡化規則">簡化規則&lt;/h3>
&lt;p>保留：平台（iOS / Android / Windows / macOS）、主要版本號（iOS 17、Android 14）、瀏覽器類型（Safari / Chrome / Firefox）。&lt;/p>
&lt;p>移除：minor version、build number、裝置型號、CPU 架構、語言設定。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">原始：Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">簡化：iOS/17 Safari&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="stack-trace-路徑清理">Stack Trace 路徑清理&lt;/h2>
&lt;p>Error 事件的 stack trace 包含檔案路徑。檔案路徑可能洩漏部署結構（&lt;code>/home/deploy_user/app/v2.3.1/src/...&lt;/code>）或開發者的個人資訊（&lt;code>/Users/alice/projects/...&lt;/code>）。&lt;/p>
&lt;h3 id="清理規則">清理規則&lt;/h3>
&lt;p>&lt;strong>移除使用者目錄前綴&lt;/strong>：&lt;code>/Users/alice/projects/app/src/main.dart:42&lt;/code> → &lt;code>src/main.dart:42&lt;/code>。保留 source file 相對路徑和行號，移除使用者名稱。&lt;/p>
&lt;p>&lt;strong>移除部署路徑前綴&lt;/strong>：&lt;code>/opt/deploy/releases/20260619/app/lib/...&lt;/code> → &lt;code>lib/...&lt;/code>。保留程式碼結構，移除部署細節。&lt;/p>
&lt;p>&lt;strong>統一 path separator&lt;/strong>：Windows 路徑（&lt;code>C:\Users\...&lt;/code>）和 Unix 路徑（&lt;code>/home/...&lt;/code>）統一處理。&lt;/p>
&lt;p>清理規則用正則表達式匹配常見的路徑前綴模式，替換為空字串。自訂的部署路徑格式需要在 collector 設定中額外註冊。&lt;/p>
&lt;h2 id="session-uuid">Session UUID&lt;/h2>
&lt;p>Session ID 用於關聯同一次使用中的多個事件。UUID v4（隨機產生）作為 session ID，沒有可預測性、沒有順序性、無法回推使用者身份。&lt;/p>
&lt;h3 id="session-id-的生命週期">Session ID 的生命週期&lt;/h3>
&lt;p>SDK 在初始化時產生一個 UUID v4 作為 session ID，所有事件附帶這個 ID。App 重新啟動時產生新的 session ID — 前後兩次使用的事件無法關聯。&lt;/p>
&lt;p>這個設計讓分析粒度限制在「一次使用」而非「一個使用者」。如果需要跨 session 關聯（例如計算 DAU），需要另一個 persistent ID — 但 persistent ID 本身就是可識別資訊，需要使用者同意。&lt;/p>
&lt;h3 id="避免使用可識別的-id">避免使用可識別的 ID&lt;/h3>
&lt;p>裝置 ID（IDFA / GAID）、安裝 ID、使用者帳號 — 這些可以關聯到特定個人，不適合作為監控系統的 session ID。使用 UUID v4 確保 session ID 的唯一性來自隨機性而非身份。&lt;/p>
&lt;p>去識別化是資料保護的一環，另一環是在資料離開 client 之前就處理 — &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/sdk-redaction-api/" data-link-title="SDK Redaction API 設計" data-link-desc="預設 redaction rule 過濾已知敏感欄位、自訂 pattern 擴展應用特有的 secret 格式 — redaction 在 SDK 端執行，敏感資料不離開 client">SDK Redaction API 設計&lt;/a>從 SDK 端攔截敏感欄位。法規層面的具體要求見 &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/gdpr-minimization/" data-link-title="GDPR 最小化原則的工程落地" data-link-desc="資料最小化、目的限制、儲存限制 — GDPR 三個核心原則在監控系統的工程實作方式">GDPR 最小化原則的工程落地&lt;/a>。去識別化完成後的資料才能用於&lt;a href="https://tarrragon.github.io/blog/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">行為分析&lt;/a> — 這是商業利用的入場條件。&lt;/p></description><content:encoded><![CDATA[<p>去識別化是把監控資料中可以關聯到特定個人的欄位，轉換成無法回溯到個人但仍保留分析價值的形式。去識別化和 <a href="/blog/monitoring/knowledge-cards/redaction/" data-link-title="Redaction" data-link-desc="說明在事件資料離開 client 之前把敏感欄位的值替換成遮罩或移除的機制">redaction</a> 的差別在於：redaction 完全移除資訊（<code>[REDACTED]</code>），去識別化保留結構化的資訊但移除可識別性。</p>
<h2 id="ip-截斷">IP 截斷</h2>
<p>IP 位址是最常見的個人識別欄位。完整的 IPv4 位址（<code>192.168.1.50</code>）可以定位到特定的網路和裝置；截斷後的 IP（<code>192.168.1.0</code>）保留網段資訊但無法定位到特定裝置。</p>
<h3 id="截斷策略">截斷策略</h3>
<p><strong>IPv4 末八位清零</strong>：<code>192.168.1.50</code> → <code>192.168.1.0</code>。保留 /24 網段資訊，足以判斷「使用者在哪個網段」但無法定位到特定裝置。Google Analytics 採用這個策略。</p>
<p><strong>IPv4 末十六位清零</strong>：<code>192.168.1.50</code> → <code>192.168.0.0</code>。更強的去識別化，但地理定位精度降低到城市級。</p>
<p><strong>IPv6</strong>：截斷更多位元。IPv6 的後 80 位通常包含 MAC 位址衍生的 interface ID — 截斷到 /48 前綴保留 ISP 資訊，移除裝置識別。</p>
<h3 id="實作位置">實作位置</h3>
<p>IP 截斷應在 collector 收到事件後、寫入儲存前執行。SDK 端不做 IP 截斷 — SDK 通常不知道自己的外部 IP（知道的是 NAT 後的內部 IP），外部 IP 是 collector 從 HTTP request 的 source IP 取得的。</p>
<h2 id="user-agent-簡化">User Agent 簡化</h2>
<p>User agent 字串包含瀏覽器版本、OS 版本、裝置型號 — 組合起來可能形成唯一的 fingerprint。簡化 user agent 保留有用的分類資訊（「iOS 17 上的 Safari」），移除可用於 fingerprinting 的細節（「iPhone 15 Pro Max, Build/22A3354」）。</p>
<h3 id="簡化規則">簡化規則</h3>
<p>保留：平台（iOS / Android / Windows / macOS）、主要版本號（iOS 17、Android 14）、瀏覽器類型（Safari / Chrome / Firefox）。</p>
<p>移除：minor version、build number、裝置型號、CPU 架構、語言設定。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">原始：Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X)
</span></span><span class="line"><span class="ln">2</span><span class="cl">簡化：iOS/17 Safari</span></span></code></pre></div><h2 id="stack-trace-路徑清理">Stack Trace 路徑清理</h2>
<p>Error 事件的 stack trace 包含檔案路徑。檔案路徑可能洩漏部署結構（<code>/home/deploy_user/app/v2.3.1/src/...</code>）或開發者的個人資訊（<code>/Users/alice/projects/...</code>）。</p>
<h3 id="清理規則">清理規則</h3>
<p><strong>移除使用者目錄前綴</strong>：<code>/Users/alice/projects/app/src/main.dart:42</code> → <code>src/main.dart:42</code>。保留 source file 相對路徑和行號，移除使用者名稱。</p>
<p><strong>移除部署路徑前綴</strong>：<code>/opt/deploy/releases/20260619/app/lib/...</code> → <code>lib/...</code>。保留程式碼結構，移除部署細節。</p>
<p><strong>統一 path separator</strong>：Windows 路徑（<code>C:\Users\...</code>）和 Unix 路徑（<code>/home/...</code>）統一處理。</p>
<p>清理規則用正則表達式匹配常見的路徑前綴模式，替換為空字串。自訂的部署路徑格式需要在 collector 設定中額外註冊。</p>
<h2 id="session-uuid">Session UUID</h2>
<p>Session ID 用於關聯同一次使用中的多個事件。UUID v4（隨機產生）作為 session ID，沒有可預測性、沒有順序性、無法回推使用者身份。</p>
<h3 id="session-id-的生命週期">Session ID 的生命週期</h3>
<p>SDK 在初始化時產生一個 UUID v4 作為 session ID，所有事件附帶這個 ID。App 重新啟動時產生新的 session ID — 前後兩次使用的事件無法關聯。</p>
<p>這個設計讓分析粒度限制在「一次使用」而非「一個使用者」。如果需要跨 session 關聯（例如計算 DAU），需要另一個 persistent ID — 但 persistent ID 本身就是可識別資訊，需要使用者同意。</p>
<h3 id="避免使用可識別的-id">避免使用可識別的 ID</h3>
<p>裝置 ID（IDFA / GAID）、安裝 ID、使用者帳號 — 這些可以關聯到特定個人，不適合作為監控系統的 session ID。使用 UUID v4 確保 session ID 的唯一性來自隨機性而非身份。</p>
<p>去識別化是資料保護的一環，另一環是在資料離開 client 之前就處理 — <a href="/blog/monitoring/07-security-privacy/sdk-redaction-api/" data-link-title="SDK Redaction API 設計" data-link-desc="預設 redaction rule 過濾已知敏感欄位、自訂 pattern 擴展應用特有的 secret 格式 — redaction 在 SDK 端執行，敏感資料不離開 client">SDK Redaction API 設計</a>從 SDK 端攔截敏感欄位。法規層面的具體要求見 <a href="/blog/monitoring/07-security-privacy/gdpr-minimization/" data-link-title="GDPR 最小化原則的工程落地" data-link-desc="資料最小化、目的限制、儲存限制 — GDPR 三個核心原則在監控系統的工程實作方式">GDPR 最小化原則的工程落地</a>。去識別化完成後的資料才能用於<a href="/blog/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">行為分析</a> — 這是商業利用的入場條件。</p>
]]></content:encoded></item><item><title>GDPR 最小化原則的工程落地</title><link>https://tarrragon.github.io/blog/monitoring/07-security-privacy/gdpr-minimization/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/07-security-privacy/gdpr-minimization/</guid><description>&lt;p>GDPR 的資料最小化原則要求「只收集達成特定目的所需的最少資料」。這個法律原則轉譯到監控系統的工程實作，影響三個設計決策：收集什麼欄位、保留多久、誰可以存取。&lt;/p>
&lt;h2 id="資料最小化只收集需要的欄位">資料最小化：只收集需要的欄位&lt;/h2>
&lt;p>資料最小化的工程落地是「每個收集的欄位都要能回答：這個欄位用來做什麼決策？」。如果一個欄位只是「可能有用」但沒有明確的消費場景，就不應該收集。&lt;/p>
&lt;h3 id="正面表列-vs-負面排除">正面表列 vs 負面排除&lt;/h3>
&lt;p>正面表列（allowlist）是列出「收集哪些欄位」— 只收集清單上的欄位，其他全部不收。&lt;/p>
&lt;p>負面排除（denylist）是列出「不收集哪些欄位」— 預設收集所有欄位，排除清單上的。&lt;/p>
&lt;p>GDPR 的精神更接近正面表列 — 每個收集行為需要有正當理由（lawful basis）。工程上的實作方式是：事件 schema 定義哪些欄位是允許的，不在 schema 中的欄位在 collector 端丟棄。&lt;/p>
&lt;h3 id="sdk-端的最小化">SDK 端的最小化&lt;/h3>
&lt;p>SDK 端的最小化更主動 — 在事件產生時就只包含必要的欄位，而非送到 collector 再過濾。&lt;/p>
&lt;p>設計 SDK 的 event API 時，不提供「送任意 key-value」的 free-form API，而是提供結構化的 API：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">// free-form（難以控制收集了什麼）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">monitor.event(&amp;#39;login&amp;#39;, data: {&amp;#39;email&amp;#39;: email, &amp;#39;ip&amp;#39;: ip, &amp;#39;device&amp;#39;: device, ...})
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">// 結構化（schema 控制收集範圍）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">monitor.event(&amp;#39;login&amp;#39;, loginMethod: &amp;#39;biometric&amp;#39;, success: true)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>結構化 API 的參數在 SDK 設計時就決定了收集範圍，code review 時可以檢查「為什麼這個 event 需要這個參數」。&lt;/p>
&lt;h2 id="目的限制收集的資料只用於聲明的目的">目的限制：收集的資料只用於聲明的目的&lt;/h2>
&lt;p>目的限制要求資料只用於收集時聲明的目的。監控系統收集事件的目的通常是 debug 和效能監控 — 如果之後要用同一份資料做行為分析或廣告投放，需要額外的法律基礎（通常是使用者同意）。&lt;/p>
&lt;h3 id="工程落地">工程落地&lt;/h3>
&lt;p>目的限制在工程上的實作是「不同目的的資料分開儲存、分開授權」。&lt;/p>
&lt;p>Debug 用的 error 事件和行為分析用的 event 事件存在不同的儲存位置（不同的 JSONL 檔案或不同的資料庫 table）。Debug 用途的 access 不需要使用者同意（legitimate interest）；行為分析用途的 access 需要使用者同意。&lt;/p>
&lt;p>分開儲存讓「使用者撤回行為分析同意」的工程操作變簡單 — 刪除行為分析的儲存，不影響 debug 儲存。&lt;/p>
&lt;h2 id="儲存限制不保留超過必要期間的資料">儲存限制：不保留超過必要期間的資料&lt;/h2>
&lt;p>儲存限制要求資料只保留達成目的所需的最短期間。監控資料的合理保留期間依用途不同：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>用途&lt;/th>
 &lt;th>合理保留期間&lt;/th>
 &lt;th>理由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Debug&lt;/td>
 &lt;td>30-90 天&lt;/td>
 &lt;td>大部分 bug 在 30 天內被發現和修復&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>效能趨勢&lt;/td>
 &lt;td>6-12 個月&lt;/td>
 &lt;td>季節性趨勢需要至少一年的資料&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>行為分析&lt;/td>
 &lt;td>依同意期間&lt;/td>
 &lt;td>使用者同意到期就刪除&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>合規審計&lt;/td>
 &lt;td>依法規要求（通常 1-7 年）&lt;/td>
 &lt;td>法規指定的最短保留期間&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="自動清理">自動清理&lt;/h3>
&lt;p>Collector 的儲存清理應該自動化 — 手動清理依賴人記得執行，最終會被遺忘。&lt;/p>
&lt;p>JSONL 儲存用「一天一檔」的命名（&lt;code>events-2026-06-19.jsonl&lt;/code>），清理腳本每天刪除超過保留期限的檔案。Cron job 或 systemd timer 定期執行。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>去識別化技術 → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/anonymization-strategy/" data-link-title="去識別化策略" data-link-desc="IP 截斷 / user agent 簡化 / stack trace 路徑清理 / session UUID — 四種去識別化技術的適用場景和實作方式">去識別化策略&lt;/a>&lt;/li>
&lt;li>監控資料洩漏的威脅分析 → &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/monitoring-data-threat-model/" data-link-title="監控資料洩漏的 Threat Model" data-link-desc="監控系統本身是攻擊面 — 四個威脅場景（傳輸竊聽 / 儲存入侵 / endpoint 濫用 / 內部越權存取）的風險評估和防護措施">監控資料洩漏的 threat model&lt;/a>&lt;/li>
&lt;li>Collector 的儲存設計 → &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">模組四 Collector 設計&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>GDPR 的資料最小化原則要求「只收集達成特定目的所需的最少資料」。這個法律原則轉譯到監控系統的工程實作，影響三個設計決策：收集什麼欄位、保留多久、誰可以存取。</p>
<h2 id="資料最小化只收集需要的欄位">資料最小化：只收集需要的欄位</h2>
<p>資料最小化的工程落地是「每個收集的欄位都要能回答：這個欄位用來做什麼決策？」。如果一個欄位只是「可能有用」但沒有明確的消費場景，就不應該收集。</p>
<h3 id="正面表列-vs-負面排除">正面表列 vs 負面排除</h3>
<p>正面表列（allowlist）是列出「收集哪些欄位」— 只收集清單上的欄位，其他全部不收。</p>
<p>負面排除（denylist）是列出「不收集哪些欄位」— 預設收集所有欄位，排除清單上的。</p>
<p>GDPR 的精神更接近正面表列 — 每個收集行為需要有正當理由（lawful basis）。工程上的實作方式是：事件 schema 定義哪些欄位是允許的，不在 schema 中的欄位在 collector 端丟棄。</p>
<h3 id="sdk-端的最小化">SDK 端的最小化</h3>
<p>SDK 端的最小化更主動 — 在事件產生時就只包含必要的欄位，而非送到 collector 再過濾。</p>
<p>設計 SDK 的 event API 時，不提供「送任意 key-value」的 free-form API，而是提供結構化的 API：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">// free-form（難以控制收集了什麼）
</span></span><span class="line"><span class="ln">2</span><span class="cl">monitor.event(&#39;login&#39;, data: {&#39;email&#39;: email, &#39;ip&#39;: ip, &#39;device&#39;: device, ...})
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">// 結構化（schema 控制收集範圍）
</span></span><span class="line"><span class="ln">5</span><span class="cl">monitor.event(&#39;login&#39;, loginMethod: &#39;biometric&#39;, success: true)</span></span></code></pre></div><p>結構化 API 的參數在 SDK 設計時就決定了收集範圍，code review 時可以檢查「為什麼這個 event 需要這個參數」。</p>
<h2 id="目的限制收集的資料只用於聲明的目的">目的限制：收集的資料只用於聲明的目的</h2>
<p>目的限制要求資料只用於收集時聲明的目的。監控系統收集事件的目的通常是 debug 和效能監控 — 如果之後要用同一份資料做行為分析或廣告投放，需要額外的法律基礎（通常是使用者同意）。</p>
<h3 id="工程落地">工程落地</h3>
<p>目的限制在工程上的實作是「不同目的的資料分開儲存、分開授權」。</p>
<p>Debug 用的 error 事件和行為分析用的 event 事件存在不同的儲存位置（不同的 JSONL 檔案或不同的資料庫 table）。Debug 用途的 access 不需要使用者同意（legitimate interest）；行為分析用途的 access 需要使用者同意。</p>
<p>分開儲存讓「使用者撤回行為分析同意」的工程操作變簡單 — 刪除行為分析的儲存，不影響 debug 儲存。</p>
<h2 id="儲存限制不保留超過必要期間的資料">儲存限制：不保留超過必要期間的資料</h2>
<p>儲存限制要求資料只保留達成目的所需的最短期間。監控資料的合理保留期間依用途不同：</p>
<table>
  <thead>
      <tr>
          <th>用途</th>
          <th>合理保留期間</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Debug</td>
          <td>30-90 天</td>
          <td>大部分 bug 在 30 天內被發現和修復</td>
      </tr>
      <tr>
          <td>效能趨勢</td>
          <td>6-12 個月</td>
          <td>季節性趨勢需要至少一年的資料</td>
      </tr>
      <tr>
          <td>行為分析</td>
          <td>依同意期間</td>
          <td>使用者同意到期就刪除</td>
      </tr>
      <tr>
          <td>合規審計</td>
          <td>依法規要求（通常 1-7 年）</td>
          <td>法規指定的最短保留期間</td>
      </tr>
  </tbody>
</table>
<h3 id="自動清理">自動清理</h3>
<p>Collector 的儲存清理應該自動化 — 手動清理依賴人記得執行，最終會被遺忘。</p>
<p>JSONL 儲存用「一天一檔」的命名（<code>events-2026-06-19.jsonl</code>），清理腳本每天刪除超過保留期限的檔案。Cron job 或 systemd timer 定期執行。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>去識別化技術 → <a href="/blog/monitoring/07-security-privacy/anonymization-strategy/" data-link-title="去識別化策略" data-link-desc="IP 截斷 / user agent 簡化 / stack trace 路徑清理 / session UUID — 四種去識別化技術的適用場景和實作方式">去識別化策略</a></li>
<li>監控資料洩漏的威脅分析 → <a href="/blog/monitoring/07-security-privacy/monitoring-data-threat-model/" data-link-title="監控資料洩漏的 Threat Model" data-link-desc="監控系統本身是攻擊面 — 四個威脅場景（傳輸竊聽 / 儲存入侵 / endpoint 濫用 / 內部越權存取）的風險評估和防護措施">監控資料洩漏的 threat model</a></li>
<li>Collector 的儲存設計 → <a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">模組四 Collector 設計</a></li>
</ul>
]]></content:encoded></item><item><title>監控資料洩漏的 Threat Model</title><link>https://tarrragon.github.io/blog/monitoring/07-security-privacy/monitoring-data-threat-model/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/07-security-privacy/monitoring-data-threat-model/</guid><description>&lt;p>監控系統收集的資料本身就是有價值的攻擊目標。Error 訊息包含 stack trace 和系統架構資訊，event 資料包含使用者行為模式，lifecycle 資料包含部署時程和系統狀態。攻擊者取得這些資料後可以用於進一步的攻擊 — stack trace 揭露程式碼結構，部署資訊揭露更新節奏，行為資料揭露高價值使用者。&lt;/p>
&lt;h2 id="威脅場景一傳輸竊聽">威脅場景一：傳輸竊聽&lt;/h2>
&lt;h3 id="攻擊方式">攻擊方式&lt;/h3>
&lt;p>攻擊者在 SDK 和 collector 之間的網路路徑上攔截未加密的 HTTP 流量。同網段的 ARP spoofing、WiFi sniffing、或中間人（MITM）proxy。&lt;/p>
&lt;h3 id="暴露的資料">暴露的資料&lt;/h3>
&lt;p>事件的完整 JSON payload — 包括 redaction 後殘留的資訊（使用者行為、系統狀態、error message）。API key 或 basic auth credential 如果在 HTTP header 中明文傳送，也會被攔截。&lt;/p>
&lt;h3 id="防護">防護&lt;/h3>
&lt;p>使用 HTTPS 加密傳輸（&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全&lt;/a>）。所有 SDK 到 collector 的通訊走 TLS — 自簽憑證在自用場景足夠，公開部署用 Let&amp;rsquo;s Encrypt。&lt;/p>
&lt;h2 id="威脅場景二儲存入侵">威脅場景二：儲存入侵&lt;/h2>
&lt;h3 id="攻擊方式-1">攻擊方式&lt;/h3>
&lt;p>攻擊者取得 collector server 的存取權限（SSH 入侵、容器逃逸、雲端 IAM 權限提升），直接讀取儲存的事件檔案。&lt;/p>
&lt;h3 id="暴露的資料-1">暴露的資料&lt;/h3>
&lt;p>所有歷史事件 — 包含 redaction 處理後的事件。如果 redaction 不完整（遺漏了某些敏感欄位），歷史事件中可能包含 secret。&lt;/p>
&lt;h3 id="防護-1">防護&lt;/h3>
&lt;p>&lt;strong>最小化儲存&lt;/strong>：只保留必要期限的資料，過期自動刪除（&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/gdpr-minimization/" data-link-title="GDPR 最小化原則的工程落地" data-link-desc="資料最小化、目的限制、儲存限制 — GDPR 三個核心原則在監控系統的工程實作方式">GDPR 最小化原則&lt;/a>）。攻擊者能取得的資料量與保留期間成正比。&lt;/p>
&lt;p>&lt;strong>檔案系統加密&lt;/strong>：LUKS（Linux）或 FileVault（macOS）對整個磁碟加密。Server 關機後磁碟資料無法被讀取。&lt;/p>
&lt;p>&lt;strong>access log 監控&lt;/strong>：記錄所有對事件儲存的存取操作（&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control&lt;/a>）。異常存取（非工作時間、非預期的 IP）觸發告警。&lt;/p>
&lt;h2 id="威脅場景三endpoint-濫用">威脅場景三：Endpoint 濫用&lt;/h2>
&lt;h3 id="攻擊方式-2">攻擊方式&lt;/h3>
&lt;p>攻擊者取得 SDK 的 API key（從 client 端的程式碼或設定檔中提取），大量寫入垃圾事件或惡意 payload。&lt;/p>
&lt;h3 id="影響">影響&lt;/h3>
&lt;p>&lt;strong>資料汙染&lt;/strong>：合法事件和垃圾事件混在一起，分析結果不可靠。&lt;/p>
&lt;p>&lt;strong>資源耗盡&lt;/strong>：大量寫入消耗 collector 的儲存和處理能力。&lt;/p>
&lt;p>&lt;strong>注入攻擊&lt;/strong>：如果 collector 的查詢介面沒有做好輸入驗證，惡意 payload 中的特殊字元可能觸發 injection。&lt;/p>
&lt;h3 id="防護-2">防護&lt;/h3>
&lt;p>&lt;strong>Rate limit&lt;/strong>：每個 API key 的寫入速率限制。正常的 SDK 行為有可預測的寫入頻率（每分鐘 N 個事件），超出正常範圍的寫入被拒絕。&lt;/p>
&lt;p>&lt;strong>Schema validation&lt;/strong>：collector 只接受符合定義 schema 的事件。格式異常的 payload 在寫入前被丟棄。&lt;/p>
&lt;p>&lt;strong>API key 輪替&lt;/strong>：如果 API key 被洩漏，輪替 key 讓舊 key 失效。SDK 端更新新 key 後恢復正常。&lt;/p>
&lt;h2 id="威脅場景四內部越權存取">威脅場景四：內部越權存取&lt;/h2>
&lt;h3 id="攻擊方式-3">攻擊方式&lt;/h3>
&lt;p>有 collector 讀取權限的人（開發者、維運人員）存取超出自己職責範圍的事件資料。例如開發者查看行為分析資料（只應該看 debug 資料），或前端開發者查看 server-side 的 error 事件。&lt;/p>
&lt;h3 id="防護-3">防護&lt;/h3>
&lt;p>&lt;strong>角色分離&lt;/strong>：不同用途的資料用不同的存取權限（&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control&lt;/a>）。Debug 資料和行為分析資料分開授權。&lt;/p>
&lt;p>&lt;strong>去識別化&lt;/strong>：即使有存取權限，看到的也是去識別化後的資料（&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/anonymization-strategy/" data-link-title="去識別化策略" data-link-desc="IP 截斷 / user agent 簡化 / stack trace 路徑清理 / session UUID — 四種去識別化技術的適用場景和實作方式">去識別化策略&lt;/a>）。IP 截斷、user agent 簡化、stack trace 路徑清理 — 降低資料的個人可識別性。&lt;/p></description><content:encoded><![CDATA[<p>監控系統收集的資料本身就是有價值的攻擊目標。Error 訊息包含 stack trace 和系統架構資訊，event 資料包含使用者行為模式，lifecycle 資料包含部署時程和系統狀態。攻擊者取得這些資料後可以用於進一步的攻擊 — stack trace 揭露程式碼結構，部署資訊揭露更新節奏，行為資料揭露高價值使用者。</p>
<h2 id="威脅場景一傳輸竊聽">威脅場景一：傳輸竊聽</h2>
<h3 id="攻擊方式">攻擊方式</h3>
<p>攻擊者在 SDK 和 collector 之間的網路路徑上攔截未加密的 HTTP 流量。同網段的 ARP spoofing、WiFi sniffing、或中間人（MITM）proxy。</p>
<h3 id="暴露的資料">暴露的資料</h3>
<p>事件的完整 JSON payload — 包括 redaction 後殘留的資訊（使用者行為、系統狀態、error message）。API key 或 basic auth credential 如果在 HTTP header 中明文傳送，也會被攔截。</p>
<h3 id="防護">防護</h3>
<p>使用 HTTPS 加密傳輸（<a href="/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全</a>）。所有 SDK 到 collector 的通訊走 TLS — 自簽憑證在自用場景足夠，公開部署用 Let&rsquo;s Encrypt。</p>
<h2 id="威脅場景二儲存入侵">威脅場景二：儲存入侵</h2>
<h3 id="攻擊方式-1">攻擊方式</h3>
<p>攻擊者取得 collector server 的存取權限（SSH 入侵、容器逃逸、雲端 IAM 權限提升），直接讀取儲存的事件檔案。</p>
<h3 id="暴露的資料-1">暴露的資料</h3>
<p>所有歷史事件 — 包含 redaction 處理後的事件。如果 redaction 不完整（遺漏了某些敏感欄位），歷史事件中可能包含 secret。</p>
<h3 id="防護-1">防護</h3>
<p><strong>最小化儲存</strong>：只保留必要期限的資料，過期自動刪除（<a href="/blog/monitoring/07-security-privacy/gdpr-minimization/" data-link-title="GDPR 最小化原則的工程落地" data-link-desc="資料最小化、目的限制、儲存限制 — GDPR 三個核心原則在監控系統的工程實作方式">GDPR 最小化原則</a>）。攻擊者能取得的資料量與保留期間成正比。</p>
<p><strong>檔案系統加密</strong>：LUKS（Linux）或 FileVault（macOS）對整個磁碟加密。Server 關機後磁碟資料無法被讀取。</p>
<p><strong>access log 監控</strong>：記錄所有對事件儲存的存取操作（<a href="/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control</a>）。異常存取（非工作時間、非預期的 IP）觸發告警。</p>
<h2 id="威脅場景三endpoint-濫用">威脅場景三：Endpoint 濫用</h2>
<h3 id="攻擊方式-2">攻擊方式</h3>
<p>攻擊者取得 SDK 的 API key（從 client 端的程式碼或設定檔中提取），大量寫入垃圾事件或惡意 payload。</p>
<h3 id="影響">影響</h3>
<p><strong>資料汙染</strong>：合法事件和垃圾事件混在一起，分析結果不可靠。</p>
<p><strong>資源耗盡</strong>：大量寫入消耗 collector 的儲存和處理能力。</p>
<p><strong>注入攻擊</strong>：如果 collector 的查詢介面沒有做好輸入驗證，惡意 payload 中的特殊字元可能觸發 injection。</p>
<h3 id="防護-2">防護</h3>
<p><strong>Rate limit</strong>：每個 API key 的寫入速率限制。正常的 SDK 行為有可預測的寫入頻率（每分鐘 N 個事件），超出正常範圍的寫入被拒絕。</p>
<p><strong>Schema validation</strong>：collector 只接受符合定義 schema 的事件。格式異常的 payload 在寫入前被丟棄。</p>
<p><strong>API key 輪替</strong>：如果 API key 被洩漏，輪替 key 讓舊 key 失效。SDK 端更新新 key 後恢復正常。</p>
<h2 id="威脅場景四內部越權存取">威脅場景四：內部越權存取</h2>
<h3 id="攻擊方式-3">攻擊方式</h3>
<p>有 collector 讀取權限的人（開發者、維運人員）存取超出自己職責範圍的事件資料。例如開發者查看行為分析資料（只應該看 debug 資料），或前端開發者查看 server-side 的 error 事件。</p>
<h3 id="防護-3">防護</h3>
<p><strong>角色分離</strong>：不同用途的資料用不同的存取權限（<a href="/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control</a>）。Debug 資料和行為分析資料分開授權。</p>
<p><strong>去識別化</strong>：即使有存取權限，看到的也是去識別化後的資料（<a href="/blog/monitoring/07-security-privacy/anonymization-strategy/" data-link-title="去識別化策略" data-link-desc="IP 截斷 / user agent 簡化 / stack trace 路徑清理 / session UUID — 四種去識別化技術的適用場景和實作方式">去識別化策略</a>）。IP 截斷、user agent 簡化、stack trace 路徑清理 — 降低資料的個人可識別性。</p>
<p><strong>access log 審計</strong>：所有讀取操作記錄在 access log 中，定期 review。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>SDK 端的 redaction → <a href="/blog/monitoring/07-security-privacy/sdk-redaction-api/" data-link-title="SDK Redaction API 設計" data-link-desc="預設 redaction rule 過濾已知敏感欄位、自訂 pattern 擴展應用特有的 secret 格式 — redaction 在 SDK 端執行，敏感資料不離開 client">SDK Redaction API 設計</a></li>
<li>Transport 層保護 → <a href="/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全</a></li>
<li>Collector 端保護 → <a href="/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control 實作</a></li>
<li>去識別化技術 → <a href="/blog/monitoring/07-security-privacy/anonymization-strategy/" data-link-title="去識別化策略" data-link-desc="IP 截斷 / user agent 簡化 / stack trace 路徑清理 / session UUID — 四種去識別化技術的適用場景和實作方式">去識別化策略</a></li>
<li>Client-side SDK 認證的多層緩解策略 → <a href="/blog/monitoring/07-security-privacy/client-sdk-authentication/" data-link-title="Client-side SDK 認證的根本限制" data-link-desc="嵌在 client 端的 credential 必然可被提取 — 認清 architecture 天花板後的多層緩解策略，從 origin 驗證到 device attestation">Client-side SDK 認證</a></li>
</ul>
]]></content:encoded></item><item><title>Client-side SDK 認證的根本限制</title><link>https://tarrragon.github.io/blog/monitoring/07-security-privacy/client-sdk-authentication/</link><pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/07-security-privacy/client-sdk-authentication/</guid><description>&lt;p>當監控 SDK 部署在使用者裝置上（瀏覽器、手機 app、本機腳本），collector 的 ingestion endpoint 就暴露在外部網路 — 認證機制需要面對 credential 必然可被提取的前提。Client-side SDK 的認證和 server-side API 的認證面對的是結構性不同的問題。Server-side 的 API key 存在環境變數或 secret store 裡，只有 server process 能讀取。Client-side SDK 的 credential 必須嵌入到使用者手上的程式碼中 — JS bundle、APK、Python script — 使用者（或攻擊者）可以直接讀取。&lt;/p>
&lt;p>這個限制來自 architecture，和 implementation 無關。混淆 JS、ProGuard 混淆 APK、編譯 Python 成 &lt;code>.pyc&lt;/code>，都只增加提取成本，不改變「credential 在 client 端」的事實。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control&lt;/a> 討論了 API key 和 mTLS 的認證機制，&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全&lt;/a> 討論了傳輸層加密。兩者的前提是 credential 被妥善保管。本章處理的是那個前提不成立時 — credential 已被提取或必然可被提取 — 的緩解策略。&lt;/p>
&lt;h2 id="商業方案的處理方式">商業方案的處理方式&lt;/h2>
&lt;p>所有主流的 client-side telemetry 方案都面對同樣的限制。它們的共同策略是：承認 client credential 會暴露，把防線從「保護 credential」轉移到「限制 credential 被濫用的影響」。&lt;/p>
&lt;p>&lt;strong>Google Analytics 4&lt;/strong>：Measurement ID（G-XXXXXXXXXX）直接寫在網頁的 JS snippet 中，任何人檢視網頁原始碼都能取得。GA4 的防護在 server-side — Google 用 domain 白名單過濾來源，加上自動的 bot traffic 偵測剔除機器流量。Measurement Protocol（server-to-server）需要額外的 API secret，但 client-side 的 gtag.js 不需要。&lt;/p>
&lt;p>&lt;strong>Sentry&lt;/strong>：DSN（Data Source Name）包含 project ID 和 public key，直接嵌在 SDK init 的程式碼中。Sentry 官方文件明確標示 DSN 是 public 的 — 攻擊者取得 DSN 只能送事件，不能讀取已收集的資料。防護靠 rate limit（每個 project 的 events/sec 上限）、allowed domains（只接受來自白名單 domain 的事件）、和 server-side 的 event 去重。&lt;/p>
&lt;p>&lt;strong>Firebase&lt;/strong>：整個 &lt;code>google-services.json&lt;/code> / &lt;code>GoogleService-Info.plist&lt;/code> 的內容 — 包含 apiKey、projectId、appId — 都視為公開資訊。Firebase 的安全模型不依賴這些 key 的保密性；它們的功能是識別（identify）而非授權（authorize）。需要保護的資源靠 Firebase Security Rules 和 App Check（device attestation）處理。&lt;/p>
&lt;p>&lt;strong>Datadog RUM&lt;/strong>：Client token 是獨立於 API key 的 credential。API key 可以讀寫所有 Datadog 資料，必須保護在 server-side；client token 只能寫入 RUM 事件，設計上可以暴露在 client 端。Datadog 建議搭配 intake proxy（collector 前面加一層自己的 server），讓 client token 不直接出現在瀏覽器中。&lt;/p></description><content:encoded><![CDATA[<p>當監控 SDK 部署在使用者裝置上（瀏覽器、手機 app、本機腳本），collector 的 ingestion endpoint 就暴露在外部網路 — 認證機制需要面對 credential 必然可被提取的前提。Client-side SDK 的認證和 server-side API 的認證面對的是結構性不同的問題。Server-side 的 API key 存在環境變數或 secret store 裡，只有 server process 能讀取。Client-side SDK 的 credential 必須嵌入到使用者手上的程式碼中 — JS bundle、APK、Python script — 使用者（或攻擊者）可以直接讀取。</p>
<p>這個限制來自 architecture，和 implementation 無關。混淆 JS、ProGuard 混淆 APK、編譯 Python 成 <code>.pyc</code>，都只增加提取成本，不改變「credential 在 client 端」的事實。</p>
<p><a href="/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control</a> 討論了 API key 和 mTLS 的認證機制，<a href="/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全</a> 討論了傳輸層加密。兩者的前提是 credential 被妥善保管。本章處理的是那個前提不成立時 — credential 已被提取或必然可被提取 — 的緩解策略。</p>
<h2 id="商業方案的處理方式">商業方案的處理方式</h2>
<p>所有主流的 client-side telemetry 方案都面對同樣的限制。它們的共同策略是：承認 client credential 會暴露，把防線從「保護 credential」轉移到「限制 credential 被濫用的影響」。</p>
<p><strong>Google Analytics 4</strong>：Measurement ID（G-XXXXXXXXXX）直接寫在網頁的 JS snippet 中，任何人檢視網頁原始碼都能取得。GA4 的防護在 server-side — Google 用 domain 白名單過濾來源，加上自動的 bot traffic 偵測剔除機器流量。Measurement Protocol（server-to-server）需要額外的 API secret，但 client-side 的 gtag.js 不需要。</p>
<p><strong>Sentry</strong>：DSN（Data Source Name）包含 project ID 和 public key，直接嵌在 SDK init 的程式碼中。Sentry 官方文件明確標示 DSN 是 public 的 — 攻擊者取得 DSN 只能送事件，不能讀取已收集的資料。防護靠 rate limit（每個 project 的 events/sec 上限）、allowed domains（只接受來自白名單 domain 的事件）、和 server-side 的 event 去重。</p>
<p><strong>Firebase</strong>：整個 <code>google-services.json</code> / <code>GoogleService-Info.plist</code> 的內容 — 包含 apiKey、projectId、appId — 都視為公開資訊。Firebase 的安全模型不依賴這些 key 的保密性；它們的功能是識別（identify）而非授權（authorize）。需要保護的資源靠 Firebase Security Rules 和 App Check（device attestation）處理。</p>
<p><strong>Datadog RUM</strong>：Client token 是獨立於 API key 的 credential。API key 可以讀寫所有 Datadog 資料，必須保護在 server-side；client token 只能寫入 RUM 事件，設計上可以暴露在 client 端。Datadog 建議搭配 intake proxy（collector 前面加一層自己的 server），讓 client token 不直接出現在瀏覽器中。</p>
<p>這些方案的共同模式：client-side credential 的角色是「識別來源」而非「授權存取」。即使被提取，攻擊者能做的事被限縮在「寫入事件」— 影響可控。</p>
<h2 id="認證天花板識別-vs-授權">認證天花板：識別 vs 授權</h2>
<p><a href="/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control</a> 的 API key 同時承擔識別和授權 — 有 key 就能寫入，沒 key 就被拒絕。在 server-side 場景下這沒有問題，因為 key 不會暴露。</p>
<p>Client-side 場景需要拆開這兩個功能：</p>
<p><strong>識別（identification）</strong>：這個 request 來自哪個 app、哪個 SDK、哪個部署版本。識別資訊可以公開 — 它的價值是讓 collector 知道事件來自哪裡，用於 access log、per-app rate limit、和事件標記。</p>
<p><strong>授權（authorization）</strong>：這個 request 有沒有權限執行寫入操作。授權依賴 credential 的保密性 — 在 client-side 場景下，credential 保密性的天花板很低。</p>
<p>接受這個區分後，client-side SDK 的 API key 更接近「識別 token」。它的洩漏不是安全事件（像 server-side API key 洩漏那樣），而是預期中的狀態。防護的重點從「防止 key 洩漏」轉移到「限制 key 被濫用時的影響」。</p>
<h2 id="多層緩解策略">多層緩解策略</h2>
<p>以下各層按實作成本遞增排列。前面的層在多數場景下足夠，後面的層在 endpoint 暴露在公開網路且面對主動攻擊時才需要。</p>
<h3 id="第一層寫入限制collector-已有">第一層：寫入限制（collector 已有）</h3>
<p><a href="/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control</a> 的寫入限制 — rate limit、payload size limit、schema validation — 是第一層防護。這些機制不區分「合法 SDK」和「偽造 client」，對所有寫入請求一視同仁地施加約束。</p>
<p>Rate limit 限制每個 API key 的事件速率。Schema validation 拒絕不符合 <a href="/blog/monitoring/02-log-schema/event-schema-fields/" data-link-title="event.schema.json 完整欄位解說" data-link-desc="監控事件的 JSON Schema 定義 — 每個欄位的語意、必填/選填、資料型別和設計理由">event.schema.json</a> 結構的 payload。兩者合起來把偽造流量的影響限制在「每秒 N 筆符合 schema 的事件」— 這個量級的資料汙染對 error tracking 的影響有限（error 事件靠 stack trace fingerprint 去重），對 funnel 分析的影響較大（行為事件的計數會被灌水）。</p>
<h3 id="第二層origin-驗證">第二層：Origin 驗證</h3>
<p>Web SDK 的 HTTP request 帶有瀏覽器自動附加的 <code>Origin</code> header。Collector 可以檢查 Origin 是否在白名單中。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="nf">originCheck</span><span class="p">(</span><span class="nx">next</span> <span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="p">,</span> <span class="nx">allowed</span> <span class="p">[]</span><span class="kt">string</span><span class="p">)</span> <span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">allowedSet</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">bool</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">o</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">allowed</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="nx">allowedSet</span><span class="p">[</span><span class="nx">o</span><span class="p">]</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="nx">http</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nx">origin</span> <span class="o">:=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Origin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">if</span> <span class="nx">origin</span> <span class="o">!=</span> <span class="s">&#34;&#34;</span> <span class="o">&amp;&amp;</span> <span class="p">!</span><span class="nx">allowedSet</span><span class="p">[</span><span class="nx">origin</span><span class="p">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="s">&#34;forbidden origin&#34;</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusForbidden</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nx">next</span><span class="p">.</span><span class="nf">ServeHTTP</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">r</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Origin 驗證擋住的是「從瀏覽器中跨域呼叫」的場景 — 攻擊者在自己的網站用 JS 向你的 collector 發 request，瀏覽器會帶上攻擊者網站的 Origin，被 collector 拒絕。</p>
<p><strong>天花板</strong>：Origin header 只有瀏覽器會自動附加。用 <code>curl</code>、Postman、或任何非瀏覽器 HTTP client 發 request 時，可以自行設定任意 Origin 值。Origin 驗證擋得住瀏覽器中的跨域呼叫，擋不住直接用 HTTP client 偽造的 request。</p>
<p>Mobile SDK（Flutter / native app）的 request 不帶 Origin header。Origin 驗證只對 Web SDK 有效。</p>
<h3 id="第三層request-signing">第三層：Request signing</h3>
<p>SDK 用 HMAC 對每個 request 簽章，collector 驗證簽章有效性。簽章的輸入包含 timestamp 和 payload hash，防止 replay attack 和 payload 竄改。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">X-Signature: a3f8c2e1b7d94f06...  (HMAC-SHA256 結果的 hex 編碼)
</span></span><span class="line"><span class="ln">2</span><span class="cl">X-Timestamp: 1719216000</span></span></code></pre></div><p>SDK 計算方式：<code>HMAC-SHA256(secret, timestamp + &quot;.&quot; + SHA256(body))</code>，結果轉 hex 字串放入 <code>X-Signature</code> header。</p>
<p>Collector 端的驗證邏輯：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="nf">verifySignature</span><span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">,</span> <span class="nx">secret</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">ts</span> <span class="o">:=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;X-Timestamp&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">sig</span> <span class="o">:=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;X-Signature&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1">// 拒絕超過 5 分鐘的 request timestamp（防 replay）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1">// 5 分鐘容忍 client-server 時鐘漂移和網路延遲；行動裝置偏差大的環境可放寬到 10 分鐘</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1">// 此處的 timestamp 是 HTTP request 發出時間，和事件的 timestamp 欄位（事件產生時間）無關</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nx">tsInt</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">strconv</span><span class="p">.</span><span class="nf">ParseInt</span><span class="p">(</span><span class="nx">ts</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">64</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="o">||</span> <span class="nf">abs</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">().</span><span class="nf">Unix</span><span class="p">()</span><span class="o">-</span><span class="nx">tsInt</span><span class="p">)</span> <span class="p">&gt;</span> <span class="mi">300</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nx">body</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">io</span><span class="p">.</span><span class="nf">ReadAll</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nx">bodyHash</span> <span class="o">:=</span> <span class="nx">sha256</span><span class="p">.</span><span class="nf">Sum256</span><span class="p">(</span><span class="nx">body</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nx">expected</span> <span class="o">:=</span> <span class="nx">hmac</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="nx">sha256</span><span class="p">.</span><span class="nx">New</span><span class="p">,</span> <span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">secret</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nx">expected</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">ts</span> <span class="o">+</span> <span class="s">&#34;.&#34;</span> <span class="o">+</span> <span class="nx">hex</span><span class="p">.</span><span class="nf">EncodeToString</span><span class="p">(</span><span class="nx">bodyHash</span><span class="p">[:])))</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nx">sigBytes</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">hex</span><span class="p">.</span><span class="nf">DecodeString</span><span class="p">(</span><span class="nx">sig</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="nx">hmac</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="nx">sigBytes</span><span class="p">,</span> <span class="nx">expected</span><span class="p">.</span><span class="nf">Sum</span><span class="p">(</span><span class="kc">nil</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Request signing 增加偽造成本 — 攻擊者需要提取 HMAC secret 並實作簽章邏輯，而非直接複製一個 API key 貼到 curl 指令。</p>
<p>HMAC secret 和 API key 一樣嵌在 client 端程式碼中，反編譯 APK 或閱讀 JS bundle 可以提取。Signing 增加的是攻擊者的工程投入（需要理解簽章算法並正確實作），而非理論上的安全性。對 casual attacker（看到 API key 就想試試的人）有效，對 motivated attacker（願意花時間逆向工程的人）無效。</p>
<h3 id="第四層行為分析異常偵測">第四層：行為分析異常偵測</h3>
<p>Collector 端統計每個 API key（或 source.app）的事件模式，建立 baseline 後偵測偏離。</p>
<p>正常 SDK 的行為有可預測的特徵：</p>
<table>
  <thead>
      <tr>
          <th>特徵</th>
          <th>正常 SDK 的 pattern</th>
          <th>偽造流量的 pattern</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>事件類型分布</td>
          <td>error / event / lifecycle / metric 四類混合</td>
          <td>可能只有單一類型</td>
      </tr>
      <tr>
          <td>事件間隔</td>
          <td>攢批送出，interval 接近 SDK config 的 flush interval</td>
          <td>固定間隔或連續送出</td>
      </tr>
      <tr>
          <td>Payload 結構</td>
          <td><code>source.sdk</code> / <code>source.platform</code> / <code>source.app</code> 值穩定</td>
          <td>可能缺少 SDK 自動填入的欄位</td>
      </tr>
      <tr>
          <td>Session 行為</td>
          <td>有 lifecycle 事件（session.begin / session.end）</td>
          <td>可能沒有 session 邊界</td>
      </tr>
      <tr>
          <td>時間分布</td>
          <td>跟使用者活動時段相關（工作時間 / 使用高峰）</td>
          <td>可能 24 小時均勻分布</td>
      </tr>
  </tbody>
</table>
<p>Collector 可以用 rule engine 偵測異常模式：</p>
<ul>
<li>單一 API key 的事件量在 10 分鐘內超過過去 24 小時平均值的 10 倍</li>
<li>連續 N 個 request 的事件全是同一個 type</li>
<li><code>source.sdk</code> 欄位的值不在已知的 SDK 版本清單中</li>
</ul>
<p>偵測到異常後的處理方式是標記而非丟棄 — 在事件中加入 <code>_flags.suspicious = true</code> flag，讓 dashboard 和分析查詢可以過濾。直接丟棄有誤殺正常流量的風險（例如行銷活動導致的真實流量暴增）。</p>
<p>攻擊者如果研究過正常 SDK 的行為模式（事件類型分布、送出間隔、payload 結構），可以模擬出相似的流量。行為分析依賴「偽造流量和正常流量有可偵測的差異」這個前提 — 對低投入的攻擊者成立，對高投入的攻擊者不一定。</p>
<h3 id="第五層device-attestation">第五層：Device attestation</h3>
<p>由作業系統或平台層驗證 client 的合法性，提供 SDK 自身無法產生的證明。</p>
<p><strong>Firebase App Check</strong>：整合 DeviceCheck（iOS）、Play Integrity（Android）、reCAPTCHA Enterprise（Web），由裝置平台出具 attestation token。Collector 向 Firebase 驗證 token 的有效性。</p>
<p><strong>Apple DeviceCheck / App Attest</strong>：iOS 裝置向 Apple server 請求 attestation，證明 request 來自一台真實的、未被篡改的 iOS 裝置上的合法 app。</p>
<p><strong>Google Play Integrity</strong>：驗證 request 來自 Google Play 安裝的 app、在未 root 的裝置上、由合法使用者操作。</p>
<p>Device attestation 提供的保證比前四層都強 — 它依賴裝置硬體和平台服務（難以偽造），而非 SDK 嵌入的 secret（可提取）。</p>
<p><strong>天花板</strong>：</p>
<ul>
<li>平台綁定 — 每個平台（iOS / Android / Web）需要各自整合不同的 attestation 服務，跨平台 SDK 的實作成本高</li>
<li>Root / 越獄裝置上 attestation 可能失敗或被繞過</li>
<li>Web 端的 reCAPTCHA 驗證依賴 Google 服務，有隱私和可用性的考量</li>
<li>自架 collector 需要額外整合 Firebase Admin SDK 或各平台的驗證 API</li>
</ul>
<p>Device attestation 適合商業產品級的 mobile app，對自架監控工具而言實作成本通常超出收益。</p>
<h2 id="自架方案的規模對應">自架方案的規模對應</h2>
<p>不同部署規模下，需要做到哪一層取決於 endpoint 的暴露程度和偽造流量的影響大小。</p>
<table>
  <thead>
      <tr>
          <th>部署場景</th>
          <th>暴露程度</th>
          <th>建議做到的層級</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自用（1 人，同機 / 同網段）</td>
          <td>低 — endpoint 不對外</td>
          <td>HTTPS + basic auth</td>
          <td>攻擊面只有同網段，認證足夠</td>
      </tr>
      <tr>
          <td>小型團隊（&lt; 100 人，VPN 內）</td>
          <td>低 — endpoint 在 VPN 後</td>
          <td>API key + rate limit</td>
          <td>VPN 已限制存取範圍，rate limit 防 SDK bug</td>
      </tr>
      <tr>
          <td>公開 endpoint（VPS / 雲端）</td>
          <td>高 — 任何人可存取</td>
          <td>第一到第四層 + WAF</td>
          <td>rate limit + origin + signing + 行為分析 + CDN/WAF 的 IP reputation 過濾</td>
      </tr>
      <tr>
          <td>商業產品（app store 發佈）</td>
          <td>高 — APK 可反編譯，JS 可檢視原始碼</td>
          <td>第一到第五層 + intake proxy</td>
          <td>需要 device attestation 和 proxy 層把 credential 從 client 端移除</td>
      </tr>
  </tbody>
</table>
<p><strong>Intake proxy 架構</strong>：在公開 endpoint 和商業產品場景下，可以在 collector 前面加一層自己的 server（proxy），SDK 送事件到 proxy，proxy 用 server-side API key 轉發到 collector。Client 端的 credential 只指向 proxy，proxy 的 API key 指向 collector — credential 分層，client 端的 key 洩漏不影響 collector 的認證。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">SDK ──(client token)──→ Intake Proxy ──(server API key)──→ Collector</span></span></code></pre></div><p>Proxy 的額外成本是多一個 server 和網路跳躍。自用場景下不需要；endpoint 公開時值得考慮。</p>
<h2 id="偽造流量的影響分析">偽造流量的影響分析</h2>
<p>偽造流量進入 collector 後，對不同類型的分析影響不同。</p>
<p><strong>Error tracking 影響較低</strong>：error 事件的價值在 stack trace 和 error message。偽造的 error 事件缺少真實的 stack trace — 即使格式正確，內容是編造的。Error 去重靠 fingerprint（error type + message + stack trace top frame），偽造事件產生的 fingerprint 不會和真實 error 碰撞，在 dashboard 上是獨立的 error group，容易識別和過濾。</p>
<p><strong>行為分析影響較高</strong>：funnel 和 cohort 分析依賴事件計數的準確性。偽造的 <code>page.view</code> 和 <code>button.click</code> 事件直接灌水計數，導致轉換率失真。偽造事件越接近真實事件的結構（正確的 event name、合理的 timestamp），影響越大。</p>
<p><strong>資源消耗是固定成本</strong>：無論事件內容是否真實，每筆事件都消耗 collector 的寫入 I/O、儲存空間、和查詢時間。Rate limit 把這個成本限制在可控範圍 — 每秒 N 筆是上限，無論來源是否合法。</p>
<h3 id="事後標記策略">事後標記策略</h3>
<p>偵測到可疑流量後，collector 在事件中加入標記欄位而非直接丟棄。丟棄有誤殺風險 — 行銷活動的流量暴增、SDK 版本升級改變了事件模式、新平台的 SDK 上線 — 這些正常場景可能觸發異常偵測。</p>
<p>標記方式是在 collector 寫入時，對符合異常條件的事件附加 metadata：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;v&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;event&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;button.click&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;sdk&#34;</span><span class="p">:</span> <span class="s2">&#34;js&#34;</span><span class="p">,</span> <span class="nt">&#34;platform&#34;</span><span class="p">:</span> <span class="s2">&#34;web&#34;</span><span class="p">,</span> <span class="nt">&#34;app&#34;</span><span class="p">:</span> <span class="s2">&#34;main-site&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nt">&#34;_flags&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;suspicious&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;reason&#34;</span><span class="p">:</span> <span class="s2">&#34;rate_anomaly&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Dashboard 查詢預設排除 <code>_flags.suspicious = true</code> 的事件。需要調查時可以包含 — 看可疑事件的模式有助於判斷是攻擊還是誤判。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Collector 端的認證和授權機制 → <a href="/blog/monitoring/07-security-privacy/collector-access-control/" data-link-title="Collector Access Control 實作" data-link-desc="認證（誰在送資料）/ 授權（允許送什麼）/ access log（誰在什麼時候送了什麼）— collector 端的三層存取控制">Collector Access Control 實作</a></li>
<li>Transport 層的加密保護 → <a href="/blog/monitoring/07-security-privacy/transport-security/" data-link-title="Transport 安全" data-link-desc="HTTPS / basic auth / 同區網也要加密的理由 — 監控資料在傳輸途中的保護機制">Transport 安全</a></li>
<li>Endpoint 濫用的威脅分析 → <a href="/blog/monitoring/07-security-privacy/monitoring-data-threat-model/" data-link-title="監控資料洩漏的 Threat Model" data-link-desc="監控系統本身是攻擊面 — 四個威脅場景（傳輸竊聽 / 儲存入侵 / endpoint 濫用 / 內部越權存取）的風險評估和防護措施">監控資料洩漏的 Threat Model</a></li>
<li>SDK 端的寫入速率控制 → <a href="/blog/monitoring/04-collector/ingestion-scaling/" data-link-title="Ingestion Scaling" data-link-desc="四層防線應對 ingestion 端的流量擴展 — SDK 取樣、Collector 背壓、水平擴展、Queue 解耦">Ingestion Scaling</a></li>
<li>行為分析和 rule engine → <a href="/blog/monitoring/04-collector/rule-engine/" data-link-title="Rule engine 設計" data-link-desc="條件 → 動作 → 模板的三段式規則結構 — 讓 collector 從被動儲存變成主動回應">Rule Engine 設計</a></li>
<li>偽造流量對資料完整性的影響 → <a href="/blog/monitoring/04-collector/data-integrity/" data-link-title="端到端資料完整性" data-link-desc="從 SDK 到 storage 的資料損失地圖 — 每個環節的損失類型、控制策略、完整性指標、被自己 SDK DDoS 的防護">端到端資料完整性</a></li>
<li>Error fingerprint 讓偽造 error 容易辨識 → <a href="/blog/monitoring/04-collector/error-fingerprint/" data-link-title="Error Fingerprint 與去重分群" data-link-desc="把大量 error 事件歸組成可管理的 issue 列表 — fingerprint 演算法、message normalization、error_groups 表設計、自架方案的務實邊界">Error Fingerprint 與去重分群</a></li>
</ul>
]]></content:encoded></item></channel></rss>