Repository files navigation

Secure Blog CMS (SQL-Free, File-Based)

Secure Blog CMS is a security-first PHP blogging platform that stores content in JSON files instead of a database. It includes a full admin UI, comment moderation, image uploads, and a Resilience Center for static exports and IPFS pinning.

Features

Publishing

  • File-based storage (no SQL/database required)
  • Drafts and published posts, slugs, excerpts, and pagination
  • Optional search, private posts, and password-protected posts
  • RSS feed generation
  • Image uploads with server-side security checks

Admin and Users

  • Admin dashboard for posts, comments, users, and settings
  • Roles: admin, editor, author
  • Comment moderation (pending/approved/spam/trash)
  • Backups and restore from the admin UI

Resilience Center

  • Static site export (HTML + RSS) for static hosting
  • ZIP bundles for easy distribution
  • Optional auto-pinning to IPFS via Pinata
  • Export bundles stored in data/exports/

Security

  • CSRF protection on all forms (single-use tokens, no replay)
  • XSS sanitization and output escaping (DOM-based HTML purification)
  • CSP headers enabled by default, plus standard HTTP security headers
  • Rate limiting (login, comments, uploads, short URLs, post passwords)
  • Account lockout after failed login attempts
  • Session hardening with IP + User-Agent fingerprinting
  • Security event logging to data/logs/
  • Mandatory SHA-256 checksums on upgrades; auto-upgrade disabled for safety
  • Proxy header spoofing protection (Cloudflare/X-Forwarded headers gated behind config toggle)
  • COOP/CORP security headers for cross-origin isolation
  • Permissions-Policy header to restrict browser APIs
  • HSTS with preload and includeSubDomains (respects proxy headers)
  • Comment author name sanitization (strip tags, length limit, email validation)
  • Site URL validation (scheme whitelist prevents javascript: and data: URLs)
  • Per-user daily upload rate limiting (50/day per user)
  • Post password brute-force protection (5 attempts per IP per 5 minutes)

Built-In Updater

  • Check for updates from the admin panel
  • Download and verify files with SHA-256 integrity checks
  • Automatic backup before upgrade
  • Config file never overwritten during updates
  • One-click upgrade process

Requirements

  • PHP 7.4+ (PHP 8.x recommended)
  • Web server (Apache/Nginx) or PHP built-in server
  • Write access to the data/ directory
  • Extensions (optional but recommended):
    • curl for Pinata IPFS pinning and in-app updates
    • zip for export ZIP bundles
    • dom for DOM-based HTML sanitization (fallback regex available)

Quick Start

Option A: Installer (recommended)

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Visit /install/index.php in your browser and complete the wizard.
  4. After install, keep data/installed.lock in place (re-install requires deleting it).
  5. Delete the install/ directory after installation for best security.

Option B: Manual install

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Generate an Argon2id password hash (use single quotes to avoid $ interpretation):
cat > /tmp/hashpass.php << 'EOF'<?phpecho password_hash('YourSecurePassword123!', PASSWORD_ARGON2ID) . PHP_EOL;EOF
php /tmp/hashpass.php
  1. Update includes/config.php with your credentials (use single quotes for the hash):
define('ADMIN_USERNAME', 'your_username');
define('ADMIN_PASSWORD_HASH', '$argon2id$v=19$m=65536,...'); // single quotes!
  1. Update site settings in includes/config.php or the admin UI.
  2. Open /admin.php and log in.

⚠️Important: Always use single quotes around ADMIN_PASSWORD_HASH. Argon2id hashes contain $ characters which PHP interprets as variable references inside double-quoted strings, corrupting the hash and breaking login.

Reverse Proxy / Cloudflare Setup

If your CMS is behind Cloudflare or a reverse proxy that sets X-Forwarded-For or CF-Connecting-IP headers, enable trusted proxy mode in includes/config.php:

define('TRUST_PROXY_HEADERS', true);

Warning: Only enable this when actually behind a trusted proxy. Enabling it without a proxy allows IP spoofing and session bypass.

Configuration

Site settings (recommended)

Most settings are managed in the admin UI at /admin/settings.php and stored in:

  • data/settings/site.json

These settings override defaults from includes/config.php.

hCaptcha (comments)

Comments can require hCaptcha. Configure via environment variables (recommended):

  • HCAPTCHA_SITEKEY
  • HCAPTCHA_SECRET

Or set hcaptcha_sitekey in data/settings/site.json and keep the secret in env.

Pinata (IPFS)

Configure Pinata credentials in /admin/settings.php to enable auto-pinning of exports.

Updating

In-app updater

  • Go to /admin/upgrade.php and check for updates.
  • The upgrader downloads update/manifest.json from the configured update source.
  • All file updates require SHA-256 checksum verification — no file is written without integrity verification.
  • includes/config.php is never overwritten — your credentials and settings are preserved.
  • Auto-upgrade has been disabled for security. All upgrades must be manually triggered.

Manual update

  1. Backup data/ and includes/config.php.
  2. Replace application files with the new release.
  3. Do NOT overwrite includes/config.php — preserve your existing credentials and settings.
  4. Re-check your settings and log in to confirm.

Creating a release

For maintainers, use the generate_manifest.sh script to prepare updates:

cd update/
./generate_manifest.sh 1.6.0 "Description of changes"# Then commit, tag, and push:
git add update/ && git commit -m "v1.6.0: update manifest"
git tag -a v1.6.0 -m "v1.6.0"
git push origin main --tags

Project Layout

secure-blog-cms/
admin/ Admin UI (posts, comments, users, settings, resilience, upgrade)
cli/ CLI utilities (password reset)
data/ JSON data storage (posts, users, comments, logs, backups)
includes/ Core classes (Security, Storage, Comments, Resilience, Uploads)
install/ Installation wizard (delete after install)
templates/ Public templates
update/ Update packages, manifest, and release files
index.php Public homepage
post.php Single post view
rss.php RSS feed
s.php Short URL redirect handler

Deployment Notes

Nginx (recommended)

A sample nginx config is included as nginx.conf with:

  • Pretty URL rewrites (WordPress-style /post/slug/, /category/tech/, etc.)
  • Security deny rules for data/, includes/, install/, and cli/ directories
  • Static file caching headers

CloudPanel / Varnish

When deploying behind CloudPanel with Varnish:

  1. Set TRUST_PROXY_HEADERS to true in includes/config.php
  2. Add nginx deny rules for data/, includes/, install/, and cli/ directories
  3. Ensure parent directory permissions are 755 (CloudPanel may reset to 770)
  4. Delete the install/ directory after setup

Important: Config Protection

  • includes/config.php contains your admin credentials and site settings
  • Never overwrite it during updates — the updater skips it automatically
  • includes/config.php.example is provided as a reference template
  • When deploying manually, always exclude includes/config.php from file copies
  • The ADMIN_PASSWORD_HASH must use single quotes (not double quotes) to prevent PHP from interpreting $ in Argon2id hashes

Changelog

v1.5.6 — User Management & Security (2026-07-14)

New Features:

  • User Management — Create, edit, and delete users from the admin panel
  • Password Policy — Enforced minimum 12 characters with uppercase, lowercase, digit, and special character requirements
  • Role-Based Permissions — Admin (full access), Editor (publish/edit any post, moderate comments), Author (create/edit own posts only)
  • Password Strength Meter — Visual strength indicator on user creation and edit forms
  • Edit User Modal — Change role and password with admin password confirmation required
  • Self-Demotion Protection — Admins cannot demote themselves to a lower role
  • CLI Password Resetcli/reset_password.php utility for emergency password resets when locked out; interactive mode avoids shell expansion of special characters
  • Installer Password Policy — Visual checklist enforces password requirements during installation
  • Special Character Safety — Passwords with $, !, *, etc. are properly handled throughout the system (CLI, admin UI, installer, JSON storage)

Security:

  • nginx.conf updated: added cli/ to blocked directories (both Option A and Option B)
  • cli/.htaccess denies all web access to CLI scripts

Improvements:

  • admin/users.php — No-cache headers for CSRF token freshness behind Cloudflare/Varnish
  • Password hashing uses Argon2id with tuned parameters (bcrypt fallback)

v1.5.5 — Category/Tag Management & Branding (2026-07-14)

New Features:

  • Category & Tag Deletion — Delete categories and tags with automatic cleanup of post references
  • Slug Collision Resolution — Auto-appends -2, -3, etc. when a slug already exists
  • Duplicate Prevention — Case-insensitive name matching rejects exact duplicates; slug collisions auto-resolved

Improvements:

  • No-cache headers on admin categories page for CSRF token freshness behind Cloudflare/Varnish
  • Branding updated to Digital Systems LLC / AfterPacket
  • Removed duplicate version display in public footer
  • Admin categories page now shows post count per category/tag

Bug Fixes:

  • Fixed PHP syntax error in addCategory() return statement
  • Fixed delete confirmation dialog quoting issues
  • Fixed CSRF token invalidation on category/tag management page

v1.5.4 — Install Cleanup & Update Channels (2026-07-14)

Critical Bug Fixes:

  • [CRITICAL] Admin session logout on idle — session fingerprint validation was destroying sessions when IP or User-Agent shifted between requests behind Cloudflare/Varnish proxies. Now logs a warning and updates the fingerprint instead of destroying the session.
  • [CRITICAL] Argon2id password hash corruption — hashes containing $ characters were corrupted by PHP variable interpolation in double-quoted strings, causing admin login failures. ADMIN_PASSWORD_HASH now uses single quotes.
  • [CRITICAL] Updater "Requested version not found in manifest" error — Upgrader required exact version string match. Now uses the manifest version directly and only checks that it's newer than the current version. Update cache also cleared before upgrading.

Improvements:

  • Session regenerate interval increased from 30 minutes to 4 hours (less disruption for users)
  • config.php has prominent DO NOT OVERWRITE warning header
  • Added config.php.example as install template — installer copies from example if config.php doesn't exist
  • includes/config.php removed from update manifest — updater will never overwrite it
  • Built-in updater now has real SHA-256 hashes for integrity verification
  • Added generate_manifest.sh script for maintainers to generate release manifests

v1.5.2 — Config Protection (2026-07-14)

Critical Bug Fix:

  • [CRITICAL] Password hash corruption fix (same root cause as v1.5.3, addressed in config.php template and installer)

Improvements:

  • Added config.php.example as install template
  • Installer copies from config.php.example; uses single-quote replacement for password hash
  • Removed config.php from update manifest

v1.5.1 — Pen Test Hardening (2026-07-14)

Security Fixes:

  • [MEDIUM] Rate limiting added to post password attempts (5 per IP per 5 minutes) — prevents brute-force attacks against password-protected posts.
  • [MEDIUM] Session fingerprint now hashes User-Agent with SHA-256 — strengthens session binding beyond IP-only, format: sha256(ip | sha256(user_agent)).
  • [MEDIUM] Site URL setting validation added — filter_var(FILTER_VALIDATE_URL) and scheme whitelist (http/https) prevent open redirect and XSS via malicious URL values.
  • [MEDIUM] Cross-Origin isolation headers added — Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin prevent cross-origin information leakage.
  • [MEDIUM] Comment author name sanitization — HTML tags stripped, length capped at 100 characters. Email validated when provided.
  • [MEDIUM] Per-user daily upload rate limit added (50 uploads/day per user) alongside existing per-IP hourly limit.

v1.5.0 — Security + Subfolder Install Fix (2026-07-13)

Security Fixes:

  • [HIGH] Removed CSRF token from image upload URL query string — tokens were being logged in server access logs and browser history. Now sent only via X-CSRF-Token header and POST body.
  • [HIGH] Removed data: from public CSP img-src — prevents SVG-based XSS through data:image/svg+xml URIs. Admin CSP still allows data: for TinyMCE paste compatibility.
  • [HIGH] HSTS header now respects TRUST_PROXY_HEADERS — previously only checked $_SERVER["HTTPS"], which is empty behind Cloudflare/Varnish. Sites behind proxies now correctly send HSTS.
  • [MEDIUM] Short URL redirect (s.php) changed from 301 to 302 — prevents browser cache poisoning if target changes.
  • [MEDIUM] Short URL redirect now validates resolved slug corresponds to a published post — prevents open redirect.
  • [MEDIUM] Post password hashing upgraded from bcrypt to Argon2id with fallback.
  • [MEDIUM] Removed ini_set() calls for allow_url_fopen/allow_url_include — these are PHP_INI_SYSTEM directives. Added comments for php.ini configuration.
  • [LOW] Removed debug error_log() from ImageUpload.php.
  • [LOW] Fixed Content-Disposition filename escaping in serve-image.php.

Bug Fixes:

  • [CRITICAL] All internal links now use cms_path() — fixes broken pagination, search, admin links, RSS, and comment forms in subfolder installs.
  • [CRITICAL] Fixed index.php line 469 — missing ?> closing tag caused 500 parse error on PHP 8.x.

v1.4.1 — Patch Release (2026-07-13)

Security Fixes:

  • [HIGH] Password input fields for post protection were type="text" — changed to type="password".
  • [HIGH]ENABLE_UPLOAD_MALWARE_SCAN was false by default — changed to true.
  • [MEDIUM] Session cookie secure flag now respects TRUST_PROXY_HEADERS.
  • [MEDIUM] Removed debug console.log statements from create/edit post pages.

Bug Fixes:

  • Image URL insertion: TinyMCE valid_elements now allows class and style on <img>.
  • Image paste: Enabled paste_data_images: true in TinyMCE.
  • RSS self-link: Now uses cms_path() for correct URLs in subfolder installs.

v1.4.0 — Security Hardening Release (2026-07-13)

Critical Fixes:

  • [CRITICAL] Removed Remote Code Execution vector in upgrade system — download_url no longer accepted from POST data. Upgrades use performUpgradeFromManifest() with hardcoded manifest URL. SHA-256 checksums mandatory. Auto-upgrade disabled.
  • [CRITICAL] Added credential placeholder detection — warns if REPLACE_ME_* defaults are still in place.

High Fixes:

  • [HIGH] Replaced regex XSS sanitizer with DOM-based HTML purification (DOMDocument + XPath). Regex fallback for servers without dom extension.
  • [HIGH] CSP headers enabled by default.
  • [HIGH] CSRF tokens now single-use for all forms — removed image_upload/edit_post_form reuse exception.
  • [HIGH] Session fingerprint gated behind TRUST_PROXY_HEADERS config (default: false).
  • [HIGH] Removed version disclosure header (X-SecureBlogCMS-Version) — only sent if SHOW_VERSION_HEADER is explicitly true.
  • [HIGH] Password protection enforced on public pages. Private posts hidden from listings, search, and RSS.

Medium Fixes:

  • Role validation whitelist enforced in addUser() and updateUser().
  • Password hashing unified to Argon2id across all user management.
  • Rate limiting on comment submissions (3/IP/hour).
  • Install directory .htaccess hardened.
  • CORS credentials set to false on image endpoints.
  • Debug logging reduced in upload endpoint.
  • Error reporting hardened to E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE.

Version: 1.5.6
Last Updated: 2026-07-14
Created by: Digital Systems LLC / AfterPacket Security Level: High

About

SQL-free PHP blog CMS with file-based JSON storage, full admin UI, IPFS pinning, static export, and security-first architecture

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Secure Blog CMS (SQL-Free, File-Based)

