mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
79f2f72f455272dc006ea9625f33539f57dba600
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
928d306b53 |
fix(agent-hooks): stop the Windows hook launcher spelling the AV-denied flag pair (STA-5237) (#16739)
* fix(agent-hooks): stop the Windows hook launcher spelling the AV-denied flag pair (STA-5237) `-WindowStyle Hidden` + `-EncodedCommand` is denied at CreateProcess by Kaspersky on Windows 11, whatever the payload decodes to. Bash reports it as `Permission denied` and every managed hook event fails, so agent status never arrives; the parent shell also briefly cannot spawn anything afterwards, so a denied hook can take the user's next command down with it. Measured on the reporting host (#16003), with a harmless `exit 0` payload: -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand 126 -NoProfile -WindowStyle Hidden -EncodedCommand 126 -WindowStyle Hidden -EncodedCommand 126 -NoProfile -EncodedCommand 0 (5/5) #16576 removed `-ExecutionPolicy Bypass`, which is the one flag of the three NOT in the signature, so hooks kept failing after that fix. The pair that has to stop being spelled is `-WindowStyle Hidden` + `-EncodedCommand`. Because the change is to the shared switch constant, it covers every site that spells the denied pair in one edit: Claude via `wrapWindowsPowerShellEncodedCommand`, gemini/cursor/droid/command-code/copilot via `wrapWindowsHookCommand`, the `runtime-home-hook-command` unsafe-HOME fallback, and the spaced-path fallback for codex/grok/devin/antigravity. Only a flag is removed, so parser and payload compatibility is unchanged for every executor: the string is still a PowerShell command line, still one self-contained token, still base64-shielded. The tradeoff, recorded rather than hidden: `-WindowStyle Hidden` was the shipped fix for #14815 (+#14828, #15117, #15447, #15767), and this removes it. Its suppression was never measured — #14825 confirmed it visually, #16576's author stated it "remains unverified on a real box", and #15506's author argued it cannot help a `.cmd` child with no console to inherit. The console is allocated by the parent chain, not by this command line. A live window measurement is still outstanding and is called out in the PR. Also adds `windows-hook-payload-delivery.test.ts` to the PR CI Windows leg, which had never run it. * test(agent-hooks): keep launcher token out of source grep |
||
|
|
c1a7748267 |
fix(agent-hooks): stop the Windows hook launcher spelling the AV-denied flag triple (#16576)
* fix(agent-hooks): stop the hook launcher spelling the AV-denied flag triple
Orca's Windows agent-hook launcher ran
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden \
-EncodedCommand <base64>
That exact combination is the textbook "hidden encoded PowerShell" malware
shape, and endpoint security denies it at process creation whatever the
payload decodes to -- even `exit 0`. Every injected hook then failed with
`powershell.exe: Permission denied` (exit 126 from bash's execve, EACCES)
on every turn, for both Claude Code and Codex, with no AV exclusion that
re-enabled it.
Dropping any one of the three flags clears the signature. `-ExecutionPolicy
Bypass` is the one that can move: it sets the Process scope, and so does
`Set-ExecutionPolicy -Scope Process`, which now rides inside the encoded
payload. `-EncodedCommand` is never policy-gated, so the bypass always gets
to run before the managed script does -- which is what keeps Copilot's .ps1
hook working under a Restricted or AllSigned machine policy.
The hidden window and the encoding are unchanged, so nothing regresses for
#14815, #14818 or #6078.
Closes #16003
* fix(agent-hooks): ship the launcher shape #16003 actually measured as allowed
The previous revision of this branch dropped only `-ExecutionPolicy Bypass`
and kept `-WindowStyle Hidden -EncodedCommand`, on the reasoning that
"dropping any one of the three flags clears the signature". That sentence is
not in the bisect. The reporter ran exactly four command lines on the affected
Kaspersky/Windows 11 host:
-NoProfile -WindowStyle Hidden -Command 'exit 0' -> 0
-NoProfile -EncodedCommand <b64> -> 0
-NoProfile -ExecutionPolicy Bypass -Command 'exit 0' -> 0
-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand -> 126
Every passing row drops two flags. No row drops exactly one, so the shape the
branch was about to ship had never been executed on the machine that reports
the bug -- and it is `-WindowStyle Hidden -EncodedCommand`, which is the
"hidden encoded PowerShell" pair the denial is named for in our own comment.
Shipping it would have closed #16003 while leaving every hook on that host
dying at CreateProcess, with no tracking left open.
So emit the measured-passing encoded row instead: `-NoProfile -EncodedCommand`.
Of the two flags there was a choice between, `-EncodedCommand` is the one that
carries correctness -- it is what keeps paths and switches intact across
cmd.exe and MSYS (#6078, #14815). `-WindowStyle Hidden` costs at most a console
flash, and only where the parent has no console to inherit.
Second, the relocated bypass now runs inside try/catch. Under a MachinePolicy
or UserPolicy GPO scope, `Set-ExecutionPolicy -Scope Process` reports that the
process scope did not take. `-ErrorAction SilentlyContinue` covers only the
non-terminating half of that; the command-line switch it replaces was silent
either way. This file already documents that non-stdout PowerShell streams
corrupt consumers merging our output into JSON stdout, so a per-invocation
ErrorRecord on stderr is a regression we should not trade for the switch.
Refs #16003
* fix(agent-hooks): keep the hook console hidden while dropping the AV-denied flag
Round 2 of this PR widened the fix from "stop spelling -ExecutionPolicy Bypass"
to "stop spelling it and -WindowStyle Hidden", on the reasoning that the #16003
reporter never measured a shape that drops exactly one flag, so keeping the
hidden+encoded pair would be extrapolation.
That trades a reproduced regression for an unmeasured one. Window suppression is
the shipped fix for #14815 and its four duplicates (#14828, #15117, #15447,
#15767): a hook launched from a parent with no console gets a fresh console per
event, which takes foreground and eats whatever the user is typing into Orca,
and never closes at all on the stdin-blocking path hook-stdin-contract.ts exists
to guard. That fires on every prompt, tool call and stop of every managed agent.
The AV denial, by contrast, is measured only for the full triple; that the
remaining pair still trips it is a hypothesis. Between a certain regression and
a possible one, keep the certainty.
So the flag that leaves the command line is the policy bypass alone — the only
one of the three with an exact in-payload equivalent, hence the only one that
can move without losing behaviour. If the pair turns out to be denied too, the
answer is a different shape that still hides the window.
* fix(agent-hooks): silence progress before the policy bypass can autoload (#16621)
Hardware-measured on Windows 11 while exercising #16576.
Set-ExecutionPolicy autoloads Microsoft.PowerShell.Security, and that module's
"Preparing modules for first use." progress record is written before any later
assignment can suppress it. Running the bypass first therefore defeated the
silencer that runs immediately after it:
bypass-first stderr = 616 bytes, first merged line '#< CLIXML'
silencer-first stderr = 0 bytes, first merged line '{"decision":"approve"}'
That is precisely the corruption HOOK_PROGRESS_SILENCER's own comment warns
about -- redirected progress becoming CLIXML that can corrupt merged JSON -- so
the PR reintroduced the hazard it documents, one line below documenting it.
Both existing tests asserted the broken order, so they enforced the bug rather
than catching it. Reordered them and added one that pins the ordering itself
rather than the literal string, since the string will drift again.
|
||
|
|
02ba70a847 |
fix(agent-hooks): make the Windows managed hook survive Claude-hooks-compat consumers (#14825)
* fix(agent-hooks): make the Windows managed hook survive Claude-hooks-compat consumers `~/.claude/settings.json` is not read only by Claude Code. Third-party Claude-hooks-compat layers (cursor-agent, Devin) import the same file and reimplement hook execution, so Orca's entry has to survive consumers that support strictly less than the documented schema. Three separate defects came from assuming otherwise. 1. The entry depended on `args`, which a compat consumer ignores. `args` is valid Claude Code syntax, but cursor-agent spawns `command` alone -- so `conhost.exe` ran bare, which opens an interactive console that never closes. Hook payloads were typed into those stranded shells (#14815). The entry is now one self-contained `command` string that depends on nothing optional. 2. `conhost.exe --headless` never relayed anything. It implements the ConPTY server protocol, not a generic no-window wrapper: it does not wait for the hosted process and relays neither exit code nor stdout. Measured directly -- `conhost --headless cmd /c "echo X& exit /b 42"` yields empty stdout and no exit code, while the replacement returns both and waits. So every hook was fire-and-forget, and whatever it printed was discarded. Replaced with `-WindowStyle Hidden`, which suppresses the window and keeps wait/exit-code/stdout intact. 3. The hook never wrote anything to stdout. Guards exited silently and curl's output went to nul. Claude Code documents empty stdout as "no decision", but cursor-agent treats PreToolUse as a permission gate, fails to parse empty stdout as JSON, and blocks the tool call -- so every shell command in every cursor-agent session on Windows failed (#14818). The script now writes `{}` first, on both the Windows and POSIX branches, which is documented to be identical to writing nothing for real Claude Code. Gemini and Antigravity already did this. Defects 2 and 3 are causally linked: `{}` cannot reach any consumer while conhost is swallowing stdout, so neither fix works without the other. Also fixed while establishing the contract: - The launcher's own missing-script fallback returned empty stdout, reproducing #14818 whenever `~/.orca` was cleaned or an install was half-finished. It now emits `{}` too. - PowerShell serializes progress records to stderr as CLIXML when stderr is redirected; a consumer merging stderr into stdout would see those bytes before the JSON. Every encoded payload now silences progress. - `runtime-home-hook-command.ts` built its own launcher without window suppression -- exactly the drift #14815 asks to prevent. All launcher construction now goes through `windows-powershell-hook-launcher.ts`, so the switch list cannot be present in one installer and missing in another. - Renamed `usesWindowsHeadlessHook` to `usesWindowsPowerShellLauncher`; nothing is headless anymore, and the flag selects a launcher. Testing: the new regression test asserts the effect a consumer observes -- it runs the exact `command` string from settings.json through both cmd.exe and Git Bash, across the guard-exit, reached-curl, and missing-script paths, and parses stdout. Verified it fails when `conhost --headless` is reintroduced. The previous tests all asserted installer intent, which is why they passed through all three defects. * fix(agent-hooks): close hook launcher review gaps --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
c3ab805d12 |
fix(agent-hooks): drain stdin before hook script early exits so agents never hit EPIPE (#8430)
* Fix hook scripts to drain stdin before any early-exit path Generated agent hook scripts and missing-script launchers could exit successfully before consuming the payload written to their stdin, leaving the writer with a broken pipe (EPIPE/ERROR_BROKEN_PIPE) once the reader closed early. Capture stdin (or drain it via a shared epilogue/fast-path guard) before any whole-script success exit across all POSIX, batch, PowerShell, and Git Bash launcher variants, and add a cross-agent lifecycle test suite plus a live Electron verification script to guard the contract going forward. * Harden hook scripts against unreadable managed scripts and add a Claude/ - Extend the POSIX launcher guard to also require `[ -r ]`, not just `-f`/`-x`, so an executable-but-unreadable managed script still drains stdin instead of erroring or silently misbehaving. - Add a verifier case (`verifyClaudeDevinSkip`) that spins up a local HTTP server and confirms the Claude hook never forwards a request that Devin already imported, catching accidental double-forwarding. - Update installer-utils tests and stdin-lifecycle docs to match the new readable-file guard and the added verification case. * Fix hook-launcher verification to derive script paths from the installed Extract the quoted path from the launcher's `if [ -f '...'` clause instead of reconstructing it via join(home, ...), so missing/failing-script test cases can't silently fall through to the real script if the install layout changes. --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
46646d7ff1 |
chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day minimum-release-age supply-chain guard; nothing here needs it). The bump is a no-op on the existing config. Enable 3 error rules (backlog autofixed to zero in this commit) and 4 warn rules (surface signal without gating CI): error (autofixed, behavior-preserving): - unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:) - typescript/no-import-type-side-effects (~36: all-inline-type -> import type) - unicorn/no-array-reverse (19: copy-then-reverse -> toReversed) warn (real signal, current fires are test-only/correct): - unicorn/no-array-fill-with-reference-type (aliasing footgun guard) - typescript/no-unsafe-function-type (bans bare Function type) - unicorn/prefer-array-flat-map (map().flat() -> flatMap()) - unicorn/prefer-regexp-test (.match() in bool ctx -> .test()) mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix ran from root and covered mobile/ too. Verification (all green): oxlint 0 errors (root+mobile+aux configs), oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed, builds (electron-vite + web + cli) succeed. node: rewrites confirmed to skip embedded SSH/CLI string payloads (AST-only); all toReversed sites verified to operate on fresh copies or write-once locals. * chore(lint): bump mobile oxlint to 1.71 so inherited rules parse mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile && oxlint' failed to parse the new rule. Bump mobile to match root (1.71). Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit pass, vitest 978 passed / 0 failed. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
b6ac7b9c92 |
Harden Windows hook command resolution (#6409)
* Keep spaced Windows Codex hook paths fast * Harden Windows hook command resolution --------- Co-authored-by: Neil <neil@stably.ai> |
||
|
|
1fde7f553c |
fix(agent-hooks): wrap Windows hook commands in cmd.exe to survive spaces in profile path (#6078) (#6083)
* fix(codex): wrap Windows hook command in cmd.exe to survive spaces in profile path (#6078) Windows splits raw hook commands on whitespace, so a user profile path like `C:\Users\Jane Doe` made Codex hooks exit with code 1. Add a wrapWindowsHookCommand helper that invokes the .cmd through `cmd.exe /d /c call "..."` and use it in getManagedCommand. * fix(agent-hooks): wrap Windows hook command in cmd.exe for all agents with raw .cmd path (#6078) Apply the wrapWindowsHookCommand helper to cursor, command-code, gemini, grok, and droid, which shared the same raw-scriptPath-on-Windows pattern as codex. A user profile path with a space (e.g. `C:\Users\Jane Doe`) used to split at the space and fail with exit code 1. Agents that already handle spaces correctly are left untouched: - claude/openclaude (Git Bash + forward slashes) - copilot (PowerShell with quoted path) - kimi (Git Bash + forward slashes) - antigravity (event-specific wrapper .cmd files) - devin (already wraps via `cmd /d /s /c ""...""`) Each fixed agent gets a Windows-only test asserting the cmd.exe wrapping survives spaces in the profile path. * fix(claude): wrap Windows hook command in cmd.exe to survive spaces in profile path (#6078) Claude Code runs hooks through Git Bash on Windows. The previous forward-slash trick only works when the path has no spaces — Git Bash splits `C:/Users/Jane Doe/...` at the space and tries to execute `C:/Users/Jane` as a command. Use wrapWindowsHookCommand so the .cmd is invoked through `cmd.exe /d /c call "..."`, which Git Bash treats as one argument. Applies to both Claude and OpenClaude (shared getManagedCommand). * Harden Windows agent hook launcher --------- Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> |
||
|
|
fe8fe16c83 |
Preserve agent settings when sleeping sessions resume (#5916)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
0ec3882cb8 |
Add project Windows runtime selection (#5519)
* Add project Windows runtime selection * Fix project Windows runtime selection Co-authored-by: Orca <help@stably.ai> * fix: preserve WSL shell variables --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
a6f9a5826e |
Add Command Code agent status tracking
Adds Command Code hook installation, status normalization, launch seeding, and terminal-output fallback detection for working/done sidebar status. Includes review hardening for long-running tool repaint cadence and prompt sanitization across split ANSI chunks. |