Skip to content

fix(meetings): keep a shared agenda's private folders and sources private - #325

Merged
novoj merged 1 commit into
devfrom
fix/keycloak-integration-tests
Nov 2, 2026
Merged

novoj merged 1 commit into
devfrom
fix/keycloak-integration-tests

Conversation

@novoj

@novoj novoj commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Summary

The Keycloak integration suites don't run in CI, and on dev 7 of their tests failed (#324). While fixing them, the MCP authorization suite showed that a shared agenda handed the owner's private data to other viewers. This PR fixes that leak and brings the suites up to date.

Privacy fix

The fix lives in the shared meeting service, so the web API (/api/meetings/*), MCP user callers and the MCP service client all get the same data for each viewer. The setting: an agenda filed into an Organization folder and into a private folder of its owner.

  • Folders: each viewer sees only folders they can see.
    • The owner sees their own folders.
    • A receiver of a person-shared folder sees their own filing of it.
    • Curators, the Organization account and the service client see the Organization's folders.
    • This applies to meeting detail, meeting lists, the folderId filter (which no longer reveals that a hidden folder exists) and meetingFolderPaths (whose fallback no longer leaks a name).
  • Suggestions: a stored suggestion is returned only when the viewer can see every source it cites: recordings, tasks and mail, including withdrawn ones. Generation applies the same rule to previous suggestions and sourced points before anything reaches the model, so a curator's run cannot paraphrase the owner's private text.
  • taskSourceItemId and snapshotSourceItemId are null when the viewer cannot see them.
  • Task counts in meeting lists count only tasks the viewer can see.

The owner's own view and all write permissions are unchanged. No schema, env or compose changes.

Keycloak suites

Failure Cause Change
SSO (4), MCP sign-in (2) The suites needed the docker-compose.e2e.sso.yml realm imported beforehand. On a bare Keycloak they failed They import and remove their own realm, as the other Keycloak suites do
Service-client tool list (1) Test drift: meeting tools (#293) and task tools (#264) Expected lists updated, with assertions that these tools return only Organization data
Curator owner refusals (2) Test drift: since #221 the owner of a shared recording writes it like a curator Owner allowed. Non-owner members and administrators are still refused

Testing

  • src/tests/regressions/324-shared-agenda-privacy.integration.test.ts: checks each viewer (owner, receiver, curator, Organization account, service client) for folders, suggestions (read and generation), source ids and counts. Each case fails on dev.
  • All 7 *.keycloak.integration.test.ts files pass against Keycloak 26.7.2 (88 tests, single worker).
  • Meetings, MCP and regression integration tests: 976 passed. Whole unit suite: 7,487 passed. Biome and tsc clean.
  • Three rounds of Codex cross review. The last one found nothing; disabling the new filters makes the privacy tests fail.

To run the Keycloak suites: start a bare Keycloak, then set KEYCLOAK_TEST_ISSUER=http://<host>:<port>/realms/master together with TEST_DATABASE_URL.

…vate

The Keycloak integration suites (not run in CI) failed on dev. Fixing them
showed that a shared agenda handed the owner's private data to other viewers.

Privacy fix, in the shared meeting service, so the web API, MCP user callers
and the MCP service client get the same viewer-correct data. With an agenda
filed into Organization and private folders:
- Folders: each viewer sees only folders they can see (the owner their own,
  a receiver of a person-shared folder their received filing, curators, the
  Organization account and the service client the Organization's folders), in
  detail, lists, the folderId filter (which no longer reveals a hidden folder)
  and meetingFolderPaths (whose fallback no longer leaks a name).
- Suggestions: a stored suggestion is returned only when every recording,
  task and mail source is visible to the viewer, withdrawn sources included.
  Generation applies the same rule to previous suggestions and sourced points
  before anything reaches the model, so a curator's run cannot paraphrase the
  owner's private text.
- taskSourceItemId and snapshotSourceItemId are null when hidden.
- Meeting-list task counts count only visible tasks.
The owner's view and all write permissions are unchanged.

Keycloak suites:
- SSO and MCP sign-in import and remove their own realm (the browser e2e
  realm), so all suites run against one bare local Keycloak; before, they
  needed the docker-compose.e2e.sso.yml realm pre-imported.
- The service client's tool lists include the meeting (#293) and task (#264)
  tools, with assertions that they return only Organization data.
- Curator: the owner of a shared recording writes it like a curator since
  #221; non-owner members and administrators are still refused.

closes #324
@claude

claude Bot commented Nov 2, 2026

Copy link
Copy Markdown

Review: fix(meetings): keep a shared agenda's private folders and sources private

Fixes a real privacy leak: an agenda filed into both an Organization folder and the owner's private folder previously exposed the union of both folders' folders/suggestions/sources to every viewer. The fix centralizes viewer-scoped projection in the shared meeting service (meetingFolderNavigation, loadAgendaSourceVisibility/hasVisibleAgendaSources) so web, MCP-user and MCP-service callers get consistent, correctly-scoped data. The accompanying Keycloak test fixes are independent test-drift corrections (owner-as-curator since #221, service tool-list drift from #293/#264, realm-import ordering), not behavior changes.

Verdict: APPROVE

Findings: Blocker 0 | Critical 0 | Major 0 | Minor 0 | Nit 0

Findings

None. Specifically checked and found correct:

  • meetingFolderVisibility scoping of the folderId list filter in listMeetingRows (no longer reveals a hidden folder's existence via 404-vs-empty).
  • agenda-source-visibility.ts's task/item visibility conditions match getVisibleTasks's conditions exactly (status, deletedAt, kind, taskVisible), so the generation-time check (agendaSuggestionInputs) and the read-time check (getMeeting) can't diverge.
  • olderTasks was changed to re-fetch real folder ids via meetingFolders(db, meetingItemId, ownerUserId) instead of reusing detail.folders (which now holds viewer-projected, possibly alias-remapped ids) — this avoids a bug the folder-visibility projection would otherwise have introduced.
  • taskSourceItemId/snapshotSourceItemId nulled when the referenced item isn't in the viewer's visible set; taskSource derived consistently from the same visibleItems map.
  • Service-client (MCP) task/meeting tool lists and the new src/tests/regressions/324-mcp-service-meeting-folders.test.ts / extended authz.keycloak.integration.test.ts confirm the org-scoped viewer can't see private sources, folders, or task pointers, including after folder withdrawal.
  • No schema/env/compose changes, matches the PR's claim; no ADR needed (this corrects a known, already-documented risk from the prior agenda-filing decisions rather than introducing a new fork).

Missing tests

None found — src/tests/regressions/324-shared-agenda-privacy.integration.test.ts is unusually thorough (folders, suggestions read+generation, task-source pointers, counts, person-shared filing/revocation, across owner/receiver/curator/org/service viewers), and the Keycloak suites add live end-to-end coverage through real tokens.

@novoj
novoj merged commit b27189e into dev Nov 2, 2026
8 checks passed
@novoj
novoj deleted the fix/keycloak-integration-tests branch November 2, 2026 12:05
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