Secure Blog CMS is a security-first PHP blogging platform that stores content in JSON files instead of a database. It includes a full admin UI, comment moderation, image uploads, and a Resilience Center for static exports and IPFS pinning.

Features

Publishing

  • File-based storage (no SQL/database required)
  • Drafts and published posts, slugs, excerpts, and pagination
  • Optional search, private posts, and password-protected posts
  • RSS feed generation
  • Image uploads with server-side security checks

Admin and Users

  • Admin dashboard for posts, comments, users, and settings
  • Roles: admin, editor, author
  • Comment moderation (pending/approved/spam/trash)
  • Backups and restore from the admin UI

Resilience Center

  • Static site export (HTML + RSS) for static hosting
  • ZIP bundles for easy distribution
  • Optional auto-pinning to IPFS via Pinata
  • Export bundles stored in data/exports/

Security

  • CSRF protection on all forms (single-use tokens, no replay)
  • XSS sanitization and output escaping (DOM-based HTML purification)
  • CSP headers enabled by default, plus standard HTTP security headers
  • Rate limiting (login, comments, uploads, short URLs, post passwords)
  • Account lockout after failed login attempts
  • Session hardening with IP + User-Agent fingerprinting
  • Security event logging to data/logs/
  • Mandatory SHA-256 checksums on upgrades; auto-upgrade disabled for safety
  • Proxy header spoofing protection (Cloudflare/X-Forwarded headers gated behind config toggle)
  • COOP/CORP security headers for cross-origin isolation
  • Permissions-Policy header to restrict browser APIs
  • HSTS with preload and includeSubDomains (respects proxy headers)
  • Comment author name sanitization (strip tags, length limit, email validation)
  • Site URL validation (scheme whitelist prevents javascript: and data: URLs)
  • Per-user daily upload rate limiting (50/day per user)
  • Post password brute-force protection (5 attempts per IP per 5 minutes)

Built-In Updater

  • Check for updates from the admin panel
  • Download and verify files with SHA-256 integrity checks
  • Automatic backup before upgrade
  • Config file never overwritten during updates
  • One-click upgrade process

Requirements

  • PHP 7.4+ (PHP 8.x recommended)
  • Web server (Apache/Nginx) or PHP built-in server
  • Write access to the data/ directory
  • Extensions (optional but recommended):
    • curl for Pinata IPFS pinning and in-app updates
    • zip for export ZIP bundles
    • dom for DOM-based HTML sanitization (fallback regex available)

Quick Start

Option A: Installer (recommended)

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Visit /install/index.php in your browser and complete the wizard.
  4. After install, keep data/installed.lock in place (re-install requires deleting it).
  5. Delete the install/ directory after installation for best security.

Option B: Manual install

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Generate an Argon2id password hash (use single quotes to avoid $ interpretation):
cat > /tmp/hashpass.php << 'EOF'<?phpecho password_hash('YourSecurePassword123!', PASSWORD_ARGON2ID) . PHP_EOL;EOF
php /tmp/hashpass.php
  1. Update includes/config.php with your credentials (use single quotes for the hash):
define('ADMIN_USERNAME', 'your_username');
define('ADMIN_PASSWORD_HASH', '$argon2id$v=19$m=65536,...'); // single quotes!
  1. Update site settings in includes/config.php or the admin UI.
  2. Open /admin.php and log in.

⚠️Important: Always use single quotes around ADMIN_PASSWORD_HASH. Argon2id hashes contain $ characters which PHP interprets as variable references inside double-quoted strings, corrupting the hash and breaking login.

Reverse Proxy / Cloudflare Setup

If your CMS is behind Cloudflare or a reverse proxy that sets X-Forwarded-For or CF-Connecting-IP headers, enable trusted proxy mode in includes/config.php:

define('TRUST_PROXY_HEADERS', true);

Warning: Only enable this when actually behind a trusted proxy. Enabling it without a proxy allows IP spoofing and session bypass.

Configuration

Site settings (recommended)

Most settings are managed in the admin UI at /admin/settings.php and stored in:

  • data/settings/site.json

These settings override defaults from includes/config.php.

hCaptcha (comments)

Comments can require hCaptcha. Configure via environment variables (recommended):

  • HCAPTCHA_SITEKEY
  • HCAPTCHA_SECRET

Or set hcaptcha_sitekey in data/settings/site.json and keep the secret in env.

Pinata (IPFS)

Configure Pinata credentials in /admin/settings.php to enable auto-pinning of exports.

Updating

In-app updater

  • Go to /admin/upgrade.php and check for updates.
  • The upgrader downloads update/manifest.json from the configured update source.
  • All file updates require SHA-256 checksum verification — no file is written without integrity verification.
  • includes/config.php is never overwritten — your credentials and settings are preserved.
  • Auto-upgrade has been disabled for security. All upgrades must be manually triggered.

Manual update

  1. Backup data/ and includes/config.php.
  2. Replace application files with the new release.
  3. Do NOT overwrite includes/config.php — preserve your existing credentials and settings.
  4. Re-check your settings and log in to confirm.

Creating a release

For maintainers, use the generate_manifest.sh script to prepare updates:

cd update/
./generate_manifest.sh 1.6.0 "Description of changes"# Then commit, tag, and push:
git add update/ && git commit -m "v1.6.0: update manifest"
git tag -a v1.6.0 -m "v1.6.0"
git push origin main --tags

Project Layout

secure-blog-cms/
admin/ Admin UI (posts, comments, users, settings, resilience, upgrade)
cli/ CLI utilities (password reset)
data/ JSON data storage (posts, users, comments, logs, backups)
includes/ Core classes (Security, Storage, Comments, Resilience, Uploads)
install/ Installation wizard (delete after install)
templates/ Public templates
update/ Update packages, manifest, and release files
index.php Public homepage
post.php Single post view
rss.php RSS feed
s.php Short URL redirect handler

Deployment Notes

Nginx (recommended)

A sample nginx config is included as nginx.conf with:

  • Pretty URL rewrites (WordPress-style /post/slug/, /category/tech/, etc.)
  • Security deny rules for data/, includes/, install/, and cli/ directories
  • Static file caching headers

CloudPanel / Varnish

When deploying behind CloudPanel with Varnish:

  1. Set TRUST_PROXY_HEADERS to true in includes/config.php
  2. Add nginx deny rules for data/, includes/, install/, and cli/ directories
  3. Ensure parent directory permissions are 755 (CloudPanel may reset to 770)
  4. Delete the install/ directory after setup

Important: Config Protection

  • includes/config.php contains your admin credentials and site settings
  • Never overwrite it during updates — the updater skips it automatically
  • includes/config.php.example is provided as a reference template
  • When deploying manually, always exclude includes/config.php from file copies
  • The ADMIN_PASSWORD_HASH must use single quotes (not double quotes) to prevent PHP from interpreting $ in Argon2id hashes

Changelog

v1.5.6 — User Management & Security (2026-07-14)

New Features:

  • User Management — Create, edit, and delete users from the admin panel
  • Password Policy — Enforced minimum 12 characters with uppercase, lowercase, digit, and special character requirements
  • Role-Based Permissions — Admin (full access), Editor (publish/edit any post, moderate comments), Author (create/edit own posts only)
  • Password Strength Meter — Visual strength indicator on user creation and edit forms
  • Edit User Modal — Change role and password with admin password confirmation required
  • Self-Demotion Protection — Admins cannot demote themselves to a lower role
  • CLI Password Resetcli/reset_password.php utility for emergency password resets when locked out; interactive mode avoids shell expansion of special characters
  • Installer Password Policy — Visual checklist enforces password requirements during installation
  • Special Character Safety — Passwords with $, !, *, etc. are properly handled throughout the system (CLI, admin UI, installer, JSON storage)

Security:

  • nginx.conf updated: added cli/ to blocked directories (both Option A and Option B)
  • cli/.htaccess denies all web access to CLI scripts

Improvements:

  • admin/users.php — No-cache headers for CSRF token freshness behind Cloudflare/Varnish
  • Password hashing uses Argon2id with tuned parameters (bcrypt fallback)

v1.5.5 — Category/Tag Management & Branding (2026-07-14)

New Features:

  • Category & Tag Deletion — Delete categories and tags with automatic cleanup of post references
  • Slug Collision Resolution — Auto-appends -2, -3, etc. when a slug already exists
  • Duplicate Prevention — Case-insensitive name matching rejects exact duplicates; slug collisions auto-resolved

Improvements:

  • No-cache headers on admin categories page for CSRF token freshness behind Cloudflare/Varnish
  • Branding updated to Digital Systems LLC / AfterPacket
  • Removed duplicate version display in public footer
  • Admin categories page now shows post count per category/tag

Bug Fixes:

  • Fixed PHP syntax error in addCategory() return statement
  • Fixed delete confirmation dialog quoting issues
  • Fixed CSRF token invalidation on category/tag management page

v1.5.4 — Install Cleanup & Update Channels (2026-07-14)

Critical Bug Fixes:

  • [CRITICAL] Admin session logout on idle — session fingerprint validation was destroying sessions when IP or User-Agent shifted between requests behind Cloudflare/Varnish proxies. Now logs a warning and updates the fingerprint instead of destroying the session.
  • [CRITICAL] Argon2id password hash corruption — hashes containing $ characters were corrupted by PHP variable interpolation in double-quoted strings, causing admin login failures. ADMIN_PASSWORD_HASH now uses single quotes.
  • [CRITICAL] Updater "Requested version not found in manifest" error — Upgrader required exact version string match. Now uses the manifest version directly and only checks that it's newer than the current version. Update cache also cleared before upgrading.

Improvements:

  • Session regenerate interval increased from 30 minutes to 4 hours (less disruption for users)
  • config.php has prominent DO NOT OVERWRITE warning header
  • Added config.php.example as install template — installer copies from example if config.php doesn't exist
  • includes/config.php removed from update manifest — updater will never overwrite it
  • Built-in updater now has real SHA-256 hashes for integrity verification
  • Added generate_manifest.sh script for maintainers to generate release manifests

v1.5.2 — Config Protection (2026-07-14)

Critical Bug Fix:

  • [CRITICAL] Password hash corruption fix (same root cause as v1.5.3, addressed in config.php template and installer)

Improvements:

  • Added config.php.example as install template
  • Installer copies from config.php.example; uses single-quote replacement for password hash
  • Removed config.php from update manifest

v1.5.1 — Pen Test Hardening (2026-07-14)

Security Fixes:

  • [MEDIUM] Rate limiting added to post password attempts (5 per IP per 5 minutes) — prevents brute-force attacks against password-protected posts.
  • [MEDIUM] Session fingerprint now hashes User-Agent with SHA-256 — strengthens session binding beyond IP-only, format: sha256(ip | sha256(user_agent)).
  • [MEDIUM] Site URL setting validation added — filter_var(FILTER_VALIDATE_URL) and scheme whitelist (http/https) prevent open redirect and XSS via malicious URL values.
  • [MEDIUM] Cross-Origin isolation headers added — Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin prevent cross-origin information leakage.
  • [MEDIUM] Comment author name sanitization — HTML tags stripped, length capped at 100 characters. Email validated when provided.
  • [MEDIUM] Per-user daily upload rate limit added (50 uploads/day per user) alongside existing per-IP hourly limit.

v1.5.0 — Security + Subfolder Install Fix (2026-07-13)

Security Fixes:

  • [HIGH] Removed CSRF token from image upload URL query string — tokens were being logged in server access logs and browser history. Now sent only via X-CSRF-Token header and POST body.
  • [HIGH] Removed data: from public CSP img-src — prevents SVG-based XSS through data:image/svg+xml URIs. Admin CSP still allows data: for TinyMCE paste compatibility.
  • [HIGH] HSTS header now respects TRUST_PROXY_HEADERS — previously only checked $_SERVER["HTTPS"], which is empty behind Cloudflare/Varnish. Sites behind proxies now correctly send HSTS.
  • [MEDIUM] Short URL redirect (s.php) changed from 301 to 302 — prevents browser cache poisoning if target changes.
  • [MEDIUM] Short URL redirect now validates resolved slug corresponds to a published post — prevents open redirect.
  • [MEDIUM] Post password hashing upgraded from bcrypt to Argon2id with fallback.
  • [MEDIUM] Removed ini_set() calls for allow_url_fopen/allow_url_include — these are PHP_INI_SYSTEM directives. Added comments for php.ini configuration.
  • [LOW] Removed debug error_log() from ImageUpload.php.
  • [LOW] Fixed Content-Disposition filename escaping in serve-image.php.

Bug Fixes:

  • [CRITICAL] All internal links now use cms_path() — fixes broken pagination, search, admin links, RSS, and comment forms in subfolder installs.
  • [CRITICAL] Fixed index.php line 469 — missing ?> closing tag caused 500 parse error on PHP 8.x.

v1.4.1 — Patch Release (2026-07-13)

Security Fixes:

  • [HIGH] Password input fields for post protection were type="text" — changed to type="password".
  • [HIGH]ENABLE_UPLOAD_MALWARE_SCAN was false by default — changed to true.
  • [MEDIUM] Session cookie secure flag now respects TRUST_PROXY_HEADERS.
  • [MEDIUM] Removed debug console.log statements from create/edit post pages.

Bug Fixes:

  • Image URL insertion: TinyMCE valid_elements now allows class and style on <img>.
  • Image paste: Enabled paste_data_images: true in TinyMCE.
  • RSS self-link: Now uses cms_path() for correct URLs in subfolder installs.

v1.4.0 — Security Hardening Release (2026-07-13)

Critical Fixes:

  • [CRITICAL] Removed Remote Code Execution vector in upgrade system — download_url no longer accepted from POST data. Upgrades use performUpgradeFromManifest() with hardcoded manifest URL. SHA-256 checksums mandatory. Auto-upgrade disabled.
  • [CRITICAL] Added credential placeholder detection — warns if REPLACE_ME_* defaults are still in place.

High Fixes:

  • [HIGH] Replaced regex XSS sanitizer with DOM-based HTML purification (DOMDocument + XPath). Regex fallback for servers without dom extension.
  • [HIGH] CSP headers enabled by default.
  • [HIGH] CSRF tokens now single-use for all forms — removed image_upload/edit_post_form reuse exception.
  • [HIGH] Session fingerprint gated behind TRUST_PROXY_HEADERS config (default: false).
  • [HIGH] Removed version disclosure header (X-SecureBlogCMS-Version) — only sent if SHOW_VERSION_HEADER is explicitly true.
  • [HIGH] Password protection enforced on public pages. Private posts hidden from listings, search, and RSS.

Medium Fixes:

  • Role validation whitelist enforced in addUser() and updateUser().
  • Password hashing unified to Argon2id across all user management.
  • Rate limiting on comment submissions (3/IP/hour).
  • Install directory .htaccess hardened.
  • CORS credentials set to false on image endpoints.
  • Debug logging reduced in upload endpoint.
  • Error reporting hardened to E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE.

Version: 1.5.6
Last Updated: 2026-07-14
Created by: Digital Systems LLC / AfterPacket Security Level: High

About

SQL-free PHP blog CMS with file-based JSON storage, full admin UI, IPFS pinning, static export, and security-first architecture

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Blog CMS (SQL-Free, File-Based)

Secure Blog CMS is a security-first PHP blogging platform that stores content in JSON files instead of a database. It includes a full admin UI, comment moderation, image uploads, and a Resilience Center for static exports and IPFS pinning.

Features

Publishing

  • File-based storage (no SQL/database required)
  • Drafts and published posts, slugs, excerpts, and pagination
  • Optional search, private posts, and password-protected posts
  • RSS feed generation
  • Image uploads with server-side security checks

Admin and Users

  • Admin dashboard for posts, comments, users, and settings
  • Roles: admin, editor, author
  • Comment moderation (pending/approved/spam/trash)
  • Backups and restore from the admin UI

Resilience Center

  • Static site export (HTML + RSS) for static hosting
  • ZIP bundles for easy distribution
  • Optional auto-pinning to IPFS via Pinata
  • Export bundles stored in data/exports/

Security

  • CSRF protection on all forms (single-use tokens, no replay)
  • XSS sanitization and output escaping (DOM-based HTML purification)
  • CSP headers enabled by default, plus standard HTTP security headers
  • Rate limiting (login, comments, uploads, short URLs, post passwords)
  • Account lockout after failed login attempts
  • Session hardening with IP + User-Agent fingerprinting
  • Security event logging to data/logs/
  • Mandatory SHA-256 checksums on upgrades; auto-upgrade disabled for safety
  • Proxy header spoofing protection (Cloudflare/X-Forwarded headers gated behind config toggle)
  • COOP/CORP security headers for cross-origin isolation
  • Permissions-Policy header to restrict browser APIs
  • HSTS with preload and includeSubDomains (respects proxy headers)
  • Comment author name sanitization (strip tags, length limit, email validation)
  • Site URL validation (scheme whitelist prevents javascript: and data: URLs)
  • Per-user daily upload rate limiting (50/day per user)
  • Post password brute-force protection (5 attempts per IP per 5 minutes)

Built-In Updater

  • Check for updates from the admin panel
  • Download and verify files with SHA-256 integrity checks
  • Automatic backup before upgrade
  • Config file never overwritten during updates
  • One-click upgrade process

Requirements

  • PHP 7.4+ (PHP 8.x recommended)
  • Web server (Apache/Nginx) or PHP built-in server
  • Write access to the data/ directory
  • Extensions (optional but recommended):
    • curl for Pinata IPFS pinning and in-app updates
    • zip for export ZIP bundles
    • dom for DOM-based HTML sanitization (fallback regex available)

Quick Start

Option A: Installer (recommended)

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Visit /install/index.php in your browser and complete the wizard.
  4. After install, keep data/installed.lock in place (re-install requires deleting it).
  5. Delete the install/ directory after installation for best security.

Option B: Manual install

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Generate an Argon2id password hash (use single quotes to avoid $ interpretation):
cat > /tmp/hashpass.php << 'EOF'<?phpecho password_hash('YourSecurePassword123!', PASSWORD_ARGON2ID) . PHP_EOL;EOF
php /tmp/hashpass.php
  1. Update includes/config.php with your credentials (use single quotes for the hash):
define('ADMIN_USERNAME', 'your_username');
define('ADMIN_PASSWORD_HASH', '$argon2id$v=19$m=65536,...'); // single quotes!
  1. Update site settings in includes/config.php or the admin UI.
  2. Open /admin.php and log in.

⚠️Important: Always use single quotes around ADMIN_PASSWORD_HASH. Argon2id hashes contain $ characters which PHP interprets as variable references inside double-quoted strings, corrupting the hash and breaking login.

Reverse Proxy / Cloudflare Setup

If your CMS is behind Cloudflare or a reverse proxy that sets X-Forwarded-For or CF-Connecting-IP headers, enable trusted proxy mode in includes/config.php:

define('TRUST_PROXY_HEADERS', true);

Warning: Only enable this when actually behind a trusted proxy. Enabling it without a proxy allows IP spoofing and session bypass.

Configuration

Site settings (recommended)

Most settings are managed in the admin UI at /admin/settings.php and stored in:

  • data/settings/site.json

These settings override defaults from includes/config.php.

hCaptcha (comments)

Comments can require hCaptcha. Configure via environment variables (recommended):

  • HCAPTCHA_SITEKEY
  • HCAPTCHA_SECRET

Or set hcaptcha_sitekey in data/settings/site.json and keep the secret in env.

Pinata (IPFS)

Configure Pinata credentials in /admin/settings.php to enable auto-pinning of exports.

Updating

In-app updater

  • Go to /admin/upgrade.php and check for updates.
  • The upgrader downloads update/manifest.json from the configured update source.
  • All file updates require SHA-256 checksum verification — no file is written without integrity verification.
  • includes/config.php is never overwritten — your credentials and settings are preserved.
  • Auto-upgrade has been disabled for security. All upgrades must be manually triggered.

Manual update

  1. Backup data/ and includes/config.php.
  2. Replace application files with the new release.
  3. Do NOT overwrite includes/config.php — preserve your existing credentials and settings.
  4. Re-check your settings and log in to confirm.

Creating a release

For maintainers, use the generate_manifest.sh script to prepare updates:

cd update/
./generate_manifest.sh 1.6.0 "Description of changes"# Then commit, tag, and push:
git add update/ && git commit -m "v1.6.0: update manifest"
git tag -a v1.6.0 -m "v1.6.0"
git push origin main --tags

Project Layout

secure-blog-cms/
admin/ Admin UI (posts, comments, users, settings, resilience, upgrade)
cli/ CLI utilities (password reset)
data/ JSON data storage (posts, users, comments, logs, backups)
includes/ Core classes (Security, Storage, Comments, Resilience, Uploads)
install/ Installation wizard (delete after install)
templates/ Public templates
update/ Update packages, manifest, and release files
index.php Public homepage
post.php Single post view
rss.php RSS feed
s.php Short URL redirect handler

Deployment Notes

Nginx (recommended)

A sample nginx config is included as nginx.conf with:

  • Pretty URL rewrites (WordPress-style /post/slug/, /category/tech/, etc.)
  • Security deny rules for data/, includes/, install/, and cli/ directories
  • Static file caching headers

CloudPanel / Varnish

When deploying behind CloudPanel with Varnish:

  1. Set TRUST_PROXY_HEADERS to true in includes/config.php
  2. Add nginx deny rules for data/, includes/, install/, and cli/ directories
  3. Ensure parent directory permissions are 755 (CloudPanel may reset to 770)
  4. Delete the install/ directory after setup

Important: Config Protection

  • includes/config.php contains your admin credentials and site settings
  • Never overwrite it during updates — the updater skips it automatically
  • includes/config.php.example is provided as a reference template
  • When deploying manually, always exclude includes/config.php from file copies
  • The ADMIN_PASSWORD_HASH must use single quotes (not double quotes) to prevent PHP from interpreting $ in Argon2id hashes

Changelog

v1.5.6 — User Management & Security (2026-07-14)

New Features:

  • User Management — Create, edit, and delete users from the admin panel
  • Password Policy — Enforced minimum 12 characters with uppercase, lowercase, digit, and special character requirements
  • Role-Based Permissions — Admin (full access), Editor (publish/edit any post, moderate comments), Author (create/edit own posts only)
  • Password Strength Meter — Visual strength indicator on user creation and edit forms
  • Edit User Modal — Change role and password with admin password confirmation required
  • Self-Demotion Protection — Admins cannot demote themselves to a lower role
  • CLI Password Resetcli/reset_password.php utility for emergency password resets when locked out; interactive mode avoids shell expansion of special characters
  • Installer Password Policy — Visual checklist enforces password requirements during installation
  • Special Character Safety — Passwords with $, !, *, etc. are properly handled throughout the system (CLI, admin UI, installer, JSON storage)

Security:

  • nginx.conf updated: added cli/ to blocked directories (both Option A and Option B)
  • cli/.htaccess denies all web access to CLI scripts

Improvements:

  • admin/users.php — No-cache headers for CSRF token freshness behind Cloudflare/Varnish
  • Password hashing uses Argon2id with tuned parameters (bcrypt fallback)

v1.5.5 — Category/Tag Management & Branding (2026-07-14)

New Features:

  • Category & Tag Deletion — Delete categories and tags with automatic cleanup of post references
  • Slug Collision Resolution — Auto-appends -2, -3, etc. when a slug already exists
  • Duplicate Prevention — Case-insensitive name matching rejects exact duplicates; slug collisions auto-resolved

Improvements:

  • No-cache headers on admin categories page for CSRF token freshness behind Cloudflare/Varnish
  • Branding updated to Digital Systems LLC / AfterPacket
  • Removed duplicate version display in public footer
  • Admin categories page now shows post count per category/tag

Bug Fixes:

  • Fixed PHP syntax error in addCategory() return statement
  • Fixed delete confirmation dialog quoting issues
  • Fixed CSRF token invalidation on category/tag management page

v1.5.4 — Install Cleanup & Update Channels (2026-07-14)

Critical Bug Fixes:

  • [CRITICAL] Admin session logout on idle — session fingerprint validation was destroying sessions when IP or User-Agent shifted between requests behind Cloudflare/Varnish proxies. Now logs a warning and updates the fingerprint instead of destroying the session.
  • [CRITICAL] Argon2id password hash corruption — hashes containing $ characters were corrupted by PHP variable interpolation in double-quoted strings, causing admin login failures. ADMIN_PASSWORD_HASH now uses single quotes.
  • [CRITICAL] Updater "Requested version not found in manifest" error — Upgrader required exact version string match. Now uses the manifest version directly and only checks that it's newer than the current version. Update cache also cleared before upgrading.

Improvements:

  • Session regenerate interval increased from 30 minutes to 4 hours (less disruption for users)
  • config.php has prominent DO NOT OVERWRITE warning header
  • Added config.php.example as install template — installer copies from example if config.php doesn't exist
  • includes/config.php removed from update manifest — updater will never overwrite it
  • Built-in updater now has real SHA-256 hashes for integrity verification
  • Added generate_manifest.sh script for maintainers to generate release manifests

v1.5.2 — Config Protection (2026-07-14)

Critical Bug Fix:

  • [CRITICAL] Password hash corruption fix (same root cause as v1.5.3, addressed in config.php template and installer)

Improvements:

  • Added config.php.example as install template
  • Installer copies from config.php.example; uses single-quote replacement for password hash
  • Removed config.php from update manifest

v1.5.1 — Pen Test Hardening (2026-07-14)

Security Fixes:

  • [MEDIUM] Rate limiting added to post password attempts (5 per IP per 5 minutes) — prevents brute-force attacks against password-protected posts.
  • [MEDIUM] Session fingerprint now hashes User-Agent with SHA-256 — strengthens session binding beyond IP-only, format: sha256(ip | sha256(user_agent)).
  • [MEDIUM] Site URL setting validation added — filter_var(FILTER_VALIDATE_URL) and scheme whitelist (http/https) prevent open redirect and XSS via malicious URL values.
  • [MEDIUM] Cross-Origin isolation headers added — Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin prevent cross-origin information leakage.
  • [MEDIUM] Comment author name sanitization — HTML tags stripped, length capped at 100 characters. Email validated when provided.
  • [MEDIUM] Per-user daily upload rate limit added (50 uploads/day per user) alongside existing per-IP hourly limit.

v1.5.0 — Security + Subfolder Install Fix (2026-07-13)

Security Fixes:

  • [HIGH] Removed CSRF token from image upload URL query string — tokens were being logged in server access logs and browser history. Now sent only via X-CSRF-Token header and POST body.
  • [HIGH] Removed data: from public CSP img-src — prevents SVG-based XSS through data:image/svg+xml URIs. Admin CSP still allows data: for TinyMCE paste compatibility.
  • [HIGH] HSTS header now respects TRUST_PROXY_HEADERS — previously only checked $_SERVER["HTTPS"], which is empty behind Cloudflare/Varnish. Sites behind proxies now correctly send HSTS.
  • [MEDIUM] Short URL redirect (s.php) changed from 301 to 302 — prevents browser cache poisoning if target changes.
  • [MEDIUM] Short URL redirect now validates resolved slug corresponds to a published post — prevents open redirect.
  • [MEDIUM] Post password hashing upgraded from bcrypt to Argon2id with fallback.
  • [MEDIUM] Removed ini_set() calls for allow_url_fopen/allow_url_include — these are PHP_INI_SYSTEM directives. Added comments for php.ini configuration.
  • [LOW] Removed debug error_log() from ImageUpload.php.
  • [LOW] Fixed Content-Disposition filename escaping in serve-image.php.

Bug Fixes:

  • [CRITICAL] All internal links now use cms_path() — fixes broken pagination, search, admin links, RSS, and comment forms in subfolder installs.
  • [CRITICAL] Fixed index.php line 469 — missing ?> closing tag caused 500 parse error on PHP 8.x.

v1.4.1 — Patch Release (2026-07-13)

Security Fixes:

  • [HIGH] Password input fields for post protection were type="text" — changed to type="password".
  • [HIGH]ENABLE_UPLOAD_MALWARE_SCAN was false by default — changed to true.
  • [MEDIUM] Session cookie secure flag now respects TRUST_PROXY_HEADERS.
  • [MEDIUM] Removed debug console.log statements from create/edit post pages.

Bug Fixes:

  • Image URL insertion: TinyMCE valid_elements now allows class and style on <img>.
  • Image paste: Enabled paste_data_images: true in TinyMCE.
  • RSS self-link: Now uses cms_path() for correct URLs in subfolder installs.

v1.4.0 — Security Hardening Release (2026-07-13)

Critical Fixes:

  • [CRITICAL] Removed Remote Code Execution vector in upgrade system — download_url no longer accepted from POST data. Upgrades use performUpgradeFromManifest() with hardcoded manifest URL. SHA-256 checksums mandatory. Auto-upgrade disabled.
  • [CRITICAL] Added credential placeholder detection — warns if REPLACE_ME_* defaults are still in place.

High Fixes:

  • [HIGH] Replaced regex XSS sanitizer with DOM-based HTML purification (DOMDocument + XPath). Regex fallback for servers without dom extension.
  • [HIGH] CSP headers enabled by default.
  • [HIGH] CSRF tokens now single-use for all forms — removed image_upload/edit_post_form reuse exception.
  • [HIGH] Session fingerprint gated behind TRUST_PROXY_HEADERS config (default: false).
  • [HIGH] Removed version disclosure header (X-SecureBlogCMS-Version) — only sent if SHOW_VERSION_HEADER is explicitly true.
  • [HIGH] Password protection enforced on public pages. Private posts hidden from listings, search, and RSS.

Medium Fixes:

  • Role validation whitelist enforced in addUser() and updateUser().
  • Password hashing unified to Argon2id across all user management.
  • Rate limiting on comment submissions (3/IP/hour).
  • Install directory .htaccess hardened.
  • CORS credentials set to false on image endpoints.
  • Debug logging reduced in upload endpoint.
  • Error reporting hardened to E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE.

Version: 1.5.6
Last Updated: 2026-07-14
Created by: Digital Systems LLC / AfterPacket Security Level: High

About

SQL-free PHP blog CMS with file-based JSON storage, full admin UI, IPFS pinning, static export, and security-first architecture

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Blog CMS (SQL-Free, File-Based)

Secure Blog CMS is a security-first PHP blogging platform that stores content in JSON files instead of a database. It includes a full admin UI, comment moderation, image uploads, and a Resilience Center for static exports and IPFS pinning.

Features

Publishing

  • File-based storage (no SQL/database required)
  • Drafts and published posts, slugs, excerpts, and pagination
  • Optional search, private posts, and password-protected posts
  • RSS feed generation
  • Image uploads with server-side security checks

Admin and Users

  • Admin dashboard for posts, comments, users, and settings
  • Roles: admin, editor, author
  • Comment moderation (pending/approved/spam/trash)
  • Backups and restore from the admin UI

Resilience Center

  • Static site export (HTML + RSS) for static hosting
  • ZIP bundles for easy distribution
  • Optional auto-pinning to IPFS via Pinata
  • Export bundles stored in data/exports/

Security

  • CSRF protection on all forms (single-use tokens, no replay)
  • XSS sanitization and output escaping (DOM-based HTML purification)
  • CSP headers enabled by default, plus standard HTTP security headers
  • Rate limiting (login, comments, uploads, short URLs, post passwords)
  • Account lockout after failed login attempts
  • Session hardening with IP + User-Agent fingerprinting
  • Security event logging to data/logs/
  • Mandatory SHA-256 checksums on upgrades; auto-upgrade disabled for safety
  • Proxy header spoofing protection (Cloudflare/X-Forwarded headers gated behind config toggle)
  • COOP/CORP security headers for cross-origin isolation
  • Permissions-Policy header to restrict browser APIs
  • HSTS with preload and includeSubDomains (respects proxy headers)
  • Comment author name sanitization (strip tags, length limit, email validation)
  • Site URL validation (scheme whitelist prevents javascript: and data: URLs)
  • Per-user daily upload rate limiting (50/day per user)
  • Post password brute-force protection (5 attempts per IP per 5 minutes)

Built-In Updater

  • Check for updates from the admin panel
  • Download and verify files with SHA-256 integrity checks
  • Automatic backup before upgrade
  • Config file never overwritten during updates
  • One-click upgrade process

Requirements

  • PHP 7.4+ (PHP 8.x recommended)
  • Web server (Apache/Nginx) or PHP built-in server
  • Write access to the data/ directory
  • Extensions (optional but recommended):
    • curl for Pinata IPFS pinning and in-app updates
    • zip for export ZIP bundles
    • dom for DOM-based HTML sanitization (fallback regex available)

Quick Start

Option A: Installer (recommended)

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Visit /install/index.php in your browser and complete the wizard.
  4. After install, keep data/installed.lock in place (re-install requires deleting it).
  5. Delete the install/ directory after installation for best security.

Option B: Manual install

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Generate an Argon2id password hash (use single quotes to avoid $ interpretation):
cat > /tmp/hashpass.php << 'EOF'<?phpecho password_hash('YourSecurePassword123!', PASSWORD_ARGON2ID) . PHP_EOL;EOF
php /tmp/hashpass.php
  1. Update includes/config.php with your credentials (use single quotes for the hash):
define('ADMIN_USERNAME', 'your_username');
define('ADMIN_PASSWORD_HASH', '$argon2id$v=19$m=65536,...'); // single quotes!
  1. Update site settings in includes/config.php or the admin UI.
  2. Open /admin.php and log in.

⚠️Important: Always use single quotes around ADMIN_PASSWORD_HASH. Argon2id hashes contain $ characters which PHP interprets as variable references inside double-quoted strings, corrupting the hash and breaking login.

Reverse Proxy / Cloudflare Setup

If your CMS is behind Cloudflare or a reverse proxy that sets X-Forwarded-For or CF-Connecting-IP headers, enable trusted proxy mode in includes/config.php:

define('TRUST_PROXY_HEADERS', true);

Warning: Only enable this when actually behind a trusted proxy. Enabling it without a proxy allows IP spoofing and session bypass.

Configuration

Site settings (recommended)

Most settings are managed in the admin UI at /admin/settings.php and stored in:

  • data/settings/site.json

These settings override defaults from includes/config.php.

hCaptcha (comments)

Comments can require hCaptcha. Configure via environment variables (recommended):

  • HCAPTCHA_SITEKEY
  • HCAPTCHA_SECRET

Or set hcaptcha_sitekey in data/settings/site.json and keep the secret in env.

Pinata (IPFS)

Configure Pinata credentials in /admin/settings.php to enable auto-pinning of exports.

Updating

In-app updater

  • Go to /admin/upgrade.php and check for updates.
  • The upgrader downloads update/manifest.json from the configured update source.
  • All file updates require SHA-256 checksum verification — no file is written without integrity verification.
  • includes/config.php is never overwritten — your credentials and settings are preserved.
  • Auto-upgrade has been disabled for security. All upgrades must be manually triggered.

Manual update

  1. Backup data/ and includes/config.php.
  2. Replace application files with the new release.
  3. Do NOT overwrite includes/config.php — preserve your existing credentials and settings.
  4. Re-check your settings and log in to confirm.

Creating a release

For maintainers, use the generate_manifest.sh script to prepare updates:

cd update/
./generate_manifest.sh 1.6.0 "Description of changes"# Then commit, tag, and push:
git add update/ && git commit -m "v1.6.0: update manifest"
git tag -a v1.6.0 -m "v1.6.0"
git push origin main --tags

Project Layout

secure-blog-cms/
admin/ Admin UI (posts, comments, users, settings, resilience, upgrade)
cli/ CLI utilities (password reset)
data/ JSON data storage (posts, users, comments, logs, backups)
includes/ Core classes (Security, Storage, Comments, Resilience, Uploads)
install/ Installation wizard (delete after install)
templates/ Public templates
update/ Update packages, manifest, and release files
index.php Public homepage
post.php Single post view
rss.php RSS feed
s.php Short URL redirect handler

Deployment Notes

Nginx (recommended)

A sample nginx config is included as nginx.conf with:

  • Pretty URL rewrites (WordPress-style /post/slug/, /category/tech/, etc.)
  • Security deny rules for data/, includes/, install/, and cli/ directories
  • Static file caching headers

CloudPanel / Varnish

When deploying behind CloudPanel with Varnish:

  1. Set TRUST_PROXY_HEADERS to true in includes/config.php
  2. Add nginx deny rules for data/, includes/, install/, and cli/ directories
  3. Ensure parent directory permissions are 755 (CloudPanel may reset to 770)
  4. Delete the install/ directory after setup

Important: Config Protection

  • includes/config.php contains your admin credentials and site settings
  • Never overwrite it during updates — the updater skips it automatically
  • includes/config.php.example is provided as a reference template
  • When deploying manually, always exclude includes/config.php from file copies
  • The ADMIN_PASSWORD_HASH must use single quotes (not double quotes) to prevent PHP from interpreting $ in Argon2id hashes

Changelog

v1.5.6 — User Management & Security (2026-07-14)

New Features:

  • User Management — Create, edit, and delete users from the admin panel
  • Password Policy — Enforced minimum 12 characters with uppercase, lowercase, digit, and special character requirements
  • Role-Based Permissions — Admin (full access), Editor (publish/edit any post, moderate comments), Author (create/edit own posts only)
  • Password Strength Meter — Visual strength indicator on user creation and edit forms
  • Edit User Modal — Change role and password with admin password confirmation required
  • Self-Demotion Protection — Admins cannot demote themselves to a lower role
  • CLI Password Resetcli/reset_password.php utility for emergency password resets when locked out; interactive mode avoids shell expansion of special characters
  • Installer Password Policy — Visual checklist enforces password requirements during installation
  • Special Character Safety — Passwords with $, !, *, etc. are properly handled throughout the system (CLI, admin UI, installer, JSON storage)

Security:

  • nginx.conf updated: added cli/ to blocked directories (both Option A and Option B)
  • cli/.htaccess denies all web access to CLI scripts

Improvements:

  • admin/users.php — No-cache headers for CSRF token freshness behind Cloudflare/Varnish
  • Password hashing uses Argon2id with tuned parameters (bcrypt fallback)

v1.5.5 — Category/Tag Management & Branding (2026-07-14)

New Features:

  • Category & Tag Deletion — Delete categories and tags with automatic cleanup of post references
  • Slug Collision Resolution — Auto-appends -2, -3, etc. when a slug already exists
  • Duplicate Prevention — Case-insensitive name matching rejects exact duplicates; slug collisions auto-resolved

Improvements:

  • No-cache headers on admin categories page for CSRF token freshness behind Cloudflare/Varnish
  • Branding updated to Digital Systems LLC / AfterPacket
  • Removed duplicate version display in public footer
  • Admin categories page now shows post count per category/tag

Bug Fixes:

  • Fixed PHP syntax error in addCategory() return statement
  • Fixed delete confirmation dialog quoting issues
  • Fixed CSRF token invalidation on category/tag management page

v1.5.4 — Install Cleanup & Update Channels (2026-07-14)

Critical Bug Fixes:

  • [CRITICAL] Admin session logout on idle — session fingerprint validation was destroying sessions when IP or User-Agent shifted between requests behind Cloudflare/Varnish proxies. Now logs a warning and updates the fingerprint instead of destroying the session.
  • [CRITICAL] Argon2id password hash corruption — hashes containing $ characters were corrupted by PHP variable interpolation in double-quoted strings, causing admin login failures. ADMIN_PASSWORD_HASH now uses single quotes.
  • [CRITICAL] Updater "Requested version not found in manifest" error — Upgrader required exact version string match. Now uses the manifest version directly and only checks that it's newer than the current version. Update cache also cleared before upgrading.

Improvements:

  • Session regenerate interval increased from 30 minutes to 4 hours (less disruption for users)
  • config.php has prominent DO NOT OVERWRITE warning header
  • Added config.php.example as install template — installer copies from example if config.php doesn't exist
  • includes/config.php removed from update manifest — updater will never overwrite it
  • Built-in updater now has real SHA-256 hashes for integrity verification
  • Added generate_manifest.sh script for maintainers to generate release manifests

v1.5.2 — Config Protection (2026-07-14)

Critical Bug Fix:

  • [CRITICAL] Password hash corruption fix (same root cause as v1.5.3, addressed in config.php template and installer)

Improvements:

  • Added config.php.example as install template
  • Installer copies from config.php.example; uses single-quote replacement for password hash
  • Removed config.php from update manifest

v1.5.1 — Pen Test Hardening (2026-07-14)

Security Fixes:

  • [MEDIUM] Rate limiting added to post password attempts (5 per IP per 5 minutes) — prevents brute-force attacks against password-protected posts.
  • [MEDIUM] Session fingerprint now hashes User-Agent with SHA-256 — strengthens session binding beyond IP-only, format: sha256(ip | sha256(user_agent)).
  • [MEDIUM] Site URL setting validation added — filter_var(FILTER_VALIDATE_URL) and scheme whitelist (http/https) prevent open redirect and XSS via malicious URL values.
  • [MEDIUM] Cross-Origin isolation headers added — Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin prevent cross-origin information leakage.
  • [MEDIUM] Comment author name sanitization — HTML tags stripped, length capped at 100 characters. Email validated when provided.
  • [MEDIUM] Per-user daily upload rate limit added (50 uploads/day per user) alongside existing per-IP hourly limit.

v1.5.0 — Security + Subfolder Install Fix (2026-07-13)

Security Fixes:

  • [HIGH] Removed CSRF token from image upload URL query string — tokens were being logged in server access logs and browser history. Now sent only via X-CSRF-Token header and POST body.
  • [HIGH] Removed data: from public CSP img-src — prevents SVG-based XSS through data:image/svg+xml URIs. Admin CSP still allows data: for TinyMCE paste compatibility.
  • [HIGH] HSTS header now respects TRUST_PROXY_HEADERS — previously only checked $_SERVER["HTTPS"], which is empty behind Cloudflare/Varnish. Sites behind proxies now correctly send HSTS.
  • [MEDIUM] Short URL redirect (s.php) changed from 301 to 302 — prevents browser cache poisoning if target changes.
  • [MEDIUM] Short URL redirect now validates resolved slug corresponds to a published post — prevents open redirect.
  • [MEDIUM] Post password hashing upgraded from bcrypt to Argon2id with fallback.
  • [MEDIUM] Removed ini_set() calls for allow_url_fopen/allow_url_include — these are PHP_INI_SYSTEM directives. Added comments for php.ini configuration.
  • [LOW] Removed debug error_log() from ImageUpload.php.
  • [LOW] Fixed Content-Disposition filename escaping in serve-image.php.

Bug Fixes:

  • [CRITICAL] All internal links now use cms_path() — fixes broken pagination, search, admin links, RSS, and comment forms in subfolder installs.
  • [CRITICAL] Fixed index.php line 469 — missing ?> closing tag caused 500 parse error on PHP 8.x.

v1.4.1 — Patch Release (2026-07-13)

Security Fixes:

  • [HIGH] Password input fields for post protection were type="text" — changed to type="password".
  • [HIGH]ENABLE_UPLOAD_MALWARE_SCAN was false by default — changed to true.
  • [MEDIUM] Session cookie secure flag now respects TRUST_PROXY_HEADERS.
  • [MEDIUM] Removed debug console.log statements from create/edit post pages.

Bug Fixes:

  • Image URL insertion: TinyMCE valid_elements now allows class and style on <img>.
  • Image paste: Enabled paste_data_images: true in TinyMCE.
  • RSS self-link: Now uses cms_path() for correct URLs in subfolder installs.

v1.4.0 — Security Hardening Release (2026-07-13)

Critical Fixes:

  • [CRITICAL] Removed Remote Code Execution vector in upgrade system — download_url no longer accepted from POST data. Upgrades use performUpgradeFromManifest() with hardcoded manifest URL. SHA-256 checksums mandatory. Auto-upgrade disabled.
  • [CRITICAL] Added credential placeholder detection — warns if REPLACE_ME_* defaults are still in place.

High Fixes:

  • [HIGH] Replaced regex XSS sanitizer with DOM-based HTML purification (DOMDocument + XPath). Regex fallback for servers without dom extension.
  • [HIGH] CSP headers enabled by default.
  • [HIGH] CSRF tokens now single-use for all forms — removed image_upload/edit_post_form reuse exception.
  • [HIGH] Session fingerprint gated behind TRUST_PROXY_HEADERS config (default: false).
  • [HIGH] Removed version disclosure header (X-SecureBlogCMS-Version) — only sent if SHOW_VERSION_HEADER is explicitly true.
  • [HIGH] Password protection enforced on public pages. Private posts hidden from listings, search, and RSS.

Medium Fixes:

  • Role validation whitelist enforced in addUser() and updateUser().
  • Password hashing unified to Argon2id across all user management.
  • Rate limiting on comment submissions (3/IP/hour).
  • Install directory .htaccess hardened.
  • CORS credentials set to false on image endpoints.
  • Debug logging reduced in upload endpoint.
  • Error reporting hardened to E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE.

Version: 1.5.6
Last Updated: 2026-07-14
Created by: Digital Systems LLC / AfterPacket Security Level: High

About

SQL-free PHP blog CMS with file-based JSON storage, full admin UI, IPFS pinning, static export, and security-first architecture

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Secure Blog CMS (SQL-Free, File-Based)

Secure Blog CMS is a security-first PHP blogging platform that stores content in JSON files instead of a database. It includes a full admin UI, comment moderation, image uploads, and a Resilience Center for static exports and IPFS pinning.

Features

Publishing

  • File-based storage (no SQL/database required)
  • Drafts and published posts, slugs, excerpts, and pagination
  • Optional search, private posts, and password-protected posts
  • RSS feed generation
  • Image uploads with server-side security checks

Admin and Users

  • Admin dashboard for posts, comments, users, and settings
  • Roles: admin, editor, author
  • Comment moderation (pending/approved/spam/trash)
  • Backups and restore from the admin UI

Resilience Center

  • Static site export (HTML + RSS) for static hosting
  • ZIP bundles for easy distribution
  • Optional auto-pinning to IPFS via Pinata
  • Export bundles stored in data/exports/

Security

  • CSRF protection on all forms (single-use tokens, no replay)
  • XSS sanitization and output escaping (DOM-based HTML purification)
  • CSP headers enabled by default, plus standard HTTP security headers
  • Rate limiting (login, comments, uploads, short URLs, post passwords)
  • Account lockout after failed login attempts
  • Session hardening with IP + User-Agent fingerprinting
  • Security event logging to data/logs/
  • Mandatory SHA-256 checksums on upgrades; auto-upgrade disabled for safety
  • Proxy header spoofing protection (Cloudflare/X-Forwarded headers gated behind config toggle)
  • COOP/CORP security headers for cross-origin isolation
  • Permissions-Policy header to restrict browser APIs
  • HSTS with preload and includeSubDomains (respects proxy headers)
  • Comment author name sanitization (strip tags, length limit, email validation)
  • Site URL validation (scheme whitelist prevents javascript: and data: URLs)
  • Per-user daily upload rate limiting (50/day per user)
  • Post password brute-force protection (5 attempts per IP per 5 minutes)

Built-In Updater

  • Check for updates from the admin panel
  • Download and verify files with SHA-256 integrity checks
  • Automatic backup before upgrade
  • Config file never overwritten during updates
  • One-click upgrade process

Requirements

  • PHP 7.4+ (PHP 8.x recommended)
  • Web server (Apache/Nginx) or PHP built-in server
  • Write access to the data/ directory
  • Extensions (optional but recommended):
    • curl for Pinata IPFS pinning and in-app updates
    • zip for export ZIP bundles
    • dom for DOM-based HTML sanitization (fallback regex available)

Quick Start

Option A: Installer (recommended)

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Visit /install/index.php in your browser and complete the wizard.
  4. After install, keep data/installed.lock in place (re-install requires deleting it).
  5. Delete the install/ directory after installation for best security.

Option B: Manual install

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Generate an Argon2id password hash (use single quotes to avoid $ interpretation):
cat > /tmp/hashpass.php << 'EOF'<?phpecho password_hash('YourSecurePassword123!', PASSWORD_ARGON2ID) . PHP_EOL;EOF
php /tmp/hashpass.php
  1. Update includes/config.php with your credentials (use single quotes for the hash):
define('ADMIN_USERNAME', 'your_username');
define('ADMIN_PASSWORD_HASH', '$argon2id$v=19$m=65536,...'); // single quotes!
  1. Update site settings in includes/config.php or the admin UI.
  2. Open /admin.php and log in.

⚠️Important: Always use single quotes around ADMIN_PASSWORD_HASH. Argon2id hashes contain $ characters which PHP interprets as variable references inside double-quoted strings, corrupting the hash and breaking login.

Reverse Proxy / Cloudflare Setup

If your CMS is behind Cloudflare or a reverse proxy that sets X-Forwarded-For or CF-Connecting-IP headers, enable trusted proxy mode in includes/config.php:

define('TRUST_PROXY_HEADERS', true);

Warning: Only enable this when actually behind a trusted proxy. Enabling it without a proxy allows IP spoofing and session bypass.

Configuration

Site settings (recommended)

Most settings are managed in the admin UI at /admin/settings.php and stored in:

  • data/settings/site.json

These settings override defaults from includes/config.php.

hCaptcha (comments)

Comments can require hCaptcha. Configure via environment variables (recommended):

  • HCAPTCHA_SITEKEY
  • HCAPTCHA_SECRET

Or set hcaptcha_sitekey in data/settings/site.json and keep the secret in env.

Pinata (IPFS)

Configure Pinata credentials in /admin/settings.php to enable auto-pinning of exports.

Updating

In-app updater

  • Go to /admin/upgrade.php and check for updates.
  • The upgrader downloads update/manifest.json from the configured update source.
  • All file updates require SHA-256 checksum verification — no file is written without integrity verification.
  • includes/config.php is never overwritten — your credentials and settings are preserved.
  • Auto-upgrade has been disabled for security. All upgrades must be manually triggered.

Manual update

  1. Backup data/ and includes/config.php.
  2. Replace application files with the new release.
  3. Do NOT overwrite includes/config.php — preserve your existing credentials and settings.
  4. Re-check your settings and log in to confirm.

Creating a release

For maintainers, use the generate_manifest.sh script to prepare updates:

cd update/
./generate_manifest.sh 1.6.0 "Description of changes"# Then commit, tag, and push:
git add update/ && git commit -m "v1.6.0: update manifest"
git tag -a v1.6.0 -m "v1.6.0"
git push origin main --tags

Project Layout

secure-blog-cms/
admin/ Admin UI (posts, comments, users, settings, resilience, upgrade)
cli/ CLI utilities (password reset)
data/ JSON data storage (posts, users, comments, logs, backups)
includes/ Core classes (Security, Storage, Comments, Resilience, Uploads)
install/ Installation wizard (delete after install)
templates/ Public templates
update/ Update packages, manifest, and release files
index.php Public homepage
post.php Single post view
rss.php RSS feed
s.php Short URL redirect handler

Deployment Notes

Nginx (recommended)

A sample nginx config is included as nginx.conf with:

  • Pretty URL rewrites (WordPress-style /post/slug/, /category/tech/, etc.)
  • Security deny rules for data/, includes/, install/, and cli/ directories
  • Static file caching headers

CloudPanel / Varnish

When deploying behind CloudPanel with Varnish:

  1. Set TRUST_PROXY_HEADERS to true in includes/config.php
  2. Add nginx deny rules for data/, includes/, install/, and cli/ directories
  3. Ensure parent directory permissions are 755 (CloudPanel may reset to 770)
  4. Delete the install/ directory after setup

Important: Config Protection

  • includes/config.php contains your admin credentials and site settings
  • Never overwrite it during updates — the updater skips it automatically
  • includes/config.php.example is provided as a reference template
  • When deploying manually, always exclude includes/config.php from file copies
  • The ADMIN_PASSWORD_HASH must use single quotes (not double quotes) to prevent PHP from interpreting $ in Argon2id hashes

Changelog

v1.5.6 — User Management & Security (2026-07-14)

New Features:

  • User Management — Create, edit, and delete users from the admin panel
  • Password Policy — Enforced minimum 12 characters with uppercase, lowercase, digit, and special character requirements
  • Role-Based Permissions — Admin (full access), Editor (publish/edit any post, moderate comments), Author (create/edit own posts only)
  • Password Strength Meter — Visual strength indicator on user creation and edit forms
  • Edit User Modal — Change role and password with admin password confirmation required
  • Self-Demotion Protection — Admins cannot demote themselves to a lower role
  • CLI Password Resetcli/reset_password.php utility for emergency password resets when locked out; interactive mode avoids shell expansion of special characters
  • Installer Password Policy — Visual checklist enforces password requirements during installation
  • Special Character Safety — Passwords with $, !, *, etc. are properly handled throughout the system (CLI, admin UI, installer, JSON storage)

Security:

  • nginx.conf updated: added cli/ to blocked directories (both Option A and Option B)
  • cli/.htaccess denies all web access to CLI scripts

Improvements:

  • admin/users.php — No-cache headers for CSRF token freshness behind Cloudflare/Varnish
  • Password hashing uses Argon2id with tuned parameters (bcrypt fallback)

v1.5.5 — Category/Tag Management & Branding (2026-07-14)

New Features:

  • Category & Tag Deletion — Delete categories and tags with automatic cleanup of post references
  • Slug Collision Resolution — Auto-appends -2, -3, etc. when a slug already exists
  • Duplicate Prevention — Case-insensitive name matching rejects exact duplicates; slug collisions auto-resolved

Improvements:

  • No-cache headers on admin categories page for CSRF token freshness behind Cloudflare/Varnish
  • Branding updated to Digital Systems LLC / AfterPacket
  • Removed duplicate version display in public footer
  • Admin categories page now shows post count per category/tag

Bug Fixes:

  • Fixed PHP syntax error in addCategory() return statement
  • Fixed delete confirmation dialog quoting issues
  • Fixed CSRF token invalidation on category/tag management page

v1.5.4 — Install Cleanup & Update Channels (2026-07-14)

Critical Bug Fixes:

  • [CRITICAL] Admin session logout on idle — session fingerprint validation was destroying sessions when IP or User-Agent shifted between requests behind Cloudflare/Varnish proxies. Now logs a warning and updates the fingerprint instead of destroying the session.
  • [CRITICAL] Argon2id password hash corruption — hashes containing $ characters were corrupted by PHP variable interpolation in double-quoted strings, causing admin login failures. ADMIN_PASSWORD_HASH now uses single quotes.
  • [CRITICAL] Updater "Requested version not found in manifest" error — Upgrader required exact version string match. Now uses the manifest version directly and only checks that it's newer than the current version. Update cache also cleared before upgrading.

Improvements:

  • Session regenerate interval increased from 30 minutes to 4 hours (less disruption for users)
  • config.php has prominent DO NOT OVERWRITE warning header
  • Added config.php.example as install template — installer copies from example if config.php doesn't exist
  • includes/config.php removed from update manifest — updater will never overwrite it
  • Built-in updater now has real SHA-256 hashes for integrity verification
  • Added generate_manifest.sh script for maintainers to generate release manifests

v1.5.2 — Config Protection (2026-07-14)

Critical Bug Fix:

  • [CRITICAL] Password hash corruption fix (same root cause as v1.5.3, addressed in config.php template and installer)

Improvements:

  • Added config.php.example as install template
  • Installer copies from config.php.example; uses single-quote replacement for password hash
  • Removed config.php from update manifest

v1.5.1 — Pen Test Hardening (2026-07-14)

Security Fixes:

  • [MEDIUM] Rate limiting added to post password attempts (5 per IP per 5 minutes) — prevents brute-force attacks against password-protected posts.
  • [MEDIUM] Session fingerprint now hashes User-Agent with SHA-256 — strengthens session binding beyond IP-only, format: sha256(ip | sha256(user_agent)).
  • [MEDIUM] Site URL setting validation added — filter_var(FILTER_VALIDATE_URL) and scheme whitelist (http/https) prevent open redirect and XSS via malicious URL values.
  • [MEDIUM] Cross-Origin isolation headers added — Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin prevent cross-origin information leakage.
  • [MEDIUM] Comment author name sanitization — HTML tags stripped, length capped at 100 characters. Email validated when provided.
  • [MEDIUM] Per-user daily upload rate limit added (50 uploads/day per user) alongside existing per-IP hourly limit.

v1.5.0 — Security + Subfolder Install Fix (2026-07-13)

Security Fixes:

  • [HIGH] Removed CSRF token from image upload URL query string — tokens were being logged in server access logs and browser history. Now sent only via X-CSRF-Token header and POST body.
  • [HIGH] Removed data: from public CSP img-src — prevents SVG-based XSS through data:image/svg+xml URIs. Admin CSP still allows data: for TinyMCE paste compatibility.
  • [HIGH] HSTS header now respects TRUST_PROXY_HEADERS — previously only checked $_SERVER["HTTPS"], which is empty behind Cloudflare/Varnish. Sites behind proxies now correctly send HSTS.
  • [MEDIUM] Short URL redirect (s.php) changed from 301 to 302 — prevents browser cache poisoning if target changes.
  • [MEDIUM] Short URL redirect now validates resolved slug corresponds to a published post — prevents open redirect.
  • [MEDIUM] Post password hashing upgraded from bcrypt to Argon2id with fallback.
  • [MEDIUM] Removed ini_set() calls for allow_url_fopen/allow_url_include — these are PHP_INI_SYSTEM directives. Added comments for php.ini configuration.
  • [LOW] Removed debug error_log() from ImageUpload.php.
  • [LOW] Fixed Content-Disposition filename escaping in serve-image.php.

Bug Fixes:

  • [CRITICAL] All internal links now use cms_path() — fixes broken pagination, search, admin links, RSS, and comment forms in subfolder installs.
  • [CRITICAL] Fixed index.php line 469 — missing ?> closing tag caused 500 parse error on PHP 8.x.

v1.4.1 — Patch Release (2026-07-13)

Security Fixes:

  • [HIGH] Password input fields for post protection were type="text" — changed to type="password".
  • [HIGH]ENABLE_UPLOAD_MALWARE_SCAN was false by default — changed to true.
  • [MEDIUM] Session cookie secure flag now respects TRUST_PROXY_HEADERS.
  • [MEDIUM] Removed debug console.log statements from create/edit post pages.

Bug Fixes:

  • Image URL insertion: TinyMCE valid_elements now allows class and style on <img>.
  • Image paste: Enabled paste_data_images: true in TinyMCE.
  • RSS self-link: Now uses cms_path() for correct URLs in subfolder installs.

v1.4.0 — Security Hardening Release (2026-07-13)

Critical Fixes:

  • [CRITICAL] Removed Remote Code Execution vector in upgrade system — download_url no longer accepted from POST data. Upgrades use performUpgradeFromManifest() with hardcoded manifest URL. SHA-256 checksums mandatory. Auto-upgrade disabled.
  • [CRITICAL] Added credential placeholder detection — warns if REPLACE_ME_* defaults are still in place.

High Fixes:

  • [HIGH] Replaced regex XSS sanitizer with DOM-based HTML purification (DOMDocument + XPath). Regex fallback for servers without dom extension.
  • [HIGH] CSP headers enabled by default.
  • [HIGH] CSRF tokens now single-use for all forms — removed image_upload/edit_post_form reuse exception.
  • [HIGH] Session fingerprint gated behind TRUST_PROXY_HEADERS config (default: false).
  • [HIGH] Removed version disclosure header (X-SecureBlogCMS-Version) — only sent if SHOW_VERSION_HEADER is explicitly true.
  • [HIGH] Password protection enforced on public pages. Private posts hidden from listings, search, and RSS.

Medium Fixes:

  • Role validation whitelist enforced in addUser() and updateUser().
  • Password hashing unified to Argon2id across all user management.
  • Rate limiting on comment submissions (3/IP/hour).
  • Install directory .htaccess hardened.
  • CORS credentials set to false on image endpoints.
  • Debug logging reduced in upload endpoint.
  • Error reporting hardened to E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE.

Version: 1.5.6
Last Updated: 2026-07-14
Created by: Digital Systems LLC / AfterPacket Security Level: High

About

SQL-free PHP blog CMS with file-based JSON storage, full admin UI, IPFS pinning, static export, and security-first architecture

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Blog CMS (SQL-Free, File-Based)

