{{ message }}
Repository navigation
fix(desktop): stop clipboard image uploads clobbering each other and mangling paths - #5512
Merged
Merged
Conversation
Two images pasted within the same millisecond were written to the same `path.join(tmpdir(), Date.now() + ext)`, so one upload sent the other's bytes and whichever finished first unlinked the file the second still needed. Write into a per-upload `mkdtemp` directory and remove that directory on cleanup; the basename stays timestamped because uploaders derive the remote filename from it. Adds the first tests for the uploader IPC handler, driving the real handler against a stand-in uploader executable. Refs #3360, #2915 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`exec()` interpolated the image path into a command string, so a shell re-interpreted it: a file named `a "quoted" $name ` + '`x`' + `.png` reached picgo as `a quoted .png`, which does not exist, so the upload always failed — and a name containing `$(…)` or backticks would have run as a command. Verified against the real picgo binary: the old form makes picgo report `/tmp/.../a quoted .png does not exist.`, the new one reports the filename verbatim. Windows keeps the quoted shell form, because `picgo` there is a .cmd shim that only a shell can start and a Windows filename cannot contain a double quote. Refs #3360, #2915 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stand-in uploader the spec puts on PATH is a /bin/sh script, and Windows resolves executables through PATHEXT, so it would never be found there. The behaviour under test is also the non-Windows branch of uploadByPicgo. Desktop unit tests run on windows-latest in CI, so guard the file with describe.skipIf, matching write-file-atomic.spec.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Build artifacts for PR #5512: Run: https://github.com/marktext/marktext/actions/runs/35749399204 |
The previous commit also quoted `${cmd}` in the Windows branch. That buys
nothing — resolvePicgoBinary only ever returns the bare names `picgo` or
`picgo.exe` on win32, never a path with spaces — while changing
behaviour on the one platform this PR cannot test: the new spec is
skipped there, so `test (windows-latest)` does not cover this line.
Restore the original command string and record in the comment what the
Windows branch does and does not protect against: `"` is illegal in a
Windows filename so the injection fixed on POSIX is not reachable, but
`%VAR%` still expands inside double quotes, so a name like `%TEMP%.png`
is still mangled. Fixing that needs .exe-vs-.cmd resolution and a
Windows machine to verify on, so it is out of scope here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
Two defects in the image-upload IPC handler (
packages/desktop/src/main/ipc/uploader.ts), found while investigating #3360 / #2915 — neither of them is the bug those issues report.1. The image path was interpolated into a shell command (the real one)
uploadByPicgoranexec(`${cmd} u "${localPath}"`), so a shell re-parsed the path: double quotes were consumed as quoting syntax,$namewas expanded, and`x`was command substitution. A file nameda "quoted" $name `x`.png— all legal characters on macOS/Linux — reached picgo asa quoted .png, which does not exist, so the upload always failed. A name containing$(…)or backticks would have executed.The path now goes through as its own argv entry via
execFile, which spawns no shell: each array element becomes one argv entry byte-for-byte.uploadByCliin the same file already did this; picgo is now consistent with it.Reachable by: dragging a local image whose filename contains
$,"or a backtick into a document with an uploader configured.2. Concurrent uploads shared one temp file (hardening, not a reachable bug today)
writeBinaryToTmpnamed the filepath.join(tmpdir(), Date.now() + ext), so two uploads starting in the same millisecond collide: one sends the other's bytes, and whichever finishes firstunlinks the file the second still needs.This is not reachable through the UI today — muya has exactly three
imageActioncall sites (paste, paste-over-selection, drag-drop), all single-image and all awaited, and a multi-file drop takes onlyfiles.find(...), the first image. It becomes a data-corruption bug the moment multi-image paste or drop is added. Each upload now gets its ownmkdtempdirectory, removed on cleanup; the basename stays timestamped because uploaders derive the remote filename from it.Windows is deliberately untouched
The Windows branch keeps the original command string byte-for-byte.
resolvePicgoBinaryonly ever returns the bare namespicgo/picgo.exethere, so quoting${cmd}would buy nothing, and this is the one platform the new spec does not cover.What that branch does and does not protect against is now recorded in the comment:
"is illegal in a Windows filename, so the injection fixed above is not reachable there, but%VAR%still expands inside double quotes, so a name like%TEMP%.pngis still mangled. Fixing that needs real.exe-vs-.cmdresolution and a Windows machine to verify on — out of scope here.Verification
Against the real picgo binary — old form vs new form, same filename:
packages/desktop/test/unit/specs/uploader-temp-file.spec.tsis new — the uploader handler had no tests at all. It drives the real registered IPC handler against a stand-in uploader executable placed onPATHthat records the path it was handed, and covers: the temp file keeps the image extension, the real bytes are written, the temp file is removed afterwards, two same-millisecond uploads get distinct paths, and a path with shell metacharacters arrives unmangled. The last two fail ondevelop.test (ubuntu-latest):✓ uploader-temp-file.spec.ts (5 tests)/bin/shscript and Windows resolves executables through PATHEXT, so the file is skipped there. The branch it would exercise is also the one left unchanged.Note on #3360 / #2915
The root cause those issues identified —
path.join(os.tmpdir(), +new Date())being handed a number, and the temp file having no extension — was already fixed in v0.19.0 (#32699309888b, and624e69cbwhich moved upload into the main process and appendedpath.extname(name)). #3360 has since been verified against a real build and closed; #2915 was closed as its duplicate. This PR fixes two further defects on the same path and closes neither.🤖 Generated with Claude Code