Skip to content

Releases: Phorum/Phorum

10.0.0-rc-3 Third Release Candidate

Choose a tag to compare

@brianlmoon brianlmoon released this 19 Sep 18:34

What's Changed

Full Changelog: v10.0.0-rc-2...v10.0.0-rc-3

10.0.0-rc-2 Second Release Candidate

Choose a tag to compare

@brianlmoon brianlmoon released this 13 Sep 19:20

Fixed public/assets/vendor not shipping in the distro.

Full Changelog: v10.0.0-rc-1...v10.0.0-rc-2

10.0.0-rc-1 First Release Candidate

Choose a tag to compare

@brianlmoon brianlmoon released this 13 Sep 18:57

Phorum has been fully rewritten from the ground up — and yet, if you're running Phorum 6 today, you can upgrade in place. The database schema is unchanged and fully compatible. Same tables, same columns, same data. What's changed is everything around it.

(If you're still on Phorum 5.x, you'll need to upgrade to Phorum 6 first before moving to Phorum 10.)

In this version:

First release candidate. Almost everything here comes from an adversarial security review of the whole application, which turned up thirteen findings across authentication, permissions, and the plugin modules. Two of the fixes need action on existing installs — see Upgrade notes below.

Security

Stored XSS in BBCode links. isSafeUrl() read parse_url()'s scheme with ?? '', but parse_url() returns NULL for a scheme containing a control character, and '' was in the allowlist so relative URLs would pass. That made java<TAB>script:alert(1) acceptable, and browsers strip tab/CR/LF from a URL before parsing it. The check now normalizes the way a browser does and matches the scheme with a regex, and the caller emits the vetted string rather than the raw one.

Stored XSS via avatar upload. Avatars were validated on file extension alone and served inline with a content-sniffed type, so a .png full of markup came back as text/html in the site's own origin. Uploads now parse the real image header and require the extension to agree; serving uses an allowlist of raster types. The executable-markup scan moved into MimeDetector so the upload and serve paths can't drift apart.

Private forum content readable by anyone. MessageController::thread() and FeedController::thread() resolved read permission from the forum id in the URL but fetched messages by thread id alone. findRoot() and findByThread() now take a forum scope, so the query itself cannot return another forum's thread.

Report page leaked any message body. ReportController had no read permission check and no status check, so deleted, pending, and other users' shadow-banned posts were all reachable by guessing a message id.

Admin panel to RCE via webhooks. payload_template was rendered as unsandboxed Twig, where {{ [...]|map("system") }} executes shell commands. It is now plain placeholder substitution, which also fixes the pre-existing bug where HTML autoescaping corrupted JSON payloads.

admin_secret shipped as a working placeholder. Anyone could forge an admin session cookie against an install that never changed it. The example config now ships blank, and empty, placeholder, or too-short values are refused rather than silently used.

Session fixation enabled CSRF bypass. The CSRF token lived in a PHP session that ran without session.use_strict_mode and was never regenerated on login, so an attacker able to fix the session id knew the victim's token. Strict mode is on, the session and token rotate on every identity change, and the OAuth module no longer starts its own session with a separate parameter list.

No rate limiting on authentication. Both login forms and the two unauthenticated mail senders accepted unlimited requests. Added per-address and per-account limits in a rolling window, backed by a new login_attempts table pruned on write (this application has no cron). Limits expire on their own and never set a lasting account lock, so they can't be used to lock someone out.

Subscriptions leaked private forums. Neither /follow/{thread} nor /forum/{id}/follow checked read permission, and an email subscription kept mailing the subject of every new post. Both are gated now, and notifySubscribers() re-checks at send time so existing rows and revoked permissions stop leaking too.

Webhook SSRF. Admin-supplied URLs were unrestricted, reaching internal networks and cloud metadata endpoints. Targets are resolved and checked against private and reserved ranges both at save and at send, and redirects are no longer followed.

Account-critical changes needed no re-authentication. Changing a password or email now requires the current password, and every password change clears the stored session tokens — including the year-long remember-me token, which a password reset previously left working.

One-time tokens were exposed. Reset and confirmation tokens are stored as a sha256 digest rather than in plaintext, a reset link is exchanged for a session-held token behind a clean URL, and confirming an email no longer creates a session — that link lives 48 hours in a mailbox and in access logs.

OAuth linked to unverified local accounts. A provider identity was attached to any local account sharing the email address. With require_confirmation off, someone could register on a victim's address and be handed their account when the victim later signed in with Google or GitHub. Linking now requires the local account to have proved the address itself. Also fixes an orphaned-identity row permanently locking a provider identity out, and adds the email uniqueness check registration never had.

Quadratic BBCode quote nesting. Resolution cost grew with the square of the nesting depth and bodies aren't length-limited, so one 64 KB post cost ~185 ms of CPU on every view, with no output cache. Nesting now resolves to 20 levels and deeper tags render as literal text, bringing the same input under a millisecond.

Added

  • Security response headers on every response: X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin, and a CSP restricting base-uri, form-action, object-src and frame-ancestors. The default policy deliberately sets no script-src/style-src: the shipped templates use inline scripts, handlers and styles, so a policy covering them would need 'unsafe-inline' and would not stop XSS. Operators can supply their own via content_security_policy, and enable HSTS with hsts_max_age. (src/Core/SecurityHeaders.php)
  • Login and password-reset rate limiting, admin-configurable: a 15-minute rolling window, 15 attempts per IP, 8 per account, 5 reset mails per IP. A successful login clears that account's counter, and both the front-end and admin login forms share the limits. (src/Service/LoginThrottleService.php)
  • trusted_proxies config so visitors behind a proxy aren't all counted as one source address. (src/Core/ClientIp.php)
  • Logout requires a POST, so another site can't force it by navigating a visitor to /logout (which would also destroy their remember-me cookie). A GET shows a confirmation form instead of acting, so existing bookmarks keep working. The admin panel behaves the same way.
  • webhook_allow_private_targets config for sites that genuinely deliver to an internal endpoint.
  • 11 new translation strings, propagated across all 14 full locale files. (fr-CA and pt-PT are intentional partial regional overrides and needed no changes.)

Changed

  • Webhook payload templates are plain {{ event }} / {{ timestamp }} / {{ data.<field> }} placeholder substitution, JSON-escaped when the content type is JSON. This is no longer a template language.
  • Direct error_log() calls in SchemaPatcher, S3StorageService and WebhookDispatcher now go through an injected PSR-3 logger, defaulting to an error_log()-backed implementation. Log lines gain a level prefix, e.g. [ERROR] S3Storage: putObject failed for key phorum/7: network down. (src/Core/ErrorLogLogger.php)

Database

All schema changes are additive; no Phorum 6 column was renamed, retyped or dropped.

  • New {PREFIX}_login_attempts table backing the rate limiter.
  • New users.email_verified column (patch 0009).
  • New index on users.password_temp (patch 0008) — every /reset-password and /confirm-email request looks a user up by this column, including unauthenticated ones with a made-up token, and Phorum 6 never indexed it.

Dependencies

  • dealnews/schema-org ^0.1.0 → ^1.3
  • league/commonmark ^2.8 → ^2.10
  • aws/aws-sdk-php 3.388.9 → 3.395.0
  • psr/log ^3.0 promoted from a transitive dependency to a direct one

Upgrade notes

Two changes affect existing installs:

  1. admin_secret under 32 characters now refuses admin login. Set a longer one before upgrading. Changing it invalidates current admin sessions.

  2. Patch 0009 backfills email_verified to 0 for every existing account, so OAuth will not link to them until they confirm an address or complete a password reset. The backfill is deliberately conservative: nothing in the schema distinguishes an account that confirmed its address from one created while require_confirmation was off, and assuming "verified" would preserve exactly the hole this closes. A site that has always run with require_confirmation on can mark existing accounts verified in one statement:

    UPDATE {PREFIX}_users SET email_verified = 1 WHERE active = 1;

Any password-reset or email-confirmation link issued before the upgrade stops matching, since those tokens are now stored as a digest. They expire in an hour and 48 hours respectively; anyone caught mid-flow requests a new one.

Full changelog: v10.0.0-alpha-5...v10.0.0-rc-1

10.0.0-alpha-5

10.0.0-alpha-5 Pre-release
Pre-release

Choose a tag to compare

@brianlmoon brianlmoon released this 25 Jul 22:48

Phorum has been fully rewritten from the ground up — and yet, if you're running Phorum 6 today, you can upgrade in place. The database schema is unchanged and fully compatible. Same tables, same columns, same data. What's changed is everything around it.

(If you're still on Phorum 5.x, you'll need to upgrade to Phorum 6 first before moving to Phorum 10.)

In this version:

Added

  • Content & posting
    • Preview option when posting or editing a message, and when composing a private message — renders the submitted content as a real message card without persisting anything.
    • Autolinking of bare URLs and email addresses in message bodies (Markdown, BBCode, and the plain-text fallback).
    • Pagination for flat (non-threaded) thread reading, matching the existing forum thread-list pagination.
  • Attachments & media
    • Per-forum attachment settings (max count, max size per file, max total size, allowed extensions) and a site-wide upload on/off toggle.
    • Image previews with a click-to-enlarge lightbox for image attachments.
    • Video attachment thumbnails with inline lightbox playback (HTTP Range/seek support).
    • Per-forum "View attachments" permission bit, independent of read access.
    • S3-backed file storage module (mods/s3storage) for attachments and avatars.
    • CDN module (mods/cdn) for serving attachment/avatar links from a configurable CDN domain.
  • Moderation & trust and safety
    • Shadow banning: a shadow-banned user's posts stay visible only to themselves.
    • Configurable minimum account age for new-post auto-moderation.
    • Karma-threshold auto-moderation, based on a user's moderator-deleted-post ratio.
    • Sticky/unsticky thread moderation action.
    • Reporter identity now shown in the moderation reports queue.
    • Poster IP address shown to moderators, gated by a per-forum toggle.
    • Admin UI for granting per-user, per-forum permission overrides (the resolution logic already existed; there was no way to create one).
  • Accounts & permissions
    • Site-wide pending-registration approval queue and moderator tools (ALLOW_MODERATE_USERS), including a proper 5-state account-status selector in the admin user editor.
    • Self-service group join/leave, and a group-moderator review panel for approving/rejecting/suspending members (group moderators still can't grant Moderator status themselves).
    • Per-user language and theme override, alongside the existing site-wide defaults and per-forum theme override.
    • Per-user timezone-aware date/time display (tz_offset + is_dst).
    • "Continue Browsing: {forum}" shortcut on your own profile, linking back to whichever forum you most recently viewed.
  • Subscriptions & notifications
    • Default follow-on-post preference (email_notify) — auto-subscribes you to a thread the moment you start or reply to it.
    • Forum-wide ("follow this whole forum") subscriptions, alongside existing per-thread following.
  • Extensibility
    • Outgoing webhooks module (mods/webhooks): signed JSON or custom Twig-templated payloads on post/registration/ban/shadow-ban events.
    • OAuth login via Google and GitHub (mods/oauth).
    • Generic module support for contributing routes (mods/{name}/routes.php) and database schema (mods/{name}/mysql.sql), not just the bundled modules.
  • Six per-forum presentation/behavior toggles wired up (previously stored but inert, and with no admin control): edit_post, allow_email_notify, count_views, count_views_per_thread, float_to_top, check_duplicate.
  • Documentation: FEATURES.md, a categorized inventory of implemented capabilities with a tracked "Known Gaps" list.

Changed

  • Outbound mail (SMTP host/port/credentials) configuration now lives only in etc/phorum.php; removed from the admin panel, where
    it never actually took effect.
  • Site Name is now genuinely database-backed (previously saved but never read back). Base URL was removed from the admin panel —
    it stays config-file-only, alongside base_path.
  • "Powered by Phorum" footer text links to phorum.org.
  • RSS button reordered to appear before the Moderat pages.
  • Images in message bodies are constrained to their container width (Markdown/BBCode rendering and feed output).
  • Moderate dropdown repositions itself to stay on-sedges.

Fixed

  • users.threaded_read — the account settings chec but nothing ever read it back; it's now honored when the forum's own default is flat.
  • Sticky threads sorted to the end of the forum lisrted sort ordering).
  • ->active truthiness checks across the codebase (permissions, login, session restore, password reset, impersonation, buddy/PM
    actions) incorrectly treated pending (negative) accplaced with strict comparisons.
  • Non-functional "threaded thread list" checkboxes removed from forum/user settings (saved but never consumed by anything).
  • Empty message-footer bar no longer renders when nte links apply.
  • Attachments (including S3-backed ones) are now actually deleted when their message or thread is deleted — previously orphaned
    permanently.
  • Unstyled helper text under admin settings checkboxes.
  • Reports queue no longer silently renders broken/ed message that's since been deleted (a Twig isdefined-vs-is not null bug).
  • Forum-wide subscribers' one-click unsubscribe/boon emails previously pointed at the wrong(thread-specific) endpoint and would have silently failed; they now target whichever subscription actually matched.
  • symfony/filesystem pinned to ^7.4 so Composern requiring PHP 8.4+.

