Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions .github/workflows/tests-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ on:
- packages/stack-auth/**
- packages/stack-profile/**
- packages/stack-kms/**
- packages/stack-kms-vendor/**
- packages/stack-encrypt/**
- conformance/stack-encrypt/**
- packages/stack-encrypt-derive/**
Expand All @@ -44,6 +45,7 @@ on:
- packages/stack-auth/**
- packages/stack-profile/**
- packages/stack-kms/**
- packages/stack-kms-vendor/**
- packages/stack-encrypt/**
- conformance/stack-encrypt/**
- packages/stack-encrypt-derive/**
Expand Down Expand Up @@ -151,12 +153,39 @@ jobs:
# with no `version`, or a crate that uses API its dependency's packaged
# source does not have yet. stack-profile and stack-auth are in the list
# because stack-kms and stack-encrypt depend on them: without them, cargo
# resolves the published versions, which lag the tree. No token.
# resolves the published versions, which lag the tree. stack-kms-vendor
# is publishable but staged in release-plz.toml (its
# first version is published by hand); packaging it here keeps it
# publishable meanwhile. No token.
# `--allow-dirty` tolerates files earlier steps leave in the tree. Once a
# version is on crates.io, cargo warns rather than fails on a dry run of
# it.
- name: Verify the stack-* crates package cleanly for crates.io
run: cargo publish --locked --dry-run --allow-dirty -p stack-profile -p stack-auth -p stack-kms -p stack-encrypt-derive -p stack-encrypt
run: cargo publish --locked --dry-run --allow-dirty -p stack-profile -p stack-auth -p stack-kms -p stack-encrypt-derive -p stack-encrypt -p stack-kms-vendor

- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2204

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2


- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2204

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2


node-bindings:
name: node bindings, napi typings, stack-auth-wasm
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/udeps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
- packages/stack-auth/**
- packages/stack-profile/**
- packages/stack-kms/**
- packages/stack-kms-vendor/**
- packages/stack-encrypt/**
- packages/stack-encrypt-derive/**
- packages/stack-guest-abi/**
Expand All @@ -33,6 +34,7 @@ on:
- packages/stack-auth/**
- packages/stack-profile/**
- packages/stack-kms/**
- packages/stack-kms-vendor/**
- packages/stack-encrypt/**
- packages/stack-encrypt-derive/**
- packages/stack-guest-abi/**
Expand Down
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Every npm package except EQL lives under `languages/typescript/`: packages in `l
`cipherstash/encrypt-query-language` repository. Old upstream issue and PR
links are provenance only.
- `packages/stack-auth`, `packages/stack-profile`, `packages/stack-kms`, `packages/stack-encrypt`, `packages/stack-encrypt-derive`, `packages/stack-guest-abi`: The Rust crates imported from `cipherstash/cipherstash-suite` with their history — `stack-auth` and `stack-profile` (published to crates.io, one version group), `stack-kms` (published to crates.io from 0.1.0, its own version group, re-exported by `stack-encrypt` as `stack_encrypt::kms`), `stack-encrypt` and `stack-encrypt-derive` (published to crates.io from 0.1.0, one version group; `eql-bindings`' `stack-encrypt` feature depends on them from the registry), and `stack-guest-abi` (`publish = false`). They are the members of the **root Cargo workspace**, with the three node binding crates below. See "Working on the Rust crates".
- `packages/stack-kms-vendor`: The key vendor seam (ADR-0013 and ADR-0014 in `packages/stack-encrypt/docs/adr/`). `stack-kms-vendor` is the vendor seam (`KeyVendorBackend`), keyset records, the derived data key mode, the static key backend and the `VendorTransport` HTTP seam (feature `http`: the native reqwest transport). Its `test-support` feature holds the test code every vendor crate shares: the contract suite (`contract`), `FakeVendor`, and the recorded-fixture harness (`replay`: fixture types, `Replay`, `Recording`); a vendor crate's `tests/common/mod.rs` holds only its own emulator, keys and constants. It is a root workspace member, publishable in the `stack-kms-vendor` version group, and staged in `release-plz.toml` (`release = false`) until its first version is published by hand. The `stack-kms-vendor` crate docs hold the capability matrix of every key backend (key lifetime, vendor calls, what the vendor sees and can refuse, credentials, rotation, known limits); change it with the code it describes. TypeScript (`@cipherstash/stack`) cannot use a key vendor backend yet.
- `languages/typescript/packages/auth`, `languages/typescript/packages/profile`, `languages/typescript/packages/stack-auth-wasm`: The node bindings of those crates. `@cipherstash/auth` (napi-rs v2) and its six `platforms/*` packages are published to npm from this repository by `release.yml` (`auth-artifacts`, `publish-auth`); a change to what it ships, the `stack-auth` crate included, needs an `@cipherstash/auth` changeset (`require-auth-npm-changeset.yml`). `@cipherstash/profile` and its platforms are private and never published; `@cipherstash/stack-auth-wasm` is private and builds the wasm that `@cipherstash/auth` ships. Their `build` and `test` scripts never invoke cargo; `build:native`, `build:debug` and `test:cargo` do.
- `languages/golang`: The Go module — the SDK `encrypt` with its generated-code support `encrypt/gensupport` and the policy packages `encrypt/policy` and `encrypt/policy/protosource`; the credential package `auth`; the generator `stashgen` and its command `cmd/stashgen`; and `internal` (the shared guest plumbing and the `record` wire model). A wazero host with no cgo. Its two WASI guests (`encrypt/guest`, `auth/guest`) are detached Cargo workspaces built by `mise run wasm:guest:build` and `mise run wasm:auth-guest:build`; `mise run wasm:guest:build:deterministic` builds the seeded TEST build the hermetic Go tests use; the `.wasm` files they embed are gitignored. The stack-encrypt guest has a second build with the EQL types (`mise run wasm:guest:build:eql`, cargo feature `eql`, linking `eql-bindings` by path; `wasm:guest:build:eql:deterministic` is its test build), embedded by `encrypt/eql` and registered on import, so a program whose generated code names an EQL type runs it (ADR-0007, amended). `encrypt/eql`'s types and `Types` table are generated from the EQL catalog by `eql-codegen` (`eql_gen.go`, written by `mise run types:generate` in `packages/eql` and drift-gated by `types:check` and `cargo test -p eql-codegen`). Generated `*_stash.go` files are committed and CI fails when `go generate ./...` changes one. Releases are cut by hand with `.github/workflows/release-golang.yml` (`gh workflow run release-golang.yml -f version=X.Y.Z[-pre] --ref <branch>`): because the guests are gitignored on main and Go fetches a version as the files committed at its tag, the workflow builds them, commits them on a release-only commit no branch points at, tags it `languages/golang/vX.Y.Z` and pushes the tag alone, then attaches the EQL v4 SQL bundle to the GitHub release. The ref must carry the EQL v4 bundle, or the build stops at "Build the EQL SQL bundles". `scripts/go-release-version.mjs` refuses, before anything is built, every version Go would refuse (non-canonical semver, `+build` metadata, a v2+ major on a module path with no `/vN` suffix, a version shaped like a pseudo-version), a final version dispatched from anywhere but `main`, and a final version while `languages/golang/go.mod` pins a `github.com/cipherstash/vitaminc/…` module at a pseudo-version (vitaminc's Go bindings have no tags, and a TEMPORARY pin on an unreleased commit must be gone before the module is tagged); a pre-release may come from a branch and may carry such a pin. The build job also refuses a version the Go proxy already lists in `@v/list`, which catches a tag deleted after the proxy fetched it; it never asks the proxy for the version itself before the tag exists, because the proxy would cache "not found" for up to 30 minutes and `verify` would fail after the push. The build checks the `Cargo.lock` of the EQL workspace and both guest workspaces with `--locked` first, since none of those builds passes it. It runs in a job with no write token and hands the guests to a `publish` job that holds `contents: write` and runs only git and gh. `publish` fetches the commit with plain unauthenticated git rather than `actions/checkout`, whose `token` input defaults to the job's writable token, and refuses a release commit whose tree is not the one the build tested. `publish` takes the paths it commits from the workflow's fixed `GUESTS` list, never from the artifact, and refuses a `guests.sha256` that is not exactly one SHA-256 line for each of those three guests: the build job runs third-party code after writing that manifest, so a path read from it could put any file on the tag (`scripts/__tests__/go-release-artifact.test.mjs`). A version is immutable once the Go proxy has fetched it, so a tag is never moved or deleted: if `publish` or `verify` fails after the tag is pushed, use "Re-run failed jobs" on the same run, which keeps a tag that run pushed and creates or completes the release; a new dispatch of the same version is refused. Never tag a main commit by hand: `go get` would fetch a module with no guests.
- `e2e/*`: Cross-package end-to-end tests (package managers, supply chain, Prisma example README)
Expand Down Expand Up @@ -715,7 +716,7 @@ term. `cts-common` and `zerokms-protocol` take caret
requirements, because the published `stack-auth` inherits them, and an
exact pin would stop the suite sharing one `cts-common` with it.

- **Three Cargo workspaces, not one.** The root workspace (the six stack-*
- **Three Cargo workspaces, not one.** The root workspace (the seven stack-*
crates and the three node binding crates), protect-ffi's and EQL's. The root
`Cargo.toml` excludes the other two, and they pin their own vitaminc. The
three fuzz crates, the two Go guests and the EQL v4 block ORE timing
Expand Down Expand Up @@ -757,7 +758,7 @@ exact pin would stop the suite sharing one `cts-common` with it.
`MISE_ENV=test` cargo tools — and the live tests), `crap-crates.yml`,
`mutants.yml`, `fuzz.yml`, `miri.yml` and `udeps.yml`.
`scripts/__tests__/crates-ci.test.mjs` fails when a mise task in the root
`mise.toml` or the five crate `tasks.toml` files loses its last CI caller.
`mise.toml` or the six crate `tasks.toml` files loses its last CI caller.
- **Auth refresh cancellation soak:** `tests-auth-refresh.yml` runs a short,
seeded, virtual-time soak on every PR and the long run on auth changes and
Version Packages PRs. `release-plz.yml` also requires the long run before
Expand Down
35 changes: 35 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"packages/stack-auth",
"packages/stack-profile",
"packages/stack-kms",
"packages/stack-kms-vendor",
"packages/stack-encrypt",
"packages/stack-encrypt-derive",
"packages/stack-guest-abi",
Expand Down
7 changes: 6 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This repository is the CipherStash Stack monorepo for JavaScript/TypeScript. It
| `@cipherstash/wizard` | AI-powered encryption setup |
| `@cipherstash/protect-ffi` | Native FFI bindings to the CipherStash Client SDK — the Rust core `@cipherstash/stack` encrypts and decrypts through |
| `@cipherstash/protect-ffi-darwin-arm64`<br>`@cipherstash/protect-ffi-darwin-x64`<br>`@cipherstash/protect-ffi-linux-arm64-gnu`<br>`@cipherstash/protect-ffi-linux-x64-gnu`<br>`@cipherstash/protect-ffi-linux-x64-musl`<br>`@cipherstash/protect-ffi-win32-x64-msvc` | Prebuilt per-platform binaries for `@cipherstash/protect-ffi`. Installed as optional dependencies; one is selected at load time for the host platform |
| `@cipherstash/auth` | Authentication strategies for CipherStash (napi-rs native binding, with a WASM build for edge runtimes) — imported from `cipherstash/cipherstash-suite` with the `stack-auth` crate it wraps |
| `@cipherstash/auth` | Authentication strategies for CipherStash (napi-rs native binding, with a WASM build for edge runtimes) — imported from `cipherstash/cipherstash-suite` with the `stack-auth` crate it wraps. |
| `@cipherstash/auth-darwin-arm64`<br>`@cipherstash/auth-darwin-x64`<br>`@cipherstash/auth-linux-arm64-gnu`<br>`@cipherstash/auth-linux-x64-gnu`<br>`@cipherstash/auth-linux-x64-musl`<br>`@cipherstash/auth-win32-x64-msvc` | Prebuilt per-platform binaries for `@cipherstash/auth`. Installed as optional peer dependencies; one is selected at load time for the host platform |
| `@cipherstash/eql` | Encrypt Query Language — the PostgreSQL SQL bundle (`eql_v3` schema: domains, operators, index-term extractors) that stores and queries encrypted payloads, plus its generated TypeScript types. Applied by `stash eql install` and by the Prisma Next adapter's migrations. Released in lockstep with the `eql-bindings` Rust crate, which emits the payloads this SQL reads |

Expand All @@ -38,6 +38,11 @@ from the stack-* crates), released by `.github/workflows/release-golang.yml`
as `languages/golang/vX.Y.Z` tags. All of these are in scope
for security reports on the same terms as the npm packages above.

One more Rust crate, **`stack-kms-vendor`**
(`packages/stack-kms-vendor`), is prepared for
crates.io but not yet published. It is in scope on the same terms, and
under the same licence as the stack-* crates below.

The five stack-* crates are licensed under the PolyForm Internal Use License
1.0.0 (each crate's `LICENSE`), not the repository's MIT licence: they may be
used for internal business operations only, and not redistributed. That
Expand Down
6 changes: 6 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
includes = [
"packages/stack-auth/tasks.toml",
"packages/stack-kms/tasks.toml",
"packages/stack-kms-vendor/tasks.toml",
"packages/stack-profile/tasks.toml",
"packages/stack-encrypt/tasks.toml",
"packages/stack-guest-abi/tasks.toml",
Expand Down Expand Up @@ -119,6 +120,11 @@ check() {
check stack-auth --no-default-features
check stack-kms --no-default-features
check stack-encrypt --no-default-features
# The key vendor seam (ADR-0014): no transport of its own, so it must build for WASI as is. Its
# `http` feature is the native transport, gated to non-wasm32 targets, so it
# must add nothing here either.
check stack-kms-vendor
check stack-kms-vendor --features http
# The Go binding's credential guest (ADR-0005) compiles stack-profile for
# WASI: the hostname and the process id are native-only there.
check stack-profile
Expand Down
76 changes: 76 additions & 0 deletions packages/stack-kms-vendor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
[package]
name = "stack-kms-vendor"
description = "Key vendor backends for Stack Encrypt: keyset records, derived data keys, and the static key backend"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
categories.workspace = true
license-file = "LICENSE"
# Publishable, in the `stack-kms-vendor` version group with every vendor crate
# (stack-kms-aws, ...). release-plz.toml keeps `release = false` until the
# first version is on crates.io: crates.io Trusted Publishing needs an
# existing crate, so the first version is published by hand (see
# docs/plans/2026-11-05-key-vendor-backends-decisions.md, CIP-5307).

[features]
# `stack_kms_vendor::contract` (the shared contract suite every key vendor
# backend runs), `FakeVendor` (an in-memory key vendor backend) and
# `stack_kms_vendor::replay` (the recorded-fixture harness every vendor
# crate's tests use). The contract suite drives a real `StackCipher`, so this
# pulls stack-encrypt in; the fixtures are serde types, so it pulls serde in.
test-support = ["dep:stack-encrypt", "dep:serde", "stack-kms/test-support"]
# `HttpVendorTransport`: the native `VendorTransport`, over the reqwest client
# and the TLS configuration stack-kms uses for ZeroKMS. Native only: on wasm32
# the dependencies are not in the graph and the type does not exist, because
# the WASI guest sends through its `transport_send` host import instead.
http = ["dep:reqwest", "dep:stack-auth", "dep:tokio"]

[dependencies]
stack-kms = { path = "../stack-kms", version = "0.2.0", default-features = false }
# The contract suite only: it seals and opens values through a cipher.
stack-encrypt = { path = "../stack-encrypt", version = "0.3.0", default-features = false, optional = true }

vitaminc = { workspace = true, features = ["protected", "random"] }
aes-gcm = { version = "0.10.3", features = ["zeroize"] }
base64ct = { version = "1.7", features = ["alloc"] }
futures = "0.3.25"
hkdf = "0.13"
sha2 = "0.11"
subtle = "2.6"
miette = { workspace = true }
# The fixture types of `replay` only.
serde = { workspace = true, optional = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
uuid = { workspace = true }
web-time = { workspace = true }
zeroize = { workspace = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
reqwest = { workspace = true, optional = true }
# Only for `stack_auth::tls::client_config`: the platform verifier with the
# compromised-CA blocklist that every bundled reqwest client in the stack
# crates uses.
stack-auth = { workspace = true, optional = true, features = ["http"] }
# The backoff timer of `HttpVendorTransport::sleep`. reqwest already runs on
# tokio, so this adds no runtime.
tokio = { version = "1.47.1", optional = true, default-features = false, features = ["time"] }

[dev-dependencies]
stack-kms = { path = "../stack-kms", default-features = false, features = ["test-support"] }
stack-encrypt = { path = "../stack-encrypt", default-features = false }
tokio = { workspace = true, features = ["rt", "macros", "time", "net", "io-util"] }

[package.metadata.docs.rs]
all-features = true

[[test]]
name = "contract"
required-features = ["test-support"]

[[test]]
name = "router"
required-features = ["test-support"]
Loading
Loading