From d2aa69bf4951e04563350639c913bae5224590a3 Mon Sep 17 00:00:00 2001 From: Christian Monch Date: Wed, 15 Jul 2026 12:40:16 +0200 Subject: [PATCH 1/3] add path validation and tests Verify that curated path and incoming path do not point outside of the storage root-tree. --- dump_things_service/collection_endpoints.py | 33 +++++++++++++ dump_things_service/tests/test_paths.py | 53 +++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 dump_things_service/tests/test_paths.py diff --git a/dump_things_service/collection_endpoints.py b/dump_things_service/collection_endpoints.py index aa302a9..6074895 100644 --- a/dump_things_service/collection_endpoints.py +++ b/dump_things_service/collection_endpoints.py @@ -136,6 +136,9 @@ async def create_or_replace_collection( # Check for incoming directory if any of the tokens allows writing validate_incoming_paths(abstract_config, body) + # Check for paths in the storage route + validate_path_destinations(instance_state, body) + # If the configuration already exist, we have to delete it here and # manifest the reduced configuration. This ensures that the new collection # is fully manifested later @@ -279,3 +282,33 @@ def validate_incoming_paths( status_code=HTTP_406_NOT_ACCEPTABLE, detail=detail, ) + + +def validate_path_destinations( + instance_state: InstanceState, + collection_request: CollectionRequest, +): + validate_path_destination( + instance_state=instance_state, + path=collection_request.curated, + name='curated', + ) + if collection_request.incoming is not None: + validate_path_destination( + instance_state=instance_state, + path=collection_request.incoming, + name='incoming', + ) + + +def validate_path_destination( + instance_state: InstanceState, + path: PurePosixPath, + name: str, +): + abs_path = (instance_state.store_path / Path(path)).resolve().absolute() + if not abs_path.is_relative_to(instance_state.store_path): + raise HTTPException( + status_code=HTTP_406_NOT_ACCEPTABLE, + detail=f"{name}-path '{path}' is outside of the store path.", + ) diff --git a/dump_things_service/tests/test_paths.py b/dump_things_service/tests/test_paths.py new file mode 100644 index 0000000..e5fbb0e --- /dev/null +++ b/dump_things_service/tests/test_paths.py @@ -0,0 +1,53 @@ +from pathlib import ( + Path, + PurePosixPath, +) + + +from dump_things_service import HTTP_406_NOT_ACCEPTABLE +from dump_things_service.collection_endpoints import CollectionRequest + + +# Path to a local simple test schema +test_schema_location = str((Path(__file__).parent / 'testschema.yaml').absolute()) + +test_curated_path_collection_name = 'test_curated_path_collection' +test_curated_path_collection_request = CollectionRequest( + name=test_curated_path_collection_name, + default_token='test_default_token', + curated=PurePosixPath('..'), + schema=test_schema_location, +) + +test_incoming_path_collection_name = 'test_incoming_path_collection' +test_incoming_path_collection_request = CollectionRequest( + name=test_incoming_path_collection_name, + default_token='test_default_token', + curated=PurePosixPath('curated/'), + schema=test_schema_location, + incoming=PurePosixPath('..'), +) + + +def test_curated_path(fastapi_client_simple): + test_client, _, admin_token = fastapi_client_simple + + # Try to add a collection with an illegal curated path + response = test_client.post( + '/collections', + headers={'x-dumpthings-token': admin_token}, + json=test_curated_path_collection_request.model_dump(mode='json', by_alias=True), + ) + assert response.status_code == HTTP_406_NOT_ACCEPTABLE + + +def test_incoming_path(fastapi_client_simple): + test_client, _, admin_token = fastapi_client_simple + + # Try to add a collection with an illegal curated path + response = test_client.post( + '/collections', + headers={'x-dumpthings-token': admin_token}, + json=test_incoming_path_collection_request.model_dump(mode='json', by_alias=True), + ) + assert response.status_code == HTTP_406_NOT_ACCEPTABLE -- 2.52.0 From 31131bec740f3bb00baa2a45f2708510d08fbf83 Mon Sep 17 00:00:00 2001 From: Christian Monch Date: Wed, 15 Jul 2026 12:44:31 +0200 Subject: [PATCH 2/3] update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ba2701..41a73ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 6.3.3 (XXXX-XX-XX) + +# Changes + +- Ensure that curated path and incoming path definitions do not point + outside of the storage-root tree. + + # 6.3.2 (2026-07-07) ## Changes -- 2.52.0 From 60d6085ce4c0b583e2c43254b99c13d06a944a44 Mon Sep 17 00:00:00 2001 From: Christian Monch Date: Wed, 15 Jul 2026 12:48:19 +0200 Subject: [PATCH 3/3] chore: apply standard formatting --- dump_things_service/tests/test_paths.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dump_things_service/tests/test_paths.py b/dump_things_service/tests/test_paths.py index e5fbb0e..aac8296 100644 --- a/dump_things_service/tests/test_paths.py +++ b/dump_things_service/tests/test_paths.py @@ -3,11 +3,9 @@ from pathlib import ( PurePosixPath, ) - from dump_things_service import HTTP_406_NOT_ACCEPTABLE from dump_things_service.collection_endpoints import CollectionRequest - # Path to a local simple test schema test_schema_location = str((Path(__file__).parent / 'testschema.yaml').absolute()) @@ -36,7 +34,9 @@ def test_curated_path(fastapi_client_simple): response = test_client.post( '/collections', headers={'x-dumpthings-token': admin_token}, - json=test_curated_path_collection_request.model_dump(mode='json', by_alias=True), + json=test_curated_path_collection_request.model_dump( + mode='json', by_alias=True + ), ) assert response.status_code == HTTP_406_NOT_ACCEPTABLE @@ -48,6 +48,8 @@ def test_incoming_path(fastapi_client_simple): response = test_client.post( '/collections', headers={'x-dumpthings-token': admin_token}, - json=test_incoming_path_collection_request.model_dump(mode='json', by_alias=True), + json=test_incoming_path_collection_request.model_dump( + mode='json', by_alias=True + ), ) assert response.status_code == HTTP_406_NOT_ACCEPTABLE -- 2.52.0