Internationalization

  • Synced all 13 fully-translated locale files with out of sync with en.php, and added a standingproject requirement to keep every locale in sync whenever English strings change.

10.0.0-alpha-4

10.0.0-alpha-4 Pre-release
Pre-release

Choose a tag to compare

@brianlmoon brianlmoon released this 18 Jul 15:05

Phorum has been fully rewritten from the ground up — and yet, if you're running Phorum 6 today, you can upgrade in place. The database schema is unchanged and fully compatible. Same tables, same columns, same data. What's changed is everything around it.

(If you're still on Phorum 5.x, you'll need to upgrade to Phorum 6 first before moving to Phorum 10.)

In this version:

New features

  • RSS, Atom, and JSON Feed subscriptions — every forum, and every thread, now has a feed, alongside a site-wide "recent posts" feed. Available in three formats (.rss, .atom, .json) so readers can pick whatever their feed app supports, with an admin on/off switch and proper <link rel="alternate"> autodiscovery tags for feed readers to pick up automatically.
  • Icons throughout the interface — the navigation bar, forum and folder listings, thread list, post actions (Reply, Edit, Delete, Follow), and announcements all now carry icons, making the UI easier to scan at a glance.
  • A cleaner moderator toolbar — moderator actions (Review Queue, Reported Content, Close, Move, Merge, Delete) are now tucked behind a single "Moderate" dropdown instead of a wall of same-weight buttons sitting next to Reply and Follow.
  • Admins can now pick up schema patches without a full version bump — visiting /upgrade?force=1 while logged in re-runs the upgrade/patch process on an already-installed site, for cases where a patch ships between releases.

Fixes

  • Editing a forum in the admin panel no longer silently turns a folder back into a regular forum on save.
  • Upgrading from Phorum 6 no longer hits a MySQL syntax error partway through the schema check.
  • A partially-applied schema patch (e.g. from an interrupted upgrade) no longer blocks every later patch from applying — already-applied changes are now detected and skipped instead of aborting the whole run.
  • Sites upgrading from Phorum 6 now get the pm_new_count column they were missing, backfilled from existing unread private messages instead of starting everyone at zero.

