Release Notes¶
Highlights for each Lakekeeper release. For the full commit-level
changelog, see the GitHub Releases
or CHANGELOG.md.
For Lakekeeper+ releases, see the Lakekeeper+ Release Notes.
📬 Get notified about new Lakekeeper releases
Subscribe by email to hear about new releases, or Watch → Releases on GitHub.
v0.15.0 (2026-11-06)¶
Lakekeeper 0.15 is here, and it is a big one! The new write privilege lets pipelines and dbt change tables without being able to drop them, Lakekeeper instances keep their caches in sync, and task workers scale up and down with the work. Views run more safely, and concurrent commits and busy storage are handled more gracefully. Several security improvements change existing behavior, so please read the Breaking Changes and Upgrade Notes before you upgrade.
Highlights¶
write: change tables, keep them safe. With OpenFGA, the newwriteprivilege lets users and pipelines change the data, schema, properties and snapshots of tables, views and generic tables, includingCREATE OR REPLACEand dbt models. Dropping, renaming and protecting stay withmodify, which includeswrite, so existing grants keep working. See Write (#2240).- All instances stay in sync. When several Lakekeeper instances share one database, a change made through one of them reaches the caches of all others, usually within a second. See Cache Invalidation Across Instances (#2157).
- Task workers that scale with the work. An autoscaler such as KEDA can start task workers only while there is work, and idle workers stop on their own. Running tasks can finish before Lakekeeper stops, failed tasks are retried with a growing wait, and large queues stay fast. See Task Workers on Kubernetes (#2147, #2155, #2172, #2186, #2187, #2189, #2190, #2206).
Features¶
- Console v0.29.0. Grant
writeright in the console, and get around faster: the navigation bar shows each page's tabs, your pinned objects and the ones you opened recently, and a pinned table or view opens in LoQE. Tables, views and generic tables move to another namespace from their menu, and S3 storage that needs an STS principal is easy to set up (#2227, #2258). - Google Cloud Storage on Google's official Rust SDK. Requests that fail for a temporary reason are retried, and every request has a time limit. Please check the GCS points under Breaking Changes before you upgrade. See GCS (#2159).
- S3-compatible storage whose STS needs a principal, such as Yandex Object Storage (thanks @Mohil-Ahuja). See S3 (#2139).
- Fewer metadata files. A commit that changes nothing, like the one Spark sends before each
mergeSchemawrite, keeps the current metadata file (#2229). - OPA bridge: metadata checks per table. The bridge can check the columns of every table and view on its own, also for users who may list everything in a namespace. See Shortcut for broad access (#2153).
- Generated API clients can choose the project with
x-project-idon every project-scoped management endpoint (thanks @baptistegh) (#2252). - More ways to extend Lakekeeper. Code that builds on Lakekeeper's crates can add management routes, keep its own caches in sync, run its own task queues and receive every audit record (thanks @raffael-dzikowski). See Customize (#2130, #2138, #2161, #2177, #2179, #2202, #2225).
Bug Fixes¶
- Concurrent commits. Writers that race on different branches of one table, or a staged (WAP) write and another commit, get a conflict that clients retry. A commit to a table that is deleted meanwhile gets "not found", and
maincannot be turned into a tag (#2218, #2233, #2251). - A client with a wrong clock cannot make a table unusable. Lakekeeper records its own clock as a table's last-updated time (#2220).
- Format-version 1 tables load with all their branches and tags, so snapshot expiry keeps every snapshot they use (#2182).
- Busy storage. Purges share a limit per bucket or storage account, storage requests have time limits, and timeouts or throttling return
503, which clients retry (#2165). - Storage stays tidy. Views are created and moved only to empty locations, as tables are. A recursive namespace delete with purge removes the files of generic tables too, and a dropped table leaves no snapshots behind (#2243, #2244, #2245, #2246).
- Tasks, warehouses and statistics. "Run now" on a task that is still finishing keeps the new run, deleting a warehouse while its protection changes fails safely, and endpoint statistics count HEAD requests and list short time windows fast (#2130, #2138, #2147).
- OPA bridge: comments on views work through Trino for users who may change the view (#2238).
- Smaller fixes. Permission checks by name look at the live table or view, the migration check honors
LAKEKEEPER__PG_SCHEMA, and registering a table with a UUID that is already used says so (#2215, #2239, #2257). - Proxies, entity tags and locations. IPv6 hosts behind a proxy get the right URLs, entity tags with commas are read correctly, and locations are joined with a single slash (thanks @fallintoplace) (#2221, #2223, #2224).
- Dependencies. The AWS SDK update drops the unmaintained
pin-utilscrate (RUSTSEC-2026-0441) (#2250).
Breaking Changes¶
- OpenFGA:
createshows only what you create. Grantdescribetoo where users need to see the tables and views of others. See Create (#2240). - Some actions need
modifyon the warehouse: registering a table, creating a generic table on a location that already holds files, and anything that skips soft deletion, such as aforcedrop. Grant it to the users and pipelines that do this. See Modify and Skipping Soft Deletion (#2239, #2249). - Views run more safely by default. See View Security (#2212, #2242, #2247, #2254).
- To create
SECURITY DEFINERviews through Trino, configure Trino as a trusted engine, and check the owners of existing views. - An INVOKER view inside a DEFINER view runs as the caller. See Chained Views to keep such views working.
- Switching a view between
SECURITY DEFINERandSECURITY INVOKERneedsmodify, which matters forCREATE OR REPLACE VIEWin Trino and dbt. See Property Protection. - Views return no storage credentials. Query engines read the tables behind a view with each table's own access.
- To create
- OPA bridge: Trino statements are checked on the objects they act on.
CREATE OR REPLACEneedswrite, metadata tables that show data needread_data, and names that Trino would read differently are hidden. Deploy the bridge policies of this release with the server. See OPA (#2215, #2238, #2242, #2254). - New names cannot contain
+or control characters. Existing tables and views keep their names (#2215). servestarts only on a migrated database. Helm chart users are not affected. In your own manifests, runmigratefirst or let the pods wait withwait-for-db -dm. See Upgrades & Migration (#2256).- S3: the AWS system identity rules apply to all storage access. A warehouse that uses the system identity without an
assume-role-arn, or assumes a role without anexternal-id, needs the missing field. See AWS System Identity (#2174). - Google Cloud Storage. If you use GCS, check these points before you upgrade. See GCS (#2159).
- Behind an HTTP proxy, allow direct connections to
storage.googleapis.comon port 443. - Write a key from
GOOGLE_APPLICATION_CREDENTIALS_JSONto a file and setGOOGLE_APPLICATION_CREDENTIALSto its path. - Convert keys in the PKCS#1 format (
BEGIN RSA PRIVATE KEY) withopenssl pkcs8 -topk8 -nocrypt. Keys created in the Google Cloud console are fine. - If you build your own minimal image, install
ca-certificates. The official images include them.
- Behind an HTTP proxy, allow direct connections to
- STACKIT credentials. Regenerate clients built from the management API: STACKIT warehouses report the new credential type
stackit-access-key(#2194). - Kafka: Kerberos (
GSSAPI) is not supported. Switch toSCRAM-SHA-512,OAUTHBEARERor mutual TLS before you upgrade. See Kafka (#2108). - Code that builds on Lakekeeper's crates. Many public traits and types changed, and workspaces need the same
sqlxpatch entries as Lakekeeper'sCargo.toml. Prebuilt binaries and images are not affected (#2138, #2147, #2157, #2177, #2186).
Upgrade Notes¶
- Plan for the database migration.
lakekeeper migratecopies the endpoint statistics, which takes about 6 seconds per million rows on a typical setup and needs free disk space of about twice that table's size. Ifpg_dumpor another long transaction is running, the migration stops without changes; just run it again (#2138, #2147, #2244). - Terraform and
write. If the community Terraform provider manages a user's or role's grants, wait for a provider release that knowswrite: v0.5.0 and earlier revokewritegrants made outside Terraform (#2240). - Keep clocks in sync. Lakekeeper refuses a new snapshot on
mainthat is dated more than a minute ahead of its own clock. See Production (#2220).
Audit log format¶
Additions — an existing parser keeps working:
-
Every audit record now carries an
event_id, a UUID that is unique to that record.Records that share a
request_id, such as those of a grant change, have differentevent_idvalues. A line delivered twice carries the sameevent_id, so you can use it to remove duplicates. It is a UUIDv7, so ids sort roughly by time.What to do: Nothing. To remove duplicate records, use
event_id.
Also worth knowing — the format itself did not change:
-
Registering files that are already in storage is authorized on the warehouse, with the new action
register_table.POST /catalog/v1/{prefix}/namespaces/{namespace}/register register_table (warehouse entity) new, in addition to create_table (namespace entity) POST /lakekeeper/v1/{prefix}/namespaces/{namespace}/generic-tables register_table (warehouse entity) new, in addition to create_generic_table (namespace entity), only when the location holds filesA register now produces an authorization record for
register_tableon the warehouse, allowed or refused, in addition to thecreate_tablerecord on the namespace.register_tableis checked before the metadata file is read. Ifregister_tableis refused, thecreate_tablerecord is also written, as refused. It names the table only by the requested name, because the metadata file is not read.If the caller may register the table but may not see it, the table stays registered, and the register writes a refused
get_metadatarecord on the table, asloadTabledoes.Creating a generic table on a location that already holds files produces a
register_tablerecord on the warehouse in addition to thecreate_generic_tablerecord. A refusal has error typeWarehouseActionForbidden, and its message says that the location holds files. A location without files produces noregister_tablerecord.What to do: Nothing. To find registers and generic tables created over existing files, match
register_table. -
Switching a Lakekeeper process into or out of read-only maintenance mode at runtime is recorded with two new actions.
POST /management/v1/server/maintenance-mode mode read-only set_maintenance_mode_read_only mode off set_maintenance_mode_offBoth are recorded on the server entity, allowed or refused. Only instance admins are allowed.
What to do: Nothing.
-
A call that skips the soft-deletion window of a warehouse also asks
modify_soft_deletionon that warehouse, which has a record of its own. The call is refused when the caller may not change the soft-deletion settings of that warehouse.drop (table, view or generic table) force: true delete (namespace) recursive: true control_tasks (warehouse) run now or run at, when a soft-deletion task is among the tasks drop (table), invoked_by: register_table_overwrite force: trueThe
control_tasksrecord does not say which control or which tasks were asked for. All four cases apply only in warehouses with soft deletion.When the check of its own action allows such a call, the call writes its record with
decision: allowed, and then a record ofmodify_soft_deletionon the warehouse. If the caller may not change the soft-deletion settings, this second record hasdecision: denied,failure_reason: action_forbiddenand error typeWarehouseActionForbidden, and the call is refused. A call that the check of its own action refuses writes nomodify_soft_deletionrecord.A change of the soft-deletion settings writes the same
modify_soft_deletionrecord, and no other authorization record. Themodify_soft_deletionrecord of a skip has the samerequest_idas the record of the call.A register with
overwritethat replaces an active table with another table (a different table UUID) in a warehouse with soft deletion records the drop of the replaced table withforce: true, also when the call is allowed. Before, it recordedforce: false.What to do: To find calls that skip the soft-deletion window, match
modify_soft_deletionrecords that share theirrequest_idwith a record of another action. If you filter or alert on drops withforce: true, these now also include drops by a register withoverwrite. You can tell them apart byinvoked_by: register_table_overwrite. -
Replacing a warehouse's storage credential is recorded as
update_storage_credential; a storage profile change recordsupdate_storageandupdate_storage_credential.before after POST …/warehouse/{id}/storage-credential update_storage update_storage_credential POST …/warehouse/{id}/storage/validate-credential update_storage update_storage_credential POST …/warehouse/{id}/storage update_storage update_storage, update_storage_credential POST …/warehouse/{id}/storage/validate-profile update_storage update_storage, update_storage_credential POST …/warehouse/{id}/storage/validate-access update_storage update_storageA record with both actions has two entries in
actionsand one entry per action inauthorizations. If the request is denied, both entries haveallowed: false. If an error stopped the evaluation, neither entry hasallowed.What to do: If a rule finds credential replacements by
update_storage, matchupdate_storage_credentialas well. If a rule reads only the first entry ofactions, read all entries. -
A view create or view commit that the view owner property rules refuse is recorded as a denied authorization.
create_view (namespace entity) error type ProtectedPropertyModification commit (view entity) error type ProtectedPropertyModification or ProtectedViewVersionChangeOn a server with authentication, a
create_vieworcommitthat writestrino.run-as-owner, or the owner property of a configured trusted engine, is refused unless it comes from a trusted engine that owns that property. Acommitthat changes the current version of a view holding such a property is refused unless it comes from that engine. The records havedecision: deniedandfailure_reason: action_forbidden. AProtectedViewVersionChangerecord has the samerequest_idas an allowedcommitrecord of the same request.If that engine changes the query of such a view and does not send the owner property, the commit removes the property. The allowed
commitrecord listsupdated_propertiesandremoved_propertiesas the engine sent them.What to do: Nothing.
-
A view commit that switches the view between running as its owner and running as the caller is recorded with the new view action
change_security_mode, in records of its own.change_security_mode (view entity) one record per checked principal, after the commit record introspect_permissions, operation view-replace recorded as commit (view entity)A view commit from a trusted engine that adds or removes
trino.run-as-owner, or the owner property of another configured trusted engine, needschange_security_modeon the view. After thecommitrecord of the request, Lakekeeper writes onechange_security_moderecord for the identity that sends the commit and, if the commit adds the property, one for the user that the property names, with that user infor_principal. Each record has its own decision and the samerequest_id. A refused switch hasdecision: denied,failure_reason: action_forbiddenand error typeSecurityModeChangeForbidden, and the view does not change. A commit that keeps the security mode writes nochange_security_moderecord.The batch-check operation
view-replaceasks whether a user may replace a view. Its decision is recorded ascommiton the view, even whenchange_security_modedecided it.What to do: Nothing.
Records from v0.15.0 carry audit_format 1.1 (was 1.0).
v0.14.2 (2026-10-25)¶
Features¶
- Read-only maintenance mode at runtime. Instance admins can switch a running Lakekeeper process into read-only maintenance mode, and back, with
POST /management/v1/server/maintenance-mode. While read-only, requests that only read keep working, and no new background tasks start. The mode belongs to one process, so call each replica on its own address (#2156). - Fewer database round trips. Read transactions, such as those of paged listings, need one fewer round trip to the database to start (#2146).
Bug Fixes¶
- GCS: vended credentials report their real expiry. When Google STS returns no expiry, a vended token now expires with the token it was made from. Before, Lakekeeper assumed one hour, so clients could get tokens that had already expired (thanks @rohankmr414) (#2160).
- S3 remote signing with an assumed role or the system identity. Lakekeeper caches the identity it signs with, so remote signing no longer calls STS for every request.
LAKEKEEPER__CACHE__STC__ENABLEDandLAKEKEEPER__CACHE__STC__CAPACITYapply to this cache too (#2170). - Soft-deleted tables and views expire in inactive warehouses. Before, their expiry was skipped, and they and their files stayed forever (#2151).
- Object paths with
@. Locations such ass3://bucket/foo@barare no longer read as containing a user name (thanks @sivakumar-mahalingam) (#2141). - Security updates of dependencies. Updated the webpki crates,
ctutilsandtokio, and removedcrypto-bigint, to fix security advisories RUSTSEC-2026-0381 to -0383 and RUSTSEC-2026-0385 to -0387 (#2166).
Audit log format¶
Also worth knowing — the format itself did not change:
-
Switching a Lakekeeper process into or out of read-only maintenance mode at runtime is recorded with two new actions.
POST /management/v1/server/maintenance-mode mode read-only set_maintenance_mode_read_only mode off set_maintenance_mode_offBoth are recorded on the server entity, allowed or refused. Only instance admins are allowed.
What to do: Nothing.
Records from v0.14.2 carry audit_format 1.0 (unchanged).
v0.14.1 (2026-10-16)¶
Bug Fixes¶
- Security: S3 remote signing signs only what it knows. The S3 signer signs requests only with query parameters and headers it knows, and signs a copy only when its source is inside the table. Unknown parameters and headers are refused, and so are writes that use an encryption key other than the warehouse's (#2102).
- Remote signing with Iceberg 1.12 or later. Clients no longer get
403 SignatureDoesNotMatcherrors when they delete more than 250 files at once, or delete files several times in a row, for example inexpire_snapshotsorremove_orphan_files. The signer now tells clients in theCache-ControlHTTP header that a signature for a write must not be reused (#2124). - Ranged reads with remote signing. Clients that reuse signatures, such as Iceberg's
S3FileIO, no longer get403 SignatureDoesNotMatcherrors on the second ranged read of a file. The signer now leaves theRangeheader out of the signature in upper and lower case (thanks @Mohil-Ahuja) (#2104). - Removing the last branch or tag. A commit that removes the last branch or tag of a table, such as
main, is now kept. Before, the reference came back when the table was loaded again (#2117). - Concurrent commits to one table. When commits to the same table block each other in the database, the client gets a
409error, which it retries. Before, it got a503error (#2117). - UI paths. The UI no longer serves a file for a path that contains
ui/a second time. Such a path now returns404(thanks @sivakumar-mahalingam) (#2119). - Security updates of dependencies. Updated
rustlsandquinn-prototo fix security advisories RUSTSEC-2026-0338 to -0342 and RUSTSEC-2026-0346 to -0349 (#2125, #2112).
v0.14.0 (2026-10-07)¶
Highlights¶
- Grants API. One API lists, grants and revokes permissions on every object, such as projects, warehouses, namespaces, tables, views and tag definitions. It works the same way with every authorizer. With OpenFGA, it uses the permissions you already have, so nothing needs to be migrated. The API is in preview. See Grants API (#1945, #1953, #1957, #2085).
- Governance tags. Define tags for a project, attach them to warehouses, namespaces, tables, views, generic tables and columns, and find every object a tag is attached to. Tags are for classification and discovery. With Cedar in Lakekeeper Plus, policies can also use them to decide who can access what. The API is in preview. See Governance Tags (#1914, #1921, #1944, #1951).
- Move and rename namespaces. You can move a namespace to another parent in the same warehouse, rename it, or both, in the UI or with the management API. Everything in it moves with it, and files stay where they are. A namespace that contains other namespaces cannot be moved or renamed. See Moving and renaming namespaces (#1950).
Features¶
- Faster on large catalogs. With OpenFGA, checking access to a warehouse, which most catalog requests do, needs far fewer OpenFGA queries. On large warehouses, this check could take too long and fail with
503. Creating tables and views, finding namespaces and listing tables are also much faster on large warehouses (#2029, #2005, #1958). - Storage validation. You can check a warehouse's storage before you create the warehouse or change its storage, and you can check an existing warehouse at any time. Lakekeeper reports the result of each check. It also warns about problems that are hard to find later, such as a bucket CORS policy that stops the query engine in the UI from reading data. See Storage Validation (#1936, #2047, #2049, #2061).
- STACKIT Object Storage. A new
stackitstorage type asks only for the settings that STACKIT needs. Storage validation warns when other credentials groups in the same STACKIT project can also access the bucket. See STACKIT (#1978, #2045, #2050, #2055). - Alibaba Cloud OSS (beta). Warehouses can use Alibaba Cloud OSS, with temporary credentials from Alibaba Cloud STS (thanks @yoogoc). See Alibaba Cloud OSS (#1894).
- Required token claims. You can set rules for the claims in a token, and Lakekeeper rejects tokens that do not match them. For example, you can allow only the users of one organization when several organizations share an identity provider. The scope check (
LAKEKEEPER__OPENID_SCOPE) also reads thescpclaim, which Microsoft Entra ID uses. See Required claims (#2001). - Names for service accounts. When a token has no name claim,
LAKEKEEPER__OPENID_DISPLAY_NAME_TEMPLATEbuilds a name from its other claims, for exampleService Account {email}. It applies to users that are registered after you set it (#1939). - Headless workers. With
LAKEKEEPER__SERVE_HTTP_API=false,lakekeeper servedoes not serve the API or the UI. It only runs background tasks and answers health checks, so you can scale task workers separately from the API. See Task Queues (#2065). - Postgres schema.
LAKEKEEPER__PG_SCHEMAkeeps Lakekeeper's own database tables in a schema other thanpublic(thanks @vyruss). See Using a non-public Postgres schema (#1926). - Default project ID.
LAKEKEEPER__DEFAULT_PROJECT_IDsets the project that Lakekeeper uses when a request does not name one. Before, this was always00000000-0000-0000-0000-000000000000(thanks @bolma-lila) (#1930). - Choose the warehouse ID. When you create a warehouse, you can set its ID yourself, so tools such as Terraform know it in advance (#1983).
- Versioned audit log. Every audit record states its format version,
1.0in this release, and a published JSON Schema describes all records. New records show grant changes and retried requests that Lakekeeper answered with the result of the first request. Authorization records also contain the client's user agent. Existing parsers must be updated; see Audit log format below. See Audit log format (#1973, #2074, #1945, #1962, #1995). - User emails and role sources in the audit log. Audit records can carry the email of the users they name. This is off by default; turn it on with
LAKEKEEPER__AUDIT__TRACING__INCLUDE_USER_EMAIL=true. Roles on audit records also carry their provider and their ID there, for example the LDAP group behind a role. To leave that ID out, setLAKEKEEPER__AUDIT__TRACING__INCLUDE_ROLE_SOURCE_ID=false. See User Emails on Audit Records (#2091). - Remote signing follows the latest Iceberg REST specification. Lakekeeper supports the new way to configure remote signing that the specification now defines. Current clients keep working without changes. See Remote Signing (#1961).
- Limit for
referenced-by. Query engines can send the list of views through which they load a table, so that Lakekeeper can check view permissions. This list may contain at most 10 views by default; longer lists are rejected. Change the limit withLAKEKEEPER__REFERENCED_BY__MAX_NESTING_DEPTH. See Chain depth (#1980). - Empty namespace directories removed on ADLS and OneLake. Dropping a namespace with
purge=truealso removes its directory if it is empty (thanks @dadavidtseng). See Empty namespace directories (#1858). - Iceberg v3
unknowntype. Table and view schemas may use theunknowntype. Before, Lakekeeper rejected these schemas (#2063). - Console v0.26.0. New governance and projects pages. With OpenFGA, a Grants tab replaces the Permissions tab. Namespaces can be moved and renamed, STACKIT and Alibaba Cloud OSS warehouses can be added, new warehouses are checked before they are created, and tags and properties can be edited on the pages that show them. Fonts and the extensions of the query engine in the UI are bundled, so the console works without internet access (#1890, #1997, #2061, #2082, #2094).
Bug Fixes¶
- Remote signing and the cleanup of old metadata files check more strictly that they stay inside the table location.
- Idempotency keys. If a client reuses an
Idempotency-Keyfor a different operation, Lakekeeper rejects the request with400. Before, it answered with success without doing anything. Retries of staged table creation and recursive namespace drops now return the result of the first request. With a read replica, a quick retry could fail with409or404; Lakekeeper now checks the key on the primary database (#1959, #1981). - Cached table loads. A client that caches tables now gets a fresh copy when the warehouse settings change or when it asks for a different kind of storage access. Before, it could be told that its old copy was still valid. After the upgrade, these clients load each table in full once (#1946, #1966).
- Location checks. Lakekeeper always refuses to create a table or view inside another one's location. Before, the check could miss this with trailing slashes, backslashes or two creates at the same time. A view commit that sets the view's current location again, with a slash at the end, no longer deletes the view's files (#2005).
- Registering a table uses the access mode the client asks for (vended credentials or remote signing), returns credentials the same way as loading a table, and enforces the warehouse's allowed table format versions (#1954, #1964).
- Dropping a namespace no longer fails when a view uses it as its default namespace (#2062).
- A new namespace uses the upper and lower case of its parent. For example, creating
a.b.cunder the existinga.Bstoresa.B.c.lakekeeper migraterepairs existing namespaces, tables and views once (#1977, #2014). - Tables can no longer set
write.object-storage.pathorwrite.folder-storage.path. These old names ofwrite.data.pathmake engines write data files outside the table location. Tables that already have them can remove them (#2044). - Deleting a warehouse also deletes its storage credential from the secret store (#1985).
- S3 warehouses with
sts-enabledandaws-kms-key-arncan be created and updated when the bucket's default KMS key is a different one (thanks @teamrunninglake) (#2036). - GCS bucket names may contain underscores, and names with dots may be up to 222 characters long, as GCS allows (thanks @fallintoplace) (#2080, #2081).
- Creating a warehouse or changing its storage fails with
412 StorageProbeTimeoutwhen the storage does not answer in time, and the error names the check that did not finish. Before, the request failed with408and no details. Connections to storage now time out after 5 seconds instead of 10 (#2047). - When Lakekeeper cannot get temporary credentials from STS, the error shows what STS answered. When OneLake rejects vended SAS tokens, the error names the likely cause, usually a Fabric workspace setting (#2055, #2059).
- When the OPA bridge cannot find a warehouse's ID, for example because the warehouse did not exist yet, it looks the ID up again within 30 seconds. Before, it kept the failed result. Deploy the updated policies from
authz/opa-bridge/policiesto get this fix (thanks @sivakumar-mahalingam) (#2052). - With OpenFGA, a denied request to the permissions API returns
403 Forbiddeninstead of401 Unauthorized(#2087). - In the audit log, the authorization record for a change to permissions, roles or tags is written before the change is applied. An
allowedrecord means that the caller was allowed to try the change, not that it succeeded (thanks @AndreaBozzo) (#1924, #1934). - With
LAKEKEEPER__ENABLE_DEFAULT_PROJECT=falseand roles read from the token, requests that do not name a project no longer fail with400 MissingProjectId(#2071). - A server with every
LAKEKEEPER__TASK_*_WORKERSset to0, for example an API-only server next to headless workers, no longer stops right after it starts (#2065). - Management API filters that take several values, such as the warehouse status filter, work. Before, any request that used them failed with
400(#1942). - Listing namespaces, tables and views no longer keeps a database connection open while permissions are checked. Before, many list requests at the same time could use up the read connections and make other requests wait (#2058).
Breaking Changes¶
- Stricter OpenID settings. Lakekeeper refuses to start when an OpenID setting cannot work as written. Check for these cases before you upgrade. See Authentication (#2001).
- A scope is empty or contains a space; only one scope is allowed.
LAKEKEEPER__OPENID_SCOPEalso requiresLAKEKEEPER__OPENID_PROVIDER_URI. - A setting under
LAKEKEEPER__OPENID_PROVIDERS__<IDP_ID>__has an unknown name, for example because of a typo. - A provider checks a scope or required claims, and its
REQUIRE_CONNECTED_ON_STARTUPisfalse. - Two providers accept the same issuer, and the later one checks a scope or required claims. The main provider comes first, then the others in alphabetical order of their ID.
- Kubernetes authentication is enabled without
LAKEKEEPER__KUBERNETES_AUTHENTICATION_AUDIENCE, and a provider checks a scope or required claims.
- A scope is empty or contains a space; only one scope is allowed.
- OpenFGA: only
manage_grantscan revoke. A user withpass_grantscan still grant privileges they hold, but cannot revoke privileges (#1991). - OpenFGA: tools that query OpenFGA directly. In model v4.13, the relations
describe,select,createandmodifycontain only the grants made on that object. Privileges that come from ownership, a stronger privilege, an admin role or a grant on a parent object are in the new_effectiverelations, such asselect_effective. The Lakekeeper API is not affected (#2029). - OpenFGA: moving tables, views and generic tables. To rename a table, view or generic table into another namespace, a user needs
manage_grantsandmodifyon it, andcreateon the destination namespace. If the destination namespace uses managed access, the user also needsmanage_grantsthere. Renames within a namespace need the same privileges as before. As with grants, moving tables, views, generic tables and namespaces, and turning managed access of a namespace on or off, are not possible while acting under an assumed role (#2095). - Cancelling a soft-deletion task needs
undrop. Cancelling the soft-deletion task of a table, view or generic table restores it, so the user also needsundropon it (#2095). - Batch check:
update_storage_credentialremoved. A request toPOST /management/v1/action/batch-checkthat contains the warehouse actionupdate_storage_credentialfails with422. Checkupdate_storageinstead, which the storage-credential endpoint already required (#1936). - Code that builds on Lakekeeper's crates. Many public traits and types changed, among them
Authorizer,CatalogStore,AdmissionGateandLakekeeperStorage. Update your own implementations. Prebuilt binaries and images are not affected. - Audit log format. Existing parsers of the audit log must be updated. See Audit log format below.
Upgrade Notes¶
- Images only on Quay. Starting with this release, Lakekeeper images are no longer published to Docker Hub (
vakamo/lakekeeper). Usequay.io/lakekeeper/cataloginstead, for examplequay.io/lakekeeper/catalog:v0.14.0(#2090). - Plan downtime for the migration, and back up the database first.
lakekeeper migratemoves table and view schemas to a new storage format, which can take several minutes on large catalogs. While it runs, changes to tables and views wait, and in its last part reads wait too. Stop all 0.13 servers before you run it: their requests can make the migration fail, and it then rolls back completely. With the Helm chart, the migration job runs while the old pods still serve, so scale the deployment to zero beforehelm upgrade. After the migration, 0.13 servers refuse to start. To go back to 0.13, restore the backup (#1897). - Tables or views that would share a location stop the migration. The migration removes trailing slashes from stored table and view locations. If two entries then have the same location, for example
s3://b/tands3://b/t/,migratefails near the end withTrimWouldShareTabularLocationsand rolls back. Before you upgrade, run this query on the database. It must return no rows:SELECT warehouse_id, rtrim(fs_location, '/'), array_agg(tabular_id || ' ' || typ || ' ' || array_to_string(tabular_namespace_name, '.') || '.' || name || CASE WHEN deleted_at IS NOT NULL THEN ' (dropped)' ELSE '' END) FROM tabular GROUP BY 1, 2 HAVING count(*) > 1 AND count(DISTINCT fs_location) > 1;Both entries in a row point to the same files. On 0.13, remove one of them without deleting files:DELETE /catalog/v1/{prefix}/namespaces/{namespace}/tables/{table}?purgeRequested=false&force=true(use/views/{view}for a view). A normalDROP TABLEwould also delete the files of the other entry. If the entry is marked(dropped), first restore it withPOST /management/v1/warehouse/{warehouse_id}/deleted-tabulars/undrop(#2005). - OpenFGA model v4.13.
lakekeeper migrateinstalls model v4.13, and servers of this version always use it. IfLAKEKEEPER__OPENFGA__AUTHORIZATION_MODEL_VERSIONis set,migrateskips OpenFGA, and servers do not start until v4.13 is installed. Remove this setting before you runmigrate(#2029, #2095). - Update the OPA bridge together with Lakekeeper. Deploy the OPA bridge policies of this release together with this server. When Trino renames a table or view into another schema, the policies now also check
moveandaccept_moved_tabular, which older servers do not know (#2095). - Larger requests by default.
LAKEKEEPER__MAX_REQUEST_BODY_SIZEdefaults to 32 MiB instead of 2 MiB. Set it to2097152to keep the old limit (#1974). - Task queue renamed. The
tabular_expirationqueue is now calledsoft_deletion. RenameLAKEKEEPER__TASK_TABULAR_EXPIRATION_WORKERStoLAKEKEEPER__TASK_SOFT_DELETION_WORKERSbefore you upgrade. If the old name is set, Lakekeeper does not start and reportsduplicate field `task_soft_deletion_workers`. The task-queue config API still acceptstabular_expirationin its path (#1881). - Role members cache removed. Lakekeeper reads role members from the database. The
LAKEKEEPER__CACHE__ROLE_MEMBERS__*settings have no effect and log a warning at startup; remove them. Cache metrics withcache_type="role_members"are no longer reported (#2072). - Building from source requires Rust 1.95 or newer (#2063).
Audit log format¶
Breaking changes — an existing parser must be updated:
-
A field is absent when it has no value, and an empty list or map is written out. Nothing in an audit record is
null.A field the request did not supply is left out. A policy factor without a name, for example, has no
namekey instead of"name": null.An empty list or map is a value and is always written once an action can carry it:
before {"action_name": "commit"} after {"action_name": "commit", "updated_properties": {}, "removed_properties": [], "target_refs": [], "update_kinds": []}This applies to
properties,updated_properties,removed_properties,source,destination,target_refsandupdate_kindsin an action, and toauthorizations[].determined_byanderror.stack. A flag such asforce,purgeorrecursiveis also always written, astrueorfalse.What to do: read "none" from an empty value, not from a missing key. Keys that are genuinely optional, such as a name or an id, still need a presence check.
-
Authorization records always carry
actionsandentitiesas lists. The singularactionandentityfields are gone.before "action": {…} or "actions": [{…}, {…}] after "actions": [{…}] before .entity.namespace .action.action_name after .entities[0].namespace .actions[0].action_nameEach entry of
authorizations[]still carries a singularactionandentity.What to do: read
actionsandentitiesas lists, and iterate: a record can carry more than one. -
A
contextvalue has its own JSON type. It is no longer always a string.before "force": "true" "dry_run": "true" "writes": "2" after "force": true "dry_run": false "writes": 2- Booleans:
force,purge,recursive,dry_runandallow_partialin an action, andself_provisioningandself_readincontext. - Numbers:
writesanddeletes. - Principals:
principalsonapply_grantsandprincipalonread_subtree_grantsandrevoke_subtree_grantsare{"user": …}or{"role": …}, the formcontext.principalhas on grant records. A subtree request states withprincipal_scope(everyorone) whether it names one principal;principalis present only forone. - Strings, lists and maps keep their type.
A product that plugs into Lakekeeper may add
contextkeys that hold an object. Its schema gives the object's definition.What to do: compare a flag with
true, not with"true". Read each key's type from the schema. - Booleans:
-
The entries of
authorizations[].determined_byhave the shape the management API returns from a permission check, and there are two new kinds.before {"Policy": {"policy_id": "p-42", "effect": {"Permit": []}, "source": "cedar"}} after {"type": "policy", "policy-id": "p-42", "effect": "permit", "source": "cedar"}- The kind is in
type. Field names are kebab-case, as in the API.effectispermitorforbid. {"type": "system-authority"}: a built-in authority, not a configured policy, decided the allow. It may carrysourceandreason.{"type": "admission-gate"}: an admission gate would refuse the user. It carriesgate, andcheckwhen the gate names one.
What to do: switch on
type. The same code can parse these entries and a/checkresponse. - The kind is in
-
A request that names nothing to check records empty lists. The made-up
unknownentry is gone.A batch check with
checks: [], and a transaction commit with no table changes, recorded oneauthorizationsentry withentity_typeunknown, and for the commitaction_nameunknowntoo.before "authorizations": [{"action": {"action_name": "unknown"}, "entity": {"entity_type": "unknown"}, …}] after "actions": [], "entities": [], "authorizations": []decisionstill carries the outcome.What to do: stop matching
unknowninentity_typeandaction_name, and handle an emptyauthorizationslist. -
Every key and every value this log names itself is spelled
snake_case, andfailure_reasonis a plain string.Keys that carried a hyphen now carry an underscore:
- in an
entity:generic_table,generic_table_id,namespace_id,project_id,role_id,role_provider_id,role_source_id,server_id,table_id,table_location,tag_definition_id,task_id,user_id,view_id,warehouse_id - in an action:
allow_partial,created_before,dry_run,removed_properties,target_refs,update_kinds,updated_properties - in an authorization record's
context:invoked_by,self_provisioning,self_read - on an
authorizations[]entry:for_principal
Values:
actor_type before assumed-role after assumed_role before lakekeeper-internal after lakekeeper_internal failure_reason before {"ActionForbidden": []} after "action_forbidden"Every
failure_reasonvalue follows the same pattern:action_forbidden,resource_not_found,cannot_see_resource,internal_authorization_error,internal_catalog_error,invalid_request_data.Three sets keep the spelling of the vocabulary they come from:
entity_typeandresource_type(generic-table,tag-definition, as in the management API) andupdate_kinds(Iceberg's update names, such asadd-schema). The objects indetermined_bykeep the management API's field names,policy-idincluded.What to do: rename these keys and values in every query. A query on the old spelling matches nothing and raises no error, so check for the new names explicitly. In
jq,.failure_reason | keys[0]becomes.failure_reason. - in an
-
A role on a record carries its
provider_idandsource_idnext to its id.provider_idis always shown;LAKEKEEPER__AUDIT__TRACING__INCLUDE_ROLE_SOURCE_ID=falseleavessource_idout.before {"role": "1f7b…"} after {"role": "1f7b…", "provider_id": "corporate-ldap", "source_id": "engineering"}This applies wherever a role is named:
authorizations[].for_principal,context.principalongrant_createdandgrant_revoked,principalsonapply_grants, andprincipalonread_subtree_grantsandrevoke_subtree_grants. Both fields are absent when the role no longer exists or could not be looked up. With the setting off,source_idis absent everywhere, including onactor.assumed_role, which carries it otherwise.What to do: correlate on the role's id. Read
provider_idandsource_idas optional on a role a record names, andsource_idas optional onactor.assumed_role. Some providers let a source id be a free-form name, so it might hold personal data; turn the setting off if that matters for your log. -
Renaming a table, view or generic table into another namespace is recorded as
move, notrename, and namespaces have a new actionaccept_moved_tabular.renameTable, renameView, renameGenericTable into another namespace before rename after move destination namespace accept_moved_tabular sourceThe record of such a rename names
moveon the renamed entity, refused or allowed, on replays as well.destinationholds the path of the destination namespace; the entity's new name is not part of it, unlike for a namespacemove, wheredestinationis the full new path. A rename within one namespace is still recorded asrename. Whether the namespace changes is decided on the paths in the request, ignoring upper and lower case of ASCII letters: two paths that differ only in the case of a non-ASCII letter are recorded asmove.accept_moved_tabularis the check on the destination namespace.sourceholds the path of the namespace the entity is moved from. It appears in records of permission checks that name it, such as/management/v1/action/batch-check.The record of cancelling soft-deletion tasks is unchanged: it still names
control_tasks. Cancelling such a task now also requiresundropon its table, view or generic table, and a refusal is recorded as a deniedcontrol_tasks.What to do: if you match table, view or generic-table renames on
rename, matchmoveas well.
Additions — an existing parser keeps working:
-
Authorization records carry three new optional fields about the request:
user_agent,break_glassandidempotency_key.user_agent: the request'sUser-Agentheader, as sent and unverified.break_glass: the reason the caller stated in thex-break-glassheader, as sent and unverified.idempotency_key: the request'sIdempotency-Key.
Each is absent when the request did not send it.
What to do: nothing. Treat
user_agentandbreak_glassas claims, never as identity. -
Three more operations record the override that makes them destructive.
role delete force revokes the grants the role still holds warehouse delete force deletes a protected warehouse and everything in it generic-table drop force skips the warehouse's soft-deletion window purge deletes the data filesThey use the same keys as namespace delete, table drop and view drop. The key is present whichever way the flag went.
What to do: if you alert on destructive operations, include these three.
"force": trueis the forced form. -
Three new kinds of audit record: grant changes, admission decisions and idempotent replays.
operation: "grant_created"and"grant_revoked": one record per grant an apply actually changed, withoutcome: "success"andcontextprincipal,privilege,resource_type, andresource_idandwarehouse_idwhere they apply.operation: "admission_decided": a request an admission gate refused.outcomeisforbiddenorunavailable;contextcarriesgate,status,error_type,message,error_id, anddenied_bywhen the gate named a rule.record_type: "replay": a request answered from an idempotency record without being executed. It carries theactionsandentitiesthe request named and theidempotency_keythat matched, and nodecision.
What to do: nothing, unless you want these records. Select them by
operationor byrecord_type. -
Records can carry the email of the users they name. Off by default;
LAKEKEEPER__AUDIT__TRACING__INCLUDE_USER_EMAIL=trueturns it on.actor.email the principal's email, for principal and assumed_role actors authorizations[].for_principal.email the user's email, for a user subject actions[].principals[].email the user's email, for a user an apply_grants request names actions[].principal.email the user's email, when a subtree grant request names one user context.principal.email the recipient's email, on grant_created and grant_revokedEach is best-effort: absent when the email is not known, never
null. Roles,anonymousandlakekeeper_internalactors never carry one. An email is metadata, not identity: emails are not unique and can change.What to do: correlate on
principal,userandrole, never onemail. If you enable the setting, treat the log as holding personal data: an email stays in the log after the user is deleted. -
Audit records carry four new top-level fields:
record_type,emitters,time, andrequest_idwhen a request caused the record."record_type": "authorization", "emitters": {"lakekeeper": "1.0"}, "request_id": "019684ff-…", "time": "2026-03-14T09:26:53.589793Z"record_typenames the record's shape:authorization,replayoroperation.emittersnames every product that contributed to the record. Each key is a product name, each value theMAJOR.MINORversion of what that product contributes. A record names two products when a component plugged into Lakekeeper supplied an action name or acontextkey.request_idis the request that caused the record: the value of thex-request-idresponse header, as sent by the caller or generated by Lakekeeper. It is absent from an operation record no request caused.timeis when the event happened, in UTC, as RFC 3339 with microseconds.
audit_formatgoverns the record's overall shape. A product's value inemittersgoverns what that product contributes. The two are equal on a record only Lakekeeper produced; do not rely on that.What to do: route on
record_type. Read a product's version from its key, for example.emitters.lakekeeper. Correlate records with a request throughrequest_id, and order them bytime.
Also worth knowing — the format itself did not change:
-
Audit records are emitted on the fixed
tracingtargetlakekeeper::audit.A log filter naming
lakekeeper::service::events::backends::audit,lakekeeper::service::admissionor a prefix of either belowlakekeepermatches no audit record. The server warns at start-up when it finds such a filter.What to do: select audit records with
RUST_LOG=warn,lakekeeper::audit=info, or suppress them withRUST_LOG=info,lakekeeper::audit=warn. -
The audit log has a published JSON Schema:
audit/schema.jsonin the documentation, linked from the "Audit Log Schema" page.- Point a validator at the document to check a whole record. Its root,
AuditRecord, requiresevent_source,audit_formatandrecord_typeand selects the shape byrecord_type:AuthorizationRecord,ReplayRecordorOperationRecord. - Every field and key is a property with its type and description.
ActionRecordlists, peraction_name, the keys that action carries. - A value set the log may extend lists its values under
x-audit-values, so a validator accepts a value a later release adds. The closed setsdecision,privilege_scope,root_leveland a policy'seffectuseenum. - A product plugged into Lakekeeper publishes its own schema for what it contributes.
emitterson a record says which schemas apply.
What to do: nothing. Use the schema to validate records or to generate a parser.
- Point a validator at the document to check a whole record. Its root,
Records from v0.14.0 carry audit_format 1.0 (the first version).
v0.13.8 (2026-10-25)¶
Bug Fixes¶
- GCS: vended credentials report their real expiry. When Google STS returns no expiry, a vended token now expires with the token it was made from. Before, Lakekeeper assumed one hour, so clients could get tokens that had already expired (thanks @rohankmr414) (#2160).
- Soft-deleted tables and views expire in inactive warehouses. Before, their expiry was skipped, and they and their files stayed forever (#2151).
- Object paths with
@. Locations such ass3://bucket/foo@barare no longer read as containing a user name (thanks @sivakumar-mahalingam) (#2141). - Security updates of dependencies. Updated the webpki crates,
ctutilsandtokio, and removedcrypto-bigint, to fix security advisories RUSTSEC-2026-0381 to -0383 and RUSTSEC-2026-0385 to -0387 (#2166).
v0.13.7 (2026-10-16)¶
Bug Fixes¶
- Security: S3 remote signing and metadata cleanup stay inside the table. The S3 signer signs only requests for objects inside the table's location, and only with query parameters and headers it knows. A copy is signed only when its source is inside the table, too. Unknown parameters and headers are refused, and so are writes that use an encryption key other than the warehouse's. When a commit expires old metadata files, Lakekeeper deletes only files inside the table location (#2102).
- Remote signing with Iceberg 1.12 or later. Clients no longer get
403 SignatureDoesNotMatcherrors when they delete more than 250 files at once, or delete files several times in a row, for example inexpire_snapshotsorremove_orphan_files. The signer now tells clients in theCache-ControlHTTP header that a signature for a write must not be reused (#2124). - Ranged reads with remote signing. Clients that reuse signatures, such as Iceberg's
S3FileIO, no longer get403 SignatureDoesNotMatcherrors on the second ranged read of a file. The signer now leaves theRangeheader out of the signature in upper and lower case (thanks @Mohil-Ahuja) (#2104). - Removing the last branch or tag. A commit that removes the last branch or tag of a table, such as
main, is now kept. Before, the reference came back when the table was loaded again (#2117). - Concurrent commits to one table. When commits to the same table block each other in the database, the client gets a
409error, which it retries. Before, it got a503error (#2117). - Dropping a namespace no longer fails when a view uses it as its default namespace (#2062).
- Listing namespaces, tables and views no longer keeps a database connection open while permissions are checked. Before, many list requests at the same time could use up the read connections and make other requests wait (#2058).
- Tables can no longer set
write.object-storage.pathorwrite.folder-storage.path. These old names ofwrite.data.pathmake engines write data files outside the table location. Tables that already have them can remove them (#2044). - S3 warehouses with
sts-enabledandaws-kms-key-arncan be created and updated when the bucket's default KMS key is a different one (thanks @teamrunninglake) (#2036). - GCS bucket names may contain underscores, and names with dots may be up to 222 characters long, as GCS allows (thanks @fallintoplace) (#2080, #2081).
- When the OPA bridge cannot find a warehouse's ID, for example because the warehouse did not exist yet, it looks the ID up again within 30 seconds. Before, it kept the failed result. Deploy the updated policies from
authz/opa-bridge/policiesto get this fix (thanks @sivakumar-mahalingam) (#2052). - With OpenFGA, a denied request to the permissions API returns
403 Forbiddeninstead of401 Unauthorized(#2087). - The UI no longer serves a file for a path that contains
ui/a second time. Such a path now returns404(thanks @sivakumar-mahalingam) (#2119). - Updated
rustls,quinn-protoandxxhash-rustto fix security advisories RUSTSEC-2026-0338 to -0342, RUSTSEC-2026-0346 to -0349 and GHSA-6g2r-675j-hx59 (#2125, #2112, #2083).
v0.13.6 (2026-09-22)¶
Bug Fixes¶
- Clients that ask for vended credentials, such as DuckDB, no longer get
403errors on S3 warehouses that do not vend credentials. Lakekeeper sent astorage-credentialsentry without credentials, and these clients then sent unsigned requests (#1923). - The S3 signer now signs
ListObjectsV2requests for a prefix inside a table location. This makes Spark'sremove_orphan_fileswithprefix_listing => truework with remote signing (#1925). - Catalog and management responses are now marked
Cache-Control: privateand vary on the caller's identity. A shared HTTP cache can no longer give one user's response, which may contain vended credentials, to another user (#1947). - The Iceberg Java client now detects that Lakekeeper supports idempotency keys, because
/configreturnsidempotency-key-lifetimeas a top-level field, as the spec requires (#1952). - The
X-Iceberg-Access-Delegationheader accepts a comma-separated list such asvended-credentials,remote-signing. A value with non-ASCII characters no longer causes a500error (#1948). - ADLS now reuses connections, and connecting to ADLS, S3 or GCS times out after 10 seconds. Before, ADLS ignored its connection pool and connect timeout, GCS had no connect timeout, and S3 had none with access keys or anonymous access. Deleting a large table on GCS no longer keeps memory for every file at the same time, which was about 2 GB for a million files (#2022).
v0.13.5 (2026-09-15)¶
Features¶
- S3 remote signing for generic tables. Clients can now read and write generic table data through Lakekeeper's S3 signer. This makes generic tables usable on S3-compatible storage without STS (thanks @N-Clerkx) (#1910).
Bug Fixes¶
- Security:
rustlsis updated to 0.23.45 for RUSTSEC-2026-0285. A TLS peer could send handshake messages without encryption that should have been encrypted. The handshake itself stayed protected (f5b8993).
v0.13.4 (2026-09-10)¶
Features¶
- GovCloud, China and ISO region support. Vended-credential policies now carry the correct ARN partition (
aws-us-gov,aws-cn,aws-iso*,aws-eusc), derived automatically from the region, endpoint or role ARN, soAssumeRolesucceeds for buckets outside the commercial partition — nothing to configure (thanks @123digits) (#1928).
Bug Fixes¶
- Fixed a permissions leak: a table, view or generic table renamed into another namespace kept inheriting grants from the namespace it left (#2013).
- Renaming a table onto a name that is already taken now returns
409 Conflictinstead of404 Not Found, matching the Iceberg REST spec, and renaming onto a soft-deleted name succeeds — as create already did (#1955). - Fixed four independent causes of resident memory growing until restart: jemalloc now compiles with
thp:never, the route table is no longer rebuilt per accepted connection, connections get TCP keepalive and a header-read timeout so vanished peers are dropped, and unmatched request paths no longer become permanent Prometheus series. Settled RSS fell 68% in testing (#1990).
Upgrade Notes¶
- Deployments that renamed a table, view or generic table across namespaces should run
lakekeeper openfga reconcile --mode add-and-delete-driftonce after upgrading. The defaultadd-missingmode cannot repair it — the stale permission edge is a surplus tuple, not a missing one.
v0.13.3 (2026-08-16)¶
Features¶
- Stable Kubernetes service-account identities.
LAKEKEEPER__KUBERNETES_AUTHENTICATION_SUBJECT_SOURCE=usernamederives a service account's user ID fromsystem:serviceaccount:<namespace>:<name>instead of the token UID, so roles and instance admins can be pre-provisioned (e.g. via the Terraform provider) and survive a cluster rebuild. The defaultuidis unchanged (#1899). - Provider-managed roles are protected from drift. Roles owned by a configured role provider (LDAP, Entra, Okta, token) can no longer be created, updated, deleted, rebound or have their members changed through the management API, since the next provider sync would silently clobber those edits. Nothing changes without a role provider configured (#1891).
Bug Fixes¶
- Fixed two denial-of-service advisories (RUSTSEC-2026-0194 and RUSTSEC-2026-0195) reachable through the S3 remote signer, which parses a client-supplied XML body;
quick-xmlis bumped to 0.41 (#1885). - Loading a table with
referenced-byno longer runs a discarded authorization check on the target view, removing a wasted authorizer evaluation per request and a misleadingSelectViewentry from audit logs (#1886).
v0.13.1 (2026-06-30)¶
Bug Fixes¶
- Console updated to v0.19.0; the default OAuth
client_idno longer carries a leading slash (ac1094a).
v0.13.0 (2026-06-30)¶
Highlights¶
- Generic Table API. Register non-Iceberg tables (e.g. Lance, Delta) as first-class generic tables and get credential vending, list/load/rename/drop, soft-delete/undrop, protection, and full authorization — without faking Iceberg metadata (#1673, #1813).
- Operator-owned warehouses. A
managed_bymarker lets a control plane (operator/IaC) own a warehouse, locking spec mutations (delete, rename, (de)activate, storage profile, protection, format-version policy) to instance admins even when authorization would otherwise allow them (#1828). - Cache hardening for large fleets. Hot read-through caches now coalesce concurrent identical misses (single-flight) and jitter their TTLs, cutting thundering-herd load on the database and on rate-limited cloud STS/SAS endpoints (#1833, #1837).
Features¶
- Per-warehouse table format-version policy. Set the allowed Iceberg table format versions and an optional default per warehouse, enforced on create/commit/upgrade (#1786).
- Customer-managed KMS encryption. Warehouses with
aws-kms-key-arnnow advertises3.sse.type=kmsto clients, so writes via vended credentials are encrypted with your KMS key (#1847). - Schedule a task directly.
POST .../task-queue/{queue_name}/scheduleschedules a task for a single table without waiting for a commit hook (#1783). - Task failures are debuggable. Failed tasks now surface the root-cause failure reason in task-detail responses, no server-log access required (#1873).
- Pluggable admission gates. A new post-authentication seam lets an external service reject already-authenticated principals that have no entitlement on this instance and contribute their resolved roles (#1865, #1866, #1869).
- Authorizer-independent role-membership API. A single management surface lists, adds, and removes a role's members (users or roles) and shows what a role or user belongs to — the same API regardless of the configured authorizer. The membership edges keep a single source of truth: the authorizer's own store when it manages assignments (e.g. OpenFGA), otherwise the catalog's Postgres tables (#1829).
- Iceberg 1.11 remote signing. Emits the new
signer.uri/signer.endpointproperties alongside the legacys3.signer.*keys, so clients ≥1.11 stop logging deprecation warnings while older clients keep working (#1820). - Per-decision authorization audit. Authorization audit events now record the contributing policies behind each allow/deny outcome (#1844).
- More observability. New client-side Postgres connection-pool metrics and event-listener dispatch timing metrics (#1838, #1863).
- Console v0.15.1 — generic-tables (Lance/Delta) tab, Iceberg format-version policy editor, OneLake/Fabric backend, and per-queue maintenance summaries (#1855).
Bug Fixes¶
GET /confignow authorizes only the requested warehouse (fixing timeouts on large projects) and masks hidden/unknown warehouses identically so existence and UUIDs can't leak (#1788).- Conditional
loadTableno longer returns304once the cached response's vended credentials have expired (#1862). - S3: keep the STS vended-credential policy within the packed-size limit and emit a reliable table resource ARN for paths with special characters (#1857).
- Azure (ADLS): add a connect timeout and a larger retry budget so transient connect failures are retried; retry storage OAuth token acquisition for ADLS and GCS (#1815, #1827).
- Postgres: migration locks are now transaction-scoped, so a failed migration no longer leaks an advisory lock that could permanently block future migrations (#1790).
Breaking Changes¶
- Default storage layout is now flat — new namespaces use
<base>/<tabular-uuid>instead of nesting tabulars under the parent-namespace UUID. Only namespaces created on/after 0.13 are affected; existing namespaces and tabulars keep their persisted paths (not retroactive, no migration), so a warehouse spanning the upgrade can hold a mix. To keep the previous behavior for new namespaces, explicitly configure the full-hierarchy layout (#1853). - Event backends are separate crates. The NATS and Kafka backends moved out of the core
lakekeepercrate intolakekeeper-events-natsandlakekeeper-events-kafka. Prebuilt binaries and images are unaffected (same env vars); building from source must depend on the new crates — thenats,kafka, andvendored-protocfeatures are removed fromlakekeeper(#1814). - Postgres backend is a separate crate. Catalog Postgres logic moved into
lakekeeper-storage-postgres, makinglakekeeperbackend-agnostic. Source consumers oflakekeeper::implementations::*must now depend on the new crate; prebuilt binary/image users are unaffected (#1812). - Role source-system rebind is now permission-gated.
PUT /role/{id}/source-systemrequires membership-control permission (OpenFGA model v4.6→v4.7), closing a privilege-escalation gap (#1848). - Custom authorizers.
are_allowed_*_actions_implnow returnVec<AuthorizationDecision>instead ofVec<bool>, andRoleActiongained a non-CopyUpdateSourceSystemvariant; out-of-tree authorizer implementations must adapt. Built-in OpenFGA users are unaffected (#1844, #1848). - Regenerate action client SDKs.
GET …/actionsnow advertises action kinds (Lakekeeper*ActionKind); the wire JSON is unchanged, but generated clients should be regenerated (#1860).
Upgrade Notes¶
- Downgrade protection.
servenow refuses to start against a database already migrated by a newer binary and does not retry. After a rollback, start the older binary withserve --force-start, accepting the schema-incompatibility risk (#1861). - Docker base images moved from Debian 12 (bookworm) to Debian 13 (trixie) (#1794).
- The docker-compose examples now use SeaweedFS instead of MinIO for object storage (#1811).
v0.12.4 (2026-06-17)¶
Features¶
- Multiple OIDC providers. Authenticate tokens from several identity providers at once (e.g. Okta for users + a cloud OIDC issuer for service accounts) via
LAKEKEEPER__OPENID_PROVIDERS; fully backwards-compatible with the existing single-provider config (#1760). - Microsoft OneLake / Fabric storage + private endpoints. New OneLake (ADLS Gen2) storage profile with configurable endpoint modes including workspace private link (#1852); Console updated to v0.14.3 for OneLake support.
Bug Fixes¶
/healthnow returns HTTP503(not200) when aggregate health is unhealthy or unknown, so Kubernetes HTTP probes correctly detect an unhealthy server; the JSON body is unchanged (#1802).
v0.12.3 (2026-05-26)¶
Features¶
- Read-only maintenance mode. Set
LAKEKEEPER__MAINTENANCE_MODE=read-onlyto reject mutating requests with503+Retry-Afterduring planned maintenance (#1765). - Atomic core + extension migrations. Schema migrations now apply in a single transaction, so an interrupted upgrade can't leave a half-migrated database (aa734bf).
- Users may share an email address. The unique-email constraint was dropped, so multiple users can have the same email (#1755).
- Survey opt-out. New
LAKEKEEPER__UI__ENABLE_SURVEYSflag disables in-console surveys and their third-party requests (719150b). - Reserved
systemrole provider for catalog-managed roles (#1776). - Console. New "Export for GitHub" support bundle (server info + UI config, no tokens), a Feedback button, role-provider IDs in the overview, and a two-column Server Settings layout (719150b).
Bug Fixes¶
- Fixed table property removal being lost when no properties remained (#1767).
- Views now preserve protection (
protected=true) across commits — previously lost on update (thanks @fallintoplace) (#1770). force=trueis now respected when dropping soft-deletion warehouses that contain views (#1779).- Fixed a memory leak from stale Vault (KV2) health status (thanks @fallintoplace) (#1773).
- Postgres: rewrote the namespace trigger so
pg_restorecan replay it (#1781).
Upgrade Notes¶
- Minimum supported Rust version (MSRV) raised to 1.94 — affects building from source.
v0.12.2 (2026-05-10)¶
Features¶
- Storage locations are canonicalised at parse time to avoid path aliases (#1743).
- Object size is now exposed on
FileInfo(#1741).
Bug Fixes¶
- Security: hardened S3 STS/CEL credential-vending policies against path injection (#1740).
- Azure (ADLS): pre-encode
%in blob names so the SDK no longer collapses distinct paths onto the same alias (#1746). - Postgres: apply
pg_acquire_timeoutto all connection-pool initialisations (#1744).
v0.12.1 (2026-05-04)¶
Highlights¶
- Instance Admins. Designate break-glass principals that bypass control-plane authorization for management actions via
LAKEKEEPER__INSTANCE_ADMINS(a list of<idp_id>~<subject>IDs). The bypass excludes data-plane operations and role-assumed requests (#1716). - Safe switch to OpenFGA. Existing deployments can adopt or rebuild OpenFGA:
openfga reconcilerebuilds hierarchy tuples from the catalog (with dry-run and drift-deletion), andreopen-bootstrapre-enables bootstrap for recovery (#1731, #1733). - OpenDAL dropped. Storage I/O now goes exclusively through the hyperscaler-native backends wrapped by
lakekeeper-io, including vended-credential validation (#1737). - Security. Upgraded
rustls-webpkito 0.103.12 for RUSTSEC-2026-0098 (#1713).
Features¶
- Trusted engines for views (
referenced-by). Validates thereferenced-byparameter and resolves view-on-view chains for batch authorization, enabling secure DEFINER-style execution for trusted query engines — configured underLAKEKEEPER__TRUSTED_ENGINES__<NAME>(#1647). - Protected security-relevant properties. Only a matched trusted engine may set or remove view owner / run-as properties (case variants rejected), and commits can no longer overwrite immutable table properties such as
encryption.key-id(#1700, #1724). - Richer audit.
introspect_permissionevents now include the inner check tuples and their decisions, and events record whether access was granted internally, via an instance admin, or by the authorizer (#1697). - Data-plane
Selectaction for views, plus a publicresolve_principalAPI for downstream API-to-authzUserOrRoleconversion (#1721, #1703). - OPA bridge. View-on-view queries via
CreateViewWithSelectFromColumns, the TrinoADD_FILESoperation, and a warehouse/namespace broad-access fast path for batch authorization (#1712, #1727). - Extended Server Info with console information and commit SHAs (#1725).
Bug Fixes¶
- Added
webpki_root_certs/ UBI native certs to the S3 client to fix TLS trust issues (#1720). - Namespace/table case handling: allow renaming a table to a different case of its own name; lookups return the caller's case, ID lookups the canonical case (7c26309).
- Pinned
gcloud-storage/gcloud-authto~1.2to avoid areqwest-middlewareconflict (#1701). - ADLS: remove the actual matched SAS token key rather than its prefix (76a091b).
- Console: fixed base-URL trailing slash, Vite 8 authentication breakage, and a stale warehouse name after rename (#1729, #1723).
Upgrade Notes¶
- Switching to OpenFGA on an existing instance is now safe: run
openfga reconcile(dry-run first; drift-deletion mode to also remove stale tuples), andreopen-bootstrapto re-enter bootstrap if needed. The minimum required OpenFGA version was raised. - OpenDAL removed — storage now relies solely on the native S3/GCS/ADLS backends in
lakekeeper-io; re-verify storage and vended-credential config after upgrade. - Deploying into a custom Postgres schema is now supported and documented (#1714).
v0.12.0 (2026-04-01)¶
Highlights¶
- Audit Event System. Authorization decisions and catalog operations emit dedicated audit events with exactly-once-per-call delivery, giving a reliable trail of who did what (b77c687).
- Idempotency keys for safe retries. Send an
Idempotency-Keyheader on mutating requests and Lakekeeper replays the original response instead of applying the change twice — on by default, scoped per warehouse, with a 30-minute key lifetime (#1671). - Customizable storage layouts. Choose how namespace/table paths are templated on S3/GCS/ADLS using
{uuid}/{name}placeholders (#1615, #1628). - Structured JSON logs. Log output is now structured JSON with objects as field values, ready for log-pipeline ingestion (b77c687).
Features¶
- Configurable STS endpoint. Set a separate
sts-endpointon an S3 storage profile when your S3-compatible storage exposes STS on a different host (#1653). - Fallback subject claims. OpenID subject-claim config accepts a comma-separated list; the first matching claim in the token wins, easing varied-IdP integration (#1646).
- Request size/time limits. New
LAKEKEEPER__MAX_REQUEST_BODY_SIZE(default 2 MB) andLAKEKEEPER__MAX_REQUEST_TIME(default30s) guard against oversized and slow requests (#1583). - Trusted engines configuration. Declare trusted engines (e.g. Trino) with a selectable Invoker/Definer security model; the engine is auto-detected from token audience and recorded in request metadata (#1629).
- Role assignment store and cache with provider-scoped role identifiers, plus a roles cache for faster authorization (#1638, #1623).
- Iceberg V3 Variant datatype support, validated against Spark 4 integration tests (daa7947).
- Tokio runtime metrics exported for runtime observability (#1664).
- Reduced memory footprint by switching the allocator to jemalloc (0eaeedc).
- OPA bridge improvements: batch-authorization optimization, configurable admin users, system-schema handling, and request-context forwarding (#1674, #1662).
- Faster listing of namespaces, tables, and views (#1618).
- Console. New Home dashboard with usage statistics and API-call charts; branch operations (create, rename, delete, rollback, fast-forward); a Properties dialog for tables/views/namespaces; storage-layout configuration; and a local query engine with memory management (#1621, #1634).
Bug Fixes¶
- Fixed duplicate results when paginating
list_tabulars(#1682, #1684). - Fixed a memory leak in the S3 identity cache (0eaeedc).
- Allowed updating the storage-profile region when an S3 endpoint is set (#1678).
- Patched security advisories in crypto dependencies (
aws-lc-sys/rustls-webpki) (#1672) and anlz4_flexmemory-leak advisory (#1665).
Breaking Changes¶
- Cache metrics unified. Per-cache metric names are replaced by shared names distinguished by a
cache_typelabel (#1641). - Structured log format. Logs now emit structured JSON with objects as field values instead of flat text (b77c687).
Upgrade Notes¶
- Cache metrics: migrate dashboards/alerts from the old per-cache metric names to the unified
lakekeeper_cache_hits_total/lakekeeper_cache_misses_total/lakekeeper_cache_size, filtering by thecache_typelabel (role,warehouse,namespace,secrets,stc). - Structured logs: log consumers must parse JSON rather than plain text. Set
LAKEKEEPER__DEBUG__EXTENDED_LOGS=trueto includefilename/line_numberfields. - S3 credential fields dropped their
aws_prefix; the old names remain accepted as aliases, but update to the new names (#1685).