diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-09-01 20:10:20 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-09-01 20:10:20 +0900 |
| commit | 55e581bab9b600e7c070ec596d3109fc897d2d86 (patch) | |
| tree | 15ca196a1858696c216946dc9608fae613ad6e5f /doc | |
feat: Firefox selection translation extension with local Ollama (initial version)
Implement the complete initial version of a Firefox WebExtension (Manifest V3)
that translates selected text on web pages using a local Ollama LLM and shows
the result in a small popover near the selection.
Specification alignment (Phase 0)
- Unified Specifications.md / Design.md / Security.md on the same approach:
Ollama REST API + Fetch API + NDJSON streaming (no ollama-js SDK dependency)
- Fixed host_permissions to http://localhost/* and http://127.0.0.1/* because
Firefox match patterns do not support port numbers (bug 1362809/1468162);
the actual connection target is restricted to localhost/127.0.0.1 + any port
by application-side validation (defense in depth, OLL-09)
- Clipboard: use navigator.clipboard.writeText() only inside the copy button
click (user activation); no deprecated execCommand fallback
Implementation (zero dependencies, vanilla JS, no npm/Node required)
- manifest.json: MV3, background.scripts + type:module (event page, no
service_worker), action popup, options_ui, content_scripts for http/https,
permissions: storage + localhost host permissions only
- src/shared: constants (ports, limits, presets, error codes), message
validation, settings schema (defaults / merge / sanitize / buildOptions /
toApiThinking), system prompt builder (fixed prompt first + user prompt)
- src/background: message-router (runtime.Port streaming + runtime.onMessage
for model list), translation-session (single active request, abort previous
on new request, stale chunk suppression), ollama-client (fetch against
/api/tags and /api/chat with buffering NDJSON line parser tolerant to chunk
boundaries inside JSON objects, AbortController, host re-validation before
every request, HTTP status to error-code mapping), settings-service
(storage.local load/save with validation and fallback to defaults)
- src/content/content.js: single classic script (Firefox does not support ES
modules in content scripts) with selection detection (selectionchange +
mouseup + keyup, debounced), Shadow DOM action button, Shadow DOM popover
with HIDDEN/LOADING/STREAMING/COMPLETED/ERROR state machine, port client
with requestId-based stale chunk filtering, enabled-state sync via
storage.onChanged; all dynamic text rendered via textContent only
- src/popup + src/options: full settings UI (enable toggle, Ollama host,
model list from Ollama with saved-model warning, target language, thinking
mode, temperature slider 0.0-2.0 step 0.1, context length presets
4k/8k/16k/32k/128k/256k, seed random/fixed with integer value, user system
prompt, advanced numeric options with steppers, stop sequences textarea)
- icons generated by tools/make-icons.ps1 (System.Drawing, no external tools)
- build.ps1 packages manifest.json + icons/ + src/ into dist zip, excluding
tests/, doc/, ollama-js/ and tools/
Tests (71 passed, 0 failed)
- In-browser test runner (tests/test.html) with custom describe/it/assert
framework; no npm, no Node.js
- Unit: constants sync between content mirror and shared module, Ollama host
validation (localhost/127.0.0.1 + any port only; rejects external hosts,
LAN IPs, https, credentials, paths), settings schema defaults and fallback,
temperature 0.1-step validation, context length presets, seed modes,
advanced option types, buildOptions omission of unset values and absence of
tfs_z/mirostat fields, system prompt composition order, message validation
with selection length boundary values
- Integration: NDJSON streaming across awkward byte boundaries, thinking
chunk forwarding, malformed line tolerance, error payload mapping, think/
options inclusion rules, model list, TranslationSession abort semantics,
settings round-trip with storage.onChanged cache refresh, full port flow
(started/chunk/completed), newer request aborts older with stale chunk
discard, malformed message rejection, OLLAMA_UNAVAILABLE and
INVALID_SETTINGS error paths, untrusted sender rejection
Security audit (Design.md section 24 checklist: all pass)
- No innerHTML/outerHTML/document.write/eval/new Function/localStorage
- No remote JS/CSS/fonts/CDN resources; no inline scripts in extension pages
- Ollama access limited to background; content scripts never fetch Ollama
- Background validates message type/length/requestId; single concurrent
translation enforced
- Clipboard API only (no execCommand fallback)
- Minimal permissions: storage + localhost host permissions
Docs
- README.md with build/load/usage instructions, API list, permission rationale
- doc/Testing-Results.md with automated test results, security audit results,
and a 14-item manual Firefox verification checklist (to be executed on a
real Firefox + Ollama environment)
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/Design.md | 871 | ||||
| -rw-r--r-- | doc/Security.md | 804 | ||||
| -rw-r--r-- | doc/Specifications.md | 391 | ||||
| -rw-r--r-- | doc/Testing-Results.md | 87 |
4 files changed, 2153 insertions, 0 deletions
diff --git a/doc/Design.md b/doc/Design.md new file mode 100644 index 0000000..20de3e5 --- /dev/null +++ b/doc/Design.md @@ -0,0 +1,871 @@ +# Design.md + +## 1. 設計概要 + +本拡張は、Webページとの接点をContent Scriptに限定し、権限を伴うOllama通信と設定管理をBackgroundへ分離する。 + +```text +Web Page + │ + │ selection + ▼ +Content Script + │ runtime.Port + ▼ +Background + │ fetch / HTTP + ▼ +Ollama + │ streaming + ▼ +Background + │ runtime.Port + ▼ +Content Script + │ + ▼ +Shadow DOM Popover +``` + +設計原則: +- ページ由来データは信頼しない。 +- Content Scriptは最小責務とする。 +- Ollama通信はBackgroundのみ(Ollama REST API + Fetch API + NDJSON streaming)。 +- UI文字列はHTMLとして評価しない。 +- 翻訳要求は常に1-shot。 +- 同時翻訳は1件に制限。 +- 設定値はUIだけでなくBackgroundでも検証する。 +- Ollama接続先は初期版ではlocalhost / 127.0.0.1 + 任意ポートのみ許可する。 + +--- + +## 2. コンポーネント構成 + +| コンポーネント | 主責務 | +|---|---| +| `content/selection-controller` | Selection検出、Range位置取得 | +| `content/action-button` | 翻訳アイコン表示/非表示 | +| `content/translation-popover` | 翻訳結果UI、stream描画、copy/regenerate/close | +| `content/port-client` | Backgroundとの長期接続 | +| `background/message-router` | Content Scriptからのrequest受信・validation | +| `background/translation-session` | requestId、AbortController、active stream管理 | +| `background/ollama-client` | Ollama REST API呼び出し(fetch + NDJSON streaming) | +| `background/settings-service` | storage読書き、default、validation | +| `popup` | 有効/無効、簡易設定、Options起動 | +| `options` | 全設定UI | +| `shared/settings-schema` | 設定型、既定値、範囲 | +| `shared/messages` | runtime message型定義 | +| `shared/constants` | 最大選択長等 | + +--- + +## 3. 推奨ディレクトリ構成 + +```text +/ +├─ manifest.json +├─ package.json +├─ src/ +│ ├─ background/ +│ │ ├─ index.js +│ │ ├─ message-router.js +│ │ ├─ translation-session.js +│ │ ├─ ollama-client.js +│ │ └─ settings-service.js +│ ├─ content/ +│ │ ├─ index.js +│ │ ├─ selection-controller.js +│ │ ├─ action-button.js +│ │ ├─ translation-popover.js +│ │ ├─ port-client.js +│ │ └─ content.css +│ ├─ popup/ +│ │ ├─ popup.html +│ │ ├─ popup.js +│ │ └─ popup.css +│ ├─ options/ +│ │ ├─ options.html +│ │ ├─ options.js +│ │ └─ options.css +│ └─ shared/ +│ ├─ settings-schema.js +│ ├─ messages.js +│ ├─ prompt.js +│ └─ constants.js +├─ tests/ +│ ├─ unit/ +│ └─ integration/ +└─ docs/ + ├─ Specifications.md + └─ Design.md +``` + +実際のビルド方式に応じて配置は調整してよいが、責務分離は維持する。 + +--- + +## 4. Manifest設計 + +概念例: + +```json +{ + "manifest_version": 3, + "name": "Selected Text Translator", + "version": "0.1.0", + "permissions": ["storage"], + "host_permissions": ["http://localhost/*", "http://127.0.0.1/*"], + "background": { + "scripts": ["background.js"], + "type": "module" + }, + "content_scripts": [ + { + "matches": ["http://*/*", "https://*/*"], + "js": ["content.js"], + "css": ["content.css"], + "run_at": "document_idle" + } + ], + "action": { + "default_popup": "popup.html" + }, + "options_ui": { + "page": "options.html", + "open_in_tab": true + } +} +``` + +### 4.1 Manifest実装ルール +- `background.service_worker` は使用しない。 +- `browser_action` は使用しない。 +- `scripting` 権限は初期版では付与しない。 +- `tabs` 権限は付与しない。 +- localhost以外のhost permissionは初期版では付与しない。 +- Firefoxのmatch patternはポート番号指定に対応していない(Firefox bug 1362809 / 1468162)。`http://localhost/*` および `http://127.0.0.1/*` は任意ポートに一致するため、実際に通信してよい接続先はアプリ側Host validatorで `http://localhost:<port>` / `http://127.0.0.1:<port>` のみに限定する。 + +--- + +## 5. Content Script設計 + +### 5.1 Selection取得 + +標準DOM APIを使用する。 + +```js +const selection = window.getSelection(); +const text = selection?.toString().trim(); + +if (!text) return; + +const range = selection.getRangeAt(0); +const rect = range.getBoundingClientRect(); +``` + +### 5.2 検出イベント +初期実装候補: +- `selectionchange`: 選択状態更新用 +- `mouseup`: 選択確定後の表示タイミング用 +- `keyup`: Shift + Arrow等のキーボード選択対応用 + +過剰発火を避けるため、UI更新には軽いdebounceを適用してよい。 + +### 5.3 Action Button表示条件 + +```text +selection exists +AND trimmed text length > 0 +AND text length <= MAX_SELECTION_LENGTH +AND extension enabled +``` + +### 5.4 位置計算 +- `Range.getBoundingClientRect()` を基本とする。 +- 選択が複数行の場合でも最後/全体矩形を基準に配置する。 +- viewport端ではポップオーバーが画面外へ出ないよう補正する。 +- scroll/resize時は再配置または非表示化する。 + +--- + +## 6. UI分離設計 + +### 6.1 Shadow DOM + +ページCSSとの衝突を避けるため、Content Scriptが専用host要素を追加し、その内部にShadow DOMを作る。 + +```js +const host = document.createElement('div'); +document.documentElement.append(host); +const shadow = host.attachShadow({ mode: 'open' }); +``` + +`open/closed` は実装都合で選択可能だが、セキュリティ境界として依存しない。 + +### 6.2 DOM挿入ルール + +禁止: +```js +element.innerHTML = translatedText; +shadowRoot.innerHTML = translatedText; +``` + +許可: +```js +element.textContent = translatedText; +``` + +静的テンプレートHTMLを自前固定文字列として構築する場合も、外部入力を文字列連結しない。 + +--- + +## 7. 翻訳ポップオーバー設計 + +### 7.1 状態 + +```text +HIDDEN +READY +LOADING +STREAMING +COMPLETED +ERROR +``` + +### 7.2 表示内容 + +| 要素 | 内容 | +|---|---| +| Header | 翻訳結果 | +| Meta | Model名 / Ollama | +| Body | streaming翻訳本文 | +| Copy | 完了/途中結果をclipboardへコピー | +| Regenerate | 同じ選択文字列で再要求 | +| Close | ポップオーバー非表示 | + +### 7.3 UI挙動 +- Icon押下直後にLOADING表示。 +- 最初のchunk受信後STREAMINGへ遷移。 +- 完了イベントでCOMPLETED。 +- Abortされた旧requestのchunkはrequestId不一致なら無視。 +- Error時は再生成可能な状態に戻す。 + +--- + +## 8. Runtime通信設計 + +Streamingには `browser.runtime.connect()` / `runtime.Port` を使用する。 + +### 8.1 Port名 + +```text +translation-stream +``` + +### 8.2 Content → Background message + +#### translate + +```js +{ + type: 'translate', + requestId: 'uuid-or-random-id', + text: 'selected text' +} +``` + +#### cancel + +```js +{ + type: 'cancel', + requestId: '...' +} +``` + +### 8.3 Background → Content message + +#### started + +```js +{ + type: 'started', + requestId: '...', + model: '...' +} +``` + +#### chunk + +```js +{ + type: 'chunk', + requestId: '...', + text: 'partial translation' +} +``` + +#### completed + +```js +{ + type: 'completed', + requestId: '...' +} +``` + +#### error + +```js +{ + type: 'error', + requestId: '...', + code: 'OLLAMA_UNAVAILABLE', + message: '...' +} +``` + +### 8.4 Validation +Backgroundは最低限以下を確認する。 +- `type` が既知値か +- `requestId` がstringか +- `text` がstringか +- text lengthが上限以内か +- 現在のsender/portがContent Script由来か + +--- + +## 9. TranslationSession設計 + +Backgroundではactive translationを原則1件だけ保持する。 + +```js +class TranslationSession { + requestId; + abortController; + sourceText; + startedAt; +} +``` + +新しい翻訳要求: + +```text +translate(request B) + ↓ +active request Aあり? + ├─ yes → Abort A + └─ no + ↓ +request Bをactiveに設定 + ↓ +Ollama streaming開始 +``` + +### 9.1 古いchunk対策 +受信chunkごとにactive requestIdを確認し、不一致なら破棄する。 + +--- + +## 10. OllamaClient設計 + +### 10.1 API + +Ollama REST APIをFetch APIで直接呼び出す。`ollama-js`等のサードパーティSDKは使用しない。 + +概念例: + +```js +const response = await fetch(`${host}/api/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: settings.model, + messages: [ + { + role: 'system', + content: buildSystemPrompt(settings) + }, + { + role: 'user', + content: selectedText + } + ], + stream: true, + think: settings.thinking, // off時は省略 + options: buildOptions(settings) + }), + signal: abortController.signal +}); + +// NDJSONストリームをバッファリングしつつ行単位でパースする +const reader = response.body.getReader(); +const decoder = new TextDecoder(); +let buffer = ''; +for (;;) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + // bufferを行単位で分割し、完全な行のみJSON.parseする + // 末尾の不完全行は次回チャンクに持ち越す +} +``` + +### 10.2 System Prompt構築 + +```text +BUILTIN_SYSTEM_PROMPT + +[User translation preferences] +USER_SYSTEM_PROMPT +``` + +固定promptは常に先頭に置く。 + +### 10.3 buildOptions +未設定値はrequest objectへ含めない。 + +例: + +```js +{ + temperature: 0.2, + num_ctx: 16384, + seed: 42, + top_k: 40, + top_p: 0.9, + min_p: 0.05, + typical_p: 1.0, + repeat_last_n: 64, + repeat_penalty: 1.1, + presence_penalty: 0.0, + frequency_penalty: 0.0, + num_keep: 4, + num_predict: -1, + stop: ['<END>'] +} +``` + +Seed可変時は`seed`をoptionsへ含めない設計を優先する。 + +--- + +## 11. 設定スキーマ + +推奨形: + +```js +{ + enabled: true, + ollamaHost: 'http://localhost:11434', + model: '', + targetLanguage: 'ja', + thinking: false, + temperature: 0.2, + contextLength: 16384, + seedMode: 'random', + seedValue: null, + userSystemPrompt: '', + advanced: { + numKeep: null, + numPredict: null, + topK: null, + topP: null, + minP: null, + typicalP: null, + repeatLastN: null, + repeatPenalty: null, + presencePenalty: null, + frequencyPenalty: null, + stop: [] + } +} +``` + +--- + +## 12. 設定値validation + +### 12.1 Basic + +| enabled | boolean | +| ollamaHost | URL形式。**`http://localhost:<port>` または `http://127.0.0.1:<port>` のみ許可(schemeはhttp固定、外部ホスト・LAN IP・httpsは拒否)。必須制限** | +| model | non-empty string | +| targetLanguage | 定義済み値 | +| thinking | `false/true/low/medium/high` 相当 | +| temperature | 0.0〜2.0、0.1刻み | +| contextLength | `4096/8192/16384/32768/131072/262144` のいずれか | +| seedMode | `random/fixed` | +| seedValue | fixed時のみinteger | +| userSystemPrompt | string、最大長を設定 | + +### 12.2 Advanced + +推奨UI step: + +| 項目 | step | validation方針 | +|---|---:|---| +| num_keep | 1 | integer | +| num_predict | 1 | integer、-1許容 | +| top_k | 1 | integer、0以上 | +| top_p | 0.05 | float | +| min_p | 0.01 | float | +| typical_p | 0.05 | float | +| repeat_last_n | 1 | integer、-1許容 | +| repeat_penalty | 0.05 | float | +| presence_penalty | 0.05 | float | +| frequency_penalty | 0.05 | float | +| stop | - | string[] | + +Ollamaが明示的な最小/最大値を保証していない項目について、UI側で過度に狭い範囲を独自設定しない。型と明らかな不正値を中心に検証する。 + +--- + +## 13. Context Length UI設計 + +固定プリセット: + +```js +const CONTEXT_PRESETS = [ + 4096, + 8192, + 16384, + 32768, + 131072, + 262144 +]; +``` + +UI表示: + +```text +4k ─ 8k ─ 16k ─ 32k ─ 128k ─ 256k +``` + +実装上はrange inputのindex値を使用してよい。 + +```text +0 => 4096 +1 => 8192 +2 => 16384 +3 => 32768 +4 => 131072 +5 => 262144 +``` + +モデル自身が対応する最大contextを超える場合、Ollama側でエラーまたは制約される可能性があるため、そのエラーをUIへ返す。 + +--- + +## 14. Temperature UI設計 + +```text +min=0.0 +max=2.0 +step=0.1 +``` + +スライダー値は表示用数値と同期する。 + +```html +<input type="range" min="0" max="2" step="0.1"> +``` + +--- + +## 15. Seed UI設計 + +```text +Seed +[ 可変 ] [ 固定 ] + +固定選択時: +固定値 (整数) [ 42 ▲▼ ] +``` + +- random時: seed optionを送らない。 +- fixed時: `seedValue` を整数として送る。 +- Seed Valueはnumber input + spinnerでよい。 + +--- + +## 16. Advanced UI設計 + +数値項目は`input type="number"`相当のUIとする。 + +例: + +```text +top_p +[ 0.90 ][▲] + [▼] +``` + +HTML native spinnerを使用できる場合は活用し、独自実装を増やさない。 + +stopは複数行textareaとし、1行1sequenceとして保存する。 + +--- + +## 17. SettingsService設計 + +### 17.1 保存 + +`browser.storage.local` を使用。 + +### 17.2 読み込み +1. default settings生成 +2. storage値取得 +3. merge +4. validation +5. 不正値はdefaultへフォールバック + +### 17.3 設定変更通知 +設定変更時に `browser.storage.onChanged` を使用し、Content Script / Background双方が必要な設定を更新できるようにする。 + +--- + +## 18. Security Design + +### 18.1 Trust Boundary + +```text +UNTRUSTED +Web Page / DOM / Selection + │ + ▼ +Content Script + │ structured messages only + ▼ +PRIVILEGED +Background / Ollama access / storage +``` + +### 18.2 XSS対策 +- 選択文字列をHTML化しない。 +- LLM出力をHTML化しない。 +- Markdown rendererを初期版では導入しない。 +- DOM挿入は`textContent`中心。 + +### 18.3 Prompt Injection対策 +翻訳用途ではページ本文を命令ではなくデータとして固定system promptで扱う。 + +ただしLLMの挙動を完全保証するセキュリティ境界としてPromptのみへ依存しない。 + +### 18.4 Remote Code禁止 +- CDN script禁止。 +- `eval`禁止。 +- `new Function`禁止。 +- remote import禁止。 +- npm依存はbundleに含める。 + +### 18.5 Storage +- `storage.local` は暗号化ストレージではない前提。 +- API key等の秘密情報は初期版では扱わない。 + +### 18.6 Message Validation +Content側のデータをBackgroundで再検証する。 + +### 18.7 Resource Abuse +- MAX_SELECTION_LENGTHを設ける。 +- active translationは1件。 +- 新規requestで旧request abort。 +- 選択だけではrequestを送らない。 + +--- + +## 19. Error Code設計 + +| Code | 意味 | +|---|---| +| `INVALID_REQUEST` | message形式不正 | +| `TEXT_EMPTY` | 空文字 | +| `TEXT_TOO_LONG` | 選択長超過 | +| `OLLAMA_UNAVAILABLE` | Ollama接続不可 | +| `MODEL_NOT_FOUND` | モデルなし | +| `INVALID_SETTINGS` | 設定値不正 | +| `REQUEST_ABORTED` | 新規要求等で中断 | +| `STREAM_ERROR` | streaming中エラー | +| `UNKNOWN_ERROR` | その他 | + +UIでは内部stack traceを表示しない。 + +--- + +## 20. Copy設計 + +Clipboard APIのみを使用する。`document.execCommand('copy')` はdeprecatedのため使用しない。 + +- コピーはポップオーバーのコピーボタンclick(user activation内)で発生するため、`navigator.clipboard.writeText()` をそのハンドラ内で直接呼び出す。 +- 書き込み失敗時は「コピーできませんでした」等の簡潔なメッセージをポップオーバーへ表示するのみとし、deprecated APIへフォールバックしない。 +- Firefox実機でclipboard動作を確認する。 + +コピー対象は現在表示中の翻訳プレーンテキストのみ。 + +--- + +## 21. モデル一覧取得 + +BackgroundのOllamaClientからモデル一覧取得APIを呼び、Settings UIへ返す。 + +要件: +- Ollama未起動時は空一覧 + 接続エラー表示。 +- 取得成功時はselect候補へ反映。 +- 保存済みmodelが一覧にない場合は警告表示。 + +--- + +## 22. 実装順序 + +### Phase 1: Skeleton +- Manifest V3 +- Background +- Content Script +- Popup / Options +- build/test基盤 + +### Phase 2: Selection UI +- Selection検出 +- Action button +- Shadow DOM +- Popover skeleton + +### Phase 3: Messaging +- runtime.Port +- message schema +- validation +- requestId + +### Phase 4: Ollama +- Ollama REST client(fetch + NDJSON streaming) +- model list +- chat stream +- Abort + +### Phase 5: Settings +- storage.local +- Basic settings +- Advanced settings +- Context slider +- Temperature slider +- Seed modes + +### Phase 6: Security Hardening +- textContent確認 +- CSP確認 +- message validation +- input length limits +- no remote code +- permissions audit + +### Phase 7: Tests +- unit +- integration +- Firefox実機 + +### Phase 8: Final Verification +- lint/test/build +- temporary add-on install +- acceptance criteriaを全件確認 + +--- + +## 23. テスト設計 + +### 23.1 Unit + +| 対象 | ケース | +|---|---| +| settings schema | default / valid / invalid | +| temperature | 0.1刻み / 範囲外 | +| contextLength | presetのみ許可 | +| seed | random / fixed / invalid integer | +| advanced options | null除外、数値反映 | +| prompt builder | fixed + user prompt結合 | +| message validation | malformed message拒否 | +| length limit | 境界値 | + +### 23.2 Integration + +Mock Ollama serverまたはmock clientを使い以下を確認。 + +```text +Content request +→ Background validation +→ Ollama stream +→ chunk forwarding +→ complete +``` + +Abortテスト: + +```text +request A start +request B start +A abort +B chunks only displayed +``` + +### 23.3 Firefox Manual/E2E + +対象ページ: +- 単純HTML +- 長文記事 +- 選択文字列にHTMLタグ表現を含むページ +- CSSが強く指定されたページ +- スクロール位置が深いページ + +確認: +- 選択位置近傍UI +- viewport端補正 +- scroll時挙動 +- selection解除 +- copy +- regenerate +- close +- settings persistence +- Ollama down/up + +--- + +## 24. Firefox API監査チェックリスト + +実装完了時に以下を確認する。 + +- [ ] Manifest V3 +- [ ] `action` 使用 +- [ ] `background.scripts` 使用 +- [ ] `background.service_worker` 不使用 +- [ ] `browser_action` 不使用 +- [ ] `tabs.executeScript()` 不使用 +- [ ] `runtime.getBackgroundPage()` 不使用 +- [ ] `window.localStorage` 不使用 +- [ ] `storage.local` 使用 +- [ ] `runtime.Port` streaming通信 +- [ ] Content ScriptからOllama fetchなし +- [ ] `eval` / `new Function`なし +- [ ] Remote JSなし +- [ ] LLM出力への`innerHTML`なし +- [ ] 不要permissionsなし +- [ ] iframe初期対応なし + +--- + +## 25. 完了時にLLMへ要求する成果物 + +実装担当LLMは最終的に以下を提示すること。 + +1. 実装済みソースコード一式 +2. `manifest.json` +3. build手順 +4. Firefox一時アドオンとしてのロード手順 +5. Ollama起動/モデル準備手順 +6. 単体テスト結果 +7. 結合テスト結果 +8. Firefox実機動作確認結果 +9. 使用WebExtensions API一覧 +10. 要求permissions一覧と理由 +11. セキュリティチェック結果 +12. 未解決事項・既知制限 + +テストを実行せず「実装したので動くはず」として完了扱いにしてはならない。実際に実行可能なテストは実行し、Firefox実機確認項目も可能な範囲で実行結果を記録する。 diff --git a/doc/Security.md b/doc/Security.md new file mode 100644 index 0000000..956f169 --- /dev/null +++ b/doc/Security.md @@ -0,0 +1,804 @@ +# Security.md + +## 1. Purpose + +This document defines the mandatory security constraints and implementation rules for the Firefox WebExtension project. + +The implementation agent MUST treat this file as a normative development policy together with `Specifications.md` and `Design.md`. + +When there is a conflict between convenience and this security policy, this policy takes precedence unless the project owner explicitly changes the requirement. + +--- + +## 2. Security Objectives + +| ID | Objective | +|---|---| +| SEC-001 | Minimize the extension's attack surface. | +| SEC-002 | Avoid third-party build-time and runtime dependency supply-chain risk. | +| SEC-003 | Treat all web page content as untrusted input. | +| SEC-004 | Prevent selected text and LLM output from being evaluated as HTML or JavaScript. | +| SEC-005 | Restrict privileged WebExtension operations to the minimum required components. | +| SEC-006 | Restrict network communication to the configured Ollama endpoint. | +| SEC-007 | Use current Firefox WebExtensions APIs and avoid deprecated or obsolete APIs. | +| SEC-008 | Keep the implementation auditable without a package manager or generated dependency tree. | + +--- + +## 3. Dependency Policy + +### 3.1 Mandatory Rules + +The project MUST be developed without Node.js or JavaScript package managers. + +The following are prohibited: + +- Node.js as a required development or runtime dependency +- npm +- yarn +- pnpm +- bun package management +- `node_modules` +- third-party JavaScript packages +- third-party CSS frameworks +- remote JavaScript +- remote CSS +- remote fonts +- CDN-hosted runtime resources + +The project SHOULD NOT contain `package.json`. + +All resources required at runtime MUST be included directly in the extension package or supplied by Firefox/Web Platform standard APIs. + +### 3.2 Allowed Technologies + +The implementation MAY use: + +- HTML +- locally stored CSS +- vanilla JavaScript +- Firefox WebExtensions APIs +- standard DOM APIs +- Fetch API +- Streams API +- `TextDecoder` / `TextDecoderStream` +- standard `JSON` APIs +- locally bundled PNG/SVG assets +- inline SVG created by the project +- operating-system fonts through CSS generic/system font families + +### 3.3 CSS and Asset Policy + +All application CSS MUST be owned by this project and stored locally in the repository. + +Do not download and vendor a CSS framework unless explicitly approved. + +Preferred approach: + +```text +content.css +popup.css +options.css +``` + +External resources such as the following MUST NOT be used: + +```text +Google Fonts +Font Awesome CDN +Material Icons CDN +Bootstrap CDN +Tailwind CDN +jsDelivr +unpkg +cdnjs +``` + +Icons SHOULD be: + +1. project-owned local SVG/PNG files, or +2. inline SVG defined directly by the application. + +System fonts SHOULD be used, for example: + +```css +font-family: system-ui, sans-serif; +``` + +--- + +## 4. Ollama Integration Policy + +### 4.1 No Ollama JavaScript SDK Dependency + +The project MUST NOT require `ollama-js`. + +The extension MUST communicate with Ollama directly using the official Ollama HTTP API and the browser Fetch API. + +The intent is to eliminate npm/package-manager supply-chain dependencies. + +### 4.2 Required API Scope + +The initial implementation SHOULD require only the minimum Ollama endpoints needed by the specification. + +Expected endpoints: + +| Endpoint | Purpose | +|---|---| +| `GET /api/tags` | Retrieve installed models. | +| `POST /api/chat` | Perform translation generation using streaming. | + +Do not add Ollama API endpoints unless required by a defined feature. + +### 4.3 Streaming + +Translation MUST use streaming responses. + +The Ollama client module MUST: + +- parse streamed response data safely; +- tolerate chunk boundaries that do not align with JSON object boundaries; +- cancel the current request when a newer translation request supersedes it; +- use `AbortController` or equivalent standard browser functionality; +- propagate only validated translation text to the content script. + +### 4.4 Ollama Endpoint + +Default endpoint: + +```text +http://localhost:11434 +``` + +The endpoint MAY be configurable according to the product specification. In the initial version, the configured endpoint MUST be limited to `http://localhost:<port>` or `http://127.0.0.1:<port>`. External hosts, LAN IPs, and HTTPS endpoints MUST be rejected by validation. + +The implementation MUST validate the configured value before use, and the Ollama client MUST re-verify the target host immediately before each request (defense in depth). + +The background component MUST be the only component that communicates directly with Ollama. + +Content scripts MUST NOT directly call Ollama. + +--- + +## 5. Firefox WebExtensions Policy + +### 5.1 Manifest + +The extension MUST use Manifest V3. + +Firefox-specific behavior MUST be verified against current MDN documentation before implementation. + +Do not blindly copy Chromium-specific Manifest V3 examples. + +### 5.2 Background Implementation + +For Firefox, use the supported background mechanism documented for Firefox. + +Do NOT assume Chromium's: + +```json +"background": { + "service_worker": "background.js" +} +``` + +is supported by Firefox. + +The implementation agent MUST verify the current Firefox support status before changing the background model. + +### 5.3 APIs to Prefer + +Use modern APIs where required: + +| Purpose | Preferred API | +|---|---| +| Toolbar action | `action` | +| Extension storage | `storage.local` | +| Long-lived extension communication | `runtime.connect()` / `runtime.Port` | +| One-shot extension messages | `runtime.sendMessage()` | +| Dynamic script injection, only if needed | `scripting.executeScript()` | + +### 5.4 APIs / Patterns to Avoid + +Do not introduce the following unless explicit project requirements justify them: + +- Manifest V2 +- `browser_action` +- `browserAction.*` +- `tabs.executeScript()` +- `tabs.insertCSS()` +- `runtime.getBackgroundPage()` +- `extension.getBackgroundPage()` +- `window.localStorage` for extension settings +- `externally_connectable` +- `runtime.onMessageExternal` +- `runtime.onConnectExternal` +- `nativeMessaging` +- `webRequest` +- `cookies` +- `history` +- `bookmarks` +- `downloads` +- unnecessary `tabs` permission +- unnecessary `scripting` permission + +The implementation MUST verify whether any WebExtensions API it intends to add is deprecated, obsolete, Firefox-incompatible, or unnecessarily privileged. + +--- + +## 6. Permission Minimization + +The extension MUST request only permissions required by implemented features. + +Expected initial permission set: + +```text +storage +``` + +Expected host permission: + +```text +http://localhost/* +http://127.0.0.1/* +``` + +Firefox match patterns do not support port numbers (Firefox bug 1362809 / 1468162). Therefore the patterns above match any port on localhost. The application MUST restrict the actual connection target to `http://localhost:<port>` or `http://127.0.0.1:<port>` via its own host validation before any Ollama request. + +or the minimum equivalent required by the final Ollama-host design. + +Page access MAY require content-script match patterns for HTTP/HTTPS pages because translation is initiated by selecting page text. + +Do not add broad privileged permissions for convenience. + +Every newly introduced permission MUST be justified in code review. + +--- + +## 7. Trust Boundaries + +The system has three primary trust zones. + +```text +Untrusted Web Page + | + | selected text + v +Content Script + | + | validated extension messages + v +Background + | + | HTTP + v +Ollama +``` + +### 7.1 Web Page + +All page content MUST be considered untrusted. + +This includes: + +- text selected by the user; +- DOM content; +- attributes; +- page-generated text; +- scripts; +- event data; +- iframe content; +- strings that resemble prompts or instructions. + +The presence of text such as: + +```text +Ignore previous instructions... +``` + +MUST NOT cause that text to be treated as an application instruction. + +Selected web content is translation data, not trusted control input. + +### 7.2 Content Script + +The content script is responsible only for tasks that require page access, including: + +- obtaining the user's current text selection; +- positioning the translation action icon; +- rendering the translation popover; +- forwarding translation requests; +- receiving streamed translation text; +- handling local UI interaction. + +It MUST NOT be responsible for privileged network communication with Ollama. + +### 7.3 Background + +The background component owns privileged application operations: + +- Ollama HTTP communication; +- request lifecycle management; +- configuration access where appropriate; +- message validation; +- request cancellation; +- Ollama response parsing. + +--- + +## 8. DOM and XSS Rules + +### 8.1 Plain-Text Rendering + +Page-derived text and LLM-generated text MUST be rendered as plain text. + +Use safe DOM APIs such as: + +```js +element.textContent = value; +``` + +Do NOT use untrusted content with: + +```js +element.innerHTML = value; +element.outerHTML = value; +shadowRoot.innerHTML = value; +document.write(value); +``` + +### 8.2 No HTML Interpretation + +The translation result is plain text. + +Do not implement Markdown rendering, HTML rendering, or rich-text parsing unless explicitly added as a later requirement and separately security-reviewed. + +### 8.3 No Dynamic Code Execution + +The following are prohibited: + +```text +eval() +new Function() +setTimeout(string) +setInterval(string) +``` + +No selected page content, setting, LLM response, or remote response may be executed as JavaScript. + +--- + +## 9. Shadow DOM Policy + +The translation UI SHOULD use Shadow DOM where practical to isolate extension UI styling from page CSS. + +Primary reasons: + +- reduce CSS collisions; +- reduce page-style interference; +- contain translation UI styles; +- improve predictable layout. + +Shadow DOM MUST NOT be treated as an XSS security boundary. + +LLM responses and page text inside a shadow root MUST still use plain-text rendering. + +--- + +## 10. Page Script Isolation + +The extension SHOULD rely on the normal WebExtensions content-script isolation boundary. + +Do not bridge page scripts and extension scripts unless a future requirement makes this unavoidable. + +The following SHOULD NOT be used: + +- `wrappedJSObject` +- `exportFunction` +- `cloneInto` +- custom page-script injection +- page-controlled `window.postMessage` communication + +If page-script communication becomes necessary later, it requires an explicit security design update. + +--- + +## 11. Message Validation + +All messages received by privileged extension components MUST be validated. + +Example translation request shape: + +```js +{ + type: "translate", + requestId: "...", + text: "..." +} +``` + +The receiver MUST validate at least: + +- message is an object; +- known `type`; +- expected fields are present; +- `text` is a string; +- `requestId` is a string; +- text length is within defined limits; +- unsupported fields do not alter privileged behavior. + +Do not dispatch privileged behavior using arbitrary method names supplied by messages. + +Avoid patterns such as: + +```js +handlers[message.method](message); +``` + +unless `message.method` is strictly allow-listed. + +--- + +## 12. Input Limits and Resource Abuse + +Selected text MUST have a maximum allowed size. + +The exact product limit MAY be defined in `Specifications.md`, but an implementation limit MUST exist. + +The purpose is to reduce: + +- accidental huge requests; +- malicious resource consumption; +- unnecessarily long model generation; +- UI hangs; +- memory pressure. + +If input exceeds the allowed limit, the extension MUST fail safely and provide a user-visible error. + +Repeated translation requests MUST cancel obsolete in-flight requests. + +--- + +## 13. LLM Prompt Boundary + +The application MUST maintain a separation between: + +1. application-controlled system instructions; +2. user-configurable system instructions; +3. selected page text. + +The application-controlled prompt MUST state that selected page content is translation material and must not be interpreted as instructions. + +Selected text MUST NOT be concatenated into trusted system instructions. + +Preferred conceptual request structure: + +```text +system: + application-controlled translation rules + + validated user translation preferences + +user: + selected page text +``` + +--- + +## 14. Settings Validation + +Every configurable Ollama generation value MUST be validated before being sent to Ollama. + +Validation MUST include: + +- expected type; +- finite numeric value where applicable; +- supported range; +- integer-only constraints where applicable; +- allowed enum values; +- context-length preset membership; +- string length limits. + +Invalid stored values MUST NOT be passed directly to Ollama. + +The implementation SHOULD fall back to safe defaults or request correction from the user. + +--- + +## 15. Seed Security / Correctness Rule + +Seed configuration MUST distinguish between: + +- variable/random behavior; +- fixed deterministic integer seed. + +When fixed seed mode is enabled, the value MUST be validated as an integer. + +When variable mode is enabled, the implementation MUST use the Ollama/API-supported behavior defined in the specification and MUST NOT generate executable or unsafe random data. + +--- + +## 16. Storage Rules + +Use: + +```text +browser.storage.local +``` + +for extension configuration. + +Do not use page `localStorage` for extension settings. + +The implementation MUST assume `storage.local` is not encrypted. + +Do not store: + +- passwords; +- arbitrary secrets; +- private authentication tokens; + +unless a later requirement explicitly adds such functionality and the security model is revised. + +The initial local-Ollama design SHOULD require no secret credentials. + +--- + +## 17. Content Security Policy + +Keep Firefox's extension CSP as restrictive as practical. + +Do not weaken CSP merely to make a library or framework work. + +Do not enable: + +- remote script execution; +- `unsafe-eval`; +- arbitrary inline executable JavaScript. + +JavaScript SHOULD be stored in local `.js` files where applicable. + +HTML event attributes such as the following SHOULD NOT be used: + +```html +<button onclick="..."> +``` + +Prefer JavaScript event listeners: + +```js +button.addEventListener("click", handler); +``` + +--- + +## 18. Remote Resource Ban + +At runtime, the extension MUST NOT load executable or styling resources from the Internet. + +Prohibited runtime resources include: + +- JavaScript; +- CSS; +- web fonts; +- icon libraries; +- UI frameworks; +- remotely hosted HTML fragments. + +The only intended network communication is application data communication with the configured Ollama service. + +A future feature that adds any other remote service requires explicit review and an update to this file. + +--- + +## 19. Build and Packaging + +The project SHOULD be directly loadable as a Firefox temporary extension without a build step. + +Expected development workflow: + +```text +edit source files + -> +about:debugging + -> +This Firefox + -> +Load Temporary Add-on + -> +select manifest.json +``` + +A build system MUST NOT be introduced unless an explicit requirement justifies it. + +Packaging SHOULD consist of the project files required by the WebExtension package, excluding tests and development-only files where appropriate. + +--- + +## 20. Testing Security Requirements + +Security-sensitive behavior MUST be tested. + +Minimum cases: + +| Test | Expected Result | +|---|---| +| Selected text contains `<script>` | Displayed as text, never executed. | +| LLM returns HTML tags | Displayed as text. | +| LLM returns `<img onerror=...>` | Displayed as text. | +| Selected text contains prompt-injection language | Treated only as translation input. | +| Oversized selected text | Rejected safely. | +| Invalid numeric option | Rejected or replaced with safe default. | +| Invalid context preset | Rejected. | +| Unknown message type | Ignored/rejected. | +| Rapid repeated translation requests | Previous request is cancelled. | +| Ollama unavailable | Safe error UI, no exception leak to page. | +| Malformed Ollama stream chunk | Parser fails safely without executing content. | +| Page has hostile CSS | Translation UI remains usable where practical. | +| Page script inspects DOM | No privileged extension object is exposed. | + +--- + +## 21. Logging + +Do not log full selected text or full translation results by default. + +Development logging SHOULD avoid unnecessary disclosure of page content. + +Do not log secrets if credential support is ever added. + +Error logs SHOULD contain technical diagnostics without unnecessarily reproducing user-selected text. + +--- + +## 22. Error Handling + +Errors MUST fail closed rather than enabling broader privileges or unsafe fallback behavior. + +Examples: + +- If Ollama host validation fails, do not attempt arbitrary network access. +- If JSON streaming parsing fails, stop or recover safely. +- If a setting cannot be validated, use a documented safe default or reject the operation. +- If content-script messaging fails, do not fall back to page-script execution. + +--- + +## 23. Source Review Policy + +Before adding any new external code, dependency, browser API, or permission, the implementation agent MUST answer: + +1. Is this required by `Specifications.md`? +2. Can the same requirement be implemented with a browser/WebExtensions standard API? +3. Does this introduce a package-manager dependency? +4. Does this introduce a runtime network dependency? +5. Does this require a new Firefox permission? +6. Is the API current and supported by Firefox? +7. Does it expand the trust boundary? +8. Can the implementation remain simpler without it? + +If the feature can reasonably be implemented without the new dependency or privilege, prefer the dependency-free / lower-privilege implementation. + +--- + +## 24. Explicitly Prohibited Design Changes + +Without project-owner approval, the implementation agent MUST NOT: + +- add npm dependencies; +- introduce Node.js tooling; +- add a JavaScript framework; +- add a CSS framework; +- load assets from a CDN; +- load Google Fonts or other remote fonts; +- add `ollama-js`; +- replace direct Ollama REST calls with a third-party client; +- request unrelated Firefox permissions; +- introduce Manifest V2 compatibility code; +- introduce Chromium-only service-worker assumptions into Firefox code; +- render LLM output using HTML; +- execute page or LLM content; +- expose privileged extension functions to page JavaScript; +- silently add telemetry or analytics; +- send selected text anywhere other than the explicitly configured Ollama endpoint. + +--- + +## 25. Privacy Requirements + +Selected text can contain private or sensitive page content. + +Therefore: + +- selected text MUST only be sent to the configured Ollama endpoint; +- the extension MUST NOT send telemetry; +- the extension MUST NOT send analytics; +- the extension MUST NOT contact external logging services; +- the extension MUST NOT automatically fall back to a cloud LLM; +- connection failures MUST NOT cause data to be sent to another provider; +- the UI SHOULD make the configured Ollama host visible to the user. + +If cloud Ollama support is added later, it must be an explicit user configuration and requires a privacy/security design update. + +--- + +## 26. Implementation Review Checklist + +Before declaring implementation complete, verify all of the following. + +### Dependencies + +- [ ] No `node_modules`. +- [ ] No npm/yarn/pnpm/bun dependency. +- [ ] No required Node.js build step. +- [ ] No third-party JS runtime dependency. +- [ ] No third-party CSS framework. +- [ ] No remote fonts. +- [ ] No CDN resources. +- [ ] No `ollama-js`. + +### Firefox + +- [ ] Manifest V3. +- [ ] Firefox-supported background implementation. +- [ ] No deprecated WebExtensions API used when a supported current alternative exists. +- [ ] Permissions are minimal. +- [ ] No unnecessary `tabs`, `scripting`, `webRequest`, `cookies`, or `nativeMessaging` permission. +- [ ] Firefox behavior manually verified in `about:debugging`. + +### DOM / Page Boundary + +- [ ] Page content treated as untrusted. +- [ ] LLM output treated as untrusted. +- [ ] `textContent` or equivalent safe APIs used. +- [ ] No untrusted `innerHTML`. +- [ ] No `eval()` / `new Function()`. +- [ ] No page-script privilege bridge. +- [ ] Shadow DOM does not contain unsafe HTML insertion. + +### Messaging + +- [ ] Message types are allow-listed. +- [ ] Payload types are validated. +- [ ] Selection length limit exists. +- [ ] Obsolete translation requests are cancelled. +- [ ] Unknown messages are rejected or ignored safely. + +### Network + +- [ ] Only background communicates with Ollama. +- [ ] Ollama host is validated. +- [ ] No silent fallback endpoint. +- [ ] Streaming parser handles malformed input safely. +- [ ] No telemetry or analytics endpoint. + +### Settings + +- [ ] Numeric values validated. +- [ ] Seed integer validated. +- [ ] Context length restricted to supported UI presets. +- [ ] Temperature validated. +- [ ] Stored invalid values are handled safely. + +### Testing + +- [ ] Script/HTML injection strings tested. +- [ ] Prompt-injection-like selected text tested. +- [ ] Oversized selection tested. +- [ ] Ollama offline case tested. +- [ ] Request cancellation tested. +- [ ] Malformed streaming data tested. +- [ ] Settings validation tested. + +--- + +## 27. Definition of Done + +Security work is complete only when: + +1. all implemented features comply with this document; +2. the extension runs without Node.js/npm; +3. all runtime resources are local except Ollama API traffic; +4. no untrusted page or LLM content is interpreted as executable markup/code; +5. Firefox permissions and APIs have been reviewed for necessity and current support; +6. security test cases pass; +7. deviations from this policy are explicitly documented and approved. diff --git a/doc/Specifications.md b/doc/Specifications.md new file mode 100644 index 0000000..ac6600d --- /dev/null +++ b/doc/Specifications.md @@ -0,0 +1,391 @@ +# Specifications.md + +## 1. 概要 + +### 1.1 目的 +Firefox上でユーザーがWebページ内の文字列を選択した際、選択範囲付近に小さな翻訳アイコンを表示し、ユーザーがそのアイコンを押すとOllama経由のLLMで翻訳を実行し、ページ上の小型ポップオーバーにストリーミング表示するFirefox WebExtensionを実装する。 + +### 1.2 基本方針 +- Firefox WebExtensions APIを使用する。 +- Manifest V3を採用する。 +- Firefox向け実装を優先し、Chromium互換性は必須要件としない。 +- Ollamaとの通信は公式Ollama REST APIをFetch APIで直接呼び出すことにより行う(`ollama-js`等のサードパーティライブラリは使用しない)。 +- Ollama呼び出しは `POST /api/chat` + `stream: true`(NDJSON streaming)のみを使用する。 +- 会話履歴は保持せず、翻訳要求ごとに独立した1-shotリクエストとする。 +- Webページ上の翻訳UIはチャットUIではなく、選択箇所付近の軽量なポップオーバーとする。 +- ページ本文およびLLM出力は常にプレーンテキストとして扱い、HTMLとして評価しない。 + +--- + +## 2. 対象環境 + +| 項目 | 要件 | +|---|---| +| ブラウザ | Firefox 最新安定版を主対象 | +| 拡張方式 | WebExtensions / Manifest V3 | +| バックエンド | Ollama | +| 既定Ollama Host | `http://localhost:11434` | +| 初期版接続先制限 | `http://localhost:<port>` / `http://127.0.0.1:<port>` のみ許可(validatorで強制) | +| 通信方式 | Fetch API + NDJSON Streaming only | +| LLM API | `POST /api/chat`(REST API) | +| 保存先 | `browser.storage.local` | + +--- + +## 3. ユーザーフロー + +1. ユーザーがWebページ上の文字列をドラッグ等で選択する。 +2. 選択範囲の近くに小型の翻訳アイコンを表示する。 +3. ユーザーが翻訳アイコンを押す。 +4. Content Scriptが選択文字列をBackgroundへ送信する。 +5. BackgroundがOllamaへストリーミング翻訳を要求する。 +6. 翻訳結果をContent Scriptへ逐次転送する。 +7. 選択範囲付近の翻訳ポップオーバーに結果を逐次表示する。 +8. ユーザーはコピー、再生成、閉じる操作を行える。 +9. 新しい翻訳要求が開始された場合、進行中の旧リクエストはキャンセルする。 + +--- + +## 4. 機能要件 + +### 4.1 選択文字列検出 + +| ID | 要件 | +|---|---| +| SEL-01 | Webページ上のユーザー選択文字列を取得できること | +| SEL-02 | 空文字・空白のみの選択は無視すること | +| SEL-03 | 選択解除時は翻訳アイコンを非表示にすること | +| SEL-04 | 選択範囲の位置を取得し、その近傍に翻訳アイコンを表示すること | +| SEL-05 | 初期版ではトップレベル文書のみ対象とし、iframe内は対象外とすること | +| SEL-06 | Firefox内部ページ、Reader View、view-source、内蔵PDF Viewer等、Content Scriptを利用できないページは対象外とすること | + +### 4.2 翻訳アイコン + +| ID | 要件 | +|---|---| +| ACT-01 | 文字列選択後に小型の翻訳アイコンを表示すること | +| ACT-02 | アイコン押下前にLLM呼び出しを開始しないこと | +| ACT-03 | アイコンは選択文字列を著しく隠さない位置に表示すること | +| ACT-04 | ページCSSの影響を極力受けないこと | +| ACT-05 | 選択変更時は最新の選択位置へ追従すること | + +### 4.3 翻訳実行 + +| ID | 要件 | +|---|---| +| TRN-01 | Ollama REST API `POST /api/chat` を使用すること | +| TRN-02 | `stream: true` 固定(NDJSON streaming)とすること | +| TRN-03 | 会話履歴を持たないこと | +| TRN-04 | アプリ固定システムプロンプトとユーザー設定システムプロンプトを結合して使用すること | +| TRN-05 | 翻訳対象文字列はuser messageとして送信すること | +| TRN-06 | ページ内に含まれる命令文は翻訳対象データとして扱い、指示として実行しないよう固定システムプロンプトで明示すること | +| TRN-07 | 新規翻訳要求開始時に旧ストリームをキャンセルできること | +| TRN-08 | Ollama通信失敗時にUI上へ簡潔なエラーを表示すること | +| TRN-09 | 翻訳要求ごとに一意のrequestIdを使用すること | + +### 4.4 翻訳ポップオーバー + +| ID | 要件 | +|---|---| +| UI-01 | 翻訳結果はページ上の小型ポップオーバーへ表示すること | +| UI-02 | サイドバーやチャット履歴UIは使用しないこと | +| UI-03 | ストリーム受信中の翻訳文字列を逐次描画すること | +| UI-04 | 翻訳結果本文をプレーンテキストとして描画すること | +| UI-05 | コピーボタンを提供すること | +| UI-06 | 再生成ボタンを提供すること | +| UI-07 | 閉じるボタンを提供すること | +| UI-08 | 使用モデル名を控えめに表示できること | +| UI-09 | 読み込み中、完了、エラー状態を視覚的に区別できること | + +### 4.5 拡張機能の有効/無効 + +| ID | 要件 | +|---|---| +| EN-01 | 拡張機能の翻訳機能を設定画面から有効/無効に切り替えられること | +| EN-02 | 無効時は選択文字列に対して翻訳アイコンを表示しないこと | + +--- + +## 5. 設定要件 + +### 5.1 基本設定 + +| 項目 | UI | 初期/参考値 | 備考 | +|---|---|---|---| +| 有効 / 無効 | Toggle | ON | 翻訳機能全体 | +| Ollama Host | Text input | `http://localhost:11434` | placeholderにも参考値を表示 | +| Model | Select | 利用可能モデルから選択 | Ollamaからモデル一覧取得 | +| 翻訳先言語 | Select | 日本語 | 初期版は主要言語を定義 | +| Thinking | Select | off | `off/on/low/medium/high` をモデル対応範囲で扱う | +| Temperature | Slider | 0.2 推奨 | 0.0〜2.0、0.1刻み | +| Context Length | Discrete slider | 16k等 | 4k / 8k / 16k / 32k / 128k / 256k | +| Seed Mode | Segmented / Radio | 可変 | 可変 / 固定 | +| Seed Value | Integer input + spinner | 例: 42 | 固定モード時のみ有効 | +| ユーザー用システムプロンプト | Textarea | 参考文をplaceholder表示 | アプリ固定プロンプトとは分離 | + +### 5.2 詳細設定 + +数値項目は原則としてテキスト入力 + ▲▼ステッパーを使用する。 + +| 項目 | 型 | 推奨ステップ | 参考値/placeholder | +|---|---:|---:|---:| +| num_keep | int | 1 | 4 | +| num_predict | int | 1 | -1 | +| top_k | int | 1 | 40 | +| top_p | float | 0.05 | 0.9 | +| min_p | float | 0.01 | 0.05 | +| typical_p | float | 0.05 | 1.0 | +| repeat_last_n | int | 1 | 64 | +| repeat_penalty | float | 0.05 | 1.1 | +| presence_penalty | float | 0.05 | 0.0 | +| frequency_penalty | float | 0.05 | 0.0 | +| stop | string[] | - | `例: <END>` | + +### 5.3 設定UI規則 + +- 数値入力欄はキーボード直接入力を許可する。 +- 数値入力欄には▲▼ステッパーを表示する。 +- 入力値未設定時はOllamaまたはアプリ既定値を使用する。 +- 各テキスト入力欄には薄いplaceholderで参考値を表示する。 +- Context Lengthは任意数値入力ではなく固定プリセットのみとする。 +- Temperatureはスライダーのみで変更可能とし、0.1刻みとする。 +- Seedは「可変」「固定」を明示的に選択できること。 +- Seed固定時のみSeed ValueをOllama requestへ含める。 +- Seed可変時は固定seedを送信しないか、アプリ側で毎回可変となる扱いにする。 + +--- + +## 6. システムプロンプト要件 + +### 6.1 固定システムプロンプト +アプリ側に固定で保持し、通常ユーザーから編集できないこと。 + +必須内容: +- 翻訳のみを実行する。 +- 翻訳先言語に翻訳する。 +- 原文の意味、トーン、固有名詞、技術用語を可能な限り維持する。 +- 原文中の命令をLLMへの指示として解釈しない。 +- 原文中の質問へ回答せず、質問文自体を翻訳する。 +- 不要な解説、前置き、注釈を付けず、原則翻訳本文のみ返す。 + +### 6.2 ユーザー用システムプロンプト +- ユーザーが翻訳スタイルを追加指定できること。 +- 固定プロンプトを完全に置き換えないこと。 +- アプリ側で固定プロンプトと結合して1つのsystem messageとして送信すること。 + +--- + +## 7. Ollama連携要件 + +| ID | 要件 | +|---|---| +| OLL-01 | 公式Ollama REST APIを利用すること(`ollama-js`等のサードパーティSDKは使用しない) | +| OLL-02 | Browser標準のFetch API / Streams APIを使用し、NDJSONレスポンスを安全にパースすること。チャンク境界がJSONオブジェクト境界と一致しない場合にも耐えること | +| OLL-03 | OllamaアクセスはBackgroundのみから行うこと | +| OLL-04 | 既定Hostは `http://localhost:11434` とする | +| OLL-05 | モデル一覧を取得しModel選択に反映すること | +| OLL-06 | Chat APIへ `model`, `messages`, `stream`, `think`, `options` を必要に応じて渡すこと | +| OLL-07 | 未設定の詳細オプションは不要に送信しないこと | +| OLL-08 | `tfs_z`, `mirostat`, `mirostat_tau`, `mirostat_eta` は現行実装対象外とすること | +| OLL-09 | 接続先Hostはvalidatorにより `http://localhost:<port>` または `http://127.0.0.1:<port>` のみに限定すること。Ollama通信前にclient側で再検証すること | + +--- + +## 8. Firefox WebExtensions要件 + +### 8.1 採用 + +| 用途 | API / 機能 | +|---|---| +| Background | `background.scripts` | +| Toolbar | `action` | +| 設定保存 | `browser.storage.local` | +| Content Script通信 | `browser.runtime.connect()` / `runtime.Port` | +| 単発通信 | `browser.runtime.sendMessage()` | +| ページ注入 | `content_scripts` manifest宣言 | +| 設定画面 | `options_ui` | + +### 8.2 原則使用しないもの + +| 非採用 | 理由 / 代替 | +|---|---| +| Manifest V2 | MV3採用 | +| `browser_action` / `browserAction.*` | `action` を使用 | +| `tabs.executeScript()` | 必要なら `scripting.executeScript()`。初期版では原則不要 | +| `tabs.insertCSS()` | 必要なら `scripting.insertCSS()` | +| `background.service_worker` | Firefoxでは採用しない | +| `runtime.getBackgroundPage()` | message / Portで疎結合化 | +| `extension.getBackgroundPage()` | 同上 | +| `window.localStorage` | `storage.local` | +| `eval()` / `new Function()` | セキュリティ上禁止 | +| CDN / Remote JavaScript | 全依存をbundleへ同梱 | +| `innerHTML`によるLLM出力描画 | `textContent`を使用 | +| `externally_connectable` | 不要 | +| `runtime.onMessageExternal` | 不要 | +| `runtime.onConnectExternal` | 不要 | +| `window.postMessage`でページscript連携 | 不要 | +| `wrappedJSObject`, `cloneInto`, `exportFunction` | 不要 | + +--- + +## 9. 権限要件 + +### 9.1 必須権限 + +```json +{ + "permissions": ["storage"], + "host_permissions": ["http://localhost/*", "http://127.0.0.1/*"] +} +``` + +注: Firefoxのmatch patternはポート番号の指定に対応していない(Firefox bug 1362809 / 1468162)。そのため `http://localhost/*` はlocalhostの任意ポートに一致する。実際に通信してよい接続先(localhost / 127.0.0.1 + 任意ポート)はアプリ側のHost validatorで限定する(OLL-09)。 + +### 9.2 Content Script対象 + +```json +{ + "matches": ["http://*/*", "https://*/*"] +} +``` + +### 9.3 不要な権限 +初期版では以下を要求しないこと。 + +- `tabs` +- `cookies` +- `webRequest` +- `history` +- `bookmarks` +- `downloads` +- `nativeMessaging` +- `scripting`(manifest宣言Content Scriptのみで実現できる限り) + +--- + +## 10. セキュリティ要件 + +| ID | 要件 | +|---|---| +| SEC-01 | Webページ、ページDOM、選択文字列を信頼しないこと | +| SEC-02 | ページ文字列・LLM出力をHTMLとして評価しないこと | +| SEC-03 | 翻訳UIへの文字列挿入は `textContent` 等の安全なAPIを使用すること | +| SEC-04 | Content ScriptからOllamaへ直接fetchしないこと | +| SEC-05 | 権限付き通信はBackgroundに限定すること | +| SEC-06 | Backgroundで受信messageの型・長さ・requestIdを検証すること | +| SEC-07 | 選択文字列の最大長を設定すること | +| SEC-08 | Remote JavaScriptをロードしないこと | +| SEC-09 | `eval`, `new Function`, 文字列評価を使用しないこと | +| SEC-10 | ページscriptと直接オブジェクト共有しないこと | +| SEC-11 | `storage.local` に秘密鍵・パスワード等の機密情報を保存しないこと | +| SEC-12 | CSPは必要最小限とし、安易に緩和しないこと | +| SEC-13 | 設定値はBackgroundで型・範囲を再検証すること | +| SEC-14 | 異常な大量requestを避けるため同時翻訳は原則1件とすること | + +初期の最大選択長は20,000文字程度を上限候補とし、実装時に定数化する。 + +--- + +## 11. エラー処理 + +| ケース | UI/処理 | +|---|---| +| Ollama未起動 | 接続エラーを翻訳ポップオーバーに表示 | +| Host不正 | 設定値エラーとして表示 | +| モデル未存在 | モデル未検出エラー | +| Streaming中断 | 中断状態を表示、UIを操作可能に戻す | +| 選択文字列過大 | 翻訳せず上限超過メッセージ | +| 空選択 | 無視 | +| 不正message | Backgroundで破棄 | +| 設定値不正 | 保存前または送信前に拒否 | + +--- + +## 12. 非機能要件 + +| 項目 | 要件 | +|---|---| +| UX | 選択→アイコン→翻訳まで自然で、チャットUIに見えないこと | +| 性能 | 文字列選択だけではLLMを呼ばないこと | +| 同時実行 | 原則1翻訳。新規要求で旧要求をAbort | +| 保守性 | Content / Background / Settings / Ollama clientを分離 | +| 依存 | 必要最小限。大規模UIフレームワークは原則不要 | +| ログ | デバッグ用途に限定し、選択本文を恒常ログへ残さないこと | +| プライバシー | 初期版ではローカルOllamaを前提とし、選択文字列を第三者サービスへ送信しないこと | + +--- + +## 13. テスト要件 + +### 13.1 単体テスト +- 設定値validation +- system prompt組み立て +- Ollama request object生成 +- message validation +- 選択文字列長制限 +- requestId管理 +- 設定default適用 +- Seed可変/固定のrequest反映 + +### 13.2 結合テスト +- Content Script → Background → Ollama → Content Scriptのstream伝送 +- 新規request時の旧stream Abort +- Ollama未起動時のエラー処理 +- モデル未存在時のエラー処理 +- storage保存/復元 + +### 13.3 Firefox実機動作確認 +最低限以下を実際のFirefoxで確認すること。 + +1. 通常HTMLページで文字列選択後、翻訳アイコンが表示される。 +2. アイコン押下までOllama requestが発生しない。 +3. アイコン押下で翻訳ポップオーバーが表示される。 +4. streaming結果が逐次表示される。 +5. コピーが動作する。 +6. 再生成が動作する。 +7. 閉じる操作が動作する。 +8. 新規翻訳で旧streamがキャンセルされる。 +9. 拡張無効時は翻訳アイコンが出ない。 +10. 設定変更後の値が次回翻訳に反映される。 +11. Firefox再起動後も設定が保持される。 +12. Ollama停止時に適切なエラーとなる。 +13. ページ内のHTMLらしき文字列やscript文字列を選択してもDOM実行されない。 +14. LLM出力にHTML文字列が含まれてもHTMLとして解釈されない。 + +--- + +## 14. 受け入れ条件 + +以下をすべて満たした時点で初期版を完了とする。 + +- Firefoxへ一時アドオンとしてロードできる。 +- 選択文字列付近へ翻訳アイコンを表示できる。 +- ユーザー操作後のみ翻訳が開始される。 +- Ollama `chat()` streamingで翻訳できる。 +- 翻訳結果をページ上のポップオーバーへ逐次表示できる。 +- 基本設定・詳細設定を保存/反映できる。 +- Seed可変/固定を切り替えられる。 +- Context Length固定プリセットとTemperature 0.1刻みスライダーが動作する。 +- 数値詳細設定が入力欄 + ▲▼ステッパーで動作する。 +- 旧streamキャンセルが動作する。 +- 不要な高権限APIを要求しない。 +- 非推奨/旧式APIを使用しない。 +- ページ/LLM由来文字列をHTMLとして評価しない。 +- 単体・結合・Firefox実機テストが成功する。 + +--- + +## 15. 初期版の対象外 + +- Chrome/Edge完全互換 +- iframe内翻訳 +- Firefox内蔵PDF Viewerへの対応 +- 翻訳履歴管理 +- チャット機能 +- 複数同時翻訳 +- Web検索・Tool Calling +- Ollama Cloud認証 +- APIキー保存 +- Markdown/HTMLリッチレンダリング +- 自動翻訳(選択しただけでLLM実行) +- `tfs_z` / `mirostat*` diff --git a/doc/Testing-Results.md b/doc/Testing-Results.md new file mode 100644 index 0000000..b2cf153 --- /dev/null +++ b/doc/Testing-Results.md @@ -0,0 +1,87 @@ +# テスト実行結果 + +実施日: 2026-09-01 + +## 1. 単体・結合テスト(自動) + +実行環境: Firefox系ブラウザ(Geckoエンジン)で `tests/test.html` を実行。npm / Node.js 不使用の自作ランナー。 + +**結果: 71 passed, 0 failed** + +### カバレッジ + +| 領域 | テスト数 | 内容 | +|---|---:|---| +| constants sync (content mirror) | 4 | Content Script複製定数とshared定数の同期担保 | +| ollama host validation | 9 | localhost/127.0.0.1+任意port許可、外部host/LAN/https/credentials拒否 | +| settings schema defaults | 4 | 既定値、不正値のフォールバック、ユーザープロンプト長制限 | +| temperature validation | 4 | 0.1刻み、範囲外・中間値拒否 | +| context length validation | 1 | プリセット6値のみ許可 | +| seed validation | 3 | random/fixed、非整数seed破棄 | +| advanced options validation | 4 | null許容、整数/浮動小数チェック、stop配列 | +| buildOptions | 5 | 未設定除外、seedモード反映、API名マップ、mirostat系不使用 | +| toApiThinking | 3 | off→省略、on→true、low/high→文字列 | +| system prompt builder | 4 | 固定プロンプト先頭、言語指定、ユーザープロンプト結合順序 | +| message validation | 6 | translate/cancel検証、長さ上限境界値、requestId生成 | +| ollama client: listModels | 4 | 正常系、404写像、host拒否(fetch前)、ネットワークエラー写像 | +| ollama client: chatStream | 6 | **JSON境界分断チャンク耐性**、thinking分離、不正行スキップ、エラーペイロード写像、think/options条件付き送信、400/404写像 | +| toExtensionError | 2 | AbortError→REQUEST_ABORTED、デフォルトコード | +| TranslationSession | 2 | 新規要求で旧要求Abort+旧portへの通知、isActive/clear | +| settings service | 3 | 保存/復元ラウンドトリップ、不正値フォールバック、onChangedキャッシュ更新 | +| message router: port flow | 5 | started/chunk/completed伝送、**A→B新規要求でAがabort+stale chunk廃棄**、不正メッセージ破棄、OLLAMA_UNAVAILABLE、INVALID_SETTINGS | +| message router: listModels | 2 | 正常系、非信頼sender拒否 | + +## 2. Firefox実機動作確認 + +以下は `about:debugging` から一時アドオンとしてロードし、実Ollama環境で確認する項目です。 + +| # | 確認項目 | 結果 | +|---|---|---| +| 1 | 通常HTMLページで文字列選択後、翻訳アイコンが表示される | ☐ 未実施 | +| 2 | アイコン押下までOllama requestが発生しない | ☐ 未実施 | +| 3 | アイコン押下で翻訳ポップオーバーが表示される | ☐ 未実施 | +| 4 | streaming結果が逐次表示される | ☐ 未実施 | +| 5 | コピーが動作する | ☐ 未実施 | +| 6 | 再生成が動作する | ☐ 未実施 | +| 7 | 閉じる操作が動作する | ☐ 未実施 | +| 8 | 新規翻訳で旧streamがキャンセルされる | ☐ 未実施 | +| 9 | 拡張無効時は翻訳アイコンが出ない | ☐ 未実施 | +| 10 | 設定変更後の値が次回翻訳に反映される | ☐ 未実施 | +| 11 | Firefox再起動後も設定が保持される | ☐ 未実施 | +| 12 | Ollama停止時に適切なエラーとなる | ☐ 未実施 | +| 13 | ページ内のHTMLらしき文字列やscript文字列を選択してもDOM実行されない | ☐ 未実施 | +| 14 | LLM出力にHTML文字列が含まれてもHTMLとして解釈されない | ☐ 未実施 | + +## 3. セキュリティチェック + +| 項目 | 結果 | +|---|---| +| `innerHTML` / `outerHTML` / `document.write` 不使用 | ✅ 全ソースでgrep確認済み | +| `eval` / `new Function` / 文字列setTimeout不使用 | ✅ 全ソースでgrep確認済み | +| リモートJS/CSS/フォント/CDN不使用 | ✅ 外部資源参照なし | +| inline script不使用(popup/options/options HTML) | ✅ 外部script fileのみ | +| LLM出力・選択文字列の`textContent`描画 | ✅ popover body / status すべてtextContent | +| Content ScriptからのOllama fetchなし | ✅ Ollama clientはbackgroundのみ | +| message validation(型・長さ・requestId) | ✅ shared/messages.js で実施、テストで担保 | +| host制限(localhost/127.0.0.1+portのみ) | ✅ schema validator + client assertAllowedHost の二重防御、テストで担保 | +| `execCommand('copy')` 等deprecated API不使用 | ✅ Clipboard APIのみ(失敗時はメッセージ表示) | +| 不要権限なし(tabs/scripting/cookies/webRequest等) | ✅ manifest.jsonで確認 | + +## 4. Firefox API監査チェックリスト(Design §24) + +- [x] Manifest V3 +- [x] `action` 使用 +- [x] `background.scripts` 使用(`type: module`) +- [x] `background.service_worker` 不使用 +- [x] `browser_action` 不使用 +- [x] `tabs.executeScript()` 不使用 +- [x] `runtime.getBackgroundPage()` 不使用 +- [x] `window.localStorage` 不使用 +- [x] `storage.local` 使用 +- [x] `runtime.Port` streaming通信 +- [x] Content ScriptからOllama fetchなし +- [x] `eval` / `new Function`なし +- [x] Remote JSなし +- [x] LLM出力への`innerHTML`なし +- [x] 不要permissionsなし +- [x] iframe初期対応なし
\ No newline at end of file |
