Skip to content

Commit 37368d7

Browse files
Merge pull request #5 from easyvibecoding/feat/smart-key-confirmation-v2
feat: Smart Key Extraction confirmation dialog + universal toggles
2 parents 6f9d323 + abb5e8c commit 37368d7

12 files changed

Lines changed: 652 additions & 31 deletions

File tree

CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ These are absolute rules — never violate them:
9191
- **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.
9292
- **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.
9393
- **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.
94+
- **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.
95+
- **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.
96+
- **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.
97+
- **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.
98+
- **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.
99+
- **Toast duration**: 25 seconds (changed from 10s for better visibility during demos).
94100

95101
## Documentation
96102

docs/01-product-spec/implementation-status.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
| React SPA masking || Dialog input 保持隱藏,不替換 value |
8686
| AWS dual-key capture || Access Key ID (DOM) + Secret Key (clipboard) |
8787
| Toast stacking || 連續 capture 堆疊顯示 |
88+
| Smart Key Extraction 確認對話框 || 三區間信心度策略 + 確認 UI + reject 恢復 |
89+
| Universal Masking / Detection || Popup 雙開關,擴展非已知平台 |
90+
| Generic key pattern || 通用 prefix 偵測(confidence 0.50) |
91+
| Turbo 導航防閃現 || turbo:before-render pre-hide |
8892
| Capture Mode (popup) || Start/Stop capture + countdown timer |
8993
| E2E 測試 (8 平台) || GitHub, HuggingFace, GitLab, OpenAI, Anthropic, AI Studio, Google Cloud, AWS |
9094
| Stripe / Slack / SendGrid | 🔶 | Pattern 已定義,未測試 |
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Smart Key Extraction 確認對話框規格
2+
3+
> 最後更新:2026-03-18
4+
5+
## 概述
6+
7+
Smart Key Extraction 確認對話框為 Chrome Extension 的 content script 內嵌 UI,用於處理中信心度 API key 的確認流程。當偵測到的 key 信心度介於 0.35~0.7 之間時,系統先遮蔽該 key,再顯示確認對話框讓使用者決定是否儲存。
8+
9+
---
10+
11+
## 三區間信心度策略
12+
13+
| 信心度範圍 | 動作 | 說明 |
14+
|-----------|------|------|
15+
| >= 0.7(高) | 自動儲存 | 直接送 `submit_captured_key` 至 Core,顯示 toast |
16+
| 0.35 ~ 0.7(中) | 遮蔽 + 確認對話框 | 先遮蔽明文,彈出 inline 對話框讓使用者確認 |
17+
| < 0.35(低) | 忽略 | 不處理,不遮蔽 |
18+
19+
---
20+
21+
## 確認對話框 UI 設計
22+
23+
```
24+
+--------------------------------------------------+
25+
| DemoSafe: Detected possible API key |
26+
| |
27+
| Service name: [__openai______________] (editable)|
28+
| Key preview: sk-proj-...xxxx |
29+
| |
30+
| [ Confirm & Store ] [ Reject ] (27s) |
31+
+--------------------------------------------------+
32+
```
33+
34+
### UI 元素
35+
36+
| 元素 | 說明 |
37+
|------|------|
38+
| 標題 | "DemoSafe: Detected possible API key" |
39+
| Service name | 可編輯文字輸入框,預設為偵測到的 platform 名稱 |
40+
| Key preview | 遮蔽後的 key 預覽(前綴 + ...末四碼) |
41+
| Confirm & Store | 確認按鈕,提交至 Core 儲存 |
42+
| Reject | 拒絕按鈕,恢復原文並加入 rejectedKeys |
43+
| 倒數計時 | 右下角顯示剩餘秒數,30s 自動 dismiss |
44+
45+
### 行為表
46+
47+
| 操作 | 行為 |
48+
|------|------|
49+
| Confirm |`submit_captured_key` 至 background → Core,關閉對話框,key 保持遮蔽 |
50+
| Reject | 恢復該 key 的原始文字,加入 `rejectedKeys` Set,關閉對話框 |
51+
| Escape | 等同 Reject |
52+
| 30s 超時 | 自動 dismiss,key 保持遮蔽狀態(不提交也不恢復) |
53+
54+
---
55+
56+
## 佇列機制
57+
58+
當頁面同時偵測到多個中信心度 key 時,對話框以佇列方式依序顯示:
59+
60+
1. 第一個 key 的對話框立即顯示
61+
2. 後續 key 加入 `pendingConfirmations` 佇列
62+
3. 前一個對話框關閉後,自動彈出下一個
63+
4. 每個對話框各自有獨立的 30s 倒數
64+
65+
---
66+
67+
## 去重機制
68+
69+
三組 Set 防止重複處理:
70+
71+
| Set | 作用 | 生命週期 |
72+
|-----|------|---------|
73+
| `submittedKeys` | 已提交至 Core 的 key(高信心度自動 + 確認) | 頁面生命週期 |
74+
| `rejectedKeys` | 使用者拒絕的 key | 頁面生命週期 |
75+
| `isAlreadyStoredKey()` | 查詢 Core 已知 key(透過 pattern 匹配) | 即時查詢 |
76+
77+
流程:偵測到 key → 檢查是否在 submittedKeys / rejectedKeys / isAlreadyStoredKey → 若已存在則跳過 → 否則依信心度分流。
78+
79+
---
80+
81+
## Universal Masking / Detection 開關
82+
83+
### Popup UI
84+
85+
兩個獨立 toggle 開關位於 popup 面板:
86+
87+
- **Universal Masking**:在非已知平台頁面也進行 DOM 遮蔽(預設 OFF)
88+
- **Universal Detection**:在非已知平台頁面也進行 key 偵測(預設 OFF)
89+
90+
### 預設行為
91+
92+
- 已知平台(capture-patterns.ts 定義的 11+ 平台):Demo Mode 開啟時自動啟用 masking 和 detection,不受 toggle 影響
93+
- 非已知平台:僅在 toggle 開啟時啟用對應功能
94+
95+
### 決策函式
96+
97+
| 函式 | 邏輯 |
98+
|------|------|
99+
| `shouldMask(url)` | `isSupportedPlatform(url) \|\| universalMaskingEnabled` |
100+
| `shouldAutoCapture(url)` | `isSupportedPlatform(url) \|\| universalDetectionEnabled` |
101+
102+
### 儲存
103+
104+
使用 `chrome.storage.local` 儲存,key 為 `universalMasking``universalDetection`
105+
106+
---
107+
108+
## Generic Key Pattern
109+
110+
| 屬性 ||
111+
|------|---|
112+
| pattern ID | `generic-key` |
113+
| confidence | 0.50 |
114+
| prefix | `""` (空字串) |
115+
| 匹配規則 | 常見 prefix(key-, token-, api-, secret-, sk-, pk-, rk-)+ 30+ 字元英數 |
116+
| 特殊處理 | `KEY_PREFIXES` 過濾空字串 prefix,避免 `containsFullKey()` 誤判 |
117+
118+
此 pattern 用於捕獲非已知平台的 API key,搭配 Universal Detection 使用。因 confidence 為 0.50(中區間),會觸發確認對話框。
119+
120+
---
121+
122+
## IPC 流程圖(中信心度 key)
123+
124+
```
125+
Content Script Background SW Core Engine
126+
| | |
127+
| 偵測到 key (0.35~0.7) | |
128+
| 遮蔽明文 | |
129+
| 顯示確認對話框 | |
130+
| | |
131+
| [使用者按 Confirm] | |
132+
| | |
133+
|-- submit_captured_key ------->| |
134+
| {key, serviceName} | |
135+
| |-- submit_captured_key -->|
136+
| | {key, serviceName} |
137+
| | |
138+
| |<-- key_stored ----------|
139+
| | {keyId, masked} |
140+
|<-- key_stored ---------------| |
141+
| | |
142+
| 顯示 toast | |
143+
```
144+
145+
---
146+
147+
## 新增 Message Types
148+
149+
| Type | 方向 | Payload | 說明 |
150+
|------|------|---------|------|
151+
| `submit_captured_key` | Content → BG → Core | `{ key: string, serviceName: string, platform: string }` | 提交捕獲的 key |
152+
| `key_stored` | Core → BG → Content | `{ keyId: string, masked: string }` | key 已儲存確認 |
153+
| `get_universal_settings` | Popup → BG | `{}` | 取得 universal toggle 狀態 |
154+
| `set_universal_settings` | Popup → BG | `{ masking: boolean, detection: boolean }` | 設定 universal toggle |
155+
| `universal_settings_changed` | BG → Content | `{ masking: boolean, detection: boolean }` | 廣播 toggle 狀態變更 |
156+
157+
---
158+
159+
## 檔案變更表
160+
161+
| 檔案 | 變更 |
162+
|------|------|
163+
| `packages/chrome-extension/src/content/masker.ts` | 三區間信心度判斷、確認對話框 UI、佇列機制、rejectedKeys |
164+
| `packages/chrome-extension/src/content/confirmation-dialog.ts` | 對話框元件:建立、事件、倒數、Escape |
165+
| `packages/chrome-extension/src/content/capture-patterns.ts` | 新增 `generic-key` pattern |
166+
| `packages/chrome-extension/src/background/service-worker.ts` | universal settings 儲存/廣播 |
167+
| `packages/chrome-extension/src/popup/popup.ts` | Universal Masking / Detection toggle UI |
168+
| `packages/chrome-extension/src/popup/popup.html` | toggle HTML 結構 |
169+
| `packages/chrome-extension/src/content/pre-hide.ts` | `turbo:before-render` 監聽 |
170+
| `packages/chrome-extension/src/content/toast.ts` | 持續時間改為 25s |

