docling-project/docling · error · ArtifactDownloadError
Refusing to download artifact from a non-public URL: {url}.
Error message
Refusing to download artifact from a non-public URL: {url}. What it means
SSRF guard: _validate_artifact_url raises ArtifactDownloadError when the artifact URL (initial or any redirect hop) does not resolve to a globally routable address — non-http(s) scheme, missing host, DNS failure, or a private/loopback/link-local/reserved/multicast IP. The check is skipped only when the client was constructed with _allow_private_artifact_urls=True.
Source
Thrown at docling/service_client/client.py:1951
)
chunks: list[bytes] = []
total = 0
async for chunk in response.aiter_bytes():
total += len(chunk)
self._check_artifact_size(total)
chunks.append(chunk)
return b"".join(chunks)
raise ArtifactDownloadError(
"Too many redirects while downloading artifact."
)
except httpx.HTTPError as exc:
raise ArtifactDownloadError(f"Artifact download failed: {exc}") from exc
def _validate_artifact_url(self, url: str) -> None:
if self._allow_private_artifact_urls:
return
if not _is_safe_artifact_url(url):
raise ArtifactDownloadError(
f"Refusing to download artifact from a non-public URL: {url}."
)
@staticmethod
def _next_redirect_url(current_url: str, response: httpx.Response) -> str:
location = response.headers.get("location")
if not location:
raise ArtifactDownloadError(
"Artifact download redirect is missing a Location header."
)
return str(httpx.URL(current_url).join(location))
def _check_artifact_size(self, total: int) -> None:
if total > self._max_artifact_download_bytes:
raise ArtifactDownloadError(
"Artifact exceeds max_artifact_download_bytes "
f"({self._max_artifact_download_bytes} bytes)."
)View on GitHub (pinned to 61d76f1ff3)
Solutions
- If the private endpoint is trusted, construct the client with _allow_private_artifact_urls=True (opt-in escape hatch)
- Better: expose the object store on a publicly routable address or run the client inside the same network with the flag set deliberately
- Check that the redirect chain does not detour through an internal load balancer
Example fix
// before
client = DoclingServiceClient('https://docling.internal')
# ArtifactDownloadError: Refusing to download artifact from a non-public URL
// after (trusted internal MinIO)
client = DoclingServiceClient('https://docling.internal', _allow_private_artifact_urls=True) Defensive patterns
Strategy: validation
Validate before calling
from docling.service_client.client import _is_safe_artifact_url # before opting out, check what the client will see: assert _is_safe_artifact_url(presigned_url), 'URL resolves private — needs opt-in'
Type guard
def is_artifact_download_error(exc: BaseException) -> bool:
return isinstance(exc, ArtifactDownloadError) Try / catch
try:
results = list(client.convert_all(sources))
except ArtifactDownloadError as exc:
if 'non-public URL' in str(exc):
client = DoclingServiceClient(url, _allow_private_artifact_urls=True) # deliberate trust decision Prevention
- Only set _allow_private_artifact_urls=True when the service and network are fully trusted
- Prefer making the object store routable over disabling the SSRF guard
When it happens
Trigger: docling-serve returns presigned URLs pointing at an internal hostname (e.g. minio.internal, 10.x.x.x, 169.254.169.254) while the client runs elsewhere; or a redirect hop lands on a private address.
Common situations: Self-hosted docling-serve in Docker/Kubernetes where the object store (MinIO) is only reachable inside the cluster; DNS resolving to a private IP from the client's network; a compromised service attempting SSRF via redirects.
Related errors
- Access to restricted IP address not allowed: {ip}
- Too many redirects while downloading artifact.
- URL must contain a valid hostname
- Cannot resolve hostname: {hostname}
- Fetching remote resources is only allowed when set explicitl
AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14).
Data as JSON: /api/errors/ad8cb371f6dec787.
Report an issue: GitHub.