Add Backend
ahpxex/open-dashboard
Everything about the data layer — pick one of six ready-to-run backend templates (TanStack Start + Drizzle + better-auth, Hono + Drizzle + better-auth, Hono + Prisma + better-auth, Hono + Drizzle +…
Deploy a CipherStash encryption rollout to a live environment without losing data — the multi-deploy ladder (schema-add + dual-write → backfill → read cutover → stop dual-writes → drop plaintext)…
$ npx skills add cipherstash/stack --skill stash-deployment -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install cipherstash/stack stash-deployment --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/stash-deployment .claude/skills/stash-deployment && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "stash-deployment" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-deployment into .claude/skills/stash-deployment/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-deployment", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/cipherstash/stack/tree/main/skills/stash-deploymentType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add cipherstash/stack --skill stash-deployment -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install cipherstash/stack stash-deployment --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/stash-deployment .agents/skills/stash-deployment && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "stash-deployment" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-deployment into .agents/skills/stash-deployment/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-deployment", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add cipherstash/stack --skill stash-deployment -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install cipherstash/stack stash-deployment --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/stash-deployment .cursor/skills/stash-deployment && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "stash-deployment" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-deployment into .cursor/skills/stash-deployment/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-deployment", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/cipherstash/stack.git --path skills/stash-deployment--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add cipherstash/stack --skill stash-deployment -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install cipherstash/stack stash-deployment --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/stash-deployment .gemini/skills/stash-deployment && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "stash-deployment" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-deployment into .gemini/skills/stash-deployment/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-deployment", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install cipherstash/stack stash-deploymentInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add cipherstash/stack --skill stash-deployment -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/stash-deployment .github/skills/stash-deployment && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "stash-deployment" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-deployment into .github/skills/stash-deployment/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-deployment", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add cipherstash/stack --skill stash-deployment -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install cipherstash/stack stash-deployment --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/stash-deployment .opencode/skills/stash-deployment && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "stash-deployment" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-deployment into .opencode/skills/stash-deployment/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-deployment", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
stash-deploymentDeploy a CipherStash encryption rollout to a live environment without losing data — the multi-deploy ladder (schema-add + dual-write → backfill → read cutover → stop dual-writes → drop plaintext)…
Stash Deployment is an agent skill from cipherstash/stack. Deploy a CipherStash encryption rollout to a live environment without losing data — the multi-deploy ladder (schema-add + dual-write → backfill → read cutover → stop dual-writes → drop plaintext), why each deploy boundary exists, what breaks if stages are merged, rollback per stage, and how to get CS credentials into a build and a runtime. Includes a Prisma Postgres / Prisma Compute section covering push-to-deploy, build-time credential requirements, destructive-migration policy, preview-branch databases, and…
Its SKILL.md is about 6.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Databases, covering ORMs and data access and Deployment. It works with Prisma, PostgreSQL and Supabase. The repository describes itself as: Searchable, application-level encryption for building privacy-first apps. The licence is MIT.
2 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 3eb459b. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npxpnpmyarnFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npx, pnpm and yarn, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
CS_CLIENT_KEYCS_CLIENT_ACCESS_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Stash Deployment loads about 6.5k tokens when it runs. Until then it costs about 191 tokens; SKILL.md has 3,349 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from cipherstash/stack at commit 3eb459b, republished under its MIT licence (© cipherstash). 3,349 words, ~6,475 tokens.
.claude/skills/stash-deployment/SKILL.md (or your agent's skills folder).Encrypting a column that already holds live data is a deployment problem, not a schema problem. The schema change is trivial; the danger is the window between "the database can hold ciphertext" and "the application reads ciphertext". Getting that window wrong loses data silently — rows written during the gap keep only plaintext, or keep only ciphertext nobody can decrypt, and nothing errors until a user reads the row.
This skill covers how to sequence that across deploys. For the API and the
lifecycle model see stash-encryption; for the commands see stash-cli; for
framework specifics see stash-drizzle / stash-supabase / stash-prisma.
Everything here describes EQL v3, the only authoring generation. The EQL v2
rollout commands were removed — stash encrypt cutover (the old v2 rename swap)
now exits with an error on every project. A column that started under v2
finishes the same way as v3: complete the backfill, switch reads to the
encrypted column by name, then stash encrypt drop. Legacy v2 payloads remain
readable; stash-encryption covers that.
Runner note.
stash initaddsstashto the project as a dev dependency, so the barestash <command>form used below runs through whichever package manager the project uses. Before init has run, prefix with your package manager's one-shot runner (bunx,pnpm dlx,yarn dlx,npx). The same substitution applies to theprisma-nextand@prisma/cliinvocations in the Prisma section.
CS_* credentials into a build pipeline and a deployed runtimeA column goes from plaintext to encrypted across at least four deploys, never one.
Not a style preference. There is no atomic operation that replaces a populated
plaintext column with an encrypted one, because ciphertext can only be produced
by the application, client-side, holding your keys. No UPDATE, no migration,
no database-side function can encrypt an existing row. So the plaintext column must
stay authoritative — and stay populated — until every row has a ciphertext twin and
the deployed code is reading it.
Any plan that adds an encrypted column and drops the plaintext one in the same deploy loses data. Any plan that backfills before dual-writes are live in production loses the rows written during the backfill. The ladder below is the minimum safe shape.
DEPLOY 1 rollout + encrypted twin (nullable) + dual-write
everywhere; reads unchanged
⛔ GATE 1 dual-writes live in the environment that owns the database
out-of-band BACKFILL encrypt historical rows, against the prod DB
migration indexes eql_v3.* extractor indexes + ANALYZE, shipped
through the integration's migration flow
DEPLOY 2 read cutover reads → encrypted column, decrypt at the
boundary; dual-writes stay
⛔ GATE 2 soak — real traffic reads decrypt correctly
DEPLOY 3 stop dual-write writes → encrypted column only; plaintext
column stays, now unwritten
⛔ GATE 3 Deploy 3 live everywhere; coverage re-checked
DEPLOY 4 drop plaintext drop the plaintext column (+ NOT NULL on
encrypted), guarded by an apply-time
coverage re-checkFour deploys, with the backfill and index build between the first two. Each gate is a human decision, not a pipeline step.
Before any application change, make sure the target environment can encrypt at all:
stash eql install;
Drizzle generates an install migration instead (apply it with
drizzle-kit migrate), and Prisma Next installs it through the migration
graph (prisma-next migrate).CS_* credentials present in the environment, minted with
stash env --name <app>-<env>. On most platforms these are needed at build
time as well as run time (see Credentials).@cipherstash/stack entry wraps a native
FFI module — exclude it from bundling (serverExternalPackages, esbuild
external, …). On runtimes that cannot load native modules at all
(Cloudflare Workers, Deno, Supabase Edge Functions), bundle
@cipherstash/stack/wasm-inline instead — it inlines the WASM build, no
externalization needed. See stash-edge.Shipping this as its own deploy is cheap and de-risks Deploy 1: a credential or bundling problem surfaces while nothing depends on encryption yet.
One PR, one deploy. It changes what the app writes, never what it reads.
| Change | Detail |
|---|---|
| Migration | Add <col>_encrypted as a nullable encrypted column alongside the untouched plaintext <col>. Nullable is mandatory — existing rows have no ciphertext yet. |
| Code | Every persistence path that mutates the row writes both columns, in the same transaction, on every code branch. |
| Reads | Unchanged. Still plaintext. |
"Dual-write" means every path. Not the ORM model, not the main service — every site. A CSV importer, an admin action, a background job, a webhook handler, a raw SQL fixup script: one missed branch means rows created in production after this deploy have no ciphertext, and the backfill (which ran earlier) will not catch them. Grep for every writer of the plaintext column before merging.
After this deploy the system is in a safe steady state and can stay there indefinitely. New rows are fully encrypted; old rows are not; reads work either way.
Not on a laptop. Not in CI. In the deployed environment whose traffic writes to the
database you are about to backfill. Verify with stash status before continuing;
stash impl will refuse a cutover plan whose columns have no dual_writing event
in cs_migrations, and stash encrypt backfill prompts for the same confirmation
(--confirm-dual-writes-deployed in CI).
Not a deploy. A one-off job run against the production database, encrypting the historical rows that predate Deploy 1.
stash encrypt backfill --table users --column emailPaginated by primary key, one transactional UPDATE per chunk plus a checkpoint,
SIGINT-safe, idempotent on re-run. Concurrent production writes are safe because
dual-writes are live — that is the entire reason for Gate 1.
Two hard requirements:
stash-zerokms documents).
The trap: CS_* env
vars beat the local ~/.cipherstash profile, so a backfill that silently
authenticates as your laptop profile can resolve a different keyset. Exporting
the app's own CS_* vars for the run is the simplest way to guarantee a
match. Keysets and grants: stash-zerokms; credential resolution order:
stash-auth.Then build the eql_v3.* extractor indexes for every capability you query and
ANALYZE — after backfill, before the read switch. One bulk build instead of per-row
index maintenance during the backfill, and the switched reads engage an index from
the first query. Ship the DDL through whatever migration flow owns the schema —
a Drizzle or Supabase migration, an index migration in the Prisma Next graph
(never out-of-band there — see stash-prisma), or your SQL migration tool.
Never ad-hoc against production. Recipes in stash-indexing.
Reads move to the encrypted column; writes still go to both.
| Change | Detail |
|---|---|
| Queries | Point them at the encrypted column by name (<col>_encrypted) and filter/sort through the encrypted operators. |
| Reads | Decrypt at the boundary before returning values to callers. Skipping this returns raw EQL payloads to end users. |
| Writes | Still dual-write. Do not remove it yet. |
There is no rename and no CLI step here — this deploy is application code only.
(There is no cutover command: stash encrypt cutover was the EQL v2 rename swap
and has been removed — running it exits with an error pointing at this
manual path.)
Keeping dual-writes through this deploy is what makes it reversible: if reads misbehave, Deploy 2 reverts to plaintext reads and every row is still correct in both columns.
Let real traffic read the encrypted column. Confirm results are correct — not just non-empty: check ordering, range filters, and free-text matches against known rows. Re-check coverage (still zero plaintext-only rows; new writes are covered by dual-writes). Only then ship Deploy 3.
Remove the dual-write logic: writes now target only the encrypted column. The
plaintext column stays in place, unwritten. If the original column is NOT NULL,
this deploy's migration must relax that (DROP NOT NULL) — otherwise inserts
fail the moment dual-writes stop.
This is the deploy that ends easy reversibility: rows written after it have no plaintext value, so reverting reads back to the plaintext column is no longer safe. That is why Gate 2's soak comes first.
Do not fold the drop into this deploy. Most pipelines apply migrations before the new code is live — a drop migration riding alongside the dual-write removal executes while the previous deploy's still-dual-writing code is serving traffic, and every insert and update fails against a column that no longer exists.
Deploy 3 must be live on every instance (rolling deploys included), and coverage re-checked one last time: zero rows with plaintext and no ciphertext. Only then is the drop safe.
Irreversible — every earlier stage can be walked back (see Rollback per stage). This deploy is migration-only: the application stopped touching the column in Deploy 3.
| Change | Detail |
|---|---|
| Migration | Drop the plaintext column, and (optionally) SET NOT NULL on the encrypted one. |
Generate the drop rather than hand-writing it where the tooling can:
stash encrypt drop --table users --column email emits a migration whose SQL takes
ACCESS EXCLUSIVE on the table, re-counts uncovered rows at apply time, and
raises instead of dropping if any remain. That re-check matters: the coverage you
verified at planning time is not the coverage at apply time.
If you author the drop by hand (some integrations require it — see the Prisma Next notes below), reproduce that property: the drop and the coverage check must be in one transaction, so a failed check rolls the drop back.
| Shortcut | What actually happens |
|---|---|
| Twin column + dual-write + backfill + read switch in one deploy | Rows written between migration-apply and code-live have no ciphertext. Reads return null/garbage for them. |
| Backfill before dual-writes are live in production | Every row written during and after the backfill window stays plaintext-only. Silent; found later by a user. |
| Backfill under credentials that resolve a different keyset (e.g. the laptop profile) | Ciphertext lands under the wrong keyset. No error at write time; in production the app either cannot decrypt those rows (no grant) or — if granted that keyset — decrypts them fine while encrypted search silently misses them. |
| Drop plaintext in the same deploy as the read switch | No rollback. If the read path is wrong, the source data is already gone. |
| Drop plaintext in the same deploy that removes dual-writes | Migrations usually apply before the new code is live: the still-deployed dual-writing code writes to a dropped column, and every insert/update fails until the rollout completes. |
| Drop without an apply-time coverage re-check | Rows written by a missed dual-write path are destroyed by the drop. |
NOT NULL on the encrypted column before coverage is proven | Migration fails mid-deploy, or (worse) succeeds and the drop already ran. |
| Stage | Rollback |
|---|---|
| Deploy 1 | Revert the code. Extra nullable column is inert; leave it. |
| Backfill | Nothing to undo — it only fills nulls. Re-runnable. |
| Deploy 2 | Revert the code; reads return to plaintext. Both columns still correct. |
| Deploy 3 | Revert the code; dual-writes resume. But rows written while it was live have no plaintext — reverting Deploy 2 after this point is unsafe. |
| Deploy 4 | None. The plaintext is gone. This is why Gates 2 and 3 exist. |
Mint per-environment credentials from your device session:
stash env --name my-app-prod # prints the four CS_* vars to stdout
stash env --name my-app-prod --json # NDJSON, no prompts, for CICS_WORKSPACE_CRN=crn:<region>:<workspace-id>
CS_CLIENT_ID=<uuid>
CS_CLIENT_KEY=<hex>
CS_CLIENT_ACCESS_KEY=CSAK…Rules that bite in practice:
stash env --name x | <secret-store-cli> is safe.--name values. Each run mints a new credential; duplicate names are rejected.db.ts-style singleton — then any build step that imports that module
authenticates during the build. Static-generation and page-data collection do
exactly this. A build without CS_* fails with Not authenticated. Local builds
mask it, because the ~/.cipherstash device profile authenticates silently.stash-zerokms.Backfills, coverage checks, and manual migrations need a plain Postgres connection to the exact database the deployed app uses. Two things go wrong here:
Run these as explicit, reviewed steps. Do not wire them into the deploy pipeline: they are one-shot, they need production credentials, and they must not re-run on every deploy.
Observed on Prisma Compute (Public Beta) and Prisma Next Early Access, July 2026. Both move quickly — verify against the current CLI help before relying on a detail.
Prisma Next is contract-first: contract.prisma is emitted to contract.json /
contract.d.ts, and the database is advanced along a migration graph. CipherStash
integrates through @cipherstash/stack-prisma, which contributes its own contract
space, so EQL installs as part of your migration graph — prisma-next migrate
(the top-level apply verb) installs the bundle alongside your schema. Never
stash eql install, which refuses on a Prisma Next project.
db initA Compute deploy (including every GitHub push-to-deploy build) runs
prisma-next contract emit followed by prisma-next db init against the target
branch database. Three consequences:
1. Additive migrations deploy themselves. The EQL bundle install and the encrypted-twin columns of Deploy 1 apply during the build. No manual migrate step.
2. Destructive migrations cannot ship through a deploy. db init is
additive-only by policy. A merge carrying dropColumn or setNotNull fails the
build:
PN-CLI-4020: Migration planning failed
Operation "Set NOT NULL on "transaction"."amountEncrypted"" requires class
"destructive", but policy allows only: additiveThis happens even when the PR contains a hand-authored migration covering exactly
that change — db init reconciles live schema against the contract and does not
consult the authored edge. The failed build itself causes no outage: the previous
deployment keeps serving and the database is untouched.
The sequence that works for the drop (Deploy 4) — run it only after the Deploy 3 merge (dual-write removal) is live, so nothing still writes the plaintext column when it disappears:
# 1. Mint a one-time connection URL for the PRODUCTION database
npx @prisma/cli database list # identify the target — see below
npx @prisma/cli database connection create <db_id>
# 2. Apply the authored migration out-of-band, BEFORE merging the Deploy 4 PR
npx prisma-next db update --db "<one-time-url>"
# 3. Remove the connection, then merge. `db init` sees no drift and passes.
npx @prisma/cli database connection remove <connection_id>Applying before the merge is strictly better than merging and recovering: the build passes on the first try. The deploy ordering is what makes the window safe — with dual-writes already removed in Deploy 3, nothing running touches the column between the out-of-band apply and the merge going live.
3. Preview branches get their own databases. Each preview branch database is
created fresh, so db init reconciles it from empty and never walks the
destructive migration history. A destructive PR's preview deploy can pass while
its production deploy fails. Do not read a green preview as proof the production
deploy will work.
database list shows one database per branch, and branch metadata is not a reliable
discriminator — entries can all report the same branch scope. Production is
identified by its name (the primary/production database), not by list order and
not by the entry named after your git branch. Confirm with
npx @prisma/cli database show <database> --json before minting a connection.
Getting this wrong is quiet: applying a migration to a preview database succeeds, and the production deploy then fails with the identical error it failed with before.
Merging to the default branch deploys everything in that merge. So the ladder above maps to separate PRs merged in sequence, with the out-of-band steps run between merges:
| Stage | PR | Manual step after merge |
|---|---|---|
| Deploy 1 | encrypted twins + dual-write | put CS_* into the production env, redeploy, then run the backfill |
| Indexes | eql_v3.* index migration, in the graph — never out-of-band | ANALYZE, verify with EXPLAIN |
| Deploy 2 | read cutover + decrypt at boundary | soak and verify |
| Deploy 3 | remove dual-writes (code; relax NOT NULL on plaintext if set) | verify writes are clean, re-check coverage |
| Deploy 4 | contract drop + authored migration | (apply the migration before merging — after Deploy 3 is live) |
Never combine two stages into one PR to save a review cycle. The gates are the safety mechanism.
CS_* and NEXT_PUBLIC_* are build-time inputsSet both before the build, for both roles (preview and production):
NEXT_PUBLIC_* values are inlined at build time.CS_* are needed at build time because cipherstashFromStack authenticates while
constructing the client, and db.ts constructs it at module load.serverExternalPackages: ["@cipherstash/stack", "@cipherstash/protect-ffi", "@cipherstash/auth"].prisma-next migration plan scaffolds dropColumn + setNotNull from the contract
diff, but the resulting migration has no coverage guard — and the usual remedy
(a dataTransform that fills the nulls in SQL) is impossible here, because
ciphertext cannot be produced in SQL and the plaintext source is dropped by the same
migration.
Until the extension pack ships a factory for this, add the guard by hand: a
dataTransform whose check selects rows still missing ciphertext acts as a
pre/post condition around a no-op run, so a single uncovered row rolls back the
whole transaction — the drop included. Order the operations so each setNotNull is
preceded by its gate. Re-run the migration file after editing so its migration.json
is re-emitted and the hashes match.
app logs is runtime output; build logs <build-id> is CI output. Different
identifiers — the id in a check run's console URL is not the build id; the build
id is printed in the check output itself.
| Symptom | Cause |
|---|---|
Not authenticated during a build | CS_* missing from the build environment. Local builds mask it via the device profile. |
| Native module fails to load (edge runtime, bundled serverless) | The default entry needs native require. Bundle @cipherstash/stack/wasm-inline instead — see stash-edge. |
Writes fail with column "…" does not exist after the drop | The drop was applied while dual-writing code was still deployed. Deploy the dual-write removal (Deploy 3) before applying the drop. |
Rows read back as null / garbage after cutover | Uncovered rows: a write path that never dual-wrote, or a backfill that ran before dual-writes were live. Re-run the backfill with --force. |
| Decrypt fails only in production | Keyset mismatch — ciphertext written under a different keyset than the app resolves, with no grant covering it (see stash-zerokms). |
| Decrypt works but encrypted search returns zero rows | Reader bound to a different keyset than the writer while granted the writer's (see stash-zerokms), or an index/predicate issue (stash-indexing, stash-postgres). |
| Raw EQL payloads reaching end users | Read path not wired through decryption. |
| Deploy fails with a destructive-operation policy error | Additive-only deploy policy. Apply the authored migration out-of-band, ideally before merging. |
| Migration applied but the deploy still fails identically | It was applied to the wrong (preview) database. |
NOT NULL migration fails at apply time | Coverage is not actually complete. Good — that is the guard working. |
stash-encryption — the encryption API and the canonical rollout/cutover modelstash-cli — stash status / plan / impl / encrypt * / env command surfacestash-indexing — the eql_v3.* extractor indexes to build between backfill and cutoverstash-zerokms — the keyset/grant model that governs who can decrypt whatstash-auth — auth strategies, the CS_* variables, and credential resolution orderstash-edge — edge/serverless runtimes and the @cipherstash/stack/wasm-inline entrystash-prisma / stash-drizzle / stash-supabase — integration specifics© cipherstash, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/stash-deployment of cipherstash/stack.
Open the folder on GitHubat commit 3eb459b
Stash Deployment next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Stash Deployment this skillcipherstash/stack | 157 | — | ~6.5k | Automated safety check: Pass | MIT | |
| Add Backendahpxex/open-dashboard | 146 | — | ~3.6k | Automated safety check: Pass | MIT | |
| Prisma 8 Contract-First ORMprisma/orm | 48k | — | ~3.8k | Automated safety check: Notes | Apache-2.0 | |
| Safe SQL Executionsupabase/supabase | 111k | — | ~4.2k | Automated safety check: Pass | Apache-2.0 | |
| Better Drizzlealmeidazs/better-drizzle | 347 | — | ~1.7k | Automated safety check: Pass | Apache-2.0 | |
| Prisma Database Setupcurvenote/curvenote | 169 | 3 repos | ~1.4k | Automated safety check: Pass | MIT |
ahpxex/open-dashboard
Everything about the data layer — pick one of six ready-to-run backend templates (TanStack Start + Drizzle + better-auth, Hono + Drizzle + better-auth, Hono + Prisma + better-auth, Hono + Drizzle +…
prisma/orm
Routes Prisma 8 tasks such as contracts, migrations, queries and upgrades to the right reference files for projects on the contract-first @prisma/orm packages.
supabase/supabase
A skill your agent uses whenever code will build, return, fetch, or execute SQL that runs against a user's real Postgres database — even when the request reads like an ordinary feature or bug fix…
almeidazs/better-drizzle
Write, review, and debug code that uses better-drizzle, the typed repository layer over Drizzle ORM 1.x (better(db), client.users.findMany, paginate, cursor, upsertMany, relation include/connect…
curvenote/curvenote
Guides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.).
DanielPodolsky/ownyourcode
Reviews schema design, SQL queries, ORM patterns. An agent skill from DanielPodolsky/ownyourcode.
cipherstash/stack
How an agent files a GitHub issue on cipherstash repos — required structure (Background / Problem / Proposal), dumbed-down wording rules, and pre-filing checks.
cipherstash/stack
How an agent authors branches, commits, and pull requests on cipherstash/stack — naming, signed commits, the changeset/skills/meta-file checklist, and PR body structure with dumbed-down wording.
cipherstash/stack
The ZeroKMS key model — keysets, clients, client keys, and the grant/revoke lifecycle.
cipherstash/stack
Supply-chain security controls for the @cipherstash/stack monorepo.
cipherstash/stack
Integrate CipherStash encryption with Drizzle ORM using @cipherstash/stack-drizzle (EQL v3).
cipherstash/stack
Run CipherStash encryption on edge and non-Node runtimes with the @cipherstash/stack/wasm-inline entry — Deno, Supabase Edge Functions, Cloudflare Workers, and Bun.
Works with
Categories
Deploy a CipherStash encryption rollout to a live environment without losing data — the multi-deploy ladder (schema-add + dual-write → backfill → read cutover → stop dual-writes → drop plaintext)…. Stash Deployment is an agent skill from cipherstash/stack. Deploy a CipherStash encryption rollout to a live environment without losing data — the multi-deploy ladder (schema-add + dual-write → backfill → read cutover → stop dual-writes → drop plaintext), why each deploy boundary exists, what breaks if stages are merged, rollback per stage, and how to get CS credentials into a build and a runtime.
Stash Deployment fits situations like: shipping encryption to staging; planning the PR sequence for an encryption rollout; wiring deploy credentials; deploying a CipherStash app to Prisma Compute.
Run `npx skills add cipherstash/stack --skill stash-deployment -a claude-code`. Or copy the skill folder (skills/stash-deployment in cipherstash/stack) into .claude/skills/stash-deployment in your project. Claude Code loads it when a task matches its description.
Run `npx skills add cipherstash/stack --skill stash-deployment -a codex`. Or copy the skill folder (skills/stash-deployment in cipherstash/stack) into .agents/skills/stash-deployment in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add cipherstash/stack --skill stash-deployment -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/stash-deployment, .gemini/skills/stash-deployment, .github/skills/stash-deployment and .opencode/skills/stash-deployment in your project.
Going by SKILL.md and its folder, Stash Deployment needs the command-line tools its instructions call (npx, pnpm and yarn) and credentials named CS_CLIENT_KEY and CS_CLIENT_ACCESS_KEY.
SKILL.md contains no URLs. Its commands use npx, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Stash Deployment is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.5k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Stash Deployment: Add Backend (ahpxex/open-dashboard, 146 stars), Prisma 8 Contract-First ORM (prisma/orm, 48k stars), Safe SQL Execution (supabase/supabase, 111k stars) and Better Drizzle (almeidazs/better-drizzle, 347 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
cipherstash (a GitHub organization) maintains it in cipherstash/stack, which has 157 GitHub stars. The repository holds 14 skills in this directory. The repository was last updated on October 8, 2026.
Source: cipherstash/stack on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.