# Obsidian Cloud KMS Encryption > πŸ‡·πŸ‡Ί [ДокумСнтация Π½Π° русском](README_RU.md) [![CI](https://github.com/ViktorUJ/obsidian-cloud-kms/actions/workflows/ci.yml/badge.svg)](https://github.com/ViktorUJ/obsidian-cloud-kms/actions/workflows/ci.yml) [![CodeQL](https://github.com/ViktorUJ/obsidian-cloud-kms/actions/workflows/codeql.yml/badge.svg)](https://github.com/ViktorUJ/obsidian-cloud-kms/actions/workflows/codeql.yml) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ViktorUJ/obsidian-cloud-kms/badge)](https://scorecard.dev/viewer/?uri=github.com/ViktorUJ/obsidian-cloud-kms) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12824/badge)](https://www.bestpractices.dev/projects/12824) [![Known Vulnerabilities](https://snyk.io/test/github/ViktorUJ/obsidian-cloud-kms/badge.svg)](https://snyk.io/test/github/ViktorUJ/obsidian-cloud-kms) [![SLSA 2](https://slsa.dev/images/gh-badge-level2.svg)](https://slsa.dev) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) An [Obsidian](https://obsidian.md) plugin providing **transparent encryption** of secret blocks and binary files using AWS KMS. ## Table of Contents - [Why](#why) - [Key Principles](#key-principles) - [How It Works](#how-it-works) - [Commands](#commands) - [Usage](#usage) - [Encrypting text in notes](#encrypting-text-in-notes) - [Nested code fences (mermaid, code)](#nested-code-fences-mermaid-code-etc) - [Encrypting binary files](#encrypting-binary-files) - [Removing encryption](#removing-encryption) - [Behavior](#behavior) - [Installation](#installation) - [Requirements](#requirements) - [From GitHub Releases](#from-github-releases) - [From source](#from-source) - [AWS Setup](#aws-setup) - [Settings](#settings) - [Security](SECURITY.md) - [Security Testing](SECURITY_TESTING.md) - [Reproducible Builds](REPRODUCIBLE_BUILDS.md) - [Verifying Release Integrity](#verifying-release-integrity) - [On-Disk Format](#on-disk-format) - [Key Access Management](#key-access-management) - [Multi-Key Architecture](#multi-key-architecture) - [User from the same AWS account](#user-from-the-same-aws-account) - [User from the same AWS Organization](#user-from-the-same-aws-organization-different-account) - [User from an external organization](#user-from-an-external-organization-external-aws-account) - [CLI Decryption (without Obsidian)](#cli-decryption-without-obsidian) - [Key Rotation / Migration (ocke-rekey)](#key-rotation--migration-ocke-rekey) - [Comparison with Alternatives](#comparison-with-alternatives) - [Git Workflow](#git-workflow) - [KMS Status Indicator](#kms-status-indicator) - [Known Limitations](#known-limitations) - [Development](#development) - [License](LICENSE) ## Why If you store your Obsidian vault in S3, Git, or any other remote storage β€” note contents are accessible to anyone who gains access to that storage. This plugin implements a **Zero Trust Storage** model: only ciphertext exists on disk and in remote. Decryption happens locally, in memory, only when Cloud KMS access is available. ## Key Principles - **Envelope Encryption** β€” each block/file is encrypted with a unique DEK (AES-256-GCM), and the DEK itself is wrapped by a CMK in cloud KMS - **Identity-based Auth** β€” no passwords; uses system credentials (AWS SSO, IAM Role, `~/.aws/credentials`) - **Local-First Crypto** β€” symmetric encryption runs locally via WebCrypto API; only the DEK goes to KMS for wrap/unwrap - **Zero Cleartext on Disk** β€” decrypted content exists only in Obsidian process memory - **Transparent** β€” encryption/decryption happens automatically on file read/write (monkey-patch vault adapter) - **Nested Content** β€” `%%secret-start%%` / `%%secret-end%%` markers don't conflict with code fences, allowing nested ```mermaid, ```js and any other markdown ## How It Works ### Markdown files (secret blocks) The plugin intercepts file reads and writes at the Obsidian vault adapter level: - **On write to disk**: all blocks between `%%secret-start%%` and `%%secret-end%%` are automatically encrypted β†’ stored on disk as `ocke-v1` block - **On read from disk**: all `ocke-v1` blocks are automatically decrypted β†’ shown in editor between `%%secret-start%%` / `%%secret-end%%` ### Binary files (PDF, images, audio) - **"Encrypt current file"** command encrypts the file in place (name unchanged) - On open β€” file is decrypted in memory (Blob URL), Obsidian displays it normally - Disk always contains encrypted bytes in OCKE format - Encrypted files are marked with πŸ”’ in the file explorer ## Commands | Command | Description | |---------|-------------| | **Wrap selection in secret block** | Wraps selected text in `%%secret-start%%` / `%%secret-end%%` | | **Unwrap secret block** | Removes encryption markers, leaving plaintext | | **Encrypt current file with AWS KMS** | Encrypts a binary file (PDF, PNG, MP3) in place | | **Decrypt current file with AWS KMS (permanent)** | Permanently decrypts a binary file (writes plaintext to disk) | ## Usage ### Encrypting text in notes 1. Select text in a note 2. `Ctrl+P` β†’ **"Wrap selection in secret block"** 3. Text is wrapped in `%%secret-start%%` / `%%secret-end%%` markers 4. On save β€” automatically encrypted on disk ### Creating a secret block manually Simply wrap text in markers: ```markdown # My note This is public text. %%secret-start%% This is secret content β€” will be encrypted on save. Passwords, tokens, private notes β€” anything. %%secret-end%% This is public text again. ``` ### Nested code fences (mermaid, code, etc.) `%%` markers are Obsidian comments, invisible in Reading view. Content between them is regular markdown that renders normally: ````markdown %%secret-start%% # Secret Architecture ```mermaid graph TD A[Client] --> B[API Gateway] B --> C[Lambda] C --> D[DynamoDB] ``` ```bash export SECRET_KEY="my-super-secret-key" aws s3 cp secret.tar.gz s3://my-bucket/ ``` Production password: `P@ssw0rd123!` %%secret-end%% ```` After saving, the entire block (including mermaid diagram and code) is encrypted on disk. On open β€” decrypted, and mermaid renders as a diagram in Reading view. ### Encrypting binary files 1. Open a PDF, image, or other binary file 2. `Ctrl+P` β†’ **"Encrypt current file with AWS KMS"** 3. File is encrypted in place (name unchanged, πŸ”’ appears in file explorer) 4. On next open β€” decrypted in memory, displayed normally For **permanent** decryption (write plaintext back to disk): - `Ctrl+P` β†’ **"Decrypt current file with AWS KMS (permanent)"** ### Removing encryption 1. Select the entire block (from `%%secret-start%%` to `%%secret-end%%`) 2. `Ctrl+P` β†’ **"Unwrap secret block"** 3. Markers are removed, text remains as regular markdown (no longer encrypted) ## Behavior | Situation | Result | |-----------|--------| | Saving .md with `%%secret-start%%` blocks | Blocks encrypted β†’ `ocke-v1` block on disk | | Opening .md with `ocke-v1` blocks (key available) | Decrypted β†’ `%%secret-start%%...%%secret-end%%` in editor | | Opening .md with `ocke-v1` blocks (key NOT available) | Remain as `ocke-v1` (encrypted base64) | | Opening encrypted PDF/PNG (key available) | Decrypted in memory β†’ displayed normally | | Opening encrypted PDF/PNG (key NOT available) | Obsidian cannot render the file | | KMS unavailable on save | File saved as-is, error shown | | Each block/file | Encrypted independently (own DEK) | | File explorer | Encrypted binary files marked with πŸ”’ | ## Installation ### Requirements - Obsidian β‰₯ 1.4.0 (desktop) - AWS credentials configured (`~/.aws/credentials` or `aws sso login`) ### From GitHub Releases 1. Go to [Releases](https://github.com/ViktorUJ/obsidian-cloud-kms/releases) 2. Download from the latest release: `main.js`, `manifest.json`, `styles.css` 3. Create folder `.obsidian/plugins/cloud-kms-encryption/` in your vault 4. Place downloaded files in that folder 5. Restart Obsidian β†’ Settings β†’ Community Plugins β†’ enable "Cloud KMS Encryption" ### From source ```bash git clone https://github.com/ViktorUJ/obsidian-cloud-kms.git cd obsidian-cloud-kms npm install npm run build ``` Copy `main.js`, `manifest.json`, and `styles.css` to `.obsidian/plugins/cloud-kms-encryption/`. ### AWS Setup 1. Create a KMS key: ```bash aws kms create-key --key-spec SYMMETRIC_DEFAULT --key-usage ENCRYPT_DECRYPT --region eu-north-1 ``` 2. Copy the key ARN (format: `arn:aws:kms:{region}:{account}:key/{key-id}`) 3. In Obsidian: Settings β†’ Cloud KMS Encryption β†’ paste the ARN 4. Verify credentials are available: ```bash aws sts get-caller-identity ``` > **Note**: region is extracted from the ARN automatically β€” no need to configure `AWS_REGION`. ## Settings | Parameter | Description | Default | |-----------|-------------|---------| | AWS KMS Key ARN | Key ARN for encryption | β€” | | Auto-decrypt blocks | Automatic decryption on read | βœ… | ## Security Detailed threat model, cryptographic design, and limitations are described in [SECURITY.md](SECURITY.md). Key points: - Decrypted data is **never written to disk** β€” adapter patch encrypts before write - Binary files are decrypted to Blob URL (RAM), not to disk - DEK is zeroed immediately after use - Each block/file uses a unique DEK + nonce - AES-256-GCM with 96-bit nonce and 128-bit auth tag - Encryption context bound to vault name + file path + format version - All KMS calls are logged in AWS CloudTrail - LRU cache of 20 decrypted binary files (old ones evicted from memory) - No telemetry, no external calls except to AWS KMS - Credentials are not stored by the plugin β€” standard AWS credential chain is used ## Verifying Release Integrity Every release is cryptographically signed. You can verify that downloaded artifacts are authentic and untampered: ### Verify SLSA Provenance ```bash # Install GitHub CLI if not already # Then verify the artifact was built in GitHub Actions: gh attestation verify main.js --repo ViktorUJ/obsidian-cloud-kms ``` ### Verify Cosign Signature ```bash # Install cosign: https://docs.sigstore.dev/cosign/system_config/installation/ # Download main.js and main.js.bundle from the release, then: cosign verify-blob main.js \ --bundle main.js.bundle \ --certificate-identity-regexp "github.com/ViktorUJ/obsidian-cloud-kms" \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" ``` If verification succeeds β€” the file was built in this repository's GitHub Actions, not modified after build. ### Verify SBOM Each release includes `sbom.spdx.json` β€” a complete list of all bundled dependencies with versions and licenses. Use it to: - Scan for known vulnerabilities: `grype sbom.spdx.json` - Check license compliance: `trivy sbom sbom.spdx.json` ## On-Disk Format ### Markdown (secret blocks) On disk, secret blocks are stored as: ````` ````ocke-v1 ```` ````` ### Binary files The file is entirely replaced with OCKE binary format: ``` [Magic: "OCKE" 4B][Version: uint16 BE][ProviderIdLen: 1B][ProviderId] [CmkIdLen: uint16 BE][CmkId][WrappedDekLen: uint16 BE][WrappedDek] [Nonce: 12B][AuthTag: 16B][CiphertextLen: uint32 BE][Ciphertext] ``` ## Key Access Management ### Multi-Key Architecture In organizations, different teams need access to different secrets. This plugin supports multiple KMS keys, allowing fine-grained access control: **Use case:** A company vault shared across teams: - **Finance team** β€” access to budget data, salaries, contracts - **R&D team** β€” access to patents, research, technical secrets - **CTO** β€” access to everything (all keys) Each secret block is encrypted with a specific key. IAM policies on the AWS side control who can decrypt what. A developer from R&D physically cannot decrypt finance data β€” even if they have access to the vault files. **Plugin settings:** ```json { "keys": [ { "alias": "finance", "arn": "arn:aws:kms:eu-north-1:790660747904:key/aaa-111" }, { "alias": "rnd", "arn": "arn:aws:kms:eu-north-1:790660747904:key/bbb-222" }, { "alias": "cto", "arn": "arn:aws:kms:eu-north-1:790660747904:key/ccc-333" } ], "defaultKeyAlias": "finance" } ``` **In notes β€” specify key alias in the marker:** ```markdown %%secret-start:finance%% Q3 Budget: $2.4M Salaries: ... %%secret-end%% %%secret-start:rnd%% Patent application for new algorithm: ... %%secret-end%% %%secret-start:cto%% Production root credentials: ... %%secret-end%% ``` **Behavior:** - When encrypting: if multiple keys are configured, a picker appears to choose which key to use - When decrypting: the key ARN is stored inside the encrypted data β€” the plugin automatically uses it - If the user has IAM access to the key β†’ block is decrypted - If not β†’ block remains encrypted (graceful degradation) - A single note can contain blocks encrypted with different keys **IAM setup on AWS side:** - Finance team β†’ IAM policy allows only `kms:Decrypt` on `key/aaa-111` - R&D team β†’ IAM policy allows only `kms:Decrypt` on `key/bbb-222` - CTO β†’ IAM policy allows `kms:Decrypt` on all three keys ### User from the same AWS account Add an IAM policy to the user/role: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey" ], "Resource": "arn:aws:kms:eu-north-1:790660747904:key/YOUR-KEY-ID" } ] } ``` ```bash aws iam put-user-policy \ --user-name colleague \ --policy-name kms-vault-access \ --policy-document file://policy.json ``` For read-only access (decryption only) β€” remove `kms:GenerateDataKey`. ### User from the same AWS Organization (different account) **Step 1.** Update the Key Policy on the key owner's side β€” allow access from another account: ```json { "Sid": "AllowCrossAccountDecrypt", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:root" }, "Action": [ "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey" ], "Resource": "*" } ``` ```bash # Get current key policy aws kms get-key-policy --key-id YOUR-KEY-ID --policy-name default --output text > key-policy.json # Add the Statement above to key-policy.json, then: aws kms put-key-policy --key-id YOUR-KEY-ID --policy-name default --policy file://key-policy.json ``` **Step 2.** On the other account's side (111122223333) β€” add IAM policy to the user: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey" ], "Resource": "arn:aws:kms:eu-north-1:790660747904:key/YOUR-KEY-ID" } ] } ``` > Both conditions are required: Key Policy allows the account, IAM Policy allows the user. ### User from an external organization (external AWS account) Similar to cross-account, but with additional restrictions via `Condition`: **Step 1.** Key Policy β€” allow a specific user/role (not the entire account): ```json { "Sid": "AllowExternalPartnerDecrypt", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::444455556666:user/partner-user" }, "Action": [ "kms:Decrypt", "kms:DescribeKey" ], "Resource": "*", "Condition": { "StringEquals": { "kms:EncryptionContext:vaultName": "shared-vault" } } } ``` > **Recommendations for external partners:** > - Specify a concrete Principal (user/role ARN), not account `root` > - Grant only `kms:Decrypt` (without `GenerateDataKey`) β€” read-only > - Use `Condition` with `kms:EncryptionContext` to restrict access to a specific vault > - Enable CloudTrail for auditing all key access **Step 2.** Partner adds IAM policy on their side (same as cross-account above). **Step 3.** Partner configures the plugin with the same key ARN and gains decryption access. ### Verifying access ```bash # As the user who was granted access: aws kms describe-key --key-id arn:aws:kms:eu-north-1:790660747904:key/YOUR-KEY-ID # If it returns key metadata β€” access is granted # If AccessDeniedException β€” check Key Policy + IAM Policy ``` ## Comparison with Alternatives | Feature | Cloud KMS Encryption | SOPS | git-crypt | Meld Encrypt | HashiCorp Vault | |---------|---------------------|------|-----------|--------------|-----------------| | Encryption at rest | βœ… | βœ… | βœ… | βœ… | βœ… | | No passwords | βœ… (IAM) | βœ… (IAM/PGP) | βœ… (GPG) | ❌ (password) | βœ… (tokens) | | Per-block granularity | βœ… | βœ… | ❌ (whole file) | βœ… | N/A | | Multi-key / multi-team | βœ… | βœ… | βœ… | ❌ | βœ… | | Git-safe (ciphertext in repo) | βœ… | βœ… | βœ… | βœ… | N/A | | Transparent edit (no manual decrypt) | βœ… | ❌ (CLI) | βœ… | ❌ (modal) | N/A | | Binary file encryption | βœ… | ❌ | βœ… | ❌ | N/A | | Obsidian integration | βœ… native | ❌ | ❌ | βœ… native | ❌ | | Audit trail (CloudTrail) | βœ… | βœ… | ❌ | ❌ | βœ… | | External audit / certification | ❌ | ❌ | ❌ | ❌ | βœ… | | HSM-grade security | ❌ | ❌ | ❌ | ❌ | βœ… | **Best fit:** - **Cloud KMS Encryption** β€” team knowledge bases with IAM access control, DevOps secrets in Obsidian - **SOPS** β€” CI/CD secrets in YAML/JSON files, GitOps workflows - **git-crypt** β€” whole-file encryption in Git repos, developer workflows - **Meld Encrypt** β€” personal password-protected notes, single-user - **HashiCorp Vault** β€” production infrastructure secrets, compliance requirements ## Git Workflow This plugin is designed to work with Git-stored vaults. On disk, only ciphertext exists β€” safe to commit and push. ### Initial setup ```bash # Clone the vault git clone git@github.com:your-org/team-vault.git cd team-vault # Open in Obsidian, configure the plugin with your KMS key ARN # Ensure AWS credentials are available: aws sts get-caller-identity ``` ### Daily workflow ```bash # Pull latest changes (encrypted on disk) cd /path/to/vault git pull # Open Obsidian β€” plugin decrypts blocks transparently # Edit notes as usual β€” secret blocks show decrypted content # Close Obsidian or just switch to terminal # Commit and push (only ciphertext goes to Git) git add -A git status # verify: no plaintext in diff git commit -m "update finance Q3 notes" git push ``` ### Verifying no plaintext leaks to Git ```bash # Check what's actually in the file on disk: cat notes/budget.md # You should see: ocke-v1 block with base64 β€” NOT plaintext # Check diff before committing: git diff --cached # Encrypted blocks show as base64 changes, not readable text # For binary files: file attachments/report.pdf # Should show: "data" (not "PDF document") β€” it's encrypted bytes ``` ### Team onboarding ```bash # New team member: # 1. Clone the vault git clone git@github.com:your-org/team-vault.git # 2. Configure AWS credentials aws configure # or: aws sso login --profile team # 3. Install plugin in Obsidian, set the same KMS key ARN # 4. IAM admin grants kms:Decrypt permission on the relevant key(s) # 5. Open vault in Obsidian β€” blocks they have access to are decrypted # Blocks they DON'T have access to remain as encrypted base64 ``` ### Conflict resolution ```bash # If Git shows merge conflict in an encrypted block: # DON'T try to merge the base64 manually β€” it's binary data # Option 1: Accept theirs or ours git checkout --theirs notes/budget.md # or git checkout --ours notes/budget.md # Option 2: Re-encrypt after resolving in Obsidian # 1. Accept one version # 2. Open in Obsidian, edit the decrypted content # 3. Save β€” plugin re-encrypts with new DEK # 4. Commit ``` ### .gitignore recommendations ```gitignore # Obsidian workspace (contains open file state, not secrets) .obsidian/workspace.json .obsidian/workspace-mobile.json # Plugin data (contains your KMS ARN β€” not secret, but personal) .obsidian/plugins/obsidian-cloud-kms-encryption/data.json # Never ignore these (they ARE the encrypted vault): # !*.md # !attachments/ ``` ### Pre-commit hook: plaintext leak protection If the plugin was disabled, credentials expired, or a file was edited outside Obsidian β€” `%%secret-start%%` markers might end up on disk unencrypted. A pre-commit hook prevents accidentally committing plaintext to Git: ```bash # Install the hook cp tools/pre-commit-hook.sh .git/hooks/pre-commit chmod +x .git/hooks/pre-commit ``` What it does: - On every `git commit`, checks all staged `.md` files - If any file contains `%%secret-start%%` β€” **blocks the commit** - Shows which file has unencrypted content and how to fix it Example output when plaintext is detected: ``` ERROR: Plaintext secret block found in staged file: notes/budget.md The file contains %%secret-start%% markers which means the encryption plugin did not encrypt before save. Fix: Open the file in Obsidian with the plugin enabled, save it, then stage again. Commit blocked: plaintext secrets detected. ``` > This is a safety net β€” if everything works correctly, `%%secret-start%%` should never appear on disk (the adapter patch encrypts it to `ocke-v1` block before write). The hook catches edge cases. ## CLI Decryption (without Obsidian) For disaster recovery, CI/CD pipelines, or backup verification β€” you can decrypt files without Obsidian using CLI tools. > **Important:** Encryption context (`vault-name` and `file-path`) must match what was used during encryption. The vault name is the folder name of your Obsidian vault. The file path is vault-relative (e.g., `folder/note.md`). ### Option 1: Bash + AWS CLI + Python Zero Node.js dependencies. Requires: `aws` CLI, `python3`, `pip install cryptography`. ```bash # Decrypt a binary file (PDF, image) ./tools/ocke-decrypt.sh report.pdf --vault-name my-vault --file-path report.pdf -o decrypted-report.pdf # Decrypt markdown blocks (prints to stdout) ./tools/ocke-decrypt.sh notes/secret.md --vault-name my-vault --file-path notes/secret.md # Using environment variables OCKE_VAULT_NAME="my-vault" OCKE_FILE_PATH="notes/secret.md" ./tools/ocke-decrypt.sh notes/secret.md -o decrypted.md ``` ### Option 2: Node.js CLI Requires: Node.js >= 18, `@aws-sdk/client-kms`. ```bash # Install dependencies (one time) npm install @aws-sdk/client-kms @aws-sdk/credential-provider-ini # Decrypt a binary file node tools/ocke-decrypt.mjs report.pdf --vault-name my-vault --file-path report.pdf -o decrypted-report.pdf # Decrypt markdown blocks node tools/ocke-decrypt.mjs notes/secret.md --vault-name my-vault --file-path notes/secret.md # Using environment variables OCKE_VAULT_NAME="my-vault" OCKE_FILE_PATH="notes/secret.md" node tools/ocke-decrypt.mjs notes/secret.md -o decrypted.md ``` ### Parameters | Parameter | Description | Required | |-----------|-------------|----------| | `` | Path to the encrypted file | Yes | | `-o ` | Write output to file (default: stdout) | No | | `--vault-name` | Obsidian vault name (folder name) | Yes | | `--file-path` | Vault-relative file path used during encryption | Yes | Or via environment variables: `OCKE_VAULT_NAME`, `OCKE_FILE_PATH`. > **How to find the correct values:** > - `vault-name` β€” the folder name of your vault (e.g., if vault is at `/home/user/my-vault/`, the name is `my-vault`) > - `file-path` β€” the path relative to vault root (e.g., `notes/secret.md`, `attachments/report.pdf`) ### When to use CLI - **Disaster recovery** β€” Obsidian is unavailable, need to access encrypted data - **CI/CD pipelines** β€” decrypt secrets during deployment without GUI - **Backup verification** β€” confirm encrypted backups are valid - **Migration** β€” bulk decrypt when moving away from the plugin ### Key Rotation / Migration (ocke-rekey) Re-encrypt an entire vault with a new KMS key β€” for migrating to a new AWS account, rotating keys, or switching regions. Only the wrapped DEK is re-encrypted (fast), ciphertext is unchanged. ```bash # Install dependencies npm install @aws-sdk/client-kms @aws-sdk/credential-provider-ini # Dry run β€” see what would change node tools/ocke-rekey.mjs /path/to/vault \ --new-key arn:aws:kms:eu-west-1:NEW_ACCOUNT:key/new-key-id \ --vault-name my-vault \ --dry-run # Execute migration node tools/ocke-rekey.mjs /path/to/vault \ --new-key arn:aws:kms:eu-west-1:NEW_ACCOUNT:key/new-key-id \ --vault-name my-vault # Migrate only blocks encrypted with a specific old key node tools/ocke-rekey.mjs /path/to/vault \ --new-key arn:aws:kms:eu-west-1:NEW_ACCOUNT:key/new-key-id \ --old-key arn:aws:kms:eu-north-1:OLD_ACCOUNT:key/old-key-id \ --vault-name my-vault ``` **Requirements:** - AWS credentials with `kms:Decrypt` on the old key AND `kms:Encrypt` on the new key - Both keys must be accessible simultaneously during migration - After migration, update the plugin settings with the new key ARN > **Note:** On Windows, use Git Bash or WSL for correct Unicode file path handling. > Both tools use the same AWS credentials as the plugin (`~/.aws/credentials`). > The KMS key ARN is stored inside the encrypted data β€” no key configuration needed. ## KMS Status Indicator The plugin displays a connection status indicator in Obsidian's bottom status bar: | Indicator | Meaning | |-----------|---------| | πŸ”“ KMS | Connection OK β€” encryption/decryption available | | πŸ”’ KMS ⚠️ | KMS unavailable β€” secret blocks will NOT be encrypted on save! | | ⏳ KMS | Checking connection... | **Behavior:** - Checks KMS accessibility on plugin load - Re-checks every 5 minutes in background - Click the indicator to manually re-check - Hover for detailed tooltip **Why this matters:** If KMS is unavailable (network issues, expired credentials, AWS outage), the plugin cannot encrypt `%%secret-start%%` blocks on save. The file will be saved with plaintext markers. The status indicator gives immediate visibility into this risk β€” if you see πŸ”’ ⚠️, do not save files with secret blocks until connectivity is restored. ## Known Limitations ### Vault Adapter Monkey-Patching This plugin intercepts `vault.adapter.read()` and `vault.adapter.write()` to provide transparent encryption. This is the same approach used by [gpgCrypt](https://github.com/tejado/obsidian-gpgCrypt) and is the only way to guarantee zero-plaintext-on-disk without Obsidian providing an official encryption API. **Potential conflicts with other plugins:** - If another plugin also patches `adapter.read()` or `adapter.write()`, the two patches may conflict - The plugin chains correctly (calls the original method after processing), but order of initialization matters - If you experience issues, try disabling other plugins that modify file I/O behavior **Mitigations:** - The patch only processes `.md` files for text encryption (binary files checked by magic bytes) - Files without `%%secret-start%%` markers or OCKE magic bytes pass through unchanged (zero overhead) - On plugin unload, original adapter methods are fully restored - The patch is transparent β€” other plugins reading/writing non-encrypted files are unaffected **If Obsidian updates break the plugin:** - Your data is safe β€” files remain encrypted on disk in documented OCKE format - Use the [CLI tools](#cli-decryption-without-obsidian) to decrypt without Obsidian - The plugin will be updated to match new Obsidian internals ## Development ```bash npm test # Run tests npm run build # Production build npm run dev # Dev build (watch) make ci # Full CI pipeline ``` ## License [MIT](LICENSE) Β© Viktar Mikalayeu