Add --allow-system-keys to ui send-keys#603
Conversation
By default, send-keys --via send-input refuses system-/shell-reserved combos (win+<key>, alt+f4, alt+tab, ctrl+esc, ...) via SystemKeyGuard because they act on the OS/shell beyond the target app when injected OS-wide. --allow-system-keys is an explicit opt-in that bypasses that one guard so a caller can drive a global hotkey (e.g. PowerToys' win+shift+v) - the low-level global hook watches the OS-wide input stream, so the injected combo fires it. - Default is unchanged (flag absent = still rejected); the bypass path logs an audit warning listing only the combo names (never the raw keys), consistent with the existing no-echo policy. - No other boundary is relaxed: the no-target guard, foreground/locked-desktop guard (no_interactive_desktop), UIPI, and post-message path are untouched. Windows still blocks secure sequences like ctrl+alt+del (SAS) regardless. - 4 new unit tests (flag sends, non-system combo unaffected, default still rejects, description discoverable); 55/55 send-keys + guard tests green. - Docs + cli-schema + SKILL.md mirrors regenerated at 0.4.1. Proven end-to-end: default rejects win+r; --allow-system-keys makes win+r open the Run dialog (a Win-modified shell hotkey firing off the injected stream). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in --allow-system-keys flag to winapp ui send-keys. By default, --via send-input refuses to synthesize system-/shell-reserved combos (e.g. win+l, alt+f4, alt+tab) because those act OS-wide beyond the target app. The new flag lets a caller explicitly opt in to fire global hotkeys (e.g. PowerToys' win+shift+v) while preserving the default safety posture and all existing guards (no-target, foreground, secure-desktop). It fits into the ui automation surface of the CLI, extending the existing SystemKeyGuard gate rather than removing it.
Changes:
- Adds
AllowSystemKeysOptiontoUiSendKeysCommand; when a system combo is detected undersend-input, the guard now blocks by default but falls through (with a warning) when the flag is set. - Adds four unit tests covering opt-in send, non-system no-op, default rejection, and option-description discoverability.
- Updates all documentation surfaces (ui-automation.md, skill fragment, cli-schema.json, and both generated SKILL.md mirrors) to describe the flag.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/winapp-CLI/WinApp.Cli/Commands/UiSendKeysCommand.cs | Adds --allow-system-keys option and the conditional guard-bypass path with an audit warning |
| src/winapp-CLI/WinApp.Cli.Tests/UiCommandTests.SendKeys.cs | Four new tests for the opt-in, unaffected non-system path, default rejection, and help-text content |
| docs/ui-automation.md | Documents the flag and updates the system-reserved-combos guidance |
| docs/fragments/skills/winapp-cli/ui-automation.md | Hand-written skill fragment updated with the opt-in example and behavior |
| docs/cli-schema.json | Regenerated schema entry for the new boolean option |
| .github/plugin/skills/winapp-cli/ui-automation/SKILL.md | Auto-generated skill mirror (example + options-table row) |
| .claude/skills/winapp-ui-automation/SKILL.md | Auto-generated Claude skill mirror (example + options-table row) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Caller explicitly opted in with --allow-system-keys (e.g. to fire a global hotkey such as | ||
| // PowerToys' win+shift+v). Record the bypass so it's auditable in persisted logs, then fall | ||
| // through and inject. (Windows still blocks secure sequences like ctrl+alt+del regardless.) | ||
| logger.LogWarning( | ||
| "{Symbol} Injecting system-reserved key(s) via --via send-input because --allow-system-keys was set: {Combos}. " + | ||
| "These act on the OS/shell beyond the target app.", | ||
| UiSymbols.Warning, string.Join(", ", systemCombos)); |
Build Metrics ReportBinary Sizes
Test Results✅ 1549 passed, 1 skipped out of 1550 tests in 465.4s (+4 tests, -12.6s vs. baseline) Test Coverage❌ 18.1% line coverage, 37.3% branch coverage · ✅ no change vs. baseline CLI Startup Time35ms median (x64, Updated 2026-07-06 03:17:34 UTC · commit |
nmetulev
left a comment
There was a problem hiding this comment.
🤖 AI-generated review — produced by GitHub Copilot CLI at a maintainer's request. Every finding comes from an automated, multi-dimensional review plus a hands-on build-and-test of this branch in an isolated worktree. Treat
file:linereferences as pointers to verify, not gospel. Nothing was pushed. Posting as a comment (no approve / request-changes).
Verdict: 🟡 SHIP-WITH-CHANGES — The double opt-in design is sound and the existing guards stay intact, but win+l should remain blocked and the silent no-op path needs a warning.
What I actually ran
Built (~33s, 0 warnings); 55/55 SendKeys + SystemKeyGuard tests pass. Against Notepad:
- Normal text
send-keys✅. win+lvia--via send-inputwithout the flag → BLOCKED, exit 1, with a clear, actionable error naming the combo and "Pass--allow-system-keysto opt in." ✅win+upwith--allow-system-keys --via send-input→ fired OS-wide (Notepad maximized);win+downrestored it — self-reversing ✅. (Injection works here when the target is foregrounded first.)win+lwith--allow-system-keysbut no--via send-input→ exit 0,via":"post-message", no warning, hotkey never fires (SEC-02).- No-target guard still fires with the flag ✅.
Must-fix
- SEC-01 (High) —
win+lis unblocked by the flag with no per-combo granularity (SystemKeyGuard.cs:56-59collapses everything to a genericwin+<key>; bypass atUiSendKeysCommand.cs:244).win+lis not a SAS — a user-modeSendInputof VK_LWIN+L reaches explorer's low-level hook and callsLockWorkStation(). A mistypedwin+lvswin+leftin automation locks the (remote) desktop. Consider keepingwin+l(andalt+f4/ctrl+shift+esc) blocked even with the opt-in; the cited use case is registered hotkeys likewin+shift+v, not lock control. - SEC-02 (Medium) — silent no-op.
--allow-system-keyswithout--via send-inputis silently accepted (exit 0, no effect). Emit aLogWarningso this isn't a silent footgun. - COR-01 add a test for the
--allow-system-keys+post-messagepath. DOCS-02 the docs name onlyctrl+alt+delas "still blocked," implying other combos are safe — call outwin+l/alt+f4explicitly.
Fit & recommendation
Real, unmet use case (global hotkeys are unreachable via post-message), default posture unchanged, guards preserved, an actionable agent-readable error, and a LogWarning audit trail on every bypass. The tension is the security surface: a CLI/agent can now fire OS-wide hotkeys. The blanket on/off is acceptable to start if the docs and the win+l gap are addressed; a per-combo allowlist (e.g. --allow-system-keys win+shift+v) would be a cleaner long-term design and would keep the blast radius to what the caller actually needs.
Summary
--allow-system-keystowinapp ui send-keys--via send-inputWhy
This enables intentional global hotkey automation (for example, PowerToys-style Win-key shortcuts) without weakening the default behavior.