Skip to content

chore(release): prepare 2.2.5 - #494

Merged
tobyhede merged 10 commits into
release/2.2from
claude/project-thread-yj11ps
Nov 2, 2026
Merged

tobyhede merged 10 commits into
release/2.2from
claude/project-thread-yj11ps

Conversation

@tobyhede

@tobyhede tobyhede commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Toby

Prepare-release PR for 2.2.5, the next 2.2.x maintenance release, cut from release/2.2.

Changes

  • Bump the workspace version to 2.2.5 (Cargo.toml, Cargo.lock).
  • Move the [Unreleased] fixes into a ## [2.2.5] CHANGELOG section, with its compare link, so the release workflow's verify-release check passes for tag v2.2.5.
  • Set flavor: latest=false on both docker/metadata-action steps in release.yml. The default latest=auto would tag any non-prerelease semver release as cipherstash/proxy:latest, which must stay on 3.x.
  • Backport security dependency updates (lockfile only): rustls 0.23.45, rustls-webpki 0.103.14, aws-lc-rs 1.18.2 / aws-lc-sys 0.46.0, rand 0.9.3 / 0.10.2, quinn-proto 0.11.15, hickory-proto / hickory-resolver 0.26.3 via reqwest 0.13.4, diesel 2.3.12 and postgres-protocol 0.6.12, with a ### Security CHANGELOG entry. tokio-postgres stays on 0.7.13 (0.7.18 conflicts through cipherstash-client); that advisory is left for 2.2.6.
  • Remove the failing Multitudes notification step from release.yml, as ci: remove the Multitudes deployment notification #477 did on main.
  • Document how 2.2.x releases are cut from release/2.2 in DEVELOPMENT.md.

Included in 2.2.5

The ## [2.2.5] date is set to today; I'll move it to the tag date if tagging happens later.

Cutting the release after merge

mise run release only runs on main, so tag manually from the merged commit on release/2.2:

git tag v2.2.5 <merged sha>
git push origin v2.2.5
gh release create v2.2.5 --target release/2.2 --latest=false \
  --generate-notes --notes-start-tag v2.2.4

Acknowledgment

By submitting this pull request, I confirm that CipherStash can use, modify, copy, and redistribute this contribution, under the terms of CipherStash's choice.


Generated by Claude Code

@tobyhede tobyhede self-assigned this Oct 26, 2026
@tobyhede
tobyhede force-pushed the claude/project-thread-yj11ps branch from 741e9b5 to d4456f4 Compare October 26, 2026 23:51
@tobyhede
tobyhede requested a review from freshtonic October 27, 2026 00:48

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head f7df5137 against release/2.2 (5f99d7e0, which includes #493 and #495). Approved. The version bump, the CHANGELOG section and the latest=false change are correct, and the dependency updates remove 10 of the 29 advisories that cargo audit reports on the base.

I pushed the fixes for S1 and S2 to this branch as two signed commits:

  • e66691ac fix(deps): backport rustls 0.23.45 to 2.2. This is a 2-line change to Cargo.lock. The CHANGELOG entry now names rustls and aws-lc-rs/aws-lc-sys, and it no longer says that 2.2.5 has every fix that 3.x ships. cargo build --workspace --all-targets --locked passes, and cargo test -p cipherstash-proxy -p eql-mapper --lib passes (283 and 79). RUSTSEC-2026-0285 is gone. The rustls advisories that remain are the same as on main.
  • 103e7a86 ci: remove the Multitudes deployment notification (2.2). This is the same change as #477 on main.

This approval covers my two commits too. Please check them before you merge.

Fixed in this PR (S1 and S2)

S1. The rustls fix that main ships is not in this backport. release/2.2 locks rustls 0.23.28, which has RUSTSEC-2026-0285 ("TLS 1.3 handshake messages incorrectly accepted across encryption levels", network, no authentication). main locks 0.23.45, which fixes it. rustls is Proxy's TLS server for clients and its TLS client to PostgreSQL, so this is the most relevant advisory that remains. cargo update -p rustls --precise 0.23.45 changes only 2 lines in Cargo.lock. I built it, and the unit tests pass.

The CHANGELOG says that the update picks up "the fixes for published security advisories in those crates that Proxy 3.x already ships". That is true for the crates it names. But a reader can think that 2.2.5 has every fix that 3.x has, and it does not. Add rustls, or say which advisories stay open.

S2. The release run for v2.2.5 will end red. release.yml on 2.2 still has the "Notify Multitudes" step. #477 removed this step from main, because the endpoint has returned 500 on every run since 2026-08-17. The images are pushed before that step, so the release is published, but the run shows as failed, and the failure hides a real failure. Remove the step here too, as #477 did.

Notes (not blocking)

  • postgres-protocol 0.6.8 has RUSTSEC-2026-0179 (a malicious server can send an unbounded SCRAM iteration count) and RUSTSEC-2026-0180. main does not have these. cargo update -p postgres-protocol --precise 0.6.12 resolves on 2.2, and it builds and passes the unit tests. It adds md-5 0.11 and sha2 0.11 beside the existing versions. The server is the operator's own PostgreSQL, so the risk is low. You can add it with S1 or later.
  • tokio-postgres 0.7.13 has RUSTSEC-2026-0178 (panic on a short DataRow). main has 0.7.18 (#465), but on 2.2, --precise 0.7.18 fails on a libredox conflict through cipherstash-client in the integration package. It is not worth blocking the release for this. A ticket for 2.2.6 is enough.
  • The lockfile update goes further than the CHANGELOG says. aws-lc-sys moves from 0.38.0 to 0.46.0, which is newer than main (0.44.0), and aws-lc-rs from 1.16.1 to 1.18.2. This fixes RUSTSEC-2026-0044 and RUSTSEC-2026-0048, so it is good. But it is a large update of the C crypto library, and it is not in the CHANGELOG. Test CI builds only x86_64. The release also builds aarch64-unknown-linux-gnu, and that build has not run on this lockfile. Before you tag, start the Release workflow on this branch with workflow_dispatch. The merge job is a dry run there, but the build job pushes untagged digests.
  • The advisories that remain and that main also has (quinn-proto, h2, crossbeam-epoch, rkyv, the other rustls ones, rustls-webpki 0.103.14, webpki-root-certs) are not a 2.2 problem.

Nits

  • The CHANGELOG date is 2026-10-26. Update it if the tag goes out on another day.

What I checked and found correct

  • Version. The workspace version is 2.2.5, and cipherstash-proxy, eql-mapper-macros and showcase are 2.2.5 in Cargo.lock. verify-release reads the first version = line of Cargo.toml and looks for ## [2.2.5] in the CHANGELOG. Both match.
  • latest=false. A release event runs the workflow file from the tag, so the 2.2 release.yml is used. The merge step makes :2.2.5 ({{version}}) and :2.2 ({{major}}.{{minor}}), and not :latest. On the build step the flavor changes only labels, so it does no harm. GitHub's "Latest" is v3.0.1 now, and --latest=false in DEVELOPMENT.md keeps it there.
  • The CHANGELOG. The 2.2.5 section has #473, #475, #481, #483 and #493. It includes the sentence about plaintext predicates that I asked for in my review of #493. #491 and #495 add tests only.
  • Cargo.toml. No dependency requirement changed. The updates are in the lockfile only.
  • Signatures. All 3 commits are verified on GitHub.

What I ran

  • cargo audit on the base, this PR and main: 29, 19 and 17 advisories.
  • cargo update -p rustls --precise 0.23.45 and -p postgres-protocol --precise 0.6.12, then cargo build and cargo test -p cipherstash-proxy --lib: 282 of 283 passed. The failure, config::tandem::tests::prometheus_config, is the known environment race. I reverted the lockfile after the run.
  • CI: all 4 checks pass.

Copy link
Copy Markdown
Contributor Author

Thanks @freshtonic. I checked your two commits and they're fine. I pushed one more signed commit, 07ffc15: postgres-protocol goes to 0.6.12 for RUSTSEC-2026-0179/0180, with the CHANGELOG entry updated. It's lockfile only. It builds with --locked, and the proxy and mapper unit tests pass (283 and 79). tokio-postgres stays for 2.2.6. I'll do the aarch64 workflow_dispatch run and the date update before tagging.


Generated by Claude Code

@tobyhede
tobyhede force-pushed the claude/project-thread-yj11ps branch 2 times, most recently from 303f809 to 49c7057 Compare October 27, 2026 23:26

Copy link
Copy Markdown
Contributor Author

All four Test jobs fail on 49c7057. Unit tests pass, but in the integration stage the proxy container never accepts connections on localhost:6432 ("Unable to connect to localhost:6432 after 20 attempts"). 303f809, which has the same tree as 07ffc15, was green, so the cause is in the 7 backport commits on top of it. The most likely one is the dependency update e8af865 (tokio-postgres 0.7.18, rustls 0.23.46, tokio 1.51.6, socket2 all). The job output doesn't include the container's logs, so docker logs proxy after mise run proxy:up should show the startup error. I'm not re-running: it fails the same way on all four, so it isn't a flake.


Generated by Claude Code

@tobyhede
tobyhede force-pushed the claude/project-thread-yj11ps branch 5 times, most recently from 316746a to d293443 Compare November 2, 2026 00:22
freshtonic and others added 10 commits November 2, 2026 12:08
The Multitudes endpoint has returned 500 on every release run since
2026-08-17, so the v2.2.5 release run would end red after the images are
published. main removed this step in #477.
Bind rewrites pair encrypted values with parameters by position. ZeroKms::encrypt padded a short result from encrypt_eql with None, and Bind skips a None value, so the parameter's original plaintext was forwarded to PostgreSQL.

ZeroKms::encrypt and Context::encrypt now fail with EncryptionResultCountMismatch when they receive fewer ciphertexts than values sent for encryption.
Proxy keeps a Statement only for statements that touch encrypted columns. A Bind naming a statement Proxy had no record of was treated as plaintext and its values sent as they were, so any path that lost or dropped a statement too early would send plaintext for an encrypted column.

Every Parse already records the statement's session, so while mapping is enabled a Bind to a name with no recorded Parse now fails closed with SQLSTATE 26000, PostgreSQL's own code for a missing prepared statement. The refusal goes through PostgreSQL as other refusals do, and the connection stays usable.

A Parse that Proxy refuses is forgotten, as main's refuse_parse does, since PostgreSQL never prepares it. Otherwise the recorded session would let a later Bind to it through with its values as written.

Backport of main's 292ebff, with the refused-Parse handling from main's refuse_parse (context/lifecycle.rs), which is part of main's protocol lifecycle rewrite and does not cherry-pick onto 2.2.
The mapper never inferred the DO UPDATE SET assignments of an INSERT ... ON CONFLICT, so a literal or param assigned to an encrypted column on the conflict path was left untyped and sent to PostgreSQL as plaintext. Because the statement still type-checked, the refusal of writes Proxy cannot type check did not catch it.

Each assignment value is now unified with the type stored in its target column, as in a plain UPDATE, so it is encrypted. The insert target is named so qualified references resolve. A conflict target on an encrypted column, MySQL INSERT ... SET and ON DUPLICATE KEY UPDATE are rejected.

The `excluded` pseudo-table is in scope only inside DO UPDATE, as in PostgreSQL, so `excluded.<col>` carries the column's EQL type there while RETURNING and the INSERT source see only the target table. An upsert ending in RETURNING id therefore type checks, and RETURNING * projects only the target table. A target table that is itself named `excluded` is shadowed for the clause and restored after it.

This is the ON CONFLICT part of the mapper fix on main (39d03e5), with main's excluded scoping from 8b6ce3b and 10ce4c3. Its window, aggregate and SELECT ... INTO changes are left out: those gaps act on ciphertext rather than forwarding plaintext. release/2.2 lacked the on_conflict_is_update helper and ImportError::TraversalInvariant (from main's 5ff506b), which are added as main has them.

Co-authored-by: James Sadler <james@cipherstash.com>
A COPY ... FROM is not type checked, so a prepared one recorded no settings, and Proxy checks it against the schema only at Parse, where the mapping-disabled path skips the check. A client could prepare COPY users (email) FROM STDIN with mapping disabled, turn mapping back on, then Bind and Execute it, and its copy data reached the encrypted column as plaintext.

A prepared COPY ... FROM now records the settings it was prepared under, as a type-checked statement does, so it is refused with SQLSTATE 26000 once mapping has been turned on or off since. This adds main's copies_into_table and requires_schema (from e4cad15), which release/2.2's COPY backport (cab9b97) left out.
Addresses GHSA-3gjw-f78c-vvpw (affected >= 0.4.0, < 0.7.18). Every package now requires 0.7.18, so a lockfile refresh cannot resolve an affected version.

tokio-postgres 0.7.15 and later render an Error as only its kind, "db error", with the server's message reached through source(). Two things on 2.2 depended on the old text:

- Proxy detected a missing EQL configuration table by matching the rendered text, so the check never matched and Proxy exited at startup against a database without EQL. It now checks for SQLSTATE 42P01 (undefined_table) naming eql_v2_configuration. main has no such check, as it infers the Encrypt configuration from the EQL v3 schema (5b4e85c).
- Integration tests asserted on err.to_string(). They now assert on the structured error: assert_db_error reads severity and message off as_db_error(), assert_client_error reads the kind off Display and the detail off source(). The customer-visible message text stays pinned.

tokio-postgres 0.7.18 also moved from socket2 0.5 to 0.6. Proxy relied on it enabling socket2 0.5's "all" feature for TcpKeepalive::with_retries and set_tcp_user_timeout, so Proxy moves to socket2 0.6 as main has, sharing tokio-postgres's copy with "all" enabled. No source changes are needed.

Backport of main's 6e3c1e2, 9461b8c and 9eaa4bc, and the tokio-postgres requirement from ee77169. Two 2.2-only integration tests are switched to structured assertions too.
tokio-postgres 0.7.18 follows the convention that an error's Display
describes only that error, with the cause reached through source(). The
previous fix worked against that by re-embedding the cause in
ConfigError::Database's Display, and it only covered errors converted into
that variant: sites logging a raw tokio_postgres::Error still dropped the
server's message.

Add ErrorChain, a Display wrapper that walks source(), and use it at every
log site that can carry a database error. ConfigError::Database is
transparent again. Recording the error as a dyn Error field is not enough:
the Structured (JSON) format, the default off a terminal, renders only
Display.

ErrorChain skips a cause the message already ends with, so variants that
embed their cause in Display are not printed twice.
On 2.2, ErrorChain is also applied to 2.2's own schema load and reload log sites in proxy/schema/manager.rs and the encrypt config load and reload log sites in proxy/encrypt_config/manager.rs, which main replaced with its coalesced schema reload.

Backport of main's b31fbb9.
Explain the clippy::double_must_use allowance on the async_trait EncryptionService trait, whose generated methods add must_use to a Future that is already must_use, and drop a redundant bool cast in an integration test.

Run the integration test tasks from tests/, as main does.

Backport of main's ee77169, apart from its tokio-postgres requirement, which is in the tokio-postgres update. Its AGENTS.md and burn-in package changes do not apply to 2.2, where those files do not exist.
Bring the security patches that main already carries to release/2.2:

- rustls 0.23.46 (RUSTSEC-2026-0285, 0346, 0347, 0348 and 0349)
- rustls-webpki 0.103.16 (RUSTSEC-2026-0382)
- webpki-root-certs 1.0.11 (RUSTSEC-2026-0383)
- aws-lc-rs 1.18.2, with aws-lc-sys 0.46.0
- tokio 1.51.6 (RUSTSEC-2026-0386)
- h2 0.4.20 (RUSTSEC-2026-0258)
- crossbeam-epoch 0.9.21 (RUSTSEC-2026-0204)
- rand 0.9.3 and 0.10.2
- quinn-proto 0.11.15
- hickory-proto and hickory-resolver 0.26.3, via reqwest 0.13.4
- diesel 2.3.12
- chacha20 0.10.2, replacing the yanked 0.10.0

Lockfile only; no code changes were needed. tokio-postgres 0.7.18 and postgres-protocol 0.6.12 come with the tokio-postgres update.

Co-authored-by: James Sadler <james@cipherstash.com>
Set `latest=false` on the Docker metadata steps so that publishing a 2.2.x release does not move the `cipherstash/proxy:latest` tag off 3.x.

Document how 2.2.x maintenance releases are cut from release/2.2: the manual tag steps, `--latest=false`, and `--notes-start-tag` so the generated notes start from the previous 2.2.x tag.

Date the 2.2.5 CHANGELOG section for the re-cut release.
@tobyhede
tobyhede force-pushed the claude/project-thread-yj11ps branch from 931f191 to 135c625 Compare November 2, 2026 01:35
@tobyhede
tobyhede merged commit 43eb71f into release/2.2 Nov 2, 2026
4 checks passed
@tobyhede
tobyhede deleted the claude/project-thread-yj11ps branch November 2, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants