Skip to content
Merged
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
415 changes: 415 additions & 0 deletions .github/workflows/release-golang.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Every npm package except EQL lives under `languages/typescript/`: packages in `l
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".
- `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. There is no Go release process yet.
- `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), and a final version dispatched from anywhere but `main`; a pre-release may come from a branch. 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)
- `languages/typescript/examples/*`: Working apps (basic, prisma, supabase-worker)
- `docs/plans/*`: Internal design plans. User-facing documentation lives at https://cipherstash.com/docs (not in this repo).
Expand Down
14 changes: 9 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ It also carries the source of five Rust crates published to crates.io,
**`stack-encrypt-derive`** (`packages/stack-kms`, `packages/stack-encrypt`,
`packages/stack-encrypt-derive`), and of the **Go module** at
`languages/golang` (`encrypt` and `auth`, over WASI guests built
from the stack-* crates), which has no release yet. All of these are in scope
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.

The five stack-* crates are licensed under the PolyForm Internal Use License
Expand All @@ -57,7 +58,10 @@ production service.
> `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
> crates.io configures Trusted Publishing on an existing crate). The Go module
> is released from here by `.github/workflows/release-golang.yml`, as
> `languages/golang/vX.Y.Z` tags that `go get` fetches through the Go module
> proxy. 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 @@ -222,13 +226,13 @@ We mitigate this by:

- Explicitly disabling all caching in every workflow that publishes an artefact
— `release.yml`, the reusables it calls (`_build-ffi-artifacts.yml`,
`_build-eql-sql.yml`, `_build-eql-docs.yml`), `release-plz.yml` and
`release-postgres-eql-image.yml`
`_build-eql-sql.yml`, `_build-eql-docs.yml`), `release-plz.yml`,
`release-postgres-eql-image.yml` and `release-golang.yml`
- Automated checks for disabled caching on high-risk workflows
(`scripts/lint-no-workflow-caching.mjs`), working from an allowlist of audited
actions rather than a denylist of cache actions — so an action it has never
seen is a finding by default. The cost is accepted: four EQL release jobs
compile Rust with no cache restore.
and the Go release's build job compile Rust with no cache restore.

[1]: https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/

Expand Down
21 changes: 21 additions & 0 deletions languages/golang/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 CipherStash

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
20 changes: 18 additions & 2 deletions languages/golang/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,21 @@ mise run wasm:auth-guest:build # the guest, with its import-surface gate
mise run go:test # the whole Go module, both packages
```

The guest module is embedded from `wasm/` and not committed; without it,
`Open` returns `ErrGuestNotBuilt` and the tests skip.
The guest module is embedded from `wasm/`. A tagged version of the module,
as `go get` resolves it, carries it: the release workflow commits it to the
commit the tag names, so nothing needs building. A pseudo-version
(`go get ...@main`, or any commit rather than a tag) does not, and neither
does a checkout of this repository, where the guest is not committed and the
commands above build it. Without it, `Open` returns `ErrGuestNotBuilt` and
the tests skip.

## Licence

The Go code in this module is MIT-licensed ([`LICENSE`](../LICENSE)). The
credential guest it embeds is compiled from Rust crates that are not:
`stack-auth`, `stack-profile` and `stack-guest-abi` are licensed under the
[PolyForm Internal Use License 1.0.0](https://polyformproject.org/licenses/internal-use/1.0.0).
Those terms allow use for your internal business operations only. Each
crate's `LICENSE` in the
[CipherStash Stack repository](https://github.com/cipherstash/stack/tree/main/packages)
has the full text.
Comment thread
coderdan marked this conversation as resolved.
27 changes: 26 additions & 1 deletion languages/golang/encrypt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ The package reference is on [pkg.go.dev]; the generator's reference is
go get -tool github.com/cipherstash/stack/languages/golang/cmd/stashgen
```

This resolves to the latest tagged version, which carries the engine
builds the module embeds; see [Building](#building).

2. Put a `stash` tag on every exported field of the struct, and a `go:generate`
comment beside it.

Expand Down Expand Up @@ -287,9 +290,31 @@ column.
## Building

The package embeds `wasm/stack_encrypt_guest.wasm`, a build artefact of the
Rust crate in [`guest/`](guest/). It is not committed: run
Rust crate in [`guest/`](guest/), and package `encrypt/eql` embeds the EQL
build beside it. A tagged version of the module (such as `v0.1.0-alpha.1`, as
`go get` resolves it) carries both builds and the credential guest: the
release workflow commits them to the commit the tag names, so a module that
requires a tagged version needs no Rust toolchain and no build step. A
pseudo-version does not. `go get ...@main`, or any commit rather than a
tag, fetches a tree without the builds; it compiles, and `NewClient` returns
`ErrGuestNotBuilt`.

In a checkout of this repository the builds are not committed: run
`mise run wasm:guest:build` (and `mise run wasm:auth-guest:build` for the
credential guest) before `go test`, and
`mise run wasm:guest:build:deterministic` for the test build the hermetic
round-trip and fixture tests use. Without the builds `NewClient` returns
`ErrGuestNotBuilt` and the tests skip.

## Licence

The Go code in this module is MIT-licensed ([`LICENSE`](../LICENSE)). The
WASI guests it embeds are compiled from Rust crates that are not: the
stack-encrypt guest and its EQL build are compiled from `stack-encrypt`,
`stack-kms`, `stack-auth`, `stack-profile` (a dependency of `stack-auth`) and
`stack-guest-abi`, which are licensed under the
Comment thread
coderdan marked this conversation as resolved.
[PolyForm Internal Use License 1.0.0](https://polyformproject.org/licenses/internal-use/1.0.0).
Those terms allow use for your internal business operations only. Each
crate's `LICENSE` in the
[CipherStash Stack repository](https://github.com/cipherstash/stack/tree/main/packages)
has the full text.
1 change: 1 addition & 0 deletions scripts/__tests__/eql-suite-ci.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,7 @@ describe('every job that compiles Rust restores the shared cache', () => {
).toEqual([
'.github/workflows/_build-eql-docs.yml (publish-docs)',
'.github/workflows/_build-eql-sql.yml (build)',
'.github/workflows/release-golang.yml (build)',
'.github/workflows/release-postgres-eql-image.yml (build-sql)',
'.github/workflows/release.yml (prerelease-eql-npm)',
])
Expand Down
Loading
Loading