-
-
Notifications
You must be signed in to change notification settings - Fork 1
WIP: full e2e tests #525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
WIP: full e2e tests #525
Conversation
| # Check if artifact exists | ||
| artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
|
|
||
| if not artifact_path.exists(): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
To fix this issue, the code should ensure that the file being accessed is strictly within the ARTIFACTS_DIR directory. The best-practice method is to:
- Join the user-provided
artifact_id(plus extension) with the base directory; - Normalize the resulting path using
os.path.normpathor (forPath).resolve(); - Verify that the resulting path is still within (a child of) the base directory;
- Only proceed to access the file if these checks pass.
In this snippet, in download_artifact, the lines constructing and checking artifact_path starting at line 71 must be changed. The check should:
- Use
Path.resolve()to get the normalized absolute path for the requested file, - Make sure the resolved path is within
ARTIFACTS_DIR(using.relative_to()or path prefix checking).
If the check fails, raise a 400 or 403 error.
No new custom helper methods are strictly required, but the code will need a proper check sequence as described above. The import for os already exists, and Path is already imported, so no new imports are required.
-
Copy modified lines R71-R77
| @@ -68,7 +68,13 @@ | ||
| ): | ||
| """Download artifact file.""" | ||
| # Check if artifact exists | ||
| artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
| # Safely join and normalize artifact path | ||
| artifact_path = (ARTIFACTS_DIR / f"{artifact_id}.zip").resolve() | ||
| # Prevent path traversal outside the artifacts directory | ||
| try: | ||
| artifact_path.relative_to(ARTIFACTS_DIR.resolve()) | ||
| except ValueError: | ||
| raise HTTPException(status_code=400, detail="Invalid artifact_id path") | ||
|
|
||
| if not artifact_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Artifact not found") |
|
|
||
| # Handle HEAD request | ||
| if request.method == "HEAD": | ||
| file_size = artifact_path.stat().st_size |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
To properly fix this vulnerability, we must ensure that the resulting artifact_path—after joining the user input—remains within the intended artifacts root directory (ARTIFACTS_DIR). This is best done by:
- Composing the final path (with any extensions/postfixes) as before.
- Normalizing the resulting path with
.resolve()(oros.path.normpath/os.path.realpath) to remove any traversal elements. - Verifying that the normalized path is a descendant (or the same as) the allowed directory (
ARTIFACTS_DIR). This can be checked by usingPath.is_relative_to(Python 3.9+) or by comparing path components if using an older version. - If the check fails, raise a 400 or 403 error.
All changes must be made only in the shown snippet, affecting the download_artifact function (lines 62–95 or so), specifically the calculation and security checking of artifact_path.
-
Copy modified lines R71-R79
| @@ -68,7 +68,15 @@ | ||
| ): | ||
| """Download artifact file.""" | ||
| # Check if artifact exists | ||
| artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
| artifact_path = (ARTIFACTS_DIR / f"{artifact_id}.zip").resolve() | ||
| # Ensure resolved artifact_path is within ARTIFACTS_DIR to prevent path traversal | ||
| try: | ||
| if not artifact_path.is_relative_to(ARTIFACTS_DIR.resolve()): | ||
| raise HTTPException(status_code=400, detail="Invalid artifact id") | ||
| except AttributeError: | ||
| # For Python <3.9 fallback | ||
| if not str(artifact_path).startswith(str(ARTIFACTS_DIR.resolve()) + os.sep): | ||
| raise HTTPException(status_code=400, detail="Invalid artifact id") | ||
|
|
||
| if not artifact_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Artifact not found") |
| if range_header: | ||
| # Parse range header (simplified implementation) | ||
| range_start = int(range_header.replace("bytes=", "").split("-")[0]) | ||
| with open(artifact_path, "rb") as f: |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
The best general fix is to ensure the constructed artifact_path cannot be manipulated to escape the intended ARTIFACTS_DIR. This is well-achieved by normalizing the resulting path (with .resolve()) and then checking that the resolved path is indeed inside the allowed directory. Specifically:
- After constructing
artifact_path, call.resolve()on it. - Check that the resolved path is a subpath (or the same path) as
ARTIFACTS_DIR.resolve()(e.g., usingis_relative_toin Python 3.9+, or a string startswith in older versions). - Only proceed to open the file if this check passes; otherwise, return a 403 error.
- These changes should be applied to the download endpoint in
server.py, lines 71–75 (right after constructingartifact_path).
Requirements:
- From Python 3.9+,
Path.is_relative_tocan be used (we'll assume Python 3.9+ since FastAPI is reasonably modern). - No extra dependencies needed; use the standard library.
-
Copy modified lines R72-R78 -
Copy modified line R80 -
Copy modified line R85 -
Copy modified line R88 -
Copy modified line R97
| @@ -69,13 +69,20 @@ | ||
| """Download artifact file.""" | ||
| # Check if artifact exists | ||
| artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
| # Prevent path traversal by resolving and ensuring within ARTIFACTS_DIR | ||
| try: | ||
| resolved_artifact_path = artifact_path.resolve(strict=False) | ||
| if not resolved_artifact_path.is_relative_to(ARTIFACTS_DIR.resolve()): | ||
| raise HTTPException(status_code=403, detail="Access denied") | ||
| except Exception: | ||
| raise HTTPException(status_code=400, detail="Invalid artifact path") | ||
|
|
||
| if not artifact_path.exists(): | ||
| if not resolved_artifact_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Artifact not found") | ||
|
|
||
| # Handle HEAD request | ||
| if request.method == "HEAD": | ||
| file_size = artifact_path.stat().st_size | ||
| file_size = resolved_artifact_path.stat().st_size | ||
| return Response(headers={"Content-Length": str(file_size)}, status_code=200) | ||
|
|
||
| # Handle Range requests for resumable downloads | ||
| @@ -83,7 +85,7 @@ | ||
| if range_header: | ||
| # Parse range header (simplified implementation) | ||
| range_start = int(range_header.replace("bytes=", "").split("-")[0]) | ||
| with open(artifact_path, "rb") as f: | ||
| with open(resolved_artifact_path, "rb") as f: | ||
| f.seek(range_start) | ||
| content = f.read() | ||
| return Response( | ||
| @@ -92,7 +94,7 @@ | ||
| headers={"Content-Range": f"bytes {range_start}-{len(content) - 1}/{artifact_path.stat().st_size}"}, | ||
| ) | ||
|
|
||
| return FileResponse(artifact_path) | ||
| return FileResponse(resolved_artifact_path) | ||
|
|
||
|
|
||
| class UpdateRequest(BaseModel): |
| return Response( | ||
| content=content, | ||
| status_code=206, | ||
| headers={"Content-Range": f"bytes {range_start}-{len(content) - 1}/{artifact_path.stat().st_size}"}, |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
To fix this vulnerability, the server should ensure that any file access using a filename derived from user input (here, artifact_id) does not allow traversal outside of the intended directory (ARTIFACTS_DIR). The simplest and most robust approach is to:
- Join and normalize the path (using
os.path.normpathorPath.resolve) after combining the base directory and the constructed filename. - Check that the resolved path is within the intended directory (i.e., that the resolved path starts with or is a descendant of
ARTIFACTS_DIR). If not, return a 400 or 403 error. - Perform existing file system operations only after this check passes.
This requires modifying the code at/near places where artifact_path is constructed and before it is used, in the download_artifact endpoint. No new methods are strictly needed—existing code can use the standard library pathlib and/or os.
-
Copy modified lines R71-R78
| @@ -68,7 +68,14 @@ | ||
| ): | ||
| """Download artifact file.""" | ||
| # Check if artifact exists | ||
| artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
| # Prevent directory traversal: ensure resolved path is within ARTIFACTS_DIR | ||
| unsafe_artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
| try: | ||
| artifact_path = unsafe_artifact_path.resolve(strict=False) | ||
| except Exception: | ||
| raise HTTPException(status_code=400, detail="Invalid artifact path") | ||
| if not str(artifact_path).startswith(str(ARTIFACTS_DIR.resolve())): | ||
| raise HTTPException(status_code=403, detail="Access to artifact denied") | ||
|
|
||
| if not artifact_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Artifact not found") |
| headers={"Content-Range": f"bytes {range_start}-{len(content) - 1}/{artifact_path.stat().st_size}"}, | ||
| ) | ||
|
|
||
| return FileResponse(artifact_path) |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
General fix:
To prevent directory traversal or unintended file access, before accessing any file path that includes user input (like artifact_id), you must validate and constrain the resolved path to ensure it does not escape a designated root directory.
Best fix for this scenario:
Normalize and join the path using os.path.normpath (or PurePath if using pathlib), and then verify that the resulting path is within ARTIFACTS_DIR. If it is outside, abort with an error. You may also wish to further restrict artifact_id (e.g., only allow alphanumerics and -/_) using a regex, but the normalization+containment check is most important.
Implementation in code:
- After composing
artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip", resolveartifact_pathto its absolute/canonical form (using.resolve()). - Check that the resolved path is a child of (or the same as)
ARTIFACTS_DIR.resolve(). - If not, raise a 400 error or similar.
Required imports/methods:
- No new external dependencies are needed, Pathlib already supports
.resolve()and path inclusion checks.
-
Copy modified lines R72-R76 -
Copy modified line R78
| @@ -69,8 +69,13 @@ | ||
| """Download artifact file.""" | ||
| # Check if artifact exists | ||
| artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
| # Normalize and validate the path to prevent directory traversal | ||
| resolved_artifact_path = artifact_path.resolve() | ||
| artifacts_dir_resolved = ARTIFACTS_DIR.resolve() | ||
| if not str(resolved_artifact_path).startswith(str(artifacts_dir_resolved) + os.sep): | ||
| raise HTTPException(status_code=400, detail="Invalid artifact_id path") | ||
|
|
||
| if not artifact_path.exists(): | ||
| if not resolved_artifact_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Artifact not found") | ||
|
|
||
| # Handle HEAD request |
| """Test helper: Upload an artifact file for testing.""" | ||
| artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
|
|
||
| with open(artifact_path, "wb") as f: |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
To fix the issue while preserving current behavior, the code should ensure user-provided artifact_id cannot escape the intended artifact storage directory (ARTIFACTS_DIR). The best general-purpose fix is to validate and sanitize the resulting file path. This can be done by joining and normalizing the path, then ensuring it resides within the intended directory via a prefix check, as recommended in the background section.
Specifically, before opening the file at artifact_path, do the following:
- Use
os.path.normpath()orPath.resolve()to get the canonical final path. - Ensure this normalized path is still within
ARTIFACTS_DIR. - If the check fails, return an error (HTTP 400 or 403).
This change is needed within the test_upload_artifact function, lines 295-299. No changes to imports are required since pathlib.Path is already imported.
-
Copy modified lines R297-R302
| @@ -294,7 +294,12 @@ | ||
| """Test helper: Upload an artifact file for testing.""" | ||
| artifact_path = ARTIFACTS_DIR / f"{artifact_id}.zip" | ||
|
|
||
| with open(artifact_path, "wb") as f: | ||
| # Validate that the resolved path is within the ARTIFACTS_DIR | ||
| resolved_path = artifact_path.resolve() | ||
| if not str(resolved_path).startswith(str(ARTIFACTS_DIR.resolve()) + os.sep): | ||
| raise HTTPException(status_code=400, detail="Invalid artifact_id path") | ||
|
|
||
| with open(resolved_path, "wb") as f: | ||
| content = await file.read() | ||
| f.write(content) | ||
|
|
| return { | ||
| "artifact_metadata": artifacts_db.get(artifact_id, {}), | ||
| "size_analysis": size_analysis_db.get(artifact_id, {}), | ||
| "has_size_analysis_file": (RESULTS_DIR / f"{artifact_id}_size_analysis.json").exists(), |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
The best way to fix this problem is to validate, before using, that the constructed file path is both safe and strictly contained within the intended parent directory (RESULTS_DIR). This can be accomplished by first joining and normalizing the constructed path with os.path.normpath (or using resolve() from pathlib), and then checking that the resulting path is still within RESULTS_DIR. If not, return an error. This solution avoids directory traversal as well as absolute-path targeting.
Concretely, in test_get_results, before any use of artifact_id to assemble a file path (in both lines 310 and 311), normalize the path and check containment:
- Build the full path using
RESULTS_DIR / f"{artifact_id}_size_analysis.json"(and similarly forartifact_id_app). - Call
.resolve()on the result. - Check if the resolved path is a child (descendant) of
RESULTS_DIR.resolve(). If not, return an HTTP 400 or 403 error or set the flag asFalse(as appropriate).
Add an import for os or use Pathlib's facilities as the rest of the file does.
-
Copy modified lines R307-R321 -
Copy modified lines R325-R326
| @@ -304,11 +304,26 @@ | ||
| @app.get("/test/results/{artifact_id}") | ||
| async def test_get_results(artifact_id: str): | ||
| """Test helper: Get analysis results for an artifact.""" | ||
| # Validate artifact_id to prevent path traversal | ||
| size_analysis_path = (RESULTS_DIR / f"{artifact_id}_size_analysis.json") | ||
| app_path = (RESULTS_DIR / f"{artifact_id}_app") | ||
|
|
||
| try: | ||
| # Ensure both are within RESULTS_DIR | ||
| size_analysis_path_resolved = size_analysis_path.resolve() | ||
| app_path_resolved = app_path.resolve() | ||
| results_dir_resolved = RESULTS_DIR.resolve() | ||
| has_size_analysis_file = size_analysis_path_resolved.parent == results_dir_resolved and size_analysis_path_resolved.exists() | ||
| has_installable_app = app_path_resolved.parent == results_dir_resolved and app_path_resolved.exists() | ||
| except Exception: | ||
| has_size_analysis_file = False | ||
| has_installable_app = False | ||
|
|
||
| return { | ||
| "artifact_metadata": artifacts_db.get(artifact_id, {}), | ||
| "size_analysis": size_analysis_db.get(artifact_id, {}), | ||
| "has_size_analysis_file": (RESULTS_DIR / f"{artifact_id}_size_analysis.json").exists(), | ||
| "has_installable_app": (RESULTS_DIR / f"{artifact_id}_app").exists(), | ||
| "has_size_analysis_file": has_size_analysis_file, | ||
| "has_installable_app": has_installable_app, | ||
| } | ||
|
|
||
|
|
| "artifact_metadata": artifacts_db.get(artifact_id, {}), | ||
| "size_analysis": size_analysis_db.get(artifact_id, {}), | ||
| "has_size_analysis_file": (RESULTS_DIR / f"{artifact_id}_size_analysis.json").exists(), | ||
| "has_installable_app": (RESULTS_DIR / f"{artifact_id}_app").exists(), |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
The best way to fix this issue is to ensure that any filename or file path constructed using untrusted input (artifact_id) cannot escape the intended "root" directory (RESULTS_DIR). To do this, we'll first construct the intended path, then normalize it using os.path.normpath or equivalent. We'll then ensure that the normalized path is still strictly within (i.e., starts with) the canonical path to RESULTS_DIR. If it does not, we raise an error.
Specifically, in the test_get_results endpoint, before using (RESULTS_DIR / f"{artifact_id}_size_analysis.json") and (RESULTS_DIR / f"{artifact_id}_app"), we will resolve and validate the filenames. This can be factored into a small helper function for reusability (since the same fix pattern will be used for both). No new functionality should be introduced; just extra checking and safe path construction. As minimum required, import os if not already, and provide the validation logic only using standard libraries.
-
Copy modified lines R18-R19 -
Copy modified lines R26-R37 -
Copy modified lines R315-R316 -
Copy modified lines R320-R321 -
Copy modified line R328
| @@ -15,12 +15,26 @@ | ||
| from pathlib import Path | ||
| from typing import Any, Dict, List, Optional | ||
|
|
||
| import os | ||
|
|
||
| from fastapi import FastAPI, Header, HTTPException, Request, Response, UploadFile | ||
| from fastapi.responses import FileResponse, JSONResponse | ||
| from pydantic import BaseModel | ||
|
|
||
| app = FastAPI(title="Mock Sentry API for Launchpad E2E Tests") | ||
|
|
||
| def safe_artifact_file_path(artifact_id: str, root_dir: Path, suffix: str) -> Path: | ||
| """ | ||
| Safely build a file path for an artifact, ensuring it stays within the root_dir. | ||
| """ | ||
| unsafe_path = root_dir / f"{artifact_id}{suffix}" | ||
| # Resolve absolute path (without actually creating files), prevent symlink attacks. | ||
| normalized_path = unsafe_path.resolve() | ||
| root_dir_resolved = root_dir.resolve() | ||
| if not str(normalized_path).startswith(str(root_dir_resolved) + os.sep): | ||
| raise HTTPException(status_code=400, detail="Invalid artifact_id") | ||
| return normalized_path | ||
|
|
||
| # Storage paths | ||
| DATA_DIR = Path("/app/data") | ||
| ARTIFACTS_DIR = DATA_DIR / "artifacts" | ||
| @@ -304,18 +312,20 @@ | ||
| @app.get("/test/results/{artifact_id}") | ||
| async def test_get_results(artifact_id: str): | ||
| """Test helper: Get analysis results for an artifact.""" | ||
| size_analysis_file = safe_artifact_file_path(artifact_id, RESULTS_DIR, "_size_analysis.json") | ||
| app_file = safe_artifact_file_path(artifact_id, RESULTS_DIR, "_app") | ||
| return { | ||
| "artifact_metadata": artifacts_db.get(artifact_id, {}), | ||
| "size_analysis": size_analysis_db.get(artifact_id, {}), | ||
| "has_size_analysis_file": (RESULTS_DIR / f"{artifact_id}_size_analysis.json").exists(), | ||
| "has_installable_app": (RESULTS_DIR / f"{artifact_id}_app").exists(), | ||
| "has_size_analysis_file": size_analysis_file.exists(), | ||
| "has_installable_app": app_file.exists(), | ||
| } | ||
|
|
||
|
|
||
| @app.get("/test/results/{artifact_id}/size-analysis-raw") | ||
| async def test_get_size_analysis_raw(artifact_id: str): | ||
| """Test helper: Get raw size analysis JSON.""" | ||
| result_path = RESULTS_DIR / f"{artifact_id}_size_analysis.json" | ||
| result_path = safe_artifact_file_path(artifact_id, RESULTS_DIR, "_size_analysis.json") | ||
|
|
||
| if not result_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Size analysis not found") |
| """Test helper: Get raw size analysis JSON.""" | ||
| result_path = RESULTS_DIR / f"{artifact_id}_size_analysis.json" | ||
|
|
||
| if not result_path.exists(): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
To fix this, the code should ensure that any file path computed using user input remains inside the intended directory. The best-practice approach is to normalize the constructed path (with os.path.normpath or similar) and ensure it is still under the intended directory (in this case, RESULTS_DIR). This can be done by resolving the path and checking that it starts with the RESULTS_DIR. If the check fails, return a 400 or 403 error. Only then should the file access proceed.
The edit is required only around the usage of artifact_id for the file path, under test_get_size_analysis_raw in tests/e2e/mock-sentry-api/server.py.
You may need to import os or use methods from pathlib, but as os and pathlib are already imported, no new imports are needed.
-
Copy modified lines R318-R325
| @@ -315,7 +315,14 @@ | ||
| @app.get("/test/results/{artifact_id}/size-analysis-raw") | ||
| async def test_get_size_analysis_raw(artifact_id: str): | ||
| """Test helper: Get raw size analysis JSON.""" | ||
| result_path = RESULTS_DIR / f"{artifact_id}_size_analysis.json" | ||
| # Validate artifact_id to prevent path traversal | ||
| candidate_path = RESULTS_DIR / f"{artifact_id}_size_analysis.json" | ||
| try: | ||
| result_path = candidate_path.resolve(strict=False) | ||
| except Exception: | ||
| raise HTTPException(status_code=400, detail="Invalid artifact ID") | ||
| if not str(result_path).startswith(str(RESULTS_DIR.resolve())): | ||
| raise HTTPException(status_code=400, detail="Invalid artifact ID") | ||
|
|
||
| if not result_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Size analysis not found") |
| if not result_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Size analysis not found") | ||
|
|
||
| return JSONResponse(json.loads(result_path.read_text())) |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High test
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
To fix this path traversal vulnerability, sanitize or validate the user-supplied artifact_id before using it in a filesystem operation. The best approach is to construct the final file path, normalize it using os.path.normpath (or .resolve() from pathlib), and then check that the final path is indeed inside the intended RESULTS_DIR directory. If the check fails, return an HTTP 400 or 403 error. This prevents attackers from using crafted artifact_id values to escape the intended directory.
What to change:
- In
test_get_size_analysis_raw, normalize the constructed path and ensure it is within theRESULTS_DIR. - If the normalized path does not start with/contain the desired base directory, raise an HTTP error.
- To use
os.path.normpath, ensureosis imported (which it is). - The fix goes in the block beginning at line 316.
-
Copy modified lines R318-R322
| @@ -315,11 +315,13 @@ | ||
| @app.get("/test/results/{artifact_id}/size-analysis-raw") | ||
| async def test_get_size_analysis_raw(artifact_id: str): | ||
| """Test helper: Get raw size analysis JSON.""" | ||
| result_path = RESULTS_DIR / f"{artifact_id}_size_analysis.json" | ||
|
|
||
| # Build and normalize the intended path | ||
| result_path = (RESULTS_DIR / f"{artifact_id}_size_analysis.json").resolve() | ||
| # Prevent directory traversal: ensure result_path remains under RESULTS_DIR | ||
| if not str(result_path).startswith(str(RESULTS_DIR.resolve()) + os.sep): | ||
| raise HTTPException(status_code=400, detail="Invalid artifact_id") | ||
| if not result_path.exists(): | ||
| raise HTTPException(status_code=404, detail="Size analysis not found") | ||
|
|
||
| return JSONResponse(json.loads(result_path.read_text())) | ||
|
|
||
|
|
No description provided.