Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 11 additions & 8 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,13 @@ jobs:
# FROZEN_PUBLISHERS (an npm map), so the switch keys on the
# `@cipherstash/auth` entry, which that PR deleted when crates.io and npm
# trusted publishing moved here together. crates.io needs a Trusted
# Publishing entry for both crates naming cipherstash/stack + release-plz.yml.
# Publishing entry for every published crate naming cipherstash/stack +
# release-plz.yml. Trusted Publishing is configured on an existing crate, so
# a crate's first version (stack-kms, stack-encrypt, stack-encrypt-derive at
# 0.1.0) is published by hand; this job then finds it on crates.io and skips
# it.
Comment thread
coderdan marked this conversation as resolved.
Outdated
#
# Publish-only: nothing here opens a release PR, so a version bump of the two
# Publish-only: nothing here opens a release PR, so a version bump of these
# crates is a hand-made pull request. `release-plz update` cannot version
# this workspace yet: it walks back to the commit that matches crates.io
# 0.42.3, and the imported history has no root Cargo.toml there. A
Expand All @@ -209,29 +213,28 @@ jobs:
run: node scripts/eql-pipeline-armed.mjs crates

release-crates:
name: "Release stack-auth and stack-profile"
name: "Release the stack-* crates"
needs: [crates-armed]
if: needs.crates-armed.outputs.armed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write # the stack-auth-v<V> / stack-profile-v<V> tags and releases
contents: write # the <crate>-v<V> tags and releases
pull-requests: write # release-plz links PRs in release notes
id-token: write # crates.io Trusted Publishing
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

- name: Is either crate version unpublished?
# The EQL preflight's crates.io question, per crate of the version
# group. It decides whether the GPG import runs; an API failure reads
- name: Is any crate version unpublished?
# The EQL preflight's crates.io question, per published crate. It decides whether the GPG import runs; an API failure reads
# as "unpublished", which fails towards importing the key.
id: preflight
Comment thread
coderdan marked this conversation as resolved.
run: |
set -euo pipefail
publish_needed=false
for crate in stack-auth stack-profile; do
for crate in stack-auth stack-profile stack-kms stack-encrypt stack-encrypt-derive; do
version="$(grep -m1 '^version = ' "packages/${crate}/Cargo.toml" | cut -d'"' -f2)"
# crates.io 403s curl's default User-Agent.
published="$(curl -fsSL --retry 3 -H "User-Agent: cipherstash-stack-release (https://github.com/cipherstash/stack)" "https://crates.io/api/v1/crates/${crate}/versions" | jq -r '.versions[].num' | grep -Fx "$version" || true)"
Comment thread
coderdan marked this conversation as resolved.
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Every npm package except EQL lives under `languages/typescript/`: packages in `l
EQL issues in this repository, never in the historical
`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), and `stack-kms`, `stack-encrypt`, `stack-encrypt-derive` 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-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`, `stack-encrypt` and `stack-encrypt-derive` (published to crates.io from 0.1.0, a second 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".
- `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 (`stackencrypt`, `stackauth`, `internal`), a wazero host with no cgo. Its two WASI guests (`*/guest`) are detached Cargo workspaces built by `mise run wasm:guest:build` and `mise run wasm:auth-guest:build`; the `.wasm` files they embed are gitignored. There is no Go release process yet.
- `e2e/*`: Cross-package end-to-end tests (package managers, supply chain, Prisma example README)
Expand Down
25 changes: 15 additions & 10 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,23 @@ This repository also carries the source of the **`eql-bindings`** Rust crate
lockstep with `@cipherstash/eql`. It is in scope for security reports on the
same terms as the npm packages above.

It also carries the source of two Rust crates published to crates.io,
It also carries the source of five Rust crates published to crates.io,
**`stack-auth`** and **`stack-profile`** (`packages/stack-auth`,
`packages/stack-profile`), and of the **Go module** at `languages/golang`
(`stackencrypt` and `stackauth`, over WASI guests built from the stack-*
crates), which has no release yet. All three are in scope for security reports
on the same terms as the npm packages above.
`packages/stack-profile`) and **`stack-kms`**, **`stack-encrypt`** and
**`stack-encrypt-derive`** (`packages/stack-kms`, `packages/stack-encrypt`,
`packages/stack-encrypt-derive`), and of the **Go module** at
`languages/golang` (`stackencrypt` and `stackauth`, over WASI guests built
from the stack-* crates), which has no release yet. All of these are in scope
for security reports on the same terms as the npm packages above.

