# Introducing Protect.js

*Published on 2025-03-13T00:00:00.000Z*

*By Dan Draper — CEO and Founder*

Protecting data that’s sensitive (such as personal health or financial information) is crucial to building applications that users trust. But getting access control right is a tricky business. Complex requirements and a plethora of tools, as well as performance considerations, can kill dev team productivity.

## Content

{% callout type="important" title="Use Stack instead" %}
Stack supersedes Protect.js — same project, renamed and expanded. For new projects, read the current product announcement instead: [Introducing @cipherstash/stack](/blog/introducing-cipherstash-stack). This post stays up for reference.
{% /callout %}


This is why we created [Protect.js](https://github.com/cipherstash/protectjs).

Protect turns data access control on its head by protecting data directly.
Unlike complex, application-specific frameworks or hand-rolled tools, Protect.js makes building secure, data-driven applications simple so you can deliver services that users trust without slowing down delivery.

## We rolled the crypto so you don’t have to

_Security experts say you shouldn't roll your own crypto but it's ok, we've done the hard work for you._

Protect’s power comes from the strongest form of access control: encryption.
Encryption is rarely considered for the fine-grained protection of data because it's hard to implement, slow, and doesn’t play nicely with other tools in the stack like identity providers or the database. That is, until today.

Based on the CipherStash encryption SDK and using our revolutionary key management service, [ZeroKMS](https://cipherstash.com/stack/zerokms), Protect.js unlocks the power of encryption but without the hassle. Protect.js:

* Works with any Node.js framework or ORM (like Next.js + Drizzle)
* Is based on AES-256 encryption and uses formally-verified cryptography
* Uses ZeroKMS key management that’s up to 14x faster than AWS KMS
* Supports queries and sorting over encrypted data
* Is so easy to use you can get started in ~under 15-minutes~

## Encryption in use

You’ve probably heard of encryption *at rest* or *in transit* but these approaches are only part of the story when it comes to effective data protection.
At-rest and in-transit encryption leave critical gaps in your data’s defenses which can lead to vulnerabilities or accidental leaks and make it harder to reason about whether data is secure, especially when trying to convince customers or an auditor.

![Encryption Approaches](//images.ctfassets.net/y8kjcno8ru3x/2hdczI8XkCIkJ7X7B40Gvb/5b9d2e8587f2963fd75b13eb429a0e34/Encryption-approaches.png)

Protect.js uses encryption *in use* to protect data.
Sensitive data items, like individual email addresses remain protected right up until an authorized end-user needs to read them.
This limits the “surface area” of a potential attack and reduces risk as well as making it easier to demonstrate that data is secure.

To protect a sensitive database field with Protect.js, you do so via a **protected schema** definition.
To specify that the `email` column of the `users` table should be encrypted:

```typescript
// src/protect/schema.ts
import { csTable, csColumn } from '@cipherstash/protect'

export const users = csTable('users', {
  email: csColumn('email'),
})
```

Then, to encrypt a value, pass any schemas you've defined to `protect` and call `encrypt`:

```typescript
// src/protect/client.ts
import { protect } from '@cipherstash/protect'
import { users } from './schema'

// Do this once when your app starts
export const protectClient = await protect(users)

// Encrypt an email address for a configured column and table
const encryptResult = await protectClient.encrypt('secret@squirrel.example', {
  column: users.email,
  table: users,
})
```

Check out the [Protect.js getting started guide](https://github.com/cipherstash/protectjs) if you want to jump right into the code.
Or read on to learn about some of Protect.js's other capabilities!

### Key management

_Encryption in use_ is similar to the idea of "field" or "row-level" encryption.
One of the main differences is in how keys are managed: Encryption in use ensures each value is encrypted by a unique data key.

I spoke about [why this is a good thing](https://youtu.be/zjI1qLakD-E?si=sEliLRNK6ZwiuCm_) at BSides SF in 2024.

![You can't read the data](/images/blog/introducing-protectjs-g2x4e.jpg)

Even the best encryption tech is useless without good key management.
When using traditional field-level encryption in a web-based application you have 2 options available:
- A local key (*fast but less secure*)
- A cloud-based key-management service (*secure but slow*)

A local key is a secret encryption key (usually stored as an environment variable).
This approach uses the _same key_ to encrypt all data in the application and comes with some nasty security problems.
For starters, if an adversary obtains the key, they can use it to access all of the data in your system.
If you need to update ("rotate") the key you'll need to re-encrypt _everything_.
More subtle problems can arise too.
AES keys are vulnerable to _key wear out_ which means that if a single key is used to encrypt more than about 4GB, previously encrypted values can be used to _recover the key itself_.

Key Management Services (KMS) like [AWS KMS](https://aws.amazon.com/kms/) or [Azure KeyVault](https://learn.microsoft.com/en-us/azure/key-vault/general/overview) mitigate these problems with a technique called _envelope encryption_.
Instead of storing an encryption key in an application environment variable, a _root_key_ is stored within the cloud provider's infrastructure inside a device called a Hardware Security Module (HSM).

![Envelope Encryption](//images.ctfassets.net/y8kjcno8ru3x/16dxopeSFpGlvrM1OQGoeO/4a839c6a4cd5ae25eb472cd5850a0c2d/Frame_1__9_.png)

To perform an encryption operation, clients must make a request to the key service which returns a randomly generated _data key_ along with a copy that has itself been encrypted using the _root key_ (called a _wrapped data key_).
The _data key_ is used to encrypt the target value (such as an email address) and discarded with the result stored in the database with the _wrapped data key_.

Envelope encryption is a big security improvement over a local key but has a major drawback.
Because each data key requires a separate HTTP request, performance can be **abysmal**.
To get around this, cloud providers added support for _data key caching_.

However, when caching data-keys many of the same problems as using a local-key can arise.
Use a single data key to encrypt too much data and you can run into wear-out issues.
A compromise of the cache used to store data-keys could lead to a pretty significant data breach.

Data key caching also means that there is no-longer a _1-to-1 mapping_ between a data-key and the specific value it was used to encrypt which means we miss out on powerful access-control and auditing capabilities.
We'll cover these in a moment!

### Security or performance: why not both?

To address these problems, Protect.js uses ZeroKMS, which eliminates the performance-security tradeoffs common with traditional KMS.
Instead of a single root key, ZeroKMS uses composable pairs of keys to generate data keys.
One half of the pair, the _client key_ is stored by your application and the other, stored by ZeroKMS.

This technique has a number of security benefits, but most relevant to this discussion is that it now makes it safe to do _bulk requests_ for data keys.
ZeroKMS can generate up to **10,000** data keys in a single operation.

To demonstrate the impact on performance of this approach, we've shared some benchmarks below of Protect.js running in a Next.js application with Postgres and Drizzle.
Each request fetches 10 user records from a table that have 2 fields encrypted (email and address).

The [first report](https://app.artillery.io/share/sh_a2775fd02bf981207dd24e2e371b144f3e7792704948ac998ae34e83e28894b3) shows AWS KMS performance without data-key caching.
It managed an average of 27 and a peak of 44 requests per second.

![AWS KMS Artillery report](//images.ctfassets.net/y8kjcno8ru3x/1PNkIWeTjvzRifGBo5qxNM/6c1623411da8d87f516bda3521d067b5/bench-kms.png)

ZeroKMS managed [7 times the average and 14 times the peak](https://app.artillery.io/share/sh_75edb9d22a060633bfdce04777ffd60d1bcfc88989393dc38d3a4924b83fc6cd) performance of AWS KMS, with a peak rate of 615 requests/second.

![zerokms-artilllery](//images.ctfassets.net/y8kjcno8ru3x/4ysxelSwO57YbJwhISuAss/ec1992c5ebadf44b27d485bbf6e82161/bench-zerokms.png)

The Apdex scores are particularly telling:

![Apdex KMS vs ZeroKMS](//images.ctfassets.net/y8kjcno8ru3x/4cK3P0X7Ehdx7ymbDEPft1/c7a2e7e96a78975b281181dd1a4e6f96/KMS_Apdexes.png)

In a future post, we'll do a deep dive on how we ran these benchmarks as well as some surprising results.

### Rust you can trust

Under the hood, Protect.js is [written in Rust](https://crates.io/crates/cipherstash-client).
We chose Rust because it's incredibly fast, memory efficient, and has some unique security properties, particularly when it comes to cryptography.
Rust is the _only_ mainstream compiled language that is also memory safe without the need for a garbage collector.
This **eliminates** the most common type of security vulnerabilities: memory bugs.

Computerphile has a [great video](https://youtu.be/pTMvh6VzDls?si=PIouKj8H_OIWn-8D) about Rust and memory safety if you'd like to learn more.

### Quantum resistant, verified cryptography

The low-level cryptographic components of Protect.js use [aws-lc-rs](https://crates.io/crates/aws-lc-rs), Amazon’s formally verified cryptography library.
Used to mathematically _prove_ the correctness of software, formal verification is the gold-standard in high-assurance, secure-by-design systems.
Protect.js is also designed to be resistant to attacks by a quantum computer should such devices ever become practical.

### Vitamins for your crypto

At CipherStash, we’re also working on formal verification for other parts of our stack and have created an open source framework, called [Vitamin C](https://github.com/cipherstash/vitaminc).
Vitamin C is like vitamins for cryptography. It simplifies the writing of high-assurance software by providing secure, highly-scrutinised and tested reusable building blocks.

### SOC 2 compliant

CipherStash, including the ZeroKMS key service is SOC 2 compliant.
You can learn more in our [trust centre](https://trust.cipherstash.com/).

## What’s coming next?

We've barely scratched the surface in this post but over the coming weeks we'll share more about what Protect.js can do to make effective data protection easy.

* Granular control with identity-based context locking
* Searchable encryption with Protect.js and when to use it
* Deep dives on the internals of Protect.js and ZeroKMS
* More benchmarks, code samples, and tutorials for specific use-cases

## Protect sensitive data in just 15 minutes

All there is to do now is give [Protect.js](https://github.com/cipherstash/protectjs) a try!
Don't forget to star us on Github, too :)

## Related blog posts

- [Adding CipherStash Encryption to a Next.js app that uses the Supabase SDK](https://cipherstash.com/blog/add-protectjs-to-nextjs-app-with-supabase-sdk.md) — Let's look at how to add CipherStash Encryption to a Next.js app that uses the Supabase SDK and server actions to read and insert encrypted data into a Supabase Postgres instance.
- [Yay we got SOC 2 for the holidays](https://cipherstash.com/blog/yay-we-got-soc-2-for-the-holidays.md) — Just in time for the end of the year when people are rushing to finish things off, our gift to you is (drumroll please)... SOC 2 compliance!
- [Compliance doesn’t have to be scary](https://cipherstash.com/blog/compliance-not-scary.md) — Does the word “compliance” fill you with dread? It doesn’t have to. You probably imagine the time that’s going to be taken away from your important day-to-day work, but as our recent experience at CipherStash shows, there’s another way.

