Skip to content

Sandbox URL Creation#16

Closed
pixeebot[bot] wants to merge 1 commit intomainfrom
pixeebot/drip-2025-07-09-pixee-python/url-sandbox
Closed

Sandbox URL Creation#16
pixeebot[bot] wants to merge 1 commit intomainfrom
pixeebot/drip-2025-07-09-pixee-python/url-sandbox

Conversation

@pixeebot
Copy link

@pixeebot pixeebot bot commented Jul 9, 2025

This codemod sandboxes calls to requests.get to be more resistant to Server-Side Request Forgery (SSRF) attacks.

Most of the time when you make a GET request to a URL, you're intending to reference an HTTP endpoint, like an internal microservice. However, URLs can point to local file system files, a Gopher stream in your local network, a JAR file on a remote Internet site, and all kinds of other unexpected and undesirable outcomes. When the URL values are influenced by attackers, they can trick your application into fetching internal resources, running malicious code, or otherwise harming the system.
Consider the following code for a Flask app:

from flask import Flask, request
import requests

app = Flask(__name__)

@app.route("/request-url")
def request_url():
    url = request.args["loc"]
    resp = requests.get(url)
    ...

In this case, an attacker could supply a value like "http://169.254.169.254/user-data/" and attempt to access user information.

Our changes introduce sandboxing around URL creation that force developers to specify some boundaries on the types of URLs they expect to create:

  from flask import Flask, request
- import requests
+ from security import safe_requests

  app = Flask(__name__)

  @app.route("/request-url")
  def request_url():
    url = request.args["loc"]
-   resp = requests.get(url)
+   resp = safe_requests.get(url)
    ...

This change alone reduces attack surface significantly because the default behavior of safe_requests.get raises a SecurityException if
a user attempts to access a known infrastructure location, unless specifically disabled.

If you have feedback on this codemod, please let us know!

F.A.Q.

Why does this codemod require a Pixee dependency?

We always prefer to use built-in Python functions or one from a well-known and trusted community dependency. However, we cannot find any such control. If you know of one, please let us know.

Why is this codemod marked as Merge After Cursory Review?

By default, the protection only weaves in 2 checks, which we believe will not cause any issues with the vast majority of code:

  1. The given URL must be HTTP/HTTPS.
  2. The given URL must not point to a "well-known infrastructure target", which includes things like AWS Metadata Service endpoints, and internal routers (e.g., 192.168.1.1) which are common targets of attacks.

However, on rare occasions an application may use a URL protocol like "file://" or "ftp://" in backend or middleware code.

If you want to allow those protocols, change the incoming PR to look more like this and get the best security possible:

-resp = requests.get(url)
+resp = safe_requests.get(url, allowed_protocols=("ftp",))

Dependency Updates

This codemod relies on an external dependency. We have automatically added this dependency to your project's requirements.txt file.

This library holds security tools for protecting Python API calls.

There are a number of places where Python project dependencies can be expressed, including setup.py, pyproject.toml, setup.cfg, and requirements.txt files. If this change is incorrect, or if you are using another packaging system such as poetry, it may be necessary for you to manually add the dependency to the proper location in your project.

More reading

🧚🤖 Powered by Pixeebot

Feedback | Community | Docs | Codemod ID: pixee:python/url-sandbox

re
surrealdb
aiohttp
security==1.3.1
Copy link
Author

Choose a reason for hiding this comment

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

This library holds security tools for protecting Python API calls.

License: MITOpen SourceMore facts

@coderabbitai
Copy link

coderabbitai bot commented Jul 9, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Join our Discord community for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jul 9, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@api.get("/assets/{asset}", description="Get an asset.")
def get_asset(asset: str, width: int = None, height: int = None):
if not width and not height:
try:

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 9 months ago

To fix the issue, we need to validate the asset parameter to ensure it does not allow directory traversal or access to files outside the intended directory. The best approach is to:

  1. Normalize the constructed path using os.path.normpath or pathlib.Path.resolve() to eliminate any .. segments.
  2. Verify that the normalized path is within the intended base directory.
  3. Reject the request if the validation fails.

Additionally, we can use a whitelist of allowed filenames if the set of valid assets is known and limited.


Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -89,7 +89,11 @@
 def get_asset(asset: str, width: int = None, height: int = None):
-    if not width and not height:
-        try:
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/{asset}")
-        except:
-            return fastapi.responses.JSONResponse(status_code=404, content={"message": "This asset does not exist."})
+    base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets"
+    try:
+        # Normalize the path and ensure it is within the base directory
+        asset_path = (base_path / asset).resolve()
+        if not str(asset_path).startswith(str(base_path)):
+            raise ValueError("Invalid asset path")
+        return fastapi.responses.FileResponse(asset_path)
+    except (FileNotFoundError, ValueError):
+        return fastapi.responses.JSONResponse(status_code=404, content={"message": "This asset does not exist."})
     else:
EOF
@@ -89,7 +89,11 @@
def get_asset(asset: str, width: int = None, height: int = None):
if not width and not height:
try:
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/{asset}")
except:
return fastapi.responses.JSONResponse(status_code=404, content={"message": "This asset does not exist."})
base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets"
try:
# Normalize the path and ensure it is within the base directory
asset_path = (base_path / asset).resolve()
if not str(asset_path).startswith(str(base_path)):
raise ValueError("Invalid asset path")
return fastapi.responses.FileResponse(asset_path)
except (FileNotFoundError, ValueError):
return fastapi.responses.JSONResponse(status_code=404, content={"message": "This asset does not exist."})
else:
Copilot is powered by AI and may make mistakes. Always verify output.
if asset == "logo_no_bg":
image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/Astroid Logo no bg.png")
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg.png")

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.

Copilot Autofix

AI 9 months ago

To fix the issue, we need to validate the width parameter before using it in constructing file paths. The best approach is to ensure that width is a positive integer and falls within a reasonable range. Additionally, we should normalize the constructed file path using os.path.normpath and verify that it remains within the intended directory.

Steps to implement the fix:

  1. Validate the width parameter to ensure it is a positive integer within a safe range.
  2. Normalize the constructed file path using os.path.normpath.
  3. Check that the normalized path starts with the intended base directory to prevent path traversal attacks.

Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -98,4 +98,9 @@
             new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg{width}x{height}.png")
+            base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
+            safe_filename = f"Astroid Logo no bg{width}x{height}.png"
+            full_path = os.path.normpath(base_path / safe_filename)
+            if not str(full_path).startswith(str(base_path)):
+                return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
+            new_image.save(full_path)
+            return fastapi.responses.FileResponse(full_path)
         elif asset == "logo":
@@ -103,4 +108,9 @@
             new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo{width}x{height}.png")
+            base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
+            safe_filename = f"Astroid Logo{width}x{height}.png"
+            full_path = os.path.normpath(base_path / safe_filename)
+            if not str(full_path).startswith(str(base_path)):
+                return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
+            new_image.save(full_path)
+            return fastapi.responses.FileResponse(full_path)
         elif asset == "banner":
@@ -108,4 +118,9 @@
             new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner{width}x{height}.png")
+            base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
+            safe_filename = f"Astroid-banner{width}x{height}.png"
+            full_path = os.path.normpath(base_path / safe_filename)
+            if not str(full_path).startswith(str(base_path)):
+                return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
+            new_image.save(full_path)
+            return fastapi.responses.FileResponse(full_path)
         else:
EOF
@@ -98,4 +98,9 @@
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg{width}x{height}.png")
base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
safe_filename = f"Astroid Logo no bg{width}x{height}.png"
full_path = os.path.normpath(base_path / safe_filename)
if not str(full_path).startswith(str(base_path)):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
new_image.save(full_path)
return fastapi.responses.FileResponse(full_path)
elif asset == "logo":
@@ -103,4 +108,9 @@
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo{width}x{height}.png")
base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
safe_filename = f"Astroid Logo{width}x{height}.png"
full_path = os.path.normpath(base_path / safe_filename)
if not str(full_path).startswith(str(base_path)):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
new_image.save(full_path)
return fastapi.responses.FileResponse(full_path)
elif asset == "banner":
@@ -108,4 +118,9 @@
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner{width}x{height}.png")
base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
safe_filename = f"Astroid-banner{width}x{height}.png"
full_path = os.path.normpath(base_path / safe_filename)
if not str(full_path).startswith(str(base_path)):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
new_image.save(full_path)
return fastapi.responses.FileResponse(full_path)
else:
Copilot is powered by AI and may make mistakes. Always verify output.
elif asset == "logo":
image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/Astroid Logo.png")
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo.png")

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.

Copilot Autofix

AI 9 months ago

To fix the issue, we need to validate and sanitize the user-controlled width and height parameters before using them in file path construction. Additionally, we should ensure that the constructed file paths are contained within a safe root directory. This can be achieved by normalizing the path using os.path.normpath and verifying that it starts with the intended base directory.

Steps to implement the fix:

  1. Validate width and height to ensure they are positive integers.
  2. Construct the file path using os.path.join and normalize it using os.path.normpath.
  3. Verify that the normalized path starts with the intended base directory.
  4. Use a safe mechanism to sanitize file names, such as werkzeug.utils.secure_filename, if applicable.

Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -98,4 +98,9 @@
             new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg{width}x{height}.png")
+            base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
+            safe_filename = f"Astroid Logo no bg{width}x{height}.png"
+            full_path = os.path.normpath(base_path / safe_filename)
+            if not str(full_path).startswith(str(base_path)):
+                return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
+            new_image.save(full_path)
+            return fastapi.responses.FileResponse(full_path)
         elif asset == "logo":
@@ -103,4 +108,9 @@
             new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo{width}x{height}.png")
+            base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
+            safe_filename = f"Astroid Logo{width}x{height}.png"
+            full_path = os.path.normpath(base_path / safe_filename)
+            if not str(full_path).startswith(str(base_path)):
+                return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
+            new_image.save(full_path)
+            return fastapi.responses.FileResponse(full_path)
         elif asset == "banner":
@@ -108,4 +118,9 @@
             new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner{width}x{height}.png")
+            base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
+            safe_filename = f"Astroid-banner{width}x{height}.png"
+            full_path = os.path.normpath(base_path / safe_filename)
+            if not str(full_path).startswith(str(base_path)):
+                return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
+            new_image.save(full_path)
+            return fastapi.responses.FileResponse(full_path)
         else:
EOF
@@ -98,4 +98,9 @@
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg{width}x{height}.png")
base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
safe_filename = f"Astroid Logo no bg{width}x{height}.png"
full_path = os.path.normpath(base_path / safe_filename)
if not str(full_path).startswith(str(base_path)):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
new_image.save(full_path)
return fastapi.responses.FileResponse(full_path)
elif asset == "logo":
@@ -103,4 +108,9 @@
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo{width}x{height}.png")
base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
safe_filename = f"Astroid Logo{width}x{height}.png"
full_path = os.path.normpath(base_path / safe_filename)
if not str(full_path).startswith(str(base_path)):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
new_image.save(full_path)
return fastapi.responses.FileResponse(full_path)
elif asset == "banner":
@@ -108,4 +118,9 @@
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner{width}x{height}.png")
base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets/resized"
safe_filename = f"Astroid-banner{width}x{height}.png"
full_path = os.path.normpath(base_path / safe_filename)
if not str(full_path).startswith(str(base_path)):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid file path."})
new_image.save(full_path)
return fastapi.responses.FileResponse(full_path)
else:
Copilot is powered by AI and may make mistakes. Always verify output.
elif asset == "banner":
image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.

Copilot Autofix

AI 9 months ago

To fix the issue, we need to validate and sanitize the width parameter before using it in file path construction. Specifically:

  1. Ensure that width is a positive integer and falls within a reasonable range to prevent abuse (e.g., excessively large values).
  2. Use a safe and normalized base directory for all file operations to prevent path traversal attacks.
  3. Construct file paths using os.path.join or pathlib to ensure proper handling of directory separators and avoid concatenating untrusted data directly into file paths.

The fix will involve:

  • Adding validation logic for the width parameter.
  • Ensuring that all file paths are constructed safely and checked against a predefined base directory.

Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -89,5 +89,17 @@
 def get_asset(asset: str, width: int = None, height: int = None):
+    base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets"
+    resized_path = base_path / "resized"
+
+    # Validate width and height
+    if width is not None and (not isinstance(width, int) or width <= 0 or width > 5000):
+        return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid width parameter."})
+    if height is not None and (not isinstance(height, int) or height <= 0 or height > 5000):
+        return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid height parameter."})
+
     if not width and not height:
         try:
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/{asset}")
+            asset_path = base_path / asset
+            if not asset_path.is_file() or not asset_path.resolve().parent == base_path:
+                raise FileNotFoundError
+            return fastapi.responses.FileResponse(str(asset_path))
         except:
@@ -95,19 +107,19 @@
     else:
-        if asset == "logo_no_bg":
-            image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/Astroid Logo no bg.png")
-            new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg{width}x{height}.png")
-        elif asset == "logo":
-            image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/Astroid Logo.png")
-            new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo{width}x{height}.png")
-        elif asset == "banner":
-            image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
+        try:
+            if asset == "logo_no_bg":
+                image = Image.open(base_path / "Astroid Logo no bg.png")
+            elif asset == "logo":
+                image = Image.open(base_path / "Astroid Logo.png")
+            elif asset == "banner":
+                image = Image.open(base_path / "Astroid-banner.png")
+            else:
+                return fastapi.responses.JSONResponse(status_code=404, content={"message": "This asset does not exist."})
+
             new_image = image.resize((width, height))
-            new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner{width}x{height}.png")
-        else:
-            return fastapi.responses.JSONResponse(status_code=404, content={"message": "This asset does not exist."})
+            resized_file = resized_path / f"{asset}{width}x{height}.png"
+            resized_file.parent.mkdir(parents=True, exist_ok=True)
+            new_image.save(resized_file)
+            return fastapi.responses.FileResponse(str(resized_file))
+        except:
+            return fastapi.responses.JSONResponse(status_code=500, content={"message": "Error processing the image."})
 
EOF
@@ -89,5 +89,17 @@
def get_asset(asset: str, width: int = None, height: int = None):
base_path = pathlib.Path(__file__).parent.parent.resolve() / "assets"
resized_path = base_path / "resized"

# Validate width and height
if width is not None and (not isinstance(width, int) or width <= 0 or width > 5000):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid width parameter."})
if height is not None and (not isinstance(height, int) or height <= 0 or height > 5000):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid height parameter."})

if not width and not height:
try:
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/{asset}")
asset_path = base_path / asset
if not asset_path.is_file() or not asset_path.resolve().parent == base_path:
raise FileNotFoundError
return fastapi.responses.FileResponse(str(asset_path))
except:
@@ -95,19 +107,19 @@
else:
if asset == "logo_no_bg":
image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/Astroid Logo no bg.png")
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo no bg{width}x{height}.png")
elif asset == "logo":
image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/Astroid Logo.png")
new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid Logo{width}x{height}.png")
elif asset == "banner":
image = Image.open(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
try:
if asset == "logo_no_bg":
image = Image.open(base_path / "Astroid Logo no bg.png")
elif asset == "logo":
image = Image.open(base_path / "Astroid Logo.png")
elif asset == "banner":
image = Image.open(base_path / "Astroid-banner.png")
else:
return fastapi.responses.JSONResponse(status_code=404, content={"message": "This asset does not exist."})

new_image = image.resize((width, height))
new_image.save(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner.png")
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.parent.resolve()}/assets/resized/Astroid-banner{width}x{height}.png")
else:
return fastapi.responses.JSONResponse(status_code=404, content={"message": "This asset does not exist."})
resized_file = resized_path / f"{asset}{width}x{height}.png"
resized_file.parent.mkdir(parents=True, exist_ok=True)
new_image.save(resized_file)
return fastapi.responses.FileResponse(str(resized_file))
except:
return fastapi.responses.JSONResponse(status_code=500, content={"message": "Error processing the image."})

Copilot is powered by AI and may make mistakes. Always verify output.
async def get_cdn_asset(assetId: str):
asset = await astroidapi.surrealdb_handler.AttachmentProcessor.get_attachment(assetId)
try:
if asset:

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 9 months ago

To fix the issue, the file path must be validated to ensure that it remains within the intended directory (astroidapi/TMP_attachments). This can be achieved by normalizing the path using os.path.normpath and verifying that the resulting path starts with the expected base directory. This approach prevents path traversal attacks by ensuring that the constructed path cannot escape the intended directory.

Steps to implement the fix:

  1. Normalize the constructed file path using os.path.normpath.
  2. Check that the normalized path starts with the base directory (astroidapi/TMP_attachments).
  3. Raise an exception or return an error response if the validation fails.

Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -165,3 +165,8 @@
         if asset:
-            return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.resolve()}/astroidapi/TMP_attachments/{assetId}.{asset['type']}")
+            base_path = pathlib.Path(__file__).parent.resolve() / "astroidapi/TMP_attachments"
+            full_path = base_path / f"{assetId}.{asset['type']}"
+            normalized_path = full_path.resolve()
+            if not str(normalized_path).startswith(str(base_path)):
+                return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid asset path."})
+            return fastapi.responses.FileResponse(str(normalized_path))
         else:
EOF
@@ -165,3 +165,8 @@
if asset:
return fastapi.responses.FileResponse(f"{pathlib.Path(__file__).parent.resolve()}/astroidapi/TMP_attachments/{assetId}.{asset['type']}")
base_path = pathlib.Path(__file__).parent.resolve() / "astroidapi/TMP_attachments"
full_path = base_path / f"{assetId}.{asset['type']}"
normalized_path = full_path.resolve()
if not str(normalized_path).startswith(str(base_path)):
return fastapi.responses.JSONResponse(status_code=400, content={"message": "Invalid asset path."})
return fastapi.responses.FileResponse(str(normalized_path))
else:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +486 to +487
except astroidapi.errors.HealtCheckError.EndpointCheckError as e:
return fastapi.responses.JSONResponse(status_code=200, content={"message": f"An error occurred: {e}",

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 9 months ago

To fix the issue, the code should avoid exposing exception details (e) directly to the user. Instead, a generic error message should be returned to the user, while the detailed stack trace and exception information should be logged securely for debugging purposes. This ensures that sensitive information is not exposed externally while still allowing developers to diagnose issues.

Steps to implement the fix:

  1. Replace the user-facing error message with a generic message like "An internal error has occurred.".
  2. Log the exception details and stack trace using the logging module for internal debugging.
  3. Ensure that the logging configuration is set up to store logs securely.
Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -486,3 +486,4 @@
         except astroidapi.errors.HealtCheckError.EndpointCheckError as e:
-            return fastapi.responses.JSONResponse(status_code=200, content={"message": f"An error occurred: {e}",
+            logging.exception("An unexpected error occurred during endpoint health check.")
+            return fastapi.responses.JSONResponse(status_code=200, content={"message": "An internal error has occurred.",
                                                                             "details": "unexpectederror"})
@@ -492,4 +493,4 @@
         except astroidapi.errors.SurrealDBHandler.GetEndpointError as e:
-            traceback.print_exc()
-            return fastapi.responses.JSONResponse(status_code=404, content={"message": f"An error occurred: {e}",
+            logging.exception("An error occurred while retrieving endpoint information.")
+            return fastapi.responses.JSONResponse(status_code=404, content={"message": "An internal error has occurred.",
                                                                             "details": "getendpointerror"})
EOF
@@ -486,3 +486,4 @@
except astroidapi.errors.HealtCheckError.EndpointCheckError as e:
return fastapi.responses.JSONResponse(status_code=200, content={"message": f"An error occurred: {e}",
logging.exception("An unexpected error occurred during endpoint health check.")
return fastapi.responses.JSONResponse(status_code=200, content={"message": "An internal error has occurred.",
"details": "unexpectederror"})
@@ -492,4 +493,4 @@
except astroidapi.errors.SurrealDBHandler.GetEndpointError as e:
traceback.print_exc()
return fastapi.responses.JSONResponse(status_code=404, content={"message": f"An error occurred: {e}",
logging.exception("An error occurred while retrieving endpoint information.")
return fastapi.responses.JSONResponse(status_code=404, content={"message": "An internal error has occurred.",
"details": "getendpointerror"})
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +493 to +494
traceback.print_exc()
return fastapi.responses.JSONResponse(status_code=404, content={"message": f"An error occurred: {e}",

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 9 months ago

To fix the issue, the code should be updated to prevent sensitive information from being exposed in the response. Instead of including the exception details (e) in the response, log the full stack trace internally using the logging module and return a generic error message to the user. This ensures that developers can still access detailed error information for debugging purposes while protecting the application from information exposure.

The changes will involve:

  1. Replacing traceback.print_exc() with logging.exception() to log the stack trace internally.
  2. Modifying the response message to exclude the exception details and use a generic error message instead.
Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -492,4 +492,4 @@
         except astroidapi.errors.SurrealDBHandler.GetEndpointError as e:
-            traceback.print_exc()
-            return fastapi.responses.JSONResponse(status_code=404, content={"message": f"An error occurred: {e}",
+            logging.exception("An error occurred while handling GetEndpointError.")
+            return fastapi.responses.JSONResponse(status_code=404, content={"message": "An error occurred while processing the request.",
                                                                             "details": "getendpointerror"})
EOF
@@ -492,4 +492,4 @@
except astroidapi.errors.SurrealDBHandler.GetEndpointError as e:
traceback.print_exc()
return fastapi.responses.JSONResponse(status_code=404, content={"message": f"An error occurred: {e}",
logging.exception("An error occurred while handling GetEndpointError.")
return fastapi.responses.JSONResponse(status_code=404, content={"message": "An error occurred while processing the request.",
"details": "getendpointerror"})
Copilot is powered by AI and may make mistakes. Always verify output.
summary = await astroidapi.health_check.HealthCheck.EndpointCheck.repair_structure(endpoint)
return fastapi.responses.JSONResponse(status_code=200, content={"message": "Repaired.", "summary": summary})
except Exception as e:
logging.exception(traceback.print_exc())

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 9 months ago

To fix the issue, the code should avoid including the exception details (e) in the response message sent to the user. Instead, a generic error message should be returned to the user, while the detailed exception information, including the stack trace, should be logged securely for debugging purposes. This ensures that sensitive information is not exposed externally while still allowing developers to diagnose issues.

The changes involve:

  1. Replacing the response message that includes e with a generic error message.
  2. Logging the exception details using the logging module for internal use.
Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -508,4 +508,4 @@
         except Exception as e:
-            logging.exception(traceback.print_exc())
-            return fastapi.responses.JSONResponse(status_code=500, content={"message": f"An error occurred: {e}"})
+            logging.exception("An error occurred while repairing the endpoint.", exc_info=e)
+            return fastapi.responses.JSONResponse(status_code=500, content={"message": "An internal error has occurred. Please contact support if the issue persists."})
     else:
EOF
@@ -508,4 +508,4 @@
except Exception as e:
logging.exception(traceback.print_exc())
return fastapi.responses.JSONResponse(status_code=500, content={"message": f"An error occurred: {e}"})
logging.exception("An error occurred while repairing the endpoint.", exc_info=e)
return fastapi.responses.JSONResponse(status_code=500, content={"message": "An internal error has occurred. Please contact support if the issue persists."})
else:
Copilot is powered by AI and may make mistakes. Always verify output.
return fastapi.responses.JSONResponse(status_code=401, content={"message": "You must provide a token."})
except KeyError:
if token == Bot.config.MASTER_TOKEN:
try:

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 9 months ago

To fix the issue, we need to validate the endpoint parameter before using it to construct a file path. The best approach is to ensure that the constructed path is contained within a safe root directory. This can be achieved by normalizing the path using os.path.normpath and verifying that the resulting path starts with the intended base directory.

Steps to implement the fix:

  1. Define a safe root directory for the endpoints folder.
  2. Normalize the constructed path using os.path.normpath.
  3. Check that the normalized path starts with the safe root directory.
  4. Raise an exception or return an error response if the validation fails.

Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -597,3 +597,8 @@
             try:
-                os.remove(f"{pathlib.Path(__file__).parent.resolve()}/endpoints/{endpoint}.json")
+                base_path = pathlib.Path(__file__).parent.resolve() / "endpoints"
+                target_path = base_path / f"{endpoint}.json"
+                normalized_path = target_path.resolve()
+                if not str(normalized_path).startswith(str(base_path)):
+                    raise HTTPException(status_code=403, detail="Invalid endpoint path.")
+                os.remove(normalized_path)
                 return fastapi.responses.JSONResponse(status_code=200, content={"message": "Deleted."})
EOF
@@ -597,3 +597,8 @@
try:
os.remove(f"{pathlib.Path(__file__).parent.resolve()}/endpoints/{endpoint}.json")
base_path = pathlib.Path(__file__).parent.resolve() / "endpoints"
target_path = base_path / f"{endpoint}.json"
normalized_path = target_path.resolve()
if not str(normalized_path).startswith(str(base_path)):
raise HTTPException(status_code=403, detail="Invalid endpoint path.")
os.remove(normalized_path)
return fastapi.responses.JSONResponse(status_code=200, content={"message": "Deleted."})
Copilot is powered by AI and may make mistakes. Always verify output.
try:
await astroidapi.surrealdb_handler.sync_server_relations()
return fastapi.responses.JSONResponse(status_code=200, content={"message": "Success."})
except Exception as e:

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 9 months ago

To fix the issue, we need to ensure that sensitive information contained in the exception object e is not exposed to the client. Instead, we should log the detailed error message on the server for debugging purposes and return a generic error message to the client. This approach aligns with the best practices for handling exceptions securely.

  1. Replace the line that includes {"message": f"An error occurred: {e}"} with a generic error message like {"message": "An internal error occurred."}.
  2. Log the exception details (e.g., stack trace) on the server using the logging module to ensure developers can still debug the issue.

Suggested changeset 1
src/api.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/src/api.py b/src/api.py
--- a/src/api.py
+++ b/src/api.py
@@ -757,3 +757,4 @@
     except Exception as e:
-        return fastapi.responses.JSONResponse(status_code=500, content={"message": f"An error occurred: {e}"})
+        logging.error("An error occurred while syncing server relations.", exc_info=True)
+        return fastapi.responses.JSONResponse(status_code=500, content={"message": "An internal error occurred."})
 
EOF
@@ -757,3 +757,4 @@
except Exception as e:
return fastapi.responses.JSONResponse(status_code=500, content={"message": f"An error occurred: {e}"})
logging.error("An error occurred while syncing server relations.", exc_info=True)
return fastapi.responses.JSONResponse(status_code=500, content={"message": "An internal error occurred."})

Copilot is powered by AI and may make mistakes. Always verify output.
@pixeebot
Copy link
Author

pixeebot bot commented Jul 17, 2025

I'm confident in this change, but I'm not a maintainer of this project. Do you see any reason not to merge it?

If this change was not helpful, or you have suggestions for improvements, please let me know!

@pixeebot
Copy link
Author

pixeebot bot commented Jul 18, 2025

Just a friendly ping to remind you about this change. If there are concerns about it, we'd love to hear about them!

@pixeebot
Copy link
Author

pixeebot bot commented Jul 24, 2025

This change may not be a priority right now, so I'll close it. If there was something I could have done better, please let me know!

You can also customize me to make sure I'm working with you in the way you want.

@pixeebot pixeebot bot closed this Jul 24, 2025
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.

0 participants