Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
12 changes: 7 additions & 5 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

| Version | Supported |
| --- | --- |
| `main` (v1.24.3) | ✅ Full support |
| `1.3.x – 1.24.2` | ⚠️ Best effort (upgrade strongly recommended) |
| `main` (v1.25.0) | ✅ Full support |
| `1.3.x – 1.24.3` | ⚠️ Best effort (upgrade strongly recommended) |
| `< 1.3.0` | ❌ Not supported |

Maintainer-facing documentation (CI, contributing, architecture) is indexed in [`README.md`](README.md#-documentation-hub) and enumerated in [`AUDIT.md`](AUDIT.md).
Expand Down Expand Up @@ -33,8 +33,10 @@ If the email channel is not configured, use GitHub Private Vulnerability Reporti
## Known Active Security Work Items

Phase 0 and Phase 2 hardening complete as of v1.19.0; the Phase 3 plugin-isolation (SEC-7) and
voice-download-UX (SEC-8) items shipped in v1.21–v1.23. The only remaining at-rest gap is SEC-6
(DuckDB OPFS encryption), whose module exists but is not yet wired into the persistence path:
voice-download-UX (SEC-8) items shipped in v1.21–v1.23. SEC-6 (DuckDB at-rest encryption) is now
partially wired as of v1.25.0 — the one column holding literal manuscript prose is encrypted;
full OPFS-file-level encryption remains infeasible (DuckDB-WASM owns the file handle directly)
and is an accepted, documented limitation for the remaining metadata columns.

| ID | Area | Description | Status |
| --- | --- | --- | --- |
Expand All @@ -43,7 +45,7 @@ voice-download-UX (SEC-8) items shipped in v1.21–v1.23. The only remaining at-
| SEC-3 | Storage | IDB at-rest encryption — `services/storage/storageEncryptionService.ts`, AES-256-GCM, PBKDF2 600k iter; `enableIdbAtRestEncryption` flag (on by default since v1.23) | ✅ Implemented (Phase 2 / B-1) |
| SEC-4 | Voice | Web Speech API consent gate — GDPR Art. 13 disclosure and explicit opt-in before audio is routed to cloud STT providers | ✅ Complete (Phase 0) |
| SEC-5 | Storage | IDB at-rest encryption UX — passphrase unlock modal, forgot-passphrase export flow, key rotation UI | ✅ Complete (2026-06-02) |
| SEC-6 | Storage | DuckDB OPFS at-rest encryption — WAL and data files outside IDB; requires separate encryption layer | 🟡 Partial (P0-4) — encryption module + unit tests exist (`services/duckdb/duckdbEncryption.ts`), but it is **not yet wired into the DuckDB persistence path** (0 production callers as of v1.23.1), so DuckDB analytics are **not** encrypted at rest. Integration pending. |
| SEC-6 | Storage | DuckDB OPFS at-rest encryption — WAL and data files outside IDB; requires separate encryption layer | 🟡 Partial (v1.25.0) — full OPFS-file-level encryption is infeasible (DuckDB-WASM owns the OPFS file handle directly; no app-level interception point). Cell-level encryption is now wired for the one column holding literal manuscript prose, `codex_mentions.excerpt`: when `enableIdbAtRestEncryption` is active, `duckdbCodexWrite()` (`services/duckdb/duckdbAnalytics.ts`) encrypts new excerpts via `services/duckdb/duckdbEncryption.ts` (AES-256-GCM, reuses the IDB encryption key) into `excerpt_enc BLOB` and nulls the plaintext column; `services/duckdb/codexExcerptEncryptionMigration.ts` backfills pre-existing plaintext rows once encryption is unlocked. All other DuckDB metadata columns (`title`/`logline`/`name`/`character_names`/`label`) remain intentionally plaintext — bounded-exposure design, not manuscript prose. |
| SEC-7 | Plugin System | Worker isolation for plugin execution — prevent main-thread access, enforce timeouts | ✅ Complete (P0-2) — plugin execution routed to an isolated worker (`workers/plugin.worker.ts`) via `pluginRegistry.ts` + `workerBusManager.ts`, sandboxed API + timeout; adversarial tests in `tests/unit/workers/plugin.worker.test.ts`. Follow-up FU-1: full timeout/abort coupling for dynamic `import()` + sync loops (low impact). |
| SEC-8 | Voice | WASM model download UX — progress feedback, cancel/retry controls for Whisper/Kokoro models | ✅ Complete (P0-5) — `components/voice/VoiceModelDownloadModal.tsx` (progress, cancel, retry), wired from `components/settings/VoiceSettingsSection.tsx`; driven by `VoiceCommandService.downloadVoiceModels(type, signal?)`. |
| SEC-9 | ProForge / Copilot | Prompt-injection hardening — reject C0 control chars, null bytes, lone surrogates in AI-proposed edits; per-item graceful skip instead of batch abort | ✅ Complete (PR #114) |
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- **Primary deploy target:** Static SPA on GitHub Pages (`/WorldScript-Studio/` base path)
- **Secondary targets:** Vercel (root base) and Cloudflare Pages via edge builds (`pnpm run build:edge`)
- **Desktop:** Tauri 2 bundles for Linux (AppImage), macOS (DMG), and Windows (MSI); auto-updater enabled via `latest.json`
- **Version:** `1.24.3`
- **Version:** `1.25.0`
- **License:** MIT

The app supports a multi-provider AI stack (Gemini, OpenAI, Claude, Grok, OpenRouter, Ollama, WebLLM, ONNX Runtime Web, Transformers.js), four AI execution modes (Hybrid / Cloud / Local / Eco), real-time collaboration with E2E encryption, a Plot Board v2 with swimlane/canvas/timeline modes, character/world management, manuscript export, voice dictation, and a 19-locale i18n layer.
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.25.0] — 2026-07-31

### Added

- **Grok (xAI) wired into the primary provider dropdown** — the backend (`streamGrok()`, BYOK key
Expand Down Expand Up @@ -34,6 +36,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
UI renders the exact `OLLAMA_ORIGINS=<origin> ollama serve` command for the current deployment.
See [ADR-0017](docs/adr/0017-pwa-browser-ollama-opt-in.md) (Issue #266 follow-up).

### Security

- **DuckDB `codex_mentions.excerpt` — the one analytics column holding literal manuscript prose —
is now cell-level encrypted (SEC-6)** when `enableIdbAtRestEncryption` is active.
`duckdbCodexWrite()` (`services/duckdb/duckdbAnalytics.ts`) encrypts new excerpts via
`services/duckdb/duckdbEncryption.ts` (AES-256-GCM, reusing the IDB at-rest encryption key) into
a new `excerpt_enc BLOB` column and nulls the plaintext `excerpt` column; a new backfill
migration (`services/duckdb/codexExcerptEncryptionMigration.ts`) re-encrypts any pre-existing
plaintext rows once encryption is unlocked, without blocking the schema's other migrations when
encryption isn't active this session. Full OPFS **file-level** encryption remains infeasible —
DuckDB-WASM owns the OPFS file handle directly, leaving no app-level interception point — so the
other DuckDB metadata columns (`title`/`logline`/`name`/`character_names`/`label`) stay
intentionally plaintext by design, not manuscript prose.

### Fixed

- **Doc-drift gate (`scripts/check-doc-metrics.mjs`) had a blind spot for still-open checklist
Expand All @@ -58,6 +74,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
mergeable-state cache-lag symptom, plus documents the stacked-PR auto-close-on-squash-merge
side effect and its recovery steps; and strengthens the CodeAnt Correction Loop policy to
explicitly cover CodeRabbit's collapsed nitpick and outside-diff-range comment sections. (#294)
- **`.github/SECURITY.md` / `docs/SECURITY-THREAT-MODEL.md` SEC-6 status corrected** to reflect the
DuckDB excerpt-encryption wiring above, and the Claude serverless proxy section gains an explicit
monitoring recommendation (platform-native Vercel/Cloudflare request analytics — no in-app
logging, which would violate the proxy's zero-console-call stateless guarantee).
- **`GROK-PROVIDER-INTEGRATION-PLAN.md`** status header updated from "Plan only — do not implement
yet" to reflect that all phases shipped; retained as the historical design record.

## [1.24.3] — 2026-07-30

Expand Down
9 changes: 5 additions & 4 deletions GROK-PROVIDER-INTEGRATION-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ from the Settings UI. **Addendum (§7):** an unrelated but adjacent follow-up to
opt-in, experimental feature flag letting the PWA attempt direct browser-to-Ollama connections,
matching the "technically: yes" option the maintainer already described in that issue's comment
thread.
**Status:** v1.24.3 (2026-07-30)
**Execution:** **Plan only — do not implement yet.** Per explicit instruction, this file is written
now and executed only after every other workstream in the current post-recovery audit sprint
(WS-3 through WS-10, including the WS-8 close-out) has merged into `main`.
**Status:** ✅ Complete — merged 2026-07-30, see `[Unreleased]`/`[1.25.0]` in
[CHANGELOG.md](CHANGELOG.md) for the user-facing summary.
**Execution:** All phases below (Grok wiring, Claude desktop + web proxy, Ollama-in-PWA opt-in
addendum) have shipped. This file is retained as the historical design record; do not treat any
"do not implement yet" language below as current.

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<img src="https://img.shields.io/badge/TypeScript-7.x_(tsgo)-3178C6?logo=typescript&logoColor=white" alt="TypeScript 7 (tsgo)">
<img src="https://img.shields.io/badge/AI-Gemini_%7C_OpenAI_%7C_OpenRouter_%7C_Ollama_%7C_WebLLM-4285F4?logo=google" alt="Gemini · OpenAI · OpenRouter · Ollama · WebLLM">
<img src="https://img.shields.io/badge/Local_AI-WebGPU_%7C_ONNX_%7C_Transformers.js-8B5CF6" alt="WebGPU · ONNX · Transformers.js">
<img src="https://img.shields.io/badge/Version-v1.24.3-6366F1" alt="v1.24.3">
<img src="https://img.shields.io/badge/Version-v1.25.0-6366F1" alt="v1.25.0">
<img src="https://img.shields.io/badge/Storage-IndexedDB_v8-F59E0B" alt="IndexedDB v8">
<img src="https://img.shields.io/badge/PWA-v3.0-5BB974?logo=pwa" alt="PWA v3.0">
<img src="https://img.shields.io/badge/i18n-19_locales-2861_keys-0EA5E9" alt="i18n 19 locales — 2861 keys">
Expand Down
38 changes: 14 additions & 24 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,19 @@ Status: 🔄 in progress | ⬜ open | ✅ done

---

## Native Grok + Claude + Ollama-in-PWA providers (2026-07-30)

README documented Grok and Claude as working cloud AI providers; neither was reachable from
`AiProviderCard.tsx`'s main flow. Full details + rationale in
[ADR-0016](docs/adr/0016-native-grok-and-claude-providers.md) and
[ADR-0017](docs/adr/0017-pwa-browser-ollama-opt-in.md); see `[Unreleased]` in
[CHANGELOG.md](CHANGELOG.md) for the user-facing summary. Execution plan:
[`GROK-PROVIDER-INTEGRATION-PLAN.md`](GROK-PROVIDER-INTEGRATION-PLAN.md).

- ✅ **Phase 1 — Grok**: wired into the primary provider dropdown + `providerFactory.ts`; the
backend (`streamGrok()`) already worked, this was purely a UI/wiring gap.
- ✅ **Phase 2 Track A — Claude on desktop**: `streamAnthropic()` now branches on
`isTauriRuntime()` before throwing; desktop calls Anthropic directly via the native-HTTP pattern
ADR-0012 established for Ollama.
- ✅ **Phase 2 Track B — Claude on web/PWA**: new stateless serverless proxy
(`api/claude-proxy.ts` + `functions/api/claude-proxy.ts`, Vercel + Cloudflare Pages) — this app's
first backend dependency. Mandatory abuse controls (schema validation, body-size cap, origin
check, rate limit, timeouts), never logs secrets. GitHub Pages shows an honest unavailable state.
- ✅ **Addendum — Ollama-in-PWA opt-in (Issue #266 follow-up)**: `enableBrowserOllama` flag
(default off) lets the browser attempt a direct Ollama connection when the user has separately
configured their own server's `OLLAMA_ORIGINS`; not a proxy, not a bypass, not the default.
- ⬜ Open WS-11's PR, run the CodeRabbit correction loop to quiescence, merge to `main`.
- ⬜ Comprehensive tag/release afterward, folding in everything from `[Unreleased]`.
## Release — v1.25.0 tag/publish (2026-07-31)

Native Grok/Claude providers, opt-in Browser-Ollama, and DuckDB `codex_mentions.excerpt`
cell-level encryption (SEC-6) all shipped in this cycle — see
[`docs/history/completed-v1.25.0-providers.md`](docs/history/completed-v1.25.0-providers.md) for
the full completed checklist. `CHANGELOG.md` is dated `[1.25.0]`, `package.json`/`README.md` are
version-bumped, and `src-tauri`/`public/sw.js` versions are synced.

- ⬜ Tag and publish the release once CI is fully green on the release branch/PR: `git tag -a
v1.25.0`, push the tag, `gh release create v1.25.0` — see the exact hand-off commands in
`docs/history/completed-v1.25.0-providers.md`'s source plan. Not auto-executed; requires explicit
go-ahead per operational-safety rules on pushing/tagging.
Comment thread
qnbs marked this conversation as resolved.
Outdated


---

Expand Down Expand Up @@ -287,7 +277,7 @@ README documented Grok and Claude as working cloud AI providers; neither was rea
- 🔄 **C-7 remainder** — Coverage → L85%/B75%/F80%; Stryker break 75→80 (current thresholds: L73/F65/B58). **Phase 3 started (2026-06-02):** +33 LoRA tests (useLoraView, training wizard, sub-panels — were 0%)
- ✅ IDB at-rest encryption UX (2026-06-02 reconciliation) — `IdbUnlockModal` (startup unlock + 2-step forgot-passphrase escape hatch, `App.tsx:182-188,638-643`), `PassphraseModal` (set/change/disable), real read/write gating `idbProjectStore.ts:209-265`, session lock + key rotation (Phase 1). `enableIdbAtRestEncryption` flag in Settings › Privacy with ⚠ warning
- ✅ **P0-2** — Plugin worker isolation (`workers/plugin.worker.ts`) — routes plugin execution to isolated worker context with timeout and sandboxed API
- 🟡 **P0-4** — DuckDB OPFS encryption (`services/duckdb/duckdbEncryption.ts`) — encryption module + unit tests landed (passphrase-derived AES-256-GCM), **but not yet wired into the DuckDB persistence path** (0 production callers as of v1.23.1), so analytics are not encrypted at rest. Integration into `duckdbClient`/`duckdbWorker` remains open. (2026-06-17 reconciliation — was over-marked ✅; see `.github/SECURITY.md` SEC-6.)
- 🟡 **P0-4** — DuckDB OPFS at-rest encryption (`services/duckdb/duckdbEncryption.ts`) — cell-level encryption is now wired for the one column holding literal manuscript prose, `codex_mentions.excerpt` (v1.25.0): `duckdbCodexWrite()` encrypts it into `excerpt_enc BLOB` when `enableIdbAtRestEncryption` is active, with `services/duckdb/codexExcerptEncryptionMigration.ts` backfilling pre-existing plaintext rows. Full OPFS **file-level** encryption remains infeasible (DuckDB-WASM owns the OPFS file handle directly) and is an accepted, permanent limitation, not a remaining task — see `.github/SECURITY.md` SEC-6.
- ✅ **P0-5** — Voice WASM model download UI (`components/voice/VoiceModelDownloadModal.tsx`) — progress modal for Whisper/Kokoro model downloads with cancel/retry
- 🔄 Whisper WASM STT model download + inference pipeline (B-2 continuation) — engine (`services/voice/wasmSttEngine.ts`) + download UI (P0-5 above) shipped; remaining scope narrowed to E2E integration test coverage only — stale ⬜ reconciled 2026-07-30.
- 🔄 Kokoro/Piper TTS WASM engines — Kokoro DONE (`services/voice/kokoroTtsEngine.ts`, tested since 2026-05-31); Piper remains an unimplemented type-level placeholder only (`preferredTtsEngine: 'piper'` in `voiceCommandService.ts` has no backing engine file) — stale ⬜ reconciled 2026-07-30.
Expand Down
11 changes: 9 additions & 2 deletions app/listenerMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ecoModeService } from '../services/ai/ecoModeService';
import { extractStoryCodex, saveStoryCodex } from '../services/codexService';
import { checkStorageHealth } from '../services/dbInitialization';
import {
loadCodexExcerptEncryptionMigration,
loadDuckdbAnalytics,
loadDuckdbMigration,
loadLocalRagService,
Expand Down Expand Up @@ -362,10 +363,16 @@ listenerMiddleware.startListening({
const projectId = project.id || 'default';
const { runRagVectorMigration } = await loadRagVectorMigration();
const ragResult = await runRagVectorMigration(projectId, project.manuscript, gate);
// QNBS-v3: SEC — if either migration aborted because analytics was opted out mid-run, do NOT mark
// QNBS-v3: SEC-6 — re-encrypts any pre-existing plaintext codex_mentions.excerpt rows once an
// IDB encryption key is unlocked. Reports aborted:false (not a failure) when encryption isn't
// active this session — see codexExcerptEncryptionMigration.ts's doc comment for why that must
// not block the other migrations' 'done' status.
const { runCodexExcerptEncryptionMigration } = await loadCodexExcerptEncryptionMigration();
const codexEncResult = await runCodexExcerptEncryptionMigration(projectId, gate);
// QNBS-v3: SEC — if any migration aborted because analytics was opted out mid-run, do NOT mark
// it 'done' (no markers were written). Reset to 'idle' so re-enabling analytics re-triggers the
// backfill (the predicate re-fires on analyticsEnabled→true while DuckDB is ready + status idle).
if (seedResult.aborted || ragResult.aborted) {
if (seedResult.aborted || ragResult.aborted || codexEncResult.aborted) {
listenerApi.dispatch(analyticsActions.setMigrationStatus('idle'));
return;
}
Expand Down
Loading
Loading