fix(security): enforce HTTPS for HTTP hook URLs

Add TLS requirement for HTTP hook destinations:
- Warn when plain http:// URLs are used
- Reject remote http:// in production mode
- Allow http://localhost and http://127.0.0.1 for dev

Prevents secret exfiltration over unencrypted channels.
This commit is contained in:
YeonGyu-Kim
2026-04-02 15:01:15 +09:00
parent a637cca702
commit 5a2814980e
2 changed files with 204 additions and 1 deletions
@@ -1,9 +1,22 @@
import type { HookHttp } from "./types"
import type { CommandResult } from "../../shared/command-executor/execute-hook-command"
import { log } from "../../shared"
const DEFAULT_HTTP_HOOK_TIMEOUT_S = 30
const ALLOWED_SCHEMES = new Set(["http:", "https:"])
function isProduction(): boolean {
return process.env.NODE_ENV === "production"
}
function isLocalhost(url: URL): boolean {
return url.hostname === "localhost" || url.hostname === "127.0.0.1"
}
function isPlainHttp(url: URL): boolean {
return url.protocol === "http:"
}
export function interpolateEnvVars(
value: string,
allowedEnvVars: string[]
@@ -40,8 +53,9 @@ export async function executeHttpHook(
hook: HookHttp,
stdin: string
): Promise<CommandResult> {
let parsed: URL
try {
const parsed = new URL(hook.url)
parsed = new URL(hook.url)
if (!ALLOWED_SCHEMES.has(parsed.protocol)) {
return {
exitCode: 1,
@@ -52,6 +66,16 @@ export async function executeHttpHook(
return { exitCode: 1, stderr: `HTTP hook URL is invalid: ${hook.url}` }
}
if (isPlainHttp(parsed)) {
log("HTTP hook URL uses insecure protocol", { url: hook.url })
if (isProduction() && !isLocalhost(parsed)) {
return {
exitCode: 1,
stderr: "HTTP hook URL must use HTTPS in production. Plain HTTP is only allowed for localhost/127.0.0.1.",
}
}
}
const timeoutS = hook.timeout ?? DEFAULT_HTTP_HOOK_TIMEOUT_S
const headers = resolveHeaders(hook)