v0.26.0-beta
Breaking changes
Check the sections that apply to your setup before updating.
OIDC: change the callback URL
In your identity provider, replace the registered redirect URI:
https://recipes.example.com/api/auth/oauth2/callback/oidc
https://recipes.example.com/api/auth/callback/oidc
Use your own public address, including any base path. GitHub and Google callback URLs are unchanged. Your OIDC provider must sign ID tokens with an RS or ES key; HS256 is not supported. See Authentication.
Accounts: connect additional sign-in methods from settings
Signing in with another provider no longer links accounts just because the emails match. Sign in to your existing account, then use Settings => User => Sign-in methods => Connect to add OIDC, Google or GitHub. The emails can differ. You must keep at least one sign-in method.
Site tokens: use full domains
Re-add tokens saved under bare names in Settings => User. For example,
replace instagram with instagram.com in Domain. Use a domain, without
https:// or a path. It also covers subdomains such as www.instagram.com.
Bare-name tokens are no longer sent. See Site authentication.
Page renderers: move configuration to admin settings
Existing OBSCURA_ENDPOINT and OBSCURA_CDP_TOKEN values are copied into
Settings => Admin => Page renderers on the first start after upgrading.
After that, edit renderer settings there; changing their environment variables
has no effect.
If you set a custom Obscura timeout, replace the old variable before the first start so it seeds the new setting:
OBSCURA_NAVIGATION_TIMEOUT_MS=60000
PAGE_RENDERER_TIMEOUT_MS=60000
The default is still 45000 ms. After the first start, change the navigation timeout in admin settings instead.
Imports and shop pages now block private-network addresses by default. If you import from a recipe site on your own network, enable Allow private addresses in the same panel. Obscura still refuses private addresses; direct fetching, Chromium or the solver must handle those pages. See Page renderers.
Summary
Your Library, recipes, cookbooks and groceries load up to 14 times faster on a slow phone, and the Docker image is about 30% smaller. Administrators can stop background jobs and use AI to clean up ingredients or fill missing nutrition, conversions and icons. Imports gain browser fallbacks, and accounts can have multiple sign-in methods.
The runtime moves to Node.js 26, the Compose examples use Valkey, and this release fixes the security issues below. The README and landing page have also been redesigned.
Security
Update as soon as you can. This release fixes several security issues:
- Video links: a recipe's video link could pass command-line options to yt-dlp, which let a signed-in user run code on the server (GHSA-732x-jh4x-wg8j). Only http(s) links reach yt-dlp now.
- Recipe pictures and videos: any signed-in user could add or delete pictures and videos on any recipe, and an upload could write files outside the uploads folder.
- Private pictures: any signed-in user could open another household's recipe, step and cookbook pictures by their address, and a shared cache in front of the app could serve them to anyone.
- Recipe imports: an import link could name a local file or a server other than a website (
file:,gopher:), and a link written with a backslash could pick up one site's sign-in tokens and send them to another. - Site authentication: a header token was sent to every other site a page import loaded scripts from, and never with the page itself.
- Site authentication domains: a token saved for a bare word like
instagramwas sent to every site with that word in its address, likeinstagram.evil.com. A token takes a full domain now, likeinstagram.com, and a bare-word token is no longer sent. - Export: a server admin whose role was taken away could still export every recipe on the instance until they signed out.
- Sign-in rate limits: behind more than one reverse proxy, everyone shared one sign-in rate limit. List your proxies in
TRUSTED_PROXIESto give each visitor their own (see upgrade notes). - Sign-in tokens: the tokens Google, GitHub or your OIDC provider hand out at sign-in were kept in the database in plain text, the OIDC one with the user's email and name in it. They were never used, so they are no longer stored.
What's new
Ingredient catalogue
Find the new options under Settings => Admin => AI & processing => Ingredient catalogue.
- Automatic AI for new ingredients: AI can merge duplicates, clean names and delete non-food entries as they are added. It can also estimate missing nutrition and conversions, and draw missing icons.
- Clean up catalogue runs AI over the ingredients you already have: only the ones nobody reviewed yet, or the whole catalogue.
- Fill gaps fills missing nutrition, conversions and icons for every ingredient missing any of these.
See the ingredient catalogue documentation.
Recipes and groceries
- Import a recipe from a PDF.
- Filter recipes by the ingredients you already have in your Pantry.
- Print a recipe or save it as a PDF, with your chosen servings and measurements.
- Scale a recipe from 0.5x to 4x, or enter a custom serving count or multiplier. The panel opens by clicking on the amount.
- Recipe imports preserve ingredient sections and handle more ingredient amounts, units and source formats.
See the recipe importing documentation and recipe page documentation.
Page renderers and fallbacks
- Configure page renderers under Settings => Admin => Page renderers: Obscura (endpoint, token and timeout), Chromium browser (CDP) and FlareSolverr or Byparr.
- When Obscura cannot read a page or hits a bot challenge, Chromium is tried next, then the solver. This applies to URL imports, grocery prices and Instagram captions. Unconfigured renderers are skipped.
- Site authentication tokens are sent to the Chromium browser like they are to Obscura, but never to the solver.
- Any Chromium with a CDP endpoint works. The example Compose file includes a commented-out
norishapp/chromiumservice.
See the recipe parser documentation.
Accounts and sign-in
- Link more than one sign-in method to your account under Settings => User => Sign-in methods: your server's OIDC provider, Google or GitHub. The provider's email doesn't have to match your account's, and you can disconnect a method as long as one remains.
- Change password now signs you out on your other devices.
Thanks to @QuirkyTurtle94 (#668). See the authentication documentation.
Administration and integrations
- Administrators can cancel running background jobs and retry them if needed.
- The import API provides a status URL to follow an import through to completion.
- Serve the app under a path such as
https://example.com/norish. Build the image with the path and setAUTH_URLto the same address. See Server & runtime. Thanks to @x84a01 (#651).
Languages added
- Swedish
- Arabic
- Brazilian Portuguese
See the localization documentation.
Fixes and improvements
- More reliable ingredient matching, naming, grocery entry and recipe editing.
- Better recipe parsing, archives, serving counts and measurement handling.
- Fixes for calendar syncing, sign-in and offline cookbook access.
- Improved keyboard and screen-reader support, API rate limits and background-job recovery.
- Updated dependencies.
Upgrade notes
Valkey in Compose examples
Valkey replaces Redis in the example stack. Redis is still supported, and
REDIS_URL remains a fallback when VALKEY_URL is unset. You do not have to
replace a working Redis server.
To adopt the new variable while keeping Redis:
REDIS_URL=redis://redis:6379
VALKEY_URL=redis://redis:6379
To switch to Valkey, update the service, its volume and the app's depends_on
entry in Compose. The relevant parts of the new configuration are:
services:
norish:
environment:
VALKEY_URL: valkey://valkey:6379
depends_on:
- db
- valkey
valkey:
image: valkey/valkey:9
restart: unless-stopped
volumes:
- valkey_data:/data
volumes:
valkey_data:
Keep your existing database and uploads volumes, and your MASTER_KEY. Moving
to a fresh Valkey volume drops pending background jobs and requires users to
sign in again. Let imports and other jobs finish before switching. See the
full Compose example.
Health probes
Use /api/v1/health/live for restart probes and /api/v1/health/ready for
readiness probes. For example:
curl --fail http://localhost:3000/api/v1/health/live
curl --fail http://localhost:3000/api/v1/health/ready
The old /api/v1/health still works as a readiness check, but is deprecated.
See Health checks.
Multiple reverse proxies
If traffic passes through more than one proxy, set TRUSTED_PROXIES to their
IPs or CIDR ranges. For example, for two proxies on your own network:
TRUSTED_PROXIES=10.0.0.2,10.0.0.3
Use your proxies' actual addresses. Without this, visitors share one sign-in rate limit. Keep the app's port reachable only through your proxy. See Networking & origins.
Custom settings
Review edited ingredient words, units, recurrence words, content indicators and AI prompts under Settings => Admin. Custom overrides are preserved and may not receive new defaults. For example, a custom units list may need newly added unit spellings copied into it. Save your customizations before restoring any defaults.
If you run from source, use Node.js 26. The published Docker image includes it.
Contributors
Thanks to everyone who contributed to this release: