Back to Blog
critical SEVERITY4 min read

Tauri type_text() Command Injection via Control Characters in

The type_text() system command handler in Tauri accepted arbitrary text up to 2000 UTF-16 characters without validating content, allowing injection of control characters that SendInput's Unicode path interprets as real key presses. An attacker could inject newline, escape, or tab characters to trigger actions in the focused window beyond mere text typing.

O
By Orbis AppSec
Published September 24, 2026Reviewed September 24, 2026

Answer Summary

The type_text() public API in Tauri-based applications accepted unvalidated text input passed directly to Windows SendInput. An attacker could inject control characters like newline or escape that SendInput interprets as actual key presses, triggering unintended actions in whatever window had focus. The fix filters all control characters using chars().filter(|c| !c.is_control()) before encoding to UTF-16. CWE-78 (OS Command Injection).

Vulnerability at a Glance

cweCWE-78 (OS Command Injection)
fixFilter control characters with is_control() before encoding to UTF-16 for SendInput
riskArbitrary key injection in focused applications, potentially triggering dangerous shortcuts or UI actions
languageRust
root causeUnvalidated text passed to SendInput with KEYEVENTF_UNICODE, where control characters become actual key presses
vulnerabilityCommand injection via keyboard input control characters

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.

Prevention and further reading

Frequently Asked Questions

Does the 2000-character limit in type_text() prevent exploitation of this vulnerability?

No. The limit only restricts total input length; a single control character anywhere in those 2000 UTF-16 units is sufficient to trigger unintended actions, and the vulnerability exists regardless of how much text surrounds it.

Why does KEYEVENTF_UNICODE with SendInput interpret control characters as key presses rather than literal Unicode code points?

SendInput's Unicode path still processes certain control characters (CR, LF, TAB, ESC) as semantic key events that the receiving window interprets according to its own logic—Enter submits forms, Escape cancels dialogs—rather than displaying them as visible glyphs.

Is filtering with is_control() sufficient, or should type_text() also restrict to printable ASCII?

is_control() is sufficient for this specific vulnerability because it removes all characters with the Unicode General Category Cc (control), which includes the problematic CR/LF/TAB/ESC bytes. However, applications with stricter requirements may additionally want to validate against a printable allowlist depending on their threat model.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

high

runStreaming() Command Injection: Defense-in-Depth for Electron Child

An Electron application's `runStreaming()` utility accepted a command string and argument array without validating either, creating a latent command injection vector. The fix adds strict type checking and a whitelist regex that rejects shell metacharacters, bounding the failure mode even if caller input becomes attacker-influenced.

critical

docker_rpc.uc Command Injection: Unsanitized RPC Parameters

A critical command injection vulnerability in the Docker RPC handler allowed authenticated attackers to execute arbitrary system commands by injecting shell metacharacters into container ID, port, user ID, or command parameters. The fix validates all user-supplied inputs against strict whitelist patterns before interpolating them into shell commands.

critical

{sample} Placeholder in shlex.split() Lets Filenames Inject Args

A protocol replay-check CLI built its subprocess argument list by calling `str.format()` on a user-supplied `--command` template and then handing the result to `shlex.split()`, so a sample filename containing spaces, quotes, or shell metacharacters could split into extra argv entries — or execute as shell code when the template wrapped the placeholder in `sh -c`. The fix wraps the interpolated path in `shlex.quote()` before formatting, so the path always survives `shlex.split()` as a single toke

high

package_abridge.js Command Injection via Unsanitized CLI Arguments

A high-severity command injection vulnerability in a build script allowed attackers who control CLI arguments to execute arbitrary shell commands by injecting metacharacters into an unvalidated parameter. The fix validates incoming CLI arguments and rejects those containing dangerous shell metacharacters before they reach command execution.

critical

shell-quote 1.8.3: Line Terminator Command Injection (CVE-2026-9277)

CVE-2026-9277 is a critical command injection vulnerability in shell-quote versions before 1.9.0, where unescaped line terminators allow attackers to break out of quoted strings and execute arbitrary shell commands. The fix upgrades the dependency across multiple React Native CLI packages and related libraries through npm overrides.

critical

SchedulePush.disableReminder Missing Authorization Check in push.js

The `disableReminder` method in the `SchedulePush` class allowed any user to disable push notification reminders for arbitrary user IDs by manipulating the `e.user_id` event parameter. The fix adds a `checkFriend()` authorization gate that verifies the requesting user has a valid friendship relationship with the bot before modifying subscription state.