๐ Recent release history (last 2 versions)
[2.188] - 2026-07-08
Security
- DNS provider credentials encrypted at rest (GHSA-38cv-3c4g-w55w) โ DNS-01 provider API keys/tokens (Cloudflare, Route53, โฆ) were stored as plaintext JSON in
dns_providers.credentialsdespite the column being labelled encrypted, so anyone with database read access (or a raw export of the field) obtained domain-control credentials. The field is now encrypted at rest via a model property (utils.encryption Fernet, mirroring the EAB HMAC key), read paths transparently decrypt (legacy plaintext rows still read), and migration052encrypts any pre-existing rows. Backup export/restore round-trips the decrypted value so cross-machine restore keeps working; regression tested (round-trip, at-rest ciphertext, constructor/restore path, migration idempotency). Reported externally by Ralph. - Dev-dependency advisories โ bumped transitive build/lint tooling out of vulnerable ranges via npm overrides:
js-yamlโ 4.3.0 (GHSA-h67p-54hq-rp68, quadratic-complexity DoS in merge-key handling; pulled in by eslint) and@babel/coreโ 7.29.7 (GHSA-4x5r-pxfx-6jf8, arbitrary file read viasourceMappingURL; pulled in by @vitejs/plugin-react). Both are build-time only and never shipped in the runtime bundle.
[2.187] - 2026-07-08
Added
- Configurable public vhost for ACME directory URLs โ a dedicated public hostname/port (
acme_public_vhost,acme_public_portin Settings โ General) can now be advertised in the local ACME server and ACME proxy directory URLs (/acme/*,/acme/proxy/*), independent of the admin UI URL โ the split admin/ACME reverse-proxy topology (e.g.admin.ucm.example.comvsacme.ucm.example.combehind one wildcard certificate). JWS verification accepts both the advertised public origin and the inbound URL; without a configured vhost, behavior is unchanged (request host). Wildcard hostnames are rejected for the vhost value (TLS SAN concept, not an advertised URL), and an optionalacme_public_tls_cert_idrecords which managed certificate to deploy on the ACME vhost (metadata only). Settings APIs exposeacme_public_base_url/acme_proxy_public_base_urland the UI directory URLs follow the configured origin (#173, thanks @fredlubrano).
Changed
- SQLAlchemy 2.0
Session.getโ all 353Model.query.get()/query.get_or_404()call sites migrated todb.session.get()/db.get_or_404(), eliminating ~4500LegacyAPIWarningper test run ahead of a future SQLAlchemy major bump.
Security
- CSR extension policy on certificate issuance โ the shared CSR signer copied every requested extension into the issued certificate verbatim, so a crafted CSR carrying
BasicConstraints CA:true(andkeyCertSign) submitted through EST, SCEP or ACME enrollment could obtain a working subordinate CA โ a trust escalation from an enrollment endpoint. Leaf certificate types now forceBasicConstraints(ca=False)and strip the CA-only key-usage bits; only the explicit intermediate-CA signing flow may assert CA powers. Regression tested. - SCEP unauthenticated auto-enrollment when no challenge is set โ per RFC 8894 ยง2.4 an omitted
challengePasswordallows unauthenticated authorisation, so with auto-approve enabled and no challenge configured any anonymous client on the public SCEP endpoint received a CA-signed certificate. InitialPKCSReqauto-issuance is now refused unless a challenge is configured (manual-approval mode and renewals are unaffected);UCM_SCEP_ALLOW_NO_CHALLENGE=1opts back in for isolated deployments. - Key-strength floor on EST and SCEP enrollment โ both protocols now reject CSRs whose public key is below policy (RSA < 2048, non-NIST EC curves) instead of signing weak/exotic keys, matching the UI/API issuance floor (EST RFC 7030 ยง3.7 and SCEP defer key policy to the local CA).
- EST request-body cap bypass via chunked encoding โ the body-size limit only inspected
Content-Length, so aTransfer-Encoding: chunkedrequest could stream an unbounded body into memory. The body is now read with a hard cap on the request stream regardless of framing.
Fixed
- Broken settings restore endpoint โ
POST /api/v2/settings/backup/restorepassed a temp-file path where the service expects raw bytes, so it returned 500 on every call and left the uploaded (encrypted) backup in/tmpon each attempt. It now reads the upload as size-capped bytes in memory (no temp file) and restores correctly. - mTLS certificate import always failed โ
POST /api/v2/mtls/enroll-importcrashed on every valid PEM (undefined variable, then a str stored into the binarycert_pemcolumn). The endpoint now imports and enrolls correctly; covered by a regression test. - Public ACME vhost hardening (post-#173 review) โ the local ACME server now accepts the JWS
urlon both the advertised public origin and the inbound request origin (the tolerance moved intoverify_jwsitself, shared with the proxy, so in-flight orders survive anacme_public_vhostchange on/acme/*too); CAA enforcement andcaaIdentitiesfollow the configured public hostname instead of the inboundHost; the vhost is validated as a real FQDN (rejects.., leading/trailing hyphens, single labels); non-string vhost values and out-of-band garbage port rows return 400/defaults instead of 500; the public origin is memoized per request (one combined SystemConfig read instead of up to 10 per proxy request); the TLS certificate is picked from a dropdown of key-bearing certificates instead of a raw database id, and clearing it removes the config row; the CA Accounts panel shows the same public directory URLs as the other tabs.
Full history: CHANGELOG.md
Installation
Docker (Recommended)
# From Docker Hub
docker pull neyslim/ultimate-ca-manager:2.189-rc2
# Or from GitHub Container Registry
docker pull ghcr.io/neyslim/ultimate-ca-manager:2.189-rc2
# Run
docker run -d -p 8443:8443 \
-e SECRET_KEY=$(openssl rand -hex 32) \
--name ucm neyslim/ultimate-ca-manager:2.189-rc2Debian/Ubuntu
wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.189-rc2/ucm_2.189.rc2_all.deb
sudo dpkg -i ucm_2.189.rc2_all.deb
sudo apt-get install -fFedora/RHEL
wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.189-rc2/ucm-2.189.rc2-1.fc43.noarch.rpm
sudo dnf install ./ucm-2.189.rc2-1.fc43.noarch.rpmSilent/Automated Install
# Skip firewall prompts for CI/automation
sudo UCM_PORT=8443 UCM_FIREWALL=no dpkg -i ucm_2.189.rc2_all.debDefault Credentials
- Username:
admin - Password:
changeme123
Change the password immediately after first login!
Documentation
- Installation Guide
- API Documentation