[JMAP] JMAP CalendarEvent privacy quirks
Robert Stepanek <rsto@fastmailteam.com> Tue, 01 September 2026 15:07 UTC
Return-Path: <rsto@fastmailteam.com>
X-Original-To: jmap@mail2.ietf.org
Delivered-To: jmap@mail2.ietf.org
Received: from localhost (localhost [127.0.0.1]) by mail2.ietf.org (Postfix) with ESMTP id BCCAC13324384; Tue, 1 Sep 2026 08:07:48 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ietf.org; s=ietf1; t=1788275268; bh=V+zZSF8KicQqGA6vjhCJ7HBGavWaWnbrEk4/sKWceUU=; h=Date:From:To:Subject; b=Hjb1ObkjwT7lheKwG89xn3/ZHtwJ15qxPy1hbnTl7mguQnd5pLa4rg8QYlk1vHqbt OL09C2ORO8mz9g3drbxC5IQMEK91uUbkxr0yl3wCST3OLb2/VzkL3qJnYlN4ML/ItM aueWYlhW1mvE5uCE/s4HUC5oNJBCSYNVfbpG5Toc=
X-Virus-Scanned: amavisd-new at ietf.org
X-Spam-Flag: NO
X-Spam-Score: -2.798
X-Spam-Level:
X-Spam-Status: No, score=-2.798 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_PASS=-0.001] autolearn=ham autolearn_force=no
Authentication-Results: mail2.ietf.org (amavisd-new); dkim=pass (2048-bit key) header.d=fastmailteam.com header.b="ctiowNbp"; dkim=pass (2048-bit key) header.d=messagingengine.com header.b="d9UvZevC"
Received: from mail2.ietf.org ([166.84.6.31]) by localhost (mail2.ietf.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id iqjo3KOtnFXR; Tue, 1 Sep 2026 08:07:47 -0700 (PDT)
Received: from fout-a6-smtp.messagingengine.com (fout-a6-smtp.messagingengine.com [103.168.172.149]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-256) server-digest SHA256) (No client certificate requested) by mail2.ietf.org (Postfix) with ESMTPS id C616C1332437A; Tue, 1 Sep 2026 08:07:47 -0700 (PDT)
Received: from phl-compute-10.internal (phl-compute-10.internal [10.202.2.50]) by mailfout.phl.internal (Postfix) with ESMTP id D9CD6EC0116; Tue, 1 Sep 2026 11:07:41 -0400 (EDT)
Received: from phl-imap-15 ([10.202.2.104]) by phl-compute-10.internal (MEProxy); Tue, 01 Sep 2026 11:07:41 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= fastmailteam.com; h=cc:content-type:content-type:date:date:from :from:in-reply-to:message-id:mime-version:reply-to:subject :subject:to:to; s=fm1; t=1788275261; x=1788361661; bh=lmr7W4+rxj qkyUlRKL6TuGInMAauOsDNqY+mWlrE+EY=; b=ctiowNbpHxqzz9DhaCl9ZEBD0/ FAIkTumMnmquTQJ9jAcIyR9f1cYuGEHisqCpmbTh+SmydIlMSEQAxB19A+lEwR7k +Pw6oppZK5v7y5hBrr53xxduHqw/y4cjXdF7u4UX7OHuFhtIgw/HjsPlTrHZMZgj Tmn7t6ouOMshSSuwiKGn8Vc9pk+Sdci6nmAxZFcKbJM+jAP3BFCUufSTRDlTBJFT 8kMWhgjFJWcMLLQPEdhblAZ7HIYMTEXHzmtyjSp8yIdyU5TtpXRIZry69HvEyive DAhFkFEV2IG/mEzfwIwgbDYJbjWsnF8Msb6xnBiJ8hHHYVsm2k8cPBMvPC2g==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:message-id :mime-version:reply-to:subject:subject:to:to:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=1788275261; x= 1788361661; bh=lmr7W4+rxjqkyUlRKL6TuGInMAauOsDNqY+mWlrE+EY=; b=d 9UvZevCaykg5bJIdiecnRYDUDUVcbzjmaOeYxOUDEBTxbKNScYsMrBAFB3j0zSyy RxSUSNSK0kXRw7u79J+oeFa2eENwR7UXnbIVHmxMh8g0P4LJrQKZsiPOiCERtf9+ cxtjpyjldZxFPdmk/nsdoyi9HHuTpeib8Ni+lyBLLBtwCxIeHpzjCda3iYkT0J9q NRmYqJ8CjivoljCIcm1ktX4LGd1qmGZDKTcjslX1kj3rOfYqaT5uqmc5zXIuwoZg KeHbfvWz3vSpFR9vJp9TgixkdIf0KxcFDwyrZeP5xgAaa0ph05HPBT9lQUVqzR+T wkrqbp6sGeOx2gxdgZ70A==
X-ME-Sender: <xms:PeqWaspRGr0pwoLD9LAiXIif1GtLgm-H4hfM1VefTP2cbAmsTPp_hw> <xme:PeqWatcid5fsgIrSLEJbyNuSDyijzqiXIucTvRVWIpx6EjNjYDgdK3pndmiG1YAyv _-jOzVbEgjVRnaBFjy6pDAdsbbsqv6daUKhB-5i5IIL04AmJEQ>
X-ME-Proxy-Cause: dmFkZTE4E+GReBquc6twB2WQwFwApvTHjdzhxfeff+mwDR47DjJDKlKmdc1CAIPWonv2V+ c8oA0bhNZAqFtdtFshsvX72HaUun2CiCgL+jjITx9+qQYthYbMUfQXPfqtzlkjmkzcX7Fe IfEeyKNiIg3gISicpxnKoDi8XtzAKtLN6QEd4TXtK986TjxI3rDjyHqwe/u9XpJw8QhJZv ZZ2fIzwZC/AuQ69S8Fh7lZ+9/YUfrRYKR1gADW8Do88/Vlm3sA6uxgragoGJSNTRxa4o1z 9psG87DVvnLzQMOv8AYRttQubl2OgtVCdAfpIANKqbHZHxldpLKP+2rKhGIW5awvfMbCcn nRWfCBVC3YnE0Oba6MfWQZwRHpN4xefHzwp9y9f6+8JYSqdzMZw1sCBIKfxQqoaai9PABJ PTpRx6G/2aq0xzRXCilWfQepsCx8Ek3OZH+1lThrd//blYxhlVlI4Ory6UBNosthJYgxNR qlqYBlZzVSWoQTY8Ql3PFM5jInLep+V2fCBJlkLZjpsm9MqGVw6NmlSjTfYTeRtu26JKdL u6DAvlQPNXZckRqKd9MqjFEzgTYfIdg9bUVDjnrnTUTz+V8nD7EG72EbQEmjBjL3zgbsqW fy+dPSkp0STLH64VZ2YtFeyZ+2cw/wZmueUnQ/l9whGf7Rnk/nexSEMs/YfA
X-ME-Proxy: <xmx:PeqWahJJdExK9I5G-B6o7JazsB2OaBEflW-sWUDqgudjPuHwAV6JUw> <xmx:PeqWajLoVNjiok3XKiSac6kirBfwA4NwUKKE0YoHPijVW4zCNUkIOw> <xmx:PeqWanW_JAP_Ab4KD0-J0-iUKIuNHHB8wsrXDW4MNDb9Iblfv3ax5Q> <xmx:PeqWatjrXngJcljS53momtlOJQr4pGct216c46KLg9nJuTbQdLUNRA> <xmx:PeqWakkfoCIxnVt9oF8kDtBamOcz5ezJJV4v5-qYp6b1FWfB8JkUI3pN>
Feedback-ID: ia5d944da:Fastmail
Received: by mailuser.phl.internal (Postfix, from userid 501) id A33347811F0; Tue, 1 Sep 2026 11:07:41 -0400 (EDT)
X-Mailer: MessagingEngine.com Webmail Interface
MIME-Version: 1.0
Date: Tue, 01 Sep 2026 17:06:58 +0200
From: Robert Stepanek <rsto@fastmailteam.com>
To: calsify@ietf.org, jmap@ietf.org
Message-Id: <7a865d64-677c-414c-b6ab-2dfaaa5b7d1c@app.fastmail.com>
Content-Type: multipart/alternative; boundary="566c0d4646d1c9c6388758e55c3595de1210794e"
Message-ID-Hash: QJI2RZQMQNIREPC74MEXXNOATJ53666F
X-Message-ID-Hash: QJI2RZQMQNIREPC74MEXXNOATJ53666F
X-MailFrom: rsto@fastmailteam.com
X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-jmap.ietf.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header
X-Mailman-Version: 3.3.9rc6
Precedence: list
Subject: [JMAP] JMAP CalendarEvent privacy quirks
List-Id: JSON Meta Access Protocol <jmap.ietf.org>
Archived-At: <https://mailarchive.ietf.org/arch/msg/jmap/JCckaRP0nLdC2CSJN5unGOJEylc>
List-Archive: <https://mailarchive.ietf.org/arch/browse/jmap>
List-Help: <mailto:jmap-request@ietf.org?subject=help>
List-Owner: <mailto:jmap-owner@ietf.org>
List-Post: <mailto:jmap@ietf.org>
List-Subscribe: <mailto:jmap-join@ietf.org>
List-Unsubscribe: <mailto:jmap-leave@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
- [JMAP] JMAP CalendarEvent privacy quirks Robert Stepanek
- [JMAP] Re: [calsify] JMAP CalendarEvent privacy q… Mauro De Gennaro