Forums Bug Reports Thread

Moderation/content-filter AI endpoints are role-gated but NOT feature-flag-gated — direct POST runs paid AI on tenants with the feature OFF

Patrick Bass · Jun 6 · 15 · 1 Locked
[Major] [High Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 8:20pm

Area: Admin deep-dive (trust/safety) (audit p15a) · Surface: POST /admin/moderation/{id}/ai/classify, /ai/reply-drafts, /admin/moderation/ai/triage, /admin/content-filters/ai/suggest, /ai/policy (Admin\ModerationAIController) · Dimension: law-8-ai-gating / authorization · Severity: major

The five AI assist endpoints hide their buttons in the template when the tenant's AI feature flag is off, but the controller actions themselves only enforce role + CSRF. A mod/admin (or any script holding a valid CSRF token) can POST directly to these routes on a tenant that has not enabled or purchased the AI feature, triggering live Anthropic API calls against the tenant's key/budget. CLAUDE.md is explicit: gate at render time AND at route registration; gating only in the template leaks the capability. This is both an entitlement-bypass and an uncontrolled-cost issue.

Evidence

src/Controllers/Admin/ModerationAIController.php — every handler starts with only `$this->requireRole(3|4); $this->validateCsrf();` and never checks Features::granted(...). grep for `granted|Features|feature` in that controller returns nothing. Yet the UI gates the buttons: templates/admin/moderation/show.php:208-210 `$aiClassifyOn = ...->granted('ai_forum_report_classify'); $aiDraftsOn = ...->granted('ai_forum_mod_drafts'); if (!$isResolvedLike && ($aiClassifyOn || $aiDraftsOn)):`; moderation/index.php:405 gates triage on `granted('ai_mod_bulk_triage')`; content-filters/index.php:121-122 gate on `granted('ai_forum_rule_suggest')`/`granted('ai_content_filter_assist')`. routes.php:1627-1633 comment says only "admin-role-gated in the controller." These flags default=false (src/Services/Features.php:246-250).

Suggested fix. Add a Features::granted(...) check at the top of each ModerationAIController action (after requireRole/validateCsrf), returning the JSON `{ok:false,error:'feature_disabled'}` with 403 when the matching flag is off — classify→ai_forum_report_classify, replyDrafts→ai_forum_mod_drafts, bulkTriage→ai_mod_bulk_triage, suggestRules→ai_forum_rule_suggest, policyToRules→ai_content_filter_assist. Mirror the exact flag keys the templates already use.

Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 5:25am

Resolved — fixed and deployed. Commit dd336ac47616, shipped dev-first then to all tenants on 2026-06-06.

Added a requireFeature() helper that returns JSON {ok:false,error:'feature_disabled'} with HTTP 403 when the flag is off, and called it after requireRole/validateCsrf in all five actions: classify->ai_forum_report_classify, replyDrafts->ai_forum_mod_drafts, bulkTriage->ai_mod_bulk_triage, suggestRules->ai_content_filter_assist, policyToRules->ai_content_filter_assist. Uses the injected $this->features->granted() pattern; flag keys verified against Features.php and FeatureCatalog.php. php -l passes.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.