Files
shockbot/utils/apiFetch.ts
T
Colin McDonnell 6f108237d4 Deployment protection bypass (#298)
* test preview bypass 2

Co-authored-by: Cursor <cursoragent@cursor.com>

* add apiFetch wrapper with Vercel bypass via query param + header

the template workflow was missing VERCEL_AUTOMATION_BYPASS_SECRET,
so all action API calls to preview deployments hit Vercel's
deployment protection without bypass. this also consolidates the
bypass logic into a single fetch wrapper that applies the secret
as both a query parameter (matching server-side forwarding) and
a header for belt-and-suspenders reliability.

Co-authored-by: Cursor <cursoragent@cursor.com>

* security hardening for Vercel bypass

- redact bypass token from webhook forwarder logs and response body
- remove dead x-preview-api-forward header
- refactor getAllSecrets() to use SENSITIVE_PATTERNS instead of hardcoded list
- enforce https:// on API_URL (localhost exempt for local dev)

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 20:01:48 +00:00

51 lines
1.5 KiB
TypeScript

import { getApiUrl } from "./apiUrl.ts";
import { log } from "./cli.ts";
type ApiFetchOptions = {
path: string;
method?: string | undefined;
headers?: Record<string, string> | undefined;
body?: string | undefined;
signal?: AbortSignal | undefined;
};
/**
* fetch wrapper for hitting the Pullfrog API with Vercel deployment protection bypass.
*
* adds the bypass secret as BOTH a query parameter and a header for maximum reliability.
* the server-side forwarding code uses query params, and the Vercel docs say both work,
* so we do both as belt-and-suspenders.
*
* the query param approach is the primary bypass mechanism (matches server-side forwarding).
* the header is added as a fallback.
*/
export async function apiFetch(options: ApiFetchOptions): Promise<Response> {
const apiUrl = getApiUrl();
const url = new URL(options.path, apiUrl);
const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
if (bypassSecret) {
url.searchParams.set("x-vercel-protection-bypass", bypassSecret);
}
const headers: Record<string, string> = {
...options.headers,
};
// also add as header for belt-and-suspenders
if (bypassSecret) {
headers["x-vercel-protection-bypass"] = bypassSecret;
}
log.debug(`api fetch: ${options.method ?? "GET"} ${url.pathname}`);
const init: RequestInit = {
method: options.method ?? "GET",
headers,
};
if (options.body) init.body = options.body;
if (options.signal) init.signal = options.signal;
return fetch(url.toString(), init);
}