Skip to content

Fix batched encrypted event records reuse per-record randomization metadata - #7124

Merged
tim-smart merged 4 commits into
mainfrom
audit/repro-b206fa5d76-eventlog-aes-gcm-iv-reuse
Aug 8, 2026
Merged

Fix batched encrypted event records reuse per-record randomization metadata#7124
tim-smart merged 4 commits into
mainfrom
audit/repro-b206fa5d76-eventlog-aes-gcm-iv-reuse

Conversation

@fubhy

@fubhy fubhy commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

encrypt generates one 12-byte IV per batch and passes that same IV and key to every subtle.encrypt call in the batch. EventLogRemote and encrypted servers carry/copy that one IV to every entry.

Important

This PR starts with focused failing reproduction tests. Add the implementation fix to this same branch; CI is expected to fail until that fix is included.

Batched encrypted event records reuse per-record randomization metadata

Module: packages/effect/src/unstable/eventlog/EventLogEncryption.ts
Audit ID: relsem-eventlog-record-randomization
Severity / confidence: high / high

What happens

encrypt generates one 12-byte IV per batch and passes that same IV and key to every subtle.encrypt call in the batch. EventLogRemote and encrypted servers carry/copy that one IV to every entry.

Why it happens

The wire/API shape returns one iv next to encryptedEntries, and encryption maps all encoded entries while closing over the single iv instead of generating a unique nonce per ciphertext.

Expected behavior

EventLogEncryption is the confidentiality/integrity service for encrypted event-log replication and uses AES-GCM under an identity-derived key.

Relevant implementation

These links and excerpts are pinned to audit base b206fa5d7655c1634c9993410a9203f6616a5ca2.

