πŸš€ Flatboard 5.8.13 "POLARIS" β€” Release announcement

Fred Fred Β·05 September 2026 Γ  17:36Β·11 min readΒ·27Β· 0 comment

Flatboard Release

Released: September 5, 2026 Β· Covers everything shipped since 5.8.0 (releases 5.8.1 β†’ 5.8.13, all under POLARIS)

This round is smaller than 5.8.0 but heavier on security: two full permission and data-exposure audits, a two-factor authentication hardening pass, a fix for outgoing mail that never checked who it was really talking to, and a new translation-coverage tool that tracked down and fixed around 180 broken locale keys across core and plugins. It also fixes outbound webhooks, which had been fully built since they were introduced but had never actually fired for real forum activity, and gives backup creation a real progress bar instead of an indefinite spinner.


πŸ” Security fixes: update to 5.8.12 regardless of which 5.8.x version you're on

Theme cache endpoints had no permission check at all (5.8.5)

/admin/themes/cache/clear and /admin/themes/cache/stats were the only two actions in ThemeController missing a requireAdmin() call. Any anonymous visitor could purge the whole theme/plugin/asset cache on demand (a cheap way to force a full rebuild on the next page load) and read cache statistics. Both now require admin.

A plugin's admin page could be reached with no permission check (5.8.5)

The generic route Flatboard uses to render a plugin's own admin page only enforced a permission when the plugin explicitly opted in, and most didn't. Several packaged plugins (FlatHome, EasyMDE, TUIEditor, Logger) had admin views reachable by anyone who knew the URL. Some genuinely exposed group, active status), another exposed audit-log entries, a third listed every translation file on the install. Nothing destructive was reachable this way, but real data was. All affected plugins now require the same permission their normal controller already does.

nginx.conf served raw PHP source for any file it didn't recognize (5.8.5)

The sample nginx config only routed two entry points to PHP-FPM. Anything else fell through to a static-file handler that returned .php files as plain text instead of running or blocking them, including plugin config files with live API keys. Apache installs weren't affected (its handler runs any .php file rather than serving it raw), which is why this went unnoticed for a while. A catch-all deny rule closes it now.

You could delete your own reply past the time limit, just not through the API (5.8.7)

The reply/thread page's own delete button had its own permission check, and that check never enforced post.delete_time_limit. The API endpoint for the same action did enforce it. Both paths now go through the same permission service.

RSS/Atom feeds could leak a logged-in session's view into the public cache (5.8.7)

Feed content runs through the same content-processing hooks as normal pages, some of which render differently per viewer. Feed output is cached for 15 minutes under a key that doesn't vary by session, so if a staff member or a post's own author simply loaded /rss in their browser, their personalized view of that content could get baked into the shared cache and served to anonymous visitors for up to 15 minutes. Feed rendering now explicitly tells plugins to render as a logged-out visitor.

Two-factor authentication secrets were stored in plain text (5.8.8)

Found while comparing Flatboard's auth code against another project's. Anyone with read access to the database, a backup or a leaked SQLite file, could clone a user's 2FA and generate valid codes without ever touching their phone. Secrets are now encrypted at rest, transparently, with no migration step for existing accounts.

A pre-moderation queue could bypass category posting restrictions (5.8.8)

PostController::store() checked post_groups after triggering the pre-moderation hook instead of before, and the approval step never re-checked whether the original author was still allowed to post in that category. Both gaps are closed.

Outgoing SMTP mail never checked the server's certificate, including password-reset emails (5.8.12)

By default, sending mail over SMTP accepted any TLS certificate, valid or not, with no admin UI, no doc, and no installer setting to know the option even existed. A network attacker able to intercept or redirect that connection could read every outgoing email undetected, tokens for password resets and account verification included. New installs now default to checking the certificate; existing ones keep working as before but get a dashboard warning until the setting is turned on. Verified against a real local SMTP+TLS server: enabling the check correctly rejects an untrusted certificate, and the previous behavior still delivers a complete email either way.


✨ What actually got fixed or added

Backup creation now shows real step-by-step progress (5.8.13)

Creating a backup β€” selective, or the full "complete archive" β€” used to show nothing but a spinner for however long the ZIP took to build, with no way to tell if it was still working or stuck. Both now report real progress through a new step-based backend (one short request per category β€” config, data, uploads, plugins, themes, and so on β€” instead of one giant blocking request), with a percentage bar and a live checklist of what's currently running, done, or failed. The same step-tracking component now also drives the local-update wizard and StorageMigrator's storage-migration wizard, replacing several independent hand-rolled copies of the same progress-bar code: about 9.9 KB of duplicated, inline JavaScript and CSS scattered across six admin pages, replaced by one shared file (~9 KB) the browser downloads once and reuses everywhere it's needed instead of re-fetching a slightly different copy of the same logic on every page.

Outbound webhooks fire for real events now (5.8.10)

The /admin/webhooks config page (queueing, retries, HMAC signing, delivery history) had been fully built but never wired to an actual event. Only the "Test" button ever sent anything. The 12 supported event types were each fired as an internal plugin hook, but never handed to the webhook service, across 18 call sites in five controllers. All 18 now call it. User-related payloads strip password hashes and 2FA secrets before leaving the server, since a webhook payload, unlike a plugin hook, actually leaves the server. As of 5.8.12, outbound webhooks are a Pro-edition feature; the inbound receiver (POST /api/webhooks) stays available on Community as a developer extension point.

A new i18n:audit command hunts down broken translation keys (5.8.8, refined 5.8.9)

It scans every Translator::trans() / __() / window.__() call across core, themes and plugins, compares it against the actual language files, and reports missing keys, unused keys, and structural drift between locales. An admin page at /admin/i18n-audit shows the same report. Running it found and fixed around 180 broken lookups: truncated keys, wrong domains, keys that were never defined anywhere, and the always-dead Translator::trans() ?: 'fallback' pattern (the function never returns an empty string, so that fallback text never once showed). It touched the reports page, category and group management, upload errors, CSRF and rate-limit messages, attachment errors, and more, in all six locales.

Deleting a discussion's first post used to leave a ghost thread behind (5.8.7)

Reported on the community forum (#193). The only "delete discussion" control lived in the first post's menu. A moderator, or an author past the time limit, would naturally reach for the ordinary "delete post" action instead, and that left the discussion sitting in the database with zero posts and no remaining way to remove it. Deleting a first post now runs the same cleanup as deleting the discussion: reactions, notifications, reports, tags, category counters, everything. Fixed across the web UI, the API, and the moderation bulk-delete tool.

Deleting a user account behaved differently depending on your storage backend (5.8.7)

On SQLite, deleting an account that had ever posted crashed outright on a foreign-key violation, because nothing reassigned their discussions and posts first. JSON storage has no such enforcement, so the delete silently "succeeded" while leaving tokens, drafts, mentions, badges and a dozen other tables pointing at a user who no longer existed. Both backends now route through a real system placeholder account (deactivated, unusable password) and cascade every dependent table consistently.

2FA gained backup codes (5.8.8)

Losing your authenticator device used to lock you out permanently. The security docs described backup codes as if they already existed, but there was no generator, no storage and no verification behind them. Enabling 2FA now issues 10 single-use codes, hashed at rest, regenerable at any time behind a fresh TOTP check, with a "use a backup code instead" option on the login page.

Every place a deleted account's name appeared still linked to their profile (5.8.8)

Posts, discussion lists, hover cards and the ban menu all linked to /u/Utilisateur%20supprimΓ©, a page that can't resolve to anything. The link is gone everywhere that name shows up. The same audit found the "newest member" sidebar widget could list the deleted-account placeholder as the forum's newest member.

A cluster of private-messaging settings had no effect at all, no matter what you configured (5.8.12)

The blocked-users list didn't actually block anyone. Max message length, max subject length, max recipients, max messages per user, the date-format preference, the typing-indicator toggle: every one of these read the config under a key name the settings form never actually saves under, so each was permanently stuck on its hardcoded fallback. Automatic message deletion after N days, fixed the same way, was the first one found; a follow-up sweep turned up nine more. All now read the real, saved value.


🧹 Smaller fixes worth knowing about

  • The admin Extensions list got a calmer visual redesign, down from seven saturated colors per plugin card (5.8.7).
  • The admin sidebar's "Extensions" section is now collapsible, with a count badge, for installs running a lot of active plugins (5.8.11).
  • @mention autocomplete no longer fires (and errors) on a bare @ before you've typed anything (5.8.6).
  • The sidebar-collapse flash on page navigation, expanded for a moment before collapsing back down, is gone (5.8.6).
  • Private messaging: the compose page loads the right editor now, a confusing "Public view" button is gone, and dashboard warnings stay inline instead of popping up as toasts (5.8.7).
  • EasyMDE's underlying editor library bumped to 2.21.0 (a checklist toolbar button, a list-switching spacing fix), and a couple of dead, unused files that had been shipping in every archive for no reason are gone (5.8.12).
  • Admin pages no longer spam the browser console with startup debug logs (BASE_URL detection, internal self-tests) on every page load; they only show up with debug mode turned on (5.8.13).
  • Plugin developers: a plugin's admin or frontend view could silently receive the wrong data (or crash) if it named one of its own template variables config β€” an internal core variable of the same name was quietly taking priority. Fixed for good, any plugin can use $config as its own variable name now (5.8.13).
  • Several sections of FlatSEO (Pro) β€” audit, redirections, per-page meta, rank tracker β€” lost all of their JavaScript on French installs: 18 places had a translated string with an apostrophe break the whole enclosing script, silently, so the relevant buttons and filters just did nothing when clicked. Fixed (5.8.13).
  • The shared toast-notification system could silently drop a link when converting an alert box into a toast β€” plugin developers, if your alert has a link in it, it now survives the conversion (5.8.13).
  • Fixing the above briefly broke something else the same day: the "replying to [post]" chip on the discussion reply form could vanish and get swept into an empty pop-up notification, breaking the reply-to-a-specific-post feature. Caught from a user report and fixed the same day (5.8.13).
  • The IPB and Bootswatch themes get Polish translations for the first time β€” the icon legend, board stats and admin panel menu for IPB, and the entire theme-picker page for Bootswatch (5.8.13).
  • The translation-coverage audit (admin/i18n-audit) was flagging 42 keys as untranslated everywhere when themes (premium, Horizon, IPB, ClassicForum, Bootswatch) were actually providing them correctly β€” the audit didn't know themes can supply their own translations for core pages. Fixed; it also caught the one real gap this was hiding, the Polish translations above (5.8.13).
  • Hovering over a user's avatar or @mention could sometimes throw a JS error and stop the hover-card tooltip from working anywhere else on the page for the rest of that visit. Fixed (5.8.13).

⬆️ Upgrading

Update from Admin β†’ Tools β†’ Updates. SQLite installs migrate automatically on first request; JSON installs need nothing. Treat this as a security update rather than a routine one, whichever 5.8.x version you're coming from: the theme-cache and plugin-admin-bypass issues (fixed in 5.8.5) don't need any special conditions to exploit, and the SMTP certificate check (fixed here, in 5.8.12) affects every version before it. After updating, if you use SMTP, open Admin β†’ Settings β†’ Email and turn on "Verify SSL/TLS certificate" unless your mail server uses a self-signed certificate you specifically trust.

Full details in the CHANGELOG.

Thanks to everyone who reported issues and requested features on the forum. Several items in this release (#183, #188, #189, #190, #191, #193) came straight from your feedback. πŸ™

Share this article:

Fred

πŸ‘¨β€πŸ’» Flatboard Founder πŸ”§ Flatboard Core Developer.
Full-Stack Web Developer
Expert in Portable and Interoperable Solutions (PHP/JSON)

Member since December 2025

0 comment

No comments yet. Be the first!

Log in to leave a comment.