Commit Graph
445 Commits
Author SHA1 Message Date
Paweł Banaszewski bab8ce9d41 feat: setup logging, tracing and metrics in standalone AI Gateway (#27068)
Adds logging, tracing and metrics setup to standalone AI Gateway.
Existing options are re-used when possible.
2026-07-09 14:02:18 +00:00
Danny Kopping affb359d13 feat: synchronise provider changes with WatchAIProviders (#27091)
## Why

PR #26797 was accidentally merged into the stale `graphite-base/26797`
branch instead of `main` (Graphite picked the wrong base), so its
changes never landed on `main`. This PR re-lands that work as a clean
cherry-pick onto the current `main`.

## What

Adds a `WatchAIProviders` streaming RPC to the `ProviderConfigurator`
service so a running standalone AI Gateway refetches its provider set
when the provider configuration changes. The server subscribes to
`AIProvidersChangedChannel` (published by the provider CRUD endpoints)
and forwards each event as a payload-free signal, plus one signal on
subscribe; the gateway calls `GetAIProviders` on each signal to rebuild
its pool. The aibridged API is bumped to v1.2.

Env-seeded providers don't need a signal: seeding finishes before coderd
serves the gateway connection, so the gateway's initial fetch already
reflects the seeded set.

## For reviewers

The change is split into two commits to make review easy:

1. **`feat: synchronise provider changes with WatchAIProviders`** is a
faithful cherry-pick of #26797, identical to the originally reviewed PR.
It is committed without pre-commit hooks because it does not build
against current `main` on its own.
2. **`fix: resolve cherry-pick conflicts against main`** contains only
the deltas needed to re-land on current `main`, and passes the full
pre-commit suite:
- `coderd/aibridged/proto/aibridged.pb.go` regenerated via the proto
make target (the cherry-picked copy was generated against the older
proto).
- `enterprise/cli/aigatewaystart.go` import block unioned; `main` added
`os` and `strings` while the PR added `sync`.
- Three `aibridgedserver.NewServer` test call sites that landed on
`main` after the original branch diverged now pass the new `pubsub`
argument.

Refs https://linear.app/codercom/issue/AIGOV-465

*This PR was produced by opencode (agent) using the
`anthropic/claude-opus-4-8` model, under human direction and review.*
2026-07-08 15:32:17 +02:00
Paweł Banaszewski 60d0859bde feat: add /healtz and /readyz endpoints to standalone Gateway (#26988)
Adds `/healthz` and `/readyz` endpoints to standalone AI Gateway.
* `/healthz`: returns 200 once the AI Gateways HTTP server is listening.
* `/readyz`: returns 200 when the DRPC connection to `coderd` is
established.

Cleanup:
Removed `initConnection*` fields from `aibridged.Server` as they where
not used anywhere.
2026-07-08 09:28:02 +00:00
Paweł BanaszewskiandDanny Kopping ccba3969ab feat: add ai-gateway start command (#26605)
> AI Tools were used to produce this PR

This PR adds `coder ai-gateway start` command that runs the AI Gateway
as an independent process.

- Standalone process doesn't have access to DB. Uses DRPC services under
`/api/v2/ai-gateway/serve`for auth, recording and provider
initialization.
- It only handles LLM traffic, other endpoints (eg. `/sessions`) are
only available though `coderd`.
- The standalone gateway reuses applicable flags from AI Gateway
deployment options. Provider-seeding and coderd-only options are
excluded.
- Only added to fat build, the slim build stub rejects the command.

Some wiring used by this new command is added.

**`NewWebsocketDialer`** - implements the standalone gateway's
connection to coderd's `/api/v2/ai-gateway/serve` endpoint. It upgrades
to a WebSocket, multiplexes with yamux, and wires all DRPC services.

**`AIGatewayDataPlaneMiddleware`** - extracts the per-request middleware
chain (concurrency limiting, rate limiting, BYOK gating) into a shared
function used by both the embedded route and the standalone gateway.

**`RootCmd.ResolveClientConnection`** - resolve the deployment URL and
builds an HTTP transport without requiring a session token. Used in
`ai-gateway start`command as it authenticates using different credential
type.

---------

Co-authored-by: Danny Kopping <danny@coder.com>
2026-07-08 11:12:53 +02:00
Sas Swart fc188fdaee fix: create agent firewall sessions without requiring agent read access (#26990)
## Overview

Part of the **boundary correlation** feature. Fixes lazy creation of
`boundary_sessions` rows so it works within the agent's RBAC
constraints, and consumes the new `ConfinedProcessName` field reported
by boundary.

Pairs with coder/boundary#206, which adds `ConfinedProcessName` to
`ReportBoundaryLogsRequest`. This branch bumps the
`github.com/coder/boundary` module to pick up that work.

## Problem

`ensureSession` did a pre-insert existence check via
`GetBoundarySessionByID`. Agents are **not permitted to read boundary
sessions**, so that read path is not viable when the session is created
from an agent-reported log batch.

## Changes

- **Remove the pre-insert read.** `ensureSession` now inserts directly
and treats a primary-key unique violation as success, covering sessions
already created by a prior batch, a reconnection, or another coderd
replica — without requiring read access.
- **Per-connection guard.** Add a mutex-protected `ensuredSessions` set
so repeated log batches on the same connection skip the existence check
and insert entirely, touching the database only for the logs. On a
transient insert failure the session is left unmarked so the next batch
retries.
- **Consume `ConfinedProcessName`.** Pass `req.GetConfinedProcessName()`
through to the session insert.
- **Bump boundary module** from `v0.9.0` to
`v0.9.1-0.20260706095856-35ba90f9e8b2`.
- **Tests.**
- Add `TestReportBoundaryLogsAgentRBAC`
(`coderd/boundary_logs_test.go`), an integration test that connects as a
real workspace agent, verifies the session and log are persisted under
agent RBAC, and asserts the agent subject cannot read boundary sessions
— guarding against reintroducing a pre-insert read.
- Add `TestReportBoundaryLogsSessionGuard` (session inserted once across
two batches, logs inserted per batch) and
`TestReportBoundaryLogsSessionRetriedOnError` (insert retried after a
transient error).
- Regenerate `agent-firewall` CLI docs/golden files and adjust the
clidocgen template to render the YAML path when a flag has no long name.

> 🤖 This PR was opened by Coder Agents on behalf of @SasSwart.
2026-07-07 10:42:01 +00:00
Paweł Banaszewski 79a74fc817 Revert "chore: hide AI Gateway key management UI/CLI/API (#26879)" (#26913)
Reverting
https://github.com/coder/coder/commit/377c1309b7a42ead9cfbd864f0af8e9b6e472851
since release 2.35 was already cut:
https://github.com/coder/coder/tree/release/2.35
2026-07-02 13:45:05 +00:00
Paweł Banaszewski 377c1309b7 chore: hide AI Gateway key management UI/CLI/API (#26879)
Hides UI, CLI and API related to AI Gateway key management +
`/api/v2/ai-gateway/serve` endpoint.
API endpoints and CLI commands are still working they are just not
visible.
2026-06-30 17:32:38 +00:00
Susana Ferreira 56373a09fc chore: rename user-facing AI Bridge strings to AI Gateway (#26700)
Rename user-facing "AI Bridge" strings to "AI Gateway" in deployment
config, RBAC display names, log messages, error strings, docs style
guide, and Grafana dashboard README.

Deprecated option names and descriptions (the `--aibridge-*` block) are
intentionally kept as "AI Bridge". The `Name` field cannot be renamed
because `serpent` uses it as a unique key during JSON serialization;
duplicating names causes `UnmarshalJSON` failures (e.g. in the support
bundle). Descriptions also stay as "AI Bridge" to avoid confusion
between the deprecated and primary options.

Refs https://linear.app/codercom/issue/AIGOV-226

> Generated with the assistance of Coder Agents (@ssncferreira)
2026-06-29 14:33:22 +01:00
Danny Kopping ce94d42e19 feat: fetch providers over DRPC (#26650)
Closes [AIGOV-455](https://linear.app/codercom/issue/AIGOV-455/extend-drpc-with-buildproviders).

## Why

The AI Gateway (`aibridged`) is being split into a standalone process that must not touch the database. `coderd` stays the source of truth and seeds the `ai_providers` / `ai_provider_keys` tables from the environment. This PR adds a DRPC call so the gateway fetches provider config from `coderd` instead of reading the DB, for both the embedded and standalone daemons.

## What

- **Proto:** new `ProviderConfigurator` service with a unary `GetAIProviders` RPC, plus `AIProvider` / `AIProviderBedrock` messages. `CurrentMinor` bumped to 1 (additive).
- **Server (`coderd/aibridgedserver`):** `GetAIProviders` runs a read-only `InTx` under `LockIDAIProvidersEnvSeed` so it never returns a mid-seed snapshot, reads providers (incl. disabled) plus keys for enabled ones, and maps to proto under `dbauthz.AsAIBridged`. Unmappable rows are skipped and logged; plaintext keys and Bedrock secrets are never logged.
- **Client:** `DRPCProviderConfiguratorClient` wired into the client union, `dialer.go`, and `CreateInMemoryAIBridgeServer`.
- **cli:** `BuildProvidersFromProto` maps the response through the existing DB-neutral `buildProvider`. A shared `poolRPCReloader` does the fetch/build/replace for both daemons: the embedded daemon reloads on every `ai_providers` change and fails startup if it cannot subscribe; the standalone gateway drives the same reloader once at startup, retrying until success and staying interruptible.
- **Dead code removed:** `BuildProvidersFromConfig`, `ProvidersFromConfig`, `AIProviderFromConfig`, and the DB-read `BuildProviders` path.
2026-06-29 13:34:58 +02:00
Paweł Banaszewski c15d483863 chore: rename 'last_used_at' column (#26749)
Renames the `last_used_at` column  to `last_heartbeat_at` in `ai_gateway_keys` table.  
`ai_gateway_keys` table has not been released yet.  
All references updated.
2026-06-26 18:45:37 +02:00
Spike Curtis 0135f29cd8 feat: add CODER_CLUSTER_HOST CLI argument (#26680)
Closes GRU-69

Adds CODER_CLUSTER_HOST enviroment variable and CLI arg.

I ended up not making it hidden since we'll just have to unhide it later and even when hidden it still shows up in some autogenerated stuff. Might as well just go for it.

I also added it to the helm chart.
2026-06-26 08:57:23 -04:00
Spike Curtis 98e1ce133c chore: modify replicasync to handle NATS explicitly (#26666)
relates to GRU-69

Modifies replicasync to handle discovering NATS enabled primary replicas explicitly, and passing that info to the NATS Pubsub.

This PR adds a new deployment value to explicitly represent the host or IP that the replica can be reached on. It isn't wired up to the CLI, but piggybacks on the DERP config for now.

We learn the NATS port directly from NATS at runtime, and propagate it thru replicasync to learn all peers for clustering.
2026-06-26 08:36:32 -04:00
Zach 953091c7bc refactor: use sync.WaitGroup.Go in tests (#26671)
Migrate `wg.Add(1); go func() { defer wg.Done(); ... }()` to
`wg.Go(func() { ... })` in tests.

Where the prior pattern passed the loop variable explicitly via a
closure parameter (`go func(id int) { ... }(i)`), drop the parameter and
reference the loop variable directly. Per-iteration loop variables since
Go 1.22 make this safe.
2026-06-25 15:41:09 -06:00
Jon Ayers c7ddcce62c fix: only return group member count for workspace acl (#26206) 2026-06-23 11:58:00 -05:00
Steven Masley 854d280834 chore: add --force-reset-all flag to oidc link repair cli (#26534)
Useful when the issuer is unchanged, but oidc subject claims have
changed.
2026-06-23 11:37:33 -05:00
Jeremy Ruppel a30631198d feat: template builder backend fixes (DEVEX-287) (#26432)
Part of the Template Builder wizard PR stack.

## Backend fixes

1. **Registry URL scheme fix**: Default
`CODER_TEMPLATE_BUILDER_REGISTRY_URL` was `https://registry.coder.com`
but Terraform module registry addresses must be scheme-less. Changed to
`registry.coder.com`.

2. **Sensitive variable defaults**: Module `.tf.tmpl` files for
claude-code, aider, amazon-q had sensitive `variable` blocks without
`default`, causing `terraform plan` to fail during template import. Also
fixed the `templatebuildermodulegen` script.

3. **Auto-quote string variables**: The backend now accepts raw string
values from callers and wraps them in HCL quotes automatically.
Previously callers were required to send pre-quoted HCL literals, which
is not a reasonable API contract.

---
> [!NOTE]
> Generated by Coder Agents on behalf of @jeremyruppel
2026-06-23 09:17:14 -04:00
Paweł Banaszewski 0dbe0442f0 feat: add CLI commands to manage AI Gateway keys (#25689)
Adds `coder ai-gateway keys` commands:
* `create <name>` creates key with given name
* `list` lists existing keys (alias `ls`)
* `delete <name | id>` removes key matching by name or key id, name has
priority (alias `rm`)
2026-06-18 09:08:17 +00:00
Steven Masley 9d0ab594fb chore: unhide 'scim-use-legacy' flag (#26465) 2026-06-17 16:44:01 +00:00
Paweł Banaszewski f1ce1013c4 chore: export AI Gateway metrics under new branding + keep old as alias (#26413)
> AI Tools where used in this request.

Registers `coder_aibridged_*` and `coder_aibridgeproxyd_*` metrics under
new prefixes: `coder_ai_gateway_*` and `coder_ai_gateway_proxy_*`.
Old prefix is still exported. Will be removed in later release.

Also updated the `metricsdocgen` static fixture. Added 4
previously-undocumented metrics `key_pool_state`,
`key_pool_state_transitions_total`, `key_pool_exhaustions_total`,
`key_pool_failover_attempts` added the `client` label to the existing
interception, prompt, and token counter samples.

Updated AI Gateway documentation.
2026-06-17 13:10:53 +02:00
Steven Masley 1d03e63f4f feat: implement package and cli tool for repairing oidc links (#26418) 2026-06-16 12:46:10 -07:00
Sas Swart 2716e2181c feat: purge boundary logs past retention (#24815)
Add a periodic purge job for `boundary_logs` rows past their retention
threshold, following the same pattern as the existing audit log and
connection log purge jobs in `dbpurge`.

Expose a `--boundary-log-retention` deployment flag (env
`CODER_BOUNDARY_LOG_RETENTION`, YAML `retention.boundary_logs`). Default
is `0` (keep indefinitely). When set to a positive duration, `purgeTick`
deletes rows where `captured_at` is older than the threshold in batches
of 10,000, matching other log purge operations. The `boundary_logs`
label is added to the `records_purged_total` Prometheus counter.

Also removes the random-UUID fallback for `OwnerID` in
`dbgen.BoundarySession`. The previous fallback generated a UUID that
could never satisfy the `boundary_sessions_owner_id_fkey` FK constraint,
masking test setup bugs. Callers must now provide a valid user ID or
accept NULL (the legitimate "user deleted" state).
2026-06-16 14:32:54 +02:00
Danny Kopping a1330e3a8c refactor: rename Ai* database identifiers to AI* (AIGOV-369) (#26327)
Adds `ai` to sqlc's `gen.go.initialisms` in `coderd/database/sqlc.yaml`
so the generated DB code follows Go's initialism convention. Adds the
matching `ai` -> `AI` case to the dbgen PascalCase helper
(`scripts/dbgen/main.go`) so the corresponding `dbmem` / mock
identifiers stay in sync. `make gen` regenerates the rest; hand-written
call sites that consume DB-generated identifiers
(`enterprise/audit/table.go`, `coderd/database/modelmethods.go`,
`enterprise/coderd/aigatewaykeys.go`, `coderd/database/dbauthz/*`, etc.)
are updated to match.

Scope is deliberately limited to the database layer:

- `coderd/rbac/*` (resource and scope generators) is untouched —
`ResourceAi*` / `ScopeAi*` constants stay on main's casing.
- `codersdk/*` (Go SDK) is untouched — `codersdk.ResourceAi*` /
`codersdk.APIKeyScopeAi*` constants stay on main's casing, so external
Go SDK consumers see no source-level break.
- `Aibridge*` identifiers (one SQL token `aibridge`, not `ai_bridge`)
are out of scope.

On-the-wire values are unchanged: enum strings, RBAC resource type
strings, API key scope strings, and JSON tags all stay the same. The
HTTP/JSON surface is unaffected.

Refs:
[AIGOV-369](https://linear.app/codercom/issue/AIGOV-369/change-ai-references-in-coderddatabasemodelsgo-to-ai)

🤖 Generated with [Coder Agents](https://coder.com)
2026-06-16 09:01:43 +00:00
Callum Styan 89f200872b feat: send connection logs from agentfake agents (#26083)
Signed-off-by: Callum Styan <callumstyan@gmail.com>
2026-06-15 13:35:28 -07:00
Danny Kopping 4a07f61c50 refactor!: remove interceptions API, request logs view, and associated code (#26213)
## Summary

Removes the deprecated `/api/v2/aibridge/interceptions` endpoint and the
Request Logs frontend page, both replaced by the session-based view.

Closes https://linear.app/codercom/issue/AIGOV-266
Closes https://linear.app/codercom/issue/AIGOV-324

## Changes

### Backend
- Remove `GET /api/v2/aibridge/interceptions` HTTP handler and route
- Remove SDK types and client method (`AIBridgeInterception`,
`AIBridgeTokenUsage`, `AIBridgeUserPrompt`, `AIBridgeToolUsage`,
`AIBridgeListInterceptionsResponse`, `AIBridgeListInterceptionsFilter`)
- Remove SQL queries `CountAIBridgeInterceptions` and
`ListAIBridgeInterceptions`
- Remove `searchquery.AIBridgeInterceptions` parser
- Remove dbauthz wrappers, in-memory implementations, metrics, and mocks
for the interceptions list queries
- Remove the `coder aibridge interceptions list` CLI command and golden
files
- Regenerate API docs, swagger, mocks, and metrics

The `/models`, `/clients`, and `/sessions` endpoints stay; the sessions
list page still consumes all three.

### Frontend
- Delete the entire `RequestLogsPage/` directory (page, view, row,
filter, stories, tests)
- Remove the `/aibridge/request-logs` route and its lazy import
- Remove the `getAIBridgeInterceptions` API method,
`paginatedInterceptions` query, and mock interception entities
- `git mv` the shared filter and icon components used by the sessions
pages:
- `RequestLogsPage/RequestLogsFilter/{Client,Model,Provider}Filter.tsx`
→ `AIBridgePage/filters/`
- `RequestLogsPage/icons/AIBridge{Client,Model,Provider}Icon.tsx` →
`AIBridgePage/icons/`
- Drop the `getProviderIconName` hack and the duplicate `anthropic-neue`
icon case now that the FIXME no longer applies

## Commits

1. `refactor: remove interceptions API and request logs view` — the bulk
removal, with explicit renames for the shared filter/icon files.
2. `refactor(site/src/pages/AIBridgePage): drop getProviderIconName
hack` — cleanup of the FIXME that depended on RequestLogsPage existing.

> [!NOTE]
> Generated by Coder Agents on behalf of @dannykopping
2026-06-12 07:50:46 +02:00
George K b5ef700dd6 fix!: only trust x-forwarded-host from configured trusted proxies (#26204)
Subdomain app routing derived the app identity from
httpapi.RequestHost, which returned the client-supplied
X-Forwarded-Host header verbatim. No middleware validated or stripped
that header, so a request from an untrusted peer could forge it. Since
the application_connect cookie is scoped to the wildcard apps domain,
JavaScript in a share=authenticated app could fetch() with a forged
X-Forwarded-Host pointing at a victim's owner-only app; coderd routed
and authorized the request as the victim and returned the private app
response same-origin to the attacker.

Replace RequestHost with httpmw.EffectiveHost, which honors
X-Forwarded-Host only when the original socket peer is a configured
trusted origin, otherwise falling back to the received Host header.
This ties host trust to the same RealIPConfig model already used for
X-Forwarded-For and -Proto. Wire it into HandleSubdomain for both
coderd and wsproxy, and log both the effective host and the raw
received_host.

Add coverage: EffectiveHost unit tests assert the trust decision uses
the socket peer rather than the spoofable forwarded client IP, and a
HandleSubdomain test confirms a forged X-Forwarded-Host from an
untrusted peer never reaches token resolution.

Refs: https://linear.app/codercom/issue/PLAT-259
2026-06-11 10:55:00 -07:00
Danny Kopping 78a6ec293e revert: "fix: avoid an errant license warning banner on new deployments that d…" (#26240)
Reverts coder/coder#26239

We cannot disable a feature which was previously enabled; this is a BC
break.
This is also using `AIGatewayRoutingEnabled` which will be removed in
the next release.
2026-06-11 08:07:32 +00:00
Sas Swart d0e9c5eda5 fix: avoid an errant license warning banner on new deployments that d… (#26239)
Problem: CODER_AI_GATEWAY_ENABLED defaulted to true, which both started
the in-memory gateway and enabled the licensed FeatureAIBridge. As a
result, deployments that never configured AI Gateway saw a spurious "AI
Governance add-on is required" warning whenever they had an older
(non-add-on) Premium license, since the feature was enabled-and-entitled
by default.

Fix: Decouple "external AI Gateway API enabled" from "in-memory daemon
running," so the external/licensed surface is off by default while Coder
Agents retain access by default.
2026-06-11 09:17:26 +02:00
Rowan Smith 77522c3945 feat: cli: add support for supplying ephemeral parameters at workspace creation (#26012)
Resolves the issue of `--prompt-ephemeral-parameters` and
`--ephemeral-parameter` not being available for use in the `coder
create` workspace creation command (they are only available in `coder
start` command). Back when they were [added
originally](https://github.com/coder/coder/pull/15030) it seems to have
been an oversight that they were left out.

The problem this solves:

```
coder create --parameter my_ephemeral_parameter=foo
error: prepare build: ephemeral parameter "my_ephemeral_parameter" can be used only with --prompt-ephemeral-parameters or --ephemeral-parameter flag
```

```
coder create my-test-ws -t general --ephemeral-parameter my_ephemeral_parameter=foo
parsing flags ([create my-test-ws -t general --ephemeral-parameter my_ephemeral_parameter=foo]) for "coder create": unknown flag: --ephemeral-parameter
```

Tested on a template with the following:

```
data "coder_parameter" "my_ephemeral_parameter" {
  name         = "my_ephemeral_parameter"
  type         = "bool"
  description  = "true or false?"
  mutable      = true
  default      = false
  ephemeral    = true
}

resource "coder_env" "debug_ephemeral" {
  agent_id = coder_agent.main.id
  name     = "EPHEMERAL_TEST"
  value    = data.coder_parameter.my_ephemeral_parameter.value
}
```

By running:

```
➜  coder git:(rowan/coder-create-5495) ✗ go run cmd/coder/main.go create --ephemeral-parameter my_ephemeral_parameter=true
> Specify a name for your workspace: ws4
Select a template below to preview the provisioned infrastructure:
?  kasmvnc-ubuntu-coder-dev used by 1 active developer
Select a preset below:
?  Small (2 CPU / 4 GB)
....
...
The ws4 workspace has been created at Jun  3 12:36:38!

➜  coder git:(rowan/coder-create-5495) ✗ coder ssh ws4               
workspace-ws4-5d6994756f-qlwnl% echo $EPHEMERAL_TEST
true
workspace-ws4-5d6994756f-qlwnl% exit
```
2026-06-11 09:06:07 +10:00
Cian JohnstonandCopilot Autofix powered by AI a26c46a3bf fix!: validate HostnameSuffix and SSHConfigOptions' (#26154)
- Adds server-side and client-side validation for
CODER_CONFIGSSH_HOSTNAME_SUFFIX and CODER_SSH_CONFIG_OPTIONS.
- **Server-side breaking change:** invalid values for either of these will cause `coderd` to exit with an error.
- Client-side: `coder config-ssh` will exit with an error if it detects invalid config.
- Adds tests for the above

Local smoke-testing: ran `develop.sh --env-file <path to an env file
containing badness>`. Validated that server startup failed as expected.

> 🤖 Generated by Coder Agents with supervision from a human.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-10 15:48:02 +01:00
Callum StyanandMux 4627b01415 fix: reduce agentfake manager startup time (#25669)
Signed-off-by: Callum Styan <callumstyan@gmail.com>
Co-authored-by: Mux <noreply@coder.com>
2026-06-04 15:28:13 -07:00
Sas Swart 52722b800b chore: rename boundary command to agent-firewall (#25889)
Renames the `coder boundary` CLI subcommand to `coder agent-firewall` as
part of the Boundaries → Agent Firewall rebrand.

`coder boundary` is retained as a hidden, deprecated alias that prints a
deprecation notice to stderr before running. Both commands use separate
builder functions backed by the same boundary base command and license
verification logic.

Closes https://linear.app/codercom/issue/AIGOV-236

<details><summary>Implementation notes</summary>

**Approach:** Two separate `*serpent.Command` objects (not `Aliases`) so
the deprecated `boundary` path can print a stderr warning while
`agent-firewall` stays clean.

**Changes:**
- `enterprise/cli/boundary.go`: Split old `boundary()` into
`buildAgentFirewallCmd()` and `buildBoundaryAliasCmd()`. Error messages
in `verifyLicense` now reference "agent-firewall".
- `enterprise/cli/root.go`: Register both commands.
- `cli/root.go`: Update YAML-only option validation bypass for the new
command name.
- Tests: Rename to `TestAgentFirewallSubcommand`, add
`TestBoundaryAlias`, update license verification tests to use
`agent-firewall`.
- Golden files and CLI reference docs regenerated.
- `docs/ai-coder/agent-firewall/version.md` and `docs/manifest.json`
updated.

</details>

> Generated with [Coder Agents](https://coder.com/agents) by @SasSwart
2026-06-04 11:14:36 +02:00
Spike Curtis 5b692bf1cc test: rename ExpectMatchContext to ExpectMatch (#25998)
Cleans the last few instances of ExpectMatch that didn't use the new `(ctx, ...)` variant, then deletes the deprecated method and renames `ExpectMatchContext` to drop the `Context` suffix.
2026-06-03 15:30:37 -04:00
Spike Curtis 7d7cc27581 test: batch 07 of refactoring CLI tests not to use PTY (#25997)
Closes [coder/internal#1400](https://github.com/coder/internal/issues/1400)

Final batch of refactored CLI tests to avoid creating PTYs.
2026-06-03 15:16:42 -04:00
Spike Curtis 38360518af test: batch 05 of refactoring CLI tests not to use PTY (#25984)
Part of [coder/internal#1400](https://github.com/coder/internal/issues/1400)

Batch of refactored CLI tests to avoid creating PTYs.
2026-06-02 15:32:36 -04:00
Zach 170c33a475 feat: encrypt gitsshkeys.private_key at rest via dbcrypt (#25872)
Adds an optional dbcrypt wrapper around gitsshkeys.private_key. The
column is encrypted on insert and update through enterprise/dbcrypt when
external token encryption is configured, and decrypted on read.

A new private_key_key_id column references
dbcrypt_keys(active_key_digest) so revocation safety is enforced by the
existing foreign key. Rows with a NULL key_id stay plaintext and remain
readable. Existing plaintext rows can be backfilled by running `coder
server dbcrypt rotate`.

Generated with assistance from Coder Agents.
2026-06-02 08:36:01 -06:00
Thomas KosiewskiandClaude Opus 4.8 f6a4ed309f ci: fix Windows runner PATH casing for mise, not in cli (#25972)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 10:46:40 +00:00
Mathias Fredriksson ed4311b2cb ci: add Git usr/bin to PATH on Windows (#25939)
## Summary

Fixes all 9 Windows CI test failures caused by the mise CI refactor
(`fe257666d7`, PR #25727).

### Root cause

`jdx/mise-action` exports `Path` (Windows convention) via `GITHUB_ENV`.
Bash on Windows maintains its own `PATH`. When Go's `os.Environ()`
returns both, `cmd.exe` subprocesses non-deterministically pick the
MSYS-translated `PATH` (forward slashes), causing Windows executables
(`printf`, `powershell.exe`, `cmd.exe`) to be unresolvable.

These failures only appeared on `main` (where `-count=1` forces real
test execution) and were masked on PRs by Go test cache.

### Fixes applied

**CI (`setup-mise` action)**:
- Write both `Path` and `PATH` to `GITHUB_ENV` with Git usr/bin
prepended

**Code (`cli/root.go`)**:
- Add `appendAndDedupEnv` helper that deduplicates case-insensitive env
vars on Windows, preferring native Windows paths (backslashes) over MSYS
paths

**Code (`cli/configssh_windows.go`)**:
- Use absolute paths for `powershell.exe` and `cmd.exe` in the SSH
config `Match exec` escape function, avoiding PATH resolution entirely

**Tests**:
- Switch `--header-command` tests from `printf` to `echo` (cmd.exe
builtin) for reliable cross-platform execution
- Add env dedup in `Test_sshConfigMatchExecEscape` for subprocess PATH
consistency

Fixes coder/internal#1556, coder/internal#1558, coder/internal#1559

> 🤖 Generated by Coder agent, will be reviewed by @mafredri. 🏂🏻
2026-06-02 11:51:16 +10:00
Danny Kopping c8555e2163 fix: deprecate ai provider seeding env config (#25854)
Environment variables used to configure AI Gateway providers are now deprecated, and we need to reflect this as such.
2026-06-01 15:15:47 +02:00
Steven Masley 4591212482 feat: implement SCIM handler for SCIM 2.0 compliance (#25572)
Rewrites the SCIM 2.0 user provisioning handler to be RFC 7644
compliant. Verified against an external IdP Okta.

Behavior is OPT IN
2026-05-28 10:00:37 -05:00
Danny Kopping 12520ee964 feat: add ai provider status and reload freshness metrics (#25770)
Add metrics for `aibridged` and `aibridgeproxyd`'s provider statuses. AI providers can be modified, and possibly misconfigured, at runtime. These metrics help operators understand the state of these provider definitions in case unexpected behaviour is observed.
2026-05-28 14:57:33 +02:00
Danny Kopping a9f5ed7644 fix: re-validate provider per request and classify reloads (#25766)
Refactors the `aibridgeproxyd` provider reload mechanism which was unnecessarily complex.

Also ensures that providers are evaluated on each CONNECT request to prevent interception of requests to (newly) disabled providers; in this case the requests will passthrough unencrypted, by design.
2026-05-28 13:22:38 +02:00
Callum StyanandMux 9d37f63fbd feat: report synthetic metadata from fake agents (#25166)
Fake agents now fetch their manifest, spawn a single per-agent metadata
goroutine, and emit batched BatchUpdateMetadata calls with 3072-byte
base64 payloads so scaletest runs mirror the load shape of real agents.
This matches what the current scaletest workspace template does for
metadata. In the future we can extend the harness here to take in a
config option for the metadata payload size.

---------

Signed-off-by: Callum Styan <callumstyan@gmail.com>
Co-authored-by: Mux <mux@coder.com>
2026-05-27 13:49:42 -07:00
Danny Kopping 79e007cf30 feat: hot-reload aibridged and aibridgeproxyd providers on DB changes (#25673)
Previously the in-process aibridge daemon and the enterprise aibridgeproxy daemon both snapshotted their provider routing once at boot. Any `ai_providers` or `ai_provider_keys` mutation required a restart for either to pick it up.

Add an `ai_providers_changed` pubsub channel that the CRUD handlers publish on after Create / Update / Delete. Both daemons subscribe:

- **aibridged** rebuilds its `[]aibridge.Provider` snapshot via `BuildProviders` and swaps it into the pool atomically. Inflight requests keep serving against the bridge they already acquired; new acquires build against the new snapshot. Per-provider construction errors stay scoped to the offending row.
- **aibridgeproxyd** rebuilds its routing snapshot from `GetAIProviders` and swaps the host→provider map atomically. The MITM listener picks up new providers without restart.

DB read for aibridgeproxyd uses the existing `AsAIProviderMetadataReader` subject for routing-only access.
2026-05-27 11:58:43 +02:00
Cian Johnston d3155e1cab test(enterprise/cli): add test to prove fix for #25699 (#25701)
Adds an end-to-end enterprise CLI test to ensure legacy AI provider keys seeded at server startup are encrypted at rest when DBCrypt external token encryption is enabled, preventing regressions related to #25699.

> Partially implemented by Coder Agents, and massaged afterwards by me.
2026-05-26 20:08:07 +00:00
Danny KoppingandClaude Opus 4.7 a56c88a0cc fix: run AI provider seed and build after newAPI so dbcrypt applies (#25699)
## Problem

Two related symptoms of the same architectural issue: the `dbcrypt`
wrapper is installed inside `enterprise/coderd.New`, so any access to
`options.Database` that happens before `newAPI` runs bypasses
encryption.

**Symptom 1 (reads):** Provider keys added via the admin UI are
encrypted at rest. `BuildProviders` was running *before* `newAPI`,
against the unwrapped store, so the ciphertext was read as-is and shoved
into the keypool as the upstream credential. Anthropic/OpenAI reject it,
and the interception log shows:

```
coderd.aibridged.pool: interception failed  ... error="all configured keys failed authentication"
  credential_kind=centralized  credential_hint=PaPb...4A==  credential_length=184
```

**Symptom 2 (writes):** `SeedAIProvidersFromEnv` was also running before
`newAPI`, against the unwrapped store, so env-derived keys
(`CODER_AIBRIDGE_OPENAI_KEY`, indexed `CODER_AIBRIDGE_PROVIDER_<N>_KEY`,
etc.) landed in `ai_provider_keys` as plaintext with `ApiKeyKeyID =
null` even when `CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS` was set.

## Fix

Move both `SeedAIProvidersFromEnv` and `BuildProviders` to after
`newAPI`, where `options.Database` is the dbcrypt-wrapped store. Writes
encrypt correctly; reads decrypt correctly.

The enterprise closure (`enterprise/cli/server.go`) runs *inside*
`newAPI` and calls `BuildProviders` for the aibridgeproxyd at that
point. Once the agpl seed moves to after `newAPI`, the proxy on first
boot would see no env-seeded providers. Add a matching seed call inside
the enterprise closure before its `BuildProviders` to cover that case.
Seeding is idempotent, so the agpl-side seed running again post-`newAPI`
is a no-op when the rows already exist.

## Known shortcomings

The clean version of this fix would just inherit `ctx` like every other
startup step and place these calls naturally. It can't, for two reasons
that are both about the surrounding handler architecture rather than
this change:

1. **`dbcrypt` wrapping is positioned inside `newAPI`, not around
`options.Database` at creation.** That's why both seed and build have to
wait until after `newAPI` in the first place. The principled fix is to
install the wrapper at the point the store is created (behind a hook the
enterprise build supplies), so every consumer sees a single
authoritative view and the ordering stops mattering. This would also
collapse the duplicated seed call back to a single site.

2. **The handler's shutdown sequence is not deferred.**
`coderAPICloser.Close()` and the other teardown steps run only if
control reaches the `select` at the bottom of the handler. An early
`return` from anywhere in Phase 1 (e.g. seed/build returning
`context.Canceled` when the user hits ctrl-c during startup) skips that
block and orphans all the goroutines `newAPI` spawned — tailnet workers,
gitsync, telemetry batcher, etc. `goleak` then catches them at package
teardown and `TestServer_TelemetryDisabled_FinalReport` fails. Moving
the shutdown into deferred closers (with a `sync.Once`-guarded close to
avoid double-close from the explicit Phase 2 call) is the principled
fix.

For this PR I took the smallest change that fixes the reported bugs: a
detached context (`context.WithoutCancel(ctx)` + a 30s timeout) at the
seed and build call sites in both the agpl and enterprise paths. It lets
the calls complete even if the user cancels during startup, after which
the handler reaches its shutdown select naturally and tears down through
Phase 2. Both shortcomings above are worth addressing separately.

## Test plan

- `make test RUN=TestServer_TelemetryDisabled_FinalReport` with `-race`;
passes locally with `-count=3`.
- Manually verified on a deployment with
`CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS` set and env-configured providers:
`ai_provider_keys.api_key_key_id` is populated, `api_key` is base64
ciphertext, and upstream auth succeeds.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 21:27:02 +02:00
Danny Kopping 282ab7de34 refactor: load AI providers from the database at startup (#25672)
Replace the env-based `BuildProviders` with a DB-backed loader. The database is now the single source of truth for runtime provider configuration; env config arrives via `SeedAIProvidersFromEnv` (run at boot) and `BuildProviders` reads it back as `aibridge.Provider` instances. `cli/server.go` and `enterprise/cli/server.go` both call the same path, so aibridged and aibridgeproxyd see the same provider set.

Per-provider `DumpDir` is replaced by a top-level `CODER_AI_GATEWAY_DUMP_DIR` base; each provider's effective dump path is `<base>/<provider name>`.
2026-05-26 15:57:01 +02:00
Danny Kopping 4ddda3a9db feat: filter interceptions and sessions by provider name (#25640)
Allows filtering sessions & interceptions by provider name, and adds a test to vaidate that provider name is immutable (at least until #25606 lands).
2026-05-25 16:31:48 +02:00
Danny Kopping ddec110b0e refactor: move aibridged out of enterprise to AGPL (#25570)
In order to allow Coder Agents to use AI Gateway in OSS, we need to rehome the `aibridged`\-related code into the AGPL path.

The HTTP API is only registered under enterprise so will still require the AI Governance Add-on to be present in order to use it, whereas Coder Agents uses an in-memory pipe to the same handlers.
2026-05-22 09:11:37 +02:00
Danny Kopping c50b0e84b9 feat!: default CODER_AI_GATEWAY_ENABLED to true (#25575)
`CODER_AI_GATEWAY_ENABLED` / `CODER_AIBRIDGE_ENABLED` is now being defaulted to `true` now that it will be used by Coder Agents.

If you previously had this value disabled explicitly, that value will persist.
2026-05-22 08:57:36 +02:00
Danny Kopping 9341efec9f feat!: seed ai_providers from env on server startup (#24895)
_Disclaimer: implemented by a Coder Agent using Claude Opus 4.7_

Part of the implementation of [RFC: Common AI Provider Configs](https://www.notion.so/coderhq/RFC-Common-AI-Provider-Configs-34bd579be59280ed958feffb82024797) (AIGOV-201).

## Note

This change can cause a previously working installation to fail to start should a conflict exist between the providers configured in the environment & those now migrated to the database.

I'll raise a PR upstack to document this process and workarounds should a startup fail.

## What this PR does

Reconciles environment-derived AI provider configuration with the `ai_providers` table at server startup. The seed runs **before** the aibridged daemon is initialized, so the runtime always reads providers from the database; the legacy `CODER_AIBRIDGE_*` environment variables become a one-shot migration source.

### Behavior

- Concurrent server starts are serialized through a Postgres advisory lock (`LockIDAIProvidersEnvSeed`).
- Missing rows are inserted with an audit entry attributed to the system actor.
- Existing rows whose canonical hash matches the env-derived hash are left alone (the common no-op restart path).
- Existing rows whose canonical hash does **not** match cause server startup to fail with a descriptive error so the operator can explicitly resolve the conflict in either env or DB.
- Soft-deleted rows are NOT resurrected from env; an explicit operator deletion is sticky across restarts.
- Indexed providers whose name conflicts with a legacy env var fail startup with a clear remediation message.
- Unknown provider types (e.g. `copilot`, until the DB enum is widened) are skipped with a log entry rather than failing startup.

### Canonical hashing

The `canonicalAIProvider` shape captures exactly the fields that determine runtime behavior — `type`, `base_url`, and the Bedrock subset of settings (access key, access key secret, region, model, small fast model) — and is hashed with SHA-256. The hash is **computed on demand from the row + env**, never persisted, so the database does not need a new column for it. API keys live in the separate `ai_provider_keys` table and are intentionally excluded from the hash so operators can rotate keys via the API without forcing a server restart.

<details>
<summary>Decision log</summary>

- The hash is intentionally not persisted in the database. The RFC discussed this trade-off; computing on demand keeps the schema minimal and lets the canonical shape evolve without a migration.
- The lock uses an `iota` slot in `coderd/database/lock.go` rather than `GenLockID` so it's stable, easy to audit, and matches the convention used for every other startup lock.
- A bearer-token Anthropic provider whose env vars also set Bedrock metadata but no AWS credentials does NOT store the Bedrock fields. Without credentials the discriminated settings would misrepresent the row as Bedrock auth.
- We deliberately do NOT publish to the `ai_providers_changed` pubsub channel from the seed because the seed completes before any subscriber is started; the follow-up PR introduces that channel.

</details>
2026-05-22 08:37:27 +02:00