Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Calendar Provider

ASecurity

Patterns for integrating business calendar providers (Google Workspace, Microsoft 365, Zoho Workplace, business CalDAV). OAuth flow, push subscriptions, echo suppression, etag concurrency, and write-back via the outbox pattern.

12 stars
0 votes
0 copies
0 views
Added 9/28/2026
ai-agentsgoapifrontendsecurity

Works with

cliapi

Security Analysis

A100/100

Scanned 9/28/2026

Install to Claude Code

$npx -y skills add Nmor/the-claude-council --skill calendar-provider --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Calendar Provider?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Calendar Provider
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nmor-calendar-provider/badge)](https://www.skillsdirectory.com/skills/nmor-calendar-provider)

More formats (shields.io, HTML) on the badges page.

Files
SKILL.md
---
name: calendar-provider
description: Patterns for integrating business calendar providers (Google Workspace, Microsoft 365, Zoho Workplace, business CalDAV). OAuth flow, push subscriptions, echo suppression, etag concurrency, and write-back via the outbox pattern.
---

# Calendar Provider Integration

> **Size budget: 17 KB** — `token-budget.mjs --check`.

Companion skill to `provider-research` and the global
`official-docs-first` rule. Activates when touching any
calendar-provider integration code (`lib/calendar/providers/*`,
calendar OAuth, sync, write-back, alarms).

## Scope

**Business providers only.**

| Provider | Auth | Push? | Cron floor |
| --- | --- | --- | --- |
| Google Workspace | OAuth — `openid email profile calendar.events calendar.readonly` | Yes — Push Notifications API, channel TTL 7 days, X-Goog-Channel-Token HMAC | n/a (push covers it) |
| Microsoft 365 Business | OAuth — `openid email profile offline_access Calendars.ReadWrite` | Yes — Graph change notifications, TTL up to 4230 min, `clientState` HMAC | n/a |
| Zoho Workplace | App-specific password — CalDAV `caldav.zoho.com` | No — Zoho has no push | 60s active tab, 5min otherwise |
| Generic business CalDAV (Fastmail / Nextcloud / Posteo / mailbox.org) | App-specific password — caller-supplied URL | No — RFC 4791 has no push | 60s active, 5min otherwise |

**Out of scope:** Apple iCloud (consumer), personal Gmail / Outlook /
Yahoo / Proton (consumer). Rejection lives in `lib/emailDomainPolicy.ts`
at signup AND social-callback parity.

## Required reading before any change

- `docs/provider-research/google-calendar.md` — scopes, push channel
  TTL, Workspace admin-policy interactions.
- `docs/provider-research/microsoft-graph.md` — `Calendars.ReadWrite`
  vs `Calendars.ReadWrite.Shared`, `tid` claim for tenant rejection,
  change-notification renewal.
- `docs/provider-research/zoho-workplace.md` — Application-Specific
  Password format, Workplace server URL distinct from personal Zoho.
- `docs/provider-research/fastmail-nextcloud-posteo.md` — per-provider
  server URL formats.

If any of these doesn't exist or hasn't been touched within 6 months,
refresh it first (see `provider-research` skill).

## Core invariants

### 1. Email-match server-side

A user signed in as `alice@acme.com` can ONLY connect a calendar
whose verified email matches `alice@acme.com`. Enforced in
`calendarOAuth.ts` after the token exchange, before persisting the
connection. Mismatch → 403 `email_mismatch` + audit row.

For CalDAV, verify the `current-user-principal` returned by PROPFIND
matches the username the user entered.

### 2. Cell-bound + plan-gated

Every read/write route runs through `authenticate()` (cell check) +
`checkFeature(orgId, "calendarImport")` (PRO+). Write routes
additionally check the per-connection `writable` flag and the
per-feature `auto_write_prefs` toggle.

### 3. Token refresh persistence

When the provider returns a refreshed access token, the calling lib
MUST persist it back to the connection row via the shared
`persistRefreshedTokens` helper. NOT persisting means every sync
re-refreshes from the (potentially long-since-rotated) refresh token
— wastes API calls and trips reuse-detection.

Code shape:

```ts
const client = await buildGoogleCalendarClient(connection, {
  onTokenRefresh: (newTokens) =>
    persistRefreshedTokens(connection.connection_id, newTokens),
});
```

### 4. Echo suppression on push

When we write to a provider, we stamp the event with
`app_origin_id` extended property. The provider push channel
then sends the event back to us as a "new event". The sync worker:

1. Looks up the row via the `origin_index` GSI by
   `app_origin_id`.
2. If status = `pending_external_write`: reconcile (update etag +
   external_event_id, flip to `synced`).
3. If status = `synced`: no-op (recognise our own echo).
4. If status = `synced` AND local copy is stale: provider wins on
   title/time, we keep alarm bindings.

Without this the sync creates a duplicate for every event we wrote.

### 5. Etag-guarded writes

On update, supply the cached `external_etag`. On `412 Precondition
Failed`:

1. Re-fetch the latest provider state.
2. Three-way merge (provider wins on title/time, we keep alarm
   bindings).
3. Retry up to 3×.
4. On persistent failure → status `write_conflict` + audit row + UI
   banner.

### 6. Outbox-published writes

Manual writes (user clicks "+ Add event" or any "mirror to calendar"
path) go through `lib/outbox.ts` so retries and DLQ routing are
handled centrally. The HTTP response is 202 with the local row
already visible to the client. The outbox worker dispatches via
`lib/calendar/writeBack.ts`.

Idempotency key: `cal:write:<app_origin_id>:v<version>`.

### 7. PII envelope at rest

Event `title`, `description`, `attendees` are encrypted via the
`piiEncryption.ts` envelope. Free/busy buckets never decrypt — only
the per-event detail dialog does.

### 8. Cron-fallback active-window logic

CalDAV (Zoho + generic) connections poll on a 60s cron when the user
has had a WS connection open in the last 5 min (active-user fast
path), 5min otherwise. `last_active_at` is stamped by the
`useCalendarSocket` composable. Without this signal the default
floor is 15 min.

### 9. Write-back consent toggles

Auto-write per source-type (`task`, `todo`, `standup_run`,
`okr_checkin`, `roadmap_milestone`, `poll_book`, `invite`,
`ai_event`) is opt-in per user in `users.calendar_auto_write_prefs`.
Default OFF for every auto path. Manual writes (the `/calendar` view's
"+ Add event" button, poll closing, AI confirmation dialog) are always
allowed.

### 10. Health-sweep + reconnect banner

`calendarHealthSweep` cron runs hourly. After 3 consecutive 401/403
returns from a provider, the connection is flipped to
`status=token_expired` (refreshable) or `status=revoked` (not), an
audit row is written, and the frontend shows a sticky "Reconnect
`<provider>`" banner.

## Test contract

For every provider-touching change:

1. **OAuth happy-path test** — calls the provider mock, verifies
   email-match guard, persists encrypted tokens.
2. **OAuth email-mismatch test** — verifies 403 `email_mismatch` +
   audit row.
3. **Sync echo-suppression test** — writes a row, simulates provider
   push, asserts no duplicate.
4. **Etag conflict test** — provider returns 412, asserts three-way
   merge + status transition.
5. **Token-refresh-persistence test** — fakes a refresh, asserts the
   connection row's `access_token_encrypted` field changed.
6. **Health-sweep test** — 3 consecutive 401s, asserts status
   transitions + banner-emit hook.

## Cross-references

- `~/.claude/rules/common/official-docs-first.md` — primary-source
  citations required.
- `provider-research` skill — workflow for the citations.
- `~/.claude/rules-library/common/no-discards.md` — refresh-token rotation
  must be bound + persisted, never dropped.
- The consuming project's `docs/<calendar-feature>.md` — feature
  page describing user-visible behaviour.
- The consuming project's `docs/runbook.md` — recovery procedures
  for reconnect / token-expiry / write-conflict states.

## Purpose

Principal-level multi-provider calendar integration: OAuth scope
negotiation (read vs write vs free-busy), incremental sync via
`syncToken` (Google) / delta queries (Microsoft Graph) / CTag +
ETag (CalDAV), push notification channels with TTL rotation
(Google watch, Graph change-notifications, CalDAV `WebDAV-Sync`),
write conflict resolution (ETag If-Match), recurring-event
expansion (RFC 5545 RRULE), timezone correctness, business-tier
vs personal-tier rejection at sign-up, refresh-token storage with
rotation detection, and the iMIP / iTIP message contracts for
invite + reply flows.

**Negative scope** (NOT what this skill covers):

- Calendar UI components — out
- Local calendar (.ics file) parsing in isolation — see RFC 5545
  directly
- iCloud consumer-tier integration — explicitly out per
  business-tier scope policy
- Custom scheduling logic (free-busy availability matching) — see
  domain-specific scheduling skills

## When NOT to use

- Single-provider integrations where multi-vendor abstraction is
  premature (Google-only) — wire directly to the SDK
- Read-only consumption of an exported `.ics` URL with no live
  sync — parse the file; no provider integration needed
- Synchronous one-shot lookups (e.g., "what's my next meeting")
  where push channels are over-engineered

## Standards Cited

- **RFC 4791 (CalDAV)** — calendar access via WebDAV
- **RFC 6638 (CalDAV Scheduling)** — invite + reply protocol
- **RFC 5545 (iCalendar)** — VCALENDAR / VEVENT / RRULE format
- **RFC 5546 (iTIP)** — calendar transport
- **RFC 6047 (iMIP)** — iCalendar over MIME (email-borne invites)
- **RFC 6749 (OAuth 2.0)** — auth framework
- **RFC 7636 (PKCE)** — public-client flow
- **Google Calendar API v3 docs** (developers.google.com/calendar)
- **Microsoft Graph API v1.0** (learn.microsoft.com/graph)
- **W3C Push API** (web push channel deliveries on the client side)
- **OWASP ASVS 4.0.3 §3.5 (Token-based Session Management)** —
  refresh-token rotation + reuse detection
- **OWASP ASVS 4.0.3 §4 (Access Control)** — per-tenant scope
  enforcement

## Anti-Patterns

| Pattern | Why bad | Correct alternative |
| --- | --- | --- |
| Full sync on every poll | API quota burn; slow | `syncToken` / delta-query incremental sync |
| Push channel without TTL renewal cron | Watch expires (Google: 7 days), updates silently stop | Scheduled re-subscribe before `expiration` |
| Storing refresh token unencrypted | Vault breach = calendar access for every user | Encrypt at field level + rotate on reuse |
| Single-write without ETag | Lost update on concurrent edit | `If-Match: <etag>` + 412 retry on conflict |
| Timezone derived from server clock | Wrong-day bug across DST + cross-region users | Always include `TZID` + canonical IANA zone |
| Treating personal Gmail as Workspace | Wrong scope set; tenant policy mismatch | Reject at signup via `tid` claim / email-domain blocklist |
| Ignoring `cancelled` / `tentative` status | Stale UI shows past invites | Parse `STATUS:` field on every event |
| RRULE expansion in DB query | Cartesian explosion | Expand on demand at read-time, capped horizon |
| Polling for write confirmation | Race + duplicate writes | Push notification + idempotency key |
| Single-provider abstraction leaking to UI | Switching cost on second provider is total rewrite | Domain-shape boundary + adapter per provider |

## Verification Checklist

- [ ] OAuth scope is minimum-necessary (read vs write vs free-busy)
- [ ] `docs/provider-research/<provider>.md` exists + cites primary
      sources per `official-docs-first.md`
- [ ] Refresh-token rotation + reuse-detection wired
- [ ] Personal-tier vs business-tier rejected at sign-up
      (documented in plan + handler)
- [ ] Incremental sync uses `syncToken` / delta / CTag (no
      poll-and-diff)
- [ ] Push channel TTL renewal cron scheduled
- [ ] ETag `If-Match` on every write
- [ ] Timezone stored as IANA zone + `TZID` round-tripped
- [ ] RRULE expansion capped to a horizon (e.g., 18 months out)
- [ ] iMIP / iTIP message handling tested (accept / decline /
      counter)
- [ ] Webhook signature verification on every change notification
- [ ] Audit log of every write per `audit-logging.md`
- [ ] Failure modes documented in `docs/runbook.md`

## Cross-References

- `~/.claude/skills/provider-research/SKILL.md` — primary-source
  citation discipline
- `~/.claude/skills/web-push-notifications/SKILL.md` — sister
  notification surface
- `~/.claude/skills/api-design/SKILL.md` — calendar API consumer
  patterns
- `~/.claude/rules/common/official-docs-first.md` — provider docs
  research mandate
- `~/.claude/rules-library/common/secrets-management.md` — refresh-token
  storage
- `~/.claude/rules-library/common/audit-logging.md` — write audit
- `~/.claude/agents/security-reviewer.md` — Council Division 4
- `~/.claude/agents/architect.md` — Council Division 1

## Why this skill exists

Multi-provider calendar integrations fail in predictable ways:
quota exhaustion from full polling, silent stop after watch
channel expiry, lost updates from missing ETag, wrong-day bugs
from naive timezone handling, personal-tier accounts leaking into
business scope. The patterns above codify the production posture:
incremental sync, push channel with TTL renewal, ETag-checked
writes, IANA-zoned timestamps, business-tier-only at signup,
refresh-token rotation with reuse detection. Teams that adopt
these survive scope changes; teams that don't rebuild from scratch
every time a provider deprecates a scope.

## Learning hooks

Per `~/.claude/rules/common/continuous-learning-mandate.md`:

**Signals to watch**:

- Calendar handler written before `docs/provider-research/<provider>.md` exists (RFC 4791 /
  Microsoft Graph / Google Calendar primary-source citation skipped)
- Refresh-token rotation silently dropped (no-discards extension fires on token write path)
- Personal Gmail / Outlook.com / iCloud consumer accepted instead of rejected at signup
  (commercial-tier scope drift)
- Write-conflict (ETag mismatch / If-Match-failed) silently overwriting remote state (lost-update
  class)
- Webhook channel subscription not auto-renewed (Google Calendar 7-day TTL, Graph subscription
  expiry) — push deliveries silently stop
- Recurrence expansion (RRULE) computed client-side without timezone-aware library (DST / locale
  bugs)
- All-day vs floating vs zoned events conflated (Outlook + Google + Apple disagree on semantics)
- Reconnect flow doesn't preserve in-flight pending writes (data loss on auth-expiry)

**Refinement candidates**:

- New provider row when a new calendar service (FastMail JMAP, ProtonMail Calendar, Zoho Calendar)
  is integrated
- Conflict-resolution policy update when a recurring write-collision pattern surfaces
  (last-write-wins vs OT vs CRDT decision)
- Webhook-renewal cron pattern when push subscriptions drop silently across multiple incidents
- Timezone-handling addendum when DST / locale bugs recur (e.g., floating events across user's home
  / travel locales)

Attribution

NmorNmor
View sourceMore from Nmor →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode that cuts output tokens while keeping technical accuracy. Levels: lite, full, ultra and the wenyan variants. Use for /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".

1074701 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

695601 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3351 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

691 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →