Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/harness/src/decopilot/built-in-tools/inspect-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { ObjectStorageHooks } from "../../harness-deps";
import { createOutputPreview, estimateJsonTokens } from "./read-tool-output";
import { toStudioStorageUri } from "../studio-storage-uri";
import { LARGE_RESULT_TOKEN_THRESHOLD } from "./constants";
import { wafBypassHeaders } from "./waf-bypass";

const InspectPageInputSchema = z.object({
url: z.string().url().describe("The URL of the web page to inspect."),
Expand Down Expand Up @@ -67,6 +68,12 @@ function buildFunctionCode(
errors.push(err.message || String(err));
});

// Deco-zone targets get the WAF bypass header (see waf-bypass.ts).
const bypassHeaders = ${JSON.stringify(wafBypassHeaders(url))};
if (Object.keys(bypassHeaders).length) {
await page.setExtraHTTPHeaders(bypassHeaders);
}

await page.goto(${JSON.stringify(url)}, {
waitUntil: ${JSON.stringify(waitUntil)},
timeout: 30000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "../studio-storage-uri";
import type { PendingImage } from "./vm-tools/types";
import { BROWSERLESS_BASE_URL } from "./constants";
import { wafBypassHeaders } from "./waf-bypass";

const FILES_URL_PATTERN = /\/api\/[^/]+\/files\/([^?#]+)/;
const MAX_REFERENCE_IMAGE_BYTES = 20 * 1024 * 1024;
Expand Down Expand Up @@ -345,6 +346,11 @@ export function createPortableTakeScreenshotTool(
quality: JPEG_QUALITY,
},
viewport: DEFAULT_VIEWPORT,
// Deco-zone targets get the WAF bypass header (see waf-bypass.ts)
// so bot management doesn't block the render.
...(Object.keys(wafBypassHeaders(input.url)).length
? { setExtraHTTPHeaders: wafBypassHeaders(input.url) }
: {}),
}),
},
);
Expand Down
5 changes: 5 additions & 0 deletions packages/harness/src/decopilot/built-in-tools/scrape-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { ObjectStorageHooks } from "../../harness-deps";
import { createOutputPreview, estimateJsonTokens } from "./read-tool-output";
import { toStudioStorageUri } from "../studio-storage-uri";
import { LARGE_RESULT_TOKEN_THRESHOLD } from "./constants";
import { wafBypassHeaders } from "./waf-bypass";

const ScrapeUrlInputSchema = z.object({
url: z.string().url().describe("The URL of the web page to scrape."),
Expand Down Expand Up @@ -55,6 +56,10 @@ export function createScrapeUrlTool(
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url: input.url,
// Deco-zone targets get the WAF bypass header (see waf-bypass.ts).
...(Object.keys(wafBypassHeaders(input.url)).length
? { setExtraHTTPHeaders: wafBypassHeaders(input.url) }
: {}),
}),
},
);
Expand Down
34 changes: 34 additions & 0 deletions packages/harness/src/decopilot/built-in-tools/waf-bypass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* WAF bypass header for deco-owned hosts.
*
* The browser tools (take_screenshot / scrape_url / inspect_page) run on
* Browserless — headless Chrome from datacenter egress — which Cloudflare
* bot management on deco's own zones scores as a bot and blocks. That breaks
* agent QA/verification of deco previews (envs-*.decocdn.com) and
* storefronts. Both zones carry a shared-secret skip rule matching the
* `x-deco-probe-bypass` header; sending it gives Studio's tooling trusted
* passage without loosening bot protection for anyone else.
*
* SECURITY: the header is attached ONLY when the target host is a deco zone
* (*.decocdn.com / *.deco.site). Browser tools can target arbitrary URLs —
* sending the secret to third-party origins would leak it.
*/
export function wafBypassHeaders(targetUrl: string): Record<string, string> {
const token =
typeof process !== "undefined"
? process.env?.DECO_WAF_BYPASS_TOKEN
: undefined;
if (!token) return {};
try {
const host = new URL(targetUrl).hostname;
const decoHost =
host === "decocdn.com" ||
host === "deco.site" ||
host.endsWith(".decocdn.com") ||
host.endsWith(".deco.site");
if (decoHost) return { "x-deco-probe-bypass": token };
} catch {
// Malformed URL — the Browserless call will fail on its own terms.
}
return {};
}
Loading