AI Chatbot Security & API Contract
Summary (3–6 bullets)
- Defines authentication, RBAC, and tenant/agent data-scoping rules for chatbot queries.
- Documents fail-closed downline-aware authorization policy and enforcement points.
- Specifies endpoint contract, error behavior, rate limits, and audit logging coverage.
- Captures production posture (demo mode disabled) and frontend integration guardrails.
- Includes troubleshooting and monitoring guidance for secure operations.
When to use this (3–6 bullets)
- When implementing or reviewing chatbot auth/RBAC behavior.
- During security incident triage for 401/403 or suspected data-scope violations.
- Before deploying frontend/backend changes that touch chatbot routing or auth headers.
- When validating production hardening and legacy-path compatibility behavior.
What you’ll walk away with (2–5 bullets)
- A complete security contract for chatbot request handling.
- Clear role-based access expectations (admin vs agent/ceo) and denial semantics.
- A deploy/debug checklist for endpoint correctness and safe client behavior.
Authentication
Authenticated Mode (Recommended)
- Include
Authorization: Bearer <JWT>header - JWT contains:
tenant_id,role,agent_name(if agent) - Queries automatically scoped to user’s tenant and agent
Demo Mode (Fallback) - DISABLED IN PRODUCTION
- Production: Demo mode disabled - authentication required (returns 401 if no token)
- Non-Production: Demo mode allowed with restricted
viewerrole (no agent-specific data) - No
Authorizationheader provided (non-prod only) - Restricted keywords blocked: commission, my, agent, downline
- Response prefixed with “[DEMO MODE - CEO View Only]“
Rate Limits
- Demo mode: 30 requests/minute (per IP)
- Authenticated: 60 requests/minute (per user)
- 429 Response: “Too many requests. Please wait…”
Data Scoping
All queries filtered by:tenant_id(from JWT)agent_id(from JWT, canonical identifier)- Role-based access control (see RBAC Policy below)
Downline-Aware RBAC Policy
Policy Overview
The AI chatbot enforces downline-aware Role-Based Access Control (RBAC) to prevent cross-agent data disclosure:role=admin: Can query any agent within tenant (but MUST provide explicit target for agent-scoped requests; no “all agents”)role=agentorrole=ceo: Can query ONLY self + downline hierarchy (including self) within tenant- Fail-closed: Missing role, missing
agent_id(for agent/ceo), unresolvable target, or non-downline access => HTTP 403
Enforcement Points
- Server-side RBAC check at
ai_query_public()entrypoint (before any sensitive BigQuery or LLM calls) - Downline check via
is_in_downline()using hierarchy traversal (auth-only query allowed) - Query layer filters by
authorized_target_agent_id(enforced at entrypoint)
Authorization Flow
- Extract
requested_targetfrom question (agent name or ID) - Resolve
requested_targettoagent_idviadim_agent_hierarchylookup (tenant-scoped) - For
agent/ceoroles: Check if resolvedagent_idis in downline usingis_in_downline() - For
adminrole: Allow if target resolves within tenant (no downline check) - If authorized: Pass
authorized_target_agent_idto data queries - If denied: Return HTTP 403 immediately (no sensitive data queries executed)
Security Guarantees
- Server-side enforcement: RBAC checks happen before any sensitive BigQuery query, preventing prompt injection bypass
- Downline-aware: AGENT/CEO can only access self + downline (not arbitrary agents)
- Tenant isolation: All queries filtered by
tenant_idfrom JWT - Fail-closed: Missing
agent_id, unknown role, or non-downline access defaults to deny - Auth queries allowed: Small auth-only queries (
is_in_downline,resolve_target_to_agent_id) are allowed, but sensitive data queries must be blocked on deny - LLM call prevention: RBAC denial happens before any LLM call - this is the real prompt injection defense
- Depth cap: Downline traversal limited to 10 levels to prevent infinite loops
- Cycle guards: Prevents self-referential parent relationships
Example Scenarios
Scenario 1: Agent asks about self- User: Kenny Young (agent)
- Question: “How much did I make this month?”
- Result: ✅ Allowed (defaults to self)
- User: Kenny Young (agent)
- Question: “How much did [downline agent] make?”
- Result: ✅ Allowed (if agent is in Kenny’s downline)
- User: Kenny Young (agent)
- Question: “How much did Tommy Dang make?” (Tommy not in Kenny’s downline)
- Result: ❌ HTTP 403 Forbidden (no sensitive query executed)
- User: Admin
- Question: “How much did Tommy Dang make?”
- Result: ✅ Allowed (admin can query any agent in tenant)
- User: Admin
- Question: “Show me all agent commissions” (no specific target)
- Result: ❌ HTTP 403 Forbidden (explicit deny - admin must provide target)
Error Responses
- 401 Unauthorized:
- Token expired/invalid
- Production: No Authorization header (demo mode disabled)
- Bearer token present but invalid (no fallback to demo)
- 403 Forbidden:
- No access to requested data
- Agent/CEO querying non-downline agent
- Missing
authorized_agent_idfor agent-scoped queries
- 429 Rate Limited: Too many requests
- 500 Internal Error: Server error
Audit Logging
Every request logged with:timestamp,tenant_id,user_email,role,agent_namequestion_length,latency_ms,is_demo
Security Features
JWT Authentication
- Real user extracted from token
- Automatic tenant and agent scoping
- Token validation with proper error handling
Demo Mode Restrictions
- CEO view only, no agent data
- Keyword filtering for sensitive queries
- Clear indication of demo mode
Rate Limiting
- Per-IP caps prevent abuse
- Graceful degradation on limits
- Clear error messages
Audit Logging
- Every query logged with user context
- Performance metrics tracked
- Security events captured
Safe Error Handling
- No logout loops on API errors
- User-friendly error messages
- Graceful fallback to demo mode
API Endpoints
POST /api/v1/ai/query-public
Request Headers:Frontend Integration
Configuration
Environment Variable:NEXT_PUBLIC_AI_URL
- Required: Yes (chatbot will fail to initialize if not set)
- Purpose: Points to the Cloud Run service where RBAC fixes are deployed
- Production Value:
https://payroll-backend-prod-evndxpcirq-uc.a.run.app - Format: Must be origin-only (e.g.,
https://payroll-backend-prod-...run.app) - DO NOT include/api/v1or any path components - Validation: Call-time validation (not module-init) prevents SSR/build crashes
- Guardrails: Warning logged if env var contains
/api/paths (will be normalized to origin-only) - Important: Never hardcode Cloud Run URLs in
aiClient.ts. Always use environment variables.
- Canonical Endpoint:
/api/v1/ai/query-public- Frontend always calls this canonical path regardless of
NEXT_PUBLIC_AI_URLformat - Backend mounts router at
/api/v1/ai(canonical) - This is the primary endpoint for all new deployments
- Frontend always calls this canonical path regardless of
- Legacy Alias:
/ai/query-public(temporary compatibility)- Backend also mounts router at
/ai(legacy alias) for compatibility with stale bundles - Same router instance ensures identical RBAC/auth behavior
- Removal Plan: Will be removed after stale bundles expire (monitor usage first)
- See
docs/AI_CHATBOT_INCIDENT_2025_12_24.mdfor details
- Backend also mounts router at
ChatBubble.tsx
- Automatically includes JWT when available
- Safe error handling without logout
- Clear user feedback for different error types
AISidebar.tsx
- Same JWT and error handling pattern
- Consistent user experience across components
Production Behavior (2025-12-24 Update)
Authentication Requirements
- Production: Authentication required - no demo mode fallback
- No Authorization header → 401 Unauthorized
- Invalid Bearer token → 401 Unauthorized (no fallback to demo)
- Non-Production: Demo mode allowed with restricted
viewerrole- No Authorization header → restricted demo mode (viewer role, no agent data)
RBAC Enforcement
- All intent handlers enforce
authorize_target_agent_id()before data fetch - Agent/CEO role: Can only query self + downline (enforced server-side)
- Admin role: Can query any agent in tenant (explicit target required)
- Fail-closed: Missing
authorized_agent_idfor agent-scoped queries → 403 Forbidden
BigQuery Hardening
- ID-based filtering: Prefers
authorized_agent_idoveragent_name - Dedupe CTE: Prevents row multiplication from duplicate names
- Exact equality: No LIKE/partial matching (prevents name collision leaks)
- NULL guards:
COALESCEandIS NOT NULLchecks for correctness
docs/AI_CHATBOT_INCIDENT_2025_12_24.md for complete fix summary.
Security Checklist
- JWT token extracted and validated
- Demo mode disabled in production (requires authentication)
- Demo mode restricted to viewer role in non-prod (no agent data)
- Bearer token present but invalid → 401 (no fallback)
- All intent handlers enforce RBAC before data fetch
- Agent/CEO role restricted to self + downline
- BigQuery queries use ID-based filtering with dedupe guards
- Rate limiting (30/min per IP)
- Audit logging for all queries
- Frontend passes JWT when available
- Frontend error handling doesn’t trigger logout
- 401/403/429 errors show helpful messages
- Documentation created
Troubleshooting
Common Issues
-
“Session expired” loop
- Frontend is calling wrong endpoint
- Check network tab for 401 responses
- Verify JWT token is valid
-
“Demo mode” for authenticated users
- JWT token not being passed
- Check Authorization header in network tab
- Verify token format:
Bearer <token>
-
Rate limiting errors
- Too many requests from same IP
- Wait 1 minute before retrying
- Check if multiple users sharing IP
-
403 Forbidden errors
- User doesn’t have access to requested data
- Check user role and agent_id in JWT
- Verify tenant_id matches
- For agent/ceo: Verify requested agent is in downline
- For admin: Verify explicit target was provided
- Known Issue: 403 for self-queries (see
docs/AI_CHATBOT_DEBUG_NEXT.md)- Symptom: Agent gets 403 when asking about own data
- Likely cause:
agent_idenrichment missing or name→id resolution mismatch - Debug: Check Cloud Run logs for
[RBAC-DEBUG]markers
Debug Steps
- Check browser network tab for request/response
- Verify JWT token in localStorage
- Check Cloud Run logs for audit entries
- Test with curl to isolate frontend vs backend issues
Monitoring
Cloud Run Logs
Look for[AI_AUDIT] entries to track:
- User activity patterns
- Performance metrics
- Security events
- Demo vs authenticated usage
Key Metrics
- Request latency (latency_ms)
- Demo mode usage (is_demo)
- Error rates by status code
- User distribution by role