Area: mobieusKnow (audit p6) · Surface: GET /know/{slug}/history (KnowledgeController@history) · Dimension: security · Severity: major
OWASP A01:2021 Broken Access Control / sensitive information disclosure. The reject reason is authored by a moderator and may name the submitter, quote the offending content, or describe internal policy. Both the existence and the metadata of pending/rejected revisions are exposed publicly, complementing finding #1 (which leaks the bodies). Combined, an anonymous user gets the full moderation trail.
Evidence
history() (KnowledgeController.php:252-269) has no requireAuth and is in the public route group (routes.php:589). It calls KnowledgeRevision::forPage() which returns EVERY status: `WHERE kr.page_id = :pid ORDER BY kr.id DESC` with no status filter (KnowledgeRevision.php:43-54). history.php renders all of them including the status badge and the moderator's free-text rejection note: line 64 `<?= $e(str_replace('_', ' ', $rev['status'])) ?>` and lines 72-75 `<?php if (!empty($rev['reject_reason'])): ?> Rejected: <?= $e($rev['reject_reason']) ?>`. The `isMod` flag (history.php:13) only gates the Restore button, not visibility of pending/rejected rows.
Suggested fix. Filter forPage() to approved/rolled_back for non-moderators (pass a viewer-is-mod flag, or add forPagePublic()). Hide reject_reason and pending/rejected rows from non-moderators in history.php.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus