( hook: HttpHook, _hookEvent: HookEvent, jsonInput: string, signal?: AbortSignal, )
| 121 | * all other references are replaced with empty strings. |
| 122 | */ |
| 123 | export async function execHttpHook( |
| 124 | hook: HttpHook, |
| 125 | _hookEvent: HookEvent, |
| 126 | jsonInput: string, |
| 127 | signal?: AbortSignal, |
| 128 | ): Promise<{ |
| 129 | ok: boolean |
| 130 | statusCode?: number |
| 131 | body: string |
| 132 | error?: string |
| 133 | aborted?: boolean |
| 134 | }> { |
| 135 | // Enforce URL allowlist before any I/O. Follows allowedMcpServers semantics: |
| 136 | // undefined → no restriction; [] → block all; non-empty → must match a pattern. |
| 137 | const policy = getHttpHookPolicy() |
| 138 | if (policy.allowedUrls !== undefined) { |
| 139 | const matched = policy.allowedUrls.some(p => urlMatchesPattern(hook.url, p)) |
| 140 | if (!matched) { |
| 141 | const msg = `HTTP hook blocked: ${hook.url} does not match any pattern in allowedHttpHookUrls` |
| 142 | logForDebugging(msg, { level: 'warn' }) |
| 143 | return { ok: false, body: '', error: msg } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | const timeoutMs = hook.timeout |
| 148 | ? hook.timeout * 1000 |
| 149 | : DEFAULT_HTTP_HOOK_TIMEOUT_MS |
| 150 | |
| 151 | const { signal: combinedSignal, cleanup } = createCombinedAbortSignal( |
| 152 | signal, |
| 153 | { timeoutMs }, |
| 154 | ) |
| 155 | |
| 156 | try { |
| 157 | // Build headers with env var interpolation in values |
| 158 | const headers: Record<string, string> = { |
| 159 | 'Content-Type': 'application/json', |
| 160 | } |
| 161 | if (hook.headers) { |
| 162 | // Intersect hook's allowedEnvVars with policy allowlist when policy is set |
| 163 | const hookVars = hook.allowedEnvVars ?? [] |
| 164 | const effectiveVars = |
| 165 | policy.allowedEnvVars !== undefined |
| 166 | ? hookVars.filter(v => policy.allowedEnvVars!.includes(v)) |
| 167 | : hookVars |
| 168 | const allowedEnvVars = new Set(effectiveVars) |
| 169 | for (const [name, value] of Object.entries(hook.headers)) { |
| 170 | headers[name] = interpolateEnvVars(value, allowedEnvVars) |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | // Route through sandbox network proxy when available. The proxy enforces |
| 175 | // the domain allowlist and returns 403 for blocked domains. |
| 176 | const sandboxProxy = await getSandboxProxyConfig() |
| 177 | |
| 178 | // Detect env var proxy (HTTP_PROXY / HTTPS_PROXY, respecting NO_PROXY). |
| 179 | // When set, configureGlobalAgents() has already installed a request |
| 180 | // interceptor that sets httpsAgent to an HttpsProxyAgent — the proxy |
no test coverage detected