cloudark
v0.8.0
Published
Encrypted key-value storage with pluggable adapters
Maintainers
Readme
cloudark
Encrypted cloud storage for TypeScript applications. The backend is blind — it stores only ciphertext. Encryption and decryption happen on the client, using the Web Crypto API.
npm install cloudarkHow it works
your data → AES-256-GCM encrypt → cloud backend (sees nothing)
↑
key derived from
your password (PBKDF2)cloudark wraps any cloud storage backend with a transparent encryption layer. Your backend — S3-compatible storage, IndexedDB, or anything else — stores base64-encoded ciphertext. Without the password, the data is unreadable.
Quick start
The example below uses MemoryAdapter, which is included in the package and requires no
peer dependencies. It is ideal for local development, tests, and getting familiar with the API.
import { Cloudark } from 'cloudark'
import { MemoryAdapter } from 'cloudark/adapters/memory'
const store = new Cloudark({
adapter: new MemoryAdapter(),
password: 'a-strong-password-known-only-to-the-user',
compress: true, // Optional: gzip data before encryption
})
// Write
await store.set('budget/2024', { income: 3200, expenses: 2100 })
// Read
const budget = await store.get<{ income: number; expenses: number }>('budget/2024')
console.log(budget) // { income: 3200, expenses: 2100 }
// List keys by prefix
const keys = await store.list('budget/')
console.log(keys) // ['budget/2024']
// Delete
await store.delete('budget/2024')IndexedDB
Perfect for browser-based offline-first applications. It requires no configuration or peer dependencies.
import { Cloudark } from 'cloudark'
import { IndexedDBAdapter } from 'cloudark/adapters/indexeddb'
const store = new Cloudark({
adapter: new IndexedDBAdapter('my-app-db', 'encrypted-records'),
password: 'user-password',
})
await store.set('key', { some: 'data' })S3-Compatible Storage (AWS, Cloudflare R2, MinIO, B2)
Install the peer dependency first:
npm install @aws-sdk/client-s3Then swap the adapter:
import { Cloudark } from 'cloudark'
import { S3Adapter } from 'cloudark/adapters/s3'
const store = new Cloudark({
adapter: new S3Adapter({
region: 'auto',
endpoint: process.env.S3_ENDPOINT,
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
bucket: process.env.S3_BUCKET,
}),
password: 'shared-secret-known-only-to-users',
})
await store.set('budget/2024', { income: 3200, expenses: 2100 })See S3 setup for how to configure the adapter, or read the full S3 usage guide for a step-by-step walkthrough.
Core concepts
Password-derived keys. cloudark derives an AES-256 key from your password using PBKDF2 (310,000 iterations, SHA-256). The derived key is held in memory only — it never leaves the device and is never stored anywhere.
Per-payload IV and salt. Every set operation generates a fresh random IV (12 bytes) and salt (16 bytes). The same data encrypted twice produces different ciphertext. IV and salt are stored alongside the ciphertext in the payload — there is no state to synchronise.
Authenticated encryption. AES-GCM provides both confidentiality and integrity. Tampered ciphertext will throw a DecryptionError on read — it will never silently return wrong data.
Backend-agnostic. The StorageAdapter interface is four methods: get, set, delete, list. Swap backends by changing one import.
Adapters
| Adapter | Import | Peer dependency | Status |
|---|---|---|---|
| Memory (dev / testing) | cloudark/adapters/memory | none | ✅ v0.1 |
| S3-Compatible | cloudark/adapters/s3 | @aws-sdk/client-s3 ^3 | ✅ v0.6 |
| IndexedDB | cloudark/adapters/indexeddb | none | ✅ v0.5 |
Writing your own adapter
import type { StorageAdapter } from 'cloudark'
class MyAdapter implements StorageAdapter {
async get(key: string): Promise<string | null> { /* ... */ }
async set(key: string, value: string): Promise<void> { /* ... */ }
async delete(key: string): Promise<void> { /* ... */ }
async list(prefix?: string): Promise<string[]> { /* ... */ }
}The adapter deals only with raw strings. cloudark handles all serialisation and encryption before calling the adapter.
S3 setup
1. Credentials and Bucket
You will need an S3-compatible bucket and an API key (Access Key ID and Secret Access Key) with read/write permissions for that bucket.
For Cloudflare R2:
- Endpoint:
https://<accountid>.r2.cloudflarestorage.com - Region:
auto
For AWS S3:
- Endpoint: (Optional)
https://s3.<region>.amazonaws.com - Region: e.g.,
us-east-1
Store these values in environment variables:
S3_ENDPOINT=your-endpoint-url
S3_ACCESS_KEY_ID=your-access-key-id
S3_SECRET_ACCESS_KEY=your-secret-access-key
S3_BUCKET=your-bucket-name
S3_REGION=auto2. Configure CORS (browser applications only)
If your application runs in a browser, the bucket must allow cross-origin requests from your domain. Without CORS, the browser will block every request.
Example CORS policy (JSON):
[
{
"AllowedOrigins": ["https://your-app-domain.com"],
"AllowedMethods": ["GET", "PUT", "DELETE", "HEAD"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3600
}
]Replace https://your-app-domain.com with your actual origin. For local development you can
add "http://localhost:3000" (or whichever port you use) as an additional entry.
Note: CORS is only needed in browser environments. Node.js and server-side runtimes make requests directly and are not subject to the browser same-origin policy.
Shared repositories
Multiple users can share access to the same encrypted data. Cloudark supports two modes:
- Shared password: All members know the same password (v0.2).
- Multi-user repositories: Each member has their own keypair, allowing for easy member addition and removal with key rotation (v0.3).
For more details on how this is implemented, see the Storage and Multi-user Structure guide.
Security model
- Data is encrypted before it touches the network. The cloud backend never sees plaintext.
- The password is never stored or transmitted. Losing the password means losing access to the data — there is no recovery mechanism.
- cloudark does not provide authentication or access control. Use your backend's auth layer (e.g. Cloudflare R2 bucket policies) to control who can read and write the ciphertext.
- cloudark does not protect against a malicious backend that silently drops writes or replays old ciphertext. It guarantees confidentiality and integrity of individual payloads, not consistency of the store.
For the full threat model and cryptographic choices, see specs/tech-stack.md.
Browser and runtime support
| Runtime | Support |
|---|---|
| Modern browsers | ✅ Native Web Crypto API |
| Node.js 18+ | ✅ Native Web Crypto API |
| React Native | ⚠️ Requires react-native-quick-crypto polyfill |
Development
git clone https://github.com/urizev/cloudark
cd cloudark
npm install
npm test # run tests
npm run build # produce ESM output in dist/
npm run typecheck # type-check
npm run lint # lintRoadmap
See specs/roadmap.md.
Contributing
Issues and pull requests are welcome. Please open an issue before starting significant work so we can discuss the approach.
License
MIT
