Skip to content

fix(api): sharing a calendar mails nobody unless sharing.notify allows it - #16

Merged
leemour merged 1 commit into
mainfrom
fix/api-acl-notify
Oct 27, 2026
Merged

leemour merged 1 commit into
mainfrom
fix/api-acl-notify

Conversation

@leemour

@leemour leemour commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Task

Raised in #14 ("Not in this PR"): sharing a calendar through gcal api mails the person it is shared with.

Why

  • Google's acl.insert, acl.patch and acl.update take sendNotifications, and the Discovery snapshot (spec/google/calendar.json) says "Optional. The default is True." (Acl: insert).
  • Since NEED-1857 those methods are offered to agents over MCP at ask, which runs without a question. So an agent could share the owner's calendar and mail a stranger.
  • The owner's rule (WC-PERM-22): anything that mails other people is its own permission key, default deny, checked on every path. gcal already does this for attendees with events.notify.

What

  • New key sharing.notify, default deny (src/config.ts, src/api-keys.ts).
    • Named like events.notify: a domain plus .notify, not tied to one path. sharing is the word --grant sharing already uses.
    • Not api.calendar.acl.notify: api.<method> keys mirror Google's methods one to one, and there is no calendar.acl.notify method.
  • api calendar-acl-insert | patch | update and api call calendar.acl.{insert,patch,update} need it when sendNotifications is absent or true. At deny or readonly the call is refused before any request. The refusal names the key and its level and says to pass --send-notifications false (send_notifications: "false" over MCP, "sendNotifications": false in api call --params) or allow the key.
  • gcal never rewrites the request.
  • If the method's own key already refuses, that refusal comes first, so a denied share is not blamed on its mail.
  • acl.delete is not affected: Google says "there are no notifications on access removal".
  • --dry-run lists sharing.notify with its level. At deny the preview refuses, as it does for events.notify. At ask one question names both keys.
  • Docs: configuration.md, security.md (an agent can still share without mail, using --send-notifications false), the skill, CHANGELOG. test-matrix.md regenerated.

Testing

  • New block in src/api.test.ts, over the CLI and MCP:
    • refused with no request: insert with no flag, insert true, patch, update, api call insert with no param, api call patch with "true"
    • false goes through, and Google gets sendNotifications=false
    • delete is unaffected
    • one question at ask; at allow the request is sent unchanged
    • the method's own deny is named first
    • dry-run behaviour
    • MCP refuses mail and allows "false"
  • Two existing share tests now pass --send-notifications false.
  • 10 of the 13 new tests fail on the old code.
  • Passing: ci:quick, lint, test:coverage (262 tests), generate:check, contracts:check, test:matrix:check, docs:check, package:check, standards:check, security:no-leak.

🤖 Generated with Claude Code

…s it

Google's acl.insert/patch/update take sendNotifications, which defaults to
true, so `api calendar-acl-insert` (and `api call calendar.acl.*`) mailed the
person a calendar was shared with. Since NEED-1857 these methods run at `ask`
over MCP, so an agent could share the owner's calendar and mail a stranger.

The owner's rule (WC-PERM-22): anything that mails other people is its own
permission key, default deny, checked on every path. New key `sharing.notify`
(deny). An acl insert/patch/update whose sendNotifications is absent or true
needs it; at deny/readonly the call is refused before any request, with a hint
to pass --send-notifications false or allow the key. The request is never
rewritten. acl.delete mails nobody (Google: "no notifications on access
removal") and is unaffected. --dry-run lists the key with its level; at ask
one question names both keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leemour
leemour merged commit 2546e1b into main Oct 27, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant