Area: mobieusAI (audit p11) · Surface: AI — mobieusAI community (legacy AiController) · Dimension: security · Severity: major
OWASP A04:2021 Insecure Design / lack of resource-consumption controls (and a business-logic access-control gap). The newer AI surfaces are explicitly designed so every Anthropic call passes AIFeatureGate (tier + monthly spend cap + per-user daily cap). The three legacy /api/ai/* endpoints skip that gate entirely, so any authenticated member can drive unbounded Anthropic spend on the tenant's BYOK key (each call hits api.anthropic.com), and the tenant admin's configured monthly_cap_cents / rate_per_user_per_day for these features is silently ineffective. searchAssist in particular runs an LLM synthesis on every POST with only a 200-char query length check.
Evidence
platform/src/Controllers/AiController.php gates only on AnthropicClient::enabled() + Features::granted() — e.g. searchAssist (lines 75-113), suggestTags (lines 59-72), summarizeThread (lines 41-52). AnthropicClient::enabled() (platform/src/Services/AnthropicClient.php:66-71) checks ONLY `$_ENV['AI_FEATURES_ENABLED']==='1'` and a non-empty `$_ENV['ANTHROPIC_API_KEY']` — no plan check, no spend cap, no rate cap. The canonical gate AIFeatureGate::check() (platform/src/Services/AI/AIFeatureGate.php:30-90) enforces tenant-plan eligibility (line 45-49), monthly_cap_cents (line 67-75) and rate_per_user_per_day (line 77-87) — none of which the legacy controller calls. The underlying legacy services confirm no gate: AiTagSuggester.php:20 and AiSearchAssist.php:23 each only check AnthropicClient::enabled(). The only throttle on these routes is the generic HTTP RateLimitMiddleware at 1800 req/min/path (platform/src/Middleware/RateLimitMiddleware.php:75), which is far above any sane LLM-cost budget.
Suggested fix. Route every legacy AI action through AIFeatureGate::check($featureKey, $userId) before invoking the service (return ai_disabled/gate_blocked on false), or decommission the legacy routes (routes.php:1094-1096) now that MemberAIController/AIAssistController enforce the gate. AISpendTracker::record() should also be called on these paths so spend is accounted.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus