Area: Cross-cutting infra (audit p14) · Surface: GateMiddleware / mobieusGate paywall (POST writes on gated surfaces) · Dimension: security · Severity: major
OWASP A01 Broken Access Control. When an operator configures a gated surface in 'write_gated' mode (read free, writes require a paid mobieusGate subscription), GateMiddleware sets a flag `$_REQUEST['_gate_write_blocked'] = true` and then returns, allowing the request to proceed to the controller. No controller, model, or service ever reads that flag. The result is that a non-subscribed (or anonymous-with-session) user can perform every write action on a 'write_gated' surface — post threads/replies, edit, etc. — completely bypassing the paywall the operator believes is enforced. The middleware silently degrades a paid access-control gate into a no-op.
Evidence
platform/src/Middleware/GateMiddleware.php:68-71 — `if ($result['mode'] === 'write_gated') { $_REQUEST['_gate_write_blocked'] = true; return; }`. Grepping the whole tree: `grep -rn "_gate_write_blocked" src/` returns ONLY the write site in GateMiddleware.php:69 — there is no consumer anywhere. GateAccessResolver.php:33 sets `$result['mode'] = $mode` where mode comes from `GateSurfaceConfig::getMode(...)`, so `write_gated` is a real, operator-selectable surface mode.
Suggested fix. Either enforce the block in the middleware (for state-changing methods POST/PUT/PATCH/DELETE on a write_gated surface, render the paywall / return 402 like read_gated does, instead of just setting a flag), or have BaseController (or each write handler) consume `$_REQUEST['_gate_write_blocked']` and abort with 402. Add a regression test that a non-subscriber POST to a write_gated forum is rejected.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus