Skip to content

fix(coderd): prevent cross-tenant workspace app rebinding#26103

Merged
dylanhuff-at-coder merged 4 commits into
mainfrom
dylan/plat-262-cross-tenant-app-rebinding
Jun 11, 2026
Merged

fix(coderd): prevent cross-tenant workspace app rebinding#26103
dylanhuff-at-coder merged 4 commits into
mainfrom
dylan/plat-262-cross-tenant-app-rebinding

Conversation

@dylanhuff-at-coder

@dylanhuff-at-coder dylanhuff-at-coder commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Fixes SEC-91 / PLAT-262 by preventing UpsertWorkspaceApp from rebinding an existing app ID to an agent owned by another workspace. The upsert now only rebinds apps when the existing and incoming agents resolve to the same workspace, while still allowing unowned import or dry-run apps to be claimed.

Adds query and provisionerdserver coverage for cross-workspace rejection, same-workspace rebuilds, unowned app claims, and workspace-owned app rebinds to agents without a workspace.

Coder Agents generated.

UpsertWorkspaceApp's ON CONFLICT (id) DO UPDATE rebound an existing app's
agent_id to any incoming agent, keyed only on the app id. A malicious build
could reuse a victim's app id and point it at the attacker's agent, proxying
the victim's app traffic across tenants (SEC-91/PLAT-262).

Guard the update in SQL so an app only rebinds when the existing and incoming
agents resolve to the same owning workspace. Apps whose existing agent resolves
to no workspace, such as template-import or dry-run agents, may be claimed
because they are not cross-tenant victims. Once an app belongs to a workspace,
rebinds to agents from other workspaces or agents that resolve to no workspace
are rejected. insertAgentApp translates the resulting sql.ErrNoRows into a
clear cross-workspace rejection that fails the build.
@linear-code

linear-code Bot commented Jun 5, 2026

Copy link
Copy Markdown

PLAT-262

SEC-91

@dylanhuff-at-coder

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Chat: Review in progress | View chat
Requested: 2026-06-08 19:14 UTC by @dylanhuff-at-coder

deep-review v0.7.1 | Round 1 | d852116..2b7eb36

Last posted: Round 1, 8 findings (2 P2, 3 P3, 2 Nit, 1 Note), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Open provisionerdserver.go:3709 Error message drops app slug; operator gets two raw UUIDs with no human-readable identifier R1 Leorio Yes
CRF-2 P2 Open workspaceapps.sql:59 Commit body/PR description claims "NULL on either side is permitted" but code blocks workspace-to-NULL R1 Mafu-san P2, Leorio P3 Yes
CRF-3 P3 Open agentapi/subagent.go:293 Second caller of UpsertWorkspaceApp does not handle new sql.ErrNoRows contract R1 Knuckle P3, Mafuuu, Knov, Kite, Ryosuke, Meruem, Zoro, Hisoka Yes
CRF-4 P3 Open provisionerdserver.go:3705 Cross-workspace rebind rejection produces no server-side structured log for operator alerting R1 Chopper Yes
CRF-5 P3 Open querier_test.go:7686 Comment says "rebinding it is permitted" but adjacent code tests the opposite direction R1 Gon Yes
CRF-6 P4 Dropped by orchestrator (security context justifies explicit SQL comment) workspaceapps.sql:59 SQL comment carries mechanism narration the SQL shows R1 Gon No
CRF-7 P4 Dropped by orchestrator (querier test covers SQL guard at query level) provisionerdserver_test.go:2404 No provisionerdserver integration test for same-workspace rebuild happy path R1 Bisky No
CRF-8 Nit Open provisionerdserver_test.go:1507 Helper function placed between TestFailJob and TestCompleteJob R1 Ryosuke Yes
CRF-9 Nit Open provisionerdserver.go:3706 Comment third line restates the error return R1 Gon Yes
CRF-10 Note Open provisionerdserver.go:3709 Error says "owned by another workspace" for all rejections including workspace-to-import where no other workspace exists R1 Razor Yes

Round log

Round 1

Panel. 2 P2, 3 P3, 2 Nit, 1 Note posted. 2 dropped (P4). Reviewed against d852116..2b7eb36.
Netero: no findings. Panel: 19 reviewers (bisky, hisoka, mafu-san, mafuuu, pariston, kurapika, razor, kite, knuckle, knov, ging-go, ryosuke, chopper, meruem, gon, leorio, robin, zoro, killua).

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@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.

Solid security fix. The SQL-level WHERE guard on the ON CONFLICT is the right structural decision: it makes cross-workspace rebinding impossible regardless of caller context, and there is no TOCTOU window because PostgreSQL evaluates the guard atomically under the row-level lock. The two-branch NOT EXISTS / OR EXISTS pattern handles all five ownership transitions correctly, and the workspace_builds.job_id UNIQUE constraint guarantees deterministic agent-to-workspace resolution. Test density is 91.4% (445 test lines for 42 production lines), covering all guard branches at the query level and all three job types through the full CompleteJob flow.

Severity counts: 2 P2, 3 P3, 2 Nit, 1 Note.

"I tried to build a case against this change and could not." (Pariston)


coderd/agentapi/subagent.go:293

P3 [CRF-3] This PR changes the contract of UpsertWorkspaceApp: on ID conflict, it now returns sql.ErrNoRows when the cross-workspace guard rejects the rebind. The insertAgentApp caller at provisionerdserver.go:3705 handles this with a descriptive error. This caller wraps all errors generically ("insert workspace app: %w"), passing through the raw sql.ErrNoRows.

Currently safe because line 263 always passes uuid.New(), so ON CONFLICT never fires. But the comment on that same line reads: "NOTE: we may need to maintain the app's ID here for stability." When stable IDs land, collisions will produce a confusing "insert workspace app: sql: no rows in result set" instead of the clear "owned by another workspace" message.

A defensive errors.Is(err, sql.ErrNoRows) check matching what insertAgentApp does, or at minimum a comment noting the changed contract, would close the gap.

(Knuckle P3, Mafuuu, Knov, Kite, Ryosuke, Meruem, Zoro, Hisoka)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/provisionerdserver/provisionerdserver.go Outdated
Comment thread coderd/database/queries/workspaceapps.sql
Comment thread coderd/provisionerdserver/provisionerdserver.go
Comment thread coderd/database/querier_test.go Outdated
Comment thread coderd/provisionerdserver/provisionerdserver_test.go Outdated
Comment thread coderd/provisionerdserver/provisionerdserver.go Outdated
Comment thread coderd/provisionerdserver/provisionerdserver.go Outdated
@dylanhuff-at-coder dylanhuff-at-coder force-pushed the dylan/plat-262-cross-tenant-app-rebinding branch from 2b7eb36 to 6ceaacc Compare June 8, 2026 23:14
@dylanhuff-at-coder dylanhuff-at-coder marked this pull request as ready for review June 9, 2026 15:50
@jdomeracki-coder jdomeracki-coder self-requested a review June 10, 2026 13:41

@jdomeracki-coder jdomeracki-coder 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.

Approved, I like the fail-close approach
Can't really comment on code quality / sticking to convention

@f0ssel f0ssel requested a review from sreya June 11, 2026 18:27
@dylanhuff-at-coder dylanhuff-at-coder merged commit f3c7c23 into main Jun 11, 2026
37 checks passed
@dylanhuff-at-coder dylanhuff-at-coder deleted the dylan/plat-262-cross-tenant-app-rebinding branch June 11, 2026 19:02
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants