Obsidian Relay transfers encrypted files through Appwrite with scoped download access and browser-side decryption.
File transfer becomes awkward when the data plane and application API share the same request-size limits. Obsidian uploads ciphertext directly to storage and uses a small function request for the encrypted envelope, metadata and access policy. This separates large-file transfer from authorization logic.
Preview · Quick start · Architecture · Development · Contributing

The browser input panel prepares a named text item for the relay. Transfers require a configured Appwrite backend; no transfer is demonstrated here.
| Workflow | Result |
|---|---|
| Send a file | Encrypt in the browser and upload ciphertext directly to storage. |
| Control retrieval | Validate item scope, access codes and failed-attempt lockouts. |
| Recover a file | Authorize a short-lived download and decrypt in the receiving browser. |
Provision an Appwrite project with Functions, Storage and Databases. Use the Node.js 22 runtime for the function. Generate a private RSA key locally; do not commit the generated key or deploy it as a public site asset.
From a fresh checkout:
git clone https://github.com/ryash14/Obsidian.git
cd Obsidian
node tools/generate-key.mjs
# Configure Appwrite project/endpoint and site functionUrl.
# Install and authenticate the Appwrite CLI first.
appwrite login
appwrite push functions
appwrite push sites
Deploy the function with Node.js 22 and entrypoint src/main.js. Set VAULT_RSA_PRIVATE_KEY from the generated key, grant database/storage/token scopes, and add the site hostname as an Appwrite Web platform. Keep private keys private. Set sites/obsidian-relay/config.js to the function URL. Restrict ALLOWED_ORIGINS for deployment. The server can unwrap content keys; this is not encryption against the server operator. Rotating the RSA key makes existing items unreadable.
| Item | Required configuration |
|---|---|
| Function | Node.js 22, entrypoint src/main.js, execution access appropriate to the relay |
VAULT_RSA_PRIVATE_KEY |
Private key generated by tools/generate-key.mjs |
| Function scopes | Database/table/schema operations, storage operations and file tokens used by the relay |
| Site API URL | Set window.RELAY_CONFIG.functionUrl in sites/obsidian-relay/config.js |
| Web platform | Register the deployed site’s hostname in the Appwrite project |
ALLOWED_ORIGINS |
Restrict API access to the intended site origins |
RELAY_MAX_BYTES |
Keep the file-size limit within the storage bucket and plan limits |
ACCESS_MAX_FAILURES, ACCESS_LOCK_SECONDS |
Failed-code lockout policy |
After deployment, request /config on the function domain to check configuration and schema provisioning. Use a small non-sensitive file to verify upload, authorized download and incorrect-code handling before relying on the relay.
sequenceDiagram
participant B as Browser
participant S as Appwrite Storage
participant F as Relay Function
participant D as Database
B->>B: Encrypt file and wrap content key
B->>S: Upload ciphertext directly
B->>F: Submit envelope and metadata
F->>D: Persist item and keyed access policy
B->>F: Request item with access scope
F->>D: Validate code and lockout state
F-->>B: Short-lived token and content key
B->>S: Download ciphertext
B->>B: Decrypt and save
Appwrite Storage holds ciphertext; the database holds item metadata, keyed access-code hashes and failed-attempt state. The function provisions its schema and bucket on first use when its scopes permit it.
This protects stored file contents, but the server can unwrap the content key. It is not end-to-end encryption against the server operator. Filenames, types and sizes remain available to the application. Rotating the RSA key invalidates existing wrapped keys and access-code hashes.
Stack: JavaScript · Node.js 22 · Web Crypto · Appwrite Functions · Appwrite Storage and Databases.
Run the repository checks after completing setup:
node --check functions/relay-api/src/main.js
node --check functions/relay-api/src/envelope.js
Use the offline checks during development. Run provider-backed integration checks separately with configured credentials and representative inputs.
Report reproducible bugs or propose changes through GitHub Issues. Include the relevant environment and a small example rather than private datasets or credentials. See contributing, security reporting and the code of conduct.
No repository-wide license is currently provided. Third-party components, models and datasets retain their own terms.