Back to blog
2026-11-13 Thijs Creemers

Release: v1.2.0, tenants kept apart and a lighter database layer


1.2.0 is mostly a security release. If you run Wagoe with more than one tenant (:wagoe/tenant), upgrade. If you don’t, upgrade anyway: new apps now have CSRF protection and rate limiting switched on, and the CSP no longer allows inline scripts. That last one can break a page of yours, so read the upgrade notes at the bottom.

There is also one new library: Wagoe’s database layer on its own, without a web server attached.

Security

Multi-tenancy was the weakest part of Wagoe, and the homepage already said so. For this release I went through every path where a tenant could reach another tenant’s data. There were more than I’d like. Every 1.x release before 1.2.0 is affected if you use :wagoe/tenant.

What was wrong, in short:

  • Any signed-in user could run requests in another tenant by naming it in a header or the host (BOU-982).

  • A tenant’s admin could manage other tenants, make themselves admin of another one, and see or edit other tenants' users, roles and groups (BOU-983, BOU-953, BOU-984, BOU-954).

  • Background work ignored the tenant. A job enqueued in a tenant ran on the public schema, events and push messages reached every tenant, a realtime topic like order:123 reached every tenant’s subscribers, and a webhook subscription without a tenant received everyone’s events (BOU-985 to BOU-990, BOU-946, BOU-947).

  • User listings and search could show other tenants' users, and a suspended tenant kept serving for up to an hour from cache (BOU-948, BOU-992).

  • With :expose-http? true, storage was too trusting: download, delete and signed URLs acted on any key a signed-in user named, an upload to a named path could overwrite someone else’s file, and a signed local URL kept working after its file was gone (BOU-945, BOU-950, BOU-991).

All of these are closed. Roles and groups don’t carry a tenant yet, so for now only a platform admin can edit them. These are the gaps I know of; keep testing isolation in your own app, and tell me when you find one I missed.

Defaults that were too loose:

  • CSRF and rate limiting were off in apps made by wagoe new and in profiles written by bb setup (BOU-940, BOU-951). They’re on for new apps now, and the boot warns while CSRF is off. An existing app keeps its config, so turn it on yourself (see below).

  • The CSP allowed inline script and eval (BOU-898). script-src is 'self' now.

  • A plaintext secret in config.edn passed bb doctor (BOU-942). It fails now, outside the test profile.

And one about trust: you could not check the installer before running it (BOU-938). Each release now has install.sh and its SHA-256 attached, and the installer pins and checks the Clojure, Babashka and bbin installers it runs.

Wagoe’s database layer without the HTTP stack

com.wagoe/wagoe-platform-sql is new. It holds platform’s database, migration, lease and transit namespaces, under the same names. wagoe-platform depends on it, so nothing changes for a full Wagoe app.

It matters if you have an app already and want Wagoe’s jobs or search in it. wagoe-jobs and wagoe-search now come with next.jdbc, HoneySQL, HikariCP and Migratus, and no Jetty, Reitit or Muuntaja. The homepage used to claim more than that was true; I said zero lock-in. Then I ran clojure -Stree tells that story, and what is still not true.

To get there, a few libraries lost dependencies they never needed. wagoe-search no longer brings wagoe-admin or wagoe-shared-ui, and wagoe-jobs no longer brings wagoe-tenant. If you got one of those through search or jobs, declare it yourself.

Stricter architecture checks

bb check:fcis keeps core/ pure, and it got stricter:

  • honey.sql in core/ now fails. Build queries in the shell, or list the file under :allow-sql in .wagoe/check-fcis.edn.

  • Requiring a shell namespace that happens to be called …shell.core, .schema or .ports from core now fails too (BOU-964).

  • println, tap> and friends, and reads of the clock or a random number in core/, now warn. They will fail from 2.0. Read the value in the shell and pass it in. If a namespace really needs it, mark it ^:wagoe/allow-print or ^:wagoe/allow-clock with a reason.

Smaller things

  • Push sends were retried after every success and so delivered twice (BOU-987). Fixed.

  • A missing or short JWT_SECRET now says WGE-103, and bb guide error WGE-103 explains it.

  • Scaffolded repositories return the schema’s types instead of the driver’s (BOU-877). Copy →entity from a fresh bb scaffold into older modules.

  • The admin sidebar is wider and you can drag it. Sign-in, registration and MFA pages now look like the admin.

Upgrade

curl -fsSL https://get.wagoe.org | bash

If you’d rather check the installer first, download it from the release and compare the checksum:

curl -fsSLO https://github.com/wagoebv/wagoe/releases/download/1.2.0/install.sh
curl -fsSLO https://github.com/wagoebv/wagoe/releases/download/1.2.0/install.sh.sha256
shasum -a 256 -c install.sh.sha256

Then pin 1.2.0 for every com.wagoe/* dependency. All 34 artifacts are on 1.2.0. Run wagoe doctor afterwards; it names anything that moved.

What you may need to change in an existing app:

  • Inline scripts. An inline <script>, onclick or hx-on stops running under the new CSP. Move it to a .js file, or set your own CSP.

  • CSRF. Set :security {:csrf {:enabled? true}} under :wagoe/http.

  • Admin users. Add :scope {:tenant-id :tenant-id} to your admin/users.edn.

  • Search. Mark an index that holds no tenant’s data :global? true, or a tenant’s caller gets a 403.

  • Secrets. Move plaintext secrets in config.edn to #secret or #env.

  • Push. Devices registered before 1.2.0 have no tenant. Re-register them.

  • Tenant schemas. sync-tenant-schemas! now runs migrations-tenant/. If your tenant tables came from its old fixed list, move their DDL there.

The full list is in the CHANGELOG, and the migration notes are in UPGRADING.md.