fix(agent-hooks): parse endpoint file and keep hook secrets off argv

The managed shell/cmd hook scripts sourced the endpoint file
(`. "$file"` / `call "%file%"`), so any shell/batch written into that
path executed with the agent's privileges. They also passed the hook
token and the raw payload as curl command-line arguments (readable by
other same-UID processes via `ps`/`/proc/<pid>/cmdline`) and did not
disable proxying for the loopback POST.

Add a shared builder (`agent-hooks/managed-hook-script.ts`) used by every
shell/cmd hook:

- Parse only the four known ORCA_AGENT_HOOK_* keys from the endpoint
  file; never execute it. Values are assigned as literals, so a command
  substitution in the file is stored, not run.
- Stream the token into curl as a header from stdin (`-H @-`) and read
  the payload from a private temp file (`--data-urlencode payload@<file>`),
  so neither appears on the command line.
- Add `--noproxy 127.0.0.1` so a configured http_proxy/ALL_PROXY cannot
  route the token and prompt off-box.
- Validate the port as digits-only; keep the Claude hook silent on stdout.

The JS, Python, and PowerShell hooks already parsed the file and sent the
token as an in-process header, so they are unchanged. On Windows cmd the
token stays on argv (no mktemp; stdin is taken by the payload) — a
documented, same-user-only exposure matching the already-ACL'd endpoint
file.

STA-1284

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
brennanb2025
2026-07-03 18:09:48 -07:00
co-authored by Orca
parent 748bced2e6
commit 82ef739ea3
22 changed files with 610 additions and 462 deletions
@@ -1,4 +1,8 @@
import { buildWindowsAgentHookPostCommand } from '../agent-hooks/installer-utils'
import {
buildPosixSecureCurlPostLines,
buildWindowsEndpointSubroutineLines
} from '../agent-hooks/managed-hook-script'
export type CommandCodeManagedScriptTarget = 'local' | 'posix'
@@ -9,7 +13,11 @@ export function buildCommandCodeManagedScript(
return [
'@echo off',
'setlocal',
'if "%ORCA_AGENT_HOOK_PORT%"=="" if defined ORCA_AGENT_HOOK_ENDPOINT if exist "%ORCA_AGENT_HOOK_ENDPOINT%" call "%ORCA_AGENT_HOOK_ENDPOINT%" 2>nul',
// Why: the endpoint file(s) are parsed, never executed, so untrusted
// content in that path cannot run. Command Code sanitizes the hook env, so
// when the primary endpoint is missing we still search %APPDATA% for the
// endpoint.cmd whose port matches the recovered loopback port.
'if "%ORCA_AGENT_HOOK_PORT%"=="" call :__orcaLoadEndpoint',
'if "%ORCA_AGENT_HOOK_TOKEN%"=="" if not "%ORCA_AGENT_HOOK_PORT%"=="" call :sourceEndpointByPort',
'if "%ORCA_AGENT_HOOK_PORT%"=="" exit /b 0',
'if "%ORCA_AGENT_HOOK_TOKEN%"=="" exit /b 0',
@@ -23,8 +31,9 @@ export function buildCommandCodeManagedScript(
'exit /b 0',
':maybeSourceEndpoint',
'if not "%ORCA_AGENT_HOOK_TOKEN%"=="" exit /b 0',
'for /f "tokens=2 delims==" %%P in (\'findstr /b /c:"set ORCA_AGENT_HOOK_PORT=" "%~1" 2^>nul\') do if "%%P"=="%ORCA_AGENT_HOOK_PORT%" call "%~1" 2>nul',
'for /f "tokens=2 delims==" %%P in (\'findstr /b /c:"set ORCA_AGENT_HOOK_PORT=" "%~1" 2^>nul\') do if "%%P"=="%ORCA_AGENT_HOOK_PORT%" call :__orcaParseEndpointFile "%~1"',
'exit /b 0',
...buildWindowsEndpointSubroutineLines(),
''
].join('\r\n')
}
@@ -116,25 +125,7 @@ export function buildCommandCodeManagedScript(
'if [ -z "$ORCA_AGENT_HOOK_PORT" ] || [ -z "$ORCA_AGENT_HOOK_TOKEN" ] || [ -z "$ORCA_PANE_KEY" ]; then',
' exit 0',
'fi',
'payload=$(cat)',
'if [ -z "$payload" ]; then',
' exit 0',
'fi',
// Timeout caps best-effort hook posts if the local listener stalls.
// Why: pipe payload to curl's stdin (`payload@-`) instead of an inline
// `payload=$VALUE` arg, so tens-of-KB tool output stays off the curl
// command line (EDR command-line false positives). Wire body is identical.
'printf \'%s\' "$payload" | curl -sS -X POST "http://127.0.0.1:${ORCA_AGENT_HOOK_PORT}/hook/command-code" \\',
' --connect-timeout 0.5 --max-time 1.5 \\',
' -H "Content-Type: application/x-www-form-urlencoded" \\',
' -H "X-Orca-Agent-Hook-Token: ${ORCA_AGENT_HOOK_TOKEN}" \\',
' --data-urlencode "paneKey=${ORCA_PANE_KEY}" \\',
' --data-urlencode "tabId=${ORCA_TAB_ID}" \\',
' --data-urlencode "launchToken=${ORCA_AGENT_LAUNCH_TOKEN}" \\',
' --data-urlencode "worktreeId=${ORCA_WORKTREE_ID}" \\',
' --data-urlencode "env=${ORCA_AGENT_HOOK_ENV}" \\',
' --data-urlencode "version=${ORCA_AGENT_HOOK_VERSION}" \\',
' --data-urlencode "payload@-" >/dev/null 2>&1 || true',
...buildPosixSecureCurlPostLines({ source: 'command-code' }),
'exit 0',
''
].join('\n')