Skip to content

Encrypt annotation data payloads on encrypted channels - #680

Merged
SimonWoolf merged 1 commit into
mainfrom
encrypted-annotation-data
Jul 30, 2026
Merged

Encrypt annotation data payloads on encrypted channels#680
SimonWoolf merged 1 commit into
mainfrom
encrypted-annotation-data

Conversation

@SimonWoolf

@SimonWoolf SimonWoolf commented Jul 29, 2026

Copy link
Copy Markdown
Member

Annotation had no encrypt() method at all, so annotation data was published in plaintext even when the channel had a cipher configured — while Message (ably/types/message.py:235) and PresenceMessage (ably/types/presence.py:123) were correctly encrypted before serialisation at their publish sites (ably/rest/channel.py:80, ably/realtime/channel.py:415).

The REST get() path compounded it by hardcoding cipher=None when building its response handler, so it could not have decrypted an encrypted annotation either. The realtime receive path (ably/realtime/channel.py:768) already passed cipher=self.cipher correctly, so it was only the REST direction that was broken.

Changes

  • Add Annotation.encrypt(), mirroring Message.encrypt().
  • Call it from both publish paths (rest/annotations.py, realtime/annotations.py), guarded on channel.cipher like the message paths are.
  • Pass the channel's cipher when decoding REST get() responses.
  • Remove three docstrings asserting annotations are not encrypted (see below).

One deliberate difference from Message.encrypt(): annotations very commonly carry no data at all — a reaction is fully described by its name, and every existing annotation test passes data=None. Message.encrypt() would raise TypeError: Unexpected object type <class 'NoneType'> from TypedBuffer.from_obj() on that input; it just never gets called that way in practice. Annotation.encrypt() returns early instead. Worth noting Message.encrypt() has the same latent flaw if it is ever called with data=None, but I have left it alone.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Annotation payloads are now encrypted when channels are configured for encryption.
    • Encrypted annotations sent through realtime and REST APIs can be decrypted when received.
    • Supports secure encryption for text, dictionary, list, and binary annotation data.
  • Bug Fixes

    • Improved handling of annotations with missing or already encrypted data.
    • Preserved encryption metadata when encrypted annotations are decoded without a configured cipher.

Annotation had no encrypt() method, so annotation data was published in
plaintext even when the channel had a cipher configured, while Message and
PresenceMessage were correctly encrypted before serialisation.

The REST get() path compounded this by hardcoding cipher=None when building
its response handler, so it could not have decrypted an encrypted annotation
either. (The realtime receive path already passed the channel's cipher.)

Add Annotation.encrypt(), mirroring Message.encrypt(), and call it from both
publish paths. Pass the channel's cipher when decoding REST get() responses.
Unlike messages, annotations commonly carry no data at all, so encrypt()
returns early rather than failing in TypedBuffer.from_obj().

The docstrings claiming annotations are not encrypted because the server needs
to parse them for summarisation were correct for the original early-preview
API, where aggregation read JSON data payloads. That changed before public
release, when aggregation moved to the count field specifically so the server
would not have to read payloads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c24f254d-7e97-401e-85d4-c225465241a5

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2cd64 and f973365.

📒 Files selected for processing (4)
  • ably/realtime/annotations.py
  • ably/rest/annotations.py
  • ably/types/annotation.py
  • test/unit/annotation_test.py

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

Annotation payloads can now be encrypted for REST and realtime channels with ciphers. Responses pass the channel cipher for decryption, and unit tests cover supported payloads, idempotency, missing data, and cipher-less decoding.

Changes

Annotation encryption flow

Layer / File(s) Summary
Annotation encryption contract
ably/types/annotation.py
Adds Annotation.encrypt() to encrypt string, dictionary, and list payloads into CipherData, while preserving encoding hints and avoiding repeated encryption.
REST and realtime transport integration
ably/rest/annotations.py, ably/realtime/annotations.py
Encrypts annotations before serialization and passes the configured channel cipher to REST response handling.
Encryption behavior validation
test/unit/annotation_test.py
Tests encrypted round trips, binary payload modes, missing data, idempotency, and decoding without a cipher.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RestAnnotations
  participant RealtimeAnnotations
  participant Annotation
  participant ChannelCipher
  RestAnnotations->>Annotation: encrypt annotation payload
  Annotation->>ChannelCipher: encrypt typed data
  ChannelCipher-->>Annotation: return CipherData
  RestAnnotations->>RestAnnotations: serialize encrypted annotation
  RealtimeAnnotations->>Annotation: encrypt annotation payload
  Annotation->>ChannelCipher: encrypt typed data
  ChannelCipher-->>Annotation: return CipherData
  RealtimeAnnotations->>RealtimeAnnotations: serialize encrypted annotation
Loading

Possibly related PRs

  • ably/ably-python#667: Introduced the annotation modules and classes extended by these encryption changes.

Poem

A bunny hops where ciphertext gleams,
Payloads now travel through encrypted streams.
REST and realtime guard the way,
While tests make sure the plaintext stays.
With cipher in hand, responses bloom—
Safe little annotations leave the room!

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch encrypted-annotation-data

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@owenpearson owenpearson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

looks good! there are ci failures on every python version but they seem unrelated (all push admin timeouts)

@SimonWoolf
SimonWoolf marked this pull request as ready for review July 30, 2026 14:06
@SimonWoolf
SimonWoolf merged commit 497f1bf into main Jul 30, 2026
2 of 10 checks passed
@SimonWoolf
SimonWoolf deleted the encrypted-annotation-data branch July 30, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants