Repository navigation
v1.12.0
A security-hardening release closing every finding from a project-wide audit,
plus a new media proxy that stops federated content from disclosing visitors'
IP addresses to remote instances.
Upgrading: no action is required for a set-up instance behind a loopback
reverse proxy. Two changes are visible: peers using RSA keys below 2048 bits
will stop federating (see Security), and a reverse proxy that does not run on
the same host now needs BAUDRATE_TRUSTED_PROXIES set.
Added
- Media proxy — remote images are served from a locally re-encoded WebP copy
via a signed/media/:sig/:encodedroute (Baudrate.Media.Proxy,
BaudrateWeb.MediaController) instead of being hotlinked. Fetches go through
the SSRF-guardedFederation.HTTPClient, are magic-byte validated, and are
re-encoded with libvips; SVG is never served. Failures fall back to a
same-origin placeholder and are negative-cached for an hour. The cache lives
atuploads/media_cache/, is bounded by a 30-day TTL and a 2 GB ceiling
evicted bySessionCleaner, and is safe to delete at any time. BAUDRATE_TRUSTED_PROXIESandBAUDRATE_REAL_IP_HEADER— runtime
configuration for reverse proxies that do not run on the application host.
Both were referenced in comments but had never been implemented, leaving
operators with a non-loopback proxy no way to widen trust without rebuilding.
Security
- Setup wizard installation-key bypass (critical) —
SetupLive's
complete_setupevent had no server-side check that theINSTALLATION_KEY
step had been passed; the gate existed only in which step was rendered. A
LiveView client can push any event regardless of the displayed step, so any
visitor reaching a freshly deployed instance could claim the admin account
without ever knowing the key. The handler now requires a verified key, and
Setup.complete_setup/2refuses to run once setup is complete. INSTALLATION_KEYis now required until setup completes — in production,
an instance with an incomplete setup and no configured key answers 503 on all
browser routes rather than serving an unguarded wizard. Enforcement is in
EnsureSetupandSetupLive.mount/3, not a boot-timeraise, so a transient
database outage cannot brick a restart. Removing the key after setup remains
supported.- Inbound replies and poll votes now honour the federation gate —
Likeand
Announcecheckedarticle_federated?/1butCreate(Note)replies and
Mastodon-style poll-vote Notes did not, so a remote actor could guess a slug
and inject a comment (plus an author notification) or a vote into an article
living only in a private or AP-disabled board. Federated poll votes are also
now refused aftercloses_at, matching the localcast_vote/3path. - No third-party image hotlinking — previously, viewing federated content
disclosed each visitor's IP address, User-Agent, and reading times to every
remote instance whose content appeared on the page. Five separate paths
emitted third-party<img>: user Markdown, RSS/bot article bodies, AP
attachment images, feed-item attachments, and remote actor avatars. All now
route through the media proxy. - Minimum RSA key size on inbound actor keys — remote actor public keys must
be RSA ≥ 2048 bits, enforced both atActorResolveringest and at signature
verification (so keys cached before this change are rejected too). A short
modulus makes an instance's signatures forgeable by a third party, who could
then impersonate that actor to this instance. Non-RSA keys are rejected
explicitly. A remote instance still using a 1024-bit key will stop
federating until it rotates — seedoc/troubleshooting.md. RealIpfails closed — an unconfiguredtrusted_proxiesno longer means
"trust every peer", and[]no longer means the opposite of what it says; the
default is now loopback only. Previously, wherever a header was configured but
the allow-list was not, any client could spoofx-forwarded-forand defeat
every per-IP rate limit (login, TOTP, AP inbox, feeds, search) while poisoning
the persistedip_addressaudit trail.- Reply-chain amplification bounded — a fabricated
inReplyTocould drive up
to 10 outbound fetches per inbound activity, roughly 600/min per hostile
domain against a target of the attacker's choosing. Now capped at 5 hops
across at most 3 distinct hosts, with a visited-URI cycle guard and rate
limits keyed on both the target host (10/min, so many hostile domains cannot
combine against one victim) and the sending domain (20/min). - Protocol-relative image sources are stripped — the Ammonia markdown
allowlist now constrainsimg[src].url_relative(PassThrough)treated
<img src="//evil.example/x.png">as relative, so it survived scheme-based
filtering and would still have hotlinked under the tightened CSP. postgrex0.22.3 → 0.22.4 — EEF-CVE-2026-66838 / GHSA-3gww-3f36-2388,
SQL injection via the:commentoption inPostgrex.stream/4. Not reachable
from this codebase, but shipped.
Changed
- CSP
img-srctightened from'self' https: data: blob:to
'self' data: blob:. No page issues a third-party subresource request any
more, so thehttps:allowance for federated avatars is no longer needed. - nginx now denies
/uploads/media_cache/directly, so cached remote images are
reachable only through the signed/media/route.
Fixed
Plug.Crypto.secure_compare/2is guarded on two binaries, so pushing the
verify_keyevent with no installation key configured — or with a malformed
payload — crashed the setup wizard with aFunctionClauseError.