fix(AI): improve remote Ollama url validation to prevent SSRF vulnerability
This commit is contained in:
parent
82f67debc1
commit
989a401f28
|
|
@ -6,6 +6,7 @@ import Service from '#models/service'
|
|||
import KVStore from '#models/kv_store'
|
||||
import { modelNameSchema } from '#validators/download'
|
||||
import { chatSchema, getAvailableModelsSchema, unloadChatModelsSchema } from '#validators/ollama'
|
||||
import { assertNotCloudMetadataUrl } from '#validators/common'
|
||||
import { inject } from '@adonisjs/core'
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import { RAG_CONTEXT_LIMITS, SYSTEM_PROMPTS } from '../../constants/ollama.js'
|
||||
|
|
@ -242,11 +243,12 @@ export default class OllamaController {
|
|||
}
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!remoteUrl.startsWith('http')) {
|
||||
try {
|
||||
assertNotCloudMetadataUrl(remoteUrl)
|
||||
} catch (err) {
|
||||
return response.status(400).send({
|
||||
success: false,
|
||||
message: 'Invalid URL. Must start with http:// or https://',
|
||||
message: err instanceof Error ? err.message : 'Invalid URL.',
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,36 @@ export function assertNotPrivateUrl(urlString: string): void {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrower SSRF guard for "remote service" URLs the user points NOMAD at
|
||||
* (e.g. an OpenAI-compatible endpoint like LM Studio, llama.cpp, vLLM, or a
|
||||
* sibling Ollama container). Unlike `assertNotPrivateUrl`, this intentionally
|
||||
* ALLOWS loopback, link-local-ish, and RFC1918 hosts because the legitimate
|
||||
* target is frequently on the same host or LAN (host.docker.internal,
|
||||
* the docker bridge gateway, or a LAN IP).
|
||||
*
|
||||
* It blocks only:
|
||||
* - the cloud instance-metadata IP (169.254.169.254), to avoid leaking
|
||||
* IAM creds on a misconfigured cloud VM
|
||||
* - non-HTTP schemes (file:, gopher:, etc.)
|
||||
*/
|
||||
export function assertNotCloudMetadataUrl(urlString: string): void {
|
||||
const parsed = new URL(urlString)
|
||||
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
throw new Error(`URL must use http or https scheme: ${parsed.protocol}`)
|
||||
}
|
||||
|
||||
const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '')
|
||||
|
||||
if (
|
||||
hostname === '169.254.169.254' ||
|
||||
hostname === 'fd00:ec2::254' // IPv6 EC2 IMDS
|
||||
) {
|
||||
throw new Error(`URL must not point to the cloud instance metadata endpoint: ${hostname}`)
|
||||
}
|
||||
}
|
||||
|
||||
export const remoteDownloadValidator = vine.compile(
|
||||
vine.object({
|
||||
url: vine
|
||||
|
|
|
|||
Loading…
Reference in New Issue