Skip to content

feat(web): Migrate from next-auth to better-auth - #3140

Merged
MohamedBassem merged 1 commit into
karakeep-app:mainfrom
MohamedBassem:codex/migrate-to-better-auth
Oct 4, 2026
Merged

MohamedBassem merged 1 commit into
karakeep-app:mainfrom
MohamedBassem:codex/migrate-to-better-auth

Conversation

@MohamedBassem

Copy link
Copy Markdown
Member

Fixes #1716

@greptile-apps

greptile-apps Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Critical risk] Replaces the entire authentication system.

Resolve existing email collisions before merging so affected accounts retain password access.

Findings

  1. P1 Existing accounts lose password access ▶
  2. P2 Failed logout returns to bookmarks ▶
Fix with agent prompt
### Issue 1
packages/db/drizzle/0094_migrate_to_better_auth.sql:114-121
If an existing database contains both `Foo@example.com` and `foo@example.com`, this migration leaves the mixed-case email unchanged. The old signup code allowed both, but `validatePassword` now always lowercases the supplied email. The owner of `Foo@example.com` can no longer exchange their password for an API key, even with the original email and correct password.

Resolve these collisions before switching all lookups to lowercase, rather than silently leaving an unreachable account.

### Issue 2
apps/web/app/logout/page.tsx:17-20
`signOut` now returns an `AuthResult`, but this page ignores `ok` and always navigates home. If logout is rejected, such as when `DEGRADED_MODE` blocks `/sign-out`, the session remains active. The home page then sends the user straight back to their bookmarks without explaining why logout failed.

Check the result and show an error instead of treating a failed request as a completed logout.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Migrates web authentication from NextAuth to Better Auth, moves passwords into credential accounts, and updates account forms, admin actions, tests, and seed helpers.

  • The migration leaves some existing case-colliding accounts unreachable through password-based API key exchange.
  • The logout page ignores failed logout results.

Reviews (1) · Last reviewed commit: "feat(web): Migrate from next-auth to bet..."

Comment on lines +114 to +121
UPDATE `user`
SET `email` = lower(`email`)
WHERE `email` != lower(`email`)
AND NOT EXISTS (
SELECT 1 FROM `user` AS `other`
WHERE lower(`other`.`email`) = lower(`user`.`email`)
AND `other`.`id` != `user`.`id`
);--> statement-breakpoint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Existing accounts lose password access

If an existing database contains both Foo@example.com and foo@example.com, this migration leaves the mixed-case email unchanged. The old signup code allowed both, but validatePassword now always lowercases the supplied email. The owner of Foo@example.com can no longer exchange their password for an API key, even with the original email and correct password.

Resolve these collisions before switching all lookups to lowercase, rather than silently leaving an unreachable account.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/db/drizzle/0094_migrate_to_better_auth.sql
Line: 114-121

Comment:
**Existing accounts lose password access**

If an existing database contains both `Foo@example.com` and `foo@example.com`, this migration leaves the mixed-case email unchanged. The old signup code allowed both, but `validatePassword` now always lowercases the supplied email. The owner of `Foo@example.com` can no longer exchange their password for an API key, even with the original email and correct password.

Resolve these collisions before switching all lookups to lowercase, rather than silently leaving an unreachable account.

**Knowledge Base Used:**
- [Data model and persistence](https://app.greptile.com/karakeep/-/custom-context/knowledge-base/karakeep-app/karakeep/-/docs/data-model-and-persistence.md)
- [Client integrations](https://app.greptile.com/karakeep/-/custom-context/knowledge-base/karakeep-app/karakeep/-/docs/client-integrations.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +17 to 20
signOut().then(() => {
clearHistory();
router.push("/");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Failed logout returns to bookmarks

signOut now returns an AuthResult, but this page ignores ok and always navigates home. If logout is rejected, such as when DEGRADED_MODE blocks /sign-out, the session remains active. The home page then sends the user straight back to their bookmarks without explaining why logout failed.

Check the result and show an error instead of treating a failed request as a completed logout.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/logout/page.tsx
Line: 17-20

Comment:
**Failed logout returns to bookmarks**

`signOut` now returns an `AuthResult`, but this page ignores `ok` and always navigates home. If logout is rejected, such as when `DEGRADED_MODE` blocks `/sign-out`, the session remains active. The home page then sends the user straight back to their bookmarks without explaining why logout failed.

Check the result and show an error instead of treating a failed request as a completed logout.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@MohamedBassem
MohamedBassem force-pushed the codex/migrate-to-better-auth branch from f6a6e14 to 159b379 Compare October 4, 2026 17:20

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — minor suggestions inline.

Reviewed changes

  • Auth backend swapped to better-auth — apps/web/server/auth.ts builds a betterAuth() instance with email/password plus the custom genericOAuth provider, enforces a path allowlist + Karakeep rate limiting in a before hook, and reimplements read-only/demo/degraded, signup/turnstile, role/quota, and logging behavior.
  • Sessions and password storage reworked — getServerAuthSession wraps better-auth getSession; passwords move from user.password/salt into account rows (providerId='credential'), with hashPassword/verifyPasswordHash/hasPassword/setUserPassword in packages/trpc/auth.ts.
  • DB schema + 0094 migration — emailVerified becomes boolean, account/session/verificationToken are rebuilt to better-auth's shape, legacy hashes are re-encoded, emails are lowercased, and old sessions/tokens are dropped.
  • Routers, UI and tooling rewired — the old users.create/changePassword/verify/reset procedures are removed in favor of better-auth endpoints; admin create/reset, invites, sign-in/signup/verify pages, OAuth provider list, config/docs, and the benchmark/e2e/seed-snapshot signup helpers are all updated.

ℹ️ OAuth ID tokens are no longer signature-verified

next-auth verified OIDC ID tokens against the provider's JWKS and enforced the OAUTH_ID_TOKEN_SIGNED_RESPONSE_ALG allowlist. better-auth's genericOAuth getUserInfo only decodeJwts the ID token — it does not check the signature — so OAuth identity and the email_verified flag used during account linking now rest entirely on the token arriving unmodified over the server-to-server token-exchange TLS channel. That is a defense-in-depth reduction rather than an end-user-exploitable bug (the token is fetched directly from the configured provider), but operators who relied on strict ID-token validation should be aware of it.

Technical details
# genericOAuth decodes ID tokens without verifying signatures

## Affected sites
- `apps/web/server/auth.ts` — `createOAuthConfig` drops `idTokenSignedResponseAlg` and delegates to `genericOAuth`, whose `getUserInfo` (`better-auth/dist/plugins/generic-oauth/routes.mjs:406-417`) returns `decodeJwt(tokens.idToken)` payload fields with no JWKS verification.
- `packages/shared/config.ts` — `OAUTH_ID_TOKEN_SIGNED_RESPONSE_ALG` and `OAUTH_TIMEOUT` are removed; existing deployments that set them will have them silently ignored.

## Required outcome
- Make the change in verification strength explicit for operators (release note / upgrade docs), and confirm no supported provider needs signed-ID-token enforcement.

## Open questions for the human
- Does any target provider require ID-token signature verification? If so, is there a better-auth hook (e.g. a custom `getUserInfo` that verifies against the discovery JWKS) worth configuring?

ℹ️ Nitpicks

  • The admin user list (apps/web/components/admin/UserList.tsx) and CLI no longer show/return localUser, so there is no longer any UI signal distinguishing password accounts from OAuth-only ones. whoami still exposes it, so this looks intentional — ignore if the removal was deliberate.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment on lines +112 to +121
-- better-auth looks users up by lowercased email. Emails that would collide
-- with another user once lowercased are left untouched.
UPDATE `user`
SET `email` = lower(`email`)
WHERE `email` != lower(`email`)
AND NOT EXISTS (
SELECT 1 FROM `user` AS `other`
WHERE lower(`other`.`email`) = lower(`user`.`email`)
AND `other`.`id` != `user`.`id`
);--> statement-breakpoint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard deliberately leaves a mixed-case row untouched when lowercasing it would collide with another account, but better-auth resolves accounts by the lowercased email. Such a user can then no longer sign in or request a password reset, and the skip is silent. Consider logging the affected ids/emails so operators can repair them (admin reset password or email change).

Technical details
# Migration silently locks out case-colliding accounts

## Affected sites
- `packages/db/drizzle/0094_migrate_to_better_auth.sql:112-121` — rows excluded by the `NOT EXISTS` collision guard keep their mixed-case email.
- `packages/trpc/auth.ts:264` and better-auth's lookups use `email.toLowerCase()`, so a mixed-case row becomes unreachable by sign-in and password reset.

## Required outcome
- Affected accounts must be discoverable by an operator instead of silently skipped.

## Suggested approach
- Before the `UPDATE`, run a `SELECT` that lists the ids/emails that will be skipped (the migration runner surfaces query output/logs), or document the manual recovery path. A true rebuild-based lowercasing is not possible without resolving the unique conflict.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta — prior migration note remains open.

Reviewed changes since the prior review — this push is a rebase/re-push; no production code changed between f6a6e14 and 159b379. The only delta is in packages/trpc/routers/apiKeys.test.ts:

  • Added an API-key authorization test — an API key cannot revoke other keys asserts apiKeys.revoke from an API-key caller rejects with API keys can only revoke themselves and leaves the target key intact.
  • Test import/formatting cleanup — the legacy-salted-password test now loads the User model locally where it's used.

The new test pins real behavior with an exact error-message assertion and a DB-state check, so it can fail if the authorization guard regresses.

The migration case-collision inline comment from the prior review is still outstanding and was not touched by this push; leaving this review non-approving until it is resolved or waived.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@MohamedBassem
MohamedBassem force-pushed the codex/migrate-to-better-auth branch from 159b379 to 1569f01 Compare October 4, 2026 18:43

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one docs nitpick below.

Reviewed changes (since the prior pullfrog review at 159b3792):

  • Made trusted origins configurable — added AUTH_TRUSTED_ORIGINS (default *) and replaced the fixed trustedOrigins: ["karakeep://"] with a getTrustedOrigins function. Explicit entries are trusted for both the Origin check and callbackURL redirects; * expands to the request's own Host/X-Forwarded-Host, restoring next-auth's "sign in from any address" behavior. Verified against better-auth 1.6.25: * deliberately does not widen callbackURL validation, so it cannot introduce an open redirect.
  • Made OAuth token-endpoint auth configurable — added OAUTH_TOKEN_ENDPOINT_AUTH_METHOD (client_secret_basic/client_secret_post) and encodeClientSecret, which form-urlencodes the client secret for the Basic header (better-auth interpolates it raw). client_secret_post sends the raw secret, which better-auth form-encodes via URLSearchParams.
  • Added coverage — new apps/web/server/auth.test.ts cases for LAN-IP and X-Forwarded-Host sign-in, cross-origin + callbackURL rejection, AUTH_TRUSTED_ORIGINS restricting/listing, and Basic vs client_secret_post token requests (exact base64 credentials and form bodies).

Commit 1569f019 (base main 292c13a5).

ℹ️ Nitpicks

  • The NEXTAUTH_URL row in docs/docs/03-configuration/01-environment-variables.md still reads "Sign-in and other authentication requests coming from any other address are rejected, so it must match the URL you open Karakeep with." That is no longer true now that AUTH_TRUSTED_ORIGINS defaults to *; the row directly below explains the new behavior, so the two rows now contradict each other.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

This PR migrates the backend from next-auth to better-auth. As part of
the migration, all web users will get signed-out due to the migration
from JWT to sessions.
@MohamedBassem
MohamedBassem force-pushed the codex/migrate-to-better-auth branch from 1569f01 to 98439ca Compare October 4, 2026 19:03

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes (since the prior pullfrog review at 1569f019):

  • Corrected the NEXTAUTH_URL docs — the row no longer claims that sign-ins from other addresses are rejected; it now describes the address's real roles and points to AUTH_TRUSTED_ORIGINS, resolving the contradiction flagged in the last review. The NEXTAUTH_SECRET wording is also updated in both the env-vars table and the dev setup page.
  • Taught the seed helpers to pass better-auth's origin check — the signUpUser HTTP helper in packages/benchmarks/src/trpc.ts, packages/e2e_tests/utils/api.ts, and tools/seed-snapshot/src/index.ts now sends an Origin header matching the target base URL, which better-auth's origin/CSRF protection requires for server-side callers.

No production code changed in this push. The one remaining open item is the migration case-collision note on packages/db/drizzle/0094_migrate_to_better_auth.sql:112-121 from the initial review; it is untouched by these commits, so this review stays non-approving.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@MohamedBassem
MohamedBassem merged commit d3e0b78 into karakeep-app:main Oct 4, 2026
6 checks passed
GiorgioAresu pushed a commit to GiorgioAresu/home-ops that referenced this pull request Oct 23, 2026
…➔ 0.34.1) (#2417)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/karakeep-app/karakeep](https://github.com/karakeep-app/karakeep) | minor | `0.33.2` → `0.34.1` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/6) for more information.

---

### Release Notes

<details>
<summary>karakeep-app/karakeep (ghcr.io/karakeep-app/karakeep)</summary>

### [`v0.34.1`](https://github.com/karakeep-app/karakeep/releases/tag/v0.34.1): 0.34.1

[Compare Source](karakeep-app/karakeep@v0.34.0...v0.34.1)

This is a security release on top of 0.34.0. The docker images move from the pinned `node:24.18.1-slim` back to `node:24-slim` (currently Node 24.22.1) to pick up the [October 2026 Node.js security releases](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases). Among other fixes, these patch a Brotli decoder out-of-bounds write (CVE-2026-111497) and several TLS certificate hostname validation bypasses (CVE-2026-105483, CVE-2026-106069, CVE-2026-106072). Thanks [@&#8203;eriktews](https://github.com/eriktews) for the fix ([#&#8203;3217](karakeep-app/karakeep#3217))!

The node version was pinned in 0.33.2 because of the better-sqlite3 crash loop ([#&#8203;2989](karakeep-app/karakeep#2989)). The upstream fix for that shipped in Node 24.22, so the pin is no longer needed.

### [`v0.34.0`](https://github.com/karakeep-app/karakeep/releases/tag/v0.34.0): 0.34.0

[Compare Source](karakeep-app/karakeep@v0.33.2...v0.34.0)

##### 0.34.0

Welcome to the `0.34.0` release of Karakeep (and sorry for the long wait)! The mobile app gets most of the attention this time: **Automatic offline downloads** for recent bookmarks and particular lists, **tablet layouts**, tag management, public lists, collaborators and attachments, all of which used to need the web app. The crawler got a big anti-bot overhaul: it now drives real **Google Chrome through patchright**, so far fewer sites will hand you a "Just a moment..." page instead of the article. There are also a lot of memory and performance improvements, along with tons of bug fixes.

Thanks to everyone who contributed to this release: [@&#8203;justadityaraj](https://github.com/justadityaraj), [@&#8203;InventorB](https://github.com/InventorB), [@&#8203;ryanchou1994](https://github.com/ryanchou1994), [@&#8203;NoiceHax](https://github.com/NoiceHax), [@&#8203;0-0mfuji](https://github.com/0-0mfuji), [@&#8203;eriktews](https://github.com/eriktews), [@&#8203;qwist1233-cpu](https://github.com/qwist1233-cpu), [@&#8203;lightsabit](https://github.com/lightsabit), [@&#8203;BrownieCoder](https://github.com/BrownieCoder), [@&#8203;itz-puneet](https://github.com/itz-puneet), [@&#8203;jreiml](https://github.com/jreiml), [@&#8203;deepcfr](https://github.com/deepcfr), [@&#8203;mislav](https://github.com/mislav), [@&#8203;pradeepb28](https://github.com/pradeepb28), [@&#8203;Kyzcreig](https://github.com/Kyzcreig), [@&#8203;gowinder](https://github.com/gowinder), [@&#8203;samuel-sarmah](https://github.com/samuel-sarmah), and everyone who triaged bugs or shared feedback.

*Note: the changes mentioned here for the extension and mobile releases will need to go through the different store (apple, google, mozilla) reviews. So it might take a couple of days until all of them are released.*

> If you enjoy using Karakeep, consider supporting the project [here ☕️](https://buymeacoffee.com/mbassem) or via GitHub [here](https://github.com/sponsors/MohamedBassem). Also share Karakeep's managed offering (☁️ [link](https://cloud.karakeep.app)) with your friends who're not into selfhosting.

<a href="https://www.buymeacoffee.com/mbassem" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="auto" height="50" ></a>

##### New Features 🚀

- **Karakeep's auth moved from next-auth to better-auth**:
  - This is an infrastructure change, but it's a big one. Karakeep moved its auth library to better-auth (away from next-auth). This will unlock a lot of new features in the future (e.g. 2fac, oauth login in the mobile app, hosted MCP auth, etc).
  - This migration is going to happen automatically after the upgrade BUT there are some caveats, please read the "Upgrading" section.
- **Automatic Offline Downloads**: 0.33 let you save single bookmarks for offline reading on the mobile app. In this release, you can let karakeep automatically download recent bookmarks for you while the app is open, and also mark specific lists to be always fully available offline.
- **Mobile app catching up with the web**:
  - Tablet layout showing multiple columns on wide screens.
  - Tag management: rename and delete tags from the app ([`fc401d0`](karakeep-app/karakeep@fc401d02)).
  - Manage public lists and list collaborators ([`292c13a`](karakeep-app/karakeep@292c13a5)).
  - View and download bookmark attachments ([`27774f9`](karakeep-app/karakeep@27774f97)).
  - A new usage statistics screen ([`6563ee1`](karakeep-app/karakeep@6563ee14)).
  - Pick a parent list, choose a list icon, and create lists straight from the "Manage Lists" page ([`7ca5ee2`](karakeep-app/karakeep@7ca5ee23), [`428d1f2`](karakeep-app/karakeep@428d1f22), [`f977712`](karakeep-app/karakeep@f9777128)).
- **A crawler that gets blocked less**:
  - The crawler now drives the browser with [patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright), a Playwright fork that doesn't leak the automation signals that bot-protection scripts look for ([`af8b0eb`](karakeep-app/karakeep@af8b0eb0)).
  - The `karakeep-chrome` image now ships Google Chrome stable instead of Chrome Headless Shell, as protected sites block the headless shell far more often ([`ea57af1`](karakeep-app/karakeep@ea57af18)). This comes at the cost of the chrome image getting bigger unfortunatly.
  - The user agent now comes from the actual browser instead of a hardcoded Chrome 124 one ([`254174a`](karakeep-app/karakeep@254174ac)).
  - Cloudflare's "Just a moment..." and DataDome's device check clear on their own after a few seconds. The crawler now waits for them (up to `CRAWLER_CHALLENGE_WAIT_SEC`, default 15s) instead of saving the challenge page ([`1800346`](karakeep-app/karakeep@18003460)).
  - New `CRAWLER_BROWSER_TIMEZONE` to make the browser's timezone match your proxy's location, as a UTC browser behind a non-UTC proxy is a strong bot signal ([`ba8c188`](karakeep-app/karakeep@ba8c1885)).
- **On-demand video preservation** ([#&#8203;3183](karakeep-app/karakeep#3183)): A new "Preserve Video" bookmark action downloads a single bookmark's video with yt-dlp. You can now keep automatic video downloads off and still preserve the ones you care about.
- **Broken links management revamp** ([#&#8203;3105](karakeep-app/karakeep#3105)): The broken links settings page now groups issues by category and supports search, sorting, retrying and bulk deletion.
- **Search**: New `has:notes` and `has:highlights` qualifiers by [@&#8203;BrownieCoder](https://github.com/BrownieCoder) ([#&#8203;3053](karakeep-app/karakeep#3053)).
- **Others**:
  - New `CRAWLER_FULL_PAGE_ARCHIVE_MAX_SIZE_MB` to discard full-page archives above a certain size (media-heavy pages can produce 1GB+ archives that never render) by [@&#8203;NoiceHax](https://github.com/NoiceHax) ([#&#8203;3054](karakeep-app/karakeep#3054)).
  - Pages uploaded as precrawled archives (e.g. SingleFile uploads, and client-side crawling) now get a screenshot too ([#&#8203;3161](karakeep-app/karakeep#3161)).
  - Banner images are now resized and converted to WebP to save storage ([#&#8203;3165](karakeep-app/karakeep#3165)).
  - The default tagging model is now `gpt-6-luna` which costs half as much as `gpt-5.6-luna` used to cost (thanks OpenAI?).

##### UX Improvements ✨

- A visual refresh of various web app components. More modern looking dialogs, buttons, dropdown menus, tooltips, etc.
- Added a "System" theme option that follows your OS by [@&#8203;deepcfr](https://github.com/deepcfr) ([#&#8203;3159](karakeep-app/karakeep#3159)).
- The bookmark preview page now has the full bookmark options menu in its action bar ([`24fb655`](karakeep-app/karakeep@24fb655c)).
- The "delete unused tags" button is now a bit more grown up ([`acbac6a`](karakeep-app/karakeep@acbac6ad)).
- Home/End now move the cursor in the search bar by [@&#8203;eriktews](https://github.com/eriktews) ([#&#8203;3110](karakeep-app/karakeep#3110)).
- The browser's theme color now follows dark mode by [@&#8203;itz-puneet](https://github.com/itz-puneet) ([#&#8203;3051](karakeep-app/karakeep#3051)).
- Mobile:
  - Lists are sorted by name, like on the web, by [@&#8203;justadityaraj](https://github.com/justadityaraj) ([#&#8203;3040](karakeep-app/karakeep#3040)).
  - Bigger toolbar tap targets by [@&#8203;jreiml](https://github.com/jreiml) ([#&#8203;2813](karakeep-app/karakeep#2813)).
  - Better looking bookmark cards when there's no metadata ([`2b2771a`](karakeep-app/karakeep@2b2771a6)).

##### Fixes 🐛

- **Performance and memory**:
  - Fix a server memory leak caused by the query cache ([#&#8203;3138](karakeep-app/karakeep#3138)).
  - Workers and web only load the dependencies of features that are enabled cutting memory usage for most setups. You should expect a 40MB drop in RSS with this release.
  - Prometheus default metrics are no longer collected when Prometheus isn't configured ([#&#8203;2397](karakeep-app/karakeep#2397)). This fixes a regerssion introduced in 0.29 and should result into a lower baseline CPU usage.
  - Faster tag searches by [@&#8203;0-0mfuji](https://github.com/0-0mfuji) ([#&#8203;3089](karakeep-app/karakeep#3089)).
  - Search indexing now defaults to 50 workers (instead of 1) for better batching. Should result in faster indexing for large imports.
  - SQLite now runs `PRAGMA optimize` on open and daily ([`355a0b0`](karakeep-app/karakeep@355a0b09)).
  - Faster lists page and list stats queries and a smaller main web bundle ([`2d7cc4b`](karakeep-app/karakeep@2d7cc4bd), [`44ea028`](karakeep-app/karakeep@44ea0284), [`b640f15`](karakeep-app/karakeep@b640f15b)).
- **Hardening**:
  - Upload body size is now enforced before parsing the multipart body ([`3e22b65`](karakeep-app/karakeep@3e22b659)).
  - Asset downloads now respect client backpressure instead of buffering in memory ([`b5cab5b`](karakeep-app/karakeep@b5cab5be)).
  - Pagination limits are now validated on all tRPC endpoints ([`94e1ea2`](karakeep-app/karakeep@94e1ea21)).
  - Content length is capped before tokenizing AI prompts ([`ef07e91`](karakeep-app/karakeep@ef07e913)).
  - Upgraded Next.js to 16.4.1 for its October security release ([#&#8203;3196](karakeep-app/karakeep#3196)).
- Collaborators' bookmarks now show up in public lists by [@&#8203;NoiceHax](https://github.com/NoiceHax) ([#&#8203;3019](karakeep-app/karakeep#3019)).
- Moving a list under one of its own sublists is now rejected, as the resulting cycle could send list deletion into an infinite loop. Reported by [@&#8203;Vertextheonesolution](https://github.com/Vertextheonesolution) (GHSA-892w-x93w-2fqp, [#&#8203;3199](karakeep-app/karakeep#3199)).
- Fix bookmark pagination tie breakers that could skip or duplicate bookmarks ([`135f116`](karakeep-app/karakeep@135f1163)).
- Fix the headers timeout for AI inference jobs with slow providers by [@&#8203;eriktews](https://github.com/eriktews) ([#&#8203;2888](karakeep-app/karakeep#2888)).
- Fix embedding generation with providers that don't default to float embeddings ([#&#8203;3009](karakeep-app/karakeep#3009)).
- Bookmarks deleted while being indexed no longer dangle in meilisearch ([`bca289d`](karakeep-app/karakeep@bca289d2)).
- The RSS fetcher now identifies itself with a karakeep specific user agent ([`adf399b`](karakeep-app/karakeep@adf399be)).
- If one of the workers fails, the whole worker process now crashes instead of silently running without it, so your container's restart policy can kick in ([`ae9e23b`](karakeep-app/karakeep@ae9e23b3)).
- Fix `.jpg` uploads being rejected in the attachment box ([`2434e65`](karakeep-app/karakeep@2434e657)).
- Fix the header shrinking in Safari ([`3c44114`](karakeep-app/karakeep@3c441143)).
- Fix duplicate "sort by" in the all tags page ([`f5e56b4`](karakeep-app/karakeep@f5e56b42)).
- Fix the sidebar's "+" (new list) button messing up the bookmark preview modal when clicked while a bookmark is open by [@&#8203;samuel-sarmah](https://github.com/samuel-sarmah) ([#&#8203;3192](karakeep-app/karakeep#3192)).
- Make `/run` owned by `node` in the docker image so non-root setups work by [@&#8203;lightsabit](https://github.com/lightsabit) ([#&#8203;3114](karakeep-app/karakeep#3114)).
- Mobile:
  - Fix lost and duplicated Android shares after the app gets killed ([`e7fbaab`](karakeep-app/karakeep@e7fbaab7)). This has been a long standing problem, and should signficantly improve share reliability for android.
  - Ignore Android's unreliable internet reachability checks that marked the app offline ([#&#8203;3127](karakeep-app/karakeep#3127)).
  - Fix the server save button getting hidden by the keyboard by [@&#8203;InventorB](https://github.com/InventorB) ([#&#8203;3010](karakeep-app/karakeep#3010)).
  - Markdown list bullets and numbers now use the foreground color by [@&#8203;ryanchou1994](https://github.com/ryanchou1994) ([#&#8203;3046](karakeep-app/karakeep#3046)).
  - Fix lists not getting auto refreshed when a bookmark is added to it ([`f4efff9`](karakeep-app/karakeep@f4efff99)) and vertical images in bookmark info ([`482369b`](karakeep-app/karakeep@482369b7)).
  - Fix a bug that was causing mobile API keys to not be revoked on mobile app logout ([`1820693`](karakeep-app/karakeep@1820693e)).
- Added missing Turkish translations by [@&#8203;qwist1233-cpu](https://github.com/qwist1233-cpu) ([#&#8203;3111](karakeep-app/karakeep#3111)).

##### For Developers 🛠️

- The MCP server has been upgraded to the MCP v2 SDK ([`39df6c6`](karakeep-app/karakeep@39df6c6e)).
- The CLI now bundles the official Karakeep skill ([`baf0b1b`](karakeep-app/karakeep@baf0b1bb)).
- Karakeep now serves `/.well-known/api-catalog`, `/.well-known/change-password` and `/.well-known/security.txt` ([`2620d9e`](karakeep-app/karakeep@2620d9e6)).
- The docs now have an `llms.txt` for agents ([`a8450b3`](karakeep-app/karakeep@a8450b3c)).
- Translations have moved away from Weblate and to an LLM driven workflow.
- Clarified `LOG_LEVEL` options in the docs by [@&#8203;mislav](https://github.com/mislav) ([#&#8203;3149](karakeep-app/karakeep#3149)).

##### Community Projects 💡

*Checkout our community projects page [here](https://docs.karakeep.app/community-projects).*

- [Alfred Karakeep](https://github.com/Kyzcreig/alfred-karakeep): An Alfred workflow for inline bookmark search using Karakeep's query language by [@&#8203;Kyzcreig](https://github.com/Kyzcreig) ([#&#8203;2974](karakeep-app/karakeep#2974)).
- [Supasend](https://supasend.app/#karakeep): A quick capture iOS app for sending photos and links to Karakeep by [@&#8203;pradeepb28](https://github.com/pradeepb28) ([#&#8203;3077](karakeep-app/karakeep#3077)).
- [KaraClone](https://github.com/gowinder/karaclone): A reimplementation of the Karakeep server API that runs on the Cloudflare Workers free tier by [@&#8203;gowinder](https://github.com/gowinder) ([#&#8203;3124](karakeep-app/karakeep#3124)).

##### Screenshots 📸

##### Automatic Offline Download

<img width="603" height="1311" alt="Screenshot 2026-10-17 at 10 27 34 PM" src="https://github.com/user-attachments/assets/02f2344e-27ee-43d7-b8dd-d74fba936280" />

##### Broken Links Page

<img width="2402" height="1250" alt="Screenshot 2026-10-17 at 22 27 11@&#8203;2x" src="https://github.com/user-attachments/assets/0df7d9c9-63b5-4739-8e55-6135e95a14f1" />

##### Upgrading 📦

**Things to know before upgrading:**

- **Everyone gets signed out once.** The move to better-auth ([#&#8203;3140](karakeep-app/karakeep#3140)) switches web sessions from JWTs to database sessions, so all web users will need to sign in again. Mobile apps and API keys are not affected.
- **Auth env changes**:
  - This release will automatically lowercase all email stored in the database (in prep for making email case insensitive), except accounts that will cause a conflict (two accounts that differ only in email case). You *Must* fix those conflicts before upgrading (by deleting the unused one). You'll get locked out if you have two conflicting accounts (neither of which are lowercase). If you get locked out, change the email address in the sqlite database to be lowercase to gain access back.
  - New `AUTH_TRUSTED_ORIGINS` (default `*`) to restrict which origins can sign in and be redirected to on top of `NEXTAUTH_URL`.
  - `NEXTAUTH_URL` and `NEXTAUTH_SECRET` keep their names and are still required.
- **Migrate to the `karakeep-chrome` image if you haven't already.** Check the migration guide [here](https://docs.karakeep.app/administration/chrome-image-migration). Browserless (mostly unraid users) will continue to work but will miss out on the stealth improvements. It's advised to migrate to karakeep-chrome's unraid template maintained by [@&#8203;Collectathon](https://github.com/Collectathon).

To upgrade:

- If you're using KARAKEEP_VERSION=release, run `docker compose pull && docker compose up -d`.
- If you're pinning it to a specific version, upgrade the version and then run `docker compose pull && docker compose up -d`.

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/Rome)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNTEuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjE2Ny41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://git.aresu.eu/GiorgioAresu/home-ops/pulls/2417
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: Migrate away of next-auth

1 participant