Severity: High CWE: CWE-22
Summary
The Zoom UploadWhiteboardFile action accepts a file.source string that it treats as a local filesystem path. Its normalize_file_input validator rewrites that path into an internal s3key of the form local://<source>, and execute then opens the path and uploads the bytes to Zoom's file API. The path is fully attacker-controlled with no allowlist or containment, so an attacker can read arbitrary files from the Composio action-runtime host and exfiltrate them to a Zoom account they control.
Affected code
apps/zoom/actions/upload_whiteboard_file.py
Validator that turns a caller-supplied path into a local:// key (lines ~44-60):
file_val = values["file"]
if isinstance(file_val, dict) and "source" in file_val:
source_path = file_val["source"]
...
values["file"] = {
"name": file_name,
"mimetype": mime_type,
"s3key": f"local://{source_path}", # attacker-controlled path
}
# (also handles file being a raw string path)
Sink in execute (lines ~148-155):
if request.file.s3key.startswith("local://"):
local_path = request.file.s3key[8:] # strip 'local://'
with open(local_path, "rb") as file_buffer: # arbitrary file read
files = {"file": (request.file.name, file_buffer, request.file.mimetype)}
... # uploaded to {fileapi_base}/whiteboards/files
Root cause
This action implements a custom source→local:// file-path mechanism instead of relying solely on the framework's scoped FileUploadable.as_buffer(project_auto_id=...) staging (used by other actions). The local:// path is opened directly with no validation, so any readable host path can be uploaded.
Note: the standard FileUploadable (mercury/tools/_base/helpers/files.py) expects s3key to reference project-scoped S3 staging; this local:// handling is unique to this action and bypasses that safety.