> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bytebase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bytebase 3.23.0 - Sep 24, 2026

> MCP Access Policy and Inline Review Comments

## 🔔 Notable Changes

* **MCP access policy** — Previously, an MCP session could reach whatever the connecting user's own permissions allowed, with no workspace-level control. You can now choose what MCP sessions may do in the workspace under **Integration** > **MCP** > **Access policy**: **Disabled**, **Read-only**, or **Read-write**. Each user's own permissions still cap every call.
  * New workspaces start at **Read-only**. A workspace that has never set a policy stays at **Read-write** until an admin sets one, so check the setting after upgrading.
  * No mode lets an MCP session change workspace settings, approve issues, or touch credentials. A policy change also applies to clients that are already connected.
  * When you connect an MCP client, the sign-in page shows the workspace policy, and **Allow** is available only when the policy is **Read-only** or **Read-write**.
  * See [MCP docs](/integrations/mcp) and [change details](#mcp).

* **Inline comments on Plan review** — Previously, review feedback lived in the Review Activity feed, detached from the SQL it was about. You can now comment on specific lines of the statement in the Plan detail editor: select lines in the gutter, write the comment, and it starts a thread with replies and Resolve/Reopen, shown both inline and in Review Activity.
  * When the SQL is edited, comments stay on the lines that did not change. Comments on lines that changed or were deleted are marked outdated and kept in Review Activity.
  * You can comment once the issue is submitted for review. A counter shows how many threads are still open, and you can jump from one to the next.

* **Workload identities: any OIDC provider and a required audience** — Previously, workload identities supported only GitHub Actions and GitLab CI. You can now use any OIDC issuer, so more of your automation can authenticate without a service account key.
  * An optional JWKS URL covers issuers that do not publish a discovery document.
  * **Existing GitHub and GitLab identities:** tokens must now carry an audience on the identity's allowed list, and a wildcard subject pattern must name at least the owner or group (`repo:my-org/*`, not `repo:*`). Upgrading adds the audiences that Bytebase-generated and documented pipelines request, so those keep working. An identity whose pipeline requests another audience, such as the GitHub Enterprise Server default, or whose pattern is broader stops authenticating until you update it.
  * The Terraform provider can now authenticate as a workload identity, and `bytebase_workload_identity` now requires `issuer_url`, `allowed_audiences`, and `subject_pattern`. See the Terraform section.
  * See [change details](#workload-identity).

* **Audit log: actor identity and refused calls** — Two changes for anyone exporting, filtering, or shipping the audit log.
  * The actor of each entry now shows whether it was a user, a service account, or a workload identity (`users/{email}`, `serviceAccounts/{email}`, `workloadIdentities/{email}`); before, everyone was recorded as a user. In the API and exports, the `user` field is renamed `actor`, so filter with `actor == 'users/bb@bytebase.com'` instead of `user`.
  * Requests denied by a permission check, or blocked outright by the MCP access policy, are no longer saved in the audit log; a query the Read-only MCP policy refuses is still saved, as `WARNING`. If **Enable audit logging to stdout** is on (self-hosted only), every refusal is written to stdout as `WARNING`. Searching the audit log is now itself logged.
  * See [audit log docs](/security/audit-log) and [change details](#audit-log).

* **API changes** — Several v1 API methods are renamed, stricter, or paginated: the login-page restriction endpoint is renamed, listing identity providers now requires authentication, the four `BatchGet` methods are all-or-nothing, Update methods reject requests whose body disagrees with `update_mask`, `ListTaskRuns` is paginated, and `GET /v1/mcp/info` is removed. Review [change details](#api-changes) before upgrading an integration.

* **PostgreSQL: data-modifying queries in SQL Editor need DML permission** — Some statements look like reads but write data: a `SELECT` whose `WITH` clause runs `INSERT`, `UPDATE`, or `DELETE`, and `EXPLAIN ANALYZE` of a write, which actually executes it. These now need DML permission; query permission (`bb.sql.select`) alone is no longer enough. See [change details](#postgresql-sql-editor-permissions).

* **Oracle database link queries now require access to the linked database** — Reading a table through a database link (`schema.table@link`) in SQL Editor, export, or MCP now needs the same grant as querying that database directly. Links to another instance, or to a database Bytebase does not manage, are not supported and are refused before the query runs. See [change details](#oracle-database-links).

## 🚀 Features

* **Query plan visualizer in SQL Editor** — Query plans now show in the result pane instead of a pop-up, as text, a diagram, a grid, or a summary, for PostgreSQL, SQL Server, and Spanner. Running `EXPLAIN` yourself also shows the visual plan, including `FORMAT JSON`, `XML`, `YAML`, and `ANALYZE` on PostgreSQL. A new maximize button expands the result pane to the full editor height.
  * The SQL Server plan view keeps working after Chrome drops XSLT support in November 2026.
  * The Visualize Explain pop-up action is removed; the plan opens inline. API: `explain_format` replaces `mssql_explain_format` — see [change details](#api-changes).

* **Last plan editor cannot approve their own change** — The user who last edited a plan's SQL can no longer approve that plan. This is controlled by the project setting **Allow approval by the person who last changed the plan**: off in new projects, left on for existing projects so nothing changes until you turn it off. When it is off, the last editor is also left out of approval-request notifications. See [approval docs](/change-database/approval).

## 🎄 Enhancements

* Instance connection setup is redesigned, with a single **Authentication** dropdown and inline validation before you submit.

* **Open SQL Editor** is now the primary action on the database page, with secondary actions moved into a **More** menu on narrower screens.

* **Data masking** — Built-in semantic types are available without adding them manually, and configuring a column's semantic type takes one dialog instead of two.

* **Self-hosted** — Set annotations on the Bytebase pod with the Helm value `bytebase.podAnnotations`, for example for Prometheus scraping, and schema sync puts less load on the metadata database.

* Service accounts: resetting a key is a single step, and a service account can no longer sign in to the web console.

* SQL Editor AI assistant: chat history is kept per workspace and user (the last 20 conversations), conversation titles are edited inline, and the chat window layout is refreshed.

* **MySQL** — gh-ost migrations gain an opt-in **Skip metadata lock check** option, off by default, with a warning that enabling it can lose writes during cut-over.

* **OceanBase** — Approval rules and risk levels that check the SQL type (`statement.sql_type`) now work for OceanBase changes.

## 🐞 Bug Fixes

* Fix wrong affected-rows estimates in plan checks, release checks, and approval and SQL review rules that use `statement.affected_rows`. Previously, TiDB always reported 0 and CockroachDB reported nothing.

* **SQL Editor** — Fix opening SQL Editor by URL or from the API immediately saving an empty "Untitled" saved query.

* Fix **Submit anyway** not working when plan checks failed or were canceled and **Enforce SQL review** is off.

* **MySQL** — Fix false syntax errors on Aurora MySQL's `LOAD DATA FROM S3`, `LOAD XML FROM S3`, and `SELECT ... INTO OUTFILE S3`; on statements that use a keyword such as `status` as a name, for example `SET status = 1` in a stored procedure; and on nested `/*` comments.

* **MySQL** — Fix Aurora and RDS IAM connections intermittently failing during large rollouts because the RDS CA bundle was downloaded on every connection. Bytebase now downloads it once, and uses your own `ssl_ca` instead when one is set.

* **PostgreSQL** — Fix SQL Editor breaking a `LIMIT ... OFFSET` query when its `LIMIT` is higher than the SQL Editor row limit.

* **SQL Server** — Fix prior backup failing on `UPDATE` and `DELETE` with a `WITH` clause; SQL Editor crashing on stored procedures that return rows and on statements with an `OUTPUT` clause, and hanging on results over the row limit; and unique indexes syncing as non-unique, which made schema diffs keep proposing to recreate them. The first sync after upgrading shows a one-time diff for those indexes.

* **Oracle/Redshift** — Fix viewing the definition of views, functions, procedures, and sequences failing with an unsupported-engine error, and Oracle view definitions missing their `INSTEAD OF` triggers.

* **TiDB** — Fix SQL Editor queries that select `_tidb_rowid` failing with a data-masking error and triggering a full schema sync on every attempt.

* **Spanner** — Fix queries that start with `WITH` failing in SQL Editor whenever a row limit applies.

* **Doris** — Fix schema sync failing on Doris and SelectDB versions that no longer support `SHOW FUNCTION STATUS`, which left the SQL Editor object browser empty.

## 🏗️ Terraform

* Upgrade to provider **3.23.0** after upgrading the server — [Migration Guide](https://github.com/bytebase/terraform-provider-bytebase/blob/main/migration/3.23.0.md). No state migration is required.
  * **New:** `bytebase_workload_identity` accepts `provider_type = "OIDC"` and an optional `jwks_url`.
  * **New:** the provider can sign in as a workload identity instead of with a service account key: set `workload_identity_email` plus `workload_identity_token` or `workload_identity_token_file`.
  * **Now required** on `bytebase_workload_identity`: `issuer_url`, `allowed_audiences`, and `subject_pattern`.

## 📃 Change Details

### MCP

* `GET /v1/mcp/info` (`WorkspaceService.GetMCPInfo`) is removed. Read `mcpSetting` from `GET /v1/actuator/info` (`ActuatorService.GetActuatorInfo`), which requires authentication; an MCP token is not accepted there. The `modes`, `methods`, `engines`, and `data_masking_available` fields have no replacement.
* MCP requests larger than 4 MiB are refused with HTTP 413.
* **Upgrading from 3.21 or earlier:** each MCP client must sign in again. Sign-ins from before 3.22.0 cannot be refreshed and fail with `invalid_grant` once the current access token expires. Remove the server from the client and add it again. An MCP capability set through the 3.21 API is not carried over.

### Workload Identity

Every workload identity — GitHub, GitLab, or OIDC — now carries the same requirements: an issuer whose keys can be fetched, at least one non-blank allowed audience, and a subject pattern that does not match everything.

| Existing identity                                                                                    | On upgrade                                                                                                                                                                                                                                                             |
| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Has an issuer and lists audiences                                                                    | Unchanged                                                                                                                                                                                                                                                              |
| Has an issuer and no audiences                                                                       | Gets `bytebase`, plus the provider default where it can be derived: `https://github.com/{owner}` for a GitHub.com identity whose subject pattern names the owner, and the instance URL for a GitLab identity. A GitHub Enterprise Server identity gets only `bytebase` |
| No issuer                                                                                            | Never authenticated; now also rejected at save time                                                                                                                                                                                                                    |
| Subject pattern that matches every repository or project, such as `*`, `repo:*`, or `project_path:*` | Stops authenticating until you set a pattern that names at least the owner or group                                                                                                                                                                                    |

A pipeline that requests an audience not on the identity's list stops authenticating; add that audience to the list, or change the pipeline to request one that is. Existing identities get a `provider_type` based on their subject pattern, and new identities must set `provider_type`, `issuer_url`, `allowed_audiences`, and `subject_pattern`.

### Audit Log

**Actor.** `AuditLog.user` is renamed `AuditLog.actor` (same field number; the JSON key changes from `user` to `actor`). Values are `users/{email}`, `serviceAccounts/{email}`, or `workloadIdentities/{email}`; before, every principal was written as `users/{email}`. In `SearchAuditLogs`, filter on `actor == 'users/bb@bytebase.com'` — a filter on `user` is rejected. The export's column header changes from `user` to `actor`.

**What is stored, what is streamed.**

| Call                                                                       | Database audit log                      | stdout audit log (when enabled) |
| -------------------------------------------------------------------------- | --------------------------------------- | ------------------------------- |
| A call that ran                                                            | Stored                                  | Streamed                        |
| A call denied by a permission check or the MCP access policy before it ran | Not stored                              | Streamed, severity `WARNING`    |
| A refused MCP connection or consent                                        | Stored                                  | Streamed                        |
| A query the Read-only MCP policy refuses                                   | Stored, severity `WARNING` (was `INFO`) | Streamed                        |
| `SearchAuditLogs`                                                          | Stored, without its result list         | Streamed                        |

3.22 stored calls the MCP policy blocked outright in the database; 3.23 streams them instead. A SIEM that reads stdout now sees every refused attempt; the database keeps calls that ran and queries the Read-only policy refused.

### API Changes

| Change                                                                     | Before                                                                                                                      | After                                                                                                                                                                                          |
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AuthService.GetAuthenticationRestriction`                                 | `GET /v1/auth/restriction`                                                                                                  | Renamed `GetAuthenticationInfo`, `GET /v1/auth/info`; the response now also lists the identity providers the login page renders                                                                |
| `IdentityProviderService.ListIdentityProviders`                            | No credential required                                                                                                      | Requires authentication and `bb.identityProviders.list`; `parent` is required. Workspace Admin has the permission; a custom role that held `bb.identityProviders.get` gains `.list` on upgrade |
| `BatchGetDatabases`, `BatchGetGroups`, `BatchGetUsers`, `BatchGetProjects` | A missing or unreadable name was dropped, swallowed, or returned out of order                                               | One resource per requested name, in request order; the first name that does not resolve fails the whole call with `NotFound`                                                                   |
| `UpdateDataSource`                                                         | A credential path that disagreed with `authentication_type` wrote the wrong credential or nothing                           | `InvalidArgument`                                                                                                                                                                              |
| `UpdateSetting` (`APP_IM`)                                                 | No `update_mask` replaced the whole setting                                                                                 | `update_mask` required; a masked provider omitted from the payload is removed                                                                                                                  |
| `UpdateIssue`                                                              | An unsupported mask path returned 200 and did nothing                                                                       | `InvalidArgument`                                                                                                                                                                              |
| `UpdateDatabaseCatalog`                                                    | `allow_missing` present                                                                                                     | `allow_missing` removed; the call replaces the whole catalog                                                                                                                                   |
| `UpdateDatabase`                                                           | `allow_missing` present (never honored)                                                                                     | Removed                                                                                                                                                                                        |
| `RolloutService.ListTaskRuns`                                              | Unpaginated                                                                                                                 | `page_size` (default and maximum 1000) and `page_token`; the response carries `next_page_token`. A caller that ignores the token sees at most 1000 task runs                                   |
| `SearchQueryHistories` filter                                              | `statement == "..."` matched as a pattern; `instance == "instances/x/%"` was the workaround for a filter that never matched | `statement ==` is exact (`%` and `_` are literal); `instance == "instances/x"` works and the `/%` form is `InvalidArgument`; `projects/{project}/instances/...` names accepted                 |
| `SearchAuditLogs` filter                                                   | `user == '...'`                                                                                                             | `actor == '...'`                                                                                                                                                                               |
| `QueryOption.mssql_explain_format`                                         | SQL Server only                                                                                                             | Removed; use `explain_format` (`TEXT`, `JSON`, `XML`, `YAML`), which every engine honors and rejects a format the engine cannot produce                                                        |
| `WorkspaceService.GetMCPInfo`                                              | `GET /v1/mcp/info`                                                                                                          | Removed; see [MCP](#mcp)                                                                                                                                                                       |
| `MCPSetting.ignore_masking_exemptions`                                     | Present (3.22.0)                                                                                                            | Removed; MCP sessions apply the caller's own masking exemptions and unmask grants, as the console does                                                                                         |
| `IamPolicy.etag` on `SetIamPolicy`                                         | Ignored                                                                                                                     | Honored: a stale etag returns `ABORTED`; `policy.etag` and the request `etag` must agree                                                                                                       |
| `WorkloadIdentityConfig`                                                   | `provider_type`, `issuer_url`, `allowed_audiences`, `subject_pattern` optional                                              | Required; `provider_type` accepts `OIDC`; new optional `jwks_url`                                                                                                                              |

### PostgreSQL SQL Editor Permissions

A `SELECT` whose `WITH` clause runs `INSERT`, `UPDATE`, or `DELETE`, `EXPLAIN ANALYZE` of such a `SELECT`, and `EXPLAIN ANALYZE` of a `MERGE` are classified as DML; `bb.sql.select` alone no longer runs them. `EXPLAIN ANALYZE` of a write is authorized per target table, so a table-scoped DML grant covers it. CockroachDB behaves the same.

### Oracle Database Links

Before 3.23.0, a query that read a table through a database link ran on Oracle; if Bytebase could not find the link's target, the query failed after running. Now the linked table is checked before the query runs:

| The link reaches                                                                                                 | Result                                                                                |
| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| A database on the same instance, in the same project                                                             | Needs the same grant as a direct query on that database; a denial names what to grant |
| Another instance                                                                                                 | Refused: not supported. Query that instance directly                                  |
| A database in another project, one Bytebase does not track, or a target it cannot identify (such as a TNS alias) | Refused                                                                               |

Under a read-only data source whose Oracle account owns private database links, every linked read is refused. Use the admin data source, or remove that account's private links.

## ⚙️ Install and Upgrade

* [Fresh install](/get-started/self-host/deploy-with-docker)

* [Upgrade](/get-started/self-host/upgrade)

*Before upgrading: 1) Back up the [metadata](/get-started/self-host/upgrade#back-up-and-restore) — in-place downgrade is not supported. 2) Do not run multiple containers on the same data directory. 3) Terraform users: upgrade Bytebase server first, then apply the new Terraform config.*
