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>
This commit is contained in:
Colin McDonnell
2026-02-13 20:01:48 +00:00
committed by pullfrog[bot]
parent d5508d99bb
commit 6f108237d4
10 changed files with 187 additions and 139 deletions
+9 -14
View File
@@ -1,5 +1,5 @@
import type { AgentName, BashPermission, PushPermission, ToolPermission } from "../external.ts";
import { getApiUrl, getVercelBypassHeaders } from "./apiUrl.ts";
import { apiFetch } from "./apiFetch.ts";
import type { RepoContext } from "./github.ts";
export interface Mode {
@@ -52,24 +52,19 @@ export async function fetchRunContext(params: {
token: string;
repoContext: RepoContext;
}): Promise<RunContext> {
const apiUrl = getApiUrl();
const timeoutMs = 30000;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(
`${apiUrl}/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context`,
{
method: "GET",
headers: {
Authorization: `Bearer ${params.token}`,
"Content-Type": "application/json",
...getVercelBypassHeaders(),
},
signal: controller.signal,
}
);
const response = await apiFetch({
path: `/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context`,
headers: {
Authorization: `Bearer ${params.token}`,
"Content-Type": "application/json",
},
signal: controller.signal,
});
clearTimeout(timeoutId);