Wheels 4.1.2: deploy that builds and pushes, honest CLI exit codes, and four security fixes
Wheels 4.1.2 is out. It’s a patch release on the 4.1.x line: CLI commands that report failure through their exit code, a wheels deploy that actually builds and pushes the image it deploys, migrator and model fixes, tighter gates on the development tools, and four security fixes. It’s available on Homebrew, Scoop, apt and yum/dnf now, and we recommend upgrading.
Full notes: GitHub Release v4.1.2 · CHANGELOG
Security fixes
4.1.2 includes four security fixes, each with its own advisory:
- CLI name and value validation (GHSA-cghp-86c6-6fjc). The Wheels CLI now validates the names and values it turns into paths and commands:
wheels deployversions and destinations, deploy config images and hosts,wheels packagesnames, and every namewheels generateandwheels destroywrite.wheels generaterefuses to write any file outside the project, including through a symlink. Values from.kamal/secretsare redacted from dry-run and printed deploy commands, andwheels deploy initcreates.kamal/secretsowner-only (0600). Scaffolded and admin views now HTML-encode record data. - Error email recipient (GHSA-8gcv-3v9h-7gj2). Error emails no longer go to an address derived from the request.
errorEmailAddressused to default towebmaster@plus the last two labels of the Host header; it now defaults to"", and with no recipient configured no error email is sent. Wheels writes a one-time warning towheels.loginstead. - Finder values bound as parameters (GHSA-96rm-h22v-925x). Values passed to dynamic finders, the query builder’s
where(),findByKey()/updateByKey()/deleteByKey()on string and UUID keys, andvalidatesUniquenessOf()are now bound as query parameters. Before, a value containing a quote could change the shape of the WHERE clause. A value with an apostrophe (O'Brien) still matches as before, and numeric columns were not affected. - IP-based debug access scoped to the request (GHSA-8r22-vwcc-v55m). IP-based debug access now grants the debug GUI and error details to the allowed client’s own request only, instead of switching them on in the shared
application.wheelsscope. Apps with the setting off (the default) are unaffected.
Three of these come with app edits; see “If you’re upgrading” below.
Deploy builds and pushes
wheels deploy and wheels deploy setup now build and push the image before deploying it, as the guides describe (#3874). They log in to the registry on your machine and on every host (the password travels only on stdin), run docker buildx build --push, then pull on the hosts. Pass --skip-push when that version’s image is already pushed.
The same PR passes the host and ssl settings to kamal-proxy (TLS and host-based routing were silently dropped before), fixes wheels deploy rollback on worker hosts, and makes --destination=<name> fail when config/deploy.<name>.yml doesn’t exist.
A CLI you can script against
- Failures exit non-zero (#3863).
wheels generaterefusals, missing arguments,wheels doctorat CRITICAL and more no longer report success, including through the MCP tools. Scripts that relied on exit 0 after printing usage need the arguments they were missing. wheels generate app --dry-runwrites nothing (#3858). It used to print “Dry run — nothing will be written” and then create the whole app.wheels upgradeno longer silently downgrades the framework whenvendor/wheels/is newer than the CLI’s copy; pass--allow-downgradeif you mean it (#3831).upgrade checkreaches GitHub again (#3854), exits non-zero when the lookup fails (#3827), and accepts--offline(#3871).wheels testruns a single spec file by dotted path or bare name (#3797).- The apt/dnf launcher ignores a
JAVA_HOMEolder than Java 21 and uses the Java 21 the package installed (#3766). wheels docs fetchverifies the bundle’s SHA-512 checksum before unpacking (#3823), andwheels coverageno longer reports 0% on a warm dev server (#3859).
Model and migrator
- Migrations can opt out of the per-step transaction with
this.useTransaction = false, so a migration that touches a second datasource runs on Adobe ColdFusion (#3792). reload()no longer truncates decimals (149.25became149, and the nextsave()wrote it back) (#3818).- An invalid
transactionmode no longer leaves the connection marked as in a transaction (#3817). - The per-request query cache keys on the datasource a query actually runs against, after tenant resolution (#3846).
- On SQLite and H2, new boolean columns from migrations are real
BOOLEANcolumns (#3870). - On Oracle,
whereBetween()on a date column no longer fails (#3877).
Controllers, URLs and status codes
- A new
baseUrlsetting gives absolute URLs a canonical host, so emailed links and links built in background jobs no longer depend on how the request arrived (#3838). - A request for a format the action doesn’t provide answers 406 instead of an empty 200 (#3866).
- Error pages return status codes monitoring can trust: missing table, datasource or column is 500, a bad CSRF token is 403 (#3862), and 404 is an allow-list of client-triggerable not-found types (#3872).
dateField()emits a bareyyyy-mm-ddvalue, sodatetimeproperties no longer show a blank date control (#3856).
If you’re upgrading
These items ask for an edit in your app:
-
Scaffolded controllers (#3868). In
requireRecord(), replace theThrow(call with$throwErrorOrShow404Page((same arguments), and wrap thefindByKey()lookup soWheels.InvalidValueis treated as a missing record. Make the same change inapp/snippets/CRUDContent.txtif you keep it. -
Scaffolded and admin views (GHSA-cghp-86c6-6fjc). Wrap the
<h1>value inapp/views/<plural>/show.cfmand the field values inindex.cfminencodeForHTML(). Regenerate admin views withwheels generate admin <Model> --force. -
Session cookie (#3830). Apps created with 4.0.6-4.1.1 should update
this.sessionCookie.secureinpublic/Application.cfc:secure: (structKeyExists(variables, "currentEnv") && currentEnv == "production") || (IsBoolean(cgi.server_port_secure) && cgi.server_port_secure) || cgi.https == "on" || cgi.http_x_forwarded_proto == "https" -
generate authlogout (#3819). Addif (!isPost() && !isDelete()) { redirectTo(route="login"); return; }at the top ofdelete()inapp/controllers/Sessions.cfc. -
IP-based debug access (GHSA-8r22-vwcc-v55m). If you use it, replace the IP-based access block in
onRequestStart()withapplication.wo.$applyIPDebugAccess();, and inonRequestEnd()replaceapplication.wheels.showDebugInformation &&withapplication.wo.$get("showDebugInformation") &&. -
Error emails (GHSA-8gcv-3v9h-7gj2). Apps that relied on the default must set
errorEmailAddress(orerrorEmailToAddress) explicitly. -
Startup error page (#3795). Copy the updated
$renderMinimalError()and related helpers from the currentwheels newtemplate intopublic/Application.cfc. -
Custom local hostnames (#3850). If you reach the dev tools as something like
myapp.test, allow it withset(devToolsAllowedHosts="myapp.test"). -
Behind a TLS proxy (#3835). Wheels 4.2 will require
trustProxyHeaders=trueto honourX-Forwarded-Proto; set it now if you’re behind a trusted reverse proxy.
Install / upgrade
brew upgrade wheels # Homebrew
scoop update wheels # Scoop
sudo apt update && sudo apt install --only-upgrade wheels # apt
sudo dnf upgrade wheels # dnf
Then confirm:
wheels --version # 4.1.2
In each app, run wheels upgrade check, then wheels upgrade apply to swap vendor/wheels/. Happy shipping.
Comments