Polish

  • Font sizes across the Emerald theme (and everything that builds on it) are now driven by a shared, named type scale instead of scattered one-off values — more consistent typography sitewide.
  • Assorted sizing and spacing refinements: thread author byline, forum descriptions, announcement subject text and spacing, RSS button placement and styling.

10.0.0-alpha-3

10.0.0-alpha-3 Pre-release
Pre-release

Choose a tag to compare

@brianlmoon brianlmoon released this 16 Jul 02:45

Phorum has been fully rewritten from the ground up — and yet, if you're running Phorum 6 today, you can upgrade in place. The database schema is unchanged and fully compatible. Same tables, same columns, same data. What's changed is everything around it.

(If you're still on Phorum 5.x, you'll need to upgrade to Phorum 6 first before moving to Phorum 10.)

In this version:

Added

  • In-place upgrade from an existing Phorum 6 database. A new /upgrade flow detects a Phorum 6 database (identified by its own internal_version setting) and brings it up to Phorum 10's schema — adding new tables and columns without touching anything Phorum 6 already created. No data is changed, deleted, or converted.
  • Forced password change. Admins can flag a user account to require a new password before it can do anything else; the user is redirected to a dedicated change-password page until they comply.
  • Per-forum edit permission + site-wide edit time limit. Editing your own posts is now gated by a real permission bit (configurable per forum/group, alongside the existing reply/post/moderate bits) and an optional site-wide time limit after which a post can no longer be edited.
  • Thread merge. Moderators can fold one thread into another as a single-form action — the merged thread's posts, permalinks, and search index entries are preserved.

Changed

  • Custom fields storage now matches real Phorum 6. Field values live in one user_custom_fields table (previously two invented tables), and field definitions are stored as a settings blob rather than a separate config table — mirroring how Phorum 6 itself has always done it.
    • Note: forum-level and message-level custom fields — which never existed in real Phorum 6 — are no longer supported, only user-profile fields. If your alpha-2 install has data in the old tables, it is not migrated or read by this release; it will need to be handled manually if it needs to be preserved.

Fixed

  • /upgrade could stay reachable and mutate the database on an already-installed site; it's now guarded the same way the installer is.
  • The upgrade flow didn't mark the site as installed on completion, so it would loop back to /upgrade forever after a successful upgrade.
  • The post-password-change redirect could double up base_path (when configured) and dropped the original query string.
  • Two admins editing different custom fields at the same time could silently overwrite one another's change; saves are now conflict-safe.
  • Merging a thread didn't carry over the target thread's open/closed state, and could leave already-read posts marked unread after a cross-forum merge.
  • A flagged admin account could use the entire admin panel indefinitely without ever being forced to change its password.
  • A database upsert helper used by several mappers could silently swallow a genuine constraint-violation error instead of surfacing it.

Improved

  • Reduced duplicated logic around URL building, open-redirect validation, schema migration, and thread moderation.
  • Extended translation coverage to the installer/upgrade screens and several previously English-only validation messages.
  • Reduced database queries on thread pages (permission checks are now resolved once per request instead of once per message) and on the upgrade confirmation screen.

Dependencies

  • Bumped league/commonmark to 2.8.3.

10.0.0-alpha-2

10.0.0-alpha-2 Pre-release
Pre-release

Choose a tag to compare

@brianlmoon brianlmoon released this 14 Jul 01:50

Phorum has been fully rewritten from the ground up — and yet, if you're running Phorum 6 today, you can upgrade in place. The database schema is unchanged and fully compatible. Same tables, same columns, same data. What's changed is everything around it.

(If you're still on Phorum 5.x, you'll need to upgrade to Phorum 6 first before moving to Phorum 10.)

In this version:

