Releases: BxnnyG/matrixctrl
Release list
MatrixCtrl 0.1.35
Changed
-
The pod is now Guaranteed QoS, so the panel is no longer among the first
processes killed when the node runs out of memory.requestsnow equallimitsfor
both containers, which movesoom_score_adjfrom 997 to -997.This came out of measuring 0.1.34's trigger instead of assuming it. MatrixCtrl was
killed while holding 14 MB against a 512Mi limit: the kernel logged
constraint=CONSTRAINT_NONE, global_oom, meaning the whole node was exhausted — by
an unrelated 18 GB process — not the container. There was no memory problem to fix.
What the log did show is that kubelet derives the kill order from the memory
request, so a 128Mi request against a 512Mi limit put the admin panel near the
front of the queue.It creates no memory; it changes who is killed instead. The reservation is about 2%
of the node, and the request/limit gap was buying nothing at 81Mi steady state.Note that QoS is a pod property: both containers need
requests == limitsor
the class stays Burstable. -
Memory and CPU limits are unchanged. Lowering the 512Mi ceiling to match real
usage was rejected — the peak during a Helm render has never been measured, and that
would trade a rare collateral kill for a self-inflicted one.
MatrixCtrl 0.1.34
Fixed
- A slow-starting MAS no longer locks you out of your own panel. The container was
OOMKilled and restarted before MAS was serving; discovery returned a proxy error
page, the single OIDC init attempt failed, and the panel showed a username/password
box for eleven hours while MAS was healthy seconds later. OIDC now retries in the
background with capped backoff and never gives up on its own — giving up after N
attempts is the same lockout on a delay. - The retry rebuilds from the effective startup config, not from the database.
ReusingReloadOIDCwould have been a silent no-op here: it reads the DB, startup
prefers env, and this deployment is env-configured. The logs would have claimed a
recovery was running while nothing changed. - The connect-OIDC setup flow wins over an in-flight retry — a person acting
deliberately outranks a background loop.
Added
/api/v1/auth/oidc/availablereportsretryingalongsideenabled. "This install
uses local login" and "Matrix login exists but its issuer is down" look identical on
screen and lead to opposite actions. The login page shows the distinction and polls
until it can switch back on its own, with no reload.
Security
- A transient IdP failure used to re-open the local password login on a public URL
indefinitely, because bootstrap login is only disabled while OIDC is configured.
That window now closes by itself. The endpoint reports only that a retry is running,
never the discovery error — it is unauthenticated by necessity.
MatrixCtrl 0.1.33
Added
- Release notes for the version you are about to install, shown on the upgrade
page beside the button that starts it. Not decoration: 26.8.0's notes say "Upgrade
Element Web to v1.12.25" and "Upgrade Synapse to v1.158.0" — exactly the upgrades
the pinned image tags were silently preventing. The screen now says both what the
version brings and, from the pin warning, what a pin will stop it bringing. - "Upgrade auf X" arrives with X selected. The version travels from the list as a
search parameter instead of being picked twice. - Notes are cached per version and the cache is bounded — published notes do not
change, and GitHub's unauthenticated limit is 60 requests an hour. - "Could not be fetched" and "no notes published" are different messages, because
they lead to different conclusions.
Security
- The version becomes a URL path segment and is validated against a strict pattern,
refused rather than escaped. - Rendered markdown links only follow
http(s). Ajavascript:URL in third-party
text must not become clickable in an admin panel.
Fixed
- The documented local deploy shipped the wrong image. The chart's committed
default isimage.tag: "latest"— CI rewrites it to the exact version only when it
packages a released chart — so a deploy from the working tree rendered:latest
and ran whatever stale build containerd held. The first attempt at this release
deployed0.1.32whilehelm listsaidAPP VERSION 0.1.33androllout status
said success. PROZESS §4 now passes
--set image.tagand ends the read-back with the container's own startup line,
which reads the artefact rather than a declaration about it.
MatrixCtrl 0.1.32
Added
- The upgrade log says what the rollout is waiting for. It used to be a clock:
Waiting for Helm rollout… (30s elapsed), fifteen times, while one pod sat in
Init:CrashLoopBackOffwith the explanation in its own logs. Now each tick names
the failing pod, its container, the reason, and the container's error text. - Pods that are merely starting are counted rather than narrated, and an unchanged
diagnosis is not repeated — the useful line must not become wallpaper. - Image tags pinned behind the chart are reported before the rollout starts. On
the instance this was built for, four components were behind: MAS 1.15.0 against
1.22.0, Synapse v1.151 against v1.158, Element Web v1.12.14 against v1.12.25,
Element Admin 0.1.11 against 0.1.12. Chart upgrades had been updating templates
while keeping old images, and nothing said so. - The MAS pin is what made the 26.8.0 upgrade fail: chart 26.8.0 writes
database.password_file, MAS 1.15 does not know the field, so it connected with no
password at all.
Deliberately not done
- Pins are reported, not fixed. Unpinning is an upgrade decision with
consequences — a seven-minor-version MAS jump with database migrations — and it
belongs to the operator. - Only tags older than the chart's are reported. Running ahead is a choice, and
anything not confidently orderable is left alone: a wrong "you are behind" costs an
upgrade nobody needed.
MatrixCtrl 0.1.31
Fixed
- MAS asked "Continue to <ULID>?" instead of "Continue to MatrixCtrl?" on the
consent screen. The generator already writesclient_name; instances registered by
an earlier version do not have it, and there was no way to add it through the
product. - Registration is now reconcilable rather than one-shot. Connecting OIDC used to
answer409 Conflictfor ever after the first time, so any field the generator
learned to write later could only reach fresh installs — every existing one was
stranded with hand-edited YAML as the only route. The setup page now reports what
the stored client is missing and offers to complete it. - The reconcile never regenerates the client ID or secret, never overwrites a value
that is already set, and refuses a fragment it cannot parse rather than replacing
it.
Changed
- A code comment claiming
client_namewas undocumented and might not render is
replaced by the verification: MAS 1.15's published config schema lists
ClientConfig.client_name. Config is also the only durable place for it — a
database edit does not survivemas-cli config sync.
MatrixCtrl 0.1.30
Answers six of the seven findings from an external security review. The seventh —
the ClusterRole being cluster-admin in all but name — is deliberately separate: it
is the most likely to break upgrades and needs its own verification.
Security
- The session JWT no longer travels in a URL. The OIDC callback handed it over
as/auth/callback?token=<jwt>, and chi's request logger writes the full URL —
400 of the last 400 log lines carried one, so the token was written to the
application log by the very request that delivered it. It is now a one-time code
in the URL fragment: fragments are never sent to a server, so there is nothing to
log and no Referer leak, and the code is single-use with a one-minute life, so the
copy left in browser history is spent. ?token=is accepted only on a genuine WebSocket upgrade, judged from the
request's own headers. It used to work on every route, which made any log line or
link carrying one a usable session.- A failed
crypto/randnow stops the process. It used to fall back to a
time-seeded string — and that fallback was reachable from the path that
persists the JWT secret, so a bad first boot would have written
matrixctrl-fallback-<unix-nanos>into the database as the permanent signing key,
derivable from the pod start time Kubernetes publishes. - Login throttling with per-IP and per-user counters, progressive backoff and a
lockout. Counted in Postgres, because an in-memory counter would make "restart the
pod and try again" the attack. - The container runs as non-root (65532) with a read-only root filesystem, no
capabilities and no privilege escalation. The ESS chart it manages already held
its own workloads to this standard. - The CORS wildcard is gone. The frontend is served by this same binary on the
same origin, so nothing needed it. RevokeSessionnow checks the signing method, matchingValidateTokenin the
same file.
Fixed
- The login backoff shifted without a bound: past ~62 failures the delay overflowed
to zero, so the most persistent attacker would have waited the least. Reachable,
because the counter keeps growing after a lockout expires. Found by a test. - The switch to non-root would have broken config saving on every existing install —
the config repo was owned by root from earlier versions. A one-shotchown
initContainer fixes ownership;fsGroupwas unavailable because it would also
apply to the Postgres sidecar's volume, and Postgres refuses to start when its data
directory is group-accessible.
MatrixCtrl 0.1.29
Added
- User write actions: lock, unlock, deactivate, reactivate, grant/revoke admin,
set password — each behind a confirmation that states what it actually does. - The dialogs carry the consequence, not "are you sure?", because every one of
these verbs is narrower than it sounds: locking does not end existing sessions,
unlock does not reactivate, reactivate does not unlock, and revoking admin leaves
existing admin sessions intact. An operator locking a compromised account needs to
know the attacker is still connected. - Only the actions that fit the account's actual state are offered — no "unlock" on
an account that is not locked. - Self-lockout is refused. MatrixCtrl admits only MAS admins, so locking or
deactivating yourself, or revoking your own admin, would close the door you need to
reopen it. Refused too when the acting identity cannot be resolved: not being able
to tell is not permission. ConfirmDialogmoved into the shared primitives from the one route that had it
inline, and now closes on Escape.
Security
- Deactivation never erases. MAS defaults to asking the homeserver to GDPR-erase
the account; MatrixCtrl always sendsskip_erase: trueand says so in the dialog. - Passwords cannot reach the audit table — the audit middleware records no request
bodies. Because of that the endpoints are verb-in-path (/grant-admin,
/revoke-admin), so the trail says which way the change went without logging
anything that must not be logged.
MatrixCtrl 0.1.28
Added
- Phase 2 starts: a user list. Accounts from the Matrix Authentication Service,
searchable, filterable by state, with cursor paging. Until now the answer to "show
me the users" was "go use element-admin". - Locked and deactivated stay distinct — separate timestamps, separate states,
separate wording. Locked is reversible and usually temporary; deactivated is the
account being gone, and an operator deciding what to do needs to know which. - The page states that it reads MAS, which is authoritative for accounts under
MSC3861, rather than implying it lists every user Synapse has ever seen. - Bootstrap mode explains that the feature needs MAS credentials instead of showing
an empty list that reads as "no users".
Changed
- MAS admin access moved out of
internal/authintointernal/mas, shared with the
login path. The admin token is now cached with its lifetime instead of minted per
call — the old behaviour doubled the request count for every page — and a401
drops the cache and retries once, so a rotated secret costs one retry rather than
every request until the process restarts.
Not in this release
- Writes: lock, deactivate, set-admin, set-password. Each is destructive in a
different way and needs confirmation plus audit entries.
MatrixCtrl 0.1.27
Added
- Calls / RTC can now check the ports from outside, on an explicit click. E19
recorded inbound reachability as a permanent unknown — true from inside, and it
quietly implied nothing could be done. One request to an outside vantage point
answered in seconds what three days of inside-out measurement could not. - A control decides whether the result is believable: a port known to be open on
an unrelated host. A blocked or broken checker reports everything as closed, and
acting on that means reconfiguring a router that was already correct. Without the
control, every result isunknownand the action says to change nothing. - The result names the distinction that cost three days: a port forward (DNAT) is
not the same as a firewall rule allowing the port. - Untestable UDP ports are counted and stated rather than dropped — the most
important port on an RTC deployment is UDP, and free checkers speak TCP.
Privacy
- This is the only code in MatrixCtrl that leaves the cluster. It is
POST, never
runs on a page load or a timer, names both third-party hosts in the UI before the
click, and stores nothing.
MatrixCtrl 0.1.26
Added
- Fields changed by hand are now visible. E21 checks the patches a hook
declares; it could not see an edit no hook knows about — which is the case P1-11
was opened for, where an Ingress carriedingressClassName: disabledapplied by
hand and Helm's three-way merge preserved it through every upgrade in silence. - The mechanism is
metadata.managedFields: the API server records which manager
set which field, so this is read rather than inferred. No manifest rendering, no
curated list of fields to watch — a curated list only ever finds what someone
already thought of. - Two levels, because they are two statements: a hand-edit no hook maintains
will never be restored by anything and is loud; one a hook maintains means someone
bypassed the product and is quiet. - Metadata-only listing, so the scan costs kilobytes rather than most of a megabyte
per poll. Ownership lives in metadata; the spec is never fetched.
Fixed
kubectl rollout restart'srestartedAtstamp and ESS's ownmatrix-toolsare
not reported. On the production cluster they were three of eight findings — enough
noise to teach an operator to skim past the two that mattered.