diff --git a/README.md b/README.md index 4868de45..fc3c8698 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,205 @@
- - CipherStash Logo + + CipherStash

CipherStash Stack for TypeScript

-Built by CipherStash -License -Docs -Join the community on Discord +

Field-level encryption for TypeScript apps — search encrypted data without decrypting it, with + zero-knowledge key management. Every value gets its own key, and your keys never leave your AWS KMS.

+ npm version + npm downloads + GitHub stars + Docs + Discord + License + +
⭐ Star this repo if encryption you can actually query is your thing!
-## What is the stack? +
+ +> **CipherStash never sees your plaintext.** Data is encrypted in your app with a unique key per value via +> [ZeroKMS][zerokms], rooted in your own [AWS KMS][aws-kms] — so a database breach leaks only ciphertext. +> [See the security architecture →][security-architecture] + +## Quick start -- [Encryption](https://cipherstash.com/docs/stack/cipherstash/encryption): Field-level encryption for TypeScript apps with searchable encrypted queries, zero-knowledge key management, and first-class ORM support. +You'll need a free CipherStash account to provision keys and a workspace — it takes about a minute. -## Quick look at the stack in action +**1. Create a free account** → **[cipherstash.com/signup][signup]** -**Encryption** +**2. Initialize your project** — the wizard authenticates you, builds an encryption schema, and wires up your database: + +```bash +npx stash init +``` + +**3. Encrypt, search, and decrypt:** ```typescript -import { Encryption, encryptedTable, encryptedColumn } from "@cipherstash/stack"; +import { Encryption } from "@cipherstash/stack"; +import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"; -// 1. Define your schema +// Define which columns are encrypted — and how you want to query them const users = encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch(), }); -// 2. Initialize the client const client = await Encryption({ schemas: [users] }); -// 3. Encrypt -const encryptResult = await client.encrypt("secret@example.com", { - column: users.email, - table: users, +// Encrypt → store the ciphertext in your own database +const enc = await client.encrypt("alice@example.com", { table: users, column: users.email }); + +// Search WITHOUT decrypting — the part nobody else does +const term = await client.encryptQuery("alice@example.com", { + table: users, column: users.email, queryType: "equality", }); -if (encryptResult.failure) { - // Handle errors your way -} - -// 4. Decrypt -const decryptResult = await client.decrypt(encryptResult.data); -if (decryptResult.failure) { - // Handle errors your way -} -// decryptResult.data => "secret@example.com" +// → drop term.data straight into your WHERE clause + +// Decrypt when you need the plaintext back +const dec = await client.decrypt(enc.data); ``` -## Install +Prefer the long version? Follow the **[5-minute quickstart →][quickstart]** -```bash -npm install @cipherstash/stack -# or -yarn add @cipherstash/stack -# or -pnpm add @cipherstash/stack -# or -bun add @cipherstash/stack +## What's in the Stack + +Three building blocks for protecting sensitive data in TypeScript apps — use one, or all three together. + +### 🔐 Searchable encryption + +Encrypt individual fields and still run real queries against them — exact match, full-text search, +range/sorting, and encrypted JSONB — all on ciphertext, in PostgreSQL. + +```typescript +const users = encryptedTable("users", { + email: encryptedColumn("email").equality().freeTextSearch().orderAndRange(), + metadata: encryptedColumn("metadata").searchableJson(), // encrypted JSONB queries +}); ``` -> [!IMPORTANT] -> **You need to opt out of bundling when using `@cipherstash/stack`.** -> It uses Node.js specific features and requires the native Node.js `require`. -> Read more about bundling in the [documentation](https://cipherstash.com/docs/stack/deploy/bundling). +→ [Searchable encryption][searchable-encryption] · [Schema][schema] · [Encrypt & decrypt][encrypt-decrypt] · [Bulk & model operations][model-ops] + +### 🔗 ORM & database integrations + +Drop encryption into the stack you already use. Type-safe operators let you query encrypted columns +exactly like normal ones. + +| Integration | Status | Guide | +|---|---|---| +| PostgreSQL (raw SQL) | ✅ | [Docs][encryption] | +| Supabase | ✅ | [Docs][supabase] | +| Drizzle ORM | ✅ | [Docs][drizzle] | +| Prisma (Prisma Next) | ✅ | [Docs][prisma-next] | +| DynamoDB | ✅ | [Docs][dynamodb] | + +```typescript +// Drizzle: query encrypted columns with auto-encrypting operators +const results = await db.select().from(usersTable) + .where(await ops.eq(usersTable.email, "alice@example.com")); +``` -## Features +### 👤 Identity-aware encryption -- **[Searchable encryption](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption)**: query encrypted data with equality, free text search, range, and [JSONB queries](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption#jsonb-queries-with-searchablejson). -- **[Type-safe schema](https://cipherstash.com/docs/stack/cipherstash/encryption/schema)**: define encrypted tables and columns with `encryptedTable` / `encryptedColumn` -- **[Model & bulk operations](https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt#model-operations)**: encrypt and decrypt entire objects or batches with `encryptModel` / `bulkEncryptModels`. -- **[Identity-aware encryption](https://cipherstash.com/docs/stack/cipherstash/encryption/identity)**: bind encryption to user identity with lock contexts for policy-based access control. +Bind a record's encryption key to the end user's identity, so only *that* user can decrypt their data. +`OidcFederationStrategy` federates your identity provider's OIDC JWT into the client — every ZeroKMS +request then authenticates *as that user* — and `.withLockContext({ identityClaim })` binds the data key to +a claim. Works with any OIDC provider: Clerk, Supabase Auth, Auth0, Okta, and more. -## Integrations +```typescript +import { Encryption, OidcFederationStrategy } from "@cipherstash/stack"; + +// Authenticate every request as the signed-in user via their OIDC JWT +const client = await Encryption({ + schemas: [users], + config: { strategy: OidcFederationStrategy.create(workspaceCrn, () => getUserJwt()) }, +}); -- [Encryption + Drizzle](https://cipherstash.com/docs/stack/cipherstash/encryption/drizzle) -- [Encryption + Supabase](https://cipherstash.com/docs/stack/cipherstash/encryption/supabase) -- [Encryption + DynamoDB](https://cipherstash.com/docs/stack/cipherstash/encryption/dynamodb) +// Bind the data key to a claim — the same claim is required to decrypt +await client + .encrypt("alice@example.com", { table: users, column: users.email }) + .withLockContext({ identityClaim: ["sub"] }); +``` -## Use cases +→ [Identity-aware encryption][identity] -- **Trusted data access**: ensure only your end-users can access their sensitive data using identity-bound encryption -- **Reduce breach impact**: limit the blast radius of exploited vulnerabilities to only the data the affected user can decrypt +> The Stack also ships a `stash` CLI for auth, schema, and database setup. See the [SDK reference][reference]. -## Documentation +## How it works -- [Documentation](https://cipherstash.com/docs) -- [Quickstart](https://cipherstash.com/docs/stack/quickstart) -- [SDK and API reference](https://cipherstash.com/docs/stack/reference) +Encryption happens in your application. Ciphertext is stored as an [EQL][eql] JSON payload in your database; +plaintext and root keys never reach CipherStash. Per-value keys are issued in bulk by ZeroKMS (so millions +of unique keys stay fast), and every decryption is logged for compliance. -## Contributing +→ [Security architecture][security-architecture] · [ZeroKMS][zerokms] -Contributions are welcome and highly appreciated. However, before you jump right into it, we would like you to review our [Contribution Guidelines](CONTRIBUTE.md) to make sure you have a smooth experience contributing. +## Why CipherStash -## Security +- **Trusted data access** — only your end-users can access their sensitive data, enforced cryptographically. +- **Shrink the blast radius** — a breached vulnerability exposes only what one user can decrypt, not your whole table. +- **Meet compliance faster** — exceed the encryption requirements of SOC 2 and ISO 27001, with an audit trail of every decryption. -For our full security policy, supported versions, and contributor guidelines, see [SECURITY.md](./SECURITY.md). +## Install -## License +```bash +npm install @cipherstash/stack # or: yarn / pnpm / bun add @cipherstash/stack +``` -This project is [MIT licensed](./LICENSE.md). +> [!IMPORTANT] +> **Opt out of bundling `@cipherstash/stack`.** It uses native Node.js features (a Rust FFI module) and the +> native `require`. [Bundling guide →][bundling] + +**Requirements:** Node.js ≥ 18. + +## Migrating from Protect.js + +> [!NOTE] +> **`@cipherstash/protect` (Protect.js) is now legacy and in maintenance mode.** It still receives critical +> security fixes, but all new development has moved to `@cipherstash/stack`. New projects should use the +> Stack; existing Protect.js users can migrate with the mapping below. + +| `@cipherstash/protect` | `@cipherstash/stack` | +|---|---| +| `protect(config)` | `Encryption(config)` | +| `csTable` / `csColumn` | `encryptedTable` / `encryptedColumn` | +| `@cipherstash/protect/identify` | `@cipherstash/stack/identity` | + +Method signatures and the `Result` (`data` / `failure`) pattern are unchanged. [Full migration guide →][reference] + +## Documentation & community + +- 📚 [Documentation][docs] · [Quickstart][quickstart] · [SDK reference][reference] +- 🧩 [Example apps][examples] +- 💬 [Discord community][discord] + +## Contributing · Security · License + +Contributions are welcome — see [CONTRIBUTE.md][contribute]. For our security policy and responsible +disclosure, see [SECURITY.md][security-policy]. [MIT licensed][license]. + + +[signup]: https://cipherstash.com/signup?utm_source=github&utm_medium=stack_readme +[docs]: https://cipherstash.com/docs/stack?utm_source=github&utm_medium=stack_readme +[quickstart]: https://cipherstash.com/docs/stack/quickstart?utm_source=github&utm_medium=stack_readme +[reference]: https://cipherstash.com/docs/stack/reference?utm_source=github&utm_medium=stack_readme +[encryption]: https://cipherstash.com/docs/stack/cipherstash/encryption?utm_source=github&utm_medium=stack_readme +[searchable-encryption]: https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption?utm_source=github&utm_medium=stack_readme +[schema]: https://cipherstash.com/docs/stack/cipherstash/encryption/schema?utm_source=github&utm_medium=stack_readme +[encrypt-decrypt]: https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt?utm_source=github&utm_medium=stack_readme +[model-ops]: https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt?utm_source=github&utm_medium=stack_readme#model-operations +[supabase]: https://cipherstash.com/docs/stack/cipherstash/encryption/supabase?utm_source=github&utm_medium=stack_readme +[drizzle]: https://cipherstash.com/docs/stack/cipherstash/encryption/drizzle?utm_source=github&utm_medium=stack_readme +[prisma-next]: https://cipherstash.com/docs/stack/cipherstash/encryption/prisma-next?utm_source=github&utm_medium=stack_readme +[dynamodb]: https://cipherstash.com/docs/stack/cipherstash/encryption/dynamodb?utm_source=github&utm_medium=stack_readme +[identity]: https://cipherstash.com/docs/stack/cipherstash/encryption/identity?utm_source=github&utm_medium=stack_readme +[security-architecture]: https://cipherstash.com/docs/stack/reference/security-architecture?utm_source=github&utm_medium=stack_readme +[zerokms]: https://cipherstash.com/docs/stack/cipherstash/kms?utm_source=github&utm_medium=stack_readme +[bundling]: https://cipherstash.com/docs/stack/deploy/bundling?utm_source=github&utm_medium=stack_readme +[eql]: https://github.com/cipherstash/encrypt-query-language +[aws-kms]: https://docs.aws.amazon.com/kms/latest/developerguide/overview.html +[discord]: https://discord.gg/5qwXUFb6PB +[examples]: ./examples +[contribute]: ./CONTRIBUTE.md +[security-policy]: ./SECURITY.md +[license]: ./LICENSE.md diff --git a/docs/plans/readme-visual-assets.md b/docs/plans/readme-visual-assets.md new file mode 100644 index 00000000..d453dbdd --- /dev/null +++ b/docs/plans/readme-visual-assets.md @@ -0,0 +1,136 @@ +# README visual assets — spec + +Two visual assets for the refreshed root `README.md`. Both target the gaps competitor +READMEs leave open: + +1. **Architecture diagram** — security/infra READMEs (Infisical, Vault) bury their architecture + off-README. A clear "how it works" diagram is the single biggest trust signal we can add. +2. **Type-safety autocomplete GIF** — none of the TS-first leaders (Prisma, Drizzle, React Email) + *show* their type-safety story; they only describe it. An autocomplete GIF beats all of them. + +## Shared conventions + +- **Host in-repo** under `docs/images/` so assets are version-controlled. +- **Reference with absolute URLs** (`https://raw.githubusercontent.com/cipherstash/stack/main/docs/images/...`). + Relative paths render on GitHub but **break on the npm package page** — npm needs absolute URLs. +- **Light + dark variants** using GitHub's mode switch: + ```html + ... + ... + ``` +- **Brand**: use the CipherStash palette and logo; match the dark-theme look of cipherstash.com. +- **Accessibility**: every asset needs descriptive `alt` text (provided below). For the GIF, keep motion + calm and the loop short (respect users who dislike motion). + +--- + +## Asset 1 — Architecture diagram ("How it works") + +**Goal.** In one glance, prove the core trust claim: *plaintext and root keys never reach CipherStash; the +database only ever holds ciphertext; every decryption is audited.* + +**Placement.** Under the `## How it works` heading, above the "Security architecture" doc link. + +**Format.** SVG preferred (crisp, tiny, theme-able). Target ~1400px wide, responsive height. + +**Layout (left → right data flow):** + +``` +┌─────────────────────────┐ ciphertext ┌──────────────────────────┐ +│ YOUR APP (TypeScript) │ ── EQL JSON payload ──▶ │ YOUR DATABASE │ +│ @cipherstash/stack │ │ PostgreSQL / JSONB │ +│ • encrypt / decrypt │ ◀── encrypted rows ─── │ • stores ciphertext only│ +│ • search on ciphertext │ │ • searchable (EQL) │ +└───────────┬─────────────┘ └──────────────────────────┘ + │ per-value key requests (bulk) + ▼ +┌─────────────────────────┐ root key ┌──────────────────────────┐ +│ ZeroKMS │ ───────────▶ │ YOUR AWS KMS │ +│ • unique key per value │ │ • root key never leaves │ +│ • bulk key ops (fast) │ └──────────────────────────┘ +│ • decryption audit log │ +└─────────────────────────┘ +``` + +**Trust-boundary callouts to overlay (the persuasive part):** +- A dashed "trust boundary" line around *Your App + Your Database + Your AWS KMS* labelled + **"Plaintext and root keys never leave your boundary."** +- A badge on ZeroKMS: **"CipherStash never sees plaintext."** +- A small tag near the audit log: **"Every decryption logged → SOC 2 / ISO 27001 evidence."** + +**Alt text:** +> "CipherStash architecture: encryption and decryption happen in your TypeScript app; only ciphertext +> (EQL JSON) is stored in your PostgreSQL database. ZeroKMS issues a unique key per value, rooted in your +> own AWS KMS. Plaintext and root keys never reach CipherStash, and every decryption is logged for audit." + +**Tooling.** Figma, Excalidraw, or draw.io → export SVG (light + dark). Keep text as real text (not +outlines) where possible for crispness and accessibility. + +**Interim option (ship today, no designer needed).** GitHub renders Mermaid natively, so this can go in +immediately and be swapped for the designed SVG later: + +```mermaid +flowchart LR + App["Your App (TypeScript)
@cipherstash/stack
encrypt · decrypt · search"] + DB[("Your Database
PostgreSQL / JSONB
ciphertext only")] + ZKMS["ZeroKMS
unique key per value
bulk ops · audit log"] + KMS["Your AWS KMS
root key never leaves"] + + App -- "ciphertext (EQL JSON)" --> DB + App -- "per-value key requests" --> ZKMS + ZKMS -- "root key" --> KMS + + subgraph Boundary["Your trust boundary — plaintext & root keys never leave"] + App + DB + KMS + end +``` + +--- + +## Asset 2 — Type-safety / autocomplete GIF + +**Goal.** Show the DX payoff in motion: an encrypted field stays **fully typed and queryable** — encryption +adds security without taking away autocomplete, inference, or compile-time safety. + +**Placement.** Inside the `### 🔐 Searchable encryption` pillar, or a short "Developer experience" callout. + +**Storyboard (single seamless loop, ≤ 10s):** +1. Show a schema: `encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch() })`. +2. Type `await client.encryptModel(user, users)` and hover the result — tooltip shows the **schema-aware + return type**: `email → Encrypted`, `id → string`, `createdAt → Date` (only schema fields change type). +3. Start typing a query: `.where(await ops.eq(usersTable.email, "` — show autocomplete offering the typed + operator and the column. +4. Briefly trigger a **red squiggle** by accessing a field not in the schema (or wrong type) — proving + errors are caught at compile time. + +**Recording specs:** +- VS Code, clean theme (record a **dark** primary; a light alt is nice-to-have). +- Font size 16–18px; minimap off; hide activity/status bar clutter; zoom so code is legible on mobile. +- Crop tight to the editor region. Width 1280–1440px. +- Length 8–12s, seamless loop. **File budget < 5 MB** (ideally < 3 MB) so the README stays fast. + +**Formats:** +- Ship a **`.gif`** for universal rendering (works on npm and GitHub). +- Optionally also provide an `.mp4`/`.webm` and embed via `