Hermes R2 Vault
approvedby Ivan
Encrypts Obsidian notes locally and publishes them to Cloudflare R2 for Hermes. - This plugin has not been manually reviewed by Obsidian staff.
Hermes R2 Vault
Russian version: README.ru.md
Encrypted Obsidian vault with Cloudflare R2 sync.
Client-side encrypted note publishing to Cloudflare R2 via a control API (Hermes VPS). Supports bidirectional sync, revision history, safe deletion, and vault restore.
Architecture
Obsidian vault → client-side encryption (XChaCha20-Poly1305) → Cloudflare R2
↕
Hermes control API (VPS)
↓
Hermes‑R2‑File‑Resolver (Python)
- Cloudflare R2 is the only persistent knowledge store.
- The control API never receives plaintext Markdown content.
- Plaintext exists only in Obsidian and briefly in plugin memory during encryption.
- Bidirectional sync with LWW (Last-Writer-Wins) conflict resolution.
Features
| Feature | Status |
|---|---|
| Client-side encryption (XChaCha20-Poly1305 + Argon2id) | ✅ |
| Bidirectional sync | ✅ |
| Content-addressable storage (CAS) for manifests | ✅ |
| Multi-device enrollment | ✅ |
| Setup URI for device pairing | ✅ |
| File revision history | ✅ |
| Single-file restore | ✅ |
| Point-in-time vault restore | ✅ |
| History integrity scanner | ✅ |
| Retention planner | ✅ |
| Safe garbage collection | ✅ |
| Mass deletion protection | ✅ |
| Conflict Center UI | ✅ |
| Audit log | ✅ |
| Russian and English localization | ✅ |
Project Structure
hermes-r2-vault/
├── src/ # Plugin source code
│ ├── main.ts # Entry point, commands, modals
│ ├── sync.ts # Sync orchestrator (core)
│ ├── models.ts # Interfaces and state types
│ ├── constants.ts # Constants and defaults
│ ├── crypto.ts # XChaCha20-Poly1305, Argon2id, X25519
│ ├── envelope.ts # Binary envelope with AAD
│ ├── transport.ts # HTTP client for presigned URLs
│ ├── cas-store-r2.ts # Content-addressable storage (R2)
│ ├── manifest-snapshot.ts # Manifest snapshots
│ ├── file-history.ts # File revision history
│ ├── reference-graph.ts # Revision reference graph
│ ├── retention-planner.ts # Retention planner
│ ├── vault-restore.ts # Vault restore
│ ├── integrity-scanner.ts # Integrity scanner
│ ├── download.ts # Download and decrypt
│ ├── conflict-center.ts # Conflict resolution UI
│ ├── mass-deletion.ts # Mass deletion protection
│ ├── deletion-metrics.ts # Deletion counters
│ ├── audit-log.ts # Event logging
│ ├── fault-handler.ts # Error handling
│ ├── safety.ts # Empty manifest + stale device guards
│ ├── key-envelope.ts # Key management
│ ├── path-utils.ts # Path normalization
│ ├── filter.ts # File filtering
│ ├── queue.ts # Change queue
│ ├── ulid.ts # ULID generation
│ ├── settings.ts # Settings UI
│ ├── log.ts # Structured logging
│ ├── errors.ts # Typed errors
│ ├── i18n/ # Internationalization
│ │ └── index.ts # Russian + English
│ └── protocol/ # Protocol types
│ ├── manifest.ts # SyncManifestV1
│ ├── manifest-snapshot.ts # ManifestSnapshotV1
│ ├── revision.ts # RevisionMetadataV1
│ ├── tombstone.ts # TombstoneV1
│ ├── file-history.ts # FileHistoryIndexV1
│ ├── differ.ts # Diff + LWW resolution
│ ├── conflict.ts # Conflict classification
│ ├── validation.ts # Type validation
│ ├── identifiers.ts # UUID/ULID validation
│ ├── paths.ts # Path encoding
│ ├── canonical-json.ts # Canonical JSON
│ ├── cas-store.ts # CasStore interface
│ ├── device.ts # Device metadata
│ ├── device-envelope.ts # Device key envelope
│ ├── setup-uri.ts # Setup URI
│ ├── setup-token.ts # Setup token
│ └── index.ts # Re-exports
├── tests/ # Tests (379 tests, Vitest)
│ ├── revision-history.test.ts
│ ├── retention-planner.test.ts
│ ├── vault-restore.test.ts
│ ├── integrity-scanner.test.ts
│ ├── protocol.test.ts
│ ├── deletion-safety.test.ts
│ ├── crypto.test.ts
│ ├── key-envelope.test.ts
│ ├── device-identity.test.ts
│ ├── transport.test.ts
│ ├── queue.test.ts
│ ├── i18n.test.ts
│ ├── differ.test.ts
│ └── ... (21 files)
├── hermes-r2-file-resolver/ # Python resolver (Hermes VPS)
├── docs/ # Documentation
│ ├── architecture.md
│ ├── sync-protocol-v1.md
│ ├── security-model.md
│ ├── threat-model.md
│ ├── revision-model-v1.md
│ ├── r2-layout-v1.md
│ ├── manifest-v1.md
│ ├── deletion-safety.md
│ └── ... (16 files)
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── build.mjs
└── manifest.json
Installation
-
Community plugin store (once approved): Settings → Community plugins → Browse → search for "Hermes R2 Vault" → Install → Enable.
-
Manual install: copy
main.js,manifest.json, andstyles.cssto<vault>/.obsidian/plugins/hermes-rvault/. -
Enable the plugin in Obsidian settings.
-
Configure the API Base URL, Vault ID, and secret names.
-
Run "Initialize encrypted vault" to generate the master key.
-
Store the access token via the settings tab (SecretStorage).
-
Run "Test connection" to verify connectivity.
-
Run "Run full scan" to upload existing notes.
Note: This plugin is desktop-only. The Obsidian mobile app does not expose the SecretStorage API required for keys and tokens.
Configuration
Plugin settings are available under Hermes R2 Vault Settings in Obsidian settings.
General Settings
| Setting | Description | Default |
|---|---|---|
| API Base URL | Control API URL (HTTPS) | — |
| Vault ID | Vault UUID | — |
| Device ID | Device identifier (auto-generated) | randomUUID() |
| Access Token Secret Name | SecretStorage key name | — |
| Auto-upload | Automatically upload changes | false |
| Debounce delay (ms) | Change processing delay | 1500 |
| Max concurrent uploads | Parallel upload limit | 2 |
Deletion Safety
| Setting | Description | Default |
|---|---|---|
| Enable remote trash | Keep deleted files in manifest | true |
| Deleted retention (days) | Deleted metadata retention period | 90 |
| Mass deletion threshold | Deletion count threshold | 10 |
| Require deletion approval | Prompt for confirmation | true |
| Empty manifest protection | Block sync on empty remote manifest | true |
| Stale device guard | Block stale device sync | true |
| Pre-deletion snapshot | Capture hash before deletion | false |
Revision History
| Setting | Description | Default |
|---|---|---|
| Revision history enabled | Enable revision history | false |
| Max revisions per file | Max revisions per file | 100 |
| Max snapshot generations | Max snapshot count | 50 |
| Revision TTL (days) | Revision time-to-live | 365 |
| Revision retention (days) | Guaranteed retention period | 90 |
| Minimum retained revisions | Minimum revisions to keep | 20 |
Commands
| Command | Description |
|---|---|
| Test connection | Verify API connectivity |
| Initialize encrypted vault | Generate vault master key |
| Recover vault key | Recover key from passphrase |
| Change recovery passphrase | Change recovery passphrase |
| Upload current note | Upload active note |
| Run full scan | Full file scan |
| Pause automatic uploads | Pause upload queue |
| Resume automatic uploads | Resume upload queue |
| Show sync status | Display sync status |
| View sync conflicts | Show sync conflicts |
| Open Conflict Center | Open conflict resolution UI |
| Undo latest deletion | Undo last deletion |
| Purge deleted file | Safe permanent purge |
| Preview retention plan | Retention plan preview |
| Apply retention plan | Execute retention GC |
| List vault restore points | List available snapshots |
| Preview vault restore | Preview vault state restore |
| Scan history integrity | Check history integrity |
| Show deletion safety metrics | Display deletion statistics |
| Show recent faults | Show recent fault records |
| Clear local sync metadata | Clear local sync state |
Sync Protocol
Full protocol specification: docs/sync-protocol-v1.md.
Protocol version: 1 (marker: docs/protocol-version-v1.json)
Feature Flags
| Flag | Status |
|---|---|
revisionHistory | ✅ true |
fileRevisionRestore | ✅ true |
pointInTimeRestore | ✅ true |
historyIntegrityScan | ✅ true |
retentionPlanner | ✅ true |
safeGarbageCollection | ✅ true |
resumableRestore | ✅ true |
hermesReadWrite | ❌ false |
R2 Layout
vaults/{vaultId}/
├── manifests/
│ ├── current.json # Manifest pointer
│ ├── by-hash/{sha256}.json # Content-addressable manifests
│ └── snapshots/{generation}.json # Per-generation snapshots
├── history/
│ ├── manifests/index.v1.json # Snapshot index
│ └── files/{fileId}/index.v1.json # File history indices
├── revisions/{fileId}/{revisionId}.hvr # Immutable revisions
├── tombstones/{fileId}.hvr # Tombstones
└── config/
├── owner-key-envelope.v1.json
└── recipients/{recipientId}.key-envelope.v1.json
Security
- Encryption: XChaCha20-Poly1305 with 24-byte nonce
- KDF: Argon2id (64MB memory, 3 iterations)
- Key derivation: HKDF-SHA256 (path key, content key)
- Key exchange: X25519 ECDH
- Authentication: HMAC-SHA256 for path identity
Details: docs/security-model.md, docs/threat-model.md.
Development
Scripts
| Command | Description |
|---|---|
npm run build | Build plugin (esbuild) |
npm run test | Run tests (Vitest) |
npm run test:coverage | Coverage report |
npm run typecheck | TypeScript type check |
npm run lint | ESLint |
Dependencies
- Runtime:
@noble/ciphers,@noble/curves,@noble/hashes - Dev: TypeScript, esbuild, Vitest, ESLint, Obsidian API
Python Resolver
The hermes-r2-file-resolver/ subproject implements the protocol in Python for the Hermes VPS backend.
Testing
Before committing:
npm run typecheck # TypeScript — clean
npm run lint # ESLint — clean
npm run test # 379 tests, 21 files
npm run build # esbuild — success
License
MIT
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.