Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ These are absolute rules — never violate them:
- **NMH no plaintext queue**: When both WS and NMH fail for `submit_captured_key`, the key is NOT queued to `chrome.storage.local`. Storing plaintext API keys in browser storage violates security red line #1. The key is lost and must be re-captured.
- **NMH isConnected semantics**: NMH relay success does NOT set `state.isConnected = true` — NMH is a one-shot relay, not a persistent connection. Popup shows "Connected (NMH)" via `connectionPath` field only, while WS reconnect continues independently.
- **NMHInstaller**: Core auto-installs NMH binary + Chrome manifest from app bundle Resources on startup. Uses binary file size comparison for version checking. `install.sh` retained as manual fallback.
- **Smart Key Extraction confirmation dialog**: Three-tier confidence strategy: >= 0.7 auto-store, 0.35~0.7 mask + confirmation dialog, < 0.35 ignore. Dialog is inline content script overlay with editable service name, 30s auto-dismiss, Escape support, queue for multiple pending. rejectedKeys Set persists for page lifetime.
- **Universal Masking/Detection toggles**: Two popup toggles (default OFF) extend masking and detection to non-supported platforms. Supported platforms unaffected — Demo Mode auto-enables both. Stored in chrome.storage.local.
- **Generic key pattern**: `generic-key` pattern (confidence 0.50) matches common prefixes (key-, token-, api-, secret-, sk-, pk-, rk-) + 30+ char alphanumeric. prefix is empty string — KEY_PREFIXES filters empty prefixes to prevent containsFullKey() false positives.
- **Turbo navigation pre-hide**: `turbo:before-render` listener in pre-hide.ts hides key elements in incoming body BEFORE Turbo renders. Prevents GitHub PAT flash on partial page transition.
- **OpenAI pre-hide scope**: preHideCSS must NOT include `td.api-key-token .api-key-token-value` — these are truncated previews (sk-...QngA) that never match full patterns, causing permanent hidden state.
- **Toast duration**: 25 seconds (changed from 10s for better visibility during demos).

## Documentation

Expand Down
4 changes: 4 additions & 0 deletions docs/01-product-spec/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
| React SPA masking | ✅ | Dialog input 保持隱藏,不替換 value |
| AWS dual-key capture | ✅ | Access Key ID (DOM) + Secret Key (clipboard) |
| Toast stacking | ✅ | 連續 capture 堆疊顯示 |
| Smart Key Extraction 確認對話框 | ✅ | 三區間信心度策略 + 確認 UI + reject 恢復 |
| Universal Masking / Detection | ✅ | Popup 雙開關,擴展非已知平台 |
| Generic key pattern | ✅ | 通用 prefix 偵測(confidence 0.50) |
| Turbo 導航防閃現 | ✅ | turbo:before-render pre-hide |
| Capture Mode (popup) | ✅ | Start/Stop capture + countdown timer |
| E2E 測試 (8 平台) | ✅ | GitHub, HuggingFace, GitLab, OpenAI, Anthropic, AI Studio, Google Cloud, AWS |
| Stripe / Slack / SendGrid | 🔶 | Pattern 已定義,未測試 |
Expand Down
170 changes: 170 additions & 0 deletions docs/04-ui-ux/confirmation-dialog-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Smart Key Extraction 確認對話框規格

> 最後更新:2026-03-18

## 概述

Smart Key Extraction 確認對話框為 Chrome Extension 的 content script 內嵌 UI,用於處理中信心度 API key 的確認流程。當偵測到的 key 信心度介於 0.35~0.7 之間時,系統先遮蔽該 key,再顯示確認對話框讓使用者決定是否儲存。

---

## 三區間信心度策略

| 信心度範圍 | 動作 | 說明 |
|-----------|------|------|
| >= 0.7(高) | 自動儲存 | 直接送 `submit_captured_key` 至 Core,顯示 toast |
| 0.35 ~ 0.7(中) | 遮蔽 + 確認對話框 | 先遮蔽明文,彈出 inline 對話框讓使用者確認 |
| < 0.35(低) | 忽略 | 不處理,不遮蔽 |

---

## 確認對話框 UI 設計

```
+--------------------------------------------------+
| DemoSafe: Detected possible API key |
| |
| Service name: [__openai______________] (editable)|
| Key preview: sk-proj-...xxxx |
| |
| [ Confirm & Store ] [ Reject ] (27s) |
+--------------------------------------------------+
```

### UI 元素

| 元素 | 說明 |
|------|------|
| 標題 | "DemoSafe: Detected possible API key" |
| Service name | 可編輯文字輸入框,預設為偵測到的 platform 名稱 |
| Key preview | 遮蔽後的 key 預覽(前綴 + ...末四碼) |
| Confirm & Store | 確認按鈕,提交至 Core 儲存 |
| Reject | 拒絕按鈕,恢復原文並加入 rejectedKeys |
| 倒數計時 | 右下角顯示剩餘秒數,30s 自動 dismiss |

### 行為表

| 操作 | 行為 |
|------|------|
| Confirm | 送 `submit_captured_key` 至 background → Core,關閉對話框,key 保持遮蔽 |
| Reject | 恢復該 key 的原始文字,加入 `rejectedKeys` Set,關閉對話框 |
| Escape | 等同 Reject |
| 30s 超時 | 自動 dismiss,key 保持遮蔽狀態(不提交也不恢復) |

---

## 佇列機制

當頁面同時偵測到多個中信心度 key 時,對話框以佇列方式依序顯示:

1. 第一個 key 的對話框立即顯示
2. 後續 key 加入 `pendingConfirmations` 佇列
3. 前一個對話框關閉後,自動彈出下一個
4. 每個對話框各自有獨立的 30s 倒數

---

## 去重機制

三組 Set 防止重複處理:

| Set | 作用 | 生命週期 |
|-----|------|---------|
| `submittedKeys` | 已提交至 Core 的 key(高信心度自動 + 確認) | 頁面生命週期 |
| `rejectedKeys` | 使用者拒絕的 key | 頁面生命週期 |
| `isAlreadyStoredKey()` | 查詢 Core 已知 key(透過 pattern 匹配) | 即時查詢 |

流程:偵測到 key → 檢查是否在 submittedKeys / rejectedKeys / isAlreadyStoredKey → 若已存在則跳過 → 否則依信心度分流。

---

## Universal Masking / Detection 開關

### Popup UI

兩個獨立 toggle 開關位於 popup 面板:

- **Universal Masking**:在非已知平台頁面也進行 DOM 遮蔽(預設 OFF)
- **Universal Detection**:在非已知平台頁面也進行 key 偵測(預設 OFF)

### 預設行為

- 已知平台(capture-patterns.ts 定義的 11+ 平台):Demo Mode 開啟時自動啟用 masking 和 detection,不受 toggle 影響
- 非已知平台:僅在 toggle 開啟時啟用對應功能

### 決策函式

| 函式 | 邏輯 |
|------|------|
| `shouldMask(url)` | `isSupportedPlatform(url) \|\| universalMaskingEnabled` |
| `shouldAutoCapture(url)` | `isSupportedPlatform(url) \|\| universalDetectionEnabled` |

### 儲存

使用 `chrome.storage.local` 儲存,key 為 `universalMasking` 和 `universalDetection`。

---

## Generic Key Pattern

| 屬性 | 值 |
|------|---|
| pattern ID | `generic-key` |
| confidence | 0.50 |
| prefix | `""` (空字串) |
| 匹配規則 | 常見 prefix(key-, token-, api-, secret-, sk-, pk-, rk-)+ 30+ 字元英數 |
| 特殊處理 | `KEY_PREFIXES` 過濾空字串 prefix,避免 `containsFullKey()` 誤判 |

此 pattern 用於捕獲非已知平台的 API key,搭配 Universal Detection 使用。因 confidence 為 0.50(中區間),會觸發確認對話框。

---

## IPC 流程圖(中信心度 key)

```
Content Script Background SW Core Engine
| | |
| 偵測到 key (0.35~0.7) | |
| 遮蔽明文 | |
| 顯示確認對話框 | |
| | |
| [使用者按 Confirm] | |
| | |
|-- submit_captured_key ------->| |
| {key, serviceName} | |
| |-- submit_captured_key -->|
| | {key, serviceName} |
| | |
| |<-- key_stored ----------|
| | {keyId, masked} |
|<-- key_stored ---------------| |
| | |
| 顯示 toast | |
```

---

## 新增 Message Types

| Type | 方向 | Payload | 說明 |
|------|------|---------|------|
| `submit_captured_key` | Content → BG → Core | `{ key: string, serviceName: string, platform: string }` | 提交捕獲的 key |
| `key_stored` | Core → BG → Content | `{ keyId: string, masked: string }` | key 已儲存確認 |
| `get_universal_settings` | Popup → BG | `{}` | 取得 universal toggle 狀態 |
| `set_universal_settings` | Popup → BG | `{ masking: boolean, detection: boolean }` | 設定 universal toggle |
| `universal_settings_changed` | BG → Content | `{ masking: boolean, detection: boolean }` | 廣播 toggle 狀態變更 |

---

## 檔案變更表

| 檔案 | 變更 |
|------|------|
| `packages/chrome-extension/src/content/masker.ts` | 三區間信心度判斷、確認對話框 UI、佇列機制、rejectedKeys |
| `packages/chrome-extension/src/content/confirmation-dialog.ts` | 對話框元件:建立、事件、倒數、Escape |
| `packages/chrome-extension/src/content/capture-patterns.ts` | 新增 `generic-key` pattern |
| `packages/chrome-extension/src/background/service-worker.ts` | universal settings 儲存/廣播 |
| `packages/chrome-extension/src/popup/popup.ts` | Universal Masking / Detection toggle UI |
| `packages/chrome-extension/src/popup/popup.html` | toggle HTML 結構 |
| `packages/chrome-extension/src/content/pre-hide.ts` | `turbo:before-render` 監聽 |
| `packages/chrome-extension/src/content/toast.ts` | 持續時間改為 25s |
4 changes: 4 additions & 0 deletions docs/en/01-product-spec/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
| React SPA masking | ✅ | Dialog inputs stay hidden, no value replacement |
| AWS dual-key capture | ✅ | Access Key ID (DOM) + Secret Key (clipboard) |
| Toast stacking | ✅ | Consecutive captures show stacked toasts |
| Smart Key Extraction confirmation dialog | ✅ | Three-tier confidence strategy + confirmation UI + reject restore |
| Universal Masking / Detection | ✅ | Popup dual toggles, extend to non-supported platforms |
| Generic key pattern | ✅ | Generic prefix detection (confidence 0.50) |
| Turbo navigation anti-flash | ✅ | turbo:before-render pre-hide |
| Capture Mode (popup) | ✅ | Start/Stop capture + countdown timer |
| E2E tested (8 platforms) | ✅ | GitHub, HuggingFace, GitLab, OpenAI, Anthropic, AI Studio, Google Cloud, AWS |
| Stripe / Slack / SendGrid | 🔶 | Patterns defined, untested |
Expand Down
170 changes: 170 additions & 0 deletions docs/en/04-ui-ux/confirmation-dialog-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Smart Key Extraction Confirmation Dialog Spec

> Last updated: 2026-03-18

## Overview

The Smart Key Extraction confirmation dialog is an inline content script UI in the Chrome Extension. When a detected key's confidence falls between 0.35 and 0.7 (medium tier), the system masks the key first, then displays a confirmation dialog for the user to decide whether to store it.

---

## Three-Tier Confidence Strategy

| Confidence Range | Action | Description |
|-----------------|--------|-------------|
| >= 0.7 (high) | Auto-store | Send `submit_captured_key` to Core directly, show toast |
| 0.35 ~ 0.7 (medium) | Mask + confirmation dialog | Mask plaintext first, show inline dialog for user confirmation |
| < 0.35 (low) | Ignore | No processing, no masking |

---

## Confirmation Dialog UI Design

```
+--------------------------------------------------+
| DemoSafe: Detected possible API key |
| |
| Service name: [__openai______________] (editable)|
| Key preview: sk-proj-...xxxx |
| |
| [ Confirm & Store ] [ Reject ] (27s) |
+--------------------------------------------------+
```

### UI Elements

| Element | Description |
|---------|-------------|
| Title | "DemoSafe: Detected possible API key" |
| Service name | Editable text input, defaults to detected platform name |
| Key preview | Masked key preview (prefix + ...last 4 chars) |
| Confirm & Store | Confirm button, submits to Core for storage |
| Reject | Reject button, restores original text and adds to rejectedKeys |
| Countdown | Bottom-right displays remaining seconds, 30s auto-dismiss |

### Behavior Table

| Action | Behavior |
|--------|----------|
| Confirm | Send `submit_captured_key` to background -> Core, close dialog, key stays masked |
| Reject | Restore key's original text, add to `rejectedKeys` Set, close dialog |
| Escape | Same as Reject |
| 30s timeout | Auto-dismiss, key stays masked (neither submitted nor restored) |

---

## Queue Mechanism

When multiple medium-confidence keys are detected simultaneously on a page, dialogs are shown sequentially via a queue:

1. First key's dialog appears immediately
2. Subsequent keys are added to the `pendingConfirmations` queue
3. After previous dialog closes, the next one automatically appears
4. Each dialog has its own independent 30s countdown

---

## Deduplication Mechanism

Three Sets prevent duplicate processing:

| Set | Purpose | Lifetime |
|-----|---------|----------|
| `submittedKeys` | Keys already submitted to Core (high-confidence auto + confirmed) | Page lifetime |
| `rejectedKeys` | Keys rejected by the user | Page lifetime |
| `isAlreadyStoredKey()` | Query Core for known keys (via pattern matching) | Real-time query |

Flow: key detected -> check submittedKeys / rejectedKeys / isAlreadyStoredKey -> skip if exists -> otherwise route by confidence tier.

---

## Universal Masking / Detection Toggles

### Popup UI

Two independent toggle switches in the popup panel:

- **Universal Masking**: Enable DOM masking on non-supported platform pages (default OFF)
- **Universal Detection**: Enable key detection on non-supported platform pages (default OFF)

### Default Behavior

- Supported platforms (11+ platforms defined in capture-patterns.ts): Demo Mode automatically enables masking and detection, unaffected by toggles
- Non-supported platforms: Only enabled when corresponding toggle is ON

### Decision Functions

| Function | Logic |
|----------|-------|
| `shouldMask(url)` | `isSupportedPlatform(url) \|\| universalMaskingEnabled` |
| `shouldAutoCapture(url)` | `isSupportedPlatform(url) \|\| universalDetectionEnabled` |

### Storage

Stored in `chrome.storage.local` with keys `universalMasking` and `universalDetection`.

---

## Generic Key Pattern

| Property | Value |
|----------|-------|
| Pattern ID | `generic-key` |
| Confidence | 0.50 |
| Prefix | `""` (empty string) |
| Match rule | Common prefixes (key-, token-, api-, secret-, sk-, pk-, rk-) + 30+ alphanumeric chars |
| Special handling | `KEY_PREFIXES` filters empty string prefix to prevent `containsFullKey()` false positives |

This pattern captures API keys on non-supported platforms, used in conjunction with Universal Detection. Since confidence is 0.50 (medium tier), it triggers the confirmation dialog.

---

## IPC Flow Diagram (Medium-Confidence Key)

```
Content Script Background SW Core Engine
| | |
| Key detected (0.35~0.7) | |
| Mask plaintext | |
| Show confirmation dialog | |
| | |
| [User clicks Confirm] | |
| | |
|-- submit_captured_key ------->| |
| {key, serviceName} | |
| |-- submit_captured_key -->|
| | {key, serviceName} |
| | |
| |<-- key_stored ----------|
| | {keyId, masked} |
|<-- key_stored ---------------| |
| | |
| Show toast | |
```

---

## New Message Types

| Type | Direction | Payload | Description |
|------|-----------|---------|-------------|
| `submit_captured_key` | Content -> BG -> Core | `{ key: string, serviceName: string, platform: string }` | Submit captured key |
| `key_stored` | Core -> BG -> Content | `{ keyId: string, masked: string }` | Key stored confirmation |
| `get_universal_settings` | Popup -> BG | `{}` | Get universal toggle states |
| `set_universal_settings` | Popup -> BG | `{ masking: boolean, detection: boolean }` | Set universal toggles |
| `universal_settings_changed` | BG -> Content | `{ masking: boolean, detection: boolean }` | Broadcast toggle state changes |

---

## File Changes Table

| File | Changes |
|------|---------|
| `packages/chrome-extension/src/content/masker.ts` | Three-tier confidence routing, confirmation dialog UI, queue mechanism, rejectedKeys |
| `packages/chrome-extension/src/content/confirmation-dialog.ts` | Dialog component: creation, events, countdown, Escape |
| `packages/chrome-extension/src/content/capture-patterns.ts` | Add `generic-key` pattern |
| `packages/chrome-extension/src/background/service-worker.ts` | Universal settings storage/broadcast |
| `packages/chrome-extension/src/popup/popup.ts` | Universal Masking / Detection toggle UI |
| `packages/chrome-extension/src/popup/popup.html` | Toggle HTML structure |
| `packages/chrome-extension/src/content/pre-hide.ts` | `turbo:before-render` listener |
| `packages/chrome-extension/src/content/toast.ts` | Duration changed to 25s |
Loading
Loading