Repository navigation
🐛 discard remote pastes with invalid content instead of writing them to the clipboard - #5234
Merged
Merged
Conversation
guiyanakuang
force-pushed
the
fix/empty-remote-paste-clipboard
branch
from
October 19, 2026 08:04
53d3f13 to
3f5a533
Compare
guiyanakuang
force-pushed
the
fix/empty-remote-paste-clipboard
branch
from
October 19, 2026 08:09
3f5a533 to
2f1f2cc
Compare
Received pastes are persisted as-is, so unlike a local capture they never went through RemoveInvalidPlugin. A paste carrying an empty text flavor (e.g. a peer that captured an empty clipboard) was handed to the clipboard, and the empty value replaced whatever the user had copied: pbpaste prints nothing and paste yields no content. Bind decoded wire pastes as PasteState.LOADED: the serializer defaults them to LOADING, for which PasteData.isValid() short-circuits to true, even though the sender had already finished the paste. Then reuse PasteData.isValid() on both clipboard-write paths: - releaseRemoteNonFilePaste keeps the paste in history (the UI filters invalid rows) but skips the clipboard write when it is invalid. - releaseRemotePasteDataWithFile applies the same check when a downloaded file paste is finalized. Valid remote pastes are written exactly as before.
guiyanakuang
force-pushed
the
fix/empty-remote-paste-clipboard
branch
from
October 19, 2026 08:17
2f1f2cc to
879d7d3
Compare
Replace the previous approach (bind every wire paste as LOADED, store the invalid row, then skip only the clipboard write) with a receive policy next to the existing oversized/invalid-file discards: - PasteData.hasValidContent() extracts the content check from isValid(), so the receiver can judge a paste without faking its pasteState. - prepareRemotePasteReceive() drops a paste whose content is invalid and settles its remote ID. It is never stored, relayed or written to the clipboard, for file and non-file pastes alike.
releaseRemotePasteDataForPush only ran validateFileTransferMetadata, which does not imply PasteFiles.isValid() (e.g. a zero-byte file or an empty hash). Apply hasValidContent() there too, before reserving the cursor or creating a row, and cover both file entry points in tests.
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.
Problem
Received pastes are persisted as-is, so unlike a local capture they never go through
RemoveInvalidPlugin. A paste carrying an empty text flavor (e.g. a peer that capturedan empty clipboard) was stored, relayed, and handed to the clipboard, where the empty
value replaced whatever the user had copied:
pbpasteprints nothing and pasting intoany app yields no content.
Fix
Treat invalid content as a receive policy, next to the existing
discardOversizedRemoteNonFilePaste/discardInvalidRemoteFilePaste:PasteData.hasValidContent()extracts the content check out ofisValid(), so thereceiver can judge a paste without depending on its
pasteState(isValid()keepsits behavior and now delegates to it for
LOADED).prepareRemotePasteReceive()callsdiscardInvalidRemotePasteContent(): a paste whosecontent is invalid is dropped before anything is stored, and its remote ID is settled so
a reconnect does not pull it again. It is logged, not notified — nothing meaningful is
lost.
releaseRemotePasteDataForPush()(HTTP push session for files/images) applies the samecheck right after its file-type check, before reserving the cursor or creating a row.
validateFileTransferMetadataalone does not implyPasteFiles.isValid()(a zero-bytefile or an empty hash passes it).
Every receive entry — WS push, pull, history pull and HTTP file push — is now checked
before anything is stored, so the invalid paste never becomes a hidden row, is never
relayed to other devices, and never reaches the clipboard. Finalize
(
releaseRemotePasteDataWithFile) needs no extra guard: its rows only come from theseentries, and receives interrupted by an older build are discarded at startup by
discardInterruptedReceives().Like the existing oversized discard, a discarded paste is reported back as rejected, so
the sender records one non-retried sync failure for it. This is intentional: it is a
receiver policy decision, not a transport error.
PasteReleaseServiceand
PasteDataare incommonMain, so this applies to desktop and mobile alike.Tests
PasteReleaseServiceRemoteDiscardTest: an empty-text remote paste is discarded — no tasksubmitted, no clipboard write, cursor settled, no notification. The "within limit" test
now runs the submitted block and asserts the clipboard write actually happens.
the pull path before any download (no task, cursor settled, no clipboard write), and
rejected on the HTTP push path before the cursor is reserved or a row is created.
./gradlew app:desktopTest(fast tier) passes;ktlintFormatclean.