Bug Description
Summary
image_documents_to_base64 in llama-index-core opens any path supplied via ImageDocument.metadata["file_path"] and base64-encodes the bytes — with no allow-listed root, no symlink check, and no MIME validation. Applications that build ImageDocument instances from user-influenced data (a common pattern: tool arguments, JSON request bodies, deserialized documents) effectively expose an arbitrary-file-read primitive whose output is forwarded to the configured multimodal LLM.
Affected code
llama-index-core/llama_index/core/multi_modal_llms/generic_utils.py, lines 65-86:
for image_document in image_documents:
if image_document.image:
image_encodings.append(image_document.image)
elif image_document.image_path and os.path.isfile(image_document.image_path):
image_encodings.append(encode_image(image_document.image_path))
elif (
"file_path" in image_document.metadata
and image_document.metadata["file_path"] != ""
and os.path.isfile(image_document.metadata["file_path"])
):
image_encodings.append(encode_image(image_document.metadata["file_path"]))
elif image_document.image_url:
response = requests.get(image_document.image_url, timeout=(60, 60))
...
encode_image then performs an unguarded open(image_path, "rb") and base64-encodes the bytes.
metadata is a free-form dict on Document, so no validator gates it. The is_image_pil check on ImageDocument.__init__ only fires for the image_path= constructor argument, not for metadata. The library's own tests use this construction pattern (tests/multi_modal_llms/test_generic_utils.py:67 instantiates ImageDocument(metadata={"file_path": "test.jpg"})).
Impact
In RAG / multimodal-agent applications that ingest user-influenced input into ImageDocument (or that allow tool calls to construct one), an attacker can read arbitrary files the process has access to — /etc/passwd, ~/.aws/credentials, /var/run/secrets/kubernetes.io/serviceaccount/token, application .env files, etc. The bytes are returned in image_encodings and assigned to ImageDocument.image, then forwarded to the LLM provider — and typically echoed back to the user when the model is asked to "describe this image."
Suggested fix
Restrict accepted paths to an explicit allow-listed root, reject symlinks, and validate MIME/size before encoding:
from pathlib import Path
ALLOWED_ROOT = Path("/var/app/uploads").resolve()
def _safe_image_path(p: str) -> Path:
resolved = Path(p).resolve(strict=True)
if ALLOWED_ROOT not in resolved.parents and resolved != ALLOWED_ROOT:
raise ValueError("image path escapes allowed directory")
if resolved.is_symlink():
raise ValueError("symlinks not allowed")
return resolved
A more defensive option: stop honoring metadata["file_path"] as a path source entirely, and require callers to set image_path (which goes through the existing is_image_pil check) or pre-encoded image.
Scope note
I'm aware the project's security policy classifies path-traversal-from-untrusted-paths as out of scope for the Huntr bounty (treated as application-layer responsibility). Filing this as a public bug rather than as a security report — many users may not realize that metadata["file_path"] bypasses the constructor's image-validation check.
Version
0.14.21
Steps to Reproduce
from llama_index.core.schema import ImageDocument
from llama_index.core.multi_modal_llms.generic_utils import image_documents_to_base64
# No image_path, no image_url — only metadata['file_path'].
# This branch is not gated by is_image_pil() validation.
doc = ImageDocument(metadata={"file_path": "/etc/passwd"})
encoded = image_documents_to_base64([doc])
print(encoded) # base64 of /etc/passwd
The same primitive works against ~/.aws/credentials, /var/run/secrets/kubernetes.io/serviceaccount/token, application .env files — anything the process can read.
In a real deployment this is reachable any time user input flows into an ImageDocument's metadata (tool-call arguments, JSON request bodies, deserialized documents, third-party feeds). The encoded bytes end up in ImageDocument.image via set_base64_and_mimetype_for_image_docs and are forwarded to the multimodal LLM provider, where they are typically echoed back when the model is asked to describe the "image."
Relevant Logs/Tracebacks
Scanned automatically with https://github.com/etairl/Probus
Bug Description
Summary
image_documents_to_base64inllama-index-coreopens any path supplied viaImageDocument.metadata["file_path"]and base64-encodes the bytes — with no allow-listed root, no symlink check, and no MIME validation. Applications that buildImageDocumentinstances from user-influenced data (a common pattern: tool arguments, JSON request bodies, deserialized documents) effectively expose an arbitrary-file-read primitive whose output is forwarded to the configured multimodal LLM.Affected code
llama-index-core/llama_index/core/multi_modal_llms/generic_utils.py, lines 65-86:encode_imagethen performs an unguardedopen(image_path, "rb")and base64-encodes the bytes.metadatais a free-form dict onDocument, so no validator gates it. Theis_image_pilcheck onImageDocument.__init__only fires for theimage_path=constructor argument, not formetadata. The library's own tests use this construction pattern (tests/multi_modal_llms/test_generic_utils.py:67instantiatesImageDocument(metadata={"file_path": "test.jpg"})).Impact
In RAG / multimodal-agent applications that ingest user-influenced input into
ImageDocument(or that allow tool calls to construct one), an attacker can read arbitrary files the process has access to —/etc/passwd,~/.aws/credentials,/var/run/secrets/kubernetes.io/serviceaccount/token, application.envfiles, etc. The bytes are returned inimage_encodingsand assigned toImageDocument.image, then forwarded to the LLM provider — and typically echoed back to the user when the model is asked to "describe this image."Suggested fix
Restrict accepted paths to an explicit allow-listed root, reject symlinks, and validate MIME/size before encoding:
A more defensive option: stop honoring
metadata["file_path"]as a path source entirely, and require callers to setimage_path(which goes through the existingis_image_pilcheck) or pre-encodedimage.Scope note
I'm aware the project's security policy classifies path-traversal-from-untrusted-paths as out of scope for the Huntr bounty (treated as application-layer responsibility). Filing this as a public bug rather than as a security report — many users may not realize that
metadata["file_path"]bypasses the constructor's image-validation check.Version
0.14.21
Steps to Reproduce
The same primitive works against
~/.aws/credentials,/var/run/secrets/kubernetes.io/serviceaccount/token, application.envfiles — anything the process can read.In a real deployment this is reachable any time user input flows into an
ImageDocument's metadata (tool-call arguments, JSON request bodies, deserialized documents, third-party feeds). The encoded bytes end up inImageDocument.imageviaset_base64_and_mimetype_for_image_docsand are forwarded to the multimodal LLM provider, where they are typically echoed back when the model is asked to describe the "image."Relevant Logs/Tracebacks