Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 39s
CRITICAL: - Add auth + admin check to 10 unprotected API routes (test-*, debug/*, config, models, fix-labels) - Add CRON_SECRET bearer auth to /api/cron/reminders (was fully open) - Add SSRF protection to getOllamaModels (blocks private/internal IPs) HIGH: - Fix getAllLabels() missing userId filter (leaked all users' labels) - Fix /api/labels OR clause leaking other users' labels - Fix IDOR in toggleAgent/getAgentActions (add ownership check) - Fix getEmbeddings() returning [] on error in all 5 providers (corrupted semantic search with NaN cosine similarity) — now throws instead Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
'use server'
|
|
|
|
interface OllamaModel {
|
|
name: string
|
|
modified_at: string
|
|
size: number
|
|
digest: string
|
|
details: {
|
|
format: string
|
|
family: string
|
|
families: string[]
|
|
parameter_size: string
|
|
quantization_level: string
|
|
}
|
|
}
|
|
|
|
interface OllamaTagsResponse {
|
|
models: OllamaModel[]
|
|
}
|
|
|
|
export async function getOllamaModels(baseUrl: string): Promise<{ success: boolean; models: string[]; error?: string }> {
|
|
if (!baseUrl) {
|
|
return { success: false, models: [], error: 'Base URL is required' }
|
|
}
|
|
|
|
// Ensure URL doesn't end with slash
|
|
const cleanUrl = baseUrl.replace(/\/$/, '')
|
|
|
|
// SSRF protection: block internal/private IPs
|
|
try {
|
|
const parsed = new URL(cleanUrl)
|
|
const hostname = parsed.hostname.toLowerCase()
|
|
const blockedHosts = ['localhost', '127.0.0.1', '0.0.0.0', '::1', '169.254.169.254']
|
|
if (blockedHosts.includes(hostname)) {
|
|
return { success: false, models: [], error: 'Private/internal URLs are not allowed' }
|
|
}
|
|
if (hostname.startsWith('10.') || hostname.startsWith('172.') || hostname.startsWith('192.168.') || hostname.startsWith('fc') || hostname.startsWith('fd')) {
|
|
return { success: false, models: [], error: 'Private/internal URLs are not allowed' }
|
|
}
|
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
return { success: false, models: [], error: 'Only http/https protocols allowed' }
|
|
}
|
|
} catch {
|
|
return { success: false, models: [], error: 'Invalid URL' }
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${cleanUrl}/api/tags`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
// Set a reasonable timeout
|
|
signal: AbortSignal.timeout(5000)
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Ollama API returned ${response.status}: ${response.statusText}`)
|
|
}
|
|
|
|
const data = await response.json() as OllamaTagsResponse
|
|
|
|
// Extract model names
|
|
const modelNames = data.models?.map(m => m.name) || []
|
|
|
|
return { success: true, models: modelNames }
|
|
} catch (error: any) {
|
|
console.error('Failed to fetch Ollama models:', error)
|
|
return {
|
|
success: false,
|
|
models: [],
|
|
error: error.message || 'Failed to connect to Ollama'
|
|
}
|
|
}
|
|
}
|