fix: harden CMS security controls

This commit is contained in:
Andy Dixon
2026-06-08 17:12:42 +01:00
parent 42fe5b9304
commit a636ecd991
297 changed files with 174942 additions and 7485 deletions
+240 -22
View File
@@ -1,52 +1,270 @@
# NeoCMS
## Version 2.0
## Version 3.0
Welcome to NeoCMS, the super lightweight, database-free content management system! Built with PHP and JavaScript, NeoCMS
is designed to be fast and easy to use. No complicated setup required—just drop it into your static website, and you’re
good to go.
Welcome to NeoCMS, the lightweight, database-free content management system. Built with PHP and JavaScript, NeoCMS is
designed to add controlled visual editing to an existing static website without changing how public pages are served.
Version 2.0 is a complete overhaul to support the latest versions of jQuery and TinyMCE (GPL). We’ve made things even
smoother for your content editing experience.
Version 3.0 adds filesystem-backed drafts, revision history, role-based permissions, scheduled publishing, page and media
management, SEO controls, shared content, navigation menus, responsive previews, accessibility checks, and an operational
dashboard. Public content remains ordinary HTML; the database server may continue its well-earned rest.
Further technical documentation:
* [Architecture](docs/ARCHITECTURE.md)
* [Development and testing](docs/DEVELOPMENT.md)
## Features
* No Database Required: NeoCMS works without a database, so you don’t have to worry about setup hassles or performance
slowdowns.
* Easy Content Editing: With our intuitive WYSIWYG editor, anyone can update content without writing a single line of
code. Adding the additional class *neo-dupe* will allow an element and it's children to be cloned.
* Easy Content Editing: The TinyMCE visual editor updates marked content regions without requiring authors to edit HTML.
Adding `neo-dupe` allows an element and its children to be cloned or removed.
* Seamless Integration: Simply add the CMS to any existing static website. Just assign a special class to the elements
you want to make editable, and you’re all set.
* Consistent Layouts: NeoCMS supports page templates to keep your layouts neat and uniform across all pages.
* Drafts and Publishing: Editors can save drafts without changing the public page. Publishers can publish immediately or
schedule a publication.
* Revision History: Every changed or deleted page receives a timestamped filesystem snapshot that publishers can restore.
* Page Management: Search, create, duplicate, rename, and delete editable HTML pages, including pages in subdirectories.
* Media Library: Browse uploaded images, reuse them, maintain alternative text, see usage counts, and remove files.
* SEO Settings: Edit the page title, description, canonical URL, Open Graph image, and robots indexing setting.
* Shared Content: Mark content with `data-neo-shared="name"` and update matching regions across the site.
* Navigation Menus: Build ordered and nested navigation markup and insert it into an editable region.
* Responsive Preview: Preview the current page at desktop, tablet, and mobile widths.
* Accessibility Checks: Check common authoring problems such as missing alt text, empty links, heading jumps, missing labels,
and absent page metadata before publishing.
* Roles and Activity: Use editor, publisher, and administrator roles. The dashboard displays drafts, schedules, system
problems, and recent activity.
## Installation Guide
Ready to get started? Just follow these simple steps:
To install NeoCMS:
* Drop the CMS folder into your static website.
* Add the uploads folder to your site as well.
* Open /cms/config.php and edit the credentials for any users you want to set up.
* Update the div elements you want to make editable by adding the class editable.
* Visit /cms/, log in, and start editing!
* Place the `cms/` directory in the document root of the static website.
* Place the `uploads/` directory in the same document root.
* Copy `cms/config.local.php.example` to `cms/config.local.php`, add password hashes, and assign roles.
* Update the elements you want to make editable by adding the configured editable class. It is `editable` by default.
* Confirm that PHP may write to the managed HTML files, `cms/data/`, `cms/logs/`, and `uploads/`.
* Visit `/cms/`, log in, and begin editing.
That’s it! You’re up and running.
## Configuration
Tracked defaults live in `cms/config.php`. Deployment credentials and overrides belong in the Git-ignored
`cms/config.local.php`, which must return an array. The supplied Apache rules deny direct access to both files.
| Setting | Type | Purpose |
| --- | --- | --- |
| `authentication` | array | Maps usernames to `password_hash()` values; plaintext credentials are rejected. |
| `roles` | array | Maps usernames to `editor`, `publisher`, or `administrator`. |
| `security` | array | Configures session expiry, login throttling, request limits, storage quotas, and audit-log retention. |
| `uploads` | array | Configures image size, dimension, file-count, and aggregate-storage limits. |
| `audit` | boolean | Enables daily activity logs under `cms/logs/`. |
| `skipWelcomePage` | boolean | Opens the public root instead of the built-in guide after login. |
| `showFullUrl` | boolean | Displays the current page path in the toolbar. |
| `editableClass` | string | Selects the single CSS class used to identify editable regions. |
`dataDirectory` is an internal override used by the integration suite and specialised deployments. Normal installations
should allow NeoCMS to use `cms/data/`.
### Editable Class
The class used to identify editable regions is configurable:
```php
'editableClass' => 'editable',
```
Set it to any single valid CSS class name without a leading dot, for example:
```php
'editableClass' => 'cms-content',
```
Your page markup must then use that class:
```html
<section class="cms-content">This region can be edited.</section>
```
Invalid configured values fall back to `editable`. The `neo-dupe` class remains fixed because it marks repeatable blocks,
not general editable content. When overriding `editableClass`, update the classes in `cms/templates/` as well so newly
created pages are immediately discoverable by the page picker.
### Users and Roles
Passwords must be generated with `password_hash()` and stored under `authentication` in `cms/config.local.php`. Assign the
same username a role under `roles`:
```php
'authentication' => [
'author' => '$2y$10$...',
'publisher' => '$2y$10$...',
],
'roles' => [
'author' => 'editor',
'publisher' => 'publisher',
],
```
* `editor`: edit content, save drafts, and upload media.
* `publisher`: editor permissions plus immediate and scheduled publishing and revision restoration.
* `administrator`: publisher permissions plus page, media, shared-content, and menu management.
Users without an explicit valid role receive `editor`, the least powerful role. Account removal, password-hash changes,
and role changes affect active sessions on their next request. Sessions expire after 30 minutes of inactivity and 12 hours
in total by default.
When HTTPS terminates at a trusted reverse proxy, set `'cookieSecure' => true` beneath `security` in the local override.
This keeps the session cookie marked Secure even when PHP receives the proxy's internal HTTP request.
Audit files rotate at 10 MiB and are retained for 90 days by default. Override `auditMaxFileBytes` and
`auditRetentionDays` beneath `security` where local retention obligations differ.
### Password Hash Example
Generate a hash on a trusted command line, then place only the resulting hash in `cms/config.local.php`:
```bash
php -r 'echo password_hash(trim(fgets(STDIN)), PASSWORD_DEFAULT), PHP_EOL;'
```
There is deliberately no enabled default account.
## Content Features
### Repeatable Blocks
Add `neo-dupe` alongside the configured editable class. The editor provides clone-before, clone-after, and delete controls:
```html
<article class="editable neo-dupe">Repeatable content</article>
```
### Shared Content
Use `data-neo-shared` to identify a global region:
```html
<footer class="editable" data-neo-shared="site-footer">Shared footer content</footer>
```
The Shared Content tool updates every HTML page containing the same key. Publishing a page also captures the current value
of its shared regions.
### Navigation Menus
Menu items use one line each in this format:
```text
Home | /
About | /about.html
Team | /team.html | About
```
The optional third value is the label of the parent item. After saving, the generated `<nav>` can be inserted into the
currently selected editable region.
### Drafts and Revisions
Drafts do not alter public HTML. When opening a page with a saved draft, NeoCMS offers to load it. Publishing creates a
revision of the previous public file first. Deleting a page also retains its last revision.
NeoCMS stores metadata, drafts, scheduled content, and revisions under `cms/data/`. Back up this directory together with
the website. The supplied `.htaccess` denies direct HTTP access on Apache; other web servers should deny `/cms/data/` and
`/cms/logs/` explicitly. Private directories and files are created with owner-only permissions where the operating system
permits it. Configurable quotas bound drafts, schedules, revisions, and uploaded media to reduce disk-exhaustion risk.
### Scheduled Publishing
Due schedules are processed whenever the CMS API is used. For publication without an active CMS user, run the CLI worker
once per minute with cron:
```cron
* * * * * /usr/bin/php /absolute/path/to/site/cms/publish_scheduled.php >/dev/null 2>&1
```
The server timezone controls PHP date handling; the browser converts the selected local time to an absolute timestamp.
## Administration Workflow
1. Open a page from **Pages**.
2. Select a region carrying the configured editable class and edit it in TinyMCE.
3. Use **Save Draft** to retain private work, or **Publish** to replace the public HTML after creating a revision.
4. Use **Schedule** when publication should occur later.
5. Check revisions, accessibility findings, SEO metadata, shared content, and menus through the relevant toolbar dialogues.
The Save button inside the content-editor modal only applies content to the in-browser preview. It does not write a public
file. This distinction is intentional and prevents an innocent wording change from making an unscheduled public debut.
## Page Markup Recommendations
For the best SEO and accessibility results, each page should include one `<h1>`, a non-empty `<title>`, a meta description,
alternative text on every image, descriptive link text, and labels associated with form fields. The checker highlights
common authoring issues but is not a complete accessibility audit.
## Requirements
* PHP 8 running on Linux
* PHP DOM and fileinfo extensions
* PHP sessions enabled
* Write access to `cms/data/`, `cms/logs/`, `uploads/`, and managed HTML pages
* A modern browser with JavaScript enabled
## Known Bugs & Issues
The administration shell loads jQuery 4.0.0 and jQuery UI 1.14.2 from the official jQuery CDN with Subresource Integrity.
TinyMCE 8.6.0 is bundled locally. Installations that must operate without internet access should self-host the two jQuery
assets and update both `cms/index.php` and the administration Content Security Policy.
* Testing: The system is functional, but it hasn’t been fully tested in all scenarios. Uploading and embedding youtube videos works perfectly!
## Web Server Security
## License
Apache users should allow the supplied `.htaccess` rules. For Nginx, add equivalent rules such as:
NeoCMS is open-source software licensed under the GNU General Public License v3.0 (GPLv3). You are free to use, modify,
and distribute this software under the terms of this license.
```nginx
location ^~ /cms/data/ { deny all; }
location ^~ /cms/logs/ { deny all; }
location ~ ^/cms/(config(?:\.local)?\.php|config\.local\.php\.example|publish_scheduled\.php)$ { deny all; }
location ~ ^/uploads/.*\.(php[0-9]?|phtml|phar|cgi|pl|py|sh)$ { deny all; }
```
For more information, see the full GPLv3 License found in LICENSE.
Also disable directory listings, prevent PHP source download through server misconfiguration, serve the CMS only over
HTTPS, and back up both public HTML and `cms/data/`. NeoCMS sends CSP, anti-framing, no-sniffing, referrer, permissions,
cross-origin isolation, no-store, and HTTPS HSTS headers where appropriate.
Set request-body limits in the web server and PHP as well as in NeoCMS. For the default upload and API limits, a 12 MiB
server limit leaves room for form overhead without letting oversized requests make themselves at home. For example:
```nginx
client_max_body_size 12m;
client_body_timeout 15s;
```
Match `post_max_size` and `upload_max_filesize` to your configured `security.maxRequestBytes` and `uploads.maxFileBytes`.
The page preview iframe is sandboxed with `allow-same-origin` but without `allow-scripts`. This preserves DOM editing while
preventing public page scripts from reaching the parent administration session.
## Testing
Run the dependency-free integration suite with:
```bash
php tests/run.php
```
It exercises authentication hardening, session revocation, login throttling, iframe sandboxing, path containment, content
limits, configurable editable regions, drafts, publication, revisions, page operations, shared content, menus, and
scheduled publishing in temporary directories. See [Development and testing](docs/DEVELOPMENT.md) for the complete check
list used by this repository.
## Licence
NeoCMS is open-source software licensed under the GNU General Public Licence v3.0 (GPLv3). You are free to use, modify,
and distribute this software under the terms of this licence.
For the complete terms, see [LICENSE](LICENSE).
## Found a Bug?
If you run into any issues or have feature requests, please submit them on our GitHub issues page. Your feedback helps
us improve NeoCMS!
Report defects and feature requests through the project's GitHub issue tracker.