Squirrel Stash 隱私權政策
本文件以繁體中文為主,English follows below。兩個版本內容等價,若有衝突 以英文版為準(App Store / Play Store 審查依據)。
繁體中文版
1. 我們是誰
「Squirrel Stash(松鼠票夾)」是一款獨立開發的票券管理 App。我們 沒有帳號系統、不需登入,您的票券、優惠券、設定預設 只儲存在您的裝置上。為了控管免費版 AI 辨識額度、防止濫用, 我們有一個最小化的 Firebase 後端服務(Cloud Functions + Firebase 匿名驗證 + Firestore):Cloud Functions 代理 AI 請求與獎勵式廣告 / 預算檢查,並只記錄一個匿名 Firebase Auth UID與 AI 使用量/ 廣告獎勵/預算計數——不含您的票券內容, 也不含姓名、email 等個人資料。詳見下方說明。
2. 我們收集哪些資料
| 類別 | 內容 | 儲存位置 | 是否離開您的裝置 |
|---|---|---|---|
| 票券 / 優惠券資料 | 標題、條碼、到期日、備註、自訂分類 | 本機 Hive 資料庫(AES-256 加密) | 否 |
| 票券截圖 | 您拍照或從相簿匯入的票券圖片 | 本機檔案系統(App 私有目錄) | 僅在使用 AI 辨識時上傳;上傳前會自動遮罩條碼(見第 3 節) |
| eSIM LPA 字串 | 電信商提供的 eSIM 啟用字串 | 本機 Hive(AES-256 加密) | 否(安裝時透過 OS 系統 URL 啟動) |
| 付費 / 訂閱資料 | Squirrel Pro 權益狀態:方案、到期日、產品 ID;訂閱驗證所需的最小平台資料 | 本機 SharedPreferences;Apple / Google IAP;Firebase Functions / Firebase custom claims | 購買由 Apple / Google IAP 處理;為驗證或還原 Pro 權益,App 可能將必要訂閱驗證資料送到 Squirrel 控制的 Firebase Function,並以匿名 Firebase Auth UID 更新 custom claim / 權益中繼資料 |
| App 使用記錄 | v1.0 上架版本不啟動 telemetry SDK,不收集使用事件 | — | 否 |
| 崩潰報告 | v1.0 上架版本不啟動 telemetry SDK,不收集遠端崩潰報告 | — | 否 |
| 帳號識別 | 我們不要求註冊帳號,不會收集 email、電話、姓名 | — | — |
| 匿名 Firebase UID | Firebase 匿名驗證建立的 UID,用於驗證 Cloud Function 呼叫、AI 使用量 / 廣告獎勵 / 預算計數與訂閱權益中繼資料 | Firebase Auth / Firestore / Firebase custom claims | 是;這是 App 功能與防濫用用途,不是廣告追蹤,也不是 Squirrel Stash 自建的裝置識別碼 |
| 廣告 ID / 裝置 ID | Free 使用者啟動 AI 辨識並選擇廣告支援解鎖時,獎勵式廣告供應商可能依其 SDK 與平台政策處理廣告或裝置識別資訊(例如 ATT 同意後的 IDFA)。Squirrel Stash 不建立自己的裝置識別碼。 | 第三方廣告供應商;Squirrel Stash 自身不儲存自建裝置 ID | 僅限您主動選擇廣告支援解鎖時;我們不把票券內容、圖片、分類名稱、條碼或 AI 辨識結果提供給廣告供應商做投放 |
| 位置資訊 | 不收集 | — | — |
3. 圖片辨識(AI VLM)— 關鍵說明
當您使用「拍照新增票券」或「AI 辨識」功能時,App 會將圖片送到第三方 AI 模型供應商以辨識票券資訊(標題、到期日、商家名稱等)。
3.1 條碼遮罩
我們在上傳前會用本機程式自動偵測並用黑色方塊覆蓋圖片中的所有 條碼 / QR Code 區域。這個動作完全在您的裝置上發生,不需要您 手動操作。
遮罩流程(概念虛擬碼,僅作說明用):
function maskBarcodes(image):
barcodes ← detectBarcodes(image) # 本機 ML 模型,不上雲端
if barcodes is empty:
return image (no masking needed)
canvas ← decode(image)
for each barcode in barcodes:
bbox ← barcode.bounds + 8px padding # 涵蓋條碼 quiet zone
canvas.fillRect(bbox, color=BLACK) # 純黑覆蓋
return encodeJpeg(canvas, quality=90)
偵測失敗或像素填充失敗時,App 會中止上傳而非送出未遮罩 的原圖(強制中止,不降級)。
目的:條碼是您的票券價值載體(兌換碼、eSIM LPA、會員號碼、付款 QR), 遮罩後即使 AI 供應商保留圖片,也無法用來盜用您的票券。
3.2 我們會把圖片送到哪裡
| 供應商 | 模型 | 用途 | 隱私政策 | 資料保留 |
|---|---|---|---|---|
| OpenAI | gpt-5.4-mini | Free 票券 / 優惠券 / eSIM 圖片辨識 + 圖片種類判定;Pro fallback | openai.com policy | API 預設 30 天保留、不訓練 |
| OpenAI | gpt-5.4 | Pro 票券 / 優惠券 / eSIM 圖片辨識主模型 | 同上 | 同上 |
我們不使用 OpenRouter / Google AI Studio / Groq 等其他第三方 推論供應商。所有 AI 影像辨識皆透過 Squirrel 控制的 Firebase Cloud Function 代理到 OpenAI API;App 用戶端不內嵌 OpenAI API key。Cloud Function 會用匿名 Firebase Auth UID 進行額度 / 獎勵式廣告 / 成本控管,但不儲存您的票券內容。
我們無法控制 OpenAI 的政策變更。Squirrel Stash 提供 的條碼遮罩是一道防線,但您仍應評估自身敏感資料的風險。如果您不希望 任何圖片離開裝置,請不要使用 AI 辨識功能,改用手動 輸入或條碼掃描(這兩個功能完全在本機運作)。
3.3 隨同圖片傳輸的內容
為了讓 AI 模型給出更準確的分類結果,圖片之外 我們會在 prompt 中夾帶:
- 您在「設定 → 自訂分類」建立的分類名稱列表(不含 icon / 顏色),讓模型可以建議現有的分類而非生造一個。 如果您把公司名 / 個人標籤當分類名(例如「公司福委會」「老婆生日」),這些字串會出現在 prompt 中。如果不希望這類資訊離開裝置,請避免用敏感字串作為 分類名稱。
- 您在「設定 → 國家偏好」的 active 國家清單(用於 eSIM 國別判定)
- 當下使用者選擇的 kind(ticket / coupon),讓模型解 ambiguous 案例
我們不會傳輸:
- 票券標題、備註、條碼內容、QR 內容(這些不需要 AI 來辨識,圖片本身就有)
- 您裝置上的其他票券 / 優惠券(每次 prompt 只看當下這張圖)
- Squirrel Stash 自建的裝置 ID(本 App 不建立這種 ID)、Apple ID、Google 帳號;AI prompt 也不包含廣告識別碼或匿名 Firebase Auth UID。廣告 SDK / Firebase 後端識別資訊的處理見第 2、3 節。
- 您圖片庫其他照片,僅限您主動選擇的當張票券圖片
4. 本機資料安全
- 所有票券 / 優惠券資料以 AES-256 加密儲存於 Hive 本機資料庫
- 加密金鑰存於系統安全儲存(iOS Keychain / Android Keystore),即使裝置 root / jailbreak 也無法直接讀取
- 截圖檔案存於 App 私有目錄,OS 層級已隔離其他 App 存取
5. 您的權利(GDPR / 台灣個資法)
5.1 存取 / 匯出
您隨時可以透過「設定 → 備份」匯出所有資料(加密 ZIP 檔)。
5.2 修改
所有資料皆可在 App 內直接編輯。
5.3 刪除
「設定 → 清除所有資料」會永久刪除所有本機票券、優惠券、截圖、設定, 並 cancel 所有通知排程。
操作流程:
- 點擊「設定 → 清除所有資料」紅色按鈕
- 在彈出對話框中輸入「刪除」二字啟用確認鈕
- 點擊「清除所有資料」立即執行
明確會清除:本機 Hive boxes、所有截圖目錄、多數 SharedPreferences 設定、所有已排程的通知。
明確保留(白名單機制):
subscription_status/subscription_expiry_date/subscription_product_id— 保留 Squirrel Pro 訂閱狀態,供 App 內顯示權益與恢復購買後離線判斷使用onboarding_completed_v1— 首次教學旗標- Hive AES key — 保留以確保未來匯入過去匯出的加密備份還能解密
注意 1:刪除本機資料不會回溯刪除過去送給 AI 供應商的圖片副本。如需向各供應商請求刪除,請參考其隱私政策。
注意 2:「設定 → 清除所有資料」是本機票券 / 優惠券 / 截圖 / 設定的刪除入口。若您也要要求刪除伺服器端以匿名 Firebase Auth UID 保存的 AI 使用量、獎勵式廣告 / 預算計數或訂閱權益中繼資料,請透過 support@squirrelwallet.app 聯絡我們;因為本 App 沒有帳號系統,若無法對應到特定匿名 UID,我們可能無法辨識您的伺服器端紀錄。
5.4 付費 / 訂閱
Squirrel Pro 透過 Apple App Store / Google Play 的 In-App Purchase 流程購買與還原。Squirrel Stash 不營運自己的付款處理系統,也不接收信用卡號或商店帳號密碼。為了驗證 / 還原 Pro 權益,App 可能將必要的 Apple 訂閱 claim / 平台驗證資料送到 Squirrel 控制的 Firebase Function,並以匿名 Firebase Auth UID 更新 Firebase custom claim / 權益中繼資料;App 也會在本機保存必要的訂閱狀態(方案、到期日、產品 ID)以顯示權益與離線判斷。
6. 兒童隱私
本 App 不刻意收集 13 歲以下兒童的資料。若我們發現意外收集,將立即刪除。
7. 政策變更
本政策若有重大變更,會在 App 內顯示提示,並更新本頁的 "Last updated" 日期。
8. 聯絡我們
- Email: support@squirrelwallet.app
English
1. Who we are
Squirrel Stash is an indie ticket / coupon management app. We have no account system and no login — your ticket / coupon data is stored on your device by default. To enforce the free-tier AI-recognition quota and prevent abuse, we do run a minimal Firebase backend (Cloud Functions + anonymous Firebase Auth + Firestore): Cloud Functions proxy AI requests and reward / budget checks, and record only an anonymous Firebase Auth UID plus AI-usage, ad-reward, budget counters, and subscription entitlement metadata — never your ticket contents, and never personal data such as your name or email. See below for details.
2. What data we touch
| Category | Content | Storage | Leaves device? |
|---|---|---|---|
| Tickets / Coupons | Titles, barcodes, expiry, notes, custom categories | Local Hive DB (AES-256 encrypted) | No |
| Ticket screenshots | Photos you take or import | Local app-private dir | Only when AI recognition is invoked; barcodes are masked first (§3) |
| eSIM LPA | Carrier-provided activation string | Local Hive (AES-256 encrypted) | No (OS handles installation) |
| Paid purchases / subscriptions | Squirrel Pro entitlement status: plan, expiry, product ID; minimal platform data needed for subscription validation | Local SharedPreferences; Apple / Google IAP; Firebase Functions / Firebase custom claims | Purchase is processed by Apple / Google IAP. To validate or restore Pro access, the app may send necessary subscription validation data to a Squirrel-controlled Firebase Function and update custom claims / entitlement metadata keyed by the anonymous Firebase Auth UID. |
| Usage analytics | Not collected in the v1.0 launch build; telemetry SDKs are not initialised | — | No |
| Crash reports | Not collected in the v1.0 launch build; telemetry SDKs are not initialised | — | No |
| Account IDs | We do not require sign-up | — | — |
| Anonymous Firebase UID | A UID created by Firebase anonymous auth to key AI-usage, ad-reward, budget counters, and subscription entitlement metadata | Firebase Auth / Firestore / Firebase custom claims | Yes; this is for App Functionality and abuse prevention, not ad tracking, and it is not a Squirrel-created device identifier |
| Ad IDs / Device IDs | When a Free user starts AI recognition and chooses the ad-supported unlock, the rewarded-ad provider may process advertising or device identifiers under its SDK and platform policies (for example IDFA after ATT consent). Squirrel Stash does not create its own device identifier. | Third-party ad provider; Squirrel Stash does not store its own device ID | Only when you choose the ad-supported unlock; we do not share wallet contents, images, category names, barcodes, or AI recognition results with ad providers for ad targeting |
| Location | Not collected | — | — |
3. AI Recognition (VLM) — Important Disclosure
When you use the "scan ticket via AI" feature, the app sends an image to OpenAI to extract ticket metadata (title, expiry, merchant).
3.1 Barcode masking
Before any upload, the app automatically detects and blacks out every barcode / QR code in the image on-device. This happens entirely on your device — no manual action required.
Masking flow (conceptual pseudocode, illustrative only):
function maskBarcodes(image):
barcodes ← detectBarcodes(image) # local ML model, never leaves device
if barcodes is empty:
return image (no masking needed)
canvas ← decode(image)
for each barcode in barcodes:
bbox ← barcode.bounds + 8px padding # cover the barcode quiet zone
canvas.fillRect(bbox, color=BLACK) # solid black fill
return encodeJpeg(canvas, quality=90)
If detection or pixel-fill fails, the app aborts the upload rather than degrading to the unmasked original. There is no fallback path that sends the original image when masking partially fails.
Rationale: barcodes carry the value of the ticket (redemption codes, eSIM LPA, membership numbers, payment QRs). Masking ensures that even if the provider retains the image, it cannot be used to redeem your tickets.
3.2 Where the masked image goes
| Provider | Model | Use | Privacy policy | Retention |
|---|---|---|---|---|
| OpenAI | gpt-5.4-mini | Free ticket / coupon / eSIM image extraction + image-kind classifier; Pro fallback | openai.com policy | API default 30-day retention, no training |
| OpenAI | gpt-5.4 | Pro ticket / coupon / eSIM primary extraction model | same as above | same as above |
We do not use OpenRouter / Google AI Studio / Groq or any other third-party inference provider. All AI image recognition is proxied to the OpenAI API through a Squirrel-controlled Firebase Cloud Function; the app client does not embed an OpenAI API key. The Cloud Function uses the anonymous Firebase Auth UID for quota, rewarded-ad, and cost controls, but does not store your ticket contents.
We do not control OpenAI's policy changes. Barcode masking is one line of defence — you should still assess the sensitivity of your data. If you prefer no image ever leaves your device, do not use AI recognition; use manual entry or barcode scanning instead (both run entirely on-device).
3.3 What travels with the image (and what doesn't)
To help the model classify accurately, the prompt does include:
- The names of your custom categories (from Settings → Custom categories), so the model recommends an existing category rather than inventing one. If you used a personal tag (company name, partner's name) as a category name, that string travels with the prompt. If you don't want such strings to leave the device, avoid using sensitive text as category names.
- Your active country list from Settings (for eSIM region inference)
- The currently-selected kind (ticket / coupon), to break ambiguous ties
The prompt does NOT send:
- Ticket title / note / barcode payload / QR content (the image itself carries those; no need to duplicate over the wire)
- Other tickets / coupons on your device (each prompt sees the current image only)
- Any Squirrel-created device ID (the app does not create one), Apple ID, Google account; the AI prompt also does not include advertising identifiers or the anonymous Firebase Auth UID. Ad SDK / Firebase backend identifier handling is described in §§2 and 3.
- Other photos from your library — only the one image you select
4. Local data security
- All ticket / coupon data is AES-256 encrypted in the local Hive database
- The encryption key lives in the OS secure store (iOS Keychain / Android Keystore) and is unreadable even on rooted / jailbroken devices
- Screenshots live in the app-private directory, OS-isolated from other apps
5. Your rights (GDPR / TW PDPA)
- Access / Export: Settings → Backup exports all data as an encrypted ZIP
- Modify: Edit in-app
- Delete: Settings → Reset all data permanently removes all local tickets / coupons / screenshots / settings and cancels notifications. Tap the red button → type "刪除" to enable confirm → tap "Reset all data". To request deletion of server-side AI-usage, rewarded-ad / budget counters, or subscription entitlement metadata stored under an anonymous Firebase Auth UID, contact support@squirrelwallet.app; because the app has no account system, we may be unable to identify your server-side records without a matching anonymous UID.
- Paid purchases / subscriptions: Squirrel Pro purchase and restore use Apple / Google IAP. Squirrel Stash does not operate a payment processor and does not receive card numbers or store account credentials. To validate / restore Pro access, the app may send necessary Apple subscription claim / platform validation data to a Squirrel-controlled Firebase Function and update Firebase custom claims / entitlement metadata keyed by the anonymous Firebase Auth UID; the app also stores the minimum local entitlement state needed for display and offline checks.
This does not retroactively delete image copies that AI providers may retain per their own policies. Contact each provider for their deletion process. Settings → Reset all data is the deletion entry point for local data; contact support@squirrelwallet.app for deletion requests covering identifiable server-side anonymous Firebase records.
6. Children's privacy
This app does not knowingly collect data from children under 13. If we discover such collection, we will delete it.
7. Changes to this policy
Material changes will be announced in-app and the "Last updated" date above will be revised.
8. Contact
- Email: support@squirrelwallet.app