Introducing Protect.js


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.
This is why we created Protect.js.
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, 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.

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:
1// src/protect/schema.ts
2import { csTable, csColumn } from '@cipherstash/protect'
3
4export const users = csTable('users', {
5 email: csColumn('email'),
6})
Then, to encrypt a value, pass any schemas you've defined to protect
and call encrypt
:
1// src/protect/client.ts
2import { protect } from '@cipherstash/protect'
3import { users } from './schema'
4
5// Do this once when your app starts
6export const protectClient = await protect(users)
7
8// Encrypt an email address for a configured column and table
9const encryptResult = await protectClient.encrypt('[email protected]', {
10 column: users.email,
11 table: users,
12})
Check out the Protect.js getting started guide 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 at BSides SF in 2024.

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 or Azure KeyVault 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).

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 shows AWS KMS performance without data-key caching. It managed an average of 27 and a peak of 44 requests per second.

ZeroKMS managed 7 times the average and 14 times the peak performance of AWS KMS, with a peak rate of 615 requests/second.

The Apdex scores are particularly telling:

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. 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 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, 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. 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.
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 a try! Don't forget to star us on Github, too :)