Skip to content

fix(#1649): add UploadFile for file-to-file sandbox upload semantics#1651

Open
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/1649-fix-upload-file-staging
Open

fix(#1649): add UploadFile for file-to-file sandbox upload semantics#1651
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/1649-fix-upload-file-staging

Conversation

@fullsend-ai-coder
Copy link
Copy Markdown

openshell sandbox upload treats the remote path as a directory, placing the uploaded file inside it with the source basename. This caused host_files staging to create a directory at the destination path (e.g. prior-review.txt/) with the file nested inside, producing EISDIR errors when the agent tried to read the file.

Add sandbox.UploadFile() — analogous to the existing DownloadFile() — which uploads to the parent directory and renames in-sandbox if the basename differs. Update all three sandbox.Upload() calls in bootstrapEnv's host_files staging and .env upload to use UploadFile instead.

Note: Go tests could not run in sandbox (go.mod requires go >= 1.26.0, sandbox has 1.24.13 with read-only GOPATH). Pre-commit could not run (same Go toolchain issue). Manual verification of go-test is required.


Closes #1649

Post-script verification

  • Branch is not main/master (agent/1649-fix-upload-file-staging)
  • Secret scan passed (gitleaks — 557c0a1189eed1adca4ec44cbdb53bf81f423703..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

openshell sandbox upload treats the remote path as a directory,
placing the uploaded file inside it with the source basename.
This caused host_files staging to create a directory at the
destination path (e.g. prior-review.txt/) with the file nested
inside, producing EISDIR errors when the agent tried to read
the file.

Add sandbox.UploadFile() — analogous to the existing
DownloadFile() — which uploads to the parent directory and
renames in-sandbox if the basename differs. Update all three
sandbox.Upload() calls in bootstrapEnv's host_files staging
and .env upload to use UploadFile instead.

Note: Go tests could not run in sandbox (go.mod requires
go >= 1.26.0, sandbox has 1.24.13 with read-only GOPATH).
Pre-commit could not run (same Go toolchain issue). Manual
verification of go-test is required.

Closes #1649
@github-actions
Copy link
Copy Markdown

Site preview

Preview: https://dc57d31b-site.fullsend-ai.workers.dev

Commit: 907d482e23b9cc71ba8ee89de32bb85e657f5b17

@fullsend-ai-review
Copy link
Copy Markdown

Review

Findings

Low

  • [correctness] internal/cli/run.go — Only 3 of ~15 sandbox.Upload() calls were migrated to UploadFile(). Other single-file upload sites (lines 725, 763, 801, 826, 919, 1188, 1528, 1559, 1629, 1651) may have the same directory-creation bug if openshell consistently treats the remote path as a directory. Consider auditing remaining call sites and migrating any that expect file-to-file semantics.
    Remediation: Audit each remaining sandbox.Upload() call in run.go to determine whether it expects file-to-file or file-to-directory semantics, and migrate file-to-file callers to UploadFile().

  • [injection-defense] internal/sandbox/sandbox.go:397 — The mv command in UploadFile uses fmt.Sprintf("mv %s %s", ...) without shell-quoting the paths. If a path contained spaces or shell metacharacters, the command would fail or behave unexpectedly. This is a pre-existing pattern in the codebase (e.g., UploadDir, chmod calls) and paths are infrastructure-controlled, but it is not defensive.
    Remediation: Use shell-safe quoting for paths in sandbox Exec commands. Consider a helper function for constructing sandbox shell commands with proper quoting.

Info

  • [documentation-currency] docs/guides/dev/cli-internals.md:346 — The sandbox function reference table lists Upload(), Download(), and SafeDownload() but omits UploadFile() and the pre-existing DownloadFile(). This is a pre-existing gap widened by the new function.
    Remediation: Add UploadFile() and DownloadFile() to the sandbox function table in the CLI internals guide.

// If that differs from the desired remotePath, rename it in-sandbox.
uploadedPath := filepath.Join(remoteDir, filepath.Base(localPath))
if uploadedPath != remotePath {
mvCmd := fmt.Sprintf("mv %s %s", uploadedPath, remotePath)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] injection-defense

The mv command in UploadFile uses fmt.Sprintf without shell-quoting paths. Pre-existing pattern but not defensive against paths with spaces or metacharacters.

Suggested fix: Use shell-safe quoting for paths in sandbox Exec commands. Consider a helper function for constructing sandbox shell commands.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix prior review artifact staging: file downloaded as directory causes EISDIR

0 participants