Skip to main content

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:

Before
https://recipes.example.com/api/auth/oauth2/callback/oidc
After
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:

Before
OBSCURA_NAVIGATION_TIMEOUT_MS=60000
After
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 instagram was sent to every site with that word in its address, like instagram.evil.com. A token takes a full domain now, like instagram.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_PROXIES to 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/chromium service.

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 set AUTH_URL to 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:

Before
REDIS_URL=redis://redis:6379
After, keeping Redis
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:

docker-compose.yml (excerpt)
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: