Switch from workspace stop/start operations to the dedicated tasks pause
and resume endpoints for cleaner semantics.
(cherry picked from commit bf639d0016)
<!--
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.
-->
## Summary
Moves expired token filtering from client-side to server-side by adding
an `include_expired` parameter to the `GetAPIKeysByLoginType` and
`GetAPIKeysByUserID` database queries. This is more efficient for large
deployments with many expired/short-lived tokens.
## Changes
- Add `include_expired` parameter to SQL queries using `OR`
short-circuit
- Add `include_expired` query parameter to `GET
/users/{user}/keys/tokens`
- Add `IncludeExpired` field to `codersdk.TokensFilter`
- Remove client-side filtering from CLI `tokens list` command
- Add `TestTokensFilterExpired` test
Fixescoder/internal#1357
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`_
This pull-request removes all the magic of `@mui/material/Alert` 🥳 We're
officially free of any alerts that are being handled by Material UI so
this is dead code.
Use the server-rendered meta tag value as an intermediate fallback for
theme preference, between the JS-fetched value and the default theme.
This ensures the correct theme is applied before the API response loads.
Fixes#20050
Previously, when secret deployment options like CODER_OIDC_CLIENT_SECRET
were populated, the API correctly returned the "secret": "true"
annotation, but the UI did not indicate that these secrets were
configured. The UI would show "Not set" regardless of whether the secret
was set or not.
Now, the UI checks both the secret annotation and the value_source
field. When a secret is configured (value_source is set), it displays
"Set" to indicate the secret is populated. When a secret is not
configured, it displays "Not set".
Fixes#18913
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
`--secure-auth-cookie` now automatically sources it's default value from `--access-url`
If the access url uses HTTPS, secure is set to `true`.
To revert to old behavior, set the value explicitly to `false`
If a deployment has 2 domains, overriding the oidc url allows the oidc
redirect to differ from the access_url
response to https://github.com/coder/coder/discussions/21500
**This config setting is hidden by default**
In relation to
[`internal#1281`](https://github.com/coder/internal/issues/1281)
Remove the `soft_limit` field from the `Feature` type and simplify
license limit handling. This change:
- Removes the `soft_limit` field from the API and SDK
- Uses the soft limit value as the single `limit` value in the UI and
API
- Simplifies warning logic to only show warnings when the limit is
exceeded
- Updates tests to reflect the new behavior
- Updates the UI to use the single limit value for display
In relation to
[`internal#1281`](https://github.com/coder/internal/issues/1281)
Managed agent workspace build limits are now advisory only. Breaching
the limit no longer blocks workspace creation — it only surfaces a
warning.
- Removed hard-limit enforcement in `checkAIBuildUsage` so AI task
builds are always permitted regardless of managed agent count.
- Updated the license warning to remove "Further managed agent builds
will be blocked." verbiage.
- Updated tests to assert builds succeed beyond the limit instead of
failing.
- Removed the "Limit" display from the `ManagedAgentsConsumption`
progress bar — the bar is now relative to the included allowance (soft
limit) only, and turns orange when usage exceeds it.
Bonus:
- De-MUI'd `LicenseBannerView` — replaced Emotion CSS and MUI `Link`
with Tailwind classes.
- Added `highlight-orange` color token to the Tailwind theme.
This pull-request implement animations for each of our `<ChevronDown />`
(and a few other chevrons) so that everything is uniform with
`<Autocomplete />`.
Based on previous PR reviews it appears we don't want to use these
components anymore. We previously deprecated the use of `<Stack />` in
this way in #20973 so it would be good to take the same approach here.
This PR stops Vite from repeatedly re-optimizing certain MUI modules
during development, which was triggering an HMR feedback loop and
crashing my dev environment on specific pages — most notably
`<LicensesSettingsPage />`.
After some digging, the culprit turned out to be:
```ts
import Paper from "@mui/material/Paper";
```
Importing components this way causes Vite to continuously re-optimize
them during HMR, which leads to the page refreshing over and over until
the dev server taps out and `504 "Outdated Optimize Dep"`'s us.
The fix ensures these modules are computed once at startup instead of
being reprocessed on every hot update. Development is now stable, and
the infinite refresh loop is gone.
I did experiment with using globs to handle this more generically, but
since they’re still early-access in this context, they ended up breaking
things 😔
In short: fewer re-optimizations, no more HMR meltdown, and a much
calmer dev experience.
Continuation of #22186 (without `vitest` addon)
Upgrades the dependency so that we can actively make use of new
features/speed/less-dependencies. Short simple sweet and lovely 🙂
## Summary
Custom roles that can create workspaces on behalf of other users need to
be able to list users to populate the owner dropdown in the workspace
creation UI. Previously, this required a separate `user:read`
permission, causing the dropdown to fail for custom roles.
## Changes
- Modified `GetUsers` in `dbauthz` to check if the user can create
workspaces for any owner (`workspace:create` with `owner_id: *`)
- If the user has this permission, they can list all users without
needing explicit `user:read` permission
- Added tests to verify the new behavior
## Testing
- Updated mock tests to assert the new authorization check
- Added integration tests for both positive and negative cases
Fixes#18203
Add a `TaskLogPreview` component that displays the last N messages of AI
chat logs when a task is paused or its build has failed. The preview
fetches log snapshots via a new `getTaskLogs` API method and renders
them in a scrollable panel with `[user]` and `[agent]` labels, colored
left borders on type transitions, and a snapshot timestamp tooltip.
The build-logs auto-scroll in `BuildingWorkspace` was simplified by
replacing the `useRef`/`useLayoutEffect` pattern with a `useCallback`
ref, and client-side message slicing was removed in favor of
server-side limits. `InfoTooltip` now accepts an optional `title` prop.
Closes#20965
This pull-request enables a quick permission check that the user is
allowed to view the `<RequestLogsPage />` under the admin panel.
Previously, users would be able to view this page and browse their own
logs if they had this permission (which was fine), however now we've
decided as this is an admin page, they should only be able to do this
via the API/CLI not from the main admin panel.
The login page component incorrectly uses client-side routing to handle
redirects to /oauth2/authorize. Since this path is not defined as a
route in the react application but as a backend endpoint for the OAuth2
provider flow, the frontend displays a 404 "Route not found" error.
- resolves#22097
<!--
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.
-->
Relates to https://github.com/coder/internal/issues/1252
When a workspace with a TaskID hits its deadline, use
BuildReasonTaskAutoPause instead of BuildReasonAutostop. This allows
downstream systems to distinguish between regular autostop and task
workspace pauses.
Created by Mux using Opus 4.5.
Closes#20859
This page previously wasn't rendered to the user, however, there is a
possibility that they can navigate to this page and things will end up
in `<Spinner />`s until the requests ultimately fail. We can mitigate
this problem by showing them the `<RequirePermission />` modal.
<img width="1456" height="861" alt="image"
src="https://github.com/user-attachments/assets/57195643-ad55-4340-9c97-f8247b05a13b"
/>
Closes#21703
This doesn't make sense to have an `Activity bump` value when the
`Default autostop` is set to `0`. There is nothing to bump if we don't
have a timed stopping mechanism on the container. This is already
present on the backend and now we're describing this to the user on the
frontend.
## Summary
The license removal confirmation dialog always showed:
> Removing this license will disable all Premium features. You add a new
license at any time.
This is misleading when the license being removed is already expired —
an expired license isn't providing any features, so removing it won't
disable anything.
## Changes
- Extracted `isExpired` variable in `LicenseCard` (reusing the existing
expiry check)
- Made the dialog description conditional:
- **Expired license**: "This license has already expired and is not
providing any features. Removing it will not affect your current
entitlements."
- **Active license**: "Removing this license will disable all Premium
features. You can add a new license at any time."
- Also fixed a minor typo in the active license message ("You add" →
"You can add")
- Added two new tests covering both dialog variants
## Testing
All 5 `LicenseCard` tests pass, including the 2 new ones:
- `shows expired removal message for expired licenses`
- `shows disabling features warning for active licenses`
---------
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Closes#16148
This pull-request resolves a few issues with wider displays.
Particularly in ensuring the content's container center's as one would
expect and the content of the headings isn't being contained into a
`max-w-prose`.
**Background**
Reported in #17417, there is a `deleted` query parameter supported by
/api/v2/templates, but we do not respect this field on the client,
showing the "Create Workspace" button for deleted templates.
**Expected Behavior**
Don't show the "Create Workspace" button for deleted templates.
**Notes**
This PR adds a new `deleted` field to the templates API response.
Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
This pull-request refactors the `<Combobox />` component from a
monolithic design to a composable compound component pattern, providing
more flexibility and reusability across the codebase
- Migrates `<SelectFilter />` to use the new `<Combobox />` instead of
the legacy `<SelectMenu />` components
- Updates all existing consumers of `<Combobox />` and `<SelectFilter
/>` to use the new API
<img
src="https://github.com/user-attachments/assets/a3336431-590c-48b5-adde-3fc5c16f459d"
/>
The `<Combobox />` component has been refactored to use a compound
component pattern, exposing:
- `Combobox` - Root component with context provider for open/value state
- `ComboboxTrigger` - Trigger wrapper (re-exports PopoverTrigger)
- `ComboboxButton` - Styled button with chevron and selected option
display
- `ComboboxContent` - Popover content with Command wrapper
- `ComboboxInput` - Search input (re-exports CommandInput)
- `ComboboxList` - List container (re-exports CommandList)
- `ComboboxItem` - Individual option with checkmark indicator
- `ComboboxEmpty` - Empty state (re-exports CommandEmpty)
- `useCombobox` - Hook to access combobox context
This pattern allows consumers to compose their own combobox layouts
while sharing consistent behavior and styling.
Furthermore, we had an issue with `CreateWorkspacePageView.stories.tsx`
lacking stories which would let us see the passed parameters and presets
in context. I've added stories to surround this.
### Updated Consumers
- `DynamicParameter.tsx` - Updated to use new Combobox API for parameter
options
- `CreateWorkspacePageView.tsx` - Updated preset combobox usage
- `IdpOrgSyncPageView.tsx` - Updated organization sync form
- `IdpGroupSyncForm.tsx` - Updated group sync form
- `IdpRoleSyncForm.tsx` - Updated role sync form
- `WorkspacesPage/filter/menus.tsx` - Updated workspace filter menus
---------
Co-authored-by: ケイラ <mckayla@hey.com>
Closes#21830
Remove redundant icon sizing across the frontend. Components like
`Button`, `DropdownMenuItem`, and `CommandItem` already control child
SVG sizes via CSS selectors (e.g., `[&>svg]:size-icon-lg`), so explicit
`size` props and `className` overrides on icons nested inside them are
unnecessary. This PR strips those out and lets parent components handle
sizing consistently.
As a bonus, also migrates the `DropdownArrow` component from Emotion
CSS-in-JS to Tailwind utilities, replaces raw `<a>` tags with the `<Link
/>` component in the Premium page, and adds Storybook coverage for
`PremiumPageView`.