Skip to content

Commit fbb2881

Browse files
committed
fix: prevent symlink/traversal attacks
Signed-off-by: Jorge Garcia Oncins <jgarciao@redhat.com>
1 parent f52b283 commit fbb2881

File tree

2 files changed

+84
-47
lines changed

2 files changed

+84
-47
lines changed

tests/llama_stack/conftest.py

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@
3737
)
3838
from tests.llama_stack.utils import (
3939
create_llama_stack_distribution,
40-
vector_store_create_file_from_path,
41-
vector_store_create_file_from_url,
40+
vector_store_upload_doc_sources,
4241
wait_for_llama_stack_client_ready,
4342
wait_for_unique_llama_stack_pod,
4443
)
@@ -809,52 +808,13 @@ def vector_store(
809808

810809
if doc_sources:
811810
try:
812-
if not isinstance(doc_sources, list):
813-
raise TypeError(f"doc_sources must be a list[str], got {type(doc_sources).__name__}")
814-
LOGGER.info(
815-
"Uploading doc_sources to vector_store (provider_id=%s, id=%s): %s",
816-
vector_io_provider,
817-
vector_store.id,
818-
doc_sources,
811+
vector_store_upload_doc_sources(
812+
doc_sources=doc_sources,
813+
repo_root=Path(request.config.rootdir).resolve(),
814+
llama_stack_client=unprivileged_llama_stack_client,
815+
vector_store=vector_store,
816+
vector_io_provider=vector_io_provider,
819817
)
820-
repo_root = Path(request.config.rootdir).resolve()
821-
for source in doc_sources:
822-
if source.startswith(("http://", "https://")):
823-
vector_store_create_file_from_url(
824-
url=source,
825-
llama_stack_client=unprivileged_llama_stack_client,
826-
vector_store=vector_store,
827-
)
828-
else:
829-
raw_path = Path(source)
830-
resolved_source = (
831-
raw_path.resolve() if raw_path.is_absolute() else (repo_root / raw_path).resolve()
832-
)
833-
if not resolved_source.is_relative_to(repo_root):
834-
raise ValueError(
835-
f"doc_sources path must be under repo root ({repo_root}): {source!r}",
836-
)
837-
source_path = resolved_source
838-
839-
if source_path.is_dir():
840-
files = sorted(source_path.iterdir())
841-
if not files:
842-
raise FileNotFoundError(f"No files found in directory: {source_path}")
843-
for file_path in files:
844-
if file_path.is_file():
845-
vector_store_create_file_from_path(
846-
file_path=file_path,
847-
llama_stack_client=unprivileged_llama_stack_client,
848-
vector_store=vector_store,
849-
)
850-
elif source_path.is_file():
851-
vector_store_create_file_from_path(
852-
file_path=source_path,
853-
llama_stack_client=unprivileged_llama_stack_client,
854-
vector_store=vector_store,
855-
)
856-
else:
857-
raise FileNotFoundError(f"Document source not found: {source_path}")
858818
except Exception:
859819
try:
860820
unprivileged_llama_stack_client.vector_stores.delete(vector_store_id=vector_store.id)

tests/llama_stack/utils.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,80 @@ def vector_store_create_file_from_path(
276276
)
277277
LOGGER.info(f"Addded uploaded file (filename{uploaded_file.filename} to vector store {vector_store.id}")
278278
return vs_file
279+
280+
281+
def vector_store_upload_doc_sources(
282+
doc_sources: Any,
283+
repo_root: Path,
284+
llama_stack_client: LlamaStackClient,
285+
vector_store: Any,
286+
vector_io_provider: str,
287+
) -> None:
288+
"""Upload parametrized document sources (URLs and repo-local paths) to a vector store.
289+
290+
Resolves each local path under ``repo_root`` and re-resolves directory entries to avoid
291+
symlink escape outside the repository.
292+
293+
Args:
294+
doc_sources: List of URL or path strings (repo-relative or absolute under repo root).
295+
repo_root: Resolved repository root; local paths must resolve under this directory.
296+
llama_stack_client: Client used for file and vector store APIs.
297+
vector_store: Target vector store (must expose ``id``).
298+
vector_io_provider: Provider id for log context only.
299+
300+
Raises:
301+
TypeError: If ``doc_sources`` is not a list.
302+
ValueError: If a local path resolves outside ``repo_root``.
303+
FileNotFoundError: If a file or non-empty directory source is missing.
304+
"""
305+
if not isinstance(doc_sources, list):
306+
raise TypeError(f"doc_sources must be a list[str], got {type(doc_sources).__name__}")
307+
LOGGER.info(
308+
"Uploading doc_sources to vector_store (provider_id=%s, id=%s): %s",
309+
vector_io_provider,
310+
vector_store.id,
311+
doc_sources,
312+
)
313+
repo_root_resolved = repo_root.resolve()
314+
for source in doc_sources:
315+
if source.startswith(("http://", "https://")):
316+
vector_store_create_file_from_url(
317+
url=source,
318+
llama_stack_client=llama_stack_client,
319+
vector_store=vector_store,
320+
)
321+
continue
322+
raw_path = Path(source) # noqa: FCN001
323+
resolved_source = raw_path.resolve() if raw_path.is_absolute() else (repo_root_resolved / raw_path).resolve()
324+
if not resolved_source.is_relative_to(repo_root_resolved):
325+
raise ValueError(
326+
f"doc_sources path must be under repo root ({repo_root_resolved}): {source!r}",
327+
)
328+
source_path = resolved_source
329+
330+
if source_path.is_dir():
331+
files = sorted(source_path.iterdir())
332+
if not files:
333+
raise FileNotFoundError(f"No files found in directory: {source_path}")
334+
for file_path in files:
335+
file_path_resolved = file_path.resolve(strict=True)
336+
if not file_path_resolved.is_relative_to(repo_root_resolved):
337+
raise ValueError(
338+
f"doc_sources directory entry must resolve under repo root "
339+
f"({repo_root_resolved}): {file_path!r} -> {file_path_resolved!r}",
340+
)
341+
if not file_path_resolved.is_file():
342+
continue
343+
vector_store_create_file_from_path(
344+
file_path=file_path_resolved,
345+
llama_stack_client=llama_stack_client,
346+
vector_store=vector_store,
347+
)
348+
elif source_path.is_file():
349+
vector_store_create_file_from_path(
350+
file_path=source_path,
351+
llama_stack_client=llama_stack_client,
352+
vector_store=vector_store,
353+
)
354+
else:
355+
raise FileNotFoundError(f"Document source not found: {source_path}")

0 commit comments

Comments
 (0)