Bludit Pro 3.22.0: A Production Stability Audit

Data Safety

Database read without file lock — race condition

File: bl-kernel/abstract/dbjson.class.php — constructor

Bludit stores content as JSON files on disk. When a page is saved, save() acquires an exclusive lock (LOCK_EX) before writing. When a page is read — which happens on every request — the database constructor reads the file with file() and no lock at all.

Under concurrent traffic, a read can begin while a write is mid-flight, producing a truncated or corrupted JSON document. The site breaks until the next successful write restores the file. Rare, but possible on a busy site where multiple visitors trigger saves (comments, page edits, scheduler events) while others are reading.

Fix: Acquire a shared lock before reading:

// bl-kernel/abstract/dbjson.class.php — __construct()
$fp = fopen($file, 'r');
flock($fp, LOCK_SH);
$lines = file($file);
fclose($fp);

A shared lock allows multiple concurrent reads but blocks writes until all readers finish. The write lock (LOCK_EX) blocks both reads and writes until the write completes. This is filesystem concurrency 101 — the same pattern you'd use in any flat-file application.

Developer's response: The Bludit developer classified this as a robustness edge case rather than a security issue — no attacker-controlled trigger, no confidentiality or authorization impact. I understand that reasoning. A corrupted JSON file isn't the same as an exposed password. I applied the patch anyway because I'd rather not be the administrator who discovers this edge case the hard way on a site with active comments and scheduled posts. If you maintain a quieter install, this one may not be urgent. If your site sees concurrent writes, it's cheap insurance.


Code Hygiene

These are low-severity issues that won't bring down the site but fix edge cases that could produce confusing behavior or mask real problems. I applied them because they cost nothing and eliminate noise from error logs.

Reading time inflates word count from HTML entities

File: bl-kernel/pagex.class.php — method readingTime()

Page::readingTime() calls $this->content(true), which returns sanitized HTML with entities encoded (& becomes &). The subsequent strip_tags() removes the HTML tags but leaves the entities intact. str_word_count() counts & as a word. A page with heavy formatting or special characters displays a reading time significantly higher than reality — a minor annoyance, but it undermines the feature's purpose.

Fix: Use unsanitized content for counting, then strip any remaining Markdown:

// bl-kernel/pagex.class.php — readingTime()
$text = $this->content(false); // raw, no entity encoding
$text = strip_tags($text);

Undefined index in Plugin::getValue() when a field isn't declared

File: bl-kernel/abstract/plugin.class.php — method getValue()

When getValue() looks for a key that's missing from both the plugin's stored data and its declared dbFields, the fallback return $this->dbFields[$field] doesn't check isset() first. Result: a PHP notice in the log. Not visible to visitors unless display_errors is on, but log noise hides real problems.

Fix: One isset() check:

// bl-kernel/abstract/plugin.class.php — getValue()
if (isset($this->dbFields[$field])) {
    return $this->dbFields[$field];
}
return null;

API image endpoint accepts files without MIME type verification

File: bl-plugins/api/plugin.php — method uploadImage()

The API's image upload handler moves the uploaded file to a temp directory and passes it to transformImage(), which checks only the file extension. While GD — the image processing library — usually refuses to process malformed data and the temp file is cleaned up either way, a file with a .jpg extension containing non-image payload is accepted further into the upload pipeline than it should be. The practical risk is low, but defense-in-depth says: validate before you move.

Fix: Check the MIME type with finfo_file() before accepting the upload into the temp directory.


The Critical Caveat

Every patch described above is a manual modification to the core Bludit files. If you apply these changes to your own Bludit installation, they will be overwritten the moment you update to a new version — whether that's a patch release from the developer or a fresh download of a newer build.

These are not plugins. They are not configuration changes. They are direct edits to core files. If you adopt them, document them. Keep a patch file or a version-controlled fork so you can re-apply them after an update. Better yet: if a finding matters to you, ask the developer to merge the fix upstream so the next release includes it for everyone.

I report these findings because transparency makes the ecosystem better, not because I want anyone running a manually-patched fork they're afraid to update. My patches are applied to my build. Yours should only be applied if you understand the code you're changing and have a plan for maintaining it.


Disclosure Timeline

  • 2026-05-10: Full source review of Bludit Pro 3.22.0 completed. Critical findings identified, documented, and reported to the developer via GitHub.
  • 2026-05-10 – 2026-05-15: Patches applied to my copy of BrownBear build and tested.
  • 2026-05-15: Patches running in production on all live Bludit sites.
  • As of publication: The patches described here represent my current production configuration. They will be revisited and re-applied as needed after any upstream update. The predictable preview token fix has been merged upstream and will ship in an upcoming release.
Technical Audit Summary

This transparency report documents production patches applied to the BrownBear build of Bludit Pro 3.22.0 before deployment. It is not a vulnerability disclosure — findings were reported to the developer on May 10, 2026 — but rather an accountability record for other administrators running the same software.

Last Audit: May 2026
Environment: Debian Trixie (13)
Bludit: Pro 3.22.0 (BrownBear build)
PHP: 8.5.6

Scope: This audit focused on the request lifecycle, content protection, scheduler execution, database integrity, and plugin hooks. Backend theme rendering, third-party plugin internals, and the install/upgrade process were outside scope.

2026-05-22: Initial publication. Eight patches documented: predictable preview token (critical, merged upstream), broken title tag on non-existent taxonomy pages, missing scheduler hook argument, database race condition, quick-search draft leak between user roles, inflated reading time from HTML entities, undefined plugin index, and missing MIME type verification in API image uploads. All patches are live in production.