diff --git a/chat/admin/admin.test.js b/chat/admin/admin.test.js
new file mode 100644
index 0000000..5c4b080
--- /dev/null
+++ b/chat/admin/admin.test.js
@@ -0,0 +1,530 @@
+const { describe, test, expect, beforeEach, afterEach, mock } = require('bun:test')
+
+describe('Admin Panel Functionality', () => {
+ let adminSessions
+ let adminLoginAttempts
+
+ const ADMIN_COOKIE_NAME = 'admin_session'
+ const ADMIN_SESSION_TTL_MS = 24 * 60 * 60 * 1000
+ const ADMIN_LOGIN_RATE_LIMIT = 5
+
+ beforeEach(() => {
+ adminSessions = new Map()
+ adminLoginAttempts = new Map()
+ })
+
+ afterEach(() => {
+ adminSessions.clear()
+ adminLoginAttempts.clear()
+ })
+
+ describe('Admin Session Management', () => {
+ function readAdminSessionToken(req) {
+ try {
+ const cookieHeader = req?.headers?.cookie || ''
+ if (!cookieHeader) return ''
+ const parts = cookieHeader.split(';').map((p) => p.trim())
+ const match = parts.find((p) => p.startsWith(`${ADMIN_COOKIE_NAME}=`))
+ if (!match) return ''
+ return decodeURIComponent(match.split('=').slice(1).join('=') || '')
+ } catch (_) {
+ return ''
+ }
+ }
+
+ function getAdminSession(req) {
+ const token = readAdminSessionToken(req)
+ if (!token) return null
+ const session = adminSessions.get(token)
+ if (!session) return null
+ if (session.expiresAt && session.expiresAt < Date.now()) {
+ adminSessions.delete(token)
+ return null
+ }
+ return { token, expiresAt: session.expiresAt }
+ }
+
+ function startAdminSession() {
+ const crypto = require('crypto')
+ const token = crypto.randomUUID()
+ const expiresAt = Date.now() + ADMIN_SESSION_TTL_MS
+ adminSessions.set(token, { expiresAt })
+ return token
+ }
+
+ function clearAdminSession(token) {
+ if (token) adminSessions.delete(token)
+ }
+
+ function requireAdminAuth(req) {
+ const session = getAdminSession(req)
+ if (!session) return null
+ return session
+ }
+
+ test('starts a new admin session', () => {
+ const token = startAdminSession()
+
+ expect(token).toBeDefined()
+ expect(adminSessions.has(token)).toBe(true)
+ expect(adminSessions.get(token).expiresAt).toBeGreaterThan(Date.now())
+ })
+
+ test('reads admin session token from cookie', () => {
+ const req = {
+ headers: {
+ cookie: `${ADMIN_COOKIE_NAME}=test-token-123; other=value`
+ }
+ }
+
+ const token = readAdminSessionToken(req)
+ expect(token).toBe('test-token-123')
+ })
+
+ test('returns null for missing cookie', () => {
+ const req = { headers: {} }
+ const token = readAdminSessionToken(req)
+ expect(token).toBe('')
+ })
+
+ test('validates active session', () => {
+ const token = startAdminSession()
+ const req = {
+ headers: { cookie: `${ADMIN_COOKIE_NAME}=${token}` }
+ }
+
+ const session = getAdminSession(req)
+ expect(session).not.toBeNull()
+ expect(session.token).toBe(token)
+ })
+
+ test('rejects expired session', () => {
+ const crypto = require('crypto')
+ const token = crypto.randomUUID()
+ adminSessions.set(token, { expiresAt: Date.now() - 1000 })
+
+ const req = {
+ headers: { cookie: `${ADMIN_COOKIE_NAME}=${token}` }
+ }
+
+ const session = getAdminSession(req)
+ expect(session).toBeNull()
+ expect(adminSessions.has(token)).toBe(false)
+ })
+
+ test('rejects invalid token', () => {
+ const req = {
+ headers: { cookie: `${ADMIN_COOKIE_NAME}=invalid-token` }
+ }
+
+ const session = getAdminSession(req)
+ expect(session).toBeNull()
+ })
+
+ test('clears admin session', () => {
+ const token = startAdminSession()
+ clearAdminSession(token)
+
+ expect(adminSessions.has(token)).toBe(false)
+ })
+
+ test('requireAdminAuth returns session for valid auth', () => {
+ const token = startAdminSession()
+ const req = {
+ headers: { cookie: `${ADMIN_COOKIE_NAME}=${token}` }
+ }
+
+ const session = requireAdminAuth(req)
+ expect(session).not.toBeNull()
+ })
+
+ test('requireAdminAuth returns null for invalid auth', () => {
+ const req = { headers: {} }
+ const session = requireAdminAuth(req)
+ expect(session).toBeNull()
+ })
+ })
+
+ describe('Admin Login Rate Limiting', () => {
+ const LOGIN_LOCKOUT_MS = 900000
+ const RATE_LIMIT_WINDOW_MS = 60000
+
+ function checkLoginRateLimit(ip, limit, attempts) {
+ const now = Date.now()
+ const record = attempts.get(ip)
+
+ if (!record) {
+ attempts.set(ip, { count: 1, windowStart: now, lockedUntil: null })
+ return { blocked: false, count: 1 }
+ }
+
+ if (record.lockedUntil && record.lockedUntil > now) {
+ return {
+ blocked: true,
+ retryAfter: Math.ceil((record.lockedUntil - now) / 1000)
+ }
+ }
+
+ if (now - record.windowStart > RATE_LIMIT_WINDOW_MS) {
+ attempts.set(ip, { count: 1, windowStart: now, lockedUntil: null })
+ return { blocked: false, count: 1 }
+ }
+
+ record.count++
+
+ if (record.count >= limit) {
+ record.lockedUntil = now + LOGIN_LOCKOUT_MS
+ return { blocked: true, retryAfter: Math.ceil(LOGIN_LOCKOUT_MS / 1000) }
+ }
+
+ return { blocked: false, count: record.count }
+ }
+
+ test('allows first login attempt', () => {
+ const result = checkLoginRateLimit('192.168.1.1', ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+
+ expect(result.blocked).toBe(false)
+ expect(result.count).toBe(1)
+ })
+
+ test('counts attempts within window', () => {
+ const ip = '192.168.1.1'
+ checkLoginRateLimit(ip, ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+ checkLoginRateLimit(ip, ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+ const result = checkLoginRateLimit(ip, ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+
+ expect(result.blocked).toBe(false)
+ expect(result.count).toBe(3)
+ })
+
+ test('blocks after exceeding limit', () => {
+ const ip = '192.168.1.1'
+ for (let i = 0; i < ADMIN_LOGIN_RATE_LIMIT; i++) {
+ checkLoginRateLimit(ip, ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+ }
+ const result = checkLoginRateLimit(ip, ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+
+ expect(result.blocked).toBe(true)
+ expect(result.retryAfter).toBeGreaterThan(0)
+ })
+
+ test('resets after lockout period', () => {
+ const ip = '192.168.1.1'
+ for (let i = 0; i < ADMIN_LOGIN_RATE_LIMIT; i++) {
+ checkLoginRateLimit(ip, ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+ }
+
+ const record = adminLoginAttempts.get(ip)
+ record.lockedUntil = Date.now() - 1000
+
+ const result = checkLoginRateLimit(ip, ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+ expect(result.blocked).toBe(false)
+ })
+
+ test('tracks different IPs separately', () => {
+ checkLoginRateLimit('192.168.1.1', ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+ checkLoginRateLimit('192.168.1.1', ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+ checkLoginRateLimit('192.168.1.2', ADMIN_LOGIN_RATE_LIMIT, adminLoginAttempts)
+
+ expect(adminLoginAttempts.get('192.168.1.1').count).toBe(2)
+ expect(adminLoginAttempts.get('192.168.1.2').count).toBe(1)
+ })
+ })
+
+ describe('Admin User Management', () => {
+ let usersDb
+
+ beforeEach(() => {
+ usersDb = [
+ { id: 'user-1', email: 'user1@test.com', plan: 'hobby', billingStatus: 'active' },
+ { id: 'user-2', email: 'user2@test.com', plan: 'starter', billingStatus: 'active' },
+ { id: 'user-3', email: 'user3@test.com', plan: 'professional', billingStatus: 'active' }
+ ]
+ })
+
+ function findUserById(userId) {
+ return usersDb.find(u => u.id === userId) || null
+ }
+
+ function normalizePlanSelection(plan) {
+ const validPlans = ['hobby', 'starter', 'professional', 'enterprise']
+ return validPlans.includes(plan) ? plan : 'hobby'
+ }
+
+ function isPaidPlan(plan) {
+ return ['starter', 'professional', 'enterprise'].includes(plan)
+ }
+
+ test('finds user by ID', () => {
+ const user = findUserById('user-1')
+ expect(user).toBeDefined()
+ expect(user.email).toBe('user1@test.com')
+ })
+
+ test('returns null for non-existent user', () => {
+ const user = findUserById('non-existent')
+ expect(user).toBeNull()
+ })
+
+ test('normalizes valid plan selections', () => {
+ expect(normalizePlanSelection('hobby')).toBe('hobby')
+ expect(normalizePlanSelection('starter')).toBe('starter')
+ expect(normalizePlanSelection('professional')).toBe('professional')
+ expect(normalizePlanSelection('enterprise')).toBe('enterprise')
+ })
+
+ test('defaults invalid plan to hobby', () => {
+ expect(normalizePlanSelection('invalid')).toBe('hobby')
+ expect(normalizePlanSelection('')).toBe('hobby')
+ expect(normalizePlanSelection(null)).toBe('hobby')
+ })
+
+ test('identifies paid plans', () => {
+ expect(isPaidPlan('hobby')).toBe(false)
+ expect(isPaidPlan('starter')).toBe(true)
+ expect(isPaidPlan('professional')).toBe(true)
+ expect(isPaidPlan('enterprise')).toBe(true)
+ })
+
+ test('updates user plan', () => {
+ const user = findUserById('user-1')
+ user.plan = 'enterprise'
+
+ expect(user.plan).toBe('enterprise')
+ })
+
+ test('lists all users', () => {
+ expect(usersDb.length).toBe(3)
+ })
+
+ test('deletes user', () => {
+ const initialCount = usersDb.length
+ const index = usersDb.findIndex(u => u.id === 'user-1')
+ usersDb.splice(index, 1)
+
+ expect(usersDb.length).toBe(initialCount - 1)
+ expect(findUserById('user-1')).toBeNull()
+ })
+ })
+
+ describe('Admin Token Management', () => {
+ let tokenUsage
+ let usersDb
+
+ beforeEach(() => {
+ tokenUsage = {}
+ usersDb = [
+ { id: 'user-1', plan: 'hobby' },
+ { id: 'user-2', plan: 'professional' }
+ ]
+ })
+
+ function ensureTokenUsageBucket(userId) {
+ if (!tokenUsage[userId]) {
+ tokenUsage[userId] = { usage: 0, tokenOverride: null }
+ }
+ return tokenUsage[userId]
+ }
+
+ function getPlanTokenLimits(plan, userId) {
+ const limits = {
+ hobby: 10000,
+ starter: 50000,
+ professional: 200000,
+ enterprise: 1000000
+ }
+ const bucket = tokenUsage[userId]
+ if (bucket?.tokenOverride !== null && bucket?.tokenOverride !== undefined) {
+ return bucket.tokenOverride
+ }
+ return limits[plan] || limits.hobby
+ }
+
+ test('creates token bucket for new user', () => {
+ const bucket = ensureTokenUsageBucket('new-user')
+
+ expect(bucket).toBeDefined()
+ expect(bucket.usage).toBe(0)
+ expect(bucket.tokenOverride).toBeNull()
+ })
+
+ test('returns existing bucket', () => {
+ tokenUsage['existing-user'] = { usage: 5000, tokenOverride: 20000 }
+ const bucket = ensureTokenUsageBucket('existing-user')
+
+ expect(bucket.usage).toBe(5000)
+ expect(bucket.tokenOverride).toBe(20000)
+ })
+
+ test('sets token limit override', () => {
+ const userId = 'user-1'
+ const bucket = ensureTokenUsageBucket(userId)
+ bucket.tokenOverride = 50000
+
+ expect(bucket.tokenOverride).toBe(50000)
+ })
+
+ test('clears override when set to 0', () => {
+ const userId = 'user-1'
+ const bucket = ensureTokenUsageBucket(userId)
+ bucket.tokenOverride = 50000
+ bucket.tokenOverride = 0 ? null : bucket.tokenOverride
+
+ bucket.tokenOverride = null
+
+ expect(bucket.tokenOverride).toBeNull()
+ })
+
+ test('sets usage directly', () => {
+ const userId = 'user-1'
+ const bucket = ensureTokenUsageBucket(userId)
+ bucket.usage = 25000
+
+ expect(bucket.usage).toBe(25000)
+ })
+
+ test('calculates remaining tokens', () => {
+ const userId = 'user-1'
+ const bucket = ensureTokenUsageBucket(userId)
+ const limit = getPlanTokenLimits('hobby', userId)
+ bucket.usage = 5000
+ const remaining = limit - bucket.usage
+
+ expect(remaining).toBe(5000)
+ })
+
+ test('uses override limit when set', () => {
+ const userId = 'user-1'
+ const bucket = ensureTokenUsageBucket(userId)
+ bucket.tokenOverride = 50000
+
+ const limit = getPlanTokenLimits('hobby', userId)
+ expect(limit).toBe(50000)
+ })
+ })
+
+ describe('Admin Model Management', () => {
+ let opencodeModels
+ let publicModels
+
+ beforeEach(() => {
+ opencodeModels = [
+ { id: 'model-1', name: 'gpt-4', label: 'GPT-4', tier: 'premium' },
+ { id: 'model-2', name: 'claude-3', label: 'Claude 3', tier: 'premium' }
+ ]
+ publicModels = [
+ { id: 'model-3', name: 'gpt-3.5', label: 'GPT-3.5', tier: 'standard' }
+ ]
+ })
+
+ function normalizeTier(tier) {
+ const validTiers = ['free', 'standard', 'premium', 'enterprise']
+ return validTiers.includes(tier) ? tier : 'standard'
+ }
+
+ function getTierMultiplier(tier) {
+ const multipliers = {
+ free: 0.5,
+ standard: 1,
+ premium: 2,
+ enterprise: 5
+ }
+ return multipliers[tier] || 1
+ }
+
+ test('lists opencode models', () => {
+ expect(opencodeModels.length).toBe(2)
+ expect(opencodeModels[0].name).toBe('gpt-4')
+ })
+
+ test('lists public models', () => {
+ expect(publicModels.length).toBe(1)
+ expect(publicModels[0].name).toBe('gpt-3.5')
+ })
+
+ test('normalizes tier values', () => {
+ expect(normalizeTier('free')).toBe('free')
+ expect(normalizeTier('premium')).toBe('premium')
+ expect(normalizeTier('invalid')).toBe('standard')
+ })
+
+ test('gets tier multipliers', () => {
+ expect(getTierMultiplier('free')).toBe(0.5)
+ expect(getTierMultiplier('standard')).toBe(1)
+ expect(getTierMultiplier('premium')).toBe(2)
+ expect(getTierMultiplier('enterprise')).toBe(5)
+ })
+
+ test('adds new model to opencode', () => {
+ const newModel = {
+ id: 'model-4',
+ name: 'llama-3',
+ label: 'LLaMA 3',
+ tier: 'standard'
+ }
+ opencodeModels.push(newModel)
+
+ expect(opencodeModels.length).toBe(3)
+ expect(opencodeModels.find(m => m.id === 'model-4')).toBeDefined()
+ })
+
+ test('updates existing model', () => {
+ const model = opencodeModels.find(m => m.id === 'model-1')
+ model.label = 'GPT-4 Turbo'
+
+ expect(model.label).toBe('GPT-4 Turbo')
+ })
+
+ test('deletes model', () => {
+ const index = opencodeModels.findIndex(m => m.id === 'model-1')
+ opencodeModels.splice(index, 1)
+
+ expect(opencodeModels.length).toBe(1)
+ expect(opencodeModels.find(m => m.id === 'model-1')).toBeUndefined()
+ })
+
+ test('reorders models', () => {
+ const newOrder = [
+ { id: 'model-2' },
+ { id: 'model-1' }
+ ]
+
+ const reordered = []
+ newOrder.forEach(m => {
+ const model = opencodeModels.find(pm => pm.id === m.id)
+ if (model) reordered.push(model)
+ })
+
+ opencodeModels = reordered
+
+ expect(opencodeModels[0].id).toBe('model-2')
+ expect(opencodeModels[1].id).toBe('model-1')
+ })
+ })
+
+ describe('Admin Authentication Validation', () => {
+ function checkHoneypot(body) {
+ return !!(body && body.website)
+ }
+
+ function isValidEmail(email) {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+ return emailRegex.test(email)
+ }
+
+ test('detects honeypot field', () => {
+ expect(checkHoneypot({ website: 'spam' })).toBe(true)
+ expect(checkHoneypot({ website: '' })).toBe(true)
+ expect(checkHoneypot({ other: 'value' })).toBe(false)
+ expect(checkHoneypot({})).toBe(false)
+ })
+
+ test('validates email format', () => {
+ expect(isValidEmail('test@example.com')).toBe(true)
+ expect(isValidEmail('user.name@domain.co.uk')).toBe(true)
+ expect(isValidEmail('invalid')).toBe(false)
+ expect(isValidEmail('no@domain')).toBe(false)
+ expect(isValidEmail('@nodomain.com')).toBe(false)
+ })
+ })
+})
diff --git a/chat/security/prompt-sanitizer.test.js b/chat/security/prompt-sanitizer.test.js
new file mode 100644
index 0000000..733349f
--- /dev/null
+++ b/chat/security/prompt-sanitizer.test.js
@@ -0,0 +1,520 @@
+const { describe, test, expect } = require('bun:test')
+const {
+ sanitizeUserInput,
+ wrapUserContent,
+ createHardenedSystemPrompt,
+ shouldBlockInput,
+ generateBoundary,
+ normalizeText,
+ hasAttackContext,
+ hasLegitimateContext,
+ isObfuscatedAttack,
+ CORE_ATTACK_PATTERNS
+} = require('./prompt-sanitizer')
+
+describe('Prompt Sanitizer Security', () => {
+ describe('sanitizeUserInput', () => {
+ test('allows normal user input', () => {
+ const result = sanitizeUserInput('Create a WordPress plugin for contact forms')
+
+ expect(result.blocked).toBe(false)
+ expect(result.sanitized).toBe('Create a WordPress plugin for contact forms')
+ })
+
+ test('handles empty input', () => {
+ const result = sanitizeUserInput('')
+
+ expect(result.blocked).toBe(false)
+ expect(result.sanitized).toBe('')
+ expect(result.confidence).toBe('none')
+ })
+
+ test('handles null input', () => {
+ const result = sanitizeUserInput(null)
+
+ expect(result.blocked).toBe(false)
+ expect(result.sanitized).toBe('')
+ })
+
+ test('handles undefined input', () => {
+ const result = sanitizeUserInput(undefined)
+
+ expect(result.blocked).toBe(false)
+ expect(result.sanitized).toBe('')
+ })
+
+ test('truncates long input', () => {
+ const longInput = 'a'.repeat(60000)
+ const result = sanitizeUserInput(longInput, { maxLength: 50000 })
+
+ expect(result.sanitized.length).toBe(50000)
+ expect(result.warnings.length).toBeGreaterThan(0)
+ })
+
+ test('escapes HTML by default', () => {
+ const result = sanitizeUserInput('')
+
+ expect(result.sanitized).toContain('<')
+ expect(result.sanitized).toContain('>')
+ expect(result.sanitized).not.toContain('