docs/en/01-product-spec/implementation-status.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@
8585
| React SPA masking || Dialog inputs stay hidden, no value replacement |
8686
| AWS dual-key capture || Access Key ID (DOM) + Secret Key (clipboard) |
8787
| Toast stacking || Consecutive captures show stacked toasts |
88+
| Smart Key Extraction confirmation dialog || Three-tier confidence strategy + confirmation UI + reject restore |
89+
| Universal Masking / Detection || Popup dual toggles, extend to non-supported platforms |
90+
| Generic key pattern || Generic prefix detection (confidence 0.50) |
91+
| Turbo navigation anti-flash || turbo:before-render pre-hide |
8892
| Capture Mode (popup) || Start/Stop capture + countdown timer |
8993
| E2E tested (8 platforms) || GitHub, HuggingFace, GitLab, OpenAI, Anthropic, AI Studio, Google Cloud, AWS |
9094
| Stripe / Slack / SendGrid | 🔶 | Patterns defined, untested |
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Smart Key Extraction Confirmation Dialog Spec
2+
3+
> Last updated: 2026-03-18
4+
5+
## Overview
6+
7+
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.
8+
9+
---
10+
11+
## Three-Tier Confidence Strategy
12+
13+
| Confidence Range | Action | Description |
14+
|-----------------|--------|-------------|
15+
| >= 0.7 (high) | Auto-store | Send `submit_captured_key` to Core directly, show toast |
16+
| 0.35 ~ 0.7 (medium) | Mask + confirmation dialog | Mask plaintext first, show inline dialog for user confirmation |
17+
| < 0.35 (low) | Ignore | No processing, no masking |
18+
19+
---
20+
21+
## Confirmation Dialog UI Design
22+
23+
```
24+
+--------------------------------------------------+
25+
| DemoSafe: Detected possible API key |
26+
| |
27+
| Service name: [__openai______________] (editable)|
28+
| Key preview: sk-proj-...xxxx |
29+
| |
30+
| [ Confirm & Store ] [ Reject ] (27s) |
31+
+--------------------------------------------------+
32+
```
33+
34+
### UI Elements
35+
36+
| Element | Description |
37+
|---------|-------------|
38+
| Title | "DemoSafe: Detected possible API key" |
39+
| Service name | Editable text input, defaults to detected platform name |
40+
| Key preview | Masked key preview (prefix + ...last 4 chars) |
41+
| Confirm & Store | Confirm button, submits to Core for storage |
42+
| Reject | Reject button, restores original text and adds to rejectedKeys |
43+
| Countdown | Bottom-right displays remaining seconds, 30s auto-dismiss |
44+
45+
### Behavior Table
46+
47+
| Action | Behavior |
48+
|--------|----------|
49+
| Confirm | Send `submit_captured_key` to background -> Core, close dialog, key stays masked |
50+
| Reject | Restore key's original text, add to `rejectedKeys` Set, close dialog |
51+
| Escape | Same as Reject |
52+
| 30s timeout | Auto-dismiss, key stays masked (neither submitted nor restored) |
53+
54+
---
55+
56+
## Queue Mechanism
57+
58+
When multiple medium-confidence keys are detected simultaneously on a page, dialogs are shown sequentially via a queue:
59+
60+
1. First key's dialog appears immediately
61+
2. Subsequent keys are added to the `pendingConfirmations` queue
62+
3. After previous dialog closes, the next one automatically appears
63+
4. Each dialog has its own independent 30s countdown
64+
65+
---
66+
67+
## Deduplication Mechanism
68+
69+
Three Sets prevent duplicate processing:
70+
71+
| Set | Purpose | Lifetime |
72+
|-----|---------|----------|
73+
| `submittedKeys` | Keys already submitted to Core (high-confidence auto + confirmed) | Page lifetime |
74+
| `rejectedKeys` | Keys rejected by the user | Page lifetime |
75+
| `isAlreadyStoredKey()` | Query Core for known keys (via pattern matching) | Real-time query |
76+
77+
Flow: key detected -> check submittedKeys / rejectedKeys / isAlreadyStoredKey -> skip if exists -> otherwise route by confidence tier.
78+
79+
---
80+
81+
## Universal Masking / Detection Toggles
82+
83+
### Popup UI
84+
85+
Two independent toggle switches in the popup panel:
86+
87+
- **Universal Masking**: Enable DOM masking on non-supported platform pages (default OFF)
88+
- **Universal Detection**: Enable key detection on non-supported platform pages (default OFF)
89+
90+
### Default Behavior
91+
92+
- Supported platforms (11+ platforms defined in capture-patterns.ts): Demo Mode automatically enables masking and detection, unaffected by toggles
93+
- Non-supported platforms: Only enabled when corresponding toggle is ON
94+
95+
### Decision Functions
96+
97+
| Function | Logic |
98+
|----------|-------|
99+
| `shouldMask(url)` | `isSupportedPlatform(url) \|\| universalMaskingEnabled` |
100+
| `shouldAutoCapture(url)` | `isSupportedPlatform(url) \|\| universalDetectionEnabled` |
101+
102+
### Storage
103+
104+
Stored in `chrome.storage.local` with keys `universalMasking` and `universalDetection`.
105+
106+
---
107+
108+
## Generic Key Pattern
109+
110+
| Property | Value |
111+
|----------|-------|
112+
| Pattern ID | `generic-key` |
113+
| Confidence | 0.50 |
114+
| Prefix | `""` (empty string) |
115+
| Match rule | Common prefixes (key-, token-, api-, secret-, sk-, pk-, rk-) + 30+ alphanumeric chars |
116+
| Special handling | `KEY_PREFIXES` filters empty string prefix to prevent `containsFullKey()` false positives |
117+
118+
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.
119+
120+
---
121+
122+
## IPC Flow Diagram (Medium-Confidence Key)
123+
124+
```
125+
Content Script Background SW Core Engine
126+
| | |
127+
| Key detected (0.35~0.7) | |
128+
| Mask plaintext | |
129+
| Show confirmation dialog | |
130+
| | |
131+
| [User clicks Confirm] | |
132+
| | |
133+
|-- submit_captured_key ------->| |
134+
| {key, serviceName} | |
135+
| |-- submit_captured_key -->|
136+
| | {key, serviceName} |
137+
| | |
138+
| |<-- key_stored ----------|
139+
| | {keyId, masked} |
140+
|<-- key_stored ---------------| |
141+
| | |
142+
| Show toast | |
143+
```
144+
145+
---
146+
147+
## New Message Types
148+
149+
| Type | Direction | Payload | Description |
150+
|------|-----------|---------|-------------|
151+
| `submit_captured_key` | Content -> BG -> Core | `{ key: string, serviceName: string, platform: string }` | Submit captured key |
152+
| `key_stored` | Core -> BG -> Content | `{ keyId: string, masked: string }` | Key stored confirmation |
153+
| `get_universal_settings` | Popup -> BG | `{}` | Get universal toggle states |
154+
| `set_universal_settings` | Popup -> BG | `{ masking: boolean, detection: boolean }` | Set universal toggles |
155+
| `universal_settings_changed` | BG -> Content | `{ masking: boolean, detection: boolean }` | Broadcast toggle state changes |
156+
157+
---
158+
159+
## File Changes Table
160+
161+
| File | Changes |
162+
|------|---------|
163+
| `packages/chrome-extension/src/content/masker.ts` | Three-tier confidence routing, confirmation dialog UI, queue mechanism, rejectedKeys |
164+
| `packages/chrome-extension/src/content/confirmation-dialog.ts` | Dialog component: creation, events, countdown, Escape |
165+
| `packages/chrome-extension/src/content/capture-patterns.ts` | Add `generic-key` pattern |
166+
| `packages/chrome-extension/src/background/service-worker.ts` | Universal settings storage/broadcast |
167+
| `packages/chrome-extension/src/popup/popup.ts` | Universal Masking / Detection toggle UI |
168+
| `packages/chrome-extension/src/popup/popup.html` | Toggle HTML structure |
169+
| `packages/chrome-extension/src/content/pre-hide.ts` | `turbo:before-render` listener |
170+
| `packages/chrome-extension/src/content/toast.ts` | Duration changed to 25s |

0 commit comments

Comments
 (0)