> **Note on publishing.** Every package in the table above, including all
> seven `@cipherstash/protect-ffi*` packages, all seven `@cipherstash/auth*`
> packages and `@cipherstash/eql`, is published from this repository by
> `.github/workflows/release.yml`; the `eql-bindings`, `stack-auth` and
> `stack-profile` crates are published from here by
> `.github/workflows/release-plz.yml`. EQL moved here at the Phase 5 cutover in
> `.github/workflows/release.yml`; the `eql-bindings`, `stack-auth`,
> `stack-profile`, `stack-kms`, `stack-encrypt` and `stack-encrypt-derive`
> crates are published from here by `.github/workflows/release-plz.yml` (the
> first version of each of the last three was published by hand, because
> crates.io configures Trusted Publishing on an existing crate). EQL moved here at the Phase 5 cutover in
> `docs/plans/2026-08-13-eql-monorepo-absorption.md`, and the auth packages and
> the two crates at the arming PR of the stack-* crates import. Releases made
> before those moves were built elsewhere: `@cipherstash/eql@3.0.5` and earlier
Expand Down Expand Up @@ -168,8 +172,9 @@ CI. See `skills/stash-supply-chain-security/SKILL.md` for the full guide.
The `release.yml` workflow publishes packages to npm using OIDC trusted
publishing (`id-token: write`). There is no long-lived `NPM_TOKEN` — the
workflow deliberately avoids one, and setting one would bypass trusted
publishing. `release-plz.yml` publishes the `eql-bindings` crate to crates.io
over the same token exchange, and likewise carries no `CARGO_REGISTRY_TOKEN`.
publishing. `release-plz.yml` publishes the `eql-bindings` crate and the stack-* crates to
crates.io over the same token exchange, and likewise carries no
`CARGO_REGISTRY_TOKEN`.
Both bind to a *workflow filename* at the registry, so renaming either file
silently invalidates its publisher configuration.

Expand Down
1 change: 0 additions & 1 deletion packages/stack-encrypt-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ categories.workspace = true
license-file = "LICENSE"
# Not yet released, like stack-encrypt (which is the only crate that should
# depend on this one: the macros are re-exported from there).
publish = false

[lib]
proc-macro = true
Expand Down
5 changes: 2 additions & 3 deletions packages/stack-encrypt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@ categories.workspace = true
license-file = "LICENSE"
Comment thread
coderdan marked this conversation as resolved.
# Not yet released: keep release-plz from picking this crate up (it processes
# any workspace crate whose Cargo.toml lacks `publish = false`).
Comment thread
coderdan marked this conversation as resolved.
Outdated
publish = false

[dependencies]
# `profile` (native only; stack-kms gates it off wasm32 itself) lets
# `StackCipher::new()` fall back to the client key `stash auth login` writes
# to the profile directory (`secretkey.json`) when CS_CLIENT_ID /
# CS_CLIENT_KEY are not set — the same place `AutoStrategy` finds the token.
stack-kms = { path = "../stack-kms", default-features = false, features = ["profile"] }
stack-kms = { path = "../stack-kms", version = "0.1.0", default-features = false, features = ["profile"] }
# `StackCipher::new()` builds a ZeroKMS client from the environment, so its
# return type names the auto-detected auth strategy. Only needed with `http`.
stack-auth = { workspace = true, optional = true }
# `#[derive(EncryptFrom)]` / `#[derive(DecryptInto)]`, re-exported from `target`.
stack-encrypt-derive = { path = "../stack-encrypt-derive" }
stack-encrypt-derive = { path = "../stack-encrypt-derive", version = "0.1.0" }

# The workspace vitaminc (0.5.0): one canonical context encoding shared by
# the AEAD and PRF derivations (`IntoContext`, cipherstash/vitaminc#339),
Expand Down
1 change: 0 additions & 1 deletion packages/stack-kms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ categories.workspace = true
license-file = "LICENSE"
# Not yet released: keep release-plz from picking this crate up (it processes
# any workspace crate whose Cargo.toml lacks `publish = false`).
publish = false

[features]
default = ["profile", "http"]
Expand Down
38 changes: 22 additions & 16 deletions release-plz.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,26 @@
# `manifest_path: Cargo.toml` and `config: release-plz.toml`. The EQL workspace
# has its own file at packages/eql/release-plz.toml.
#
# Two crates publish from here, `stack-auth` and `stack-profile`, in one
# version group of their own. In cipherstash-suite they shared a group with
# `cipherstash-client`, which stays there. Every other workspace member is
# `publish = false` in its Cargo.toml and is also listed below with
# `release = false`, so `release-plz update` does not bump its version or
# write its changelog either. Each also repeats `publish = false`: the
# workspace `publish = true` applies to every package that does not override
# it, and release-plz refuses to run when that contradicts the Cargo.toml.
# Five crates publish from here, in two version groups:
#
# Tags are release-plz's default, `stack-auth-v<semver>` and
# `stack-profile-v<semver>`. No workflow triggers on either.
# - `stack-auth` and `stack-profile` (the 0.43 line). In cipherstash-suite
# they shared a group with `cipherstash-client`, which stays there.
# - `stack-kms`, `stack-encrypt` and `stack-encrypt-derive` (from 0.1.0).
# `stack-encrypt` depends on the other two by path and version, and the
# derive emits code against `stack-encrypt`'s traits, so they move as one.
# `eql-bindings` depends on `stack-encrypt` (its optional `stack-encrypt`
# feature), so a crates.io release of `eql-bindings` needs the matching
# `stack-encrypt` published first.
#
# Every other workspace member is `publish = false` in its Cargo.toml and is
# also listed below with `release = false`, so `release-plz update` does not
# bump its version or write its changelog either. Each also repeats
# `publish = false`: the workspace `publish = true` applies to every package
# that does not override it, and release-plz refuses to run when that
# contradicts the Cargo.toml.
#
# Tags are release-plz's default, `<crate>-v<semver>`. No workflow triggers
# on any of them.

