# Introducing @cipherstash/stack

*Published on 2026-05-18T00:00:00.000Z*

*By Dan Draper — CEO and Founder*

Building blocks for Data Level Access Control in TypeScript

## Content

**Data Level Access Control (DLAC)** enforces access on individual data values: each one carries its own encryption, and the right to decrypt it is bound to an identity. Encryption at rest protects the disk; row-level security gates which rows a user sees — but in both, the database still reads every value in the clear. DLAC protects the data itself, and that protection travels with the value wherever it goes.

**CipherStash Stack** is our set of composable building blocks for adding DLAC to the TypeScript app you already have, one field at a time.

## What agents change about data access

Application security has always assumed one kind of principal: a human behind a session. An agent doesn't fit — it acts for a user without being one, chains tools, and spreads data through prompts, logs, and traces. That's not misuse; it's how agents work, and a poor fit for controls built around a person reading data once per request.

DLAC doesn't depend on that assumption. Because protection lives in each value, it holds whoever the principal is — human or agent.

## Five pieces: encryption, keys, proxy, auth, and skills

[Stack][stack-github] is five composable pieces. You don't have to adopt them all on day one. Most teams start with one or two and grow into the rest.

- **Encryption.** Value-level, searchable encryption for any field. Postgres filters, sorts, and joins run directly over the ciphertext via [EQL](https://cipherstash.com/docs/stack/reference/eql-guide), so your data stays encrypted at rest, in transit, *and in use*.
- **Key management.** [ZeroKMS](https://cipherstash.com/docs/stack/cipherstash/kms), our hyper-fast key service backed by AWS KMS. A unique data key per value, with no caching gymnastics, up to 14x faster than AWS KMS direct.
- **Proxy.** [CipherStash Proxy](https://cipherstash.com/docs/stack/cipherstash/proxy) sits between your app and Postgres and applies encryption transparently. Drop it in front of a legacy app with zero code changes, or use it as a debugging tool when you want to inspect what your SDK is producing.
- **Auth.** OIDC integration with caching and claims mapping, so an end-user's identity is what authorises a decrypt, not a service account or a shared key. Built on the [CipherStash Token Service](https://cipherstash.com/docs/stack/cipherstash/kms/cts).
- **Skills.** Agent skills for encryption and data security with CipherStash Stack. The same building blocks your team uses are documented in a form your agents can pick up correctly the first time.


## Get a field encrypted in under 15 minutes

The fastest way in is the `stash` CLI:

```sh
npx stash init
```

It'll walk you (or an agent in your editor) through choosing a database connection, generating an encryption schema, and installing the EQL extension into your Postgres. Same flow whether you drive it manually step-by-step or let an agent handle it end-to-end. From a fresh checkout to your first encrypted field, most teams are there in under fifteen minutes.

Stack is designed to be adopted **one field at a time**. Pick a single sensitive column, an email address, a phone number, a free-text note, encrypt it, ship it, then come back next sprint and do the next one. No big-bang migrations.

{% eyebrow index="01" label="Encryption" heading="Encrypting a field, then querying it two ways" /%}

Declare which fields are encrypted in a schema:

```ts
// src/encryption/schema.ts
import { encryptedTable, encryptedColumn } from '@cipherstash/stack/schema';

export const users = encryptedTable('users', {
  email: encryptedColumn('text'),
  meta: encryptedColumn('json'),
});
```

[Searchable encryption][searchable-encryption] is opt-in and specified by query type on each field:

```ts
// src/encryption/schema.ts
export const users = encryptedTable('users', {
  // serachable encrypted text
  email: encryptedColumn('text').equality().freeTextSearch(),

  // searchable encrypted JSON
  meta: encryptedColumn('json').searchableJson(),
});
```

Simple equality lookups, `ILIKE` style free-text search, range queries, `ORDER BY` and even operations on JSON objects are supported.



From your TypeScript application, encrypt and query through the SDK:

```ts
import { client } from '@cipherstash/stack';
import { users } from './encryption/schema';

await db.insert(users).values({
  email: await client.encrypt('alice@example.com', { column: users.email }),
});

// encrypted query term
const needle = await client.encrypt('alice@example.com', { column: users.email });

// use it in a query
const rows = await db
  .select()
  .from(users)
  .where(eq(users.email, query));
```

Stack composes with what you already use. [Prisma Next](https://cipherstash.com/docs/stack/cipherstash/encryption/prisma-next) declares encrypted fields directly in the data contract. [Drizzle](https://cipherstash.com/docs/stack/cipherstash/encryption/drizzle) gets encrypted column types and operators that fit alongside the existing query builder. [Supabase](https://cipherstash.com/docs/stack/cipherstash/encryption/supabase) wraps with `encryptedSupabase` and keeps `.eq()`, `.like()`, `.order()` working.

Not on Postgres? [DynamoDB](https://cipherstash.com/docs/stack/cipherstash/encryption/dynamodb) is supported today as well, with more integrations to follow.

{% eyebrow index="02" label="Key management" heading="ZeroKMS: a key for every value" /%}

Every encrypted value gets its own data key. That should be expensive — millions of keys, one per value, fetched on every read — but [ZeroKMS](https://cipherstash.com/docs/stack/cipherstash/kms) makes it cheap. Backed by AWS KMS, it mints and retrieves keys in bulk, up to 14× faster than calling AWS KMS directly, with no key caching for your application to get wrong. Per-value keys are what make identity-bound decryption and a precise, cryptographic audit trail possible in the first place.

{% eyebrow index="03" label="Proxy" heading="A no-code escape hatch" /%}

If you don't want to touch the application — say you need to reach the same data from a different service, from `psql`, or from a BI tool — point at [Proxy][proxy-github] and write plain SQL:

```sql
-- Connecting through CipherStash Proxy on :6432
INSERT INTO users (email) VALUES ('alice@example.com');

SELECT * FROM users WHERE email = 'alice@example.com';
SELECT * FROM users WHERE email ILIKE '%@example.com';
```

Proxy rewrites the statement, encrypts the parameters, executes against Postgres, and decrypts the result on the way back. Your application sees a regular Postgres connection. And it's not just a debugging aid — teams run Proxy permanently in production, directly in their application's hot path.

Whether you use Typescript or Proxy (or both!) your data remains securely accessible. Two paths in, depending on what fits your team.


{% eyebrow index="04" label="Auth" heading="Decryption bound to a real identity" /%}

Encryption controls *where* plaintext exists; auth controls *who* can bring it back. Stack authorises **every** decryption against a real end-user identity from your own identity provider — never a shared service account, never a long-lived key.

You wire this up once. In your [workspace settings](https://dashboard.cipherstash.com/sign-in), add your existing IDP as an OIDC provider — choose the vendor, paste the issuer URL — and from then on the [CipherStash Token Service](https://cipherstash.com/docs/stack/cipherstash/kms/cts) verifies tokens, caches them, and maps claims on every encrypt and decrypt. The same model extends to agents, which carry their own identity rather than borrowing a human's.

{% figure src="/images/blog/introducing-cipherstash-stack-oidc-provider.png" alt="The CipherStash dashboard dialog for adding a new OIDC provider, with Vendor set to Auth0 and an Issuer URL field" caption="Add your IDP as an OIDC provider in workspace settings — it then authorises every decryption." width="two-thirds" /%}

{% eyebrow index="05" label="Skills" heading="Encryption your agents get right" /%}

The fastest way to get encryption wrong is to hand-roll it — and an agent writing your code will hand-roll it confidently. Stack ships [agent skills](https://github.com/cipherstash/stack/tree/main/skills): the same building blocks, documented in a form an agent in your editor can pick up and apply correctly the first time. `npx stash init` is already agent-drivable, and the skills cover everything after it, so encryption decisions land as reviewable code in a pull request instead of improvised guesses.

Add the skills to your project in one command:

```sh
npx skills add cipherstash/stack
```

## A billion values in production

The numbers:

- **454μs** median for an encrypted exact-match query
- **1.46ms**{% fnref /%} median for an encrypted range query
- **Up to 14×** faster than AWS KMS direct, for bulk encrypt/decrypt against ZeroKMS

The same primitives are protecting over a billion sensitive values in production right now — we [crossed that milestone](https://cipherstash.com/blog/one-billion-encrypted-values) earlier this year. Three of the teams running on Stack today:

- **[Amber Electric](https://cipherstash.com/case-studies/amber).** Climate-focused energy retailer expanding internationally. Calls the Encryption SDK from TypeScript Lambdas against Postgres and DynamoDB to satisfy GDPR and per-market compliance without re-platforming
- **[Journalia](https://cipherstash.com/case-studies/journalia).** AI-driven medical transcription for European healthcare. Identity-bound encryption so only the doctor who created a patient note can decrypt it — special-category data protected cryptographically rather than by policy
- **[BNDRY](https://cipherstash.com/case-studies/bndry).** Financial-crime risk and compliance platform. Runs CipherStash Proxy in their Kubernetes cluster to protect customer JSONB data, including encrypted search over form responses

Three of the most regulated industries in the world, on the same set of building blocks.

## Where Stack fits

If you've already evaluated transparent database encryption, row-level security, or an external tokenisation vault, here's the placement.

Transparent database encryption protects against stolen disks. Against a stolen credential, it does nothing — the database still hands plaintext to anyone who can log in. Row-level security takes the problem a step further and gates access by policy, but the policy lives in the database's own configuration, and the database itself still reads plaintext to evaluate it. External tokenisation vaults solve the problem differently again: by moving the plaintext out of your application entirely. The cost is that every read becomes a round trip to the vault, and the rest of your code gradually starts to revolve around it.

CipherStash Stack puts ciphertext in your existing database and keeps the queries working. Per-value keys, identity-bound decryption, and a cryptographic audit trail are properties of the encryption itself — not properties of the configuration that wraps it. A misconfigured rule produces ciphertext; it does not leak plaintext.

We'll be publishing deeper comparisons of Stack against each of these approaches — transparent database encryption, row-level security, and tokenisation vaults — in upcoming articles.

## Agents are first-class identities, not service accounts

Stack treats agents as first-class identities. An agent gets its own keys and its own policy, distinct from the user it's acting for. Because enforcement happens in the encryption layer, the agent only ever sees decrypted bytes for the fields it is explicitly authorised to read, scoped to the user and the task at hand. Everything else stays as ciphertext: through tool calls, prompts, logs, traces, and whatever context gets snapshotted along the way.

A prompt-injected agent cannot exfiltrate what it does not hold keys for. A background worker that should only summarise one user's chats cannot accidentally pull another's. Row-level security still bounds what rows an agent can fetch; CipherStash bounds what it can actually read. The two compose.


## Get Started

```sh
npx stash init
npx skills add cipherstash/stack
```

Check out the [docs](https://cipherstash.com/docs), the [GitHub](https://github.com/cipherstash/stack) or come and say hi in [Discord](https://discord.com/invite/5qwXUFb6PB).

{% footnote %}
Measured against a 10-million-row dataset. Methodology and raw results are in our open-source [benchmark suite](https://github.com/cipherstash/benches) ([full report](https://github.com/cipherstash/benches/blob/main/report/BENCHMARK_REPORT.md)).
{% /footnote %}


[searchable-encryption]: https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption
[stack-github]: https://github.com/cipherstash/stack
[proxy-github]: https://github.com/cipherstash/proxy

## Related blog posts

- [One billion sensitive values encrypted in production](https://cipherstash.com/blog/one-billion-encrypted-values.md) — From a tough sell five years ago to over one billion encrypted values today — here's how CipherStash earned the trust of dev teams who were nervous about encrypting their most precious data.
- [Encryption in use with PostgreSQL](https://cipherstash.com/blog/encryption-in-use-with-postgresql.md) — Don't just rely on encryption at rest and in transit to protect your sensitive data. Use searchable encryption to enable encryption in use to harden data privacy in Postgres.
- [CipherStash + Prisma Next: Data Level Access Control, declared in your contract](https://cipherstash.com/blog/cipherstash-prisma-next-data-level-access-control.md) — Add searchable field-level encryption to a Prisma Next app the same way you'd add any other field — declared once in your contract, evolved the same way.

