import { env } from "cloudflare:workers";
import { assertPublicHttpsUrl } from "../git/relay";
import { assertResolvesPublic } from "../net/resolve-guard";
import { HttpError, badRequest } from "../errors";
const OUTBOUND_TIMEOUT_MS = 10_000;
const MAX_REQUEST_BYTES = 256 * 1024;
const MAX_RESPONSE_BYTES = 64 * 1024;
const HTTP_OK_MIN = 200;
const HTTP_OK_MAX = 300;
const HTTP_REDIRECT_MIN = 300;
const HTTP_REDIRECT_MAX = 400;
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
/** `permanent` failures (bad config, rejected URL, most 4xx) are not worth retrying. */
export type OutboundResult = {
status: number | null;
body: string;
error: string | null;
durationMs: number;
permanent?: boolean;
};
export function isAllowedLoopback(url: URL): boolean {
return env.INTEGRATIONS_ALLOW_LOOPBACK === "true" && LOOPBACK_HOSTS.has(url.hostname);
}
/**
* Every integration URL must be public https (no IP literals, internal suffixes or our own zone). Local
* development can opt into loopback sinks with INTEGRATIONS_ALLOW_LOOPBACK, which production never sets.
* Legacy repo webhooks may use plain http only when the operator sets WEBHOOKS_ALLOW_HTTP; the host rules
* still apply.
*/
export function assertOutboundUrl(
raw: string,
allowLoopback = env.INTEGRATIONS_ALLOW_LOOPBACK === "true",
allowHttp = false,
): URL {
if (allowLoopback) {
try {