fix: suppress verbose PHP errors and bound login/audit housekeeping cost #41

Merged
GeoffMoore1979 merged 1 commits from feature/media-users-pages into master 2026-10-02 12:28:28 +02:00
GeoffMoore1979 commented 2026-10-02 12:24:30 +02:00 (Migrated from github.com)

Summary

Three small hardening fixes found during a self-review security pass over the #40 changes, applied on top of that already-merged branch:

  • cms/bootstrap.php: display_errors/display_startup_errors are now explicitly disabled (errors still reach PHP's own error log via log_errors). Previously an uncaught engine-level error raised before CMSController's try/catch could render a stack trace with filesystem paths straight to the client, depending on the host's php.ini defaults.
  • cms/src/NeoCMS/LoginRateLimiter.php: the login-attempt state file now caps tracked address/identity buckets at 20,000, evicting the least recently active first. Previously a distributed flood of distinct addresses or login names (each staying under the per-bucket threshold) could grow login-attempts.json without bound.
  • cms/src/NeoCMS/Logger.php: expired audit logs are now pruned probabilistically (1-in-100 requests) instead of on every single request. A Logger is constructed on every request, and the previous unconditional prune did a full directory scan + filemtime() per rotated log file every time — the same approach PHP's own session garbage collector uses (session.gc_probability/gc_divisor).

No behaviour change for normal use; all three are defence-in-depth against low-probability/high-volume scenarios.

Test plan

  • php -l on all three changed files
  • php tests/run.php passes (full suite, unchanged)

🤖 Generated with Claude Code

## Summary Three small hardening fixes found during a self-review security pass over the #40 changes, applied on top of that already-merged branch: - **`cms/bootstrap.php`**: `display_errors`/`display_startup_errors` are now explicitly disabled (errors still reach PHP's own error log via `log_errors`). Previously an uncaught engine-level error raised before `CMSController`'s try/catch could render a stack trace with filesystem paths straight to the client, depending on the host's `php.ini` defaults. - **`cms/src/NeoCMS/LoginRateLimiter.php`**: the login-attempt state file now caps tracked address/identity buckets at 20,000, evicting the least recently active first. Previously a distributed flood of distinct addresses or login names (each staying under the per-bucket threshold) could grow `login-attempts.json` without bound. - **`cms/src/NeoCMS/Logger.php`**: expired audit logs are now pruned probabilistically (1-in-100 requests) instead of on every single request. A `Logger` is constructed on every request, and the previous unconditional prune did a full directory scan + `filemtime()` per rotated log file every time — the same approach PHP's own session garbage collector uses (`session.gc_probability`/`gc_divisor`). No behaviour change for normal use; all three are defence-in-depth against low-probability/high-volume scenarios. ## Test plan - [x] `php -l` on all three changed files - [x] `php tests/run.php` passes (full suite, unchanged) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.