mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 08:02:12 +00:00
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.
This commit is contained in:
@@ -21,7 +21,7 @@ vi.mock('os', async () => {
|
||||
import { CommandCodeHookService } from './hook-service'
|
||||
|
||||
const WINDOWS_POWERSHELL_LAUNCHER =
|
||||
/^[A-Za-z]:\/[^"]*\/System32\/WindowsPowerShell\/v1\.0\/powershell\.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand \S+$/
|
||||
/^[A-Za-z]:\/[^"]*\/System32\/WindowsPowerShell\/v1\.0\/powershell\.exe -NoProfile -WindowStyle Hidden -EncodedCommand \S+$/
|
||||
|
||||
describe('CommandCodeHookService', () => {
|
||||
let homeDir: string
|
||||
|
||||
Reference in New Issue
Block a user