Skip to content

upload_file reports success for a nonexistent path or a directory and attaches a bogus file #2944

Description

@coygeek

Description of the bug

With a server-launched local browser, upload_file answers File uploaded from <path>. with isError: false when the path does not exist, and the page's <input type=file> receives a 0-byte file named after the missing path, with a change event. A directory path is accepted the same way and attached as a single "file" named after the directory. An agent therefore believes an attachment was uploaded and may submit a form carrying an empty or bogus file. Both tested builds behave the same.

Actual behavior

Identical on both builds:

upload_file a.txt               isError=false  "File uploaded from <workspace>/up/a.txt."
upload_file does-not-exist.txt  isError=false  "File uploaded from <workspace>/up/does-not-exist.txt."
upload_file dir                 isError=false  "File uploaded from <workspace>/up/dir."
evaluate_script -> "change: a.txt:6\nchange: does-not-exist.txt:0\nchange: dir:96\n"

The page saw a change event with a 0-byte does-not-exist.txt, then a change event with a 96-byte "file" named dir (the size reported for the directory entry on this macOS host). The paths in the success text were printed in their canonical form (/private/tmp/... on macOS).

Reproduction

  1. Create a workspace directory <workspace> containing up/a.txt (content hello\n, 6 bytes) and a subdirectory up/dir/ holding one small file. Do not create up/does-not-exist.txt. Start the server under test with the flags above plus --workspace <workspace>, so all three paths are inside the allowed roots.

  2. Serve this page as /upload.html:

<!doctype html><title>Upload</title>
<label>Attachment <input id="f" type="file"></label>
<pre id="out"></pre>
<script>f.addEventListener('change', () => { out.textContent += 'change: ' + [...f.files].map(x => x.name + ':' + x.size).join(',') + '\n'; });</script>
  1. Send these calls in order (take_snapshot returned uid=1_2 button "Attachment" for the file input in every run):
{"name":"navigate_page","arguments":{"pageId":1,"url":"http://127.0.0.1:PORT/upload.html"}}
{"name":"take_snapshot","arguments":{"pageId":1}}
{"name":"upload_file","arguments":{"pageId":1,"uid":"1_2","filePaths":["<workspace>/up/a.txt"]}}
{"name":"upload_file","arguments":{"pageId":1,"uid":"1_2","filePaths":["<workspace>/up/does-not-exist.txt"]}}
{"name":"upload_file","arguments":{"pageId":1,"uid":"1_2","filePaths":["<workspace>/up/dir"]}}
{"name":"evaluate_script","arguments":{"pageId":1,"function":"() => document.getElementById('out').textContent"}}

The first upload of an existing file is a control. Each build ran the sequence once for this report (with an evaluate_script read of f.files after each upload), and two earlier independent runs by other investigators saw the same nonexistent-path result on both builds.

Expectation

upload_file is described as "Upload a file through a provided element." and reports File uploaded from <path>. (docs/tool-reference.md; src/tools/input.ts:545-601 at the main commit). A path that does not name a readable regular file cannot be uploaded, so the call should fail with an error naming the path and leave the input's files unchanged. The tool already treats local-browser paths as server-side files: its verifyFilesSchema validates filePaths against the workspace roots for a local browser and skips validation only for remote browsers, whose files the server cannot see (src/tools/input.ts:566-574).

MCP configuration

--headless --isolated --no-usage-statistics --no-performance-crux unless the reproduction states otherwise (update checks disabled with CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=1).

Chrome DevTools MCP version

1.10.1 (npm) and main 5ddb0a3 (local build)

Chrome version

154.0.8037.98 (stable)

Node version

v22.23.2

Operating system

macOS 27.0

Environment details

  • chrome-devtools-mcp 1.10.1 from npm (the release build).
  • chrome-devtools-mcp built from main at commit 5ddb0a3 (the main build; its server info still reports version 1.10.1).
  • Google Chrome 154.0.8037.98 stable, headless; Node v22.23.2; macOS 27.
  • Both builds were started with --headless --isolated --no-usage-statistics --no-performance-crux and CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=1, and driven by a scripted stdio MCP client that sends one tools/call at a time and records isError and wall time. Fixtures were served from a loopback HTTP server on an ephemeral port (PORT below); any static server, such as python3 -m http.server --bind 127.0.0.1, serves the static fixtures the same way.

Evidence

  • Expected source: upload_file description, success text and verifyFilesSchema comment in src/tools/input.ts:545-601 at the main commit; docs/tool-reference.md upload_file section.
  • Failure source: scripted MCP run above on both builds, read back from the page's change handler. Static breadcrumb: the local-browser validation in validateToolFiles (src/ToolHandler.ts:146-175) checks root containment only, and Puppeteer's file upload passes paths to DOM.setFileInputFiles without checking that they exist (its FileChooser.accept documentation says it "will not validate whether the file paths exists").
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Fix check

With a server-launched browser and the steps above, upload_file with <workspace>/up/does-not-exist.txt and with <workspace>/up/dir each return isError: true with a message naming the path, and the page logs no change event for them (the final text is change: a.txt:6\n). The control upload of a.txt still succeeds with a 6-byte file. A browser connected with --browserUrl on another host, whose paths the server cannot check, is outside this restoration check.

Additional context

The full open and closed issue and PR corpus was screened as of 2026-10-06 (searches included upload_file nonexistent, upload_file empty, does not exist, ENOENT); no issue reports this. #2310 (tab crash when uploading through a chooser-opening element) and closed #2741 (CLI filePaths parsing) concern other upload_file failures.

Another investigator reported (not rerun for this report) that the chrome-devtools CLI resolves a relative path against the daemon's directory rather than the caller's, so a CLI user who passes file.txt from another directory can hit this silently as a 0-byte upload; that path-resolution difference is a separate CLI matter, addressed by open PR #2821 ("fix: resolve CLI file paths before daemon IPC").

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions