cln-application 26.09
This release moves the application to Node 22, adds optional native HTTPS and reverse-proxy support, tightens how settings, passwords and sessions are handled, and modernises the build and CI pipeline. Nothing in the UI changes shape; most of the work is under the hood.
Highlights
- Optional native HTTPS. Set
APP_TLS_KEY_FILEandAPP_TLS_CERT_FILEto serve TLS directly, or keep a reverse proxy in front as before. The README now explains the three supported transport setups and whatAPP_PROTOCOLmeans. - Reverse-proxy aware. New
APP_TRUST_PROXYsetting (false,true, a hop count or a list of proxy addresses) controls whichX-Forwarded-*headers are honoured. - Node 22. The Docker image, the CI workflows and the runtime now run on Node 22; the root
package.jsondeclaresengines.node >= 20. - Backend unit tests. A first suite covers authentication, settings validation and request protection, and runs in CI alongside the frontend tests.
Application
- New passwords must be at least 12 characters and different from the current one; the form says so.
- The "Confirm New Password" field must be typed; pasted or dropped text is ignored there, so a typo in the new password cannot be confirmed by pasting the same wrong value.
- Changing the password signs out every other open session. Logging out ends the session on the server as well as in the browser.
- Application settings are validated before they are saved; invalid values return a clear 400 response listing the accepted options.
- Fiat exchange rates are fetched only after sign-in and cached for five minutes.
- Creating an invoice rune from the Connect Wallet screen is idempotent: a second attempt answers 409 instead of adding another entry.
- Error responses are consistent: 400 for malformed JSON, 413 for bodies over 500 kB, and a generic 500 for unexpected failures. Node error messages still pass through for lightning calls.
- Commando connections use the
wsclient on every Node version and reconnect on demand instead of giving up after a few attempts. - gRPC protocol definitions ship with the backend instead of being fetched at start-up.
- Startup logs describe the transport configuration and whether single sign-on mode is active.
- Opening another application on the same hostname, such as Ride The Lightning, no longer breaks the session check. The CSRF cookie has its own name and the client never lets a foreign
XSRF-TOKENcookie replace its token (#165). If the session check does fail, the login dialog now shows the actual reason instead of implying a wrong password. - Large satoshi balances in the summary boxes read in millions instead of thousands: 1,250,000 sats now shows as
1.25Mrather than1,250K, and anything under a million is shown in full (#158). BTC display is unchanged. - Paying a BOLT 12 offer now checks the invoice returned by the issuer against the amount shown in the form. If the issuer asks for a different amount, the payment is not sent and both amounts are shown.
- The invoice rune created from the Connect Wallet screen now allows
invoiceandlistinvoicesas alternatives within one restriction, so wallets that receive it can actually use it. A rune created by an earlier version could not authorize either call; see Upgrade notes. - The first password of a fresh install can only be set from the server's own address:
localhost, an IP address, the configuredAPP_HOST, or a.local/.onionname. Logins and later password changes work from any address as before. - API responses are sent with
Cache-Control: no-store, so nothing returned by the API stays in the browser cache after logout. - Logging out waits for the server to end the session before clearing the screen; if the request fails, the session view stays open and the error is shown.
- The server parses JSON request bodies only.
Container, build and CI
- Base images are pinned by digest; the frontend build inside the image emits no source maps and no inline runtime script.
.dockerignorekeeps the build context small; apt caches are cleaned in both stages.- GitHub Actions are pinned by commit, every workflow declares its permissions, and a weekly job runs a dependency audit and a secret scan.
- Release images are published with provenance and SBOM attestations. Dependabot proposes updates for npm, Docker and GitHub Actions.
csurfis replaced bycsrf-csrf,helmetsets the response headers, and the unusedts-nodedependency is gone. All runtime dependencies are at their latest versions with a clean audit.- The development compose file is a hard-coded regtest example with all data under
/tmp. - The runtime image carries the real
package-lock.json; earlier images held a copy ofpackage.jsonunder that name.
Platform notes
- StartOS. Keep
APP_PROTOCOL=httpsand leaveAPP_TLS_KEY_FILEandAPP_TLS_CERT_FILEempty; the platform proxy terminates TLS and the app still listens on plain HTTP behind it.config.jsonkeeps the same keys, but after the first login thepasswordvalue changes from a 64-character hex string to a longerscrypt$…string; a package that validates that file must keep treatingpasswordas an opaque string. Nothing else is written to the file. If the package also exposes the UI over a plain-HTTP Tor address, verify login there once: cookies are now markedSecurewheneverAPP_PROTOCOL=https, and a browser accepts them only on origins it treats as secure. New installs set the first password through the platform's.localor.onionaddress, which the first-password rule accepts, so nothing changes there. - Umbrel. No configuration change is needed. The app keeps running in single sign-on mode behind the Umbrel proxy; two new lines appear in its logs at startup, one saying single sign-on is on and one saying the app is served over plain HTTP on a non-loopback address. Both are expected on Umbrel and can be ignored. The image is multi-architecture (amd64, arm64, arm/v7) as before. Running Ride The Lightning alongside on the same hostname no longer breaks the session (#165). The first-password rule for new installs does not apply here: single sign-on has no password screen.
- Standalone Docker. Pull the new image; the
docker runinvocation, the mountedconfig.jsonand the commando env file are unchanged. If a reverse proxy sits in front, setAPP_PROTOCOL=httpsand pointAPP_TRUST_PROXYat the proxy. On a fresh install, set the first password by opening the app vialocalhost, its IP address or the name inAPP_HOST; a custom DNS name works for everything after that. - Bare metal. Run on Node 22 (the tested runtime;
enginesallows 20 or newer) and install withnpm ciunder the same Node you run with. New environment variables are optional:APP_TRUST_PROXY,APP_TLS_KEY_FILE,APP_TLS_CERT_FILE. The same first-password rule applies as for Docker.
Upgrade notes
- Passwords. Existing installs keep working. A password stored by an earlier version is converted to a salted hash on the first successful login.
APP_PROTOCOL=httpsdescribes what the browser sees. Set it only when TLS is actually in front of the app (a proxy or the new native option), otherwise cookies will not be accepted.- Body size. Requests larger than 500 kB are rejected.
- CSRF cookie name. The cookie is now
cln_csrf; the header staysX-XSRF-TOKEN. Custom clients that read the old_csrfcookie by name must update. - New environment variables.
APP_TRUST_PROXY(defaultfalse),APP_TLS_KEY_FILE,APP_TLS_CERT_FILE(default empty). - First password. On a fresh install the first password is accepted only when the app is opened via
localhost, an IP address,APP_HOST, or a.local/.onionname. Existing installs are not affected. - Invoice rune. If you created an invoice rune from the Connect Wallet screen with an earlier version, it cannot be used by wallets. Remove the
INVOICE_RUNEline from the commando env file, restart, and create a new one from the Connect Wallet screen; the old rune can be blacklisted on the node withblacklistrune. - JSON only. Custom clients must send
application/jsonbodies; form-encoded bodies are no longer parsed.