feat(codex): support MCP workspace file uploads - #558
Conversation
wanghaojie124
commented
Sep 7, 2026
- add a turn-scoped dynamic tool for uploading workspace files to configured MCP servers
- validate workspace paths, upload sizes, content types, same-origin targets, redirects, and cancellation
- guard unresolved runtime input files before media-type handling
- localize the AGENT-ERR-25 pending sensitive-check response in the Web UI
- extend runtime and frontend coverage for the new behavior
- add a turn-scoped dynamic tool for uploading workspace files to configured MCP servers - validate workspace paths, upload sizes, content types, same-origin targets, redirects, and cancellation - guard unresolved runtime input files before media-type handling - localize the AGENT-ERR-25 pending sensitive-check response in the Web UI - extend runtime and frontend coverage for the new behavior
|
Will handle this issue after #557 is merged. |
|
@wanghaojie124 Please fix the conflict |
|
Resolved the conflicts against the latest |
| if !ok { | ||
| return appServerDynamicToolResponse(false, "file upload is unavailable outside an active turn"), nil | ||
| } | ||
| if err := uploadAppServerWorkspaceFile(uploadCtx, live.spec, args); err != nil { |
There was a problem hiding this comment.
Enforce read-only mode before executing uploads
This branch executes host filesystem reads and HTTP PUTs even when live.spec.ExecutionMode is read_only. EnsureEngineSession registers the upload tool for read-only agents too, bypassing that mode's filesystem deny policy and rejection of external mutations. A local reproduction through the dynamic request handler successfully uploaded workspace contents from a read-only session. Reject uploads in read-only mode and omit this tool from its registration.
| if err := json.Unmarshal(params.Arguments, &args); err != nil { | ||
| return appServerDynamicToolResponse(false, "invalid upload arguments"), nil | ||
| } | ||
| uploadCtx, ok := live.appServerTurnContext(params.ThreadID) |
There was a problem hiding this comment.
Bind upload contexts to the requesting turn
Server requests run asynchronously, so a delayed request from canceled turn A can execute after turn B starts on the same thread. This lookup ignores params.TurnID, retrieves B's uncanceled context, and performs A's upload. A deterministic reproduction using the asynchronous dispatch path confirms that the HTTP PUT succeeds in this interleaving. Validate both thread and turn IDs and atomically retrieve the matching active context.
| return fmt.Errorf("open Runtime workspace: %w", err) | ||
| } | ||
| defer root.Close() | ||
| file, err := root.Open(cleaned) |
There was a problem hiding this comment.
Reject non-regular files without a blocking open
root.Open blocks when the workspace path refers to a FIFO without a writer, before the regular-file check or HTTP timeout can apply. Canceling the turn also cannot interrupt that open. A local FIFO reproduction remains blocked after cancellation and returns only after a writer connects. Use a nonblocking open with descriptor validation, or another race-safe approach that rejects special files without waiting for a writer.