Admin

  • Added Phorum branding (logo) to the admin panel
  • Admin now shows the running Phorum version
  • Forums/folders can now be reordered from the admin
  • Added per-forum status controls: read-only, admin-only, disabled, etc.
  • Forum admin restructured into a nested tree for clarity
  • Left nav is now hidden by default in the admin
  • Fixed a mislabeled field when editing a forum
  • Fixed a regression that broke viewing a single folder
  • Fixed incorrect folder headings in the forum list
  • Fixed a routing bug that caused a fatal error

Forums & Messages

  • Forum descriptions now support HTML
  • Various forum and message list layout tweaks
  • Announcements restored to their own table (matching the classic Phorum 6 look)
  • Removed a redundant heading in the title bar

UI / Styling

  • General font size and color tweaks across the theme
  • Normalized and consolidated the theme's font sizes onto a consistent rem-based scale

Dependencies & Infrastructure

  • Updated Twig and PHPMailer
  • Fixed PHP 8.5 deprecation warnings
  • Added code coverage via pcov
  • Added GitHub Actions CI
  • Bumped actions/checkout (v4 → v7) and actions/cache (v4 → v6)

10.0.0-alpha-1

10.0.0-alpha-1 Pre-release
Pre-release

Choose a tag to compare

@brianlmoon brianlmoon released this 11 Jul 19:50

Phorum 10 — A Complete Rewrite, Built for Modern PHP

A note on the version number: Phorum 10 is a ground-up rewrite, schema-compatible with earlier versions but otherwise entirely new code. The version number reflects that break, not a count of ten sequential releases.

Phorum has been fully rewritten from the ground up — and yet, if you're running Phorum 6 today, you can upgrade in place. The database schema is unchanged and fully compatible. Same tables, same columns, same data. What's changed is everything around it.

(If you're still on Phorum 5.x, you'll need to upgrade to Phorum 6 first before moving to Phorum 10.)

A modern foundation

The old procedural, PHP4-era codebase is gone. Phorum now runs on:

  • PHP 8.3+, with a proper class-based architecture — controllers, services, and data mappers replace the global $PHORUM superglobal and bare procedural functions
  • Composer for dependency management and autoloading (PSR-4)
  • Twig 3 templates instead of Phorum's custom template syntax
  • PHPUnit, with nearly 700 tests covering the application

Existing plugins aren't abandoned: the classic phorum_api_hook() plugin hook system is preserved (via a compatibility wrapper), and the BBCode module has already been ported over.

New capabilities

  • Markdown support, alongside BBCode — posts can use either, per-message
  • A real admin panel, including a moderation audit log, ban management, custom fields, and user groups with per-forum permissions
  • Automatic redirects from old URLs — legacy read.php?1,2,3-style links 301-redirect to the new clean URL structure, so old bookmarks and search rankings survive the migration
  • Flood control and edit-history diffs (line-level, so you can see exactly what changed in an edited post)
  • A web-based installer

Built for search engines and social sharing

  • Canonical URLs and rel=prev/rel=next pagination hints on every listing page
  • OpenGraph tags on forums and threads, so links posted to Slack, Discord, or social media get proper title/description previews
  • Schema.org structured data (JSON-LD) on forum and thread pages
  • Smart pagination that scales to forums with tens of thousands of pages, instead of rendering every page number

Design

  • All six original themes — Emerald, Topaz, Sapphire, Ruby, Diamond, Amethyst — carried forward, rebuilt with modern CSS (custom properties, no more table-layout hacks)
  • Fully responsive: table-based lists collapse into touch-friendly cards on mobile
  • Accessibility built in: skip links, ARIA labels, visible focus rings, proper heading structure

Speaks your language

16 locales ship out of the box (Arabic, Bengali, Chinese Simplified/Traditional, Dutch, French/Canadian French, German, Hindi, Indonesian, Portuguese/Brazilian Portuguese, Russian, Spanish, Urdu), with English as the reference translation and a fallback chain so partial translations never show blank strings.

Security

CSRF protection is enforced on every form submission, and output is auto-escaped by Twig by default — the two most common ways old-school forum software gets exploited.