Google, security keys and one-time codes, GitHub¶
The question: what technical facts decide how Sioul reaches Google calendars, contacts and tasks from a desktop app; how security keys (WebAuthn, FIDO2) and one-time codes (TOTP) can work in its embedded browser (Qt WebEngine 6.11); and how GitHub's issues and pull requests can become tasks?
Researched on 3 October 2026, from official documentation (linked at each fact), source code (Qt WebEngine 6.11, Chromium, systemd) and third-party reports (named as such). "(unverified)" marks what could not be confirmed; "inferred from source" means read in the code, not documented. "Built", "not built" and "next" say where Sioul stood on 4 October 2026. The features: google.md, sites.md, github.md.
1. Google calendars, contacts and tasks¶
1.1 Calendars over CalDAV¶
Source unless noted: Google's CalDAV API Developer's Guide, https://developers.google.com/workspace/calendar/caldav/v2/guide
- Endpoints: principal
https://apidata.googleusercontent.com/caldav/v2/CALENDAR_ID/user, collection…/CALENDAR_ID/events; "The calendar ID for a user's primary calendar is the same as that user's email address." The oldhttps://www.google.com/calendar/dav"is deprecated and is no longer supported". - OAuth only: "The CalDAV server refuses to authenticate a request unless it arrives over HTTPS with OAuth 2.0 authentication of a Google Account. Attempting to connect over HTTP or using Basic Authentication results in an HTTP 401". No app passwords for DAV.
- The APIs to enable are "CalDAV API" and "CardDAV API": "not the Calendar and Contacts APIs, those are different and won't work" (vdirsyncer's documentation, https://vdirsyncer.pimutils.org/en/stable/config.html#google).
- Scope: the guide names none; Thunderbird and DAVx⁵ use
https://www.googleapis.com/auth/calendar(https://hg.mozilla.org/comm-central/file/tip/mailnews/base/src/OAuth2Providers.sys.mjs, https://www.davx5.com/tested-with/google). Whether the narrowercalendar.eventsworks over CalDAV: unverified. - Supported: GET, PUT, HEAD, DELETE, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT; sync-collection (RFC 6578: "Client applications must switch to this mode of operation after the initial sync"); ctag; ETags with If-Match.
- Not supported: MKCALENDAR (new calendars only on the web or through Calendar API v3); "the If* header (except for If-Match)", so no
If-None-Match: *on creation; arbitrary properties; ACL; VTODO and VJOURNAL; sound alarms; free-busy. Invitations received are "automatically delivered into your 'events' collection". - Which calendars appear is chosen on https://calendar.google.com/calendar/syncselect (vdirsyncer's documentation).
- Observed by BusyCal (third party, https://www.busymac.com/docs/faqs/google-calendar-limitations, https://www.busymac.com/docs/faqs/google-calendar-adding-unwanted-default-alarms): the server adds the calendar's default notifications to every event created or changed; floating times are not supported (events shift by hours); no attachments; meeting invitations reliable only on the primary calendar.
- Invitations: whether Google e-mails attendees itself when a client PUTs an event with ATTENDEE lines is not documented (unverified); if it does, a client's own invitation would make duplicates. Calendar API v3 (
sendUpdates) is the documented control. - Quotas: "The CalDAV API has the same quota limits as the Calendar API"; for projects created from 1 May 2026, 600 requests per minute per user. One person is far below. https://developers.google.com/workspace/calendar/api/guides/quota
1.2 Contacts over CardDAV¶
Source unless noted: https://developers.google.com/people/carddav
- Discovery from
https://www.googleapis.com/.well-known/carddav; "You must not hardcode any URI"; the redirect "must not be permanently cached", and discovery is re-run "every 2-4 weeks". - OAuth only, with an application registered on Google's console; every other method has been off since 6 June 2023 (DAVx⁵ page above). Scope
https://www.googleapis.com/auth/carddav, absent from Google's published scope list (Thunderbird's source; DAVx⁵ adds it by hand). - New contacts by POST (RFC 5995: "The response will contain the ID of the new contact"); no new address books from a client (no MKCOL).
- Sync: ctag only for the first sync ("Periodically polling for the getctag property will result in throttling"), then sync-collection; "Issued tokens are valid for 29 days".
- Format: "Google uses VCard 3.0".
- Losses: labels are not mapped; a birthday without a year is dropped (vdirsyncer; https://issuetracker.google.com/issues/36761530). In 2014 the author of sabre/dav found unknown properties and groups silently dropped and UID and URL replaced by Google's own (https://evertpot.com/google-carddav-issues/); current behaviour unverified, but the guide still says the server assigns the ID on creation.
1.3 Google Tasks¶
- No CalDAV access: "Doesn't support VTODO or VJOURNAL data" (the CalDAV guide); "Tasks over CalDAV are not provided by Google" (DAVx⁵).
- REST API v1: base
https://tasks.googleapis.com/tasks/v1/; scopehttps://www.googleapis.com/auth/tasks; a "courtesy limit of 50,000 queries per day". https://developers.google.com/identity/protocols/oauth2/scopes#tasks · https://developers.google.com/workspace/tasks/limits - Fields (https://developers.google.com/workspace/tasks/reference/rest/v1/tasks): title (max 1024 characters), notes (max 8192), status
needsActionorcompleted,due,completed,parent(one level),position,links(output only). dueis a day, not a deadline: "This represents the day that the task should be done, or that the task is visible on the calendar grid. It doesn't represent the deadline of the task. Only date information is recorded". Google's apps added a separate deadline in late 2025; the API has no field for it (https://www.androidauthority.com/google-tasks-deadline-release-3616315).- No sync token: incremental sync is
updatedMinwith deleted and hidden tasks shown. - Not in the API: recurrence, due time, reminders ("the third-party Google Tasks API did not receive a similar update", https://www.tasks.org/docs/sync_google_tasks), start date, priority, tags, attachments; sub-tasks one level only.
- What Sioul's tasks lose there: the day to start, the time of the date asked, repeating, alarms, priority, categories (kinds,
joy,someday), the estimate, links between tasks other than one parent level, contacts, notes or titles over the limits. What survives: title, notes, done or not, a day, one level of steps, order.
1.4 OAuth for a desktop app (Google's rules, October 2026)¶
Sources: https://developers.google.com/identity/protocols/oauth2/native-app · https://developers.google.com/identity/protocols/oauth2
- Client type "Desktop app"; the answer comes to a loopback address,
http://127.0.0.1:porton "a random available port". The copy-and-paste flow is dead (blocked for all clients since 31 January 2023: https://developers.google.com/identity/protocols/oauth2/resources/oob-migration). - The system browser only: sign-in inside an embedded browser is refused (
disallowed_useragent); Google has blocked "sign-ins from embedded browser frameworks" since June 2019 (https://security.googleblog.com/2019/04/better-protection-against-man-in-middle.html). So Google's sign-in never opens in Sioul's own browser, and a Google web app pinned in Sites may refuse to sign in too. - PKCE with S256; "Incremental authorization is not supported for installed apps": every scope is asked at once; people can untick scopes, so the granted ones are read back.
- The client secret: for installed apps "the client secret is obviously not treated as a secret"; developers report the token endpoint still asking for it (https://discuss.google.dev/t/is-it-ok-to-put-a-client-secret-in-a-desktop-app/296820, https://discuss.google.dev/t/desktop-oauth-pkce-exchange-returns-invalid-request-after-successful-loopback-callback-with-no-client-secret/390526), so it is sent. Since June 2025 the console shows a new secret only once, and deletes clients unused for six months (https://developers.googleblog.com/en/usability-and-safety-updates-to-google-auth-platform/).
- Refresh tokens are "always returned for installed applications" and stop working when revoked, unused for six months, after a password change with Gmail scopes, past 100 per account and client, and after 7 days while the project is in "Testing".
- Testing against production (https://support.google.com/cloud/answer/15549945): Testing allows 100 listed test users and "Authorizations by a test user will expire seven days from the time of consent"; in production without verification, an "unverified app" screen and a lifetime cap of 100 users. Calendar and contacts are sensitive scopes (https://developers.google.com/identity/protocols/oauth2/production-readiness/sensitive-scope-verification); only Gmail's and Drive's are restricted, needing a paid security assessment (https://support.google.com/cloud/answer/13464325). Personal use is exempt from verification: "If the app is for your personal use (fewer than 100 users), you and your limited number of users can continue using the app without going through verification" (https://support.google.com/cloud/answer/13464323).
- Shipping a client in open source: vdirsyncer reads Google's terms ("Confidential Matters") as forbidding credentials hard-coded in open-source code; Thunderbird ships its own anyway (Thunderbird's source above).
What Sioul does¶
- One Google account, three channels: events over Google's CalDAV and contacts over its CardDAV, through Sioul's own DAV client with a bearer token; tasks over the Google Tasks API, each list kept here as a folder of VTODO files. Built (google.md).
- Signing in: Google's page in the system browser; the answer on
127.0.0.1at a random port, five minutes at most;state, PKCE S256, and the ID token's address checked against the one typed; every scope asked at once; the client ID, secret and refresh token in the system keyring, the access token in memory. When Google ends the access: "Google asks you to sign in again", and no loop; a project still in testing is named as the reason after seven days. Removing the account revokes the access. Built. - Whose OAuth client: the research recommended that each person bring their own (a free project, published without verification under the personal-use exemption). Built, with the steps in google.md. Also built: a build reads Sioul's own client from the release workflow's secrets, never from the repository, as Thunderbird ships its own, so that people sign in with one click once that client is registered with Google; a build without it asks for a client of one's own.
- Google's quirks kept as data: no MKCALENDAR (making a calendar greyed, with why); no
If-None-Matchon creation; no tasks in a Google calendar; vCard 3.0 for Google (what only 4.0 has goes, said before a move); new contacts by PUT, else POST; Tasks' fields that Google does not keep greyed in the task form, kept in the file here and not sent. Built. - Not built: converting floating times before upload (Google's shifted times come back as Google keeps them); reading a contact back after writing it to say which fields Google dropped. Untested: everything against Google itself, invitations included: no Google account was used (google.md, "What was tested").
- Gmail keeps its app password; OAuth for Gmail waits for its restricted scope to be dealt with (porch.md, "Next").
2. Security keys and one-time codes in the Sites page¶
Qt WebEngine 6.11 is "Based on Chromium version 140.0.7339.264", with security patches up to 154 (https://github.com/qt/qtwebengine/blob/6.11/CHROMIUM_VERSION). Fedora 44's package (qt6-qtwebengine-6.11.2-2.fc44) is linked to libudev.so.1 (checked with ldd).
2.1 The API¶
WebEngineView.webAuthUxRequested(QML) andQWebEnginePage::webAuthUxRequested, since Qt 6.7: "QtWebEngine currently supports user verification, resident credentials, and display request failure UX requests." https://doc.qt.io/qt-6.11/qml-qtwebengine-webengineview.html · https://doc.qt.io/qt-6.11/qwebenginewebauthuxrequest.html- The dialog an application must draw, by state: choosing an account (
SelectAccount, for passkeys on the key); a PIN (CollectPin: enter it, with the attempts left; set one; change it; errors such asWrongPin,TooShort); "touch your key again" (FinishTokenCollection); a failure with its reason (Timeout,KeyNotRegistered,SoftPinBlock,HardPinBlock…), with Retry and Close. Qt's own example: https://github.com/qt/qtwebengine/blob/6.11/examples/webenginequick/quicknanobrowser/WebAuthDialog.qml - Silent cases in 6.11: a second factor that only needs a touch emits nothing (inferred from source: the dialog is created only on the first state change, https://github.com/qt/qtwebengine/blob/6.11/src/core/authenticator_request_dialog_controller.cpp); with no key present, "webAuthUxRequested does not fire unless an authentication method is available", fixed in 6.12 with a
Discoverystate (QTBUG-145876, https://qt-project.atlassian.net/browse/QTBUG-145876). - A regression in 6.10–6.11:
PublicKeyCredential.getClientCapabilities()never resolves, so some sites hang ("a YubiKey that used to work will instead hang"); the workaround is a user script that deletes it before the page's scripts run (QTBUG-149575; https://github.com/qutebrowser/qutebrowser/issues/8930). - Qt's own scope: "only yubikey being tested and supported … rather than all 5 types of what Chromium can handle" (QTBUG-148073).
2.2 Platforms¶
- Linux, USB keys: work when Qt WebEngine is built with the system's libudev: Chromium creates its Linux HID service only with udev (https://chromium.googlesource.com/chromium/src/+/refs/heads/main/services/device/hid/hid_service.cc), Qt passes it only with
webengine-system-libudev(https://github.com/qt/qtwebengine/blob/6.11/src/core/CMakeLists.txt), and Fedora builds with it on (https://src.fedoraproject.org/rpms/qt6-qtwebengine/blob/rawhide/f/qt6-qtwebengine.spec). A Qt developer, April 2026: "packaged 6.9 isn't built against udev, but 6.10 and newer are" (QTBUG-139582). - Linux permissions: nothing to install with systemd 244 or later: its
fido_id"identifies FIDO CTAP1 (“U2F”)/CTAP2 security tokens", and70-uaccess.rulesgives the seat's user access to/dev/hidrawN. https://github.com/systemd/systemd/blob/main/NEWS · https://github.com/systemd/systemd/blob/main/rules.d/60-fido-id.rules · https://github.com/systemd/systemd/blob/main/rules.d/70-uaccess.rules.in . Older systems need Yubico's or libfido2's rules. - Flatpak:
/dev/hidraw*is reachable only with--device=all;--device=usbcovers only/dev/bus/usb. https://docs.flatpak.org/en/latest/flatpak-command-reference.html - Windows: Windows' own WebAuthn dialog handles PIN, touch, accounts, Windows Hello and phones; "browsers or applications don't have direct access to the FIDO2 transports" (https://learn.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/webauthn-apis), and Qt's signal does not fire (QTBUG-129894, QTBUG-137439).
- macOS: no Touch ID and no iCloud Keychain passkeys in Qt WebEngine (https://github.com/qt/qtwebengine/blob/6.11/src/core/authenticator_request_client_delegate_qt.h; Apple's entitlement needs an app that "can act as a user's web browser", https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.web-browser.public-key-credential; QTBUG-145187). USB keys on macOS: unverified.
- Phones as authenticators (QR codes): not on Linux or macOS (inferred from source); Windows only, through its own dialog. Passkeys in the system or a password manager: none on Linux. Passkeys stored on the key itself work on Linux.
2.3 One-time codes (TOTP)¶
- The algorithm (RFC 6238, https://www.rfc-editor.org/rfc/rfc6238): HOTP over 30-second steps; HMAC-SHA-1 by default, SHA-256 and SHA-512 allowed; 6–8 digits; secrets as base32 in
otpauth://totp/…URIs (https://github.com/google/google-authenticator/wiki/Key-Uri-Format). - Bitwarden keeps the "Authenticator key" in a login's
totpfield: base32, anotpauth://URI, orsteam://. "Storing keys … is available to all accounts. Generating TOTP codes is available with Premium" (https://bitwarden.com/help/integrated-authenticator/); since the key itself can be read, a client can compute the code. - No hook in Qt WebEngine: it has no password manager or autofill (https://doc.qt.io/qt-6.11/qtwebengine-features.html); the WebOTP API is for codes received by SMS (https://developer.chrome.com/docs/identity/web-apis/web-otp).
- What an application can do: show the code, or fill it: find
input[autocomplete="one-time-code"], else a field named like a code, set its value as typing would, only on the site the secret belongs to, and only on a click. The research recommended doing this in an isolated JavaScript world (WebEngineScript.ApplicationWorld, https://doc.qt.io/qt-6.11/qml-webenginescript.html).
What Sioul does¶
- The security-key dialog, driven by the request's state (which account, the PIN with the attempts left, "touch it again", failures in plain words with Retry): built. A plain touch still shows nothing (Qt 6.11), and Sioul's wrapper for the silent cases was not built: Qt 6.12's
Discoverystate is the fix (sites.md). - The
getClientCapabilitiesworkaround: one line in every page before its own scripts: built. - Packaging: nothing to install on Fedora; the Flatpak needs
--device=all, said in sites.md. - One-time codes: computed by Sioul from the vault's secret (RFC 6238: base32,
otpauth://with its digits, period and algorithm, Steam's five characters) and written into the page by "Fill the login", only on the site the login belongs to, only on a click: built (sites.md, "Bitwarden"). The script runs in the page's own world, with the values passed as JSON so nothing in them can change the script; an isolated world, as recommended: not built. - One-time codes received by mail from verified senders keep their own exception: they are shown at once (porch.md).
3. GitHub: issues and pull requests as tasks¶
3.1 The REST API¶
X-GitHub-Api-Version: 2026-03-10; without it, requests use2022-11-28, supported until 10 March 2028. https://docs.github.com/en/rest/about-the-rest-api/api-versions- One list for "mine":
GET /issues?filter=assigned|created|mentioned|…; "GitHub's REST API considers every pull request an issue". https://docs.github.com/en/rest/issues/issues#list-issues-assigned-to-the-authenticated-user - Across all of GitHub:
GET /search/issueswithassignee:@me,author:@me,mentions:@me,user-review-requested:@me("pull requests that you have directly been asked to review"),archived:false. Limits: 30 searches a minute, at most 1,000 results a search. Fine-grained tokens need no permission for it. https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests · https://docs.github.com/en/rest/search/search - Structure that maps to tasks: sub-issues and dependencies (
blocked_by,blocking), with the "Issues" read permission. https://docs.github.com/en/rest/issues/sub-issues · https://docs.github.com/en/rest/issues/issue-dependencies - Notifications (
GET /notifications) work only with a classic token ("This endpoint does not work with … fine-grained personal access tokens"). https://docs.github.com/en/rest/activity/notifications - Rate limits: 5,000 requests an hour per user token; conditional requests answered 304 do not count. https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api · https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api#use-conditional-requests
3.2 Tokens¶
Source: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
- Fine-grained: one resource owner per token; "Tokens always include read-only access to all public repositories on GitHub"; permissions needed here: Issues read, Pull requests read, Metadata read; expiry up to 366 days or none; no notifications API.
- A creation link can be filled in advance with documented parameters (name, description, expiry, permissions); the repositories are still chosen by hand.
- Classic: needed only for the notifications inbox; reading private issue bodies would need
repo, full control of every private repository: too wide. GitHub "automatically removes personal access tokens that haven't been used in a year".
3.3 GitHub's notification mail¶
- Documented headers (https://docs.github.com/en/subscriptions-and-notifications/reference/email-notification-headers):
From: … <notifications@github.com>;List-Id: OWNER/REPOSITORY <REPOSITORY.OWNER.github.com>; the reason inCcas<reason>@noreply.github.com. - The
Toaddress posts as the account: "The reply-to address on each email notification identifies the thread and the account that the comment will be posted from." https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications . It is treated as a secret. - Threading, checked on 2026 mail in the W3C's public archive of GitHub notifications (https://lists.w3.org/Archives/Public/public-webapps-github/2026Aug/): a new issue is
Message-ID: <OWNER/REPO/issues/N@github.com>, a pull request<OWNER/REPO/pull/N@github.com>; every follow-up hasIn-Reply-Topointing at that root: the issue or pull request itself is the thread's root. Discussions, releases, Actions and security mail: not checked.
What Sioul does¶
- A fine-grained token, read-only (Issues, Pull requests, Metadata), made from a filled-in link and kept in the system keyring; no classic token, no notifications inbox: built (github.md).
- Search across all of GitHub for what is assigned, what asks a review, and optionally what one opened or is mentioned in; every thirty minutes while Sioul is open, each answer asked with its ETag and the API version: built.
- Tasks in a list kept on this computer only,
github:owner/repo#N, with the reason they are yours; GitHub's state followed, what the person adds kept; an issue that is no longer found is checked before it is dropped: built. Sub-issues and dependencies as links between tasks: not built. - Mail tied to its issue's task through
In-Reply-To, elseMessage-ID; mail about an issue that is not yours makes nothing; theToaddress is never shown, logged or exported: built. - Off unless asked, and nothing is ever written to GitHub.