6f108237d4
* 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>
51 lines
1.5 KiB
TypeScript
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);
|
|
}
|