[workspace]
changelog_config = "cliff.toml"
Expand All @@ -34,18 +43,15 @@ version_group = "stack-auth"

[[package]]
name = "stack-kms"
release = false
publish = false
version_group = "stack-encrypt"

[[package]]
name = "stack-encrypt"
release = false
publish = false
version_group = "stack-encrypt"

[[package]]
name = "stack-encrypt-derive"
release = false
publish = false
version_group = "stack-encrypt"

[[package]]
name = "stack-guest-abi"
Expand Down
15 changes: 11 additions & 4 deletions scripts/__tests__/cargo-publish-opt-out.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,18 @@ const WORKSPACES = [
},
{
// The root workspace. The stack-* crates' release-plz step publishes
// exactly these two (the stack-kms, stack-encrypt, stack-encrypt-derive
// and stack-guest-abi names are unclaimed on crates.io, and publishing
// them is a separate decision).
// exactly these five: stack-auth and stack-profile, and stack-kms,
// stack-encrypt and stack-encrypt-derive (which `eql-bindings`'
// `stack-encrypt` feature depends on from crates.io). stack-guest-abi is
// the Go guests' internal ABI and stays unpublished.
root: '.',
publishable: new Set(['packages/stack-auth', 'packages/stack-profile']),
publishable: new Set([
'packages/stack-auth',
'packages/stack-profile',
'packages/stack-kms',
'packages/stack-encrypt',
'packages/stack-encrypt-derive',
]),
expects: 'packages/stack-auth',
},
// Single-package workspaces: `[workspace]` with no members, so the package
Expand Down
27 changes: 17 additions & 10 deletions scripts/__tests__/release-plz-root-config.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import { readWorkflow } from './lib/workflows.mjs'
*
* `publish = false` in a crate's manifest stops `release-plz release` from
* uploading it, but not `release-plz update` from bumping its version and
* writing its changelog. So the two published crates, `stack-auth` and
* `stack-profile`, share one `version_group`, and every other member carries
* `release = false`. A member added to the workspace without a line here would
* writing its changelog. So the published crates are each in a named
* `version_group` (`stack-auth` and `stack-profile` in one, `stack-kms`,
* `stack-encrypt` and `stack-encrypt-derive` in another), and every other
* member carries `release = false`. A member added to the workspace without a line here would
* be versioned on the next release-plz run.
*
* Each of those members also carries `publish = false`. The workspace table
Expand All @@ -28,7 +29,15 @@ import { readWorkflow } from './lib/workflows.mjs'

const WORKFLOW = '.github/workflows/release-plz.yml'
const CONFIG = 'release-plz.toml'
const PUBLISHED = ['stack-auth', 'stack-profile']
// Each published crate, and the version group it releases in.
const GROUPS = {
'stack-auth': 'stack-auth',
'stack-profile': 'stack-auth',
'stack-kms': 'stack-encrypt',
'stack-encrypt': 'stack-encrypt',
'stack-encrypt-derive': 'stack-encrypt',
}
const PUBLISHED = Object.keys(GROUPS).sort()

/** The root workspace's members, as `{ path, name, publish }`. */
function rootMembers() {
Expand Down Expand Up @@ -78,7 +87,7 @@ describe('root release-plz.toml', () => {
)
})

it('releases exactly the crates that may publish, in one version group', () => {
it('releases exactly the crates that may publish, each in its version group', () => {
const publishable = members
.filter((member) => member.publish)
.map((member) => member.name)
Expand All @@ -91,11 +100,9 @@ describe('root release-plz.toml', () => {
.sort()
expect(released).toEqual(PUBLISHED)

const groups = new Set(
PUBLISHED.map((name) => packages.get(name)?.versionGroup),
)
expect([...groups]).toHaveLength(1)
expect([...groups][0]).toBeTruthy()
for (const name of PUBLISHED) {
expect(packages.get(name)?.versionGroup, name).toBe(GROUPS[name])
}
})

it('opts every unpublished member out of publishing here too', () => {
Expand Down
Loading