View problematic code at packages/effect/src/unstable/eventlog/EventLogEncryption.ts:1
/**

View exact lines on GitHub

Reproduction

pnpm test --run packages/effect/test/unstable/eventlog/EventLog.test.ts -t "uses a distinct AES-GCM IV for each entry"

Observed failure: Independently rerun; failed at the intended semantic assertion.

Implementation handoff

The initial reproduction tests on this branch are the regression specification for the implementation fix that should follow in this PR.

  1. Start with the pinned implementation excerpts and the Why it happens analysis above.
  2. Change the implementation so it satisfies the stated Expected behavior; do not weaken or remove the reproduction assertions.
  3. Run the focused reproduction command(s) and confirm the observed failures become passing tests:
pnpm test --run packages/effect/test/unstable/eventlog/EventLog.test.ts -t "uses a distinct AES-GCM IV for each entry"
  1. Run the affected package's existing tests, then the repository lint and type checks before requesting review.

Audit provenance

  • Audit base: b206fa5d7655c1634c9993410a9203f6616a5ca2
  • Reproduction base: b206fa5d7655c1634c9993410a9203f6616a5ca2
  • Findings: relsem-eventlog-record-randomization
  • Initial patch: focused reproduction tests; implementation fix pending

Closes EFF-559

@fubhy fubhy added the audit Findings originating from the Effect runtime correctness audit label Aug 7, 2026
@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 946e53d

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

This PR includes changesets to release 30 packages
Name Type
effect Patch
@effect/ai-anthropic Patch
@effect/ai-openai Patch
@effect/ai-openai-compat Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node Patch
@effect/platform-node-shared Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/vitest 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

@effect-slopcop effect-slopcop Bot added 4.0 bug Something isn't working labels Aug 7, 2026

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

Important

The regression test is well-targeted, but the PR title says "Fix … reuse …" while the actual implementation fix is not present. Merging now would land a failing test on main and leave the AES-GCM IV-reuse vulnerability unfixed.

Reviewed changes

This run reviewed the single commit 8a674cc7a80b2c9e5dadd581b439113f5e07f040, which adds a focused reproduction test in packages/effect/test/unstable/eventlog/EventLog.test.ts that intercepts subtle.encrypt calls and asserts each batched entry receives a distinct AES-GCM IV. I ran the focused reproduction command and confirmed it fails against the current EventLogEncryption implementation as expected.

⚠️ Implementation fix is missing

EventLogEncryption.encrypt still generates one IV per batch and reuses it for every entry in packages/effect/src/unstable/eventlog/EventLogEncryption.ts. The new test correctly fails against this code. Before merging, the PR needs the actual fix plus the changes that ripple from it:

  • Generate a unique IV per entry inside makeEncryptionSubtle.encrypt.
  • Change the service return type from { iv, encryptedEntries } to a per-entry shape, e.g. ReadonlyArray<{ readonly iv: Uint8Array; readonly encryptedEntry: Uint8Array }>.
  • Update callers/consumers: EventLogRemote.makeEncrypted, EventLogServerEncrypted, and SqlEventLogServerEncrypted.
  • Update the wire schemas (WriteEntries, EncryptedEntry, EncryptedRemoteEntry) so each entry carries its own IV.
  • Update the existing "encrypts and decrypts entries" test and any other code that destructures the old batch-level iv.
  • Add a changeset, because the public EventLogEncryption shape and protocol message schemas change.
Technical details
# Missing fix and ripple scope

## Affected sites
- `packages/effect/src/unstable/eventlog/EventLogEncryption.ts:104-123` — still reuses one `iv` for the whole batch.
- `packages/effect/src/unstable/eventlog/EventLogMessage.ts:270-275``WriteEntries` carries a single top-level `iv`.
- `packages/effect/src/unstable/eventlog/EventLogRemote.ts:308-321``makeEncrypted` maps the single `iv` to all entries.
- `packages/effect/src/unstable/eventlog/EventLogServerEncrypted.ts:69-76``onWrite` stores `request.iv` for every persisted entry.
- `packages/effect/src/unstable/eventlog/SqlEventLogServerEncrypted.ts:220-244` — row encoding/decoding assumes one `iv` per entry already persisted in `entry.iv`.

## Required outcome
- `EventLogEncryption.encrypt` returns per-entry IVs.
- The wire protocol and storage layers preserve one IV per encrypted entry.
- All existing tests still pass after the API change.
- A changeset documents the breaking API/protocol change.

## Suggested approach
Return `ReadonlyArray<{ readonly iv: Uint8Array; readonly encryptedEntry: Uint8Array }>` from `encrypt`, then move `iv` into `EncryptedEntry` and `EncryptedRemoteEntry` so the protocol message schemas and SQL row shapes stay consistent.

ℹ️ Nitpicks

  • Consider marking the PR as draft while the fix is pending, or update the title to reflect that this is the reproduction-only stage.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix it ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

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

ℹ️ No critical issues — minor suggestions inline.

Reviewed changes

This run reviewed the new commit 20d5d8a42 since the prior Pullfrog review at 8a674cc7a. It adds the missing implementation fix for the AES-GCM IV-reuse issue and updates the ripple sites.

  • Fixed per-entry AES-GCM IV generation: makeEncryptionSubtle.encrypt now generates a fresh 12-byte IV inside the per-entry map callback and returns ReadonlyArray<{ iv, encryptedEntry }>.
  • Updated wire/storage schemas: WriteEntries no longer carries a top-level iv; EncryptedEntry now includes the per-entry IV, matching the existing per-entry iv in EncryptedRemoteEntry and PersistedEntry.
  • Updated callers/consumers: EventLogRemote.makeEncrypted, EventLogServerEncrypted.onWrite, and SqlEventLogServerEncrypted (already row-per-entry) use the per-entry IV.
  • Updated tests and added a changeset: existing tests destructure the new return shape; the new regression test asserts distinct IVs per batch entry; .changeset/wise-bats-encrypt.md documents the patch.

Validation run: focused regression test, full EventLog.test.ts, SqlEventLogServerEncrypted.test.ts, pnpm lint, and pnpm check all pass.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix all ➔Fix 👍s ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

Comment thread packages/effect/src/unstable/eventlog/EventLogEncryption.ts Outdated
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 6.92 KB 6.92 KB 0.00 KB (0.00%)
batching.ts 9.72 KB 9.72 KB 0.00 KB (0.00%)
brand.ts 6.60 KB 6.60 KB 0.00 KB (0.00%)
cache.ts 10.59 KB 10.59 KB 0.00 KB (0.00%)
config.ts 20.88 KB 20.88 KB 0.00 KB (0.00%)
differ.ts 19.74 KB 19.74 KB 0.00 KB (0.00%)
http-client.ts 21.52 KB 21.52 KB 0.00 KB (0.00%)
logger.ts 10.81 KB 10.81 KB 0.00 KB (0.00%)
metric.ts 8.86 KB 8.86 KB 0.00 KB (0.00%)
optic.ts 6.68 KB 6.68 KB 0.00 KB (0.00%)
pubsub.ts 14.86 KB 14.86 KB 0.00 KB (0.00%)
queue.ts 11.54 KB 11.54 KB 0.00 KB (0.00%)
schedule.ts 10.71 KB 10.71 KB 0.00 KB (0.00%)
schema-class.ts 19.45 KB 19.45 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 29.32 KB 29.32 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.59 KB 25.59 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.52 KB 13.52 KB 0.00 KB (0.00%)
schema-string.ts 11.05 KB 11.05 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.35 KB 15.35 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 21.48 KB 21.48 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 23.96 KB 23.96 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 18.70 KB 18.70 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 18.53 KB 18.53 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.39 KB 18.39 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.16 KB 22.16 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.05 KB 19.05 KB 0.00 KB (0.00%)
schema.ts 18.69 KB 18.69 KB 0.00 KB (0.00%)
stm.ts 12.59 KB 12.59 KB 0.00 KB (0.00%)
stream.ts 9.67 KB 9.67 KB 0.00 KB (0.00%)

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

✅ No new issues found.

Reviewed changes

This run reviewed the new commit 71086fc since the prior Pullfrog review at 20d5d8a4. It addresses the remaining style feedback from the previous review.

  • Avoided async/await in Effect code: the per-entry encryption callback in EventLogEncryption.makeEncryptionSubtle now uses crypto.subtle.encrypt(...).then(...) instead of an async function, matching repository conventions.
  • Security semantics unchanged: a fresh 12-byte IV is still generated inside each map callback, so every encrypted event-log entry receives a distinct AES-GCM nonce.
  • Validation: the focused regression test, full EventLog.test.ts, SqlEventLogServerEncrypted.test.ts, pnpm lint, and pnpm check all pass.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

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

✅ No new issues found.

Reviewed changes

This run reviewed the new commit 946e53d ("Strengthen event log IV regression coverage") since the prior Pullfrog review at 71086fc.

  • Replaced the Proxy-based distinct-IV test with a stronger merged round-trip test in EventLog.test.ts: it encrypts two entries, asserts assert.notDeepEqual(encrypted[0].iv, encrypted[1].iv), and completes a full encryptdecrypt round-trip asserting the recovered entry ids match. The assertion genuinely fails if a shared per-batch IV were reintroduced, so it remains a real regression guard.
  • Tightened the EncryptedEntry JSDoc to mention the per-entry initialization vector.
  • Expanded the changeset to note that encrypted clients and servers must be upgraded together because the WriteEntries wire shape changed.
  • Verified all callers of encrypt consume the per-entry { iv, encryptedEntry } shape with no stale references to the old batch-level iv/encryptedEntries.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@tim-smart
tim-smart enabled auto-merge (squash) August 8, 2026 01:13
@tim-smart
tim-smart merged commit deed5fb into main Aug 8, 2026
20 checks passed
@tim-smart
tim-smart deleted the audit/repro-b206fa5d76-eventlog-aes-gcm-iv-reuse branch August 8, 2026 01:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 audit Findings originating from the Effect runtime correctness audit bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants