Skip to content

feat: add chat sharing foundation#25041

Merged
DanielleMaywood merged 11 commits into
mainfrom
dm/chat-sharing-foundation
May 18, 2026
Merged

feat: add chat sharing foundation#25041
DanielleMaywood merged 11 commits into
mainfrom
dm/chat-sharing-foundation

Conversation

@DanielleMaywood

@DanielleMaywood DanielleMaywood commented May 7, 2026

Copy link
Copy Markdown
Contributor

Note

🤖 This PR was written by Coder Agent on behalf of Danielle Maywood

Stacked on #25080. Adds the chat sharing behavior foundation on top of the database-only ACL foundation.

This PR adds RBAC/authz wiring, the deployment kill switch, the chat:share scope, linked-chat file fallback authorization, and write-path hardening needed before shared chat readers can exist. It intentionally does not add public chat ACL endpoints. Those remain in #24968, stacked above this branch.

The scope of this stack is intentionally read-only sharing. Chat owners can grant read access to specific users or groups, and recipients can view the shared chat, messages, and linked files. Shared readers cannot send messages, submit tool results, edit messages, rename chats, interrupt runs, regenerate or propose titles, or reshare the chat.

Write-level chat sharing is deferred because chat execution still depends on the owner's identity and credentials. Solving that correctly needs a larger identity-management design rather than a small ACL extension. Redacting or hiding tool-call details is also deferred to keep this stack small and reviewable.

This uses ACLs because Coder already has per-user and per-group ACL infrastructure, and the product requirement here is identity-scoped sharing, for example "only Danielle can view this", not anonymous "anyone with the link" sharing. The underlying mechanism is still swappable if we decide later that chat sharing needs a different model.

Implementation plan

Alternative split plan for chat ACL foundation

Question

Consider splitting current PR #25041 into two stacked PRs:

  1. Database-layer foundation.
  2. Actual ACL, RBAC, dbauthz, and handler behavior.

Recommendation

This split is feasible and probably cleaner than keeping everything in one
foundation PR if we also accept the SQL view refactor from DEREM-3.

The split should be:

  1. dm/chat-acl-database-foundation, base main.
  2. dm/chat-sharing-foundation, base dm/chat-acl-database-foundation.
  3. dm/chat-sharing, base dm/chat-sharing-foundation.
  4. dm/chat-sharing-frontend, base dm/chat-sharing.

PR 1: database-layer foundation

Purpose: add inert persistence and effective-read query infrastructure without
making chat sharing usable.

Include:

  • coderd/database/migrations/000489_chat_acl_sharing.*.sql.
  • coderd/database/dump.sql schema updates.
  • coderd/database/types.go ChatACL type.
  • coderd/database/models.go generated chat ACL fields.
  • coderd/database/sqlc.yaml overrides for chat ACL columns and any view
    columns.
  • coderd/database/queries/chats.sql raw ACL persistence queries:
    • GetChatACLByID.
    • UpdateChatACLByID.
  • coderd/database/queries/chats.sql effective chat read view or equivalent
    query changes.
  • Generated database files from make gen.
  • enterprise/audit/table.go ACL tracking.
  • Database tests for:
    • ChatACL scan/value/RBAC conversion.
    • effective ACL inheritance through the view/query path.
    • auto-archive ACL audit mapping, if fixed in this layer.

Do not include:

  • dbauthz authorization wrappers for chat ACL behavior.
  • ChatConverter() SQL authz mapping.
  • DisableChatSharing deployment option.
  • RBAC ActionShare exposure for chats.
  • HTTP handler authorization changes.

SQL view refactor belongs here if we do it. This PR can define something like
chats_effective_acl or chats_with_effective_acl, then use it for read
queries that should return inherited ACLs. The raw table remains available for
writes and raw ACL reads.

PR 2: behavior foundation

Purpose: turn the database foundation into enforced ACL behavior.

Include:

  • dbauthz wrappers:
    • GetChatACLByID.
    • UpdateChatACLByID.
    • chat file linked-chat fallback behavior.
    • GetChatsByWorkspaceIDs post-filter behavior using effective ACLs.
  • coderd/rbac/regosql/configs.go ChatConverter().
  • chat ACL kill switch behavior.
  • policy.ActionShare and generated RBAC/API scope files.
  • agents-access role decision if regular users should share their own chats.
  • handler ActionUpdate hardening in coderd/exp_chats.go.
  • dbauthz and handler tests for actual authorization behavior.

Cost

The split adds branch work and at least one more draft PR, but reduces review
complexity.

Extra cost:

Reduced cost:

  • Reviewers can approve schema/query mechanics separately from authorization
    behavior.
  • The SQL view refactor becomes easier to justify because it lives in the
    database-only PR.
  • Review feedback like DEREM-3 and DEREM-5 can be resolved in the lower PR
    without mixing in RBAC and handler changes.

Risks

  • sqlc may generate view-backed structs or raw JSON types unless the view and
    overrides are shaped carefully.
  • Query names returning database.Chat must keep the same Go signatures or the
    upper PR will churn.
  • Any read query using effective ACLs must be explicit. Raw write/locking paths
    should continue using the chats table.
  • More stack management: every upper PR must be rebased after rewriting the
    lower PRs.

Decision point

If we accept this split, revise the current feedback plan:

  • Move C005 and C022 into PR 1.
  • Move C023 into PR 1 if we treat audit mapping as database-layer data shaping.
  • Keep C003, C004, C007, C008, C009, C010, C012, C013, C016, C017, C018,
    C019, C020, C024, and C027 in PR 2 unless they directly depend on generated
    database output.

Recommendation: do the split if we want to accept the SQL view refactor now.
Do not do it just for size reduction, because the current PR is already stacked
and another layer increases coordination cost.

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown

Docs preview

📖 View docs preview for docs/admin/security/audit-logs.md

@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch 2 times, most recently from 1f00c89 to 57c2c31 Compare May 7, 2026 16:00
Comment thread coderd/database/dbauthz/dbauthz_test.go Outdated
Comment thread coderd/database/dbauthz/dbauthz_test.go
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ACL persistence model, RBAC wiring, and write-path hardening are well-structured. The CHECK constraint chat_acl_only_on_root_chats enforcing ACL-inheritance at the schema level is a strong design choice. The four new ActionUpdate guards on mutation endpoints are consistent and correct. The owner-scoped markChatAsRead guard in streamChat is a thoughtful detail.

5 P2, 9 P3, 4 Nit, 1 Note.

The dominant theme is inconsistent ACL inheritance: three query families apply the CTE, three do not, and there is no type-level distinction between a Chat with effective ACLs and one with stored (empty) ACLs. GetChatsByWorkspaceIDs is user-facing and will deny shared readers access to sub-chats once the stacked PR adds shared-reader paths. Ryosuke's suggestion of a SQL VIEW to bake in inheritance as the default read path would eliminate this class of bug structurally.

The kill switch has two gaps: the SQL-filtered GetAuthorizedChats path unconditionally matches raw ACL columns (mirroring the same gap in workspace sharing), and the write path (UpdateChatACLByID) does not check ChatACLDisabled(), allowing ACL accumulation while sharing is disabled.

chatFromAutoArchiveRow in dbpurge does not map the new UserACL/GroupACL fields, producing audit records with nil ACLs for shared chats.

Process note: the PR body's implementation plan is a literal $(sed ...) shell expansion that does not render in GitHub markdown. The plan content is unrecoverable from the PR itself.

"Shall I show you?" (Hisoka, on the 16 call sites for GetChatByIDForUpdate that could one day reach shared readers on sub-chats)

Design notes for the stacked PR:

  • ActionShare is currently unreachable for regular users; only owner and orgAdmin can share. If owner-initiated sharing is intended, the agents-access role needs ActionShare.
  • has_unread reflects the owner's read position. Shared readers will see the owner's read state, not their own.
  • GetChatACLByID returns raw (stored) ACLs, not effective (inherited) ACLs. Sub-chats always return empty. The stacked PR's "show current ACLs" endpoint needs to resolve to the root chat.

Generated by Coder Agent (deep-review v0.4.8). 13-reviewer panel, round 1.


coderd/database/queries/chats.sql:1482

P2 [DEREM-5] GetChatsByWorkspaceIDs is the only user-facing read query that returns Chat objects without the ACL-inheritance CTE. Sub-chats have user_acl = '{}', group_acl = '{}' by constraint. When fetchWithPostFilter in dbauthz calls RBACObject() on these rows, the RBAC object carries empty ACLs, and any grant from the root chat is invisible. A shared reader who can access the root chat via inherited ACLs will be silently denied sub-chats through this path.

The handler at exp_chats.go:281 calls this through dbauthz in user context. Three of six Chat-returning query families apply the CTE; three do not. RBACObject() cannot distinguish which shape of Chat it received.

"The inconsistency is between these two groups." (Ryosuke)

A SQL VIEW that bakes in the COALESCE(root.user_acl, c.user_acl) join would make inheritance the default read path and make this class of bug structurally impossible. (Bisky P2, Hisoka P2, Mafuuu P2, Knov P2, Kite P2, Ryosuke P2, Knuckle P2, Kurapika P3)

🤖

coderd/database/dbpurge/dbpurge.go:460

P2 [DEREM-9] chatFromAutoArchiveRow stops at ClientType and does not map UserACL or GroupACL. The AutoArchiveInactiveChatsRow struct carries both as json.RawMessage, but neither is copied to the returned database.Chat. The audit table marks both fields as ActionTrack, so auto-archive audit records will show nil ACLs regardless of what the chat stored.

The fix follows the same Scan pattern used for Labels at line 425:

var userACL database.ChatACL
if err := userACL.Scan([]byte(r.UserACL)); err != nil {
    logger.Warn(...)
}
// same for GroupACL

(Chopper P2, Knov P3)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/modelmethods.go
Comment thread coderd/database/dbauthz/dbauthz.go Outdated
Comment thread coderd/database/queries/chats.sql
Comment thread coderd/database/queries/chats.sql
Comment thread coderd/rbac/regosql/configs.go
Comment thread coderd/database/dbauthz/dbauthz.go Outdated
Comment thread coderd/database/types.go Outdated
Comment thread coderd/database/queries/chats.sql
Comment thread coderd/database/modelmethods_internal_test.go
Comment thread coderd/rbac/object.go Outdated
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch from 57c2c31 to 32efc17 Compare May 8, 2026 14:52
@DanielleMaywood DanielleMaywood changed the base branch from main to dm/chat-acl-database-foundation May 8, 2026 14:53

This comment was marked as duplicate.

This comment was marked as duplicate.

This comment was marked as duplicate.

This comment was marked as duplicate.

This comment was marked as duplicate.

