Releases: ingres-si/ingressi
Release list
v1.13.3
Fixes
- L4 proxy hosts with load balancing enabled no longer make Caddy reject the whole config with
unknown field "retries"(#301, #302). The unsupported Retries and Unhealthy Latency fields are removed from the L4 dialog, and legacy stored values are ignored.
Dependencies
- better-auth 1.7.6, drizzle-orm 0.45.3, next 16.3.6, maplibre-gl 6.11.2, apexcharts 7.6.0, lucide-react 1.48.0, plus the dev-dependency group (#299, #300).
- Docker golang and ubuntu base images bumped (#297, #298); Caddy compatibility pins refreshed (#294).
Upgrade notes
No upgrade actions needed: docker compose pull && docker compose up -d.
v1.13.2
A security fix release for v1.13.1. It covers forward-auth identity headers, the WAF "Skip OWASP CRS for path" template, secrets left in the database file after encryption, instance sync, disabled users and WAF event redaction. Some changes need action from operators: check the X-CPM-User and WAF template items, and rotate CA keys and DNS credentials if database backups from before the upgrade exist. The README section Upgrade Notes → Upgrading from v1.13.1 covers every item.
Upgrade notes
Pull and recreate the containers with docker compose pull && docker compose up -d.
These changes also apply when upgrading from an earlier release.
X-CPM-Useris now the sign-in username, or the email address for an account without one. It used to be the display name, which is not unique (users, OAuth providers andADMIN_USERNAMEchoose it), so an upstream trusting it could take one user for another. An upstream that maps users by this header (e.g. Grafana's auth proxy) sees new values and may create new users for them; map the existing ones or key them onX-CPM-User-Id, the account's id. See Identity headers.- WAF template "Skip OWASP CRS for path". The v1.13.x template matched the raw request URI, so
/api/../index.phpor/api/%2e%2e/index.phpturned the OWASP CRS off for a path outside/api/. Replace stored copies (SecRule REQUEST_URI "@beginsWith /api/" … ctl:ruleRemoveByTag=OWASP_CRS") with the new template:SecRule REQUEST_FILENAME "@rx \A/api/(?:[^.]|\.[^.])*\.?\z" "id:9001,phase:1,pass,nolog,ctl:ruleRemoveByTag=OWASP_CRS", which matches the decoded path and skips nothing when it contains... - Deleted secrets are wiped from the database file. SQLite now overwrites deleted content (
PRAGMA secure_delete), and the first start after the upgrade runsVACUUMonce, as does any start whose migrations encrypt or replace stored secrets. Before this, the plaintext CA private keys and DNS provider credentials that earlier releases encrypted on startup, and the CA keys the first sync removed from slaves, could remain in the file's free pages. VACUUM needs free disk space about the size of the database; if it fails, the start logsFailed to VACUUM the databaseand continues. Backups or copies of the database made before the upgrade may still hold them: if any exist, rotate the CA keys and DNS provider credentials. - Forward auth: copy headers
Authorization,Proxy-Authorization,Cookie. When one of them is listed in the copy headers of generic or Authentik forward auth and the auth server returns no value for it, the client's own value is now removed on protected routes, after the auth server has seen it, instead of reaching the upstream. On excluded paths and on API bypass routes the client's credentials still reach the upstream unchanged, since nothing authenticates those requests. - Stored WAF directives left out of the config are now listed on the WAF page. A left-out deny rule (e.g. one using
@ipMatchFromFile) no longer blocks anything, and when aSecDefaultActionis left out, later rules usingblockfollow Coraza's default action instead, which does not block. - Disabled users get no Better Auth session: signing in answers as a wrong password does, and disabling a user now also ends their dashboard sessions.
- Self-registration refuses an email address that another account has or signs in with using Better Auth's "User already exists. Use another email.", whatever the reason. An OAuth sign-up whose email has no
@, or ends in@localhost(the forward-auth portal's names), is refused. - Editing a user's email (dashboard or
PUT /api/v1/users/{id}) stores it trimmed and lowercased, and refuses an address that lowercasing would turn into another one (such as one with the Kelvin sign), as creating a user does. - WAF events also redact credential query parameters in the request URI (
token,api_key,password,code, …), form fields and query parameters with such names in rule messages (ARGS:password), and headers whose names contain such words (e.g.X-Access-Token).
v1.13.1
A security hardening release covering authentication, forward auth, stored secrets, instance sync, mTLS and the WAF (#296), plus a fix that stops CPM from deriving sign-in usernames from email addresses. v1.13.0 has been withdrawn and is replaced by this release. Several changes need action from operators. If you run v1.12.0 or earlier, upgrade directly to v1.13.1 and read the upgrade notes below before pulling the new images. The README section Upgrade Notes → Upgrading from v1.12.0 or earlier covers every item in full.
Upgrade notes
Pull and recreate the containers with docker compose pull && docker compose up -d. docker compose restart does not re-read .env.
Check before upgrading
-
Example secrets are refused. In production the web container refuses to start when
SESSION_SECRETis a shipped placeholder (including the old.env.examplevalueyour-secure-session-secret-here-min-32-chars) orADMIN_PASSWORDis an example password from an earlier README or.env.example("ADMIN_PASSWORD is an example value from the documentation; choose your own password"). Generate a new secret withopenssl rand -base64 32. Stored secrets that were encrypted under the placeholder are re-encrypted with the new secret on the next start, so you don't have to re-enter anything. For any other rotation, put the old value inSESSION_SECRET_PREVIOUS(see Rotating SESSION_SECRET in the README)..env.examplenow leavesSESSION_SECRET,ADMIN_PASSWORDandCLICKHOUSE_PASSWORDempty. -
Using your own compose file? Pass the new variables through. The stock
docker-compose.ymlpassesSESSION_SECRET_PREVIOUS,FORWARD_AUTH_ALLOWED_PORTS,TRUSTED_CLIENT_IP_HEADERandINSTANCE_SYNC_TIMEOUT_MSto the web container. The web container only gets the variables listed in thewebservice'senvironment, so a value that exists only in.envhas no effect. If you run a custom or modified compose file, add these variables there. This also applies to existing variables the stock file doesn't list (e.g.INSTANCE_MODE: ${INSTANCE_MODE:-}). Give numeric variables their documented default rather than an empty value, because an empty value is read as0(e.g.LOGIN_MAX_ATTEMPTS: ${LOGIN_MAX_ATTEMPTS:-5}). -
Forward auth on a non-standard port. If browsers reach forward-auth protected sites on a port other than 80/443 (e.g. Caddy published as
8443:443, or NAT), setFORWARD_AUTH_ALLOWED_PORTS=8443(comma-separated for several) and recreate the web container. Without it, existing forward-auth sessions stop validating, the portal shows "This site is served on port 8443, which is not allowed for forward authentication…" and the web container logs[forward-auth] Rejected host:8443 … FORWARD_AUTH_ALLOWED_PORTS. -
Instance sync: upgrade slaves before the master, or at the same time. An upgraded master seals certificate private keys and DNS provider credentials to each slave's own key (see Instance sync under Highlights). A slave still on v1.12.0 or earlier gets the old payload and needs the master's
SESSION_SECRETas its own. A master still on v1.12.0 or earlier sends DNS provider credentials encrypted with its ownSESSION_SECRET, so every slave needs that secret asSESSION_SECRETor inSESSION_SECRET_PREVIOUS. Otherwise applying the synced config fails. Keep the shared secret until every instance runs v1.13.1. After that, each instance can have its own secret. -
Instance sync no longer follows redirects. Point each slave URL (in the UI or in
INSTANCE_SLAVES) at the final URL. A slave that redirects now fails with "Sync key request failed with HTTP 302" (or 301/308). Each sync request is limited toINSTANCE_SYNC_TIMEOUT_MS(default 60 s; reported as "Sync timed out"), so raise it for slaves that take longer to apply a config. Slave URLs with credentials, a query string or a fragment are rejected, and suchINSTANCE_SLAVESentries are skipped withSkipping INSTANCE_SLAVES entry <index>: <reason>. -
Proxies in front of a slave must pass
GETas well asPOSTon/api/instances/sync, including theAuthorizationheader and the query string, and must not cache theGETreply. Before every sync, the master fetches the slave's sync key with this request. A405makes an upgraded slave look like an older release. Any other refusal fails the sync with "Sync key request failed with HTTP ". -
Sync key pinning starts on the first sync. After the upgrade, the master pins each slave's sync key the first time it sees one. From then on:
- A slave that comes back with a different key (e.g. reinstalled with a new
SESSION_SECRET) fails with "Slave sync key changed; verify the slave, then pin its new key or reset its key pin" until you pin its new key on the master. - When you rotate a slave's
SESSION_SECRET, keep the old value in the slave'sSESSION_SECRET_PREVIOUSuntil the master has synced to it once (click Sync now on the master). The master then re-pins the new key automatically. For slaves pinned withsyncPublicKey/syncKeyIdinINSTANCE_SLAVES, update the entry instead. - After downgrading a slave to v1.12.0 or earlier, reset its key pin. Otherwise its syncs fail with "Sync key request failed with HTTP 405".
- To leave no trust-on-first-use window, pin each slave's key before the first sync (see New configuration).
- A slave that comes back with a different key (e.g. reinstalled with a new
-
CA private keys stay on the master. CA private keys are now encrypted at rest with
SESSION_SECRETand are no longer synced. The first sync removes the copies that older versions stored on slaves. Slaves still validate client certificates, but a slave promoted to master cannot issue certificates from the existing CAs. Back up the master's database together with itsSESSION_SECRET. -
WAF custom directives. Some lines are no longer sent to Caddy. Stored rules are kept, but they are left out of the generated config, and the web container logs
[waf] <source>: N custom directive line(s) are not sent to Caddy and have no effect: …. The dropped lines are:- rules using file-reading or exec operators (
@pmFromFile/@pmf,@ipMatchFromFile/@ipMatchF,@inspectFile,@validateSchema). The data-file operators still work with the embedded@owasp_crs/*.datafiles when the CRS is loaded. - the
setenvaction, andctl:ruleEnginein any spacing or quoting. - lines Coraza cannot parse, and directives continued over several lines with a trailing
\. - the rest of a chain when one of its lines is dropped.
- rules that reuse an earlier rule's
id:.
Check the log after upgrading and rewrite any affected rules.
- rules using file-reading or exec operators (
-
Host placeholders are sent literally.
{env.*},{system.*}and{file.*}are no longer expanded in default responses, error pages, path-block bodies and redirect rule targets. Request placeholders such as{http.request.uri}and{http.request.host}still expand. For example, rewritehttps://{env.PRIMARY_DOMAIN}{http.request.uri}with a literal host. -
Database file permissions. On startup, the SQLite database and its
-journal/-wal/-shmfiles lose their world permission bits. Owner and group bits are unchanged. A backup job that reads the files as an unrelated user needs to run as the owner or a member of the files' group.
Behaviour changes
- Admin credentials from the environment are applied only when they change.
ADMIN_USERNAME/ADMIN_PASSWORDare applied when the admin is created and whenever they change, instead of on every start. A password changed in the UI now survives restarts. On the first start after upgrading, a stored admin password that differs fromADMIN_PASSWORD(and is notadminor a documented example) is kept, and a warning is logged. To force the env password, changeADMIN_PASSWORDagain and recreate the web container. A changedADMIN_USERNAMEis still applied on that first start, unless another account already signs in with it (see Sign-in usernames under Highlights). The same steps recover a lost admin password: they reset the primary admin's password and its username toADMIN_USERNAME, restore its admin role, re-activate it, and sign out its sessions. - Password policy everywhere. Passwords must be 12–256 characters with upper- and lowercase letters, a digit and a special character. This now also applies to admin-created users (dashboard and
POST /api/v1/users), password changes, and Better Auth self-registration (AUTH_ALLOW_SELF_REGISTRATION=true) and reset. Scripts that create users with weaker passwords get400. - Password changes sign out other sessions. Changing or setting a password signs out the user's other dashboard sessions and all of their forward-auth sessions. API tokens are kept, and you can revoke them under Profile → API Tokens.
- Unused Better Auth self-service endpoints are disabled:
/api/auth/update-user,/change-password,/change-email,/delete-user,/unlink-account,/update-session,/verify-passwordand/is-username-available. Use the Profile page or/api/v1/instead. WithAUTH_ALLOW_OAUTH_REGISTRATION=false, an OAuth sign-in can no longer create an account, even if the client asks for sign-up. - Sign-in usernames. The login page signs in by username only and ignores case. CPM never generates a username from an email address. Startup, profile edits and email changes leave stored usernames as they are. The one exception is the primary admin: applying a changed
ADMIN_USERNAMEorADMIN_PASSWORDresets its username toADMIN_USERNAMEand its email to<ADMIN_USERNAME>@localhost. An account without a username gets its own email address, lowercased, as its username only if that address is already a valid username (3–255 characters fromA-Z a-z 0-9 _ . @ -) and no other account signs in with it or has it as its email address. Otherwise an administrator sets the username on the Users page (**...
v1.12.0
Highlights
Generic forward auth (#188)
Proxy hosts can now use a generic forward-auth provider (Authelia preset or fully custom) alongside the existing Authentik integration and the built-in CPM portal, with the split browser vs API pattern for mixed UI + API services:
- Browser requests (
Accept: text/html, noX-Requested-With) keep the auth server's portal-redirect flow — 302s pass through so users get the normal login experience. - API split mode: with
api_splitenabled, API clients and WebSocket handshakes get a bare401instead of an HTML login page mid-stream — a second route converts any 3xx from the auth server into a static 401. - Bypass headers (e.g.
X-Api-Key) let requests skip forward auth entirely so the upstream can enforce its own API-key auth (the Moonraker/Spoolman case), replacing hand-writtencustomPreHandlersJsonsubroutes.
Security hardening: identity headers the auth server returns (Remote-User, Remote-Groups, …) are stripped from inbound requests on every route of the host — protected or not — preventing identity spoofing to the upstream (same class of fix as the X-CPM-* stripping). Header names are validated against the RFC 7230 token grammar, placeholders are stripped from endpoints and paths, and enabled-but-invalid configs are rejected at write time so a host can never silently publish unprotected.
Surface: forwardAuth field on proxy hosts, Settings > Forward Auth Defaults (synced to slave instances), /api/v1/settings/forward-auth API group, and the OpenAPI ForwardAuthConfig schema. Tested with 14 functional e2e tests against real Caddy via a mock Authelia container (browser 302 / API 401 / WS 401 / bypass / forged-header stripping) plus real Authelia and real Moonraker integration tests.
L4 hosts can no longer bind reserved ports 80/443/2019 (#295)
An L4 host listening on :443 (or :80) while regular HTTPS proxy hosts exist made CPM generate two independent listeners on the same port. Caddy sets SO_REUSEPORT on every listener, so the second bind succeeded silently — and the kernel then split new connections between the two sockets, failing ~50% of TLS handshakes with tlsv1 alert internal error (reported and confirmed at kernel level in #295). Two independent HTTP and layer4 servers sharing port 443 is not supported by Caddy (the caddy-l4 docs require listener wrappers for that), so CPM now refuses the invalid configuration instead of generating it:
- L4 listen addresses on ports 80, 443 and 2019 are rejected on create and update — in the UI, the REST API and server actions — with the reserved ports named in the error.
- Instance sync rejects synced L4 hosts on reserved ports, so a replica can't inherit the broken config.
- Config generation and the L4 port manager skip legacy rows already on a reserved port and log a warning, so an existing bad row can't poison
:443for every other host until it's fixed. - The create/edit dialog documents the reservation; the wiki no longer describes the TLS SNI example as "on port 443" and gained a troubleshooting entry.
If you have an existing L4 host on :443, move it to a different listen port (e.g. :8443) — SNI matching and passthrough behave the same, and "Apply Port Changes" publishes the new mapping.
Caddy monitor: config drift detected by content
The monitor's restart detection compared the live config ID against a literal "empty" marker — but Caddy always serves an ETag, so the marker was unreachable and no configuration re-push ever happened after the l4-port-manager recreated the caddy container, leaving it unconfigured until something else pushed config. Every successful applyCaddyConfig() now records the sha256 of the config Caddy is actually serving, and the monitor re-pushes whenever the live hash differs — catching empty configs, the image's default Caddyfile, and stale autosaves alike.
Maintenance
- Caddy: bump
otlptracegrpc/otlptracehttpto 1.46.0 and theotel/log0.21.0 line so the tree compiles against otel core 1.46.0 (#284, #285–#288). - Bump TypeScript 5.9.3 → 7.0.2 (#293), 9 production (#292) and 4 development (#291) dependency updates.
- e2e: real Authelia and real Moonraker integration tests for the generic forward-auth provider.
- Repo cleanup: the forward-auth security analysis moved out of the repository (
docs/is now gitignored).
Changes
- e5e7f00 fix(l4): reject reserved listen ports 80/443/2019 for L4 proxy hosts (#295)
- 93b05fd deps(deps-dev): bump typescript from 5.9.3 to 7.0.2 (#293)
- f0bd0f0 deps(deps): bump the production-dependencies group with 9 updates (#292)
- 3b2a595 deps(deps-dev): bump the development-dependencies group with 4 updates (#291)
- 286de86 caddy: bump otel exporters to match merged otel 1.46.0 core (#285-#288)
- 9d75485 chore: remove security analysis from repo and ignore docs/
- c8a360b test(e2e): add real Authelia and real Moonraker integration tests for generic forward auth
- 7b6a1d1 feat(forward-auth): add generic forward-auth provider with split browser vs API auth (#188)
- 255ab49 caddy: bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc (#284)
- 3dbb3b8 fix(caddy-monitor): detect config drift by content, not an unreachable sentinel
Full Changelog: v1.11.3...v1.12.0
v1.11.3
Highlights
- Fix all logins failing with
SCHEMA_MISMATCHonaccounts.issuer(#283). Better Auth ≥ 1.7.3 validates the database schema at startup and rejects theaccountstable because itsissuercolumn wasNOT NULLwithout a default — every sign-in/sign-up request then died withBetterAuthError: SCHEMA_MISMATCH (unexpected-required-column). This release makes the schema conform again:accounts.issuernow has aDEFAULT ''(migration0025_accounts_issuer_defaultrebuilds the table on existing databases and backfills empty issuers the same way0024did).- A boot-time schema check repairs legacy database shapes automatically on startup (including upgrades from ≤ v1.11.2).
- An
account.createafter-hook backfills the issuer (local:credentialfor password accounts,local:oauth:<provider>otherwise) because Better Auth 1.7.4 silently drops unknown fields in its adapter — without this, newly created accounts would have an empty issuer and CPM's issuer-keyed queries (password change, account linking, identity lookup) would miss them. - If the data directory is not writable (the
docker cpownership trap from the issue), startup now fails with an explicit message naming uid10001and thechown 10001:10001fix instead of a rawSQLITE_READONLYerror.
Scope note: the released tags v1.10.0 – v1.11.2 pinned Better Auth 1.7.2 and were never affected; the bug shipped in Docker images built after the Better Auth 1.7.4 upgrade — i.e. the
latesttag since 2026-09-15. This release moveslatestto a fixed build.
Maintenance
- e2e: plant the stale L4 apply lock as root (mirrors how a crashed apply leaves the files owned by the sidecar user on the shared volume) and raise the
tcpSendread timeout to 10 s so emulatedlinux/amd64echo backends don't flake under full-suite load.
Changes
- 46c8481 test(e2e): plant stale L4 lock as root and tolerate slow emulated echo backends
- 4af1bf6 fix(auth): give accounts.issuer a DB default and repair legacy shapes (#283)
Full Changelog: v1.11.2...v1.11.3
v1.11.2
Highlights
- Expose DNS propagation delay/timeout settings with netcup defaults (#258): DNS-01 issuance could fail on slow-propagation providers (notably netcup) because Caddy's defaults (no delay, 2m timeout) expire before the TXT record becomes visible, even though the provider API works. Every DNS provider now has optional Propagation Delay / Propagation Timeout fields (new duration field type, validated as Go/Caddy duration syntax), emitted as Caddy
challenges.dns.propagation_delay/propagation_timeout. Netcup ships sensible defaults (600s delay, 900s timeout,-1disables the propagation check), overridable per provider.
Note: the
v1.11.1tag was never published as a GitHub release; this release supersedes it and includes its change.
Maintenance
- Refresh Caddy compatibility pins (#271), bump
google.golang.org/grpcin the Caddy image (#272) and the Go builder image (#265) - Verify Caddy pin refreshes by building the Dockerfile directly in CI (#270)
- Dependency updates: vitest 5.0.0, next 16.3.4, zod 4.5.4, lucide-react 1.40.0, apexcharts 7.1.0, maplibre-gl 6.7.0, react-map-gl 8.1.3, postcss 8.5.28, plus dev-dependency group bumps
- Correct the log-rotation documentation to reflect the real root cause of the 2026-09 disk-fill incident (logs directory permissions silently disabling timberjack cleanup — not Caddy defaults) and warn against write-only bind-mounted
/logsdirectories
Changes
- 7e57917 docs: correct log-rotation comment — perms, not Caddy defaults, filled the disk
- 9f2f128 caddy: bump google.golang.org/grpc in /docker/caddy (#272)
- f125793 caddy: refresh compatibility pins (#271)
- 64a0398 ci: verify Caddy pin refresh by building Dockerfile directly (#270)
- eeb810e deps(deps-dev): bump vitest from 4.1.11 to 5.0.0 (#269)
- 3c95f67 deps(deps-dev): bump @vitest/ui from 4.1.11 to 5.0.0 (#268)
- df24d53 deps(deps): bump the production-dependencies group with 7 updates (#267)
- aa41b26 deps(deps-dev): bump the development-dependencies group with 5 updates (#266)
- 7a36fe0 docker: bump golang from
4013ae0to512690ain /docker/caddy (#265) - f6ff473 Expose DNS propagation delay/timeout settings with netcup defaults (#258)
Full Changelog: v1.11.0...v1.11.2
v1.11.0
Highlights
- Add ClouDNS DNS provider for ACME DNS-01 challenges (#260)
- Show the application version in the web GUI (#259): release Docker builds bake the git tag into the app, displayed in the sidebar logo block (desktop + mobile drawer) and under the login card. The same constant now feeds the OpenAPI spec version instead of the previously hardcoded 1.0.0.
- Fix OAuth link/unlink not synchronizing
users.providerandusers.subject(#261): the Profile page could show an account as not linked even though OAuth sign-in worked, and kept claiming it was linked after unlinking. The identity columns are now re-derived from the authoritativeaccountstable on link/sign-in and after unlinking, the Profile page derives its connection state fromaccountsdirectly, and a one-time repair migration fixes existing deployments. - Fix WAF timestamps flipping between dots and slashes after refresh (#233): locale- and timezone-dependent rendering made the server (UTC) and the browser disagree after a full page reload. Formatting is now pinned to en-GB/UTC across the WAF events page (table, drawer, mobile cards), the audit log and the analytics blocked-events table, and WAF custom-range filters use UTC to match the displayed timestamps — headers and inputs are labelled "(UTC)".
- Clarify secret decryption errors with component context (#263): error messages now name the stored value that failed to decrypt (DNS provider credential, OAuth provider secret, instance API token, certificate private key), explain the likely cause (the
SESSION_SECRETchanged) and how to recover.
Changes
- 04a75e7 Clarify secret decryption errors with component context (#263)
- 54816f3 Fix OAuth link/unlink not synchronizing users.provider and users.subject (#261)
- 15449c0 Show application version in the web GUI (#259)
- a52e537 caddy: bump google.golang.org/grpc in /docker/caddy (#262)
- 18a1073 Fix timezone-dependent epoch expectations in WAF period filter test
- 1f99a7f Add ClouDNS DNS provider for DNS-01 challenges (#260)
- cd74731 Fix WAF timestamps flipping between dots and slashes after refresh (#233)
Full Changelog: v1.10.0...v1.11.0
v1.10.0
Highlights
- Add a configurable default response for requests that don't match any proxy host (#241). Unknown
Hostheaders and direct IP access were previously answered by Caddy's built-in behaviour; the response status and body are now configurable under Settings → Default Response and via the/api/v1/settingsREST endpoint. - Add WAF request body limit settings (#252):
SecRequestBodyLimit,SecRequestBodyInMemoryLimitandSecRequestBodyLimitActionare now exposed as global and per-host WAF settings, entered in MiB. Out-of-range values are refused at save time instead of failing the whole Caddy config load, and Caddy config errors now name known Coraza rejection causes. - Fix OAuth account linking (#247): the per-provider auto-link switch and
OAUTH_ALLOW_AUTO_LINKINGnever reached Better Auth, so every link attempt returnedaccount_not_linked. Linking now works from the profile page, which also explains why a given provider cannot be linked. Better Auth 1.7 account issuer identity is adopted and the OAuth callback URL is exposed via the API. - Harden API security boundaries.
- Add netcup DNS provider for ACME DNS-01 challenges (#258).
- Fix WAF events being dropped unless the request was actually blocked (#233).
- Fix stale UI after saves on the L4 proxy hosts page and in the GeoIP settings (#241): rapid successive L4 host creates and enable toggles only appeared after a manual browser refresh, and the GeoIP form appeared to revert to pre-save values until reload.
- Fix the analytics world map rendering as empty ocean for locally built Docker images: the staged maplibre worker could go stale across maplibre-gl upgrades because versions 6.4.1 and 6.6.0 ship worker files of identical size. Worker staging now compares file content, and Docker builds stage the worker from the container's own node_modules so a stale host copy can no longer leak into the image.
Changes
- 1b0b7c3 Fix WAF events dropped unless the request was blocked (#233)
- 28aa9e3 deps(deps-dev): bump the development-dependencies group with 5 updates (#235)
- 0139b7c deps(deps): bump the production-dependencies group with 7 updates (#236)
- 8f7936b deps(deps-dev): bump the development-dependencies group with 5 updates (#238)
- 56aeb62 deps(deps-dev): bump @types/better-sqlite3 from 7.6.13 to 9.6.0 (#240)
- 43814f8 deps(deps): bump the production-dependencies group across 1 directory with 6 updates (#239)
- 74552e4 docker: bump golang from 1.26 to 1.27 in /docker/caddy (#244)
- 41af21e deps(deps-dev): bump the development-dependencies group with 3 updates (#245)
- 15a78b5 deps(deps): bump the production-dependencies group with 5 updates (#246)
- 2467246 Add configurable default Caddy response (#241)
- 3f40c74 Harden API security boundaries
- cf78d20 Fix API hardening regressions
- 049b965 Adopt Better Auth 1.7 account issuer identity
- 0730df4 Wire OAuth account linking into Better Auth (#247)
- 5b5ff0c ci(deps): bump actions/setup-go from 6 to 7 (#248)
- ba954f3 deps(deps-dev): bump the development-dependencies group with 4 updates (#249)
- 94107a0 deps(deps): bump the production-dependencies group with 3 updates (#250)
- e35568a Add WAF request body limit settings
- 230ca11 docker: bump golang from
0ecdc2ato4013ae0in /docker/caddy (#253) - 27ac577 deps(deps-dev): bump the development-dependencies group with 4 updates (#254)
- 3db2e94 caddy: bump github.com/corazawaf/coraza-caddy/v2 (#255)
- 1051be3 deps(deps): bump the production-dependencies group with 4 updates (#256)
- cd00c05 deps(deps): bump apexcharts from 6.10.0 to 7.0.0 (#257)
- a8f5a5f Expose OAuth callback URL in API and drop stale next-auth helper
- 5956e59 Fix flaky certificate delete assertion with toHaveCount(0)
- 61b7da2 caddy: make manifest module a buildable Go package for CodeQL and tidy
- deccd59 Add netcup DNS provider for DNS-01 challenges (#258)
- 402789b Fix stale UI after saves on L4 hosts page and GeoIP settings (#241)
- 14e223a Fix stale maplibre worker staged into Docker builds
Full Changelog: v1.9.1...v1.10.0
v1.9.1
Highlights
- Fix Authentik defaults not applied when editing a proxy host (#232) —
EditHostDialognever passedauthentikDefaultsdown toAuthentikFields, so enabling Authentik Forward Auth on an existing host left Outpost Domain, Outpost Upstream and Auth Endpoint blank and the save failed on the required fields. New hosts were unaffected. - Fix the analytics world map rendering as empty ocean and the analytics page crashing on API errors —
maplibre-glv6 resolves its tile worker viaimport.meta.url, which Turbopack can't resolve, so the worker never started. The worker is now staged intopublic/maplibre/at build time. The analytics client also now checksresponse.okbefore parsing, instead of throwing and unmounting the page on a 5xx. - Add Infomaniak DNS provider for ACME DNS-01 challenges (#223).
- Document the OAuth self-registration setting in the README and
docker-compose.yml.
Changes
- 8cb09d0 Fix Authentik defaults not applied when editing a proxy host (#232)
- bad34ec Update .gitignore
- e77cbc3 Fix analytics world map and analytics page error handling
- 37e03e1 deps(deps): bump the production-dependencies group with 2 updates (#230)
- 31bc80e deps(deps-dev): bump the development-dependencies group with 3 updates (#229)
- 252f1ef ci(deps): bump actions/stale from 10 to 11 (#228)
- 99613e9 docs: expose OAuth self-registration setting
- d4680dc deps: bump dependencies, keep typescript on 5.9
- b1f6c63 deps(deps): bump the production-dependencies group with 21 updates (#225)
- dbb100e deps(deps-dev): bump the development-dependencies group with 5 updates (#224)
- 5bbeefe Delete docs directory
- 64eaaf1 Add Infomaniak DNS provider
Full Changelog: v1.9...v1.9.1
v1.9
Highlights
- Add a global Trusted Proxies setting that writes server-level
trusted_proxies/client_ip_headers/trusted_proxies_strictto the Caddy HTTP server (#222). When CPM runs behind another proxy (Pangolin/Newt, Cloudflare Tunnel, nginx/HAProxy), Caddy now resolves the real client IP instead of the immediate peer — fixing client-IP attribution in access logs, analytics, the country map, and any downstream handler. Accepts CIDRs and theprivate_rangesshorthand, supports custom client IP headers (e.g.Cf-Connecting-Ip), and can optionally default the geoblock trusted-proxy list from the same value. Configurable via Settings → Networking, the/api/v1/settings/trusted-proxiesREST endpoint, and master→slave instance sync. Empty by default, preserving current behaviour on upgrade.
Changes
- 8f44a50 Add server-level trusted_proxies / client_ip_headers setting
Full Changelog: v1.8.1...v1.9