Skip to content

Commit eb73cb6

Browse files
bpamiriPeter Amiriclaude
authored
docs(web/blog): announce Wheels 4.0.5 (with 4.0.4 hardening) (#3231)
Covers the 4.0.4 hardening pass (security, performance, deploy, cross-engine) and the 4.0.5 install-anywhere packaging (arm64 Linux + daily install-smoke CI). 4.0.4 was superseded by 4.0.5 the same day, so this single post covers both. Signed-off-by: Peter Amiri <petera@pai.com> Co-authored-by: Peter Amiri <petera@pai.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5ac5e8a commit eb73cb6

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
title: 'Wheels 4.0.5: a hardening release — 100+ fixes across security, performance, and deploy, now installable anywhere'
3+
slug: wheels-4-0-5-released
4+
publishedAt: '2026-06-19T13:30:00.000Z'
5+
updatedAt: '2026-06-19T13:30:00.000Z'
6+
author: Peter Amiri
7+
tags:
8+
- wheels-4
9+
- release-notes
10+
- frameworks
11+
categories:
12+
- Releases
13+
excerpt: >-
14+
Wheels 4.0.5 is out, and together with 4.0.4 it's one of the most substantial
15+
hardening passes on the 4.0 line — 100+ changes spanning security (open-redirect
16+
and info-disclosure fixes, fail-closed gates, SQL-surface tightening), warm-path
17+
performance, a much tougher `wheels deploy`, and Adobe CF / BoxLang cross-engine
18+
fixes. 4.0.5 then makes the whole thing installable the same way on every major
19+
platform — Homebrew, Scoop, apt, dnf — including arm64 Linux, verified daily.
20+
---
21+
22+
Wheels 4.0.5 is out — and the story is really two releases. **4.0.4 was a large hardening pass (100+ commits across security, performance, and deploy), and 4.0.5 makes that work installable the same way on every major platform, including arm64 Linux.** 4.0.4 went out and was superseded by 4.0.5 the same day, before any announcement, so this post covers both.
23+
24+
## Security hardening
25+
26+
4.0.4 closed a batch of real security gaps in the framework's request surface:
27+
28+
- **Open-redirect hardening.** `$isSafeRedirectUrl()` now normalizes input per WHATWG URL parsing (stripping embedded tab/CR/LF) before it classifies a redirect, and rejects backslash and schemeless-authority tricks (`/\evil.com`, `\/evil.com`, `https:/evil.com`).
29+
- **Information disclosure.** The HTML and JSON branches of `/wheels/info` no longer render secrets — `csrfCookieEncryptionSecretKey` is no longer printed in plaintext, and the JSON branch no longer dumps the full application metadata (datasource credentials, ORM settings).
30+
- **Fail-closed by default.** The production block on `/wheels/*` dev-UI handlers is now a fail-closed allowlist, and the URL reload gate refuses to act unless a non-empty `reloadPassword` is configured — `?reload=true` with no password set is denied, not allowed.
31+
- **Path traversal.** `$getRequestFormat` rejects non-alphanumeric `url.format` values, closing a local-file-inclusion vector through the error-template include.
32+
- **SQL surface.** Scope-handler arguments and `findAll(select=)` items are tightened (values like `Union Pacific` round-trip unchanged while genuinely suspicious `select` items get flagged), and `updateAll(include=)` join parsing no longer splits on a bare `ON` token inside an identifier.
33+
- **Proxy trust.** A new `trustProxyHeaders` setting (default `false`) governs whether `X-Forwarded-*` is believed — `isSecure()`, maintenance-mode IP exceptions, and the reload rate-limit key all stop trusting client-supplied forwarded headers unless you opt in.
34+
- **Deploy secrets** are redacted from remote-execution failure messages and sent over SSH stdin (`docker login --password-stdin`) so they never surface in output.
35+
36+
## Performance
37+
38+
Ten warm-path optimizations, focused on the work every request repeats:
39+
40+
- `model()` and `controller()` take a **lock-free fast path** on cache hits instead of a reflective double-checked lock.
41+
- `URLFor()` route lookups are memoized in application scope (with negative caching), and database column metadata is cached per datasource+table.
42+
- The per-request action-dispatch gate is now an **O(1)** lookup instead of an O(n) list scan, the HTTP status-code map and partial column lists are memoized instead of rebuilt per render, and mixin-free apps no longer pay for a throwaway `wheels.Plugins` instance on every request.
43+
44+
## `wheels deploy` grew up
45+
46+
The Kamal-port deploy command got roughly twenty fixes hardening it for real fleets:
47+
48+
- Works on **fresh hosts** now (the proxy boot guard never reached `boot()`), acquires its fleet lock **all-or-nothing**, bounds secret resolution with a timeout, and writes an on-server audit trail.
49+
- Correct `rollback --destination` overlay handling, proxy traffic built from `proxy.app_port`, env-file secret delivery to app and accessory containers, stricter IPv6 host validation, and same-version redeploys that no longer collide on the container name.
50+
51+
## Cross-engine fixes
52+
53+
- **Adobe ColdFusion**: authorized reloads returned HTTP 500 on case-sensitive filesystems — i.e. every stock Linux Adobe deployment — and URL environment switches into production/maintenance silently reverted. Both fixed, along with a Lucee-only `cfabort;` that 500'd `GET /` on Adobe.
54+
- **BoxLang**: hardened null handling across the router, dispatcher, and error handler for BoxLang's stricter semantics.
55+
- **Oracle / SQL Server**: identity retrieval after INSERT now uses the JDBC driver's generated keys (or session-scoped sequence values) instead of the race-prone `@@IDENTITY` / `MAX(ROWID)` fallbacks.
56+
57+
## New capabilities
58+
59+
- **`wheels jobs work`** — a long-lived background-job worker loop (`--queue`, `--interval`, `--max-jobs`), plus `wheels jobs status`.
60+
- **`wheels upgrade apply`** swaps an app's `vendor/wheels/` for the framework bundled in the installed CLI, and **`wheels upgrade check --strict`** escalates advisory findings to a hard CI-gating failure.
61+
- A `subpath` setting for subfolder-mounted apps, a `/up` liveness/warm-up endpoint in scaffolded apps (used by the deploy healthcheck), and per-tool MCP input schemas.
62+
63+
…and roughly **eighty fixes** in total — routing `scope()`/`namespace()` callbacks, named capture groups, CORS duplicate-header arbitration, multi-node `RateLimiter` storage, honest `migrate`/`seed`/`test` exit codes and output, `hasMany` shortcut associations, and more. The complete list is in the [changelog](https://github.com/wheels-dev/wheels/blob/main/CHANGELOG.md).
64+
65+
## Now install it anywhere — including arm64
66+
67+
4.0.5's own headline is distribution. The `wheels` CLI installs the same way on every major platform:
68+
69+
```bash
70+
# macOS / Linux — Homebrew
71+
brew install wheels-dev/wheels/wheels
72+
73+
# Windows — Scoop
74+
scoop bucket add wheels https://github.com/wheels-dev/scoop-wheels
75+
scoop install wheels
76+
77+
# Debian / Ubuntu — apt
78+
curl -fsSL https://apt.wheels.dev/wheels.gpg | sudo gpg --dearmor -o /usr/share/keyrings/wheels.gpg
79+
echo "deb [signed-by=/usr/share/keyrings/wheels.gpg] https://apt.wheels.dev stable main" | sudo tee /etc/apt/sources.list.d/wheels.list
80+
sudo apt update && sudo apt install wheels
81+
82+
# RHEL / Fedora / Rocky / Alma — dnf
83+
sudo dnf config-manager --add-repo https://yum.wheels.dev/wheels.repo
84+
sudo dnf install wheels
85+
```
86+
87+
The Linux `.deb`/`.rpm` are now **architecture-independent** — the CLI launches through a portable `java -jar` runner — so `apt install` / `dnf install` work on **arm64 (aarch64)** as well as x86_64: Raspberry Pi, AWS Graviton, Ampere, arm64 Linux VMs on Apple silicon. 4.0.5 also fixes a bug where the RHEL/Fedora package couldn't locate its Java runtime, so `dnf install wheels` now works out of the box on Rocky, Alma, and Fedora. Java 21 is pulled in automatically on every channel.
88+
89+
And we now **verify all four channels every day**: a CI job installs the published CLI through Homebrew, Scoop, apt, and yum on real macOS / Windows / Ubuntu / Rocky runners (amd64 *and* arm64) and asserts `wheels --version` matches the release — so a broken `brew install wheels` becomes our problem before it becomes yours.
90+
91+
## A note on versions
92+
93+
If you see a **4.0.4** tag, that's expected: 4.0.4 shipped the hardening work above but its Linux packages weren't yet architecture-independent, so 4.0.5 superseded it the same day. Everything in 4.0.4 is in 4.0.5 — just install or upgrade to **4.0.5**.
94+
95+
## Upgrading
96+
97+
```bash
98+
brew upgrade wheels # Homebrew
99+
scoop update wheels # Scoop
100+
sudo apt update && sudo apt install --only-upgrade wheels # apt
101+
sudo dnf upgrade wheels # dnf
102+
```
103+
104+
Then confirm:
105+
106+
```bash
107+
wheels --version # Wheels Version: 4.0.5
108+
```
109+
110+
The framework itself lives in `vendor/wheels/` in your app — run `wheels upgrade check` to see anything worth adjusting before you bump a project, or `wheels upgrade apply` to swap it. Happy shipping.

0 commit comments

Comments
 (0)