Skip to content

security: fix unauthenticated file-read and path-traversal file-write in Flask server#623

Open
hoanghai2110-web wants to merge 1 commit into
google-ai-edge:mainfrom
hoanghai2110-web:main
Open

security: fix unauthenticated file-read and path-traversal file-write in Flask server#623
hoanghai2110-web wants to merge 1 commit into
google-ai-edge:mainfrom
hoanghai2110-web:main

Conversation

@hoanghai2110-web
Copy link
Copy Markdown

@hoanghai2110-web hoanghai2110-web commented Apr 16, 2026

Summary

Fix missing path validation in two Flask endpoints in server.py.

Bug A — /api/v1/read_text_file

Before: user-supplied path passed directly to open() with no validation.

After: resolve path with os.path.realpath(), then enforce an allowlist — only paths inside the user home directory or system temp directory are permitted. Return HTTP 403 otherwise.

real = os.path.realpath(os.path.expanduser(path))
allowed_roots = [
    os.path.realpath(os.path.expanduser('~')),
    os.path.realpath(tempfile.gettempdir()),
]
if not any(real == root or real.startswith(root + os.sep) for root in allowed_roots):
    return _make_json_response({'error': 'access denied'}), 403

Bug B — /apipost/v1/upload

Before: raw f.filename joined with os.path.join()../ sequences not stripped (Werkzeug docs explicitly warn to use secure_filename() before using a user-supplied filename).

After: sanitise with secure_filename(), then verify the resolved path stays inside tmp_dir before saving.

file_name = secure_filename(f.filename or 'no_name') or 'upload'
file_path = os.path.join(tmp_dir, file_name)
if not os.path.realpath(file_path).startswith(os.path.realpath(tmp_dir) + os.sep):
    return _make_json_response({'error': 'invalid filename'}), 400
f.save(file_path)

… in Flask server

Two unauthenticated endpoints with no path validation chain into
persistent RCE via usercustomize.py implant in site-packages.

Bug A — /api/v1/read_text_file (arbitrary file read)
- Add os.path.realpath() to resolve symlinks before any check
- Enforce an allowlist: only paths inside the user home directory or
  the system temp directory are permitted
- Return HTTP 403 for any path outside the allowlist

Bug B — /apipost/v1/upload (path-traversal file write)
- Replace raw f.filename with werkzeug.utils.secure_filename() to
  strip ../ sequences and directory separators
- Add a realpath jail check: verify the resolved file_path stays
  inside tmp_dir before calling f.save()
- Return HTTP 400 for any filename that escapes the temp directory

Without these fixes an attacker can:
1. Read arbitrary files (credentials, SSH keys, /proc/self/environ)
2. Write an arbitrary payload to site-packages/usercustomize.py
3. Achieve persistent RCE on every subsequent Python invocation,
   surviving uninstall of model-explorer

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
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.

1 participant