Skip to content

[BUG] Path Traversal in API Server #1588

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

geckosecurity
Copy link

Version: 0.7.16-rc1

Path Traversal in API Server

Description

A critical path traversal vulnerability has been identified in Composio's FastAPI server, specifically in the /api/download endpoint. This vulnerability allows attackers to read arbitrary files from the server's filesystem by manipulating the file parameter. Due to a lack of proper path validation and sanitization, an attacker can access sensitive files both within and outside the intended directory structure.

The vulnerability exists because the endpoint takes a user-supplied path, converts it directly to a Path object, and serves the file without any path validation or directory restrictions. This allows for path traversal attacks that can access sensitive system files, configuration files containing credentials, or any other files readable by the server process.

This vulnerability can be exploited without authentication if the server is run without setting the ACCESS_TOKEN environment variable. Even when authentication is enabled, any authenticated user (regardless of privilege level) can exploit this vulnerability to read arbitrary files.

Source-Sink Analysis

Source: User-controlled input via the file query parameter in the /api/download endpoint.

Sink: The FileResponse function that serves the file content directly to the client.

Vulnerable Code Path:

  1. The vulnerable function is in python/composio/server/api.py:
@app.get("/api/download")
def _download_file_or_dir(file: t.Optional[str] = None):
    """Get list of available developer tools."""
    if not file:
        raise HTTPException(
            status_code=400, detail="File path is required as query parameter"
        )
    path = Path(file)  # No sanitization of user input
    if not path.exists():
        return Response(
            content=APIResponse[None](
                data=None,
                error=f"{path} not found",
            ).model_dump_json(),
            status_code=404,
        )

    if path.is_file():
        return FileResponse(path=path)  # Direct file access without restrictions

    tempdir = tempfile.TemporaryDirectory()
    zipfile = Path(tempdir.name, path.name + ".zip")
    return FileResponse(path=_archive(directory=path, output=zipfile))

Authentication Mechanism:
The authentication middleware completely bypasses authentication checks if the ACCESS_TOKEN environment variable is not set:

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    if access_token is None:  # If ACCESS_TOKEN isn't set, authentication is bypassed
        return await call_next(request)
    
    if "x-api-key" in request.headers and request.headers["x-api-key"]:
        return await call_next(request)
    
    return Response(
        content=APIResponse[None](
            data=None,
            error="Unauthorised request",
        ).model_dump_json(),
        status_code=401,
    )

Proof of Concept

# Read /etc/passwd (system user information)
curl "http://localhost:8000/api/download?file=/etc/passwd" -o passwd.txt

# Read a file from user's home directory
curl "http://localhost:8000/api/download?file=~/.ssh/id_rsa" -o id_rsa.txt

# Download an entire directory as a zip file
curl "http://localhost:8000/api/download?file=/etc" -o etc.zip

Impact

This vulnerability has severe security implications. Attackers can access sensitive files such as:

  • Configuration files containing API keys, database credentials, etc.
  • System files containing user information (/etc/passwd, etc.).
  • Private SSH keys and other authentication material.
  • Application source code.
  • Internal documentation.

Contact

If you have any queries regarding this bug report, send an email to gecko@gecko.security.

Copy link

vercel bot commented May 7, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
composio ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 7, 2025 9:01pm

Copy link

Review Summary

Skipped posting 1 drafted comments based on your review threshold. Feel free to update them here.

Draft Comments
python/composio/server/api.py:219-237
The `auth_middleware` function has a logical issue where it checks for missing API key twice in the `else` branch, making the second check redundant and potentially confusing.

Scores:

  • Production Impact: 1
  • Fix Specificity: 1
  • Urgency Impact: 1
  • Total Score: 3

Reason for filtering: The bug description claims there's a redundant check for missing API key, but the code snippet doesn't show this issue. The first check is for missing/empty API key, while the second check validates the key value against access_token. These are different logical checks serving different purposes.

Analysis: The review comment incorrectly identifies a logical issue that doesn't exist. The first check verifies the API key exists and isn't empty, while the second check validates the key value. These are distinct validations with different error messages. The total score is well below the threshold of 13, and the comment would introduce confusion rather than fix an actual issue.

Comment on lines +177 to +179
for dep in dependencies:
if not re.match(r'^[a-zA-Z0-9_\-\.]+[a-zA-Z0-9_\-\.=<>]*$', dep):
return False, f"Invalid dependency format: {dep}"

Choose a reason for hiding this comment

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

Security: The validate_dependencies function uses a regex that may allow some potentially dangerous characters in dependency names, which could lead to command injection.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
for dep in dependencies:
if not re.match(r'^[a-zA-Z0-9_\-\.]+[a-zA-Z0-9_\-\.=<>]*$', dep):
return False, f"Invalid dependency format: {dep}"
for dep in dependencies:
if not re.match(r'^[a-zA-Z0-9_\-\.]+[a-zA-Z0-9_\-\.=<>~!]*$', dep):
return False, f"Invalid dependency format: {dep}"

Comment on lines 388 to +389
if tempfile.exists():
raise ValueError("Tools from this module already exits!")
raise ValueError("Tools from this module already exist!")

Choose a reason for hiding this comment

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

Correctness: The _upload_workspace_tools function doesn't properly handle the case where the tool file already exists, raising a ValueError instead of returning a proper HTTP response.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if tempfile.exists():
raise ValueError("Tools from this module already exits!")
raise ValueError("Tools from this module already exist!")
if tempfile.exists():
raise HTTPException(
status_code=409,
detail="Tools from this module already exist!"
)

@geckosecurity
Copy link
Author

Hi @angrybayblade, @kaavee315 just following up to see if there’s anything we should update or clarify in the fix. Happy to make any changes if needed. Appreciate your time!

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.

2 participants