Skip to content

IMAT Stack Viewer new endpoints#310

Merged
Pasarus merged 7 commits intomainfrom
imat-stack-viewer
Apr 10, 2026
Merged

IMAT Stack Viewer new endpoints#310
Pasarus merged 7 commits intomainfrom
imat-stack-viewer

Conversation

@Pasarus
Copy link
Copy Markdown
Member

@Pasarus Pasarus commented Feb 9, 2026

Closes None, it's the backend support for this issue: fiaisis/frontend#622

Description

2 endpoints added, to list, and return images using smart methodologies for compression.

Comment thread plotting_service/routers/imat.py Fixed
Comment thread plotting_service/routers/imat.py Fixed
Comment thread plotting_service/routers/imat.py Fixed
Comment thread plotting_service/routers/imat.py Fixed
Comment thread plotting_service/routers/imat.py Fixed
Comment thread plotting_service/routers/imat.py Fixed
keiranjprice101
keiranjprice101 previously approved these changes Feb 9, 2026
Copy link
Copy Markdown
Collaborator

@keiranjprice101 keiranjprice101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

) -> list[str]:
"""Return a sorted list of TIFF images in the given directory."""

dir_path = (Path(CEPH_DIR) / path).resolve()

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 8 days ago

General approach: treat user input as untrusted, resolve it against a fixed trusted base directory, and explicitly enforce that the resolved result is inside that base. Reject absolute user paths and traversal attempts before accessing the filesystem.

Best concrete fix in plotting_service/routers/imat.py (around lines 104–110 in list_imat_images):

  • Create a trusted base path once in the function: base_dir = Path(CEPH_DIR).resolve().
  • Reject absolute user input (Path(path).is_absolute()).
  • Resolve candidate path from base + relative input.
  • Enforce containment with dir_path.relative_to(base_dir) inside a try/except ValueError.
  • Return HTTP 400 for invalid/traversal paths, and keep existing 404 behavior for not-found directories.
  • Keep existing functionality otherwise (same successful output, same sorting/filtering).

No new imports or dependencies are required; Path and HTTPException/HTTPStatus are already present.

Suggested changeset 1
plotting_service/routers/imat.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/plotting_service/routers/imat.py b/plotting_service/routers/imat.py
--- a/plotting_service/routers/imat.py
+++ b/plotting_service/routers/imat.py
@@ -101,7 +101,17 @@
 ) -> list[str]:
     """Return a sorted list of TIFF images in the given directory."""
 
-    dir_path = (Path(CEPH_DIR) / path).resolve()
+    base_dir = Path(CEPH_DIR).resolve()
+    requested_path = Path(path)
+    if requested_path.is_absolute():
+        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path")
+
+    dir_path = (base_dir / requested_path).resolve()
+    try:
+        dir_path.relative_to(base_dir)
+    except ValueError as err:
+        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path") from err
+
     # Security: Ensure path is within CEPH_DIR
     try:
         safe_check_filepath(dir_path, CEPH_DIR)
EOF
@@ -101,7 +101,17 @@
) -> list[str]:
"""Return a sorted list of TIFF images in the given directory."""

dir_path = (Path(CEPH_DIR) / path).resolve()
base_dir = Path(CEPH_DIR).resolve()
requested_path = Path(path)
if requested_path.is_absolute():
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path")

dir_path = (base_dir / requested_path).resolve()
try:
dir_path.relative_to(base_dir)
except ValueError as err:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path") from err

# Security: Ensure path is within CEPH_DIR
try:
safe_check_filepath(dir_path, CEPH_DIR)
Copilot is powered by AI and may make mistakes. Always verify output.
except FileNotFoundError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err

if not dir_path.exists() or not dir_path.is_dir():

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 8 days ago

General fix: normalize the user-supplied relative path against a trusted base directory and explicitly verify the resolved target remains under that base before any filesystem access.

Best concrete fix in plotting_service/routers/imat.py:

  • In list_imat_images, resolve a trusted base_dir = Path(CEPH_DIR).resolve().
  • Reject absolute input paths early (Path(path).is_absolute()).
  • Build dir_path = (base_dir / path).resolve().
  • Enforce containment with dir_path.relative_to(base_dir) inside a try/except ValueError; if it fails, return HTTP 400.
  • Keep existing not-found behavior for non-existing/non-directory targets.
  • Keep existing functionality (listing image filenames) unchanged for valid in-scope paths.

No new imports are required.

Suggested changeset 1
plotting_service/routers/imat.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/plotting_service/routers/imat.py b/plotting_service/routers/imat.py
--- a/plotting_service/routers/imat.py
+++ b/plotting_service/routers/imat.py
@@ -101,7 +101,17 @@
 ) -> list[str]:
     """Return a sorted list of TIFF images in the given directory."""
 
-    dir_path = (Path(CEPH_DIR) / path).resolve()
+    base_dir = Path(CEPH_DIR).resolve()
+    user_path = Path(path)
+    if user_path.is_absolute():
+        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path")
+
+    dir_path = (base_dir / user_path).resolve()
+    try:
+        dir_path.relative_to(base_dir)
+    except ValueError as err:
+        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path") from err
+
     # Security: Ensure path is within CEPH_DIR
     try:
         safe_check_filepath(dir_path, CEPH_DIR)
EOF
@@ -101,7 +101,17 @@
) -> list[str]:
"""Return a sorted list of TIFF images in the given directory."""

dir_path = (Path(CEPH_DIR) / path).resolve()
base_dir = Path(CEPH_DIR).resolve()
user_path = Path(path)
if user_path.is_absolute():
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path")

dir_path = (base_dir / user_path).resolve()
try:
dir_path.relative_to(base_dir)
except ValueError as err:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path") from err

# Security: Ensure path is within CEPH_DIR
try:
safe_check_filepath(dir_path, CEPH_DIR)
Copilot is powered by AI and may make mistakes. Always verify output.
except FileNotFoundError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err

if not dir_path.exists() or not dir_path.is_dir():

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 8 days ago

Use explicit path containment validation in list_imat_images before any filesystem access:

  • Build a canonical base path: base_dir = Path(CEPH_DIR).resolve().
  • Build and resolve candidate path from user input.
  • Enforce containment with dir_path.relative_to(base_dir) (raises ValueError if outside base).
  • Reject invalid paths with HTTP 400.
  • Keep existing not-found behavior for missing/non-directory paths.

This preserves functionality (valid relative paths under CEPH_DIR still work) while preventing traversal/absolute path escapes and making the sanitizer obvious to CodeQL.

Edit only plotting_service/routers/imat.py, inside list_imat_images around lines 104–112.

Suggested changeset 1
plotting_service/routers/imat.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/plotting_service/routers/imat.py b/plotting_service/routers/imat.py
--- a/plotting_service/routers/imat.py
+++ b/plotting_service/routers/imat.py
@@ -15,8 +15,8 @@
     convert_image_to_rgb_array,
     find_latest_image_in_directory,
 )
-from plotting_service.utils import safe_check_filepath
 
+
 ImatRouter = APIRouter()
 
 IMAT_DIR: Path = Path(os.getenv("IMAT_DIR", "/imat")).resolve()
@@ -101,12 +100,14 @@
 ) -> list[str]:
     """Return a sorted list of TIFF images in the given directory."""
 
-    dir_path = (Path(CEPH_DIR) / path).resolve()
-    # Security: Ensure path is within CEPH_DIR
+    base_dir = Path(CEPH_DIR).resolve()
+    dir_path = (base_dir / path).resolve()
+
+    # Security: ensure resolved path stays under CEPH_DIR
     try:
-        safe_check_filepath(dir_path, CEPH_DIR)
-    except FileNotFoundError as err:
-        raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err
+        dir_path.relative_to(base_dir)
+    except ValueError as err:
+        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path") from err
 
     if not dir_path.exists() or not dir_path.is_dir():
         raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found")
EOF
@@ -15,8 +15,8 @@
convert_image_to_rgb_array,
find_latest_image_in_directory,
)
from plotting_service.utils import safe_check_filepath


ImatRouter = APIRouter()

IMAT_DIR: Path = Path(os.getenv("IMAT_DIR", "/imat")).resolve()
@@ -101,12 +100,14 @@
) -> list[str]:
"""Return a sorted list of TIFF images in the given directory."""

dir_path = (Path(CEPH_DIR) / path).resolve()
# Security: Ensure path is within CEPH_DIR
base_dir = Path(CEPH_DIR).resolve()
dir_path = (base_dir / path).resolve()

# Security: ensure resolved path stays under CEPH_DIR
try:
safe_check_filepath(dir_path, CEPH_DIR)
except FileNotFoundError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err
dir_path.relative_to(base_dir)
except ValueError as err:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid directory path") from err

if not dir_path.exists() or not dir_path.is_dir():
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found")
Copilot is powered by AI and may make mistakes. Always verify output.
) -> Response:
"""Return the raw data of a specific TIFF image as binary."""

image_path = (Path(CEPH_DIR) / path).resolve()

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 8 days ago

Use strict, local path validation before any filesystem access:

  1. Resolve a canonical base path once: ceph_root = Path(CEPH_DIR).resolve().
  2. Reject absolute user paths early (Path(path).is_absolute()).
  3. Build candidate path and resolve: image_path = (ceph_root / path).resolve().
  4. Enforce containment with image_path.relative_to(ceph_root) (raises ValueError if traversal escapes root).
  5. Keep existing safe_check_filepath call for defense in depth.
  6. Keep functional behavior unchanged otherwise.

Apply this in plotting_service/routers/imat.py within get_imat_image around current lines 133–139.

Suggested changeset 1
plotting_service/routers/imat.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/plotting_service/routers/imat.py b/plotting_service/routers/imat.py
--- a/plotting_service/routers/imat.py
+++ b/plotting_service/routers/imat.py
@@ -130,7 +130,17 @@
 ) -> Response:
     """Return the raw data of a specific TIFF image as binary."""
 
-    image_path = (Path(CEPH_DIR) / path).resolve()
+    ceph_root = Path(CEPH_DIR).resolve()
+    requested_path = Path(path)
+    if requested_path.is_absolute():
+        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Path must be relative to CEPH_DIR")
+
+    image_path = (ceph_root / requested_path).resolve()
+    try:
+        image_path.relative_to(ceph_root)
+    except ValueError as err:
+        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid path") from err
+
     # Security: Ensure path is within CEPH_DIR
     try:
         safe_check_filepath(image_path, CEPH_DIR)
EOF
@@ -130,7 +130,17 @@
) -> Response:
"""Return the raw data of a specific TIFF image as binary."""

image_path = (Path(CEPH_DIR) / path).resolve()
ceph_root = Path(CEPH_DIR).resolve()
requested_path = Path(path)
if requested_path.is_absolute():
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Path must be relative to CEPH_DIR")

image_path = (ceph_root / requested_path).resolve()
try:
image_path.relative_to(ceph_root)
except ValueError as err:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid path") from err

# Security: Ensure path is within CEPH_DIR
try:
safe_check_filepath(image_path, CEPH_DIR)
Copilot is powered by AI and may make mistakes. Always verify output.
except FileNotFoundError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err

if not image_path.exists() or not image_path.is_file():

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 8 days ago

General fix: for user-controlled file paths, normalize/canonicalize and explicitly enforce that the resolved path stays under a trusted root before any filesystem operations.

Best fix here (single-file, minimal behavior change): in plotting_service/routers/imat.py, inside get_imat_image, compute a resolved base_dir = Path(CEPH_DIR).resolve() and enforce containment with image_path.relative_to(base_dir) (or equivalent). If containment fails, return 404 (matching current not-found behavior style). Keep existing safe_check_filepath call for compatibility/defense-in-depth.

Changes needed:

  • In get_imat_image around lines 133–140:
    • Introduce base_dir.
    • Build image_path from base_dir.
    • Add explicit relative_to containment guard.
    • Keep existing safe_check_filepath try/except.
  • No new imports or dependencies required.
Suggested changeset 1
plotting_service/routers/imat.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/plotting_service/routers/imat.py b/plotting_service/routers/imat.py
--- a/plotting_service/routers/imat.py
+++ b/plotting_service/routers/imat.py
@@ -130,9 +130,17 @@
 ) -> Response:
     """Return the raw data of a specific TIFF image as binary."""
 
-    image_path = (Path(CEPH_DIR) / path).resolve()
-    # Security: Ensure path is within CEPH_DIR
+    base_dir = Path(CEPH_DIR).resolve()
+    image_path = (base_dir / path).resolve()
+
+    # Security: Ensure resolved path is contained within CEPH_DIR
     try:
+        image_path.relative_to(base_dir)
+    except ValueError:
+        raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found")
+
+    # Security: Keep existing centralized path safety validation
+    try:
         safe_check_filepath(image_path, CEPH_DIR)
     except FileNotFoundError as err:
         raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err
EOF
@@ -130,9 +130,17 @@
) -> Response:
"""Return the raw data of a specific TIFF image as binary."""

image_path = (Path(CEPH_DIR) / path).resolve()
# Security: Ensure path is within CEPH_DIR
base_dir = Path(CEPH_DIR).resolve()
image_path = (base_dir / path).resolve()

# Security: Ensure resolved path is contained within CEPH_DIR
try:
image_path.relative_to(base_dir)
except ValueError:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found")

# Security: Keep existing centralized path safety validation
try:
safe_check_filepath(image_path, CEPH_DIR)
except FileNotFoundError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err
Copilot is powered by AI and may make mistakes. Always verify output.
except FileNotFoundError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err

if not image_path.exists() or not image_path.is_file():

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 8 days ago

Use a strict, explicit path containment check in get_imat_image (and similarly in list_imat_images for consistency) by resolving both the base directory and target path and verifying the target is within the base via Path.relative_to(...). Reject absolute user paths early. This preserves existing behavior (valid relative paths under CEPH_DIR still work) while making the security check clear and analyzer-friendly.

Best concrete fix in plotting_service/routers/imat.py:

  • In list_imat_images and get_imat_image, replace direct (Path(CEPH_DIR) / path).resolve() + safe_check_filepath(...) block with:
    • base_dir = Path(CEPH_DIR).resolve()
    • requested = Path(path)
    • reject if requested.is_absolute()
    • resolved = (base_dir / requested).resolve()
    • enforce containment using resolved.relative_to(base_dir) in a try/except ValueError
  • Keep existing existence/type checks and response logic unchanged.

No new imports are needed (already using Path, HTTPException, HTTPStatus).

Suggested changeset 1
plotting_service/routers/imat.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/plotting_service/routers/imat.py b/plotting_service/routers/imat.py
--- a/plotting_service/routers/imat.py
+++ b/plotting_service/routers/imat.py
@@ -101,11 +101,15 @@
 ) -> list[str]:
     """Return a sorted list of TIFF images in the given directory."""
 
-    dir_path = (Path(CEPH_DIR) / path).resolve()
-    # Security: Ensure path is within CEPH_DIR
+    base_dir = Path(CEPH_DIR).resolve()
+    requested_path = Path(path)
+    if requested_path.is_absolute():
+        raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found")
+
+    dir_path = (base_dir / requested_path).resolve()
     try:
-        safe_check_filepath(dir_path, CEPH_DIR)
-    except FileNotFoundError as err:
+        dir_path.relative_to(base_dir)
+    except ValueError as err:
         raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err
 
     if not dir_path.exists() or not dir_path.is_dir():
@@ -130,12 +133,16 @@
 ) -> Response:
     """Return the raw data of a specific TIFF image as binary."""
 
-    image_path = (Path(CEPH_DIR) / path).resolve()
-    # Security: Ensure path is within CEPH_DIR
+    base_dir = Path(CEPH_DIR).resolve()
+    requested_path = Path(path)
+    if requested_path.is_absolute():
+        raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found")
+
+    image_path = (base_dir / requested_path).resolve()
     try:
-        safe_check_filepath(image_path, CEPH_DIR)
-    except FileNotFoundError as err:
-        raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err
+        image_path.relative_to(base_dir)
+    except ValueError as err:
+        raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found") from err
 
     if not image_path.exists() or not image_path.is_file():
         raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found")
EOF
@@ -101,11 +101,15 @@
) -> list[str]:
"""Return a sorted list of TIFF images in the given directory."""

dir_path = (Path(CEPH_DIR) / path).resolve()
# Security: Ensure path is within CEPH_DIR
base_dir = Path(CEPH_DIR).resolve()
requested_path = Path(path)
if requested_path.is_absolute():
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found")

dir_path = (base_dir / requested_path).resolve()
try:
safe_check_filepath(dir_path, CEPH_DIR)
except FileNotFoundError as err:
dir_path.relative_to(base_dir)
except ValueError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err

if not dir_path.exists() or not dir_path.is_dir():
@@ -130,12 +133,16 @@
) -> Response:
"""Return the raw data of a specific TIFF image as binary."""

image_path = (Path(CEPH_DIR) / path).resolve()
# Security: Ensure path is within CEPH_DIR
base_dir = Path(CEPH_DIR).resolve()
requested_path = Path(path)
if requested_path.is_absolute():
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found")

image_path = (base_dir / requested_path).resolve()
try:
safe_check_filepath(image_path, CEPH_DIR)
except FileNotFoundError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Directory not found") from err
image_path.relative_to(base_dir)
except ValueError as err:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found") from err

if not image_path.exists() or not image_path.is_file():
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found")
Copilot is powered by AI and may make mistakes. Always verify output.
@Pasarus Pasarus merged commit 8025e6b into main Apr 10, 2026
5 of 6 checks passed
@Pasarus Pasarus deleted the imat-stack-viewer branch April 10, 2026 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants