Skip to content

feat(acl): chat-surface ACL (append_message + list_messages)#171

Merged
jrosskopf merged 1 commit into
mainfrom
feat/acl-chat-surface
Jun 15, 2026
Merged

feat(acl): chat-surface ACL (append_message + list_messages)#171
jrosskopf merged 1 commit into
mainfrom
feat/acl-chat-surface

Conversation

@jrosskopf

Copy link
Copy Markdown
Contributor

Closes the chat-surface confidentiality gap the read/write ACL work left open: list_messages/append_message were world-read/write, so any member's token could read or append to another member's chat history.

A chat group (ADR-13: chat_group_id := community_member_id) is now private to its owning member, under the same ESCUREL_WRITE_ACL flag (off|log|enforce):

  • Indexer::may_access_chat — owner = the community_member instance whose id == chat_group_id (its owner_field, e.g. credential) vs caller sub; admin bypasses; an unknown/unowned chat group is ungated (compat).
  • append_message denies with a forbidden error; list_messages denies with an empty page (non-leaking, like expand→null).

Tests (no mocks, real HTTP MCP): chat_acl.rs — owner appends+reads / non-owner blocked / admin reads any / off-mode open / unknown-chat ungated. Instance read + write ACL e2e regression stays green; clippy clean.

This is point 2 of the ACL follow-ups (chat surface). Remaining: capture_event/assign_event event ACLs + point 3 read gaps (resolve/neighbours/run_stored_query).

🤖 Generated with Claude Code

The chat surface was world-read/write: any member's token could read or
append to another member's chat history via list_messages/append_message.
Close it under the same ESCUREL_WRITE_ACL flag: a chat group (ADR-13:
chat_group_id := community_member_id) is private to its owning member.

Indexer::may_access_chat resolves the owner = the community_member instance
whose id == chat_group_id (its owner_field, e.g. credential) vs the caller
sub; admin bypasses; a chat_group_id with no owning instance (or unresolvable
owner) is ungated (compat for non-member chat groups). append denies with a
forbidden error; list_messages denies with an EMPTY page (non-leaking, like
expand→null). Off → legacy open; log → warn+allow; enforce → block.

Tests (no mocks, real HTTP MCP): escurel-server/tests/chat_acl.rs —
owner-appends+reads / non-owner-blocked / admin-reads-any / off-open /
unknown-chat-ungated. Read + write ACL e2e regression green; clippy clean.
@jrosskopf jrosskopf merged commit dd02d9c into main Jun 15, 2026
1 check 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