bypass Vercel deployment protection on preview API calls

action API calls to preview deployments were getting 401'd by Vercel's
deployment protection. add x-vercel-protection-bypass header to the 3
server-to-server fetch sites when VERCEL_AUTOMATION_BYPASS_SECRET is set.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Colin McDonnell
2026-02-13 15:25:32 +00:00
committed by pullfrog[bot]
parent d7759734f2
commit dc611c9f78
7 changed files with 1633 additions and 1597 deletions
+14 -4
View File
@@ -139418,7 +139418,14 @@ var Octokit2 = Octokit.plugin(requestLog, legacyRestEndpointMethods, paginateRes
// utils/apiUrl.ts
function getApiUrl() {
return process.env.API_URL || "https://pullfrog.com";
const url4 = process.env.API_URL || "https://pullfrog.com";
log.debug(`resolved API_URL: ${url4}`);
return url4;
}
function getVercelBypassHeaders() {
const secret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
if (!secret) return {};
return { "x-vercel-protection-bypass": secret };
}
// utils/retry.ts
@@ -139477,7 +139484,8 @@ async function acquireTokenViaOIDC(opts) {
method: "POST",
headers: {
Authorization: `Bearer ${oidcToken}`,
"Content-Type": "application/json"
"Content-Type": "application/json",
...getVercelBypassHeaders()
},
signal: controller.signal
};
@@ -144014,7 +144022,8 @@ function UploadFileTool(ctx) {
method: "POST",
headers: {
Authorization: `Bearer ${ctx.apiToken}`,
"Content-Type": "application/json"
"Content-Type": "application/json",
...getVercelBypassHeaders()
},
body: JSON.stringify({
filename,
@@ -146731,7 +146740,8 @@ async function fetchRunContext(params) {
method: "GET",
headers: {
Authorization: `Bearer ${params.token}`,
"Content-Type": "application/json"
"Content-Type": "application/json",
...getVercelBypassHeaders()
},
signal: controller.signal
}
+10 -2
View File
@@ -25682,7 +25682,14 @@ import { createSign } from "node:crypto";
// utils/apiUrl.ts
function getApiUrl() {
return process.env.API_URL || "https://pullfrog.com";
const url = process.env.API_URL || "https://pullfrog.com";
log.debug(`resolved API_URL: ${url}`);
return url;
}
function getVercelBypassHeaders() {
const secret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
if (!secret) return {};
return { "x-vercel-protection-bypass": secret };
}
// utils/retry.ts
@@ -25741,7 +25748,8 @@ async function acquireTokenViaOIDC(opts) {
method: "POST",
headers: {
Authorization: `Bearer ${oidcToken}`,
"Content-Type": "application/json"
"Content-Type": "application/json",
...getVercelBypassHeaders()
},
signal: controller.signal
};
+2 -1
View File
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
import * as path from "node:path";
import { type } from "arktype";
import { fileTypeFromBuffer } from "file-type";
import { getApiUrl } from "../utils/apiUrl.ts";
import { getApiUrl, getVercelBypassHeaders } from "../utils/apiUrl.ts";
import type { ToolContext } from "./server.ts";
import { execute, tool } from "./shared.ts";
@@ -32,6 +32,7 @@ export function UploadFileTool(ctx: ToolContext) {
headers: {
Authorization: `Bearer ${ctx.apiToken}`,
"Content-Type": "application/json",
...getVercelBypassHeaders(),
},
body: JSON.stringify({
filename,
+1587 -1587
View File
File diff suppressed because it is too large Load Diff
+16 -1
View File
@@ -1,3 +1,5 @@
import { log } from "./cli.ts";
/**
* resolve the Pullfrog API base URL.
*
@@ -5,5 +7,18 @@
* in local dev: API_URL=http://localhost:3000 (from .env).
*/
export function getApiUrl(): string {
return process.env.API_URL || "https://pullfrog.com";
const url = process.env.API_URL || "https://pullfrog.com";
log.debug(`resolved API_URL: ${url}`);
return url;
}
/**
* returns headers needed to bypass Vercel deployment protection on preview deployments.
* when VERCEL_AUTOMATION_BYPASS_SECRET is set (preview repos), includes the bypass header.
* otherwise returns an empty object (production / local dev).
*/
export function getVercelBypassHeaders(): Record<string, string> {
const secret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
if (!secret) return {};
return { "x-vercel-protection-bypass": secret };
}
+2 -1
View File
@@ -2,7 +2,7 @@ import { createSign } from "node:crypto";
import * as core from "@actions/core";
import { throttling } from "@octokit/plugin-throttling";
import { Octokit } from "@octokit/rest";
import { getApiUrl } from "./apiUrl.ts";
import { getApiUrl, getVercelBypassHeaders } from "./apiUrl.ts";
import { retry } from "./retry.ts";
export interface InstallationToken {
@@ -109,6 +109,7 @@ async function acquireTokenViaOIDC(opts?: AcquireTokenOptions): Promise<string>
headers: {
Authorization: `Bearer ${oidcToken}`,
"Content-Type": "application/json",
...getVercelBypassHeaders(),
},
signal: controller.signal,
};
+2 -1
View File
@@ -1,5 +1,5 @@
import type { AgentName, BashPermission, PushPermission, ToolPermission } from "../external.ts";
import { getApiUrl } from "./apiUrl.ts";
import { getApiUrl, getVercelBypassHeaders } from "./apiUrl.ts";
import type { RepoContext } from "./github.ts";
export interface Mode {
@@ -65,6 +65,7 @@ export async function fetchRunContext(params: {
headers: {
Authorization: `Bearer ${params.token}`,
"Content-Type": "application/json",
...getVercelBypassHeaders(),
},
signal: controller.signal,
}