Secure Blog CMS is a security-first PHP blogging platform that stores content in JSON files instead of a database. It includes a full admin UI, comment moderation, image uploads, and a Resilience Center for static exports and IPFS pinning.

Features

Publishing

  • File-based storage (no SQL/database required)
  • Drafts and published posts, slugs, excerpts, and pagination
  • Optional search, private posts, and password-protected posts
  • RSS feed generation
  • Image uploads with server-side security checks

Admin and Users

  • Admin dashboard for posts, comments, users, and settings
  • Roles: admin, editor, author
  • Comment moderation (pending/approved/spam/trash)
  • Backups and restore from the admin UI

Resilience Center

  • Static site export (HTML + RSS) for static hosting
  • ZIP bundles for easy distribution
  • Optional auto-pinning to IPFS via Pinata
  • Export bundles stored in data/exports/

Security

  • CSRF protection on all forms (single-use tokens, no replay)
  • XSS sanitization and output escaping (DOM-based HTML purification)
  • CSP headers enabled by default, plus standard HTTP security headers
  • Rate limiting (login, comments, uploads, short URLs, post passwords)
  • Account lockout after failed login attempts
  • Session hardening with IP + User-Agent fingerprinting
  • Security event logging to data/logs/
  • Mandatory SHA-256 checksums on upgrades; auto-upgrade disabled for safety
  • Proxy header spoofing protection (Cloudflare/X-Forwarded headers gated behind config toggle)
  • COOP/CORP security headers for cross-origin isolation
  • Permissions-Policy header to restrict browser APIs
  • HSTS with preload and includeSubDomains (respects proxy headers)
  • Comment author name sanitization (strip tags, length limit, email validation)
  • Site URL validation (scheme whitelist prevents javascript: and data: URLs)
  • Per-user daily upload rate limiting (50/day per user)
  • Post password brute-force protection (5 attempts per IP per 5 minutes)

Built-In Updater

  • Check for updates from the admin panel
  • Download and verify files with SHA-256 integrity checks
  • Automatic backup before upgrade
  • Config file never overwritten during updates
  • One-click upgrade process

Requirements

  • PHP 7.4+ (PHP 8.x recommended)
  • Web server (Apache/Nginx) or PHP built-in server
  • Write access to the data/ directory
  • Extensions (optional but recommended):
    • curl for Pinata IPFS pinning and in-app updates
    • zip for export ZIP bundles
    • dom for DOM-based HTML sanitization (fallback regex available)

Quick Start

Option A: Installer (recommended)

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Visit /install/index.php in your browser and complete the wizard.
  4. After install, keep data/installed.lock in place (re-install requires deleting it).
  5. Delete the install/ directory after installation for best security.

Option B: Manual install

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Generate an Argon2id password hash (use single quotes to avoid $ interpretation):
cat > /tmp/hashpass.php << 'EOF'<?phpecho password_hash('YourSecurePassword123!', PASSWORD_ARGON2ID) . PHP_EOL;EOF
php /tmp/hashpass.php
  1. Update includes/config.php with your credentials (use single quotes for the hash):
define('ADMIN_USERNAME', 'your_username');
define('ADMIN_PASSWORD_HASH', '$argon2id$v=19$m=65536,...'); // single quotes!
  1. Update site settings in includes/config.php or the admin UI.
  2. Open /admin.php and log in.

⚠️Important: Always use single quotes around ADMIN_PASSWORD_HASH. Argon2id hashes contain $ characters which PHP interprets as variable references inside double-quoted strings, corrupting the hash and breaking login.

Reverse Proxy / Cloudflare Setup

If your CMS is behind Cloudflare or a reverse proxy that sets X-Forwarded-For or CF-Connecting-IP headers, enable trusted proxy mode in includes/config.php:

define('TRUST_PROXY_HEADERS', true);

Warning: Only enable this when actually behind a trusted proxy. Enabling it without a proxy allows IP spoofing and session bypass.

Configuration

Site settings (recommended)

Most settings are managed in the admin UI at /admin/settings.php and stored in:

  • data/settings/site.json

These settings override defaults from includes/config.php.

hCaptcha (comments)

Comments can require hCaptcha. Configure via environment variables (recommended):

  • HCAPTCHA_SITEKEY
  • HCAPTCHA_SECRET

Or set hcaptcha_sitekey in data/settings/site.json and keep the secret in env.

Pinata (IPFS)

Configure Pinata credentials in /admin/settings.php to enable auto-pinning of exports.

Updating

In-app updater

  • Go to /admin/upgrade.php and check for updates.
  • The upgrader downloads update/manifest.json from the configured update source.
  • All file updates require SHA-256 checksum verification — no file is written without integrity verification.
  • includes/config.php is never overwritten — your credentials and settings are preserved.
  • Auto-upgrade has been disabled for security. All upgrades must be manually triggered.

Manual update

  1. Backup data/ and includes/config.php.
  2. Replace application files with the new release.
  3. Do NOT overwrite includes/config.php — preserve your existing credentials and settings.
  4. Re-check your settings and log in to confirm.

Creating a release

For maintainers, use the generate_manifest.sh script to prepare updates:

cd update/
./generate_manifest.sh 1.6.0 "Description of changes"# Then commit, tag, and push:
git add update/ && git commit -m "v1.6.0: update manifest"
git tag -a v1.6.0 -m "v1.6.0"
git push origin main --tags

Project Layout

secure-blog-cms/
admin/ Admin UI (posts, comments, users, settings, resilience, upgrade)
cli/ CLI utilities (password reset)
data/ JSON data storage (posts, users, comments, logs, backups)
includes/ Core classes (Security, Storage, Comments, Resilience, Uploads)
install/ Installation wizard (delete after install)
templates/ Public templates
update/ Update packages, manifest, and release files
index.php Public homepage
post.php Single post view
rss.php RSS feed
s.php Short URL redirect handler

Deployment Notes

Nginx (recommended)

A sample nginx config is included as nginx.conf with:

  • Pretty URL rewrites (WordPress-style /post/slug/, /category/tech/, etc.)
  • Security deny rules for data/, includes/, install/, and cli/ directories
  • Static file caching headers

CloudPanel / Varnish

When deploying behind CloudPanel with Varnish:

  1. Set TRUST_PROXY_HEADERS to true in includes/config.php
  2. Add nginx deny rules for data/, includes/, install/, and cli/ directories
  3. Ensure parent directory permissions are 755 (CloudPanel may reset to 770)
  4. Delete the install/ directory after setup

Important: Config Protection

  • includes/config.php contains your admin credentials and site settings
  • Never overwrite it during updates — the updater skips it automatically
  • includes/config.php.example is provided as a reference template
  • When deploying manually, always exclude includes/config.php from file copies
  • The ADMIN_PASSWORD_HASH must use single quotes (not double quotes) to prevent PHP from interpreting $ in Argon2id hashes

Changelog

v1.5.6 — User Management & Security (2026-07-14)

New Features:

  • User Management — Create, edit, and delete users from the admin panel
  • Password Policy — Enforced minimum 12 characters with uppercase, lowercase, digit, and special character requirements
  • Role-Based Permissions — Admin (full access), Editor (publish/edit any post, moderate comments), Author (create/edit own posts only)
  • Password Strength Meter — Visual strength indicator on user creation and edit forms
  • Edit User Modal — Change role and password with admin password confirmation required
  • Self-Demotion Protection — Admins cannot demote themselves to a lower role
  • CLI Password Resetcli/reset_password.php utility for emergency password resets when locked out; interactive mode avoids shell expansion of special characters
  • Installer Password Policy — Visual checklist enforces password requirements during installation
  • Special Character Safety — Passwords with $, !, *, etc. are properly handled throughout the system (CLI, admin UI, installer, JSON storage)

Security:

  • nginx.conf updated: added cli/ to blocked directories (both Option A and Option B)
  • cli/.htaccess denies all web access to CLI scripts

Improvements:

  • admin/users.php — No-cache headers for CSRF token freshness behind Cloudflare/Varnish
  • Password hashing uses Argon2id with tuned parameters (bcrypt fallback)

v1.5.5 — Category/Tag Management & Branding (2026-07-14)

New Features:

  • Category & Tag Deletion — Delete categories and tags with automatic cleanup of post references
  • Slug Collision Resolution — Auto-appends -2, -3, etc. when a slug already exists
  • Duplicate Prevention — Case-insensitive name matching rejects exact duplicates; slug collisions auto-resolved

Improvements:

  • No-cache headers on admin categories page for CSRF token freshness behind Cloudflare/Varnish
  • Branding updated to Digital Systems LLC / AfterPacket
  • Removed duplicate version display in public footer
  • Admin categories page now shows post count per category/tag

Bug Fixes:

  • Fixed PHP syntax error in addCategory() return statement
  • Fixed delete confirmation dialog quoting issues
  • Fixed CSRF token invalidation on category/tag management page

v1.5.4 — Install Cleanup & Update Channels (2026-07-14)

Critical Bug Fixes:

  • [CRITICAL] Admin session logout on idle — session fingerprint validation was destroying sessions when IP or User-Agent shifted between requests behind Cloudflare/Varnish proxies. Now logs a warning and updates the fingerprint instead of destroying the session.
  • [CRITICAL] Argon2id password hash corruption — hashes containing $ characters were corrupted by PHP variable interpolation in double-quoted strings, causing admin login failures. ADMIN_PASSWORD_HASH now uses single quotes.
  • [CRITICAL] Updater "Requested version not found in manifest" error — Upgrader required exact version string match. Now uses the manifest version directly and only checks that it's newer than the current version. Update cache also cleared before upgrading.

Improvements:

  • Session regenerate interval increased from 30 minutes to 4 hours (less disruption for users)
  • config.php has prominent DO NOT OVERWRITE warning header
  • Added config.php.example as install template — installer copies from example if config.php doesn't exist
  • includes/config.php removed from update manifest — updater will never overwrite it
  • Built-in updater now has real SHA-256 hashes for integrity verification
  • Added generate_manifest.sh script for maintainers to generate release manifests

v1.5.2 — Config Protection (2026-07-14)

Critical Bug Fix:

  • [CRITICAL] Password hash corruption fix (same root cause as v1.5.3, addressed in config.php template and installer)

Improvements:

  • Added config.php.example as install template
  • Installer copies from config.php.example; uses single-quote replacement for password hash
  • Removed config.php from update manifest

v1.5.1 — Pen Test Hardening (2026-07-14)

Security Fixes:

  • [MEDIUM] Rate limiting added to post password attempts (5 per IP per 5 minutes) — prevents brute-force attacks against password-protected posts.
  • [MEDIUM] Session fingerprint now hashes User-Agent with SHA-256 — strengthens session binding beyond IP-only, format: sha256(ip | sha256(user_agent)).
  • [MEDIUM] Site URL setting validation added — filter_var(FILTER_VALIDATE_URL) and scheme whitelist (http/https) prevent open redirect and XSS via malicious URL values.
  • [MEDIUM] Cross-Origin isolation headers added — Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin prevent cross-origin information leakage.
  • [MEDIUM] Comment author name sanitization — HTML tags stripped, length capped at 100 characters. Email validated when provided.
  • [MEDIUM] Per-user daily upload rate limit added (50 uploads/day per user) alongside existing per-IP hourly limit.

v1.5.0 — Security + Subfolder Install Fix (2026-07-13)

Security Fixes:

  • [HIGH] Removed CSRF token from image upload URL query string — tokens were being logged in server access logs and browser history. Now sent only via X-CSRF-Token header and POST body.
  • [HIGH] Removed data: from public CSP img-src — prevents SVG-based XSS through data:image/svg+xml URIs. Admin CSP still allows data: for TinyMCE paste compatibility.
  • [HIGH] HSTS header now respects TRUST_PROXY_HEADERS — previously only checked $_SERVER["HTTPS"], which is empty behind Cloudflare/Varnish. Sites behind proxies now correctly send HSTS.
  • [MEDIUM] Short URL redirect (s.php) changed from 301 to 302 — prevents browser cache poisoning if target changes.
  • [MEDIUM] Short URL redirect now validates resolved slug corresponds to a published post — prevents open redirect.
  • [MEDIUM] Post password hashing upgraded from bcrypt to Argon2id with fallback.
  • [MEDIUM] Removed ini_set() calls for allow_url_fopen/allow_url_include — these are PHP_INI_SYSTEM directives. Added comments for php.ini configuration.
  • [LOW] Removed debug error_log() from ImageUpload.php.
  • [LOW] Fixed Content-Disposition filename escaping in serve-image.php.

Bug Fixes:

  • [CRITICAL] All internal links now use cms_path() — fixes broken pagination, search, admin links, RSS, and comment forms in subfolder installs.
  • [CRITICAL] Fixed index.php line 469 — missing ?> closing tag caused 500 parse error on PHP 8.x.

v1.4.1 — Patch Release (2026-07-13)

Security Fixes:

  • [HIGH] Password input fields for post protection were type="text" — changed to type="password".
  • [HIGH]ENABLE_UPLOAD_MALWARE_SCAN was false by default — changed to true.
  • [MEDIUM] Session cookie secure flag now respects TRUST_PROXY_HEADERS.
  • [MEDIUM] Removed debug console.log statements from create/edit post pages.

Bug Fixes:

  • Image URL insertion: TinyMCE valid_elements now allows class and style on <img>.
  • Image paste: Enabled paste_data_images: true in TinyMCE.
  • RSS self-link: Now uses cms_path() for correct URLs in subfolder installs.

v1.4.0 — Security Hardening Release (2026-07-13)

Critical Fixes:

  • [CRITICAL] Removed Remote Code Execution vector in upgrade system — download_url no longer accepted from POST data. Upgrades use performUpgradeFromManifest() with hardcoded manifest URL. SHA-256 checksums mandatory. Auto-upgrade disabled.
  • [CRITICAL] Added credential placeholder detection — warns if REPLACE_ME_* defaults are still in place.

High Fixes:

  • [HIGH] Replaced regex XSS sanitizer with DOM-based HTML purification (DOMDocument + XPath). Regex fallback for servers without dom extension.
  • [HIGH] CSP headers enabled by default.
  • [HIGH] CSRF tokens now single-use for all forms — removed image_upload/edit_post_form reuse exception.
  • [HIGH] Session fingerprint gated behind TRUST_PROXY_HEADERS config (default: false).
  • [HIGH] Removed version disclosure header (X-SecureBlogCMS-Version) — only sent if SHOW_VERSION_HEADER is explicitly true.
  • [HIGH] Password protection enforced on public pages. Private posts hidden from listings, search, and RSS.

Medium Fixes:

  • Role validation whitelist enforced in addUser() and updateUser().
  • Password hashing unified to Argon2id across all user management.
  • Rate limiting on comment submissions (3/IP/hour).
  • Install directory .htaccess hardened.
  • CORS credentials set to false on image endpoints.
  • Debug logging reduced in upload endpoint.
  • Error reporting hardened to E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE.

Version: 1.5.6
Last Updated: 2026-07-14
Created by: Digital Systems LLC / AfterPacket Security Level: High

About

SQL-free PHP blog CMS with file-based JSON storage, full admin UI, IPFS pinning, static export, and security-first architecture

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Blog CMS (SQL-Free, File-Based)

Secure Blog CMS is a security-first PHP blogging platform that stores content in JSON files instead of a database. It includes a full admin UI, comment moderation, image uploads, and a Resilience Center for static exports and IPFS pinning.

Features

Publishing

  • File-based storage (no SQL/database required)
  • Drafts and published posts, slugs, excerpts, and pagination
  • Optional search, private posts, and password-protected posts
  • RSS feed generation
  • Image uploads with server-side security checks

Admin and Users

  • Admin dashboard for posts, comments, users, and settings
  • Roles: admin, editor, author
  • Comment moderation (pending/approved/spam/trash)
  • Backups and restore from the admin UI

Resilience Center

  • Static site export (HTML + RSS) for static hosting
  • ZIP bundles for easy distribution
  • Optional auto-pinning to IPFS via Pinata
  • Export bundles stored in data/exports/

Security

  • CSRF protection on all forms (single-use tokens, no replay)
  • XSS sanitization and output escaping (DOM-based HTML purification)
  • CSP headers enabled by default, plus standard HTTP security headers
  • Rate limiting (login, comments, uploads, short URLs, post passwords)
  • Account lockout after failed login attempts
  • Session hardening with IP + User-Agent fingerprinting
  • Security event logging to data/logs/
  • Mandatory SHA-256 checksums on upgrades; auto-upgrade disabled for safety
  • Proxy header spoofing protection (Cloudflare/X-Forwarded headers gated behind config toggle)
  • COOP/CORP security headers for cross-origin isolation
  • Permissions-Policy header to restrict browser APIs
  • HSTS with preload and includeSubDomains (respects proxy headers)
  • Comment author name sanitization (strip tags, length limit, email validation)
  • Site URL validation (scheme whitelist prevents javascript: and data: URLs)
  • Per-user daily upload rate limiting (50/day per user)
  • Post password brute-force protection (5 attempts per IP per 5 minutes)

Built-In Updater

  • Check for updates from the admin panel
  • Download and verify files with SHA-256 integrity checks
  • Automatic backup before upgrade
  • Config file never overwritten during updates
  • One-click upgrade process

Requirements

  • PHP 7.4+ (PHP 8.x recommended)
  • Web server (Apache/Nginx) or PHP built-in server
  • Write access to the data/ directory
  • Extensions (optional but recommended):
    • curl for Pinata IPFS pinning and in-app updates
    • zip for export ZIP bundles
    • dom for DOM-based HTML sanitization (fallback regex available)

Quick Start

Option A: Installer (recommended)

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Visit /install/index.php in your browser and complete the wizard.
  4. After install, keep data/installed.lock in place (re-install requires deleting it).
  5. Delete the install/ directory after installation for best security.

Option B: Manual install

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Generate an Argon2id password hash (use single quotes to avoid $ interpretation):
cat > /tmp/hashpass.php << 'EOF'<?phpecho password_hash('YourSecurePassword123!', PASSWORD_ARGON2ID) . PHP_EOL;EOF
php /tmp/hashpass.php
  1. Update includes/config.php with your credentials (use single quotes for the hash):
define('ADMIN_USERNAME', 'your_username');
define('ADMIN_PASSWORD_HASH', '$argon2id$v=19$m=65536,...'); // single quotes!
  1. Update site settings in includes/config.php or the admin UI.
  2. Open /admin.php and log in.

⚠️Important: Always use single quotes around ADMIN_PASSWORD_HASH. Argon2id hashes contain $ characters which PHP interprets as variable references inside double-quoted strings, corrupting the hash and breaking login.

Reverse Proxy / Cloudflare Setup

If your CMS is behind Cloudflare or a reverse proxy that sets X-Forwarded-For or CF-Connecting-IP headers, enable trusted proxy mode in includes/config.php:

define('TRUST_PROXY_HEADERS', true);

Warning: Only enable this when actually behind a trusted proxy. Enabling it without a proxy allows IP spoofing and session bypass.

Configuration

Site settings (recommended)

Most settings are managed in the admin UI at /admin/settings.php and stored in:

  • data/settings/site.json

These settings override defaults from includes/config.php.

hCaptcha (comments)

Comments can require hCaptcha. Configure via environment variables (recommended):

  • HCAPTCHA_SITEKEY
  • HCAPTCHA_SECRET

Or set hcaptcha_sitekey in data/settings/site.json and keep the secret in env.

Pinata (IPFS)

Configure Pinata credentials in /admin/settings.php to enable auto-pinning of exports.

Updating

In-app updater

  • Go to /admin/upgrade.php and check for updates.
  • The upgrader downloads update/manifest.json from the configured update source.
  • All file updates require SHA-256 checksum verification — no file is written without integrity verification.
  • includes/config.php is never overwritten — your credentials and settings are preserved.
  • Auto-upgrade has been disabled for security. All upgrades must be manually triggered.

Manual update

  1. Backup data/ and includes/config.php.
  2. Replace application files with the new release.
  3. Do NOT overwrite includes/config.php — preserve your existing credentials and settings.
  4. Re-check your settings and log in to confirm.

Creating a release

For maintainers, use the generate_manifest.sh script to prepare updates:

cd update/
./generate_manifest.sh 1.6.0 "Description of changes"# Then commit, tag, and push:
git add update/ && git commit -m "v1.6.0: update manifest"
git tag -a v1.6.0 -m "v1.6.0"
git push origin main --tags

Project Layout

secure-blog-cms/
admin/ Admin UI (posts, comments, users, settings, resilience, upgrade)
cli/ CLI utilities (password reset)
data/ JSON data storage (posts, users, comments, logs, backups)
includes/ Core classes (Security, Storage, Comments, Resilience, Uploads)
install/ Installation wizard (delete after install)
templates/ Public templates
update/ Update packages, manifest, and release files
index.php Public homepage
post.php Single post view
rss.php RSS feed
s.php Short URL redirect handler

Deployment Notes

Nginx (recommended)

A sample nginx config is included as nginx.conf with:

  • Pretty URL rewrites (WordPress-style /post/slug/, /category/tech/, etc.)
  • Security deny rules for data/, includes/, install/, and cli/ directories
  • Static file caching headers

CloudPanel / Varnish

When deploying behind CloudPanel with Varnish:

  1. Set TRUST_PROXY_HEADERS to true in includes/config.php
  2. Add nginx deny rules for data/, includes/, install/, and cli/ directories
  3. Ensure parent directory permissions are 755 (CloudPanel may reset to 770)
  4. Delete the install/ directory after setup

Important: Config Protection

  • includes/config.php contains your admin credentials and site settings
  • Never overwrite it during updates — the updater skips it automatically
  • includes/config.php.example is provided as a reference template
  • When deploying manually, always exclude includes/config.php from file copies
  • The ADMIN_PASSWORD_HASH must use single quotes (not double quotes) to prevent PHP from interpreting $ in Argon2id hashes

Changelog

v1.5.6 — User Management & Security (2026-07-14)

New Features:

  • User Management — Create, edit, and delete users from the admin panel
  • Password Policy — Enforced minimum 12 characters with uppercase, lowercase, digit, and special character requirements
  • Role-Based Permissions — Admin (full access), Editor (publish/edit any post, moderate comments), Author (create/edit own posts only)
  • Password Strength Meter — Visual strength indicator on user creation and edit forms
  • Edit User Modal — Change role and password with admin password confirmation required
  • Self-Demotion Protection — Admins cannot demote themselves to a lower role
  • CLI Password Resetcli/reset_password.php utility for emergency password resets when locked out; interactive mode avoids shell expansion of special characters
  • Installer Password Policy — Visual checklist enforces password requirements during installation
  • Special Character Safety — Passwords with $, !, *, etc. are properly handled throughout the system (CLI, admin UI, installer, JSON storage)

Security:

  • nginx.conf updated: added cli/ to blocked directories (both Option A and Option B)
  • cli/.htaccess denies all web access to CLI scripts

Improvements:

  • admin/users.php — No-cache headers for CSRF token freshness behind Cloudflare/Varnish
  • Password hashing uses Argon2id with tuned parameters (bcrypt fallback)

v1.5.5 — Category/Tag Management & Branding (2026-07-14)

New Features:

  • Category & Tag Deletion — Delete categories and tags with automatic cleanup of post references
  • Slug Collision Resolution — Auto-appends -2, -3, etc. when a slug already exists
  • Duplicate Prevention — Case-insensitive name matching rejects exact duplicates; slug collisions auto-resolved

Improvements:

  • No-cache headers on admin categories page for CSRF token freshness behind Cloudflare/Varnish
  • Branding updated to Digital Systems LLC / AfterPacket
  • Removed duplicate version display in public footer
  • Admin categories page now shows post count per category/tag

Bug Fixes:

  • Fixed PHP syntax error in addCategory() return statement
  • Fixed delete confirmation dialog quoting issues
  • Fixed CSRF token invalidation on category/tag management page

v1.5.4 — Install Cleanup & Update Channels (2026-07-14)

Critical Bug Fixes:

  • [CRITICAL] Admin session logout on idle — session fingerprint validation was destroying sessions when IP or User-Agent shifted between requests behind Cloudflare/Varnish proxies. Now logs a warning and updates the fingerprint instead of destroying the session.
  • [CRITICAL] Argon2id password hash corruption — hashes containing $ characters were corrupted by PHP variable interpolation in double-quoted strings, causing admin login failures. ADMIN_PASSWORD_HASH now uses single quotes.
  • [CRITICAL] Updater "Requested version not found in manifest" error — Upgrader required exact version string match. Now uses the manifest version directly and only checks that it's newer than the current version. Update cache also cleared before upgrading.

Improvements:

  • Session regenerate interval increased from 30 minutes to 4 hours (less disruption for users)
  • config.php has prominent DO NOT OVERWRITE warning header
  • Added config.php.example as install template — installer copies from example if config.php doesn't exist
  • includes/config.php removed from update manifest — updater will never overwrite it
  • Built-in updater now has real SHA-256 hashes for integrity verification
  • Added generate_manifest.sh script for maintainers to generate release manifests

v1.5.2 — Config Protection (2026-07-14)

Critical Bug Fix:

  • [CRITICAL] Password hash corruption fix (same root cause as v1.5.3, addressed in config.php template and installer)

Improvements:

  • Added config.php.example as install template
  • Installer copies from config.php.example; uses single-quote replacement for password hash
  • Removed config.php from update manifest

v1.5.1 — Pen Test Hardening (2026-07-14)

Security Fixes:

  • [MEDIUM] Rate limiting added to post password attempts (5 per IP per 5 minutes) — prevents brute-force attacks against password-protected posts.
  • [MEDIUM] Session fingerprint now hashes User-Agent with SHA-256 — strengthens session binding beyond IP-only, format: sha256(ip | sha256(user_agent)).
  • [MEDIUM] Site URL setting validation added — filter_var(FILTER_VALIDATE_URL) and scheme whitelist (http/https) prevent open redirect and XSS via malicious URL values.
  • [MEDIUM] Cross-Origin isolation headers added — Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin prevent cross-origin information leakage.
  • [MEDIUM] Comment author name sanitization — HTML tags stripped, length capped at 100 characters. Email validated when provided.
  • [MEDIUM] Per-user daily upload rate limit added (50 uploads/day per user) alongside existing per-IP hourly limit.

v1.5.0 — Security + Subfolder Install Fix (2026-07-13)

Security Fixes:

  • [HIGH] Removed CSRF token from image upload URL query string — tokens were being logged in server access logs and browser history. Now sent only via X-CSRF-Token header and POST body.
  • [HIGH] Removed data: from public CSP img-src — prevents SVG-based XSS through data:image/svg+xml URIs. Admin CSP still allows data: for TinyMCE paste compatibility.
  • [HIGH] HSTS header now respects TRUST_PROXY_HEADERS — previously only checked $_SERVER["HTTPS"], which is empty behind Cloudflare/Varnish. Sites behind proxies now correctly send HSTS.
  • [MEDIUM] Short URL redirect (s.php) changed from 301 to 302 — prevents browser cache poisoning if target changes.
  • [MEDIUM] Short URL redirect now validates resolved slug corresponds to a published post — prevents open redirect.
  • [MEDIUM] Post password hashing upgraded from bcrypt to Argon2id with fallback.
  • [MEDIUM] Removed ini_set() calls for allow_url_fopen/allow_url_include — these are PHP_INI_SYSTEM directives. Added comments for php.ini configuration.
  • [LOW] Removed debug error_log() from ImageUpload.php.
  • [LOW] Fixed Content-Disposition filename escaping in serve-image.php.

Bug Fixes:

  • [CRITICAL] All internal links now use cms_path() — fixes broken pagination, search, admin links, RSS, and comment forms in subfolder installs.
  • [CRITICAL] Fixed index.php line 469 — missing ?> closing tag caused 500 parse error on PHP 8.x.

v1.4.1 — Patch Release (2026-07-13)

Security Fixes:

  • [HIGH] Password input fields for post protection were type="text" — changed to type="password".
  • [HIGH]ENABLE_UPLOAD_MALWARE_SCAN was false by default — changed to true.
  • [MEDIUM] Session cookie secure flag now respects TRUST_PROXY_HEADERS.
  • [MEDIUM] Removed debug console.log statements from create/edit post pages.

Bug Fixes:

  • Image URL insertion: TinyMCE valid_elements now allows class and style on <img>.
  • Image paste: Enabled paste_data_images: true in TinyMCE.
  • RSS self-link: Now uses cms_path() for correct URLs in subfolder installs.

v1.4.0 — Security Hardening Release (2026-07-13)

Critical Fixes:

  • [CRITICAL] Removed Remote Code Execution vector in upgrade system — download_url no longer accepted from POST data. Upgrades use performUpgradeFromManifest() with hardcoded manifest URL. SHA-256 checksums mandatory. Auto-upgrade disabled.
  • [CRITICAL] Added credential placeholder detection — warns if REPLACE_ME_* defaults are still in place.

High Fixes:

  • [HIGH] Replaced regex XSS sanitizer with DOM-based HTML purification (DOMDocument + XPath). Regex fallback for servers without dom extension.
  • [HIGH] CSP headers enabled by default.
  • [HIGH] CSRF tokens now single-use for all forms — removed image_upload/edit_post_form reuse exception.
  • [HIGH] Session fingerprint gated behind TRUST_PROXY_HEADERS config (default: false).
  • [HIGH] Removed version disclosure header (X-SecureBlogCMS-Version) — only sent if SHOW_VERSION_HEADER is explicitly true.
  • [HIGH] Password protection enforced on public pages. Private posts hidden from listings, search, and RSS.

Medium Fixes:

  • Role validation whitelist enforced in addUser() and updateUser().
  • Password hashing unified to Argon2id across all user management.
  • Rate limiting on comment submissions (3/IP/hour).
  • Install directory .htaccess hardened.
  • CORS credentials set to false on image endpoints.
  • Debug logging reduced in upload endpoint.
  • Error reporting hardened to E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE.

Version: 1.5.6
Last Updated: 2026-07-14
Created by: Digital Systems LLC / AfterPacket Security Level: High

About

SQL-free PHP blog CMS with file-based JSON storage, full admin UI, IPFS pinning, static export, and security-first architecture

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Secure Blog CMS (SQL-Free, File-Based)

Secure Blog CMS is a security-first PHP blogging platform that stores content in JSON files instead of a database. It includes a full admin UI, comment moderation, image uploads, and a Resilience Center for static exports and IPFS pinning.

Features

Publishing

  • File-based storage (no SQL/database required)
  • Drafts and published posts, slugs, excerpts, and pagination
  • Optional search, private posts, and password-protected posts
  • RSS feed generation
  • Image uploads with server-side security checks

Admin and Users

  • Admin dashboard for posts, comments, users, and settings
  • Roles: admin, editor, author
  • Comment moderation (pending/approved/spam/trash)
  • Backups and restore from the admin UI

Resilience Center

  • Static site export (HTML + RSS) for static hosting
  • ZIP bundles for easy distribution
  • Optional auto-pinning to IPFS via Pinata
  • Export bundles stored in data/exports/

Security

  • CSRF protection on all forms (single-use tokens, no replay)
  • XSS sanitization and output escaping (DOM-based HTML purification)
  • CSP headers enabled by default, plus standard HTTP security headers
  • Rate limiting (login, comments, uploads, short URLs, post passwords)
  • Account lockout after failed login attempts
  • Session hardening with IP + User-Agent fingerprinting
  • Security event logging to data/logs/
  • Mandatory SHA-256 checksums on upgrades; auto-upgrade disabled for safety
  • Proxy header spoofing protection (Cloudflare/X-Forwarded headers gated behind config toggle)
  • COOP/CORP security headers for cross-origin isolation
  • Permissions-Policy header to restrict browser APIs
  • HSTS with preload and includeSubDomains (respects proxy headers)
  • Comment author name sanitization (strip tags, length limit, email validation)
  • Site URL validation (scheme whitelist prevents javascript: and data: URLs)
  • Per-user daily upload rate limiting (50/day per user)
  • Post password brute-force protection (5 attempts per IP per 5 minutes)

Built-In Updater

  • Check for updates from the admin panel
  • Download and verify files with SHA-256 integrity checks
  • Automatic backup before upgrade
  • Config file never overwritten during updates
  • One-click upgrade process

Requirements

  • PHP 7.4+ (PHP 8.x recommended)
  • Web server (Apache/Nginx) or PHP built-in server
  • Write access to the data/ directory
  • Extensions (optional but recommended):
    • curl for Pinata IPFS pinning and in-app updates
    • zip for export ZIP bundles
    • dom for DOM-based HTML sanitization (fallback regex available)

Quick Start

Option A: Installer (recommended)

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Visit /install/index.php in your browser and complete the wizard.
  4. After install, keep data/installed.lock in place (re-install requires deleting it).
  5. Delete the install/ directory after installation for best security.

Option B: Manual install

  1. Copy the project into your web root.
  2. Ensure the data/ directory is writable by the web server.
  3. Generate an Argon2id password hash (use single quotes to avoid $ interpretation):
cat > /tmp/hashpass.php << 'EOF'<?phpecho password_hash('YourSecurePassword123!', PASSWORD_ARGON2ID) . PHP_EOL;EOF
php /tmp/hashpass.php
  1. Update includes/config.php with your credentials (use single quotes for the hash):
define('ADMIN_USERNAME', 'your_username');
define('ADMIN_PASSWORD_HASH', '$argon2id$v=19$m=65536,...'); // single quotes!
  1. Update site settings in includes/config.php or the admin UI.
  2. Open /admin.php and log in.

⚠️Important: Always use single quotes around ADMIN_PASSWORD_HASH. Argon2id hashes contain $ characters which PHP interprets as variable references inside double-quoted strings, corrupting the hash and breaking login.

Reverse Proxy / Cloudflare Setup

If your CMS is behind Cloudflare or a reverse proxy that sets X-Forwarded-For or CF-Connecting-IP headers, enable trusted proxy mode in includes/config.php:

define('TRUST_PROXY_HEADERS', true);

Warning: Only enable this when actually behind a trusted proxy. Enabling it without a proxy allows IP spoofing and session bypass.

Configuration

Site settings (recommended)

Most settings are managed in the admin UI at /admin/settings.php and stored in:

  • data/settings/site.json

These settings override defaults from includes/config.php.

hCaptcha (comments)

Comments can require hCaptcha. Configure via environment variables (recommended):

  • HCAPTCHA_SITEKEY
  • HCAPTCHA_SECRET

Or set hcaptcha_sitekey in data/settings/site.json and keep the secret in env.

Pinata (IPFS)

Configure Pinata credentials in /admin/settings.php to enable auto-pinning of exports.

Updating

In-app updater

  • Go to /admin/upgrade.php and check for updates.
  • The upgrader downloads update/manifest.json from the configured update source.
  • All file updates require SHA-256 checksum verification — no file is written without integrity verification.
  • includes/config.php is never overwritten — your credentials and settings are preserved.
  • Auto-upgrade has been disabled for security. All upgrades must be manually triggered.

Manual update

  1. Backup data/ and includes/config.php.
  2. Replace application files with the new release.
  3. Do NOT overwrite includes/config.php — preserve your existing credentials and settings.
  4. Re-check your settings and log in to confirm.

Creating a release

For maintainers, use the generate_manifest.sh script to prepare updates:

cd update/
./generate_manifest.sh 1.6.0 "Description of changes"# Then commit, tag, and push:
git add update/ && git commit -m "v1.6.0: update manifest"
git tag -a v1.6.0 -m "v1.6.0"
git push origin main --tags

Project Layout

secure-blog-cms/
admin/ Admin UI (posts, comments, users, settings, resilience, upgrade)
cli/ CLI utilities (password reset)
data/ JSON data storage (posts, users, comments, logs, backups)
includes/ Core classes (Security, Storage, Comments, Resilience, Uploads)
install/ Installation wizard (delete after install)
templates/ Public templates
update/ Update packages, manifest, and release files
index.php Public homepage
post.php Single post view
rss.php RSS feed
s.php Short URL redirect handler

Deployment Notes

Nginx (recommended)

A sample nginx config is included as nginx.conf with:

  • Pretty URL rewrites (WordPress-style /post/slug/, /category/tech/, etc.)
  • Security deny rules for data/, includes/, install/, and cli/ directories
  • Static file caching headers

CloudPanel / Varnish

When deploying behind CloudPanel with Varnish:

  1. Set TRUST_PROXY_HEADERS to true in includes/config.php
  2. Add nginx deny rules for data/, includes/, install/, and cli/ directories
  3. Ensure parent directory permissions are 755 (CloudPanel may reset to 770)
  4. Delete the install/ directory after setup

Important: Config Protection

  • includes/config.php contains your admin credentials and site settings
  • Never overwrite it during updates — the updater skips it automatically
  • includes/config.php.example is provided as a reference template
  • When deploying manually, always exclude includes/config.php from file copies
  • The ADMIN_PASSWORD_HASH must use single quotes (not double quotes) to prevent PHP from interpreting $ in Argon2id hashes

Changelog

v1.5.6 — User Management & Security (2026-07-14)

New Features:

  • User Management — Create, edit, and delete users from the admin panel
  • Password Policy — Enforced minimum 12 characters with uppercase, lowercase, digit, and special character requirements
  • Role-Based Permissions — Admin (full access), Editor (publish/edit any post, moderate comments), Author (create/edit own posts only)
  • Password Strength Meter — Visual strength indicator on user creation and edit forms
  • Edit User Modal — Change role and password with admin password confirmation required
  • Self-Demotion Protection — Admins cannot demote themselves to a lower role
  • CLI Password Resetcli/reset_password.php utility for emergency password resets when locked out; interactive mode avoids shell expansion of special characters
  • Installer Password Policy — Visual checklist enforces password requirements during installation
  • Special Character Safety — Passwords with $, !, *, etc. are properly handled throughout the system (CLI, admin UI, installer, JSON storage)

Security:

  • nginx.conf updated: added cli/ to blocked directories (both Option A and Option B)
  • cli/.htaccess denies all web access to CLI scripts

Improvements:

  • admin/users.php — No-cache headers for CSRF token freshness behind Cloudflare/Varnish
  • Password hashing uses Argon2id with tuned parameters (bcrypt fallback)

v1.5.5 — Category/Tag Management & Branding (2026-07-14)

New Features:

  • Category & Tag Deletion — Delete categories and tags with automatic cleanup of post references
  • Slug Collision Resolution — Auto-appends -2, -3, etc. when a slug already exists
  • Duplicate Prevention — Case-insensitive name matching rejects exact duplicates; slug collisions auto-resolved

Improvements:

  • No-cache headers on admin categories page for CSRF token freshness behind Cloudflare/Varnish
  • Branding updated to Digital Systems LLC / AfterPacket
  • Removed duplicate version display in public footer
  • Admin categories page now shows post count per category/tag

Bug Fixes:

  • Fixed PHP syntax error in addCategory() return statement
  • Fixed delete confirmation dialog quoting issues
  • Fixed CSRF token invalidation on category/tag management page

v1.5.4 — Install Cleanup & Update Channels (2026-07-14)

Critical Bug Fixes:

  • [CRITICAL] Admin session logout on idle — session fingerprint validation was destroying sessions when IP or User-Agent shifted between requests behind Cloudflare/Varnish proxies. Now logs a warning and updates the fingerprint instead of destroying the session.
  • [CRITICAL] Argon2id password hash corruption — hashes containing $ characters were corrupted by PHP variable interpolation in double-quoted strings, causing admin login failures. ADMIN_PASSWORD_HASH now uses single quotes.
  • [CRITICAL] Updater "Requested version not found in manifest" error — Upgrader required exact version string match. Now uses the manifest version directly and only checks that it's newer than the current version. Update cache also cleared before upgrading.

Improvements:

  • Session regenerate interval increased from 30 minutes to 4 hours (less disruption for users)
  • config.php has prominent DO NOT OVERWRITE warning header
  • Added config.php.example as install template — installer copies from example if config.php doesn't exist
  • includes/config.php removed from update manifest — updater will never overwrite it
  • Built-in updater now has real SHA-256 hashes for integrity verification
  • Added generate_manifest.sh script for maintainers to generate release manifests

v1.5.2 — Config Protection (2026-07-14)

Critical Bug Fix:

  • [CRITICAL] Password hash corruption fix (same root cause as v1.5.3, addressed in config.php template and installer)

Improvements:

  • Added config.php.example as install template
  • Installer copies from config.php.example; uses single-quote replacement for password hash
  • Removed config.php from update manifest

v1.5.1 — Pen Test Hardening (2026-07-14)

Security Fixes:

  • [MEDIUM] Rate limiting added to post password attempts (5 per IP per 5 minutes) — prevents brute-force attacks against password-protected posts.
  • [MEDIUM] Session fingerprint now hashes User-Agent with SHA-256 — strengthens session binding beyond IP-only, format: sha256(ip | sha256(user_agent)).
  • [MEDIUM] Site URL setting validation added — filter_var(FILTER_VALIDATE_URL) and scheme whitelist (http/https) prevent open redirect and XSS via malicious URL values.
  • [MEDIUM] Cross-Origin isolation headers added — Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin prevent cross-origin information leakage.
  • [MEDIUM] Comment author name sanitization — HTML tags stripped, length capped at 100 characters. Email validated when provided.
  • [MEDIUM] Per-user daily upload rate limit added (50 uploads/day per user) alongside existing per-IP hourly limit.

v1.5.0 — Security + Subfolder Install Fix (2026-07-13)

Security Fixes:

  • [HIGH] Removed CSRF token from image upload URL query string — tokens were being logged in server access logs and browser history. Now sent only via X-CSRF-Token header and POST body.
  • [HIGH] Removed data: from public CSP img-src — prevents SVG-based XSS through data:image/svg+xml URIs. Admin CSP still allows data: for TinyMCE paste compatibility.
  • [HIGH] HSTS header now respects TRUST_PROXY_HEADERS — previously only checked $_SERVER["HTTPS"], which is empty behind Cloudflare/Varnish. Sites behind proxies now correctly send HSTS.
  • [MEDIUM] Short URL redirect (s.php) changed from 301 to 302 — prevents browser cache poisoning if target changes.
  • [MEDIUM] Short URL redirect now validates resolved slug corresponds to a published post — prevents open redirect.
  • [MEDIUM] Post password hashing upgraded from bcrypt to Argon2id with fallback.
  • [MEDIUM] Removed ini_set() calls for allow_url_fopen/allow_url_include — these are PHP_INI_SYSTEM directives. Added comments for php.ini configuration.
  • [LOW] Removed debug error_log() from ImageUpload.php.
  • [LOW] Fixed Content-Disposition filename escaping in serve-image.php.

Bug Fixes:

  • [CRITICAL] All internal links now use cms_path() — fixes broken pagination, search, admin links, RSS, and comment forms in subfolder installs.
  • [CRITICAL] Fixed index.php line 469 — missing ?> closing tag caused 500 parse error on PHP 8.x.

v1.4.1 — Patch Release (2026-07-13)

Security Fixes:

  • [HIGH] Password input fields for post protection were type="text" — changed to type="password".
  • [HIGH]ENABLE_UPLOAD_MALWARE_SCAN was false by default — changed to true.
  • [MEDIUM] Session cookie secure flag now respects TRUST_PROXY_HEADERS.
  • [MEDIUM] Removed debug console.log statements from create/edit post pages.

Bug Fixes:

  • Image URL insertion: TinyMCE valid_elements now allows class and style on <img>.
  • Image paste: Enabled paste_data_images: true in TinyMCE.
  • RSS self-link: Now uses cms_path() for correct URLs in subfolder installs.

v1.4.0 — Security Hardening Release (2026-07-13)

Critical Fixes:

  • [CRITICAL] Removed Remote Code Execution vector in upgrade system — download_url no longer accepted from POST data. Upgrades use performUpgradeFromManifest() with hardcoded manifest URL. SHA-256 checksums mandatory. Auto-upgrade disabled.
  • [CRITICAL] Added credential placeholder detection — warns if REPLACE_ME_* defaults are still in place.

High Fixes:

  • [HIGH] Replaced regex XSS sanitizer with DOM-based HTML purification (DOMDocument + XPath). Regex fallback for servers without dom extension.
  • [HIGH] CSP headers enabled by default.
  • [HIGH] CSRF tokens now single-use for all forms — removed image_upload/edit_post_form reuse exception.
  • [HIGH] Session fingerprint gated behind TRUST_PROXY_HEADERS config (default: false).
  • [HIGH] Removed version disclosure header (X-SecureBlogCMS-Version) — only sent if SHOW_VERSION_HEADER is explicitly true.
  • [HIGH] Password protection enforced on public pages. Private posts hidden from listings, search, and RSS.

Medium Fixes:

  • Role validation whitelist enforced in addUser() and updateUser().
  • Password hashing unified to Argon2id across all user management.
  • Rate limiting on comment submissions (3/IP/hour).
  • Install directory .htaccess hardened.
  • CORS credentials set to false on image endpoints.
  • Debug logging reduced in upload endpoint.
  • Error reporting hardened to E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE.

Version: 1.5.6
Last Updated: 2026-07-14
Created by: Digital Systems LLC / AfterPacket Security Level: High

About

SQL-free PHP blog CMS with file-based JSON storage, full admin UI, IPFS pinning, static export, and security-first architecture

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages