fix(security): widget consents and shared files cleared on logout; forged shares refused (#266, #267) #429

Merged
jared merged 1 commits from fix/widget-share-hardening into lotus 2026-10-19 15:18:58 -04:00
Owner

Fixes #266 and #267.

#266, widget consents

  • Remembered approvals (lotus.widgetConsent.<room>.<widget>) survived logout. They applied silently to the next account on the device, and their keys listed the previous account's rooms. clearPlaintextCaches now sweeps the prefix. A test checks the prefix stays in sync with widgetUtils; it's a string constant there so logout doesn't pull widget code into the entry chunk.
  • PROTECTED_STATE_TYPES gains m.space.child, m.space.parent, org.matrix.msc3401.call, org.matrix.msc3401.call.member, m.call.member, io.lotus.voice_limit and io.lotus.room_quality. No widget can be granted them, even with consent.

#267, share target

  • Forged shares: the issue suggested checking Sec-Fetch-Site, but that header never reaches a service worker. I logged what the worker sees: accept, content-type, origin, sec-ch-ua…, with no sec-fetch-*. A check on it would have refused every real share. The worker now uses Origin: it refuses a share from another site, and accepts null, an absent header (the OS share sheet's POST has no web initiator) or the app's own origin.
  • A page can still produce Origin: null (sandboxed frame, no-referrer policy). So /share now says: "This came from another app or website. Check it before you send it." Sending still needs the user to pick a room.
  • Leftover files: both logout paths now await clearSharedPayload(). The server-forced path in ClientRoot also clears the soundboard clip cache, which #57 says every logout path must.

Verified

  • Unit tests: widgetUtils 10/10, plaintextCaches 6/6, swShare 3/3. New: protected types can't be granted; consents swept, unrelated keys kept; Origin rules, including a look-alike host and the wrong scheme.
  • Browser, dev service worker controlling the page:
    • The issue's forged form, auto-submitted from another origin (localhost:8099) → dropped: lands on /home/, no trace of the text.
    • The same form submitted by the app itself → /share with the text and the new notice.

I couldn't test a real Android share sheet here. The null/absent acceptance is what keeps it working.

🤖 Generated with Claude Code

https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5

Fixes #266 and #267. **#266, widget consents** - Remembered approvals (`lotus.widgetConsent.<room>.<widget>`) survived logout. They applied **silently to the next account** on the device, and their keys listed the previous account's rooms. `clearPlaintextCaches` now sweeps the prefix. A test checks the prefix stays in sync with `widgetUtils`; it's a string constant there so logout doesn't pull widget code into the entry chunk. - `PROTECTED_STATE_TYPES` gains `m.space.child`, `m.space.parent`, `org.matrix.msc3401.call`, `org.matrix.msc3401.call.member`, `m.call.member`, `io.lotus.voice_limit` and `io.lotus.room_quality`. No widget can be granted them, even with consent. **#267, share target** - **Forged shares:** the issue suggested checking `Sec-Fetch-Site`, but **that header never reaches a service worker**. I logged what the worker sees: `accept, content-type, origin, sec-ch-ua…`, with no `sec-fetch-*`. A check on it would have refused every real share. The worker now uses **`Origin`**: it refuses a share from another site, and accepts `null`, an absent header (the OS share sheet's POST has no web initiator) or the app's own origin. - A page can still produce `Origin: null` (sandboxed frame, no-referrer policy). So `/share` now says: "This came from another app or website. Check it before you send it." Sending still needs the user to pick a room. - **Leftover files:** both logout paths now `await clearSharedPayload()`. The server-forced path in `ClientRoot` also clears the soundboard clip cache, which #57 says every logout path must. **Verified** - Unit tests: widgetUtils 10/10, plaintextCaches 6/6, swShare 3/3. New: protected types can't be granted; consents swept, unrelated keys kept; Origin rules, including a look-alike host and the wrong scheme. - Browser, dev service worker controlling the page: - The issue's forged form, auto-submitted from another origin (`localhost:8099`) → **dropped**: lands on `/home/`, no trace of the text. - The same form submitted by the app itself → `/share` with the text and the new notice. I couldn't test a real Android share sheet here. The `null`/absent acceptance is what keeps it working. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared added 1 commit 2026-10-19 14:47:15 -04:00
fix(security): widget consents and shared files cleared on logout; forged shares refused (#266, #267)
CI / Build & Quality Checks (pull_request) Successful in 1m53s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 7s
CI / Playwright smoke (e2e) (pull_request) Successful in 12m51s
14f05a3d5c
#266: remembered widget approvals (lotus.widgetConsent.*) survived logout,
so they applied silently to the next account on the device and listed the
previous account's rooms; clearPlaintextCaches now sweeps them. Space
structure, call membership and Lotus room-policy state join
PROTECTED_STATE_TYPES, so no widget can be granted them.

#267: any website could auto-submit a form to /share-target and land the
user in the share picker with attacker-chosen text and files. The worker
now refuses a share whose Origin is another site. (Sec-Fetch-Site, as the
issue suggested, never reaches a service worker: checked in Chromium.)
Origin null/absent, as the OS share sheet sends, stays accepted, and
/share now says the content came from another app or website. Both logout
paths await clearSharedPayload(); the server-forced one also clears the
soundboard clip cache, as #57 intended.

Fixes #266
Fixes #267

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
jared merged commit 7ad2a94e2e into lotus 2026-10-19 15:18:58 -04:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#429