Surfaced while fixing the bracketed-paste injection (#280). Security-adjacent — read item 1 first.
1. Swift's dragged-URL branch has no control-character rejection
TerminalView.dropContentString's web-URL branch passes the URL through ShellEscape.escape(url) and nothing else — unlike the file-path branch, it never rejects control characters. A dragged URL carrying escape bytes therefore reaches the PTY.
Current mitigation: wrapBracketedPaste (added in #280) strips bracketed-paste markers at the paste boundary, so the specific early-terminate injection that motivated #280 is blocked. What is not blocked is other escape sequences when DEC mode 2004 is off — comparable to the baseline risk of pasting arbitrary text into a terminal, which is why this is defense-in-depth rather than an open hole.
Wanted: reject (don't strip — see #280's discussion) control characters on the URL branch, matching the file-path branch, and give Rust an equivalent since it has no dragged-URL path at all today.
2. The filter predicates differ between the UIs
Rust drop_content::resolve rejects ['\n', '\r', '\u{1b}']; Swift dropContentString rejects Character.isNewline || "\u{1b}". isNewline is a superset (VT U+000B, FF U+000C, NEL U+0085, LS U+2028, PS U+2029), so Swift rejects paths Rust accepts. Not an integrity bug — Swift rejects, never mutates — but the two UIs disagree about what is droppable.
Converge on one predicate with shared test vectors, the way shell_escape.rs / ShellEscape.swift already do (see #280 for that pattern).
Surfaced while fixing the bracketed-paste injection (#280). Security-adjacent — read item 1 first.
1. Swift's dragged-URL branch has no control-character rejection
TerminalView.dropContentString's web-URL branch passes the URL throughShellEscape.escape(url)and nothing else — unlike the file-path branch, it never rejects control characters. A dragged URL carrying escape bytes therefore reaches the PTY.Current mitigation:
wrapBracketedPaste(added in #280) strips bracketed-paste markers at the paste boundary, so the specific early-terminate injection that motivated #280 is blocked. What is not blocked is other escape sequences when DEC mode 2004 is off — comparable to the baseline risk of pasting arbitrary text into a terminal, which is why this is defense-in-depth rather than an open hole.Wanted: reject (don't strip — see #280's discussion) control characters on the URL branch, matching the file-path branch, and give Rust an equivalent since it has no dragged-URL path at all today.
2. The filter predicates differ between the UIs
Rust
drop_content::resolverejects['\n', '\r', '\u{1b}']; SwiftdropContentStringrejectsCharacter.isNewline || "\u{1b}".isNewlineis a superset (VT U+000B, FF U+000C, NEL U+0085, LS U+2028, PS U+2029), so Swift rejects paths Rust accepts. Not an integrity bug — Swift rejects, never mutates — but the two UIs disagree about what is droppable.Converge on one predicate with shared test vectors, the way
shell_escape.rs/ShellEscape.swiftalready do (see #280 for that pattern).