Skip to content

feat(core): add encp sealed-box encryption primitive - #3093

Merged
TooTallNate merged 3 commits into
mainfrom
nate/encp-sealed-box
Jul 27, 2026
Merged

feat(core): add encp sealed-box encryption primitive#3093
TooTallNate merged 3 commits into
mainfrom
nate/encp-sealed-box

Conversation

@TooTallNate

@TooTallNate TooTallNate commented Jul 24, 2026

Copy link
Copy Markdown
Member

PR 1 of 7 in a stack that removes the ~350ms cross-deployment encryption-key lookup from every hot path.

Why

Cross-run writes — a hook resumption targeting another run, a child writing into a parent's forwarded stream — currently require the writer to hold the recipient's symmetric key. That grants decrypt capability the writer doesn't need, and obtaining it across a deployment boundary costs a run-key API round trip.

A sealed box fixes both: the writer needs only a public key, which isn't secret and can therefore travel on the run entity for free.

Key hierarchy

Both keys descend from the per-run key material K that World.getEncryptionKeyForRun() already returns, so key acquisition, the World interface, and the Vercel API are all untouched:

K  (32 bytes, per-run, = HKDF(VERCEL_DEPLOYMENT_KEY, "projectId|runId"))
├── AES-256 key = K used directly            → 'encr' envelopes (unchanged)
└── X25519 scalar = HKDF(K, versioned label) → 'encp' envelopes
    └── public key = basepoint · scalar          (published, not secret)

Construction

ECIES-style over the same primitives as HPKE base mode (DHKEM(X25519), HKDF-SHA256, AES-256-GCM), binding both public keys into the KDF info as HPKE's kem_context does — that's the part that prevents key-substitution/unknown-key-share attacks. The deviation from strict RFC 9180 framing is documented, and the HKDF labels are versioned so a conformant profile can be added later without touching existing payloads.

Wire format (encp prefix attached by the serialization layer in #3094):

[ephemeral public key (32)][nonce (12)][ciphertext + GCM tag (16)]

Nothing produces encp yet

This PR is the primitive only. The o11y layer is hardened defensively so sealed payloads render as ciphertext rather than throwing Unsupported serialization format, and hydrateDataWithKey skips the AES path for them (opening a sealed payload needs the private scalar, not the symmetric key).

Notes for reviewers

  • encapsulate/decapsulate are split out so stream writers can amortize the KEM across frames. The docs state the obligation that comes with that: keep random per-frame nonces and re-encapsulate per connection attempt. A long-lived content key plus counter nonces would repeat (key, nonce) after a reconnect or a durable replay — catastrophic for AES-GCM. Enforced in feat(core): route sealed envelopes through the serialization layer #3094.
  • Web Crypto cannot import a raw X25519 private key, so a derived scalar is wrapped in a fixed PKCS#8 prefix (RFC 8410) and its public half read from a JWK export. That's subtle enough that the test suite cross-validates the derived public key against node:crypto's native X25519.
  • Low-order public keys and wrong-length keys are rejected by the platform (OperationError on all-zero shared secret), giving us HPKE's contributory-behavior check for free — asserted in tests.
  • Optional AAD added to the AES helpers; passing no AAD is byte-identical to previous behavior.

30 new tests. Full core unit suite passes; workspace typecheck clean.

Copilot AI review requested due to automatic review settings July 24, 2026 20:56
@TooTallNate
TooTallNate requested review from a team and ijjk as code owners July 24, 2026 20:56
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jul 27, 2026 6:40pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jul 27, 2026 6:40pm
example-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-astro-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-express-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-fastify-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-hono-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-nestjs-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-nitro-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workbench-vite-workflow Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jul 27, 2026 6:40pm
workflow-swc-playground Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workflow-tarballs Ready Ready Preview, Comment Jul 27, 2026 6:40pm
workflow-web Ready Ready Preview, Comment Jul 27, 2026 6:40pm

@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 10850c2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
@workflow/core Minor
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
workflow Minor
@workflow/world-testing Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/nuxt Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 10850c2 · Mon, 27 Jul 2026 18:57:37 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1302 (+83%) 🔻 1351 🔴 (+31%) 🔻 1380 🔴 (+27%) 🔻 1394 🔴 (-1.8%) 30
TTFS stream 1219 (+42%) 🔻 1339 🔴 (+40%) 🔻 1349 🔴 (+39%) 🔻 1562 🔴 (+57%) 🔻 30
TTFS hook + stream 510 (-53%) 💚 1589 🔴 (+29%) 🔻 1642 🔴 (+30%) 🔻 2450 🔴 (+67%) 🔻 30
STSO 1020 steps (1-20) 175 (-3.3%) 271 🔴 (-1.8%) 489 🔴 (+61%) 🔻 494 🔴 (+25%) 🔻 19
STSO 1020 steps (101-120) 183 (-1.6%) 269 🔴 (+0.7%) 303 🔴 (+0.7%) 1137 🔴 (-53%) 💚 19
STSO 1020 steps (1001-1020) 462 (+3.4%) 549 🔴 (+6.4%) 614 🔴 (+14%) 799 🔴 (+22%) 🔻 19
WO 1020 steps 385142 (±0%) 385142 (±0%) 385142 (±0%) 385142 (±0%) 1
SL stream latency 93 (+21%) 🔻 179 🔴 (+44%) 🔻 209 🔴 (+45%) 🔻 366 🔴 (+44%) 🔻 30
SO stream overhead (text) 105 (+8.2%) 149 (-3.9%) 153 (-13%) 176 (-11%) 30
SO stream overhead (structured) 106 (+5.0%) 167 (-0.6%) 243 (+37%) 🔻 360 (-76%) 💚 30
📜 Previous results (3)

916cffb

Sat, 25 Jul 2026 09:10:27 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 213 (-70%) 💚 1322 🔴 (+27%) 🔻 1339 🔴 (+21%) 🔻 1456 🔴 (+26%) 🔻 30
TTFS stream 198 (-80%) 💚 1322 🔴 (+23%) 🔻 1362 🔴 (+18%) 🔻 1564 🔴 (+22%) 🔻 30
TTFS hook + stream 389 (-69%) 💚 1525 🔴 (+8.5%) 1545 🔴 (+6.6%) 1652 🔴 (-1.4%) 30
STSO 1020 steps (1-20) 181 (-9.0%) 297 🔴 (+7.6%) 489 🔴 (+42%) 🔻 525 🔴 (+36%) 🔻 19
STSO 1020 steps (101-120) 195 (-9.3%) 258 🔴 (-7.9%) 352 🔴 (+18%) 🔻 360 🔴 (-4.5%) 19
STSO 1020 steps (1001-1020) 471 (-3.7%) 585 🔴 (+4.5%) 606 🔴 (+1.7%) 677 🔴 (+12%) 19
WO 1020 steps 404095 (-2.0%) 404095 (-2.0%) 404095 (-2.0%) 404095 (-2.0%) 1
SL stream latency 94 (-5.1%) 168 🔴 (+28%) 🔻 194 🔴 (+31%) 🔻 669 🔴 (+222%) 🔻 30
SO stream overhead (text) 115 (-12%) 252 🔴 (+16%) 🔻 321 (+30%) 🔻 682 (+153%) 🔻 30
SO stream overhead (structured) 117 (-16%) 💚 194 (-12%) 253 (+1.6%) 372 (-47%) 💚 30

bf9a09c

Fri, 24 Jul 2026 23:23:29 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 440 (-39%) 💚 1322 🔴 (+28%) 🔻 1338 🔴 (+25%) 🔻 1677 🔴 (+33%) 🔻 30
STSO 1020 steps (1-20) 180 (+9.1%) 308 🔴 (+11%) 358 🔴 (-12%) 368 🔴 (-11%) 19
STSO 1020 steps (101-120) 179 (+3.5%) 297 🔴 (+26%) 🔻 300 🔴 (+14%) 351 🔴 (-14%) 19
STSO 1020 steps (1001-1020) 486 (+5.7%) 550 🔴 (+4.4%) 664 🔴 (+18%) 🔻 666 🔴 (+18%) 🔻 19
WO 1020 steps 394683 (+1.1%) 394683 (+1.1%) 394683 (+1.1%) 394683 (+1.1%) 1
SL stream latency 95 (+19%) 🔻 174 🔴 (+58%) 🔻 198 🔴 (+43%) 🔻 316 🔴 (-8.4%) 30
SO stream overhead (text) 111 (+1.8%) 150 (-4.5%) 198 (+5.3%) 246 (-7.5%) 30
SO stream overhead (structured) 113 (+24%) 🔻 176 (+12%) 219 (+30%) 🔻 233 (-63%) 💚 30

db8e937

Fri, 24 Jul 2026 21:18:46 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 419 (-42%) 💚 1170 🔴 (+12%) 1261 🔴 (+18%) 🔻 1421 🔴 (-1.3%) 30
TTFS stream 1108 (+16%) 🔻 1153 🔴 (+13%) 1167 🔴 (+13%) 1384 🔴 (+32%) 🔻 30
TTFS hook + stream 1451 (+22%) 🔻 1521 🔴 (+15%) 🔻 1632 🔴 (+18%) 🔻 2153 🔴 (+29%) 🔻 30
STSO 1020 steps (1-20) 176 (+8.6%) 316 🔴 (+27%) 🔻 341 🔴 (+13%) 694 🔴 (+128%) 🔻 19
STSO 1020 steps (101-120) 165 (-11%) 239 🔴 (-14%) 302 🔴 (-7.4%) 402 🔴 (+13%) 19
STSO 1020 steps (1001-1020) 456 (±0%) 521 🔴 (-6.0%) 550 🔴 (-8.0%) 650 🔴 (-21%) 💚 19
WO 1020 steps 384982 (+0.7%) 384982 (+0.7%) 384982 (+0.7%) 384982 (+0.7%) 1
SL stream latency 81 (+8.0%) 153 🔴 (+26%) 🔻 163 🔴 (+18%) 🔻 201 🔴 (-76%) 💚 30
SO stream overhead (structured) 101 (+3.1%) 142 (-17%) 💚 159 (-36%) 💚 281 (±0%) 30
ℹ️ Metric definitions & methodology

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

Scenarios — step: one trivial no-op step, no stream; no hooks, so the run stays in turbo mode (in-process fast path) · stream: one streaming step; no hooks, so the run stays in turbo mode (in-process fast path) · hook + stream: registers a hook before one step, which exits turbo mode (dispatch path) · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges, and WO is the whole-run overhead outside step bodies · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000 · STSO (1-20) 20/30/60 · STSO (101-120) 30/45/90 · STSO (1001-1020) 40/60/120

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
Passed Failed Skipped Total
✅ ▲ Vercel Production 1455 0 239 1694
✅ 💻 Local Development 1621 0 227 1848
✅ 📦 Local Production 1621 0 227 1848
✅ 🐘 Local Postgres 1621 0 227 1848
✅ 🪟 Windows 154 0 0 154
✅ 📋 Other 1020 0 212 1232
✅ vercel-multi-region 27 0 0 27
Total 7519 0 1132 8651
Details by Category

✅ ▲ Vercel Production

App Passed Failed Skipped
✅ astro 126 0 28
✅ example 126 0 28
✅ express 126 0 28
✅ fastify 126 0 28
✅ hono 126 0 28
✅ nextjs-turbopack 151 0 3
✅ nextjs-webpack 151 0 3
✅ nitro 126 0 28
✅ nuxt 126 0 28
✅ sveltekit 145 0 9
✅ vite 126 0 28

✅ 💻 Local Development

App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-canary 135 0 19
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26

✅ 📦 Local Production

App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-canary 135 0 19
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26

✅ 🐘 Local Postgres

App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-canary 135 0 19
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26

✅ 🪟 Windows

App Passed Failed Skipped
✅ nextjs-turbopack 154 0 0

✅ 📋 Other

App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 128 0 26
✅ e2e-local-dev-tanstack-start- 128 0 26
✅ e2e-local-postgres-nest-stable 128 0 26
✅ e2e-local-postgres-tanstack-start- 128 0 26
✅ e2e-local-prod-nest-stable 128 0 26
✅ e2e-local-prod-tanstack-start- 128 0 26
✅ e2e-vercel-prod-nest 126 0 28
✅ e2e-vercel-prod-tanstack-start 126 0 28

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

📋 View full workflow run

Copilot AI 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.

Pull request overview

Adds a new encp (“sealed”) serialization format and introduces a sealed-box encryption primitive in @workflow/core to enable cross-run writes that only require the recipient run’s public key (avoiding symmetric key distribution and cross-deployment key lookups).

Changes:

  • Introduces sealed-box (X25519 + HKDF-SHA256 + AES-256-GCM) primitives: derive per-run keypairs, seal/open payloads, and support amortized KEM for streaming.
  • Extends serialization-format helpers to recognize encp as ciphertext (UI-safe), and ensures o11y hydration won’t attempt AES-GCM decrypt on sealed payloads.
  • Adds optional AES-GCM AAD support in the shared encryption helpers, plus unit tests and a capabilities table entry.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/core/src/serialization/types.ts Adds SEALED: 'encp' format prefix with inline documentation.
packages/core/src/serialization-format.ts Treats encp as ciphertext for display; adds isSealedData; avoids AES decrypt attempts for sealed payloads in hydrateDataWithKey.
packages/core/src/serialization-format.test.ts Adds regression tests for encp detection and hydration pass-through behavior.
packages/core/src/sealed-box.ts New sealed-box primitive (derive run keypair, encapsulate/decapsulate, seal/open, run-bound AAD helper).
packages/core/src/sealed-box.test.ts New test suite validating correctness, tamper detection, AAD behavior, and cross-implementation checks.
packages/core/src/encryption.ts Adds optional AAD to AES-GCM encrypt/decrypt helpers.
packages/core/src/capabilities.ts Adds encp to the run capabilities format version table and history notes.
.changeset/encp-sealed-box.md Changeset for @workflow/core minor release introducing encp primitive.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/sealed-box.ts Outdated
Comment thread packages/core/src/sealed-box.ts
Comment thread packages/core/src/sealed-box.ts Outdated

@vercel vercel Bot 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.

Additional Suggestion:

Accidentally committed quickjs-assets.generated.ts imports quickjs-wasi, which is not a dependency of @workflow/core, causing tsc to fail with TS2307 and breaking every deployment.

Fix on Vercel

Cross-run writes (hook resumptions targeting another run, forwarded
writable stream frames) currently require the writer to hold the
recipient run's symmetric key, which also grants decrypt capability and
costs a ~350ms `run-key` API round trip across a deployment boundary.

Add the crypto foundation for sealing those writes to a public key
instead. Both keys descend from the per-run key material `K` that
`World.getEncryptionKeyForRun()` already returns, so key acquisition, the
World interface, and the Vercel API are all untouched:

    K
    ├── AES-256 key = K used directly        → 'encr' (unchanged)
    └── X25519 scalar = HKDF(K, label)       → 'encp'
        └── public key (published, not secret)

`sealed-box.ts` implements an ECIES-style construction over the same
primitives as HPKE base mode (DHKEM(X25519, HKDF-SHA256), AES-256-GCM),
binding both public keys into the KDF `info` as HPKE's `kem_context` does
to prevent key-substitution attacks. The deviation from strict RFC 9180
framing is documented, and the HKDF labels are versioned so a conformant
profile can be added later without touching existing payloads.

Nothing produces `encp` payloads yet — this is the primitive only. The
o11y layer is hardened defensively so sealed payloads render as
ciphertext rather than throwing `Unsupported serialization format`, and
`hydrateDataWithKey` skips the AES path for them since opening a sealed
payload needs the private scalar rather than the symmetric key.

- optional AAD on the AES helpers, used to bind `projectId|runId`
- `encapsulate`/`decapsulate` split so stream writers can amortize the
  KEM across frames; documented that they must keep random per-frame
  nonces and re-encapsulate per connection attempt, since a long-lived
  content key plus counter nonces would repeat `(key, nonce)` after a
  reconnect or a durable replay
- public key derivation is cross-validated against node:crypto's native
  X25519 in tests, since it reads the public half out of a JWK export
Addresses review feedback on the sealed-box primitive:

- The module doc pointed at `getSerializeStream` as enforcing the
  re-encapsulate-per-writer rule, but nothing in-tree uses `encapsulate`
  yet, and the stream path added later seals per frame instead. Reworded
  to state the two rules as the caller's contract, since this module
  enforces neither.
- `derivePublicKeyFromScalar` now asserts the JWK-derived public key is
  32 bytes. That decode is the one place this module trusts an external
  encoding; a short value would otherwise fail much later inside key
  agreement with a far less obvious message.
- `open()` used bare 12/16 for the nonce and tag sizes. Those now come
  from exported `NONCE_LENGTH`/`TAG_BYTES` in the AES layer, so the wire
  format check cannot drift from the implementation.

@karthikscale3 karthikscale3 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.

ai review: The sealed-box primitive is well scoped and thoroughly tested. I verified the X25519/HKDF/AES-GCM construction, AAD binding, malformed-key and tamper handling, observability fallback, and the additive legacy AES helper change. The three existing inline findings are fixed on this head and their threads are resolved. The broad Vercel production E2E failures do not appear specific to this change; the focused unit, module-boundary, local framework, and multi-region checks pass.

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 4ba223a (AI decision).

This is new feature work: it adds an encp sealed-box encryption primitive (new sealed-box.ts module, new SEALED serialization format, new isSealedData export, optional AAD parameters on the AES helpers) as PR 1 of 7 in a stack that removes a cross-deployment key lookup. The commit explicitly ships with a minor changeset and nothing yet produces encp payloads, so there is no user-visible defect on stable being fixed. The defensive o11y hardening (not attempting AES decrypt on sealed payloads) only matters once later PRs in the stack start writing encp, which will never happen on stable.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

4ba223a01c56126aec5c982d3583f779ed96e8ca

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.

3 participants