Subject: [JMAP] JMAP CalendarEvent privacy quirks
From: "Robert Stepanek" <[email-masked]>
To: [email-masked], [email-masked]
Date: Tue, 01 Sep 2026 17:06:58 +0200
List-Id: JSON Meta Access Protocol <jmap.ietf.org>

*Cross-posting to calext and jmap; please keep replies on the calext list.*

We are reimplementing support for private calendar events in Cyrus IMAP and encountered quirks with the `privacy` property. I would like to discuss them now, rather than after publication. I understand that Last Call for the relevant documents ends later this week. To allow for discussion, I have asked the chairs to extend Last Call at least for a couple of days.

This email is long, but I want us to make an informed decision. Also, the formatting and length of this email might tempt you to think that this was produced by an LLM. It is not, I edited this on HedgeDocs and structured it for readability. In Section 1, I briefly describe my core assumptions, Sections 2 and 3 describe the quirks with private events I encountered, and Section 4 then outlines my proposal.

TLDR, I am proposing three changes:
 1. For JSCalendar, we forbid patching `privacy` in a recurrence override, rather than ignoring it.
 2. For JMAP Calendars, we require the same privacy level for all CalendarEvents sharing a `uid` within an account.
 3. For iCalendar conversion, we redefine that the most restrictive `CLASS` property of a main component and its recurrence overrides converts to JSCalendar privacy.
1. Introduction

For the following, I will assume that anyone implementing JMAP Calendars and CalDAV will want to enforce privacy consistently in both protocols (rather than differently or not at all for CalDAV). Even if that’s not the case, you might still want to understand the quirks I outline below, as some apply to JMAP Calendars regardless.

Also, privacy is only enforced for shared calendars within one calendar service. For iMIP, any privacy level provided by the sender is only a suggestion: a recipient may choose to ignore it and iTIP payloads always contain full event details, even for privacy levels “private” or “secret”.

Lastly, from here on I use “private event” (without quotes) to mean an event whose privacy level is either “private” or “secret”. For the relevant definitions see jscalendarbis-18 <https://www.ietf.org/archive/id/draft-ietf-calext-jscalendarbis-18.html#name-privacy>, jmap-calendars <https://www.ietf.org/archive/id/draft-ietf-jmap-calendars-28.html> and jscalendar-icalendar <https://www.ietf.org/archive/id/draft-ietf-calext-jscalendar-icalendar-25.html#name-class>.
2. Quirks with non-recurring private events

Non-recurring private events mostly are straightforward to implement consistently for both JMAP and CalDAV: a calendar owner can read and write events of all privacy levels. A calendar sharee can read and write “public” events in full, can read only the basic time and metadata of “private” events but cannot write them, and a sharee cannot see or otherwise access “secret” events at all. Except for the following quirk:

*Quirk#1*: The existence of a “secret” event can leak to sharees with write access. A CalDAV client on a shared calendar may PUT, COPY or MOVE a calendar event where the UID or CalDAV resource name matches that of a “secret” event. In this case, the server has to reject the request with a `CALDAV:no-uid-conflict` precondition, which reveals that a resource with that name or UID exists. Likewise, a JMAP Calendars server must reject a `CalendarEvent/set` method if it would create an already existing secret `uid` in that account. The server can return a `forbidden` or `invalidProperties` SetError, but either leaks existence of that secret `uid`.

I see this first quirk as inevitable. If leaking the existence of a secret event identifier is a concern, then the service must prevent secret events from ever becoming non-secret, whether by forbidding scheduling these events or by forbidding an event that has ever been non-secret from becoming secret. In any case, even that does not prevent a client from enumerating random (or not so random) identifiers.
3. Quirks with recurring private events

For recurring private events things become more complicated. Currently, JSCalendar defines that patching the `privacy` property in a recurrence override must be ignored. For example, a client may attempt to create the following calendar event with a "private" override, but in fact all occurrences of the event will be “public”:

{
  "privacy": "public",
  "recurrenceOverrides": { "2026-08-28T01:00:00" : { "privacy": "private" } }
  ...
}

*Quirk#2*: For `CalendarEvent/get`, an unaware developer might be led to assume by the above snippet that overriding `privacy` is possible. They may retrieve such an event either because another client created it, or the event got converted from iCalendar where CLASS may differ between the main event and its override exceptions.

*Quirk#3*: For `CalendarEvent/set`, it is underspecified what “ignoring the privacy patch” means. A server that interprets it to remove privacy from the patch object can indicate that to a client in the `CalendarEvent/set` response fields. A server that ignores but preserves the patch does not, and will return it verbatim in a later `/get`.

*Quirk#4*: For `CalendarEvent/set`, whether a `privacy` patch takes effect depends on what the event id refers to. For a synthetic id retrieved from a recurring event when expanding recurrences in `/query`, the server must process the patch as an update to a recurrence override of the base event, and `privacy` is ignored. If the id denotes a stand-alone recurrence instance, `privacy` is a top-level property and the patch takes effect. Clients can only tell the two apart by the `baseEventId` property.

*Quirk#5*: Related to the previous quirk, two stand-alone recurrence instances may have differing privacy levels and both take effect, but only as long as no main event for these instances exists. As soon as the main event exists, their previous privacy levels become ineffective and are defined by the main event.

*Quirk#6*: For CalDAV interoperability, a server might either keep on accepting but ignoring differing CLASS property values in override exceptions, or it has to rewrite the CLASS properties of these overrides during PUT. For the latter, it might also need to rewrite its existing calendar resources and instruct CalDAV clients to GET the rewritten data by bumping the ETag.
4. Proposal

To address all but the last quirk of recurring events, I propose the following changes:
 • In JSCalendar version “2.0” and later, we forbid patching `privacy` in a recurrence override, rather than ignoring it. JMAP Calendars implementations that keep on supporting version “1.0” should accept but strip `privacy` patches in recurrenceOverrides and should not return such patches in `CalendarEvent/get`.
 • For JMAP Calendars, we require the same privacy level for all calendar objects having the same `uid`. A `CalendarEvent/set` that, after all changes in the `/set` have been applied, causes the privacy of a given `uid` to become ambiguous within an account must be rejected. For scheduling invites, an updated event keeps whatever privacy was assigned to an existing event with that `uid`.
 • When converting from iCalendar to JSCalendar, the most restrictive CLASS value of a main component and all its recurrence overrides converts to the privacy of the converted Event or Task. For stand-alone recurrence instances with the same UID, the most restrictive of their CLASS values converts to the privacy of each converted object.
This leaves quirk #6, for I see no way around it, regardless if we keep the existing `privacy` definitions or update them as I propose. The core issue is that CalDAV does not define anything about the CLASS property and its iCalendar definition is very vague, too. In our implementation, we will aim to rewrite newly created and existing iCalendar data to match JMAP Calendars and JSCalendar semantics as much as possible.

Regards,
Robert