Skip to content

feat(security): migrate API keys to SecretStorage#257

Merged
chhoumann merged 1 commit into
masterfrom
feat/secret-storage
Jul 10, 2026
Merged

feat(security): migrate API keys to SecretStorage#257
chhoumann merged 1 commit into
masterfrom
feat/secret-storage

Conversation

@chhoumann

@chhoumann chhoumann commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

Move PodNotes' OpenAI and Deepgram API keys out of plugin data.json and normal settings exports into Obsidian SecretStorage.

This change also raises the pending release's minimum Obsidian version to 1.11.5. Obsidian 1.11.4 introduced the SecretStorage API, but 1.11.5 added operating-system-backed encryption on disk. Published compatibility history remains unchanged, so the release planner will map only 2.19.0 to the new floor.

Migration and security boundary

  • Schema v2 decodes legacy v0/v1 plaintext credentials into a migration-only payload, never runtime settings.
  • Each value is written under a provider-specific PodNotes-owned SecretStorage ID and read back exactly before its reference is persisted.
  • Exact values are reused on retry. Conflicting values are never overwritten and receive a numeric suffix.
  • Only after all secret writes verify does PodNotes write the schema v2 snapshot. A failure leaves legacy data.json unchanged and retryable.
  • Retired plaintext fields are reserved against unknown-field passthrough. If they appear in schema v2, PodNotes scrubs them without importing their values and fails closed if the scrub cannot be saved.
  • Persisted foreign or wrong-provider secret references are removed without dereferencing them.
  • Explicitly selected shared secrets are copied into collision-safe provider-owned IDs before a reference reaches data.json.
  • Runtime credential reads fail closed and do not cache secret values in the repository.

SecretStorage is vault-local shared infrastructure, not plugin isolation. Other installed plugins can access the same secret collection. Moving a key cannot erase copies in backups, Obsidian Sync history, snapshots, or old plaintext exports, so the documentation includes rotation and cleanup guidance.

Settings, import, and lifecycle behavior

  • The settings UI uses Obsidian's native SecretComponent for selecting or creating secrets.
  • Normal settings exports do not resolve or serialize secret values or device-local references.
  • Explicit plaintext export opt-in uses a separate secrets payload and aborts if a configured value is unavailable locally.
  • Legacy v1 exports and raw legacy data extract old credential fields into the migration-only payload.
  • Credential imports verify SecretStorage writes before persisting references and preserve destination references when credentials are omitted.
  • Imports and both provider selectors share one mutation lane. Failed imports use three-way rollback plus authoritative store replay, so newer store events, selector changes, volume changes, and episode-list changes are not lost or overwritten.
  • Ordinary settings controls are disabled only while an import transaction is active, with prior disabled states restored after the lane drains.
  • Plugin unload aborts supported OpenAI requests, retry waits, and new chunks; suppresses late non-cancelable audio or Deepgram results; stops nested folder creation between segments; prevents post-unload note writes; and cancels active notice timers.

Compatibility and release proof

  • Pending manifest.minAppVersion: 1.11.5
  • Existing versions.json entry: 2.18.4 -> 0.15.9, unchanged
  • Release planner result: 2.19.0
  • Materialized next entry: 2.19.0 -> 1.11.5
  • Release payload remains exactly package.json, package-lock.json, manifest.json, and versions.json

Verification

  • npm run lint
  • npm run format:check
  • npm run typecheck
  • npm run build - 436.89 kB, gzip 129.60 kB
  • npm run test - 76 files, 1,055 tests
  • Both Svelte checks - 0 errors, 0 warnings
  • Pinned MkDocs build using docs/requirements.txt
  • Real isolated Obsidian E2E - 14/14 scenarios
  • Native SecretComponent interaction - shared test secret copied to a provider-owned ID, source preserved, schema v2 persisted, no plaintext in runtime or on disk
  • Clean plugin reload - reference stable and secret available
  • Interaction and clean-reload diagnostic windows - no runtime errors
  • Fail-first regression proof for import/selector save races, ABA store rollback, active OpenAI work, late Deepgram completion, pending audio, nested folder creation, and notice-timer disposal
  • Two independent closure reviews - no remaining P1/P2 findings

The two updated settings screenshots in docs/docs/resources/ were captured from the isolated Obsidian runtime.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying podnotes with  Cloudflare Pages  Cloudflare Pages

Latest commit: d7deb9d
Status: ✅  Deploy successful!
Preview URL: https://0a112463.podnotes.pages.dev
Branch Preview URL: https://feat-secret-storage.podnotes.pages.dev

View logs

Base automatically changed from feat/versioned-persistence to master July 10, 2026 09:44
@chhoumann chhoumann force-pushed the feat/secret-storage branch from f299477 to d7deb9d Compare July 10, 2026 12:31
@chhoumann chhoumann marked this pull request as ready for review July 10, 2026 12:34
@chhoumann chhoumann merged commit 396d0ef into master Jul 10, 2026
6 checks passed
@chhoumann chhoumann deleted the feat/secret-storage branch July 10, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant