Affected Versions
| Affected | not applicable (first-party code) |
| Fixed in | not applicable (first-party code) — see PR for fix commit |
| Ecosystem | N/A |
| CVE / GHSA | not assigned |
| CWE | CWE-78 (OS Command Injection) |
The Vulnerability Explained
The type_text() function exposes Windows SendInput functionality to Tauri command handlers, accepting arbitrary text input that it translates into simulated keyboard events. The vulnerable code passed this text directly through without examining its contents:
fn type_text(text: &str) -> Result<(), String> {
// ...
let mut inputs: Vec<INPUT> = Vec::new();
for unit in text.encode_utf16().take(2000) {
for up in [false, true] {
let mut flags = KEYEVENTF_UNICODE;
// ...
The critical issue: KEYEVENTF_UNICODE tells SendInput to treat the input as Unicode character codes, but Windows still interprets certain control characters (U+000D carriage return, U+000A line feed, U+0009 horizontal tab, U+001B escape, and others) as semantic key events. When type_text() receives a string like "malicious\n", that newline becomes an actual Enter key press in whatever window currently has focus—not a visible newline character, but the form submission, dialog confirmation, or command execution that Enter typically triggers.
An attacker controlling the text parameter could craft payloads like:
- "\x1b" — injects Escape, potentially dismissing security prompts or canceling operations
- "\t\n" — Tab followed by Enter, navigating to a default button and activating it
- "text\x0d\x0a" — Carriage return + line feed submitting whatever form or dialog is active
This transforms a "type text" capability into an arbitrary key press injection primitive, with consequences depending entirely on what application happens to be focused at the moment of injection.
The Fix
The remediation adds explicit control character filtering before the UTF-16 encoding:
// Strip control characters (CR/LF/TAB/ESC/etc.): SendInput's Unicode path can have
// these interpreted as real key presses (e.g. Enter/Escape) by the focused window,
// letting injected text trigger actions instead of merely typing printable content.
let safe_text: String = text.chars().filter(|c| !c.is_control()).collect();
let mut inputs: Vec<INPUT> = Vec::new();
for unit in safe_text.encode_utf16().take(2000) {
Before: text.encode_utf16() — raw input encoded directly, control characters become key events
After: text.chars().filter(|c| !c.is_control()).collect().encode_utf16() — only non-control characters proceed
The is_control() method in Rust's standard library returns true for all Unicode characters with the General Category "Cc" (Other, Control), which includes ASCII control characters 0x00–0x1F and 0x7F, plus C1 control codes 0x80–0x9F. This precisely targets the characters that SendInput interprets semantically rather than literally.
The fix maintains support for international text, emoji, and other Unicode content while removing the dangerous injection vector. The 2000-character limit remains as a defense-in-depth measure against excessive input, but the control character filter is what eliminates the CWE-78 violation.
Key Takeaways
- SendInput with KEYEVENTF_UNICODE is not safe for untrusted text — the Unicode path still processes control characters as key events, not literal glyphs
- Rust's
is_control()precisely matches the dangerous character set — no need for manual ASCII range checks or regex patterns when the standard library provides semantic categorization - Input validation must happen before encoding — filtering after UTF-16 conversion would miss multi-byte control sequences and complicate the logic
- "Type text" APIs are command injection surfaces — any interface that simulates keyboard input carries the risk of triggering application shortcuts, form submissions, or security dialogs through carefully crafted character sequences
How Orbis AppSec Detected This
Source: The text parameter passed to the type_text() Tauri command handler
Sink: SendInput Windows API invoked with KEYEVENTF_UNICODE flag, where control characters in the input array generate semantic key events
Missing control: No validation that input characters were printable before encoding to UTF-16; is_control() check absent
CWE: CWE-78 (OS Command Injection) — injection of special characters that alter the interpretation of a system command (here, the "command" being the sequence of key events)
Fix: Filter all control characters using chars().filter(|c| !c.is_control()) before UTF-16 encoding, ensuring only printable content reaches SendInput
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This vulnerability demonstrates how even seemingly simple "type this text" functionality can become a command injection vector when the underlying platform interprets certain byte values as control signals. The Windows SendInput API's Unicode mode doesn't provide the isolation one might expect—control characters retain their semantic meaning and become actual key presses in the target application.
For Tauri developers and anyone building automation interfaces, the lesson is clear: validate early, use semantic character properties rather than manual range checks, and never assume that "Unicode" means "safe." The is_control() method provides exactly the right abstraction for this case, filtering the General Category Cc characters that trigger unwanted behavior while preserving the international text support that makes Unicode valuable in the first place.