Forums Bug Reports Thread

Missing CSRF on state-changing AI POST endpoints /api/help/ai/*

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

Area: mobieusHelp (audit p7) · Surface: mobieusHelp /api/help/ai/* (AgentAIController) · Dimension: security · Severity: major

OWASP A01 / CSRF. These endpoints mutate state indirectly (categorize/auto-suggest spend tenant AI budget; AISpendTracker writes) and read sensitive ticket data, but accept cross-site POSTs from any origin while the victim agent is logged in, with no anti-CSRF token. An attacker page can force an authenticated agent's browser to fire these, draining the tenant Anthropic key and exfiltrating nothing directly but enabling forced-spend abuse.

Evidence

Routes /api/help/ai/reply-suggest, /summary, /categorize, /sentiment, /canned-generate, /audit-qa, /resolution-predict are POST (routes.php L756-762) registered in the group opened at routes.php L340: `$router->group('', [PerformanceMiddleware, ProbeDetectionMiddleware, IpBanMiddleware, LlmBotBlockMiddleware, MaintenanceMiddleware, AccessModeMiddleware, LurkerTrackerMiddleware, SuspensionGateMiddleware, GateMiddleware], ...)` — this list contains NO CsrfValidationMiddleware. AgentAIController has no $this->validateCsrf() call anywhere (it only checks $_SESSION['user_id'] in __construct, L39).

Suggested fix. Add CsrfValidationMiddleware to these route registrations (or move them into the authenticated group at routes.php L775 which includes CsrfValidationMiddleware), and/or call $this->validateCsrf() in AgentAIController. The app's app.min.js already injects X-CSRF-TOKEN on fetch, so legitimate callers are unaffected.

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


Patrick Bass
@mobieus

🚀 Jun 7, 2026 4:59am

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

Added defense-in-depth $this->validateCsrf() as the first statement in all 7 POST handlers (replySuggest, summary, categorize, sentiment, cannedGenerate, auditQA, resolutionPredict). validateCsrf() (inherited from BaseController) reads _POST['_csrf_token'] or the HTTP_X_CSRF_TOKEN header that app.min.js injects, so legitimate fetch callers are unaffected while cross-site POSTs are rejected. The GET endpoint knowledgeGaps() was intentionally left untouched since GET is idempotent and carries no token.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.