This comment was marked as duplicate.

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong response to R1. All 5 P2 findings addressed (4 in this PR, 1 in #25080). All 3 contested nits closed by unanimous panel vote (6/6 accept defenses). The PR split into #25080 (database foundation) + this PR (behavior) resolves the CTE duplication and ACL inheritance inconsistency structurally.

0 P2, 4 P3, 1 P4.

The kill switch now enforces at three layers (RBAC object, SQL filter, dbauthz guard). The canReadLinkedChat helper with LIMIT 1 replaces the unbounded iteration cleanly. Write-path hardening covers all mutation handlers and has rejection tests.

Two testing gaps remain: the GetAuthorizedChatsByChatFileID SQL path (file access for shared readers) is tested only through mocks, and the group ACL SQL matcher has no real-DB coverage. Both are fail-closed (over-deny, not over-grant), so the risk is degraded UX, not a security hole.

One behavior inconsistency: regenerateChatTitle and proposeChatTitle return 404 (not 403) to shared readers who demonstrably have read access. The other four hardened handlers return 403.

"One real-DB test that creates a chat, shares it via ACL, links a file, and reads that file as the recipient would nail this shut." (Hisoka)


Generated by Coder Agent (deep-review v0.4.8). 6-reviewer panel, round 2.


coderd/exp_chats.go:3486

P3 [DEREM-24] regenerateChatTitle and proposeChatTitle return httpapi.ResourceNotFound(rw) on ActionUpdate denial. The four handlers hardened in this PR (patchChat, patchChatMessage, deleteChatQueuedMessage, interruptChat) all return httpapi.Forbidden(rw). A shared reader who can stream and read messages gets 404 for title operations, implying the resource doesn't exist, contradicting the read path that just served it. 403 is the correct response for a reader who lacks update permission on a resource they demonstrably have read access to.

Fix: change both handlers to httpapi.Forbidden(rw) to match the pattern this PR establishes. (Mafuuu P3)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/modelqueries.go
Comment thread coderd/database/querier_test.go
Comment thread coderd/exp_chats.go
Comment thread coderd/database/modelqueries.go
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch from 32efc17 to d43fb9f Compare May 11, 2026 09:43
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-acl-database-foundation branch 2 times, most recently from 545192e to 84c4226 Compare May 11, 2026 10:37
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch 2 times, most recently from 295dbe5 to d00ecb3 Compare May 11, 2026 12:55
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-acl-database-foundation branch from f6cbfda to cfa03fc Compare May 12, 2026 12:28
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch from d00ecb3 to ac20edb Compare May 12, 2026 12:28

Copy link
Copy Markdown
Contributor Author

🤖 This comment was written by Coder Agent on behalf of Danielle Maywood 🤖

Agreed. ChatParam has already proved the caller can read the chat before these handlers run, so ActionUpdate denial should be 403 rather than another 404. I will change regenerateChatTitle and proposeChatTitle to return Forbidden for that branch and update/add tests for the shared read-only case. Fixed in f01b1d508b8c.

@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch from f01b1d5 to dfc4533 Compare May 13, 2026 10:46
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All R2 findings resolved. DEREM-23 (markChatAsRead guard untested) closed by unanimous panel vote (3/3 accept defense). The dbauthz layer enforces ActionUpdate on UpdateChatLastReadMessageID, so the stream guard is defense-in-depth preventing log noise, not a correctness boundary.

0 P2, 1 P3, 1 P4.

The test suite is now comprehensive: real-DB coverage for user ACL sharing, group ACL sharing, linked-chat file authorization, kill switch denial, and the OwnedOnly/SharedOnly/ViewerID validations. TestChatReadOnlySharedWriteHandlers covers 6 of 9 write handlers; the remaining 3 (PostChatMessages, PromoteChatQueuedMessage, PostChatToolResults) are protected by the same ActionUpdate check but lack explicit shared-reader coverage.

27 findings across 3 rounds: 5 P2, 14 P3, 2 P4, 3 Nit, 1 Note. 22 fixed, 4 contested and closed by panel vote, 1 acknowledged. 2 open (P3 + P4).

"No worthy opponents this round." (Hisoka)


Generated by Coder Agent (deep-review v0.4.8). 3-reviewer panel, round 3.

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/exp_chats_test.go
Comment thread coderd/database/dbauthz/dbauthz.go
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 27 findings across 4 rounds resolved. Zero open findings.

22 fixed by author, 4 contested and closed by panel vote, 1 acknowledged as intentional scaffolding.

The PR delivers a solid chat sharing foundation: three-layer kill switch enforcement (RBAC object, SQL filter, dbauthz guard), consistent write-path hardening across all 9 mutation handlers with shared-reader rejection tests, linked-chat file authorization with hoisted SQL filter preparation, and comprehensive real-DB test coverage for user ACL, group ACL, and file-link authorization paths.


Generated by Coder Agent (deep-review v0.4.8). 4 rounds, 27 findings.

🤖 This review was automatically generated with Coder Agents.

@DanielleMaywood DanielleMaywood force-pushed the dm/chat-acl-database-foundation branch from eee5f5f to d443ca2 Compare May 14, 2026 15:25
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch from f6b913d to 4b52604 Compare May 14, 2026 16:01
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-acl-database-foundation branch from d443ca2 to 499b122 Compare May 14, 2026 16:01
Base automatically changed from dm/chat-acl-database-foundation to main May 14, 2026 16:18
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch from 4b52604 to b7d9745 Compare May 14, 2026 16:41
@DanielleMaywood DanielleMaywood marked this pull request as ready for review May 14, 2026 16:41
@DanielleMaywood DanielleMaywood requested a review from Emyrk as a code owner May 14, 2026 16:41
@DanielleMaywood DanielleMaywood marked this pull request as draft May 14, 2026 16:41
@DanielleMaywood DanielleMaywood marked this pull request as ready for review May 14, 2026 19:31
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch from 1917b45 to b1c44d5 Compare May 15, 2026 16:04
Comment thread coderd/database/dbauthz/dbauthz.go
Comment thread coderd/rbac/roles.go
Comment thread coderd/exp_chats_test.go
Comment thread coderd/database/types.go
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-sharing-foundation branch from 7e8ecf0 to c544179 Compare May 18, 2026 21:15
@DanielleMaywood DanielleMaywood merged commit 170a6e1 into main May 18, 2026
30 of 31 checks passed
@DanielleMaywood DanielleMaywood deleted the dm/chat-sharing-foundation branch May 18, 2026 21:32
@github-actions github-actions Bot locked and limited conversation to collaborators May 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants