Files
orca/src/main/command-code/command-code-managed-script.ts
T
brennanb2025andOrca 82ef739ea3 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>
2026-07-03 18:09:48 -07:00

133 lines
6.8 KiB
TypeScript

import { buildWindowsAgentHookPostCommand } from '../agent-hooks/installer-utils'
import {
buildPosixSecureCurlPostLines,
buildWindowsEndpointSubroutineLines
} from '../agent-hooks/managed-hook-script'
export type CommandCodeManagedScriptTarget = 'local' | 'posix'
export function buildCommandCodeManagedScript(
target: CommandCodeManagedScriptTarget = 'local'
): string {
if (target === 'local' && process.platform === 'win32') {
return [
'@echo off',
'setlocal',
// 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',
'if "%ORCA_PANE_KEY%"=="" exit /b 0',
buildWindowsAgentHookPostCommand('command-code'),
'exit /b 0',
':sourceEndpointByPort',
'if not defined APPDATA exit /b 0',
'if exist "%APPDATA%\\orca-dev\\agent-hooks" for /r "%APPDATA%\\orca-dev\\agent-hooks" %%F in (endpoint.cmd) do call :maybeSourceEndpoint "%%~fF"',
'if "%ORCA_AGENT_HOOK_TOKEN%"=="" if exist "%APPDATA%\\orca\\agent-hooks" for /r "%APPDATA%\\orca\\agent-hooks" %%F in (endpoint.cmd) do call :maybeSourceEndpoint "%%~fF"',
'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 :__orcaParseEndpointFile "%~1"',
'exit /b 0',
...buildWindowsEndpointSubroutineLines(),
''
].join('\r\n')
}
return [
'#!/bin/sh',
'__orca_read_ancestor_var() {',
' __orca_name="$1"',
' __orca_pid="${PPID:-}"',
' while [ -n "$__orca_pid" ] && [ "$__orca_pid" != "0" ] && [ "$__orca_pid" != "1" ]; do',
' __orca_value=""',
' if [ -r "/proc/$__orca_pid/environ" ]; then',
' __orca_value=$(tr "\\000" "\\n" < "/proc/$__orca_pid/environ" 2>/dev/null | sed -n "s/^${__orca_name}=//p" | head -n 1)',
' fi',
' if [ -z "$__orca_value" ]; then',
' __orca_value=$(ps eww -p "$__orca_pid" -o command= 2>/dev/null | tr " " "\\n" | sed -n "s/^${__orca_name}=//p" | head -n 1)',
' fi',
' if [ -n "$__orca_value" ]; then',
' printf "%s\\n" "$__orca_value"',
' return 0',
' fi',
' __orca_pid=$(ps -o ppid= -p "$__orca_pid" 2>/dev/null | tr -d " ")',
' done',
' return 1',
'}',
'__orca_fill_from_ancestor() {',
' __orca_name="$1"',
' case "$__orca_name" in',
' ORCA_AGENT_HOOK_ENDPOINT) [ -z "${ORCA_AGENT_HOOK_ENDPOINT:-}" ] || return 0 ;;',
' ORCA_AGENT_HOOK_PORT) [ -z "${ORCA_AGENT_HOOK_PORT:-}" ] || return 0 ;;',
' ORCA_AGENT_HOOK_TOKEN) [ -z "${ORCA_AGENT_HOOK_TOKEN:-}" ] || return 0 ;;',
' ORCA_AGENT_HOOK_ENV) [ -z "${ORCA_AGENT_HOOK_ENV:-}" ] || return 0 ;;',
' ORCA_AGENT_HOOK_VERSION) [ -z "${ORCA_AGENT_HOOK_VERSION:-}" ] || return 0 ;;',
' ORCA_PANE_KEY) [ -z "${ORCA_PANE_KEY:-}" ] || return 0 ;;',
' ORCA_TAB_ID) [ -z "${ORCA_TAB_ID:-}" ] || return 0 ;;',
' ORCA_WORKTREE_ID) [ -z "${ORCA_WORKTREE_ID:-}" ] || return 0 ;;',
' ORCA_AGENT_LAUNCH_TOKEN) [ -z "${ORCA_AGENT_LAUNCH_TOKEN:-}" ] || return 0 ;;',
' *) return 0 ;;',
' esac',
' __orca_value=$(__orca_read_ancestor_var "$__orca_name") || return 0',
' [ -n "$__orca_value" ] && export "$__orca_name=$__orca_value"',
'}',
'__orca_endpoint_value() {',
' __orca_endpoint_name="$1"',
' __orca_endpoint_path="$2"',
' sed -n "s/^${__orca_endpoint_name}=//p" "$__orca_endpoint_path" 2>/dev/null | head -n 1',
'}',
'__orca_fill_from_endpoint_file() {',
' __orca_endpoint_path="$1"',
' [ -r "$__orca_endpoint_path" ] || return 0',
' __orca_endpoint_port=$(__orca_endpoint_value ORCA_AGENT_HOOK_PORT "$__orca_endpoint_path")',
' if [ -n "${ORCA_AGENT_HOOK_PORT:-}" ] && [ -n "$__orca_endpoint_port" ] && [ "$__orca_endpoint_port" != "$ORCA_AGENT_HOOK_PORT" ]; then',
' return 0',
' fi',
' for __orca_endpoint_name in ORCA_AGENT_HOOK_PORT ORCA_AGENT_HOOK_TOKEN ORCA_AGENT_HOOK_ENV ORCA_AGENT_HOOK_VERSION; do',
' eval "__orca_current=\\${$__orca_endpoint_name:-}"',
' [ -z "$__orca_current" ] || continue',
' __orca_endpoint_value=$(__orca_endpoint_value "$__orca_endpoint_name" "$__orca_endpoint_path")',
' [ -n "$__orca_endpoint_value" ] && export "$__orca_endpoint_name=$__orca_endpoint_value"',
' done',
'}',
'# Why: Command Code sanitizes hook subprocess env. The parent TUI process',
'# still has Orca pane/hook metadata, so recover it before posting.',
'for __orca_name in ORCA_AGENT_HOOK_ENDPOINT ORCA_AGENT_HOOK_PORT ORCA_AGENT_HOOK_TOKEN ORCA_AGENT_HOOK_ENV ORCA_AGENT_HOOK_VERSION ORCA_PANE_KEY ORCA_TAB_ID ORCA_WORKTREE_ID ORCA_AGENT_LAUNCH_TOKEN; do',
' __orca_fill_from_ancestor "$__orca_name"',
'done',
'if [ -n "$ORCA_AGENT_HOOK_ENDPOINT" ] && [ -r "$ORCA_AGENT_HOOK_ENDPOINT" ]; then',
' __orca_fill_from_endpoint_file "$ORCA_AGENT_HOOK_ENDPOINT"',
'fi',
'# Why: Command Code strips TOKEN-like env vars before invoking hooks. If',
'# ORCA_AGENT_HOOK_ENDPOINT was not exported into this PTY, recover the',
'# matching endpoint file by the unstripped loopback port.',
'if [ -z "$ORCA_AGENT_HOOK_TOKEN" ] && [ -n "$ORCA_AGENT_HOOK_PORT" ]; then',
' for endpoint in \\',
' "$HOME/Library/Application Support/orca-dev/agent-hooks"/*/endpoint.env \\',
' "$HOME/Library/Application Support/orca-dev/agent-hooks/endpoint.env" \\',
' "${XDG_CONFIG_HOME:-$HOME/.config}/orca-dev/agent-hooks"/*/endpoint.env \\',
' "${XDG_CONFIG_HOME:-$HOME/.config}/orca-dev/agent-hooks/endpoint.env" \\',
' "$HOME/Library/Application Support/orca/agent-hooks/endpoint.env" \\',
' "${XDG_CONFIG_HOME:-$HOME/.config}/orca/agent-hooks/endpoint.env"; do',
' [ -r "$endpoint" ] || continue',
' endpoint_port=$(sed -n "s/^ORCA_AGENT_HOOK_PORT=//p" "$endpoint" | head -n 1)',
' if [ "$endpoint_port" = "$ORCA_AGENT_HOOK_PORT" ]; then',
' __orca_fill_from_endpoint_file "$endpoint"',
' break',
' fi',
' done',
'fi',
'if [ -z "$ORCA_AGENT_HOOK_PORT" ] || [ -z "$ORCA_AGENT_HOOK_TOKEN" ] || [ -z "$ORCA_PANE_KEY" ]; then',
' exit 0',
'fi',
...buildPosixSecureCurlPostLines({ source: 'command-code' }),
'exit 0',
''
].join('\n')
}