Validate that a URL is in the allowed LLM API URL list. Compares the scheme://host:port portion of the URL against config.ALLOWED_LLM_API_URLS. Path is not checked — different providers use different paths. Allowlist entries may use ``:*`` for the port to match any port on
(url)
| 142 | |
| 143 | |
| 144 | def validate_api_url(url): |
| 145 | """ |
| 146 | Validate that a URL is in the allowed LLM API URL list. |
| 147 | |
| 148 | Compares the scheme://host:port portion of the URL against |
| 149 | config.ALLOWED_LLM_API_URLS. Path is not checked — different |
| 150 | providers use different paths. |
| 151 | |
| 152 | Allowlist entries may use ``:*`` for the port to match any port |
| 153 | on that host (e.g. ``http://localhost:*`` matches localhost on |
| 154 | every port). This is useful for local self-hosting where the |
| 155 | user picks the port (LiteLLM, vLLM, LM Studio, etc.) — the |
| 156 | same host check still blocks link-local cloud metadata |
| 157 | endpoints like 169.254.169.254. |
| 158 | |
| 159 | Returns True if the URL is allowed, False otherwise. |
| 160 | An empty allowlist means no restriction (admin opt-out). |
| 161 | """ |
| 162 | from urllib.parse import urlparse |
| 163 | |
| 164 | if not url: |
| 165 | return False |
| 166 | |
| 167 | allowed_urls = getattr(config, 'ALLOWED_LLM_API_URLS', []) |
| 168 | if not allowed_urls: |
| 169 | return True |
| 170 | |
| 171 | parsed = urlparse(url) |
| 172 | scheme = parsed.scheme.lower() |
| 173 | hostname = parsed.hostname |
| 174 | if hostname: |
| 175 | hostname = hostname.lower() |
| 176 | |
| 177 | if not scheme or not hostname: |
| 178 | return False |
| 179 | |
| 180 | # Only allow http and https schemes |
| 181 | if scheme not in ('http', 'https'): |
| 182 | return False |
| 183 | |
| 184 | # Infer default port from scheme if not specified |
| 185 | try: |
| 186 | port = parsed.port |
| 187 | except ValueError: |
| 188 | return False |
| 189 | if port is None: |
| 190 | port = 443 if scheme == 'https' else 80 |
| 191 | |
| 192 | for allowed in allowed_urls: |
| 193 | parsed_entry = _parse_allowlist_entry(allowed) |
| 194 | if parsed_entry is None: |
| 195 | continue |
| 196 | a_scheme, a_hostname, a_port = parsed_entry |
| 197 | |
| 198 | if a_scheme != scheme or a_hostname != hostname: |
| 199 | continue |
| 200 | if a_port == '*' or a_port == port: |
| 201 | return True |