(parsedUrl: URL)
| 187 | } |
| 188 | |
| 189 | export async function validateDownloadSourceUrl(parsedUrl: URL): Promise<void> { |
| 190 | if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { |
| 191 | throw new AppError('INVALID_ARGS', `Unsupported source URL protocol: ${parsedUrl.protocol}`); |
| 192 | } |
| 193 | const hostname = parsedUrl.hostname.toLowerCase(); |
| 194 | if (isBlockedSourceHostname(hostname)) { |
| 195 | throw new AppError('INVALID_ARGS', `Source URL host is not allowed: ${parsedUrl.hostname}`, { |
| 196 | hint: 'Use a public artifact URL.', |
| 197 | }); |
| 198 | } |
| 199 | |
| 200 | const resolved = await dns |
| 201 | .lookup(parsedUrl.hostname, { all: true, verbatim: true }) |
| 202 | .catch(() => []); |
| 203 | if (resolved.some((entry) => isBlockedIpAddress(entry.address))) { |
| 204 | throw new AppError( |
| 205 | 'INVALID_ARGS', |
| 206 | `Source URL host resolved to a private or loopback address: ${parsedUrl.hostname}`, |
| 207 | { |
| 208 | hint: 'Use a public artifact URL.', |
| 209 | }, |
| 210 | ); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | export function isTrustedInstallSourceUrl(sourceUrl: string | URL): boolean { |
| 215 | const parsed = sourceUrl instanceof URL ? sourceUrl : new URL(sourceUrl); |
no test coverage detected