From a0449b6d80e6974b2ec07156bace6486c1efbed6 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 16:43:06 -0700 Subject: [PATCH 1/2] fix(api-block): stop spoofing a browser fingerprint on outbound HTTP requests getDefaultHeaders() sent a Chrome User-Agent, a Referer pointing at Sim's own app, and mismatched Sec-Ch-Ua client hints on every API block request. Atlassian's Jira/Confluence Cloud REST API (and other browser-aware anti-CSRF/bot-defense layers) reject requests carrying a browser User-Agent with 403 "XSRF check failed", even with a valid Basic-auth header and X-Atlassian-Token: no-check set. Default headers now identify honestly as Sim, matching how other HTTP clients (curl, Postman, axios) behave. User-supplied headers still override any default. --- apps/sim/tools/http/request.test.ts | 65 ++++++++++------------------- apps/sim/tools/http/utils.ts | 15 ++++--- 2 files changed, 28 insertions(+), 52 deletions(-) diff --git a/apps/sim/tools/http/request.test.ts b/apps/sim/tools/http/request.test.ts index ad612560595..5192e38bf4e 100644 --- a/apps/sim/tools/http/request.test.ts +++ b/apps/sim/tools/http/request.test.ts @@ -119,28 +119,29 @@ describe('HTTP Request Tool', () => { expect(headers2['content-type']).toBe('text/plain') }) - it('should set dynamic Referer header correctly', async () => { - const originalWindow = global.window - Object.defineProperty(global, 'window', { - value: { - location: { - origin: 'https://sim.ai', - }, - }, - writable: true, + it('should not set a default Referer header', async () => { + tester.setup(mockHttpResponses.simple) + + await tester.execute({ + url: 'https://api.example.com', + method: 'GET', }) + const fetchCall = (global.fetch as any).mock.calls[0] + expect(fetchCall[1].headers.Referer).toBeUndefined() + }) + + it('should respect a user-provided Referer header', async () => { tester.setup(mockHttpResponses.simple) await tester.execute({ url: 'https://api.example.com', method: 'GET', + headers: [{ cells: { Key: 'Referer', Value: 'https://custom.example.com' } }], }) const fetchCall = (global.fetch as any).mock.calls[0] - expect(fetchCall[1].headers.Referer).toBe('https://sim.ai') - - global.window = originalWindow + expect(fetchCall[1].headers.Referer).toBe('https://custom.example.com') }) it('should set dynamic Host header correctly', async () => { @@ -193,16 +194,6 @@ describe('HTTP Request Tool', () => { it('should apply default and dynamic headers to requests', async () => { tester.setup(mockHttpResponses.simple) - const originalWindow = global.window - Object.defineProperty(global, 'window', { - value: { - location: { - origin: 'https://sim.ai', - }, - }, - writable: true, - }) - await tester.execute({ url: 'https://api.example.com/data', method: 'GET', @@ -212,15 +203,13 @@ describe('HTTP Request Tool', () => { const headers = fetchCall[1].headers expect(headers.Host).toBe('api.example.com') - expect(headers.Referer).toBe('https://sim.ai') - expect(headers['User-Agent']).toContain('Mozilla') + expect(headers.Referer).toBeUndefined() + expect(headers['User-Agent']).toBe('Sim/1.0 (+https://sim.ai)') expect(headers.Accept).toBe('*/*') expect(headers['Accept-Encoding']).toContain('gzip') expect(headers['Cache-Control']).toBe('no-cache') expect(headers.Connection).toBe('keep-alive') - expect(headers['Sec-Ch-Ua']).toContain('Chromium') - - global.window = originalWindow + expect(headers['Sec-Ch-Ua']).toBeUndefined() }) it('should handle successful GET requests', async () => { @@ -434,16 +423,6 @@ describe('HTTP Request Tool', () => { it('should apply all default headers correctly', async () => { tester.setup(mockHttpResponses.simple) - const originalWindow = global.window - Object.defineProperty(global, 'window', { - value: { - location: { - origin: 'https://sim.ai', - }, - }, - writable: true, - }) - await tester.execute({ url: 'https://api.example.com/data', method: 'GET', @@ -452,18 +431,16 @@ describe('HTTP Request Tool', () => { const fetchCall = (global.fetch as any).mock.calls[0] const headers = fetchCall[1].headers - expect(headers['User-Agent']).toMatch(/Mozilla\/5\.0.*Chrome.*Safari/) + expect(headers['User-Agent']).toBe('Sim/1.0 (+https://sim.ai)') expect(headers.Accept).toBe('*/*') expect(headers['Accept-Encoding']).toBe('gzip, deflate, br') expect(headers['Cache-Control']).toBe('no-cache') expect(headers.Connection).toBe('keep-alive') - expect(headers['Sec-Ch-Ua']).toMatch(/Chromium.*Not-A\.Brand/) - expect(headers['Sec-Ch-Ua-Mobile']).toBe('?0') - expect(headers['Sec-Ch-Ua-Platform']).toBe('"macOS"') - expect(headers.Referer).toBe('https://sim.ai') + expect(headers['Sec-Ch-Ua']).toBeUndefined() + expect(headers['Sec-Ch-Ua-Mobile']).toBeUndefined() + expect(headers['Sec-Ch-Ua-Platform']).toBeUndefined() + expect(headers.Referer).toBeUndefined() expect(headers.Host).toBe('api.example.com') - - global.window = originalWindow }) it('should allow overriding default headers', async () => { diff --git a/apps/sim/tools/http/utils.ts b/apps/sim/tools/http/utils.ts index 97c2a7ed383..71d57513d32 100644 --- a/apps/sim/tools/http/utils.ts +++ b/apps/sim/tools/http/utils.ts @@ -1,9 +1,13 @@ -import { getBaseUrl } from '@/lib/core/utils/urls' import { transformTable } from '@/tools/shared/table' import type { TableRow } from '@/tools/types' /** - * Creates a set of default headers used in HTTP requests + * Creates a set of default headers used in HTTP requests. + * + * Identifies as Sim rather than impersonating a browser — browser-fingerprint + * headers (Referer, Sec-Ch-Ua*) trip anti-CSRF/bot-defense heuristics on + * providers like Atlassian, which explicitly reject REST calls carrying a + * browser User-Agent. See https://support.atlassian.com/jira/kb/rest-api-calls-with-a-browser-user-agent-header-may-fail-csrf-checks/ * @param customHeaders Additional user-provided headers to include * @param url Target URL for the request (used for setting Host header) * @returns Record of HTTP headers @@ -13,16 +17,11 @@ export const getDefaultHeaders = ( url?: string ): Record => { const headers: Record = { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', + 'User-Agent': 'Sim/1.0 (+https://sim.ai)', Accept: '*/*', 'Accept-Encoding': 'gzip, deflate, br', 'Cache-Control': 'no-cache', Connection: 'keep-alive', - Referer: getBaseUrl(), - 'Sec-Ch-Ua': 'Chromium;v=91, Not-A.Brand;v=99', - 'Sec-Ch-Ua-Mobile': '?0', - 'Sec-Ch-Ua-Platform': '"macOS"', ...customHeaders, } From 326ae911e1696de1dd81b4eecb9bd8745fbaefd0 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 16:55:13 -0700 Subject: [PATCH 2/2] fix(testing): sync shared tool-tester mock headers with new defaults createMockHeaders in the shared @sim/testing tool-tester builder still hardcoded the old Chrome UA / Referer / Sec-Ch-Ua fallback values. It was unreachable in current tests but would silently diverge from production getDefaultHeaders() for any future test hitting its fallback path. --- apps/sim/tools/http/utils.ts | 7 ++++--- packages/testing/src/builders/tool-tester.builder.ts | 7 +------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/sim/tools/http/utils.ts b/apps/sim/tools/http/utils.ts index 71d57513d32..2a75e974a14 100644 --- a/apps/sim/tools/http/utils.ts +++ b/apps/sim/tools/http/utils.ts @@ -4,10 +4,11 @@ import type { TableRow } from '@/tools/types' /** * Creates a set of default headers used in HTTP requests. * - * Identifies as Sim rather than impersonating a browser — browser-fingerprint + * Identifies as Sim rather than impersonating a browser. Browser-fingerprint * headers (Referer, Sec-Ch-Ua*) trip anti-CSRF/bot-defense heuristics on - * providers like Atlassian, which explicitly reject REST calls carrying a - * browser User-Agent. See https://support.atlassian.com/jira/kb/rest-api-calls-with-a-browser-user-agent-header-may-fail-csrf-checks/ + * providers like Atlassian, which reject REST calls carrying a browser + * User-Agent regardless of X-Atlassian-Token. See + * https://support.atlassian.com/jira/kb/rest-api-calls-with-a-browser-user-agent-header-may-fail-csrf-checks/ * @param customHeaders Additional user-provided headers to include * @param url Target URL for the request (used for setting Host header) * @returns Record of HTTP headers diff --git a/packages/testing/src/builders/tool-tester.builder.ts b/packages/testing/src/builders/tool-tester.builder.ts index ba165e9683a..6eb4f432c63 100644 --- a/packages/testing/src/builders/tool-tester.builder.ts +++ b/packages/testing/src/builders/tool-tester.builder.ts @@ -43,16 +43,11 @@ export interface ToolResponse { */ const createMockHeaders = (customHeaders: Record = {}) => { return { - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', + 'User-Agent': 'Sim/1.0 (+https://sim.ai)', Accept: '*/*', 'Accept-Encoding': 'gzip, deflate, br', 'Cache-Control': 'no-cache', Connection: 'keep-alive', - Referer: 'https://www.sim.ai', - 'Sec-Ch-Ua': 'Chromium;v=91, Not-A.Brand;v=99', - 'Sec-Ch-Ua-Mobile': '?0', - 'Sec-Ch-Ua-Platform': '"macOS"', ...customHeaders, } }