Currently when a user clicks either the Cancel or Allow button on the
authorization page the client app URI is executed but the page does not
land to the main dashboard page, leaving the two buttons open for
multiple clicks from the user. Aside from the potential problems it
might cause by activating the callback URI multiple times, the page also
provides poor UX because users usually expect the authorization tab to
return to the dashboard.
The consent page now executes the OAuth2 callback (auth code on Allow,
`access_denied` on Cancel) and hides the two buttons and updates the
existing description with a user instruction to close the window.
Initial implementation relied on a pop-up window executing the callback
while the main window was redirected to the dashboard main page.
- resolves https://github.com/coder/coder/issues/20323
<!--
If you have used AI to produce some or all of this PR, please ensure you
have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.
-->
*Disclaimer: implemented by a Coder Agent using Claude Opus 4.6*
Follow-up to https://github.com/coder/coder/pull/24630 which added
backend Charm Crush client detection.
## Summary
Adds a `"Charm Crush"` case to the `AIBridgeClientIcon` switch so the
client filter and request logs show an icon instead of the generic
question mark fallback.
Uses a 💘 emoji-based SVG as a placeholder, matching Crush's branding.
Replace with an official brand SVG when one becomes available from the
Charm team.
## Changes
- `site/static/icon/charm-crush.svg`: new emoji-based placeholder icon.
-
`site/src/pages/AIBridgePage/RequestLogsPage/icons/AIBridgeClientIcon.tsx`:
added `"Charm Crush"` case.
Adds the devcontainers icon sourced from [Microsoft Fluent UI
`ic_fluent_cube_32_filled`](https://github.com/microsoft/fluentui-system-icons/blob/78c9587b995299d5bfc007a0077773556ecb0994/assets/Cube/SVG/ic_fluent_cube_32_filled.svg).
The registry module `devcontainers-cli` already references
`/icon/devcontainers.svg` — this adds the missing icon.
## Changes
- `site/static/icon/devcontainers.svg` — new icon
- `site/src/theme/icons.json` — auto-generated entry
- `site/src/theme/externalImages.ts` — registers icon as `monochrome` so
it renders correctly on both dark and light themes
> 🤖 This PR was created with the help of Coder Agents, and needs a human
review. 🧑💻
---------
Co-authored-by: Jake Howell <jake@hwll.me>
## What
Fixes the `TestSVGIconAttributes/texlive.svg` CI failure introduced by
#24312.
Two changes:
1. **Fix `texlive.svg` viewBox**: Changed from `0 0 1024 1024` to `0 0
256 256` (wrapping content in `<g transform="scale(0.25)">` to preserve
rendering). Also cleaned up non-standard attributes (`version`, `style`,
`preserveAspectRatio`) to match other icons.
2. **Add icon/theme paths to CI go filter**: Added `site/static/icon/**`
and `site/src/theme/**` to the `go` path filter in `ci.yaml` so Go tests
(`test-go-pg`, `test-go-pg-17`, `test-go-race-pg`) run when icons or
theme config change. This is why the failure wasn't caught on the PR —
only `site/` files were modified, so Go tests were skipped entirely.
Closes https://github.com/coder/internal/issues/1468
- Remove Kyleosophy alternative completion chimes (keeps original chime
intact)
- Extract 5 sub-components from the 717-line god component:
- `PersonalInstructionsSettings` — user prompt textarea form
- `SystemInstructionsSettings` — admin system prompt + TextPreviewDialog
- `VirtualDesktopSettings` — admin desktop toggle
- `WorkspaceAutostopSettings` — admin autostop toggle + duration form
- `RetentionPeriodSettings` — admin retention toggle + number input
- Parent is now a ~160-line layout shell
- `isAnyPromptSaving` coupling preserved via prop
- Add `docs/plans/` to `.gitignore`
> 🤖 Written by a Coder Agent. Reviewed by a human.
- Add "Enable Kyleosophy" toggle to Settings > Behavior
- When enabled, replaces standard completion chime with random Kyle
sound clips
- Ships 8 alternative `.mp3` files as static assets (~82KB total)
- localStorage preference (`agents.kyleosophy`), defaults to off
- Pauses orphaned Audio elements on sound URL change to prevent overlap
<details><summary>Review findings addressed</summary>
- **P2** Stale JSDoc on `playChimeAudio` — updated to reflect
parameterized behavior
- **P3** Overlapping audio on rapid completions — added
`chimeAudio?.pause()` before replacement
- **P3** Test ordering dependency — pinned `Math.random` for
determinism, documented cache behavior
- **Nit** Setter naming — `setLocalKyleosophy` → `setKylesophyLocal`
- Toggle moved to bottom of Behavior page per product request
- Description changed to "IYKYK" per product request
</details>
> 🤖 Written by a Coder Agent. Reviewed by a human.
Fixes a regression where image attachments in user chat messages were
rendered twice, once inside the bubble container and once outside it.
- **ConversationTimeline.tsx**: Remove 43 duplicate lines (outer image
block + second fade overlay) from the `ChatMessageItem` user-message
branch.
- **ConversationTimeline.stories.tsx** (new): Add focused stories for
`ConversationTimeline` with `play` function assertions on image
thumbnail counts to guard against this class of regression.
The `apple-touch-icon.png` had weird padding/cropping that looked wrong
when added to the iOS home screen. This replaces it with a 180×180
resize of the canonical `pwa-icon-512.png` so the icon matches the PWA
icon exactly — no extra padding.
Adds progressive web app support for the agents page so it can be
installed as a standalone app on mobile/desktop.
## Changes
- **`manifest.json`** — Web app manifest with `display: standalone`,
`start_url: /agents`, Coder theme colors
- **PWA icons** — 192x192, 512x512 PNGs + 180x180 apple-touch-icon,
rendered from the existing favicon SVG
- **`index.html`** — Added manifest link, apple-touch-icon, and mobile
web app meta tags (`apple-mobile-web-app-capable`,
`mobile-web-app-capable`, `apple-mobile-web-app-status-bar-style`,
title)
- **Service worker** — `notificationclick` now focuses an existing
agents tab or opens `/agents` in a new window
## Testing
1. Open `/agents` on a mobile device
2. Use browser "Add to Home Screen" / "Install App"
3. App should launch in standalone mode pointing at the agents page
4. Push notifications should navigate to the agents page on click
Polishes the right panel UI introduced in #22633:
<img width="3138" height="1596" alt="image"
src="https://github.com/user-attachments/assets/d3947db0-6600-4469-b7e2-6eb80aadb7bc"
/>
Over 2k lines of this is just the Seti font definition.
The file tree view isn't actually adjusting much, it's just a scroll
helper. Soon I'll add a comment system so users can leave agents
feedback directly from the code.
Adds two new icons to the icon library:
- **`anthropic.svg`** — Anthropic logo
- **`gemini-monochrome.svg`** — Gemini logo, monochrome variant
Both use `monochrome` theme handling to adapt for dark and light
backgrounds.
### Changes
- Added `anthropic.svg` and `gemini-monochrome.svg` to
`site/static/icon/`
- Registered both in `site/src/theme/icons.json` (alphabetically sorted)
- Added `monochrome` theme handling for both in
`site/src/theme/externalImages.ts`
---
Created on behalf of @tracyjohnsonux
---------
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
## Summary
Harden the OAuth2 provider with multiple security fixes addressing
`coder/security#121` (CSRF session takeover) and converge on OAuth 2.1
compliance.
### Security Fixes
| Fix | Description | Commits |
|-----|-------------|---------|
| **CSRF on `/oauth2/authorize`** | Enforce CSRF protection on the
authorize endpoint POST (consent form submission) | `ba7d646`, `b94a64e`
|
| **Clickjacking: `frame-ancestors` CSP** | Prevent consent page from
being iframed (`Content-Security-Policy: frame-ancestors 'none'` +
`X-Frame-Options: DENY`) | `597aeb2` |
| **Exact redirect URI matching** | Changed from prefix matching to full
string exact matching per OAuth 2.1 §4.1.2.1 | `73d64b1`, `93897f1` |
| **Store & verify `redirect_uri`** | Store redirect_uri with auth code
in DB, verify at token exchange matches exactly (RFC 6749 §4.1.3) |
`50569b9`, `d7ca315` |
| **Mandatory PKCE** | Require `code_challenge` at authorization (for
`response_type=code`) + unconditional `code_verifier` verification at
token exchange | `d7ca315`, `1cda1a9` |
| **Reject implicit grant** | `response_type=token` now returns
`unsupported_response_type` error page (OAuth 2.1 removes implicit flow)
| `d7ca315`, `91b8863` |
### Changes by File
**`coderd/httpmw/csrf.go`** — Extended the CSRF `ExemptFunc` to enforce
CSRF on `/oauth2/authorize` in addition to `/api` routes. The consent
form POST is now CSRF-protected to prevent cross-site authorization code
theft.
**`site/site.go`** — Added `Content-Security-Policy: frame-ancestors
'none'` and `X-Frame-Options: DENY` headers to `RenderOAuthAllowPage`
(consent page only — does not affect the SPA/global CSP used by AI
tasks).
**`coderd/httpapi/queryparams.go`** — Changed `RedirectURL` from prefix
matching (`strings.HasPrefix(v.Path, base.Path)`) to full URI exact
matching (`v.String() != base.String()`), comparing scheme, host, path,
and query.
**`coderd/oauth2provider/authorize.go`** — Added PKCE enforcement:
`code_challenge` is required when `response_type=code` (via a
conditional check, not `RequiredNotEmpty`, so `response_type=token` can
reach the explicit rejection path). `ShowAuthorizePage` (GET) validates
`response_type` before rendering and returns a 400 error page for
unsupported types. `ProcessAuthorize` (POST) stores the `redirect_uri`
with the auth code when explicitly provided.
**`coderd/oauth2provider/tokens.go`** — PKCE verification is now
unconditional (not gated on `code_challenge` being present in DB). If
the stored code has a `redirect_uri`, the token endpoint verifies it
matches exactly — mismatch returns `errBadCode` → `invalid_grant`.
Missing `code_verifier` returns `invalid_grant`.
**`codersdk/oauth2.go`** — `OAuth2ProviderResponseTypeToken` constant
and `Valid()` acceptance are **kept** so the authorize handler can parse
`response_type=token` and return the proper `unsupported_response_type`
error rather than failing at parameter validation.
**`coderd/database/migrations/000421_*`** — Added `redirect_uri text`
column to `oauth2_provider_app_codes`.
### Design Decisions
**`state` parameter remains optional** — The plan initially required
`state` via `RequiredNotEmpty`, but this was reverted in `376a753` to
avoid breaking existing clients. The `state` is still hashed and stored
when provided (via `state_hash` column), securing clients that opt in.
**`response_type=token` kept in `Valid()`** — Removing it from `Valid()`
would cause the parameter parser to reject the request before the
authorize handler can return the proper `unsupported_response_type`
error. The constant is kept for correct error handling flow.
**CSP scoped to consent page only** — `frame-ancestors 'none'` is set
only on the OAuth consent page renderer, not globally. The SPA/global
CSP was previously changed to allow framing for AI tasks
([#18102](https://github.com/coder/coder/pull/18102)); this change does
not regress that.
### Out of Scope (follow-up PRs)
- Bearer tokens in query strings (needs internal caller audit)
- Scope enforcement on OAuth2 tokens
- Rate limiting on dynamic client registration
---
<details>
<summary>📋 Implementation Plan</summary>
# Plan: Harden OAuth2 Provider — Security Fixes + OAuth 2.1 Compliance
## Context & Why
Security issue `coder/security#121` reports a critical session takeover
via CSRF on the OAuth2 provider. This plan covers all remaining security
fixes from that issue **plus** convergence on OAuth 2.1 requirements.
The goal is a single PR that closes all actionable gaps.
## Current State (already committed on branch `csrf-sjx1`)
| Fix | Status | Commits |
|-----|--------|---------|
| Fix 1: CSRF on `/oauth2/authorize` | ✅ Done | `ba7d646`, `b94a64e` |
| CSRF token in consent form HTML | ✅ Done | `b94a64e` |
| `state_hash` column + storage | ✅ Done (hash stored, but state still
optional) | `9167d83`, `b94a64e` |
| Tests for CSRF + state hash | ✅ Done | `e4119b5` |
## Remaining Work
### ~~Fix 2 — Require `state` parameter~~ (DROPPED)
> **Decision:** Do not enforce `state` as required. The `state`
parameter is still hashed and stored when provided (via
`hashOAuth2State` / `state_hash` column from prior commits), but clients
are not forced to supply it. This avoids breaking existing integrations
that omit state.
**Rollback:** Remove `"state"` from the `RequiredNotEmpty` call in
`coderd/oauth2provider/authorize.go:42`:
```go
// BEFORE (current on branch)
p.RequiredNotEmpty("response_type", "client_id", "state", "code_challenge")
// AFTER
p.RequiredNotEmpty("response_type", "client_id", "code_challenge")
```
No test changes needed — tests already pass `state` voluntarily.
### Fix 4 — Exact redirect URI matching
Currently `coderd/httpapi/queryparams.go:233` uses prefix matching:
```go
// CURRENT — prefix match
if v.Host != base.Host || !strings.HasPrefix(v.Path, base.Path) {
```
OAuth 2.1 requires **exact string matching**. Change to:
```go
// AFTER — exact match (OAuth 2.1 §4.1.2.1)
if v.Host != base.Host || v.Path != base.Path {
```
**File: `coderd/httpapi/queryparams.go` — `RedirectURL` method**
Also update the error message from "must be a subset of" to "must
exactly match".
**Additionally**, store `redirect_uri` with the auth code and verify at
the token endpoint (RFC 6749 §4.1.3):
1. **New migration** (same migration file or a new `000421`): Add
`redirect_uri text` column to `oauth2_provider_app_codes`
2. **Update INSERT query** in `coderd/database/queries/oauth2.sql` to
include `redirect_uri`
3. **`coderd/oauth2provider/authorize.go`**: Store
`params.redirectURL.String()` when inserting the code
4. **`coderd/oauth2provider/tokens.go`**: After retrieving the code from
DB, verify that `redirect_uri` from the token request matches the stored
value exactly. Currently `tokens.go:103` calls `p.RedirectURL(vals,
callbackURL, "redirect_uri")` for prefix validation only — it must
compare against the stored redirect_uri from the code, not just the
app's callback URL.
<details>
<summary>Why both exact match AND store+verify?</summary>
Exact matching at the authorize endpoint prevents open redirectors
(attacker can't use a sub-path).
Storing and verifying at the token endpoint prevents code injection — an
attacker who steals a code can't exchange it with a different
redirect_uri than was originally authorized. This is required by RFC
6749 §4.1.3 and OAuth 2.1.
</details>
### Fix 7 — `frame-ancestors` CSP on consent page
The consent page can be iframed by a workspace app (same-site), which is
the attack vector. Add a `Content-Security-Policy` header to prevent
framing.
**File: `site/site.go` — `RenderOAuthAllowPage` function (~line 731)**
Before writing the response, add:
```go
func RenderOAuthAllowPage(rw http.ResponseWriter, r *http.Request, data RenderOAuthAllowData) {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
// Prevent the consent page from being framed to mitigate
// clickjacking attacks (coder/security#121).
rw.Header().Set("Content-Security-Policy", "frame-ancestors 'none'")
rw.Header().Set("X-Frame-Options", "DENY")
...
```
Both headers for defense-in-depth (CSP for modern browsers,
X-Frame-Options for legacy).
### OAuth 2.1 — Mandatory PKCE
Currently PKCE is checked only when `code_challenge` was provided during
authorization (`tokens.go:258`):
```go
// CURRENT — conditional check
if dbCode.CodeChallenge.Valid && dbCode.CodeChallenge.String != "" {
// verify PKCE
}
```
OAuth 2.1 requires PKCE for ALL authorization code flows. Change to:
**File: `coderd/oauth2provider/authorize.go`** — Add `"code_challenge"`
to required params:
```go
p.RequiredNotEmpty("response_type", "client_id", "code_challenge")
```
**File: `coderd/oauth2provider/tokens.go:257-265`** — Make PKCE
verification unconditional:
```go
// AFTER — PKCE always required (OAuth 2.1)
if req.CodeVerifier == "" {
return codersdk.OAuth2TokenResponse{}, errInvalidPKCE
}
if !dbCode.CodeChallenge.Valid || dbCode.CodeChallenge.String == "" {
// Code was issued without a challenge — should not happen
// with the authorize endpoint enforcement, but defend in
// depth.
return codersdk.OAuth2TokenResponse{}, errInvalidPKCE
}
if !VerifyPKCE(dbCode.CodeChallenge.String, req.CodeVerifier) {
return codersdk.OAuth2TokenResponse{}, errInvalidPKCE
}
```
**File: `codersdk/oauth2.go`** — Remove
`OAuth2ProviderResponseTypeToken` from the enum or reject it explicitly
in the authorize handler. Currently it's defined at line 216 but the
handler ignores `response_type` and always issues a code. We should
either:
- (a) Remove the `"token"` variant from the enum and reject it with
`unsupported_response_type`, OR
- (b) Add an explicit check in `ProcessAuthorize` that rejects
`response_type=token`
Option (b) is simpler and more backwards-compatible:
```go
// In ProcessAuthorize, after extracting params:
if params.responseType != codersdk.OAuth2ProviderResponseTypeCode {
httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest,
codersdk.OAuth2ErrorCodeUnsupportedResponseType,
"Only response_type=code is supported")
return
}
```
### OAuth 2.1 — Bearer tokens in query strings
`coderd/httpmw/apikey.go:743` accepts `access_token` from URL query
parameters. OAuth 2.1 prohibits this. However, this may be used
internally (e.g., workspace apps, DERP). Need to audit callers before
removing.
**Approach:** This is a larger change with potential breakage. Mark as a
**separate follow-up issue** rather than including in this PR. Document
the finding.
### OAuth 2.1 — Removed flows
✅ **Already compliant.** `tokens.go` only supports `authorization_code`
and `refresh_token` grant types. The implicit grant
(`response_type=token`) will be explicitly rejected per the PKCE section
above.
### OAuth 2.1 — Refresh token rotation
✅ **Already compliant.** `tokens.go:442` deletes the old API key when a
refresh token is used.
## Migration Plan
All DB changes can go in a single new migration (or extend 000420 if the
branch is rebased before merge). Columns to add:
- `redirect_uri text` on `oauth2_provider_app_codes`
The `state_hash` column is already added by migration 000420.
## Implementation Order
1. **Fix 7** — CSP headers on consent page (isolated, no deps)
2. ~~**Fix 2** — Require `state` parameter~~ (DROPPED — state stays
optional)
3. **Fix 4** — Exact redirect URI matching + store/verify redirect_uri
4. **PKCE mandatory** — Require `code_challenge` + reject
`response_type=token`
5. **Rollback** — Remove `"state"` from `RequiredNotEmpty` in
`authorize.go`
6. **Tests** — Update/add tests for all changes
7. **`make gen`** after DB changes
## Out of Scope (separate PRs)
- Bearer tokens in query strings (needs internal caller audit)
- Scope enforcement on OAuth2 tokens
- Rate limiting / quota on dynamic client registration
</details>
---
_Generated with [`mux`](https://github.com/coder/mux) • Model:
`anthropic:claude-opus-4-6` • Thinking: `xhigh`_
Related to #21320
This pull-request ensures that all the icons are now `256px * 256px` so
that they're standardised across all rendering situations.
| Previous | Now |
| --- | --- |
| <img width="886" height="1652" alt="CleanShot 2025-12-21 at 12 25
03@2x"
src="https://github.com/user-attachments/assets/c298325c-4419-485b-97e2-bb54e6ccc8c0"
/> | <img width="962" height="980" alt="CleanShot 2025-12-21 at 12 26
35@2x"
src="https://github.com/user-attachments/assets/c9cef5ee-115d-4318-8e12-188ea79cc88b"
/> |
* All `*.svg` files are now `256px * 256px` and..
* Added a `do.svg` file.
* Added a `ruby.svg` file.
* Added a `gcp.svg` file.
* All `*.png` files are now `256px * 256px` and..
* Updated the `aws.png` file to match `aws.svg` (deprecated).
* Updated the `azure.png` file to match `azure.svg` (deprecated).
* Updated the `docker.png` file to match `docker.svg` (deprecated).
* Updated the `do.png` file to fill the full `256px` width.
* Deprecated the `do.png`.
* Deprecated the `ruby.png`.
* Deprecated the `gcp.png`.
Closes#21052 and is related to #21218
This pull request implements a bunch of changes the team requested to
improve the overall user experience when navigating to the Request Logs
page. Since these are mostly UI changes, screenshots do a much better
job of showing what's different than me trying to explain everything in
words, so I've included them below.
> [!WARNING]
> More globally we've changed how we're rendering `<PaginationHeader
/>`, therefore we've renamed it to `<PaginationAmount />`. This will
effect multiple layouts however its a global improvement we're
intentionally looking to make.
### Preview

### Changes
1. Date formatting set to a standardised way including the month as a
`MMM` (i.e `Dec`)
2. Names and avatars are now rendered larger, furthermore they render
the `.name` field falling back on `.username`.
3. Font-size increased from `text-xs` to `text-sm` to increase overall
legibility globally.
4. Initiator is now rendered inside of a `<Badge />` and gives longer
names a chance to render.
5. Models are now rendered in a `<Badge />` alongside their icons.
6. Input/Output tokens are in a shared `<Badge />`-group alongside
truncation (when necessary).
7. Badge from `5` is rendered with the proper name being visible on
hover (for use if truncated).
8. Move globally the `Showing X of X` counts to the bottom of the tables
(effects other components outside of AI Bridge).
9. Icons are now included within the dropdown for the `Provider` filter.
10. Truncation of elements is now handled so larger prompts don't cause
the table to require further scrolling.
11. New merged Input/Output `<dt>` element with the badge
| Position | Pull-request |
| -------- | ------------ |
| ✅ | [fix: improve AI Bridge request logs
UI/UX](https://github.com/coder/coder/pull/21252) |
| | [feat: add AI Bridge request logs model
filter](https://github.com/coder/coder/pull/21259) |
| | [chore!: promote AIBridge from
`ExperimentalHandler`](https://github.com/coder/coder/pull/21278) |
| | [feat: implement request log collapsing prompt (`<RequestLogsPrompt
/>`)](https://github.com/coder/coder/pull/21313) |
---------
Co-authored-by: ケイラ <mckayla@hey.com>
Closes#19984
As part of this, I refactored the error template to take in a slice of
actions rather than using individual booleans and strings to control the
behavior.
We decided a link resolves the issue for now so that is what I added,
although we may want to consider a way to start the workspace and follow
the logs dynamically on that page and then show the app when finished
(similar to the tasks page), or at least make the link automatically
start the workspace instead of only taking you to the dashboard where
you have to then start the workspace.
### Description
This pull-request ensures that we're using the right colors (and
themeing things within the actual coder brand) on the `error.html` page.
Furthermore, I went ahead and cleaned up the CSS Variables and converted
all `px` units to a standard `rem` unit (16px base).
### Preview
<img width="3516" height="2388" alt="CleanShot 2025-12-02 at 11 09
55@2x"
src="https://github.com/user-attachments/assets/781623ea-a487-4a2e-a08e-bec86